@flowgram-vue/type-editor 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +22 -0
  2. package/dist/index.cjs +0 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +0 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +63 -0
  8. package/src/components/index.ts +8 -0
  9. package/src/components/type-editor/columns/index.ts +404 -0
  10. package/src/components/type-editor/common.ts +44 -0
  11. package/src/components/type-editor/formatter/index.ts +44 -0
  12. package/src/components/type-editor/hooks/active-pos.ts +20 -0
  13. package/src/components/type-editor/hooks/disabled.ts +22 -0
  14. package/src/components/type-editor/hooks/formatter-value.ts +53 -0
  15. package/src/components/type-editor/hooks/index.ts +8 -0
  16. package/src/components/type-editor/index.ts +9 -0
  17. package/src/components/type-editor/mode/declare-assign.ts +102 -0
  18. package/src/components/type-editor/mode/index.ts +15 -0
  19. package/src/components/type-editor/mode/type-definition.ts +46 -0
  20. package/src/components/type-editor/table.vue +293 -0
  21. package/src/components/type-editor/type-editor.vue +107 -0
  22. package/src/components/type-editor/type.ts +142 -0
  23. package/src/components/type-editor/utils.ts +173 -0
  24. package/src/components/type-selector/index.ts +51 -0
  25. package/src/components/type-selector/type-selector.vue +100 -0
  26. package/src/contexts/index.ts +106 -0
  27. package/src/env.d.ts +10 -0
  28. package/src/index.ts +15 -0
  29. package/src/json-schema-exports.ts +18 -0
  30. package/src/preset/index.ts +6 -0
  31. package/src/preset/object-type-editor.vue +91 -0
  32. package/src/services/clipboard-service.ts +93 -0
  33. package/src/services/index.ts +11 -0
  34. package/src/services/shortcut-service.ts +9 -0
  35. package/src/services/type-editor-service.ts +396 -0
  36. package/src/services/type-operation-service.ts +99 -0
  37. package/src/services/type-registry-manager.ts +14 -0
  38. package/src/services/utils.ts +28 -0
  39. package/src/styles.css +235 -0
  40. package/src/type-registry/array.ts +18 -0
  41. package/src/type-registry/boolean.ts +30 -0
  42. package/src/type-registry/index.ts +22 -0
  43. package/src/type-registry/integer.ts +24 -0
  44. package/src/type-registry/number.ts +23 -0
  45. package/src/type-registry/object.ts +13 -0
  46. package/src/type-registry/string.ts +21 -0
  47. package/src/types/index.ts +7 -0
  48. package/src/types/registry.ts +52 -0
  49. package/src/types/type-editor.ts +150 -0
  50. package/src/utils/index.ts +6 -0
  51. package/src/utils/monitor-data/index.ts +7 -0
  52. package/src/utils/monitor-data/monitor-data.ts +43 -0
  53. package/src/utils/monitor-data/use-monitor-data.ts +29 -0
  54. package/src/utils/registry-adapter.ts +83 -0
  55. package/src/utils/toast.ts +16 -0
@@ -0,0 +1,396 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { inject, injectable } from 'inversify';
7
+ import { Emitter } from '@flowgram-vue/utils';
8
+ import { IJsonSchema } from '@flowgram-vue/json-schema';
9
+
10
+ import { MonitorData } from '../utils';
11
+ import {
12
+ type TypeEditorColumnType,
13
+ type TypeEditorRowData,
14
+ TypeEditorDropInfo,
15
+ TypeEditorColumnConfig,
16
+ TypeEditorPos,
17
+ TypeEditorColumnViewConfig,
18
+ ShortcutContext,
19
+ } from '../types';
20
+ import { TypeRegistryCreatorsAdapter } from '../contexts';
21
+ import { ROOT_FIELD_ID } from '../components/type-editor/common';
22
+ import { TypeEditorRegistryManager } from './type-registry-manager';
23
+ import { ClipboardService } from './clipboard-service';
24
+
25
+ @injectable()
26
+ export class TypeEditorService<TypeSchema extends Partial<IJsonSchema>> {
27
+ private _configs: Map<TypeEditorColumnType, TypeEditorColumnConfig<TypeSchema>> = new Map();
28
+
29
+ private _activePos: TypeEditorPos = { x: -1, y: -1 };
30
+
31
+ // -1 为 header
32
+ private _dropInfo: TypeEditorDropInfo = {
33
+ rowDataId: '',
34
+ indent: -1,
35
+ index: -2,
36
+ };
37
+
38
+ public errorMsgs = new MonitorData<{ pos: TypeEditorPos; msg?: string }[]>([]);
39
+
40
+ public editValue: unknown;
41
+
42
+ public onChange: (
43
+ typeSchema?: TypeSchema,
44
+ ctx?: {
45
+ storeState?: boolean;
46
+ }
47
+ ) => void;
48
+
49
+ public onRemoveEmptyLine: (id: string) => void;
50
+
51
+ public onGlobalAdd: ((id: string) => void) | undefined;
52
+
53
+ public typeRegistryCreators?: TypeRegistryCreatorsAdapter<TypeSchema>[];
54
+
55
+ private dataSource: TypeEditorRowData<TypeSchema>[] = [];
56
+
57
+ public dataSourceMap: Record<string, TypeEditorRowData<TypeSchema>> = {};
58
+
59
+ public dataSourceTouchedMap: Record<string, boolean> = {};
60
+
61
+ public blink = new MonitorData(false);
62
+
63
+ public columnViewConfig: TypeEditorColumnViewConfig[] = [];
64
+
65
+ public onActivePosChange = new Emitter<TypeEditorPos>();
66
+
67
+ public onDropInfoChange = new Emitter<TypeEditorDropInfo>();
68
+
69
+ @inject(ClipboardService)
70
+ public clipboard: ClipboardService;
71
+
72
+ @inject(TypeEditorRegistryManager)
73
+ public typeDefinition: TypeEditorRegistryManager<TypeSchema>;
74
+
75
+ public rootTypeSchema: TypeSchema;
76
+
77
+ public setErrorMsg = (pos: TypeEditorPos, msg?: string) => {
78
+ const newMsgs = [...this.errorMsgs.data];
79
+ const item = newMsgs.find((v) => v.pos.x === pos.x && v.pos.y === pos.y);
80
+ if (item) {
81
+ item.msg = msg;
82
+ } else {
83
+ newMsgs.push({ pos, msg });
84
+ }
85
+ this.errorMsgs.update(newMsgs);
86
+ };
87
+
88
+ public refreshErrorMsgAfterRemove = (index: number) => {
89
+ // 删除被删去那行的 errorMsgs
90
+ const newMsgs = this.errorMsgs.data.filter((msg) => msg.pos.y !== index);
91
+
92
+ newMsgs.forEach((msg) => {
93
+ if (msg.pos.y > index) {
94
+ msg.pos.y = msg.pos.y - 1;
95
+ }
96
+ });
97
+
98
+ this.errorMsgs.update(newMsgs);
99
+ };
100
+
101
+ public checkActivePosError = () => {
102
+ const pos = this.activePos;
103
+
104
+ return !!this.errorMsgs.data.find((v) => v.pos.x === pos.x && v.pos.y === pos.y && v.msg);
105
+ };
106
+
107
+ public setEditValue = (val: unknown) => {
108
+ this.editValue = val;
109
+ };
110
+
111
+ public registerConfigs(
112
+ config: TypeEditorColumnConfig<TypeSchema> | TypeEditorColumnConfig<TypeSchema>[]
113
+ ): void {
114
+ const configs = Array.isArray(config) ? config : [config];
115
+
116
+ configs.map((c) => {
117
+ this._configs.set(c.type, c);
118
+ });
119
+ }
120
+
121
+ public addConfigProps(
122
+ type: TypeEditorColumnType,
123
+ config: Partial<Omit<TypeEditorColumnConfig<TypeSchema>, 'type'>>
124
+ ): void {
125
+ const configByType = this.getConfigByType(type);
126
+
127
+ if (!configByType) {
128
+ return;
129
+ }
130
+
131
+ const newConfig = {
132
+ ...configByType,
133
+ ...config,
134
+ };
135
+
136
+ this._configs.set(type, newConfig);
137
+ }
138
+
139
+ public getConfigs = (): TypeEditorColumnConfig<TypeSchema>[] =>
140
+ Array.from(this._configs.values());
141
+
142
+ public getConfigByType(
143
+ type: TypeEditorColumnType
144
+ ): TypeEditorColumnConfig<TypeSchema> | undefined {
145
+ return this._configs.get(type);
146
+ }
147
+
148
+ public triggerShortcutEvent(
149
+ event: 'enter' | 'tab' | 'left' | 'right' | 'up' | 'down' | 'copy' | 'paste' | 'delete'
150
+ ): void {
151
+ const column = this.columnViewConfig[this.activePos.x];
152
+
153
+ const columnConfig = this.getConfigByType(column?.type);
154
+ if (!columnConfig) {
155
+ return;
156
+ }
157
+
158
+ const ctx: ShortcutContext<TypeSchema> = {
159
+ value: this.editValue,
160
+ rowData: this.dataSource[this.activePos.y],
161
+ onRemoveEmptyLine: this.onRemoveEmptyLine,
162
+ onChange: this.onChange,
163
+ typeEditor: this,
164
+ typeDefinitionService: this.typeDefinition,
165
+ };
166
+
167
+ switch (event) {
168
+ case 'enter': {
169
+ columnConfig.shortcuts?.onEnter?.(ctx);
170
+ return;
171
+ }
172
+ case 'tab': {
173
+ columnConfig.shortcuts?.onTab?.(ctx);
174
+ return;
175
+ }
176
+ case 'down': {
177
+ columnConfig.shortcuts?.onDown?.(ctx);
178
+ return;
179
+ }
180
+ case 'up': {
181
+ columnConfig.shortcuts?.onUp?.(ctx);
182
+ return;
183
+ }
184
+ case 'left': {
185
+ columnConfig.shortcuts?.onLeft?.(ctx);
186
+ return;
187
+ }
188
+ case 'right': {
189
+ columnConfig.shortcuts?.onRight?.(ctx);
190
+ return;
191
+ }
192
+ case 'copy': {
193
+ columnConfig.shortcuts?.onCopy?.(ctx);
194
+ return;
195
+ }
196
+ case 'paste': {
197
+ columnConfig.shortcuts?.onPaste?.(ctx);
198
+ return;
199
+ }
200
+ case 'delete': {
201
+ columnConfig.shortcuts?.onDelete?.(ctx);
202
+ return;
203
+ }
204
+
205
+ default: {
206
+ return;
207
+ }
208
+ }
209
+ }
210
+
211
+ public get activePos(): TypeEditorPos {
212
+ return this._activePos;
213
+ }
214
+
215
+ private checkRowDataColumnCanEdit = (
216
+ rowData: TypeEditorRowData<TypeSchema>,
217
+ column: TypeEditorColumnType
218
+ ): boolean =>
219
+ !(rowData.disableEditColumn || []).map((v) => v.column).includes(column) &&
220
+ this.getConfigByType(column)?.focusable !== false;
221
+
222
+ /**
223
+ * 获取可编辑的下一列/上一列
224
+ */
225
+ private getCanEditColumn(originPos: TypeEditorPos, direction: 'next' | 'last'): TypeEditorPos {
226
+ const newX =
227
+ (originPos.x + this.columnViewConfig.length + (direction === 'next' ? 1 : -1)) %
228
+ this.columnViewConfig.length;
229
+
230
+ const newPos = {
231
+ y: originPos.y,
232
+ x: newX,
233
+ };
234
+
235
+ if (
236
+ this.checkRowDataColumnCanEdit(
237
+ this.dataSource[newPos.y],
238
+ this.columnViewConfig[newPos.x].type
239
+ )
240
+ ) {
241
+ return newPos;
242
+ }
243
+
244
+ return this.getCanEditColumn(newPos, direction);
245
+ }
246
+
247
+ /**
248
+ * 获取可编辑的下一行/上一行
249
+ */
250
+ private getCanEditLine(originPos: TypeEditorPos, direction: 'next' | 'last'): TypeEditorPos {
251
+ const newY =
252
+ (originPos.y + this.dataSource.length + (direction === 'next' ? 1 : -1)) %
253
+ this.dataSource.length;
254
+
255
+ const newPos = {
256
+ y: newY,
257
+ x: originPos.x,
258
+ };
259
+
260
+ if (
261
+ this.checkRowDataColumnCanEdit(
262
+ this.dataSource[newPos.y],
263
+ this.columnViewConfig[newPos.x].type
264
+ )
265
+ ) {
266
+ return newPos;
267
+ }
268
+
269
+ return this.getCanEditLine(newPos, direction);
270
+ }
271
+
272
+ /**
273
+ * 获取下一个可编辑的
274
+ */
275
+ private getNextEditItem = (pos: TypeEditorPos): TypeEditorPos => {
276
+ const newPos = { ...pos };
277
+
278
+ if (newPos.x === this.columnViewConfig.length - 1) {
279
+ newPos.y = (1 + newPos.y) % this.dataSource.length;
280
+ newPos.x = 0;
281
+ } else {
282
+ newPos.x = newPos.x + 1;
283
+ }
284
+
285
+ if (
286
+ this.checkRowDataColumnCanEdit(
287
+ this.dataSource[newPos.y],
288
+ this.columnViewConfig[newPos.x].type
289
+ )
290
+ ) {
291
+ return newPos;
292
+ }
293
+
294
+ return this.getNextEditItem(newPos);
295
+ };
296
+
297
+ public moveActivePosToNextLine(): void {
298
+ const newPos = this.getCanEditLine(this.activePos, 'next');
299
+
300
+ this.setActivePos(newPos);
301
+ }
302
+
303
+ public moveActivePosToNextLineWithAddLine(rowData: TypeEditorRowData<TypeSchema>): void {
304
+ const newPos = { ...this.activePos };
305
+
306
+ if (!rowData.parentId) {
307
+ return;
308
+ }
309
+
310
+ const parentData = this.dataSourceMap[rowData.parentId] || this.dataSourceMap[ROOT_FIELD_ID];
311
+
312
+ const id = this.dataSourceMap[rowData.parentId] ? rowData.parentId : ROOT_FIELD_ID;
313
+ const addChild = parentData.index + parentData.deepChildrenCount === rowData.index;
314
+
315
+ if (addChild) {
316
+ if (this.onGlobalAdd) {
317
+ this.onGlobalAdd(id);
318
+ newPos.y = newPos.y + 1;
319
+ } else {
320
+ newPos.y = -1;
321
+ }
322
+ } else {
323
+ newPos.y = newPos.y + 1;
324
+ }
325
+ this.setActivePos(newPos);
326
+ }
327
+
328
+ public moveActivePosToLastLine(): void {
329
+ const newPos = this.getCanEditLine(this.activePos, 'last');
330
+
331
+ this.setActivePos(newPos);
332
+ }
333
+
334
+ public moveActivePosToLastColumn(): void {
335
+ const newPos = this.getCanEditColumn(this.activePos, 'last');
336
+ this.setActivePos(newPos);
337
+ }
338
+
339
+ public moveActivePosToNextColumn(): void {
340
+ const newPos = this.getCanEditColumn(this.activePos, 'next');
341
+
342
+ this.setActivePos(newPos);
343
+ }
344
+
345
+ public moveActivePosToNextItem(): void {
346
+ const newPos = this.getNextEditItem(this.activePos);
347
+
348
+ this.setActivePos(newPos);
349
+ }
350
+
351
+ public setActivePos(pos: TypeEditorPos): void {
352
+ if (this.checkActivePosError()) {
353
+ return;
354
+ }
355
+ this._activePos = pos;
356
+
357
+ this.onActivePosChange.fire(this._activePos);
358
+ }
359
+
360
+ public clearActivePos(): void {
361
+ this._activePos = { x: -1, y: -1 };
362
+ this.onActivePosChange.fire(this._activePos);
363
+ }
364
+
365
+ public setDataSource(newData: TypeEditorRowData<TypeSchema>[]): void {
366
+ this.dataSource = newData;
367
+ }
368
+
369
+ public getDataSource(): TypeEditorRowData<TypeSchema>[] {
370
+ return this.dataSource;
371
+ }
372
+
373
+ public setColumnViewConfig(config: TypeEditorColumnViewConfig[]): void {
374
+ this.columnViewConfig = config;
375
+ }
376
+
377
+ public get dropInfo(): TypeEditorDropInfo {
378
+ return this._dropInfo;
379
+ }
380
+
381
+ public setDropInfo(dropInfo: TypeEditorDropInfo): void {
382
+ if (
383
+ dropInfo.indent === this.dropInfo.indent &&
384
+ this.dropInfo.rowDataId === dropInfo.rowDataId &&
385
+ this.dropInfo.index === dropInfo.index
386
+ ) {
387
+ return;
388
+ }
389
+ this._dropInfo = dropInfo;
390
+ this.onDropInfoChange.fire(dropInfo);
391
+ }
392
+
393
+ public clearDropInfo(): void {
394
+ this.setDropInfo({ rowDataId: '', indent: -1, index: -2 });
395
+ }
396
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { isEqual } from 'lodash-es';
7
+ import { injectable } from 'inversify';
8
+ import { IJsonSchema } from '@flowgram-vue/json-schema';
9
+
10
+ import { MonitorData } from '../utils';
11
+
12
+ interface StackItem {
13
+ id: string;
14
+ value: string;
15
+ }
16
+
17
+ // 操作注册
18
+ @injectable()
19
+ export class TypeEditorOperationService<TypeSchema extends Partial<IJsonSchema>> {
20
+ public undoStack: StackItem[] = [];
21
+
22
+ public redoStack: StackItem[] = [];
23
+
24
+ private _id_idx = 0;
25
+
26
+ private _getNewId(): string {
27
+ return `${this._id_idx++}`;
28
+ }
29
+
30
+ public _storeState = (value: TypeSchema) => {
31
+ if (this.redoStack.length > 0) {
32
+ this.redoStack.splice(0);
33
+ }
34
+
35
+ this.undoStack.push({
36
+ id: this._getNewId(),
37
+ value: JSON.stringify(value),
38
+ });
39
+ this.refreshUndoRedoStatus();
40
+ };
41
+
42
+ public canUndo = new MonitorData(false);
43
+
44
+ public canRedo = new MonitorData(false);
45
+
46
+ public constructor() {
47
+ this.refreshUndoRedoStatus();
48
+ }
49
+
50
+ public refreshUndoRedoStatus() {
51
+ this.canRedo.update(this.redoStack.length !== 0);
52
+ this.canUndo.update(this.undoStack.length > 1);
53
+ }
54
+
55
+ public getCurrentState(): TypeSchema | undefined {
56
+ const top = this.undoStack[this.undoStack.length - 1];
57
+
58
+ if (top) {
59
+ return JSON.parse(top.value);
60
+ }
61
+ return;
62
+ }
63
+
64
+ public clear(): void {
65
+ this.undoStack = [];
66
+ this.redoStack = [];
67
+ }
68
+
69
+ public storeState(value: TypeSchema): void {
70
+ if (isEqual(this.getCurrentState(), value)) {
71
+ return;
72
+ }
73
+
74
+ this._storeState(value);
75
+ }
76
+
77
+ public async undo(): Promise<void> {
78
+ const top = this.undoStack.pop();
79
+ if (top) {
80
+ this.redoStack.push(top);
81
+ }
82
+
83
+ this.refreshUndoRedoStatus();
84
+ }
85
+
86
+ public async redo(): Promise<void> {
87
+ const top = this.redoStack.pop();
88
+
89
+ if (top) {
90
+ this.undoStack.push(top);
91
+ }
92
+
93
+ this.refreshUndoRedoStatus();
94
+ }
95
+
96
+ public debugger(): void {
97
+ console.log('getCurrentState - debugger', this.getCurrentState());
98
+ }
99
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { injectable } from 'inversify';
7
+ import { JsonSchemaTypeManager, IJsonSchema } from '@flowgram-vue/json-schema';
8
+
9
+ import { TypeEditorRegistry } from '../types';
10
+
11
+ @injectable()
12
+ export class TypeEditorRegistryManager<
13
+ TypeSchema extends Partial<IJsonSchema>
14
+ > extends JsonSchemaTypeManager<TypeSchema, TypeEditorRegistry<TypeSchema>> {}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { IJsonSchema } from '@flowgram-vue/json-schema';
7
+
8
+ export const traverseIJsonSchema = (
9
+ root: Partial<IJsonSchema> | undefined,
10
+ cb: (type: Partial<IJsonSchema>) => void
11
+ ): void => {
12
+ if (root) {
13
+ cb(root);
14
+
15
+ if (root.items) {
16
+ traverseIJsonSchema(root.items, cb);
17
+ }
18
+ if (root.additionalProperties) {
19
+ traverseIJsonSchema(root.additionalProperties, cb);
20
+ }
21
+
22
+ if (root.properties) {
23
+ Object.values(root.properties).forEach((v) => {
24
+ traverseIJsonSchema(v, cb);
25
+ });
26
+ }
27
+ }
28
+ };