@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,220 @@
1
+ import type { EditContext, IEditor, RectProps } from '@visactor/vtable-editors';
2
+ import { validateEntityFieldValue } from '../../entity-value.utils.js';
3
+ import { DAISY_COLORS, refocusTable, scheduleEditorSetup } from './global-overlay-editor.js';
4
+
5
+ type TableLike = { getCellOriginValue(col: number, row: number): unknown } | null;
6
+
7
+ /** TextFormatEditor 支持的 format 判别 */
8
+ export type TextFormatMode = 'email' | 'phone' | 'url';
9
+
10
+ /**
11
+ * 文本语义编辑器(url / email / phone)
12
+ *
13
+ * 与普通文本输入一致,但提交时按 format 语义校验(协议白名单 / 邮箱形状 / 电话形状),
14
+ * 校验失败显示红色错误提示并拒绝提交。
15
+ */
16
+ export class TextFormatEditor implements IEditor<unknown> {
17
+ #value: string | null = null;
18
+ #originalValue: string | null = null;
19
+ #input: HTMLInputElement | null = null;
20
+ #tooltip: HTMLDivElement | null = null;
21
+ #container: HTMLElement | null = null;
22
+ #endEdit: (() => void) | null = null;
23
+ #outsideHandler: ((e: MouseEvent) => void) | null = null;
24
+ readonly #mode: TextFormatMode;
25
+ readonly #schemes: readonly string[] | undefined;
26
+
27
+ /** VTable 编辑器类型标识(注册到 VTable 的编辑器名) */
28
+ readonly editorType = 'text-format-editor';
29
+
30
+ /**
31
+ * 创建文本语义编辑器
32
+ *
33
+ * @param mode - 校验模式
34
+ * @param schemes - url 模式的协议白名单(不传则不限制协议)
35
+ */
36
+ constructor(mode: TextFormatMode, schemes?: readonly string[]) {
37
+ this.#mode = mode;
38
+ this.#schemes = schemes;
39
+ }
40
+
41
+ /** 创建并显示输入框与错误提示浮层,绑定校验与提交逻辑 */
42
+ onStart(ctx: EditContext<unknown>): void {
43
+ const container = ctx.container as HTMLElement;
44
+ this.#container = container;
45
+ this.#endEdit = () => {
46
+ refocusTable(container);
47
+ ctx.endEdit();
48
+ };
49
+ const origin = (ctx.table as TableLike)?.getCellOriginValue(ctx.col, ctx.row) ?? ctx.value;
50
+ this.#value = origin != null && origin !== '' ? String(origin) : null;
51
+ this.#originalValue = this.#value;
52
+
53
+ const rect = ctx.referencePosition.rect;
54
+ const cRect = container.getBoundingClientRect();
55
+ const absLeft = cRect.left + rect.left;
56
+ const absTop = cRect.top + rect.top;
57
+ const w = Math.max(60, rect.width ?? 0);
58
+ const h = Math.max(24, rect.height ?? 0);
59
+
60
+ const input = document.createElement('input');
61
+ input.type = 'text';
62
+ input.inputMode =
63
+ this.#mode === 'phone' ? 'tel'
64
+ : this.#mode === 'email' ? 'email'
65
+ : 'url';
66
+ input.value = this.#value ?? '';
67
+ input.style.cssText = [
68
+ 'position:fixed',
69
+ 'z-index:9999',
70
+ 'padding:4px',
71
+ 'box-sizing:border-box',
72
+ `background:${DAISY_COLORS.bg}`,
73
+ `color:${DAISY_COLORS.text}`,
74
+ 'border-radius:0',
75
+ `border:2px solid ${DAISY_COLORS.primary}`,
76
+ 'outline:none',
77
+ 'font-size:13px',
78
+ `left:${absLeft}px`,
79
+ `top:${absTop}px`,
80
+ `width:${w}px`,
81
+ `height:${h}px`
82
+ ].join(';');
83
+
84
+ const tooltip = document.createElement('div');
85
+ tooltip.style.cssText = [
86
+ 'position:fixed',
87
+ 'z-index:10000',
88
+ 'padding:4px 8px',
89
+ 'font-size:11px',
90
+ `color:${DAISY_COLORS.errorContent}`,
91
+ `background:${DAISY_COLORS.error}`,
92
+ 'border-radius:4px',
93
+ 'box-shadow:0 2px 8px rgba(0,0,0,.15)',
94
+ 'white-space:nowrap',
95
+ 'display:none',
96
+ 'pointer-events:none',
97
+ `left:${absLeft}px`,
98
+ `top:${absTop + h + 2}px`
99
+ ].join(';');
100
+
101
+ const setError = (show: boolean, msg?: string) => {
102
+ if (show && msg) {
103
+ tooltip.textContent = msg;
104
+ tooltip.style.display = 'block';
105
+ input.style.borderColor = DAISY_COLORS.error;
106
+ } else {
107
+ tooltip.textContent = '';
108
+ tooltip.style.display = 'none';
109
+ input.style.borderColor = DAISY_COLORS.primary;
110
+ }
111
+ };
112
+
113
+ let commitError = '';
114
+ const commit = (): boolean => {
115
+ if (input.value.trim() === '') {
116
+ this.#value = null;
117
+ return true;
118
+ }
119
+ const error = validateEntityFieldValue(
120
+ {
121
+ field: 'value',
122
+ displayName: '',
123
+ type: 'string',
124
+ format: this.#mode === 'url' ? { kind: 'url', schemes: this.#schemes } : { kind: this.#mode }
125
+ },
126
+ input.value
127
+ );
128
+ if (error) {
129
+ commitError = error.message.trim();
130
+ return false;
131
+ }
132
+ this.#value = input.value;
133
+ return true;
134
+ };
135
+
136
+ input.addEventListener('keydown', e => {
137
+ if (e.key === 'Enter' || e.key === 'Tab') {
138
+ e.preventDefault();
139
+ e.stopPropagation();
140
+ if (commit()) {
141
+ this.#endEdit?.();
142
+ } else {
143
+ setError(true, commitError);
144
+ }
145
+ } else if (e.key === 'Escape') {
146
+ e.preventDefault();
147
+ e.stopPropagation();
148
+ this.#value = this.#originalValue;
149
+ this.#endEdit?.();
150
+ }
151
+ });
152
+
153
+ document.body.appendChild(input);
154
+ document.body.appendChild(tooltip);
155
+ this.#input = input;
156
+ this.#tooltip = tooltip;
157
+
158
+ this.#outsideHandler = (e: MouseEvent) => {
159
+ const target = e.target as Node;
160
+ if (target !== input && !tooltip.contains(target)) {
161
+ if (!commit()) {
162
+ this.#value = this.#originalValue;
163
+ }
164
+ this.#cleanup();
165
+ this.#endEdit?.();
166
+ }
167
+ };
168
+ scheduleEditorSetup(() => {
169
+ if (!this.#outsideHandler) return;
170
+ document.addEventListener('mousedown', this.#outsideHandler, { capture: true });
171
+ input.focus();
172
+ input.select();
173
+ });
174
+ }
175
+
176
+ /** 返回当前文本值,空值为 null */
177
+ getValue(): string | null {
178
+ return this.#value;
179
+ }
180
+
181
+ /** 移除输入框与提示浮层,解绑全局点击事件 */
182
+ onEnd(): void {
183
+ this.#cleanup();
184
+ }
185
+
186
+ /** 判断目标元素是否属于本编辑器(输入框或错误提示浮层) */
187
+ isEditorElement(el: HTMLElement | EventTarget): boolean {
188
+ const node = el as Node;
189
+ return node === this.#input || this.#tooltip?.contains(node) === true;
190
+ }
191
+
192
+ /** 按新的单元格矩形重新定位输入框与错误提示浮层 */
193
+ adjustPosition(rect: RectProps): void {
194
+ const input = this.#input;
195
+ const container = this.#container;
196
+ if (!input || !container) return;
197
+ const cRect = container.getBoundingClientRect();
198
+ const absLeft = cRect.left + rect.left;
199
+ const absTop = cRect.top + rect.top;
200
+ const h = Math.max(24, rect.height ?? 0);
201
+ input.style.left = `${absLeft}px`;
202
+ input.style.top = `${absTop}px`;
203
+ if (this.#tooltip) {
204
+ this.#tooltip.style.left = `${absLeft}px`;
205
+ this.#tooltip.style.top = `${absTop + h + 2}px`;
206
+ }
207
+ }
208
+
209
+ #cleanup(): void {
210
+ if (this.#outsideHandler) {
211
+ document.removeEventListener('mousedown', this.#outsideHandler, { capture: true });
212
+ this.#outsideHandler = null;
213
+ }
214
+ this.#input?.remove();
215
+ this.#input = null;
216
+ this.#tooltip?.remove();
217
+ this.#tooltip = null;
218
+ this.#container = null;
219
+ }
220
+ }
@@ -0,0 +1,198 @@
1
+ import type { EditContext, IEditor, RectProps } from '@visactor/vtable-editors';
2
+ import { UUID_RE } from '../../entity-value.utils.js';
3
+ import { DAISY_COLORS, refocusTable, scheduleEditorSetup } from './global-overlay-editor.js';
4
+
5
+ type TableLike = { getCellOriginValue(col: number, row: number): unknown } | null;
6
+
7
+ /** UUID 格式正则(re-export,定义见 entity-value.utils) */
8
+ export { UUID_RE };
9
+
10
+ /**
11
+ * UUID 文本编辑器
12
+ *
13
+ * 在单元格上方弹出固定定位输入框,提交时校验 UUID 格式(非法则红色提示)。
14
+ * Enter / Tab 提交;Escape 恢复原值;点击外部提交,非法输入恢复原值。
15
+ */
16
+ export class UuidEditor implements IEditor<unknown> {
17
+ #value: string | null = null;
18
+ #originalValue: string | null = null;
19
+ #input: HTMLInputElement | null = null;
20
+ #tooltip: HTMLDivElement | null = null;
21
+ #container: HTMLElement | null = null;
22
+ #endEdit: (() => void) | null = null;
23
+ #outsideHandler: ((e: MouseEvent) => void) | null = null;
24
+
25
+ /** VTable 编辑器类型标识(注册到 VTable 的编辑器名) */
26
+ readonly editorType = 'uuid-editor';
27
+
28
+ /** 创建并显示输入框与错误提示浮层,绑定格式校验与提交逻辑 */
29
+ onStart(ctx: EditContext<unknown>): void {
30
+ const container = ctx.container as HTMLElement;
31
+ this.#container = container;
32
+ this.#endEdit = () => {
33
+ refocusTable(container);
34
+ ctx.endEdit();
35
+ };
36
+ const origin = (ctx.table as TableLike)?.getCellOriginValue(ctx.col, ctx.row) ?? ctx.value;
37
+ this.#value = origin != null && origin !== '' ? String(origin).trim() : null;
38
+ this.#originalValue = this.#value;
39
+
40
+ const rect = ctx.referencePosition.rect;
41
+ const cRect = container.getBoundingClientRect();
42
+ const absLeft = cRect.left + rect.left;
43
+ const absTop = cRect.top + rect.top;
44
+ const w = Math.max(80, rect.width ?? 0);
45
+ const h = Math.max(24, rect.height ?? 0);
46
+
47
+ const input = document.createElement('input');
48
+ input.type = 'text';
49
+ input.value = this.#value ?? '';
50
+ input.placeholder = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
51
+ input.style.cssText = [
52
+ 'position:fixed',
53
+ 'z-index:9999',
54
+ 'padding:4px',
55
+ 'box-sizing:border-box',
56
+ `background:${DAISY_COLORS.bg}`,
57
+ `color:${DAISY_COLORS.text}`,
58
+ 'border-radius:0',
59
+ `border:2px solid ${DAISY_COLORS.primary}`,
60
+ 'outline:none',
61
+ 'font-size:13px',
62
+ `left:${absLeft}px`,
63
+ `top:${absTop}px`,
64
+ `width:${w}px`,
65
+ `height:${h}px`
66
+ ].join(';');
67
+
68
+ const tooltip = document.createElement('div');
69
+ tooltip.style.cssText = [
70
+ 'position:fixed',
71
+ 'z-index:10000',
72
+ 'padding:4px 8px',
73
+ 'font-size:11px',
74
+ `color:${DAISY_COLORS.errorContent}`,
75
+ `background:${DAISY_COLORS.error}`,
76
+ 'border-radius:4px',
77
+ 'box-shadow:0 2px 8px rgba(0,0,0,.15)',
78
+ 'white-space:nowrap',
79
+ 'display:none',
80
+ 'pointer-events:none',
81
+ `left:${absLeft}px`,
82
+ `top:${absTop + h + 2}px`
83
+ ].join(';');
84
+
85
+ const setError = (msg: string) => {
86
+ if (msg) {
87
+ tooltip.textContent = msg;
88
+ tooltip.style.display = 'block';
89
+ input.style.borderColor = DAISY_COLORS.error;
90
+ } else {
91
+ tooltip.textContent = '';
92
+ tooltip.style.display = 'none';
93
+ input.style.borderColor = DAISY_COLORS.primary;
94
+ }
95
+ };
96
+
97
+ input.addEventListener('input', () => {
98
+ const v = input.value.trim();
99
+ if (v === '' || UUID_RE.test(v)) {
100
+ setError('');
101
+ } else {
102
+ setError('请输入合法的 UUID 格式');
103
+ }
104
+ });
105
+
106
+ input.addEventListener('keydown', e => {
107
+ if (e.key === 'Enter' || e.key === 'Tab') {
108
+ e.preventDefault();
109
+ e.stopPropagation();
110
+ const v = input.value.trim();
111
+ if (v === '') {
112
+ this.#value = null;
113
+ this.#endEdit?.();
114
+ } else if (UUID_RE.test(v)) {
115
+ this.#value = v.toLowerCase();
116
+ this.#endEdit?.();
117
+ } else {
118
+ setError('请输入合法的 UUID 格式 (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)');
119
+ }
120
+ } else if (e.key === 'Escape') {
121
+ e.preventDefault();
122
+ e.stopPropagation();
123
+ this.#value = this.#originalValue;
124
+ this.#endEdit?.();
125
+ }
126
+ });
127
+
128
+ document.body.appendChild(input);
129
+ document.body.appendChild(tooltip);
130
+ this.#input = input;
131
+ this.#tooltip = tooltip;
132
+
133
+ this.#outsideHandler = (e: MouseEvent) => {
134
+ const target = e.target as Node;
135
+ if (target !== input && !tooltip.contains(target)) {
136
+ const v = input.value.trim();
137
+ if (v === '' || UUID_RE.test(v)) {
138
+ this.#value = v === '' ? null : v.toLowerCase();
139
+ } else {
140
+ this.#value = this.#originalValue;
141
+ }
142
+ this.#cleanup();
143
+ this.#endEdit?.();
144
+ }
145
+ };
146
+ scheduleEditorSetup(() => {
147
+ if (!this.#outsideHandler) return;
148
+ document.addEventListener('mousedown', this.#outsideHandler, { capture: true });
149
+ input.focus();
150
+ input.select();
151
+ });
152
+ }
153
+
154
+ /** 返回当前 UUID 值,空值为 null */
155
+ getValue(): string | null {
156
+ return this.#value;
157
+ }
158
+
159
+ /** 移除输入框与提示浮层,解绑全局点击事件 */
160
+ onEnd(): void {
161
+ this.#cleanup();
162
+ }
163
+
164
+ /** 判断目标元素是否属于本编辑器(输入框或错误提示浮层) */
165
+ isEditorElement(el: HTMLElement | EventTarget): boolean {
166
+ const node = el as Node;
167
+ return node === this.#input || this.#tooltip?.contains(node) === true;
168
+ }
169
+
170
+ /** 按新的单元格矩形重新定位输入框与错误提示浮层 */
171
+ adjustPosition(rect: RectProps): void {
172
+ const input = this.#input;
173
+ const container = this.#container;
174
+ if (!input || !container) return;
175
+ const cRect = container.getBoundingClientRect();
176
+ const absLeft = cRect.left + rect.left;
177
+ const absTop = cRect.top + rect.top;
178
+ const h = Math.max(24, rect.height ?? 0);
179
+ input.style.left = `${absLeft}px`;
180
+ input.style.top = `${absTop}px`;
181
+ if (this.#tooltip) {
182
+ this.#tooltip.style.left = `${absLeft}px`;
183
+ this.#tooltip.style.top = `${absTop + h + 2}px`;
184
+ }
185
+ }
186
+
187
+ #cleanup(): void {
188
+ if (this.#outsideHandler) {
189
+ document.removeEventListener('mousedown', this.#outsideHandler, { capture: true });
190
+ this.#outsideHandler = null;
191
+ }
192
+ this.#input?.remove();
193
+ this.#input = null;
194
+ this.#tooltip?.remove();
195
+ this.#tooltip = null;
196
+ this.#container = null;
197
+ }
198
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * 实体表格模块
3
+ *
4
+ * 提供 VTable 驱动的实体表格基础设施:接口与配置、表格工厂/主题/操作、
5
+ * 自定义编辑器与列构建工具的统一出口。
6
+ *
7
+ * @module entity-table
8
+ */
9
+ // 接口与配置
10
+ export type {
11
+ BatchChangeItem,
12
+ CellChangeEvent,
13
+ EntityTableConfig,
14
+ EntityTableRecord,
15
+ PendingWrite,
16
+ RowDeleteEvent,
17
+ RowReorderEvent
18
+ } from './interfaces.js';
19
+
20
+ // VTable 基础设施
21
+ export {
22
+ TableClipboardManager,
23
+ applyClipboard,
24
+ applySystemText,
25
+ collectDeleteWrites,
26
+ formatAsTsv,
27
+ snapshotSelection,
28
+ type ClipboardCell,
29
+ type ClipboardContent
30
+ } from './vtable/table-clipboard.js';
31
+ export {
32
+ _resetEditorRegistry,
33
+ buildTableOptions,
34
+ createListTable,
35
+ ensureBaseEditorRegistered,
36
+ registerEditor
37
+ } from './vtable/table-factory.js';
38
+ export { handleTableKeydown, type KeyboardHandlerContext } from './vtable/table-keyboard.js';
39
+ export {
40
+ collectReorderedIds,
41
+ patchDragIconForReadonlyRows,
42
+ syncHeaderSortIcon,
43
+ updateTableRecords,
44
+ type HeaderSortClick
45
+ } from './vtable/table-operations.js';
46
+ export { createTheme, getCSSVariables, getDefaultColors, type CSSVariables } from './vtable/table-theme.js';
47
+ export { CellTooltipManager, computeTooltipPosition } from './vtable/table-tooltip.js';
48
+ export {
49
+ ROW_SERIES_COL_OFFSET,
50
+ changeCellValue,
51
+ completeTableEdit,
52
+ getTableContainer,
53
+ isTableEditing,
54
+ setCellSwitchState
55
+ } from './vtable/vtable-compat.js';
56
+
57
+ // 自定义编辑器
58
+ export { ColorEditor } from './editors/color-editor.js';
59
+ export { DateEditor } from './editors/date-editor.js';
60
+ export { EnumEditor, type EnumItem } from './editors/enum-editor.js';
61
+ export {
62
+ DAISY_COLORS,
63
+ GlobalOverlayEditor,
64
+ positionOverlayPanel,
65
+ scheduleEditorSetup
66
+ } from './editors/global-overlay-editor.js';
67
+ export { IconListEditor, type IconLabeledItem } from './editors/icon-list-editor.js';
68
+ export { JsonEditor } from './editors/json-editor.js';
69
+ export { KeyValueEditor } from './editors/key-value-editor.js';
70
+ export { lucideToSvgElement, type IconData } from './editors/lucide-svg.js';
71
+ export { MultiSelectEditor, type MultiSelectOption } from './editors/multiselect-editor.js';
72
+ export { NumberEditor, type NumberEditorOptions } from './editors/number-editor.js';
73
+ export { RelationEditor } from './editors/relation-editor.js';
74
+ export { SafeListEditor } from './editors/safe-list-editor.js';
75
+ export { TagsEditor, type TagsMode } from './editors/tags-editor.js';
76
+ export { TextFormatEditor, type TextFormatMode } from './editors/text-format-editor.js';
77
+
78
+ // 列工具
79
+ export {
80
+ DELETE_COLOR,
81
+ actionsColumn,
82
+ buildPropertyColumn,
83
+ disabledEditorForReadonly,
84
+ formatDateValue,
85
+ getCellRecord,
86
+ isReadonly,
87
+ isReadonlyOrAddRow,
88
+ makeDeleteIcons,
89
+ makeEditorForReadonly,
90
+ parsePropertyColumnValue,
91
+ switchDisabledForReadonly,
92
+ type KVSchemaEntry,
93
+ type PropertyColumnConfig,
94
+ type PropertyTypeString,
95
+ type SwitchDisableCallback
96
+ } from './columns/column-utils.js';
@@ -0,0 +1,72 @@
1
+ /**
2
+ * 通用实体表记录基础接口
3
+ *
4
+ * 业务层扩展此接口添加领域字段。
5
+ * `_` 前缀字段为内部元数据,不会被剪贴板/编辑操作处理。
6
+ */
7
+ export interface EntityTableRecord {
8
+ /** 记录字段值(字段名 → 值) */
9
+ [key: string]: unknown;
10
+ /** 只读行(不可编辑、不可拖拽、不可删除) */
11
+ _readonly?: boolean;
12
+ /** 新增模板行 */
13
+ _isAddRow?: boolean;
14
+ /** 树结构子节点 */
15
+ children?: EntityTableRecord[];
16
+ /** VTable 树节点展开状态 */
17
+ hierarchyState?: 'expand' | 'collapse';
18
+ }
19
+
20
+ /** 单元格变更事件 */
21
+ export interface CellChangeEvent {
22
+ /** 变更所在列索引 */
23
+ col: number;
24
+ /** 变更所在行索引 */
25
+ row: number;
26
+ /** 变更的字段名 */
27
+ field: string;
28
+ /** 变更后的值 */
29
+ value: unknown;
30
+ /** 变更所在行记录 */
31
+ record: EntityTableRecord;
32
+ }
33
+
34
+ /** 批量变更项 */
35
+ export interface BatchChangeItem {
36
+ /** 目标记录 ID */
37
+ recordId: string;
38
+ /** 字段名到新值的映射 */
39
+ changes: Record<string, unknown>;
40
+ }
41
+
42
+ /** 行删除事件 */
43
+ export interface RowDeleteEvent {
44
+ /** 被删除的行记录 */
45
+ record: EntityTableRecord;
46
+ }
47
+
48
+ /** 行重排事件 */
49
+ export interface RowReorderEvent {
50
+ /** 重排后的行 ID 顺序列表 */
51
+ orderedIds: string[];
52
+ }
53
+
54
+ /** 剪贴板/批量写入的待提交操作 */
55
+ export interface PendingWrite {
56
+ /** 目标列索引 */
57
+ col: number;
58
+ /** 目标行索引 */
59
+ row: number;
60
+ /** 待写入的值 */
61
+ value: unknown;
62
+ /** 目标列字段名 */
63
+ field: string;
64
+ /** 目标行记录快照(写入时刻) */
65
+ record: EntityTableRecord;
66
+ }
67
+
68
+ /** 表格配置 */
69
+ export interface EntityTableConfig {
70
+ /** 单元格错误 tooltip 延迟(毫秒),默认 800 */
71
+ tooltipDelay?: number;
72
+ }