@nocobase/client-v2 2.1.0-alpha.45 → 2.1.0-alpha.47

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 (99) hide show
  1. package/es/authRedirect.d.ts +12 -0
  2. package/es/collection-manager/filter-operators/index.d.ts +1 -0
  3. package/es/components/form/TypedVariableInput.d.ts +75 -0
  4. package/es/components/form/filter/CollectionFilterItem.d.ts +5 -1
  5. package/es/components/form/filter/FilterValueInput.d.ts +5 -0
  6. package/es/components/form/index.d.ts +1 -0
  7. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
  8. package/es/flow/components/ConditionBuilder.d.ts +1 -0
  9. package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
  10. package/es/flow/components/FieldAssignValueInput.d.ts +1 -0
  11. package/es/flow/components/fieldAssignOptions.d.ts +10 -0
  12. package/es/flow/components/filter/LinkageFilterItem.d.ts +1 -0
  13. package/es/flow/components/filter/VariableFilterItem.d.ts +1 -0
  14. package/es/flow/components/filter/metaTreeAssociationDepth.d.ts +13 -0
  15. package/es/flow/internal/utils/operatorSchemaHelper.d.ts +26 -3
  16. package/es/flow/models/base/AssociationFieldGroupModel.d.ts +2 -33
  17. package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +8 -0
  18. package/es/flow/models/blocks/filter-manager/utils.d.ts +23 -1
  19. package/es/flow/models/blocks/table/TableColumnModel.d.ts +2 -0
  20. package/es/index.mjs +106 -106
  21. package/lib/index.js +106 -106
  22. package/package.json +7 -7
  23. package/src/__tests__/browserChecker.test.ts +103 -0
  24. package/src/__tests__/dataSourceRuntime.test.tsx +22 -1
  25. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +5 -0
  26. package/src/authRedirect.ts +43 -0
  27. package/src/collection-field-interface/CollectionFieldInterface.ts +14 -7
  28. package/src/collection-manager/filter-operators/index.ts +29 -2
  29. package/src/components/README.md +48 -0
  30. package/src/components/README.zh-CN.md +48 -0
  31. package/src/components/form/TypedVariableInput.tsx +441 -0
  32. package/src/components/form/__tests__/TypedVariableInput.test.tsx +152 -0
  33. package/src/components/form/filter/CollectionFilterItem.tsx +10 -2
  34. package/src/components/form/filter/FilterValueInput.tsx +27 -2
  35. package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +39 -0
  36. package/src/components/form/index.tsx +1 -0
  37. package/src/flow/__tests__/FlowRoute.test.tsx +8 -1
  38. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +100 -0
  39. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +369 -2
  40. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +23 -1
  41. package/src/flow/actions/dataScope.tsx +8 -1
  42. package/src/flow/actions/linkageRules.tsx +61 -5
  43. package/src/flow/actions/linkageRulesRefresh.tsx +2 -3
  44. package/src/flow/actions/setTargetDataScope.tsx +1 -1
  45. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +15 -1
  46. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +75 -1
  47. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +11 -0
  48. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +2 -4
  49. package/src/flow/common/Markdown/Edit.tsx +32 -4
  50. package/src/flow/components/ConditionBuilder.tsx +12 -2
  51. package/src/flow/components/DefaultValue.tsx +14 -8
  52. package/src/flow/components/FieldAssignRulesEditor.tsx +37 -46
  53. package/src/flow/components/FieldAssignValueInput.tsx +14 -14
  54. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +112 -1
  55. package/src/flow/components/__tests__/fieldAssignOptions.test.ts +71 -1
  56. package/src/flow/components/code-editor/__tests__/runjsCompletions.test.ts +272 -27
  57. package/src/flow/components/code-editor/runjsCompletions.ts +490 -9
  58. package/src/flow/components/fieldAssignOptions.ts +23 -6
  59. package/src/flow/components/filter/LinkageFilterItem.tsx +26 -12
  60. package/src/flow/components/filter/VariableFilterItem.tsx +32 -13
  61. package/src/flow/components/filter/__tests__/fieldsToOptions.test.ts +48 -0
  62. package/src/flow/components/filter/__tests__/metaTreeAssociationDepth.test.ts +60 -0
  63. package/src/flow/components/filter/fieldsToOptions.ts +9 -4
  64. package/src/flow/components/filter/metaTreeAssociationDepth.ts +69 -0
  65. package/src/flow/internal/utils/operatorSchemaHelper.ts +112 -5
  66. package/src/flow/models/actions/FilterActionModel.tsx +15 -3
  67. package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +90 -0
  68. package/src/flow/models/base/AssociationFieldGroupModel.tsx +20 -3
  69. package/src/flow/models/base/GridModel.tsx +1 -1
  70. package/src/flow/models/base/PageModel/ChildPageModel.tsx +5 -1
  71. package/src/flow/models/base/PageModel/__tests__/ChildPageModel.test.ts +12 -0
  72. package/src/flow/models/base/__tests__/AssociationFieldGroupModel.test.ts +86 -0
  73. package/src/flow/models/base/__tests__/GridModel.render.test.tsx +53 -0
  74. package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +12 -3
  75. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +14 -4
  76. package/src/flow/models/blocks/assign-form/__tests__/assignFieldValuesFlow.editor.test.tsx +216 -0
  77. package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +142 -40
  78. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +14 -12
  79. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +6 -1
  80. package/src/flow/models/blocks/filter-manager/__tests__/getDefaultOperator.test.ts +35 -1
  81. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +148 -0
  82. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/defaultOperator.test.tsx +85 -0
  83. package/src/flow/models/blocks/filter-manager/flow-actions/connectFields.tsx +48 -6
  84. package/src/flow/models/blocks/filter-manager/flow-actions/customizeFilterRender.tsx +14 -44
  85. package/src/flow/models/blocks/filter-manager/flow-actions/defaultOperator.tsx +3 -5
  86. package/src/flow/models/blocks/filter-manager/flow-actions/operatorComponentProps.tsx +2 -12
  87. package/src/flow/models/blocks/filter-manager/utils.ts +143 -4
  88. package/src/flow/models/blocks/form/FormAssociationItemModel.tsx +9 -0
  89. package/src/flow/models/blocks/form/FormItemModel.tsx +9 -0
  90. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  91. package/src/flow/models/blocks/form/__tests__/FormItemModel.defineChildren.test.ts +27 -2
  92. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +29 -0
  93. package/src/flow/models/blocks/form/value-runtime/runtime.ts +5 -1
  94. package/src/flow/models/blocks/table/TableColumnModel.tsx +15 -5
  95. package/src/flow/models/blocks/table/TableCustomColumnModel.tsx +2 -2
  96. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +18 -0
  97. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +6 -7
  98. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +5 -3
  99. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +70 -0
@@ -9,7 +9,12 @@
9
9
 
10
10
  import { Completion } from '@codemirror/autocomplete';
11
11
  import { EditorView } from '@codemirror/view';
12
- import { getRunJSDocFor, setupRunJSContexts, listSnippetsForContext } from '@nocobase/flow-engine';
12
+ import {
13
+ getRunJSDocFor,
14
+ setupRunJSContexts,
15
+ listSnippetsForContext,
16
+ type RunJSDocCompletionDoc,
17
+ } from '@nocobase/flow-engine';
13
18
  import { formatDocInfo } from './formatDocInfo';
14
19
 
15
20
  export type SnippetEntry = {
@@ -23,6 +28,483 @@ export type SnippetEntry = {
23
28
  scenes?: string[];
24
29
  };
25
30
 
31
+ type StaticCompletionEntry = {
32
+ label: string;
33
+ type: Completion['type'];
34
+ detail?: string;
35
+ info?: string;
36
+ insertText?: string;
37
+ boost?: number;
38
+ requires?: Array<'element'>;
39
+ };
40
+
41
+ const NON_ELEMENT_COMPLETION_SCENES = new Set(['eventFlow', 'formValue', 'linkage']);
42
+
43
+ const normalizeScenes = (scene?: string | string[]): string[] =>
44
+ (Array.isArray(scene) ? scene : scene ? [scene] : [])
45
+ .map((sceneName) => (typeof sceneName === 'string' ? sceneName.trim() : ''))
46
+ .filter(Boolean);
47
+
48
+ const hasRunJSElementContext = (doc: any, apiInfos: any, requestedScenes: string[]): boolean => {
49
+ if (requestedScenes.some((sceneName) => NON_ELEMENT_COMPLETION_SCENES.has(sceneName))) return false;
50
+ return !!((doc as any)?.properties?.element || (apiInfos as any)?.element);
51
+ };
52
+
53
+ const usesCtxElement = (text?: string): boolean => typeof text === 'string' && /\bctx\.element\b/.test(text);
54
+
55
+ const satisfiesCompletionRequirements = (
56
+ completionSpec: Pick<RunJSDocCompletionDoc, 'requires'> | undefined,
57
+ capabilities: { element: boolean },
58
+ ): boolean => {
59
+ if (!completionSpec?.requires?.length) return true;
60
+ return completionSpec.requires.every((requirement) => requirement !== 'element' || capabilities.element);
61
+ };
62
+
63
+ const createApply = (insertText?: string) => (view: EditorView, _completion: Completion, from: number, to: number) => {
64
+ if (!insertText) return;
65
+ view.dispatch({
66
+ changes: { from, to, insert: insertText },
67
+ selection: { anchor: from + insertText.length },
68
+ scrollIntoView: true,
69
+ });
70
+ };
71
+
72
+ const staticEntry = ({ label, type, detail, info, insertText, boost = 85 }: StaticCompletionEntry): Completion =>
73
+ ({
74
+ label,
75
+ type,
76
+ detail,
77
+ info,
78
+ boost,
79
+ apply: insertText ? createApply(insertText) : undefined,
80
+ }) as Completion;
81
+
82
+ const SAFE_GLOBAL_COMPLETIONS: StaticCompletionEntry[] = [
83
+ {
84
+ label: 'window.open()',
85
+ type: 'function',
86
+ detail: '(url: string, target?: string, features?: string) => Window | null',
87
+ info: 'Open a safe http/https/about:blank URL in a new tab.',
88
+ insertText: "window.open('https://example.com')",
89
+ boost: 105,
90
+ },
91
+ {
92
+ label: 'window.addEventListener()',
93
+ type: 'function',
94
+ detail: '(type: string, listener: EventListener) => void',
95
+ info: 'Attach a listener to the safe window proxy.',
96
+ insertText: "window.addEventListener('resize', () => {})",
97
+ boost: 105,
98
+ },
99
+ {
100
+ label: 'window.setTimeout()',
101
+ type: 'function',
102
+ detail: '(handler: Function, timeout?: number) => number',
103
+ insertText: 'window.setTimeout(() => {}, 0)',
104
+ boost: 100,
105
+ },
106
+ {
107
+ label: 'window.clearTimeout()',
108
+ type: 'function',
109
+ detail: '(id?: number) => void',
110
+ insertText: 'window.clearTimeout(timer)',
111
+ boost: 100,
112
+ },
113
+ {
114
+ label: 'window.setInterval()',
115
+ type: 'function',
116
+ detail: '(handler: Function, timeout?: number) => number',
117
+ insertText: 'window.setInterval(() => {}, 1000)',
118
+ boost: 100,
119
+ },
120
+ {
121
+ label: 'window.clearInterval()',
122
+ type: 'function',
123
+ detail: '(id?: number) => void',
124
+ insertText: 'window.clearInterval(timer)',
125
+ boost: 100,
126
+ },
127
+ {
128
+ label: 'window.FormData',
129
+ type: 'class',
130
+ detail: 'FormData constructor',
131
+ insertText: 'new window.FormData()',
132
+ boost: 95,
133
+ },
134
+ { label: 'window.Blob', type: 'class', detail: 'Blob constructor', insertText: 'window.Blob', boost: 95 },
135
+ { label: 'window.URL', type: 'class', detail: 'URL constructor', insertText: 'window.URL', boost: 95 },
136
+ { label: 'window.location.origin', type: 'property', detail: 'string', boost: 100 },
137
+ { label: 'window.location.protocol', type: 'property', detail: 'string', boost: 100 },
138
+ { label: 'window.location.host', type: 'property', detail: 'string', boost: 100 },
139
+ { label: 'window.location.hostname', type: 'property', detail: 'string', boost: 100 },
140
+ { label: 'window.location.port', type: 'property', detail: 'string', boost: 100 },
141
+ { label: 'window.location.pathname', type: 'property', detail: 'string', boost: 100 },
142
+ {
143
+ label: 'window.location.assign()',
144
+ type: 'function',
145
+ detail: '(url: string) => void',
146
+ insertText: "window.location.assign('/path')",
147
+ boost: 105,
148
+ },
149
+ {
150
+ label: 'window.location.replace()',
151
+ type: 'function',
152
+ detail: '(url: string) => void',
153
+ insertText: "window.location.replace('/path')",
154
+ boost: 105,
155
+ },
156
+ {
157
+ label: 'window.location.reload()',
158
+ type: 'function',
159
+ detail: '() => void',
160
+ insertText: 'window.location.reload()',
161
+ boost: 110,
162
+ },
163
+ {
164
+ label: 'window.location.href =',
165
+ type: 'snippet',
166
+ detail: 'safe assignment only',
167
+ info: 'RunJS allows assigning window.location.href, but reading href is blocked.',
168
+ insertText: "window.location.href = '/path'",
169
+ boost: 95,
170
+ },
171
+ {
172
+ label: 'document.createElement()',
173
+ type: 'function',
174
+ detail: '(tagName: string) => HTMLElement',
175
+ insertText: "document.createElement('div')",
176
+ boost: 105,
177
+ },
178
+ {
179
+ label: 'document.querySelector()',
180
+ type: 'function',
181
+ detail: '(selectors: string) => Element | null',
182
+ insertText: "document.querySelector('.selector')",
183
+ boost: 105,
184
+ },
185
+ {
186
+ label: 'document.querySelectorAll()',
187
+ type: 'function',
188
+ detail: '(selectors: string) => NodeListOf<Element>',
189
+ insertText: "document.querySelectorAll('.selector')",
190
+ boost: 105,
191
+ },
192
+ {
193
+ label: 'navigator.clipboard.writeText()',
194
+ type: 'function',
195
+ detail: '(text: string) => Promise<void>',
196
+ insertText: "await navigator.clipboard.writeText('text')",
197
+ boost: 105,
198
+ },
199
+ { label: 'navigator.onLine', type: 'property', detail: 'boolean', boost: 100 },
200
+ { label: 'navigator.language', type: 'property', detail: 'string', boost: 100 },
201
+ { label: 'navigator.languages', type: 'property', detail: 'string[]', boost: 100 },
202
+ {
203
+ label: 'console.log()',
204
+ type: 'function',
205
+ detail: '(...args: any[]) => void',
206
+ insertText: "console.log('message')",
207
+ boost: 95,
208
+ },
209
+ {
210
+ label: 'console.info()',
211
+ type: 'function',
212
+ detail: '(...args: any[]) => void',
213
+ insertText: "console.info('message')",
214
+ boost: 95,
215
+ },
216
+ {
217
+ label: 'console.warn()',
218
+ type: 'function',
219
+ detail: '(...args: any[]) => void',
220
+ insertText: "console.warn('message')",
221
+ boost: 95,
222
+ },
223
+ {
224
+ label: 'console.error()',
225
+ type: 'function',
226
+ detail: '(...args: any[]) => void',
227
+ insertText: "console.error('message')",
228
+ boost: 95,
229
+ },
230
+ {
231
+ label: 'setTimeout()',
232
+ type: 'function',
233
+ detail: '(handler: Function, timeout?: number) => number',
234
+ insertText: 'setTimeout(() => {}, 0)',
235
+ boost: 95,
236
+ },
237
+ {
238
+ label: 'clearTimeout()',
239
+ type: 'function',
240
+ detail: '(id?: number) => void',
241
+ insertText: 'clearTimeout(timer)',
242
+ boost: 95,
243
+ },
244
+ {
245
+ label: 'setInterval()',
246
+ type: 'function',
247
+ detail: '(handler: Function, timeout?: number) => number',
248
+ insertText: 'setInterval(() => {}, 1000)',
249
+ boost: 95,
250
+ },
251
+ {
252
+ label: 'clearInterval()',
253
+ type: 'function',
254
+ detail: '(id?: number) => void',
255
+ insertText: 'clearInterval(timer)',
256
+ boost: 95,
257
+ },
258
+ { label: 'Blob', type: 'class', detail: 'Blob constructor', insertText: 'Blob', boost: 90 },
259
+ { label: 'URL', type: 'class', detail: 'URL constructor', insertText: 'URL', boost: 90 },
260
+ ];
261
+
262
+ const RUNJS_RUNTIME_COMPLETIONS: StaticCompletionEntry[] = [
263
+ {
264
+ label: 'ctx.runjs()',
265
+ type: 'function',
266
+ detail: '(code: string, variables?: object, options?: object) => Promise<any>',
267
+ insertText: "await ctx.runjs('return 1')",
268
+ boost: 105,
269
+ },
270
+ {
271
+ label: 'ctx.loadCSS()',
272
+ type: 'function',
273
+ detail: '(href: string) => Promise<void>',
274
+ insertText: "await ctx.loadCSS('https://example.com/style.css')",
275
+ boost: 105,
276
+ },
277
+ {
278
+ label: 'ctx.previewRunJS()',
279
+ type: 'function',
280
+ detail: '(code: string, version?: string) => Promise<PreviewRunJSResult>',
281
+ insertText: "await ctx.previewRunJS('console.log(1)', 'v2')",
282
+ boost: 105,
283
+ },
284
+ {
285
+ label: 'ctx.requireAsync()',
286
+ type: 'function',
287
+ detail: '(url: string) => Promise<any>',
288
+ insertText: "const lib = await ctx.requireAsync('https://cdn.example.com/lib.umd.js')",
289
+ boost: 100,
290
+ },
291
+ {
292
+ label: 'ctx.importAsync()',
293
+ type: 'function',
294
+ detail: '(url: string) => Promise<any>',
295
+ insertText: "const mod = await ctx.importAsync('lodash-es')",
296
+ boost: 100,
297
+ },
298
+ {
299
+ label: 'ctx.t()',
300
+ type: 'function',
301
+ detail: '(key: string, options?: object) => string',
302
+ insertText: "ctx.t('Hello')",
303
+ boost: 100,
304
+ },
305
+ {
306
+ label: 'ctx.resolveJsonTemplate()',
307
+ type: 'function',
308
+ detail: '(template: any) => Promise<any>',
309
+ insertText: "await ctx.resolveJsonTemplate({ value: '{{ctx.record.id}}' })",
310
+ boost: 100,
311
+ },
312
+ {
313
+ label: 'ctx.sql.run()',
314
+ type: 'function',
315
+ detail: '(sql: string, options?: SQLRunOptions) => Promise<any>',
316
+ insertText: "await ctx.sql.run('SELECT 1', { type: 'selectRows' })",
317
+ boost: 100,
318
+ },
319
+ {
320
+ label: 'ctx.sql.save()',
321
+ type: 'function',
322
+ detail: '(data: { uid: string; sql: string; dataSourceKey?: string }) => Promise<void>',
323
+ insertText: "await ctx.sql.save({ uid: 'sql-uid', sql: 'SELECT 1' })",
324
+ boost: 95,
325
+ },
326
+ {
327
+ label: 'ctx.sql.runById()',
328
+ type: 'function',
329
+ detail: '(uid: string, options?: SQLRunOptions) => Promise<any>',
330
+ insertText: "await ctx.sql.runById('sql-uid', { type: 'selectRows' })",
331
+ boost: 100,
332
+ },
333
+ {
334
+ label: 'ctx.sql.destroy()',
335
+ type: 'function',
336
+ detail: '(uid: string) => Promise<void>',
337
+ insertText: "await ctx.sql.destroy('sql-uid')",
338
+ boost: 95,
339
+ },
340
+ {
341
+ label: 'ctx.logger.info()',
342
+ type: 'function',
343
+ detail: 'Pino logger info',
344
+ insertText: "ctx.logger.info({ foo: 1 }, 'message')",
345
+ boost: 95,
346
+ },
347
+ {
348
+ label: 'ctx.logger.warn()',
349
+ type: 'function',
350
+ detail: 'Pino logger warn',
351
+ insertText: "ctx.logger.warn({ foo: 1 }, 'message')",
352
+ boost: 95,
353
+ },
354
+ {
355
+ label: 'ctx.logger.error()',
356
+ type: 'function',
357
+ detail: 'Pino logger error',
358
+ insertText: "ctx.logger.error({ err }, 'message')",
359
+ boost: 95,
360
+ },
361
+ {
362
+ label: 'ctx.logger.debug()',
363
+ type: 'function',
364
+ detail: 'Pino logger debug',
365
+ insertText: "ctx.logger.debug({ foo: 1 }, 'message')",
366
+ boost: 95,
367
+ },
368
+ {
369
+ label: 'ctx.logger.child()',
370
+ type: 'function',
371
+ detail: '(bindings: object) => Logger',
372
+ insertText: "ctx.logger.child({ module: 'runjs' })",
373
+ boost: 90,
374
+ },
375
+ {
376
+ label: 'ctx.libs.React.createElement()',
377
+ type: 'function',
378
+ detail: 'React.createElement',
379
+ insertText: "ctx.libs.React.createElement('div', null, 'Hello')",
380
+ boost: 90,
381
+ },
382
+ {
383
+ label: 'ctx.libs.React.useState()',
384
+ type: 'function',
385
+ detail: 'React.useState',
386
+ insertText: 'ctx.libs.React.useState(initialValue)',
387
+ boost: 90,
388
+ },
389
+ {
390
+ label: 'ctx.libs.React.useEffect()',
391
+ type: 'function',
392
+ detail: 'React.useEffect',
393
+ insertText: 'ctx.libs.React.useEffect(() => {}, [])',
394
+ boost: 90,
395
+ },
396
+ {
397
+ label: 'ctx.libs.React.useMemo()',
398
+ type: 'function',
399
+ detail: 'React.useMemo',
400
+ insertText: 'ctx.libs.React.useMemo(() => value, [])',
401
+ boost: 85,
402
+ },
403
+ {
404
+ label: 'ctx.libs.React.useCallback()',
405
+ type: 'function',
406
+ detail: 'React.useCallback',
407
+ insertText: 'ctx.libs.React.useCallback(() => {}, [])',
408
+ boost: 85,
409
+ },
410
+ {
411
+ label: 'ctx.libs.ReactDOM.createRoot()',
412
+ type: 'function',
413
+ detail: 'ReactDOM.createRoot',
414
+ insertText: 'ctx.libs.ReactDOM.createRoot(ctx.element.__el)',
415
+ boost: 90,
416
+ requires: ['element'],
417
+ },
418
+ { label: 'ctx.libs.antd.Button', type: 'class', detail: 'Ant Design Button', boost: 90 },
419
+ { label: 'ctx.libs.antd.Table', type: 'class', detail: 'Ant Design Table', boost: 90 },
420
+ { label: 'ctx.libs.antd.Form', type: 'class', detail: 'Ant Design Form', boost: 90 },
421
+ { label: 'ctx.libs.antd.Input', type: 'class', detail: 'Ant Design Input', boost: 90 },
422
+ { label: 'ctx.libs.antd.Select', type: 'class', detail: 'Ant Design Select', boost: 90 },
423
+ { label: 'ctx.libs.antd.Modal', type: 'class', detail: 'Ant Design Modal', boost: 90 },
424
+ { label: 'ctx.libs.dayjs()', type: 'function', detail: 'dayjs', insertText: 'ctx.libs.dayjs()', boost: 90 },
425
+ {
426
+ label: 'ctx.libs.lodash.get()',
427
+ type: 'function',
428
+ detail: 'lodash.get',
429
+ insertText: "ctx.libs.lodash.get(obj, 'a.b')",
430
+ boost: 90,
431
+ },
432
+ {
433
+ label: 'ctx.libs.lodash.set()',
434
+ type: 'function',
435
+ detail: 'lodash.set',
436
+ insertText: "ctx.libs.lodash.set(obj, 'a.b', value)",
437
+ boost: 85,
438
+ },
439
+ {
440
+ label: 'ctx.libs.lodash.debounce()',
441
+ type: 'function',
442
+ detail: 'lodash.debounce',
443
+ insertText: 'ctx.libs.lodash.debounce(() => {}, 300)',
444
+ boost: 85,
445
+ },
446
+ {
447
+ label: 'ctx.libs.lodash.cloneDeep()',
448
+ type: 'function',
449
+ detail: 'lodash.cloneDeep',
450
+ insertText: 'ctx.libs.lodash.cloneDeep(value)',
451
+ boost: 85,
452
+ },
453
+ {
454
+ label: 'ctx.libs.formula.SUM()',
455
+ type: 'function',
456
+ detail: 'Formula.js SUM',
457
+ insertText: 'ctx.libs.formula.SUM(1, 2, 3)',
458
+ boost: 85,
459
+ },
460
+ {
461
+ label: 'ctx.libs.formula.AVERAGE()',
462
+ type: 'function',
463
+ detail: 'Formula.js AVERAGE',
464
+ insertText: 'ctx.libs.formula.AVERAGE(1, 2, 3)',
465
+ boost: 85,
466
+ },
467
+ {
468
+ label: 'ctx.libs.math.evaluate()',
469
+ type: 'function',
470
+ detail: 'mathjs evaluate',
471
+ insertText: "ctx.libs.math.evaluate('2 + 3')",
472
+ boost: 85,
473
+ },
474
+ {
475
+ label: 'ctx.libs.math.round()',
476
+ type: 'function',
477
+ detail: 'mathjs round',
478
+ insertText: 'ctx.libs.math.round(value, 2)',
479
+ boost: 85,
480
+ },
481
+ {
482
+ label: 'ctx.React.createElement()',
483
+ type: 'function',
484
+ detail: 'React.createElement alias',
485
+ insertText: "ctx.React.createElement('div', null, 'Hello')",
486
+ boost: 80,
487
+ },
488
+ {
489
+ label: 'ctx.ReactDOM.createRoot()',
490
+ type: 'function',
491
+ detail: 'ReactDOM.createRoot alias',
492
+ insertText: 'ctx.ReactDOM.createRoot(ctx.element.__el)',
493
+ boost: 80,
494
+ requires: ['element'],
495
+ },
496
+ { label: 'ctx.antd.Button', type: 'class', detail: 'Ant Design Button alias', boost: 80 },
497
+ { label: 'ctx.antd.Modal', type: 'class', detail: 'Ant Design Modal alias', boost: 80 },
498
+ { label: 'ctx.dayjs()', type: 'function', detail: 'dayjs alias', insertText: 'ctx.dayjs()', boost: 80 },
499
+ ];
500
+
501
+ function buildStaticRuntimeCompletions(capabilities: { element: boolean }): Completion[] {
502
+ return [...SAFE_GLOBAL_COMPLETIONS, ...RUNJS_RUNTIME_COMPLETIONS]
503
+ .filter((entry) => satisfiesCompletionRequirements(entry, capabilities))
504
+ .filter((entry) => capabilities.element || !usesCtxElement(entry.insertText))
505
+ .map(staticEntry);
506
+ }
507
+
26
508
  export async function buildRunJSCompletions(
27
509
  hostCtx: any,
28
510
  version = 'v1',
@@ -31,6 +513,7 @@ export async function buildRunJSCompletions(
31
513
  completions: Completion[];
32
514
  entries: SnippetEntry[];
33
515
  }> {
516
+ const requestedScenes = normalizeScenes(scene);
34
517
  const isFunctionLikeDocNode = (node: any, completionSpec: any): boolean => {
35
518
  if (!node || typeof node !== 'object' || Array.isArray(node)) return false;
36
519
  const type = typeof (node as any).type === 'string' ? String((node as any).type) : '';
@@ -56,7 +539,7 @@ export async function buildRunJSCompletions(
56
539
  let apiInfos: any = null;
57
540
  try {
58
541
  if (hostCtx && typeof (hostCtx as any).getApiInfos === 'function') {
59
- apiInfos = await (hostCtx as any).getApiInfos({ version });
542
+ apiInfos = await (hostCtx as any).getApiInfos({ version, includeCompletion: true });
60
543
  }
61
544
  } catch (_) {
62
545
  apiInfos = null;
@@ -73,10 +556,12 @@ export async function buildRunJSCompletions(
73
556
  };
74
557
  const docApis = { ...((doc as any)?.properties || {}), ...normalizeMethodsRecord((doc as any)?.methods) };
75
558
  const apisSource = { ...(apiInfos && typeof apiInfos === 'object' ? apiInfos : {}), ...(docApis || {}) };
76
- const completions: Completion[] = [];
559
+ const hasElementContext = hasRunJSElementContext(doc, apiInfos, requestedScenes);
560
+ const completions: Completion[] = buildStaticRuntimeCompletions({ element: hasElementContext });
77
561
  const priorityRoots = new Set(['api', 'resource', 'viewer', 'record', 'formValues', 'popup']);
78
562
  const hiddenDecisionCache = new Map<string, { hideSelf: boolean; hideSubpaths: string[] }>();
79
563
  const hiddenPathPrefixes = new Set<string>();
564
+ if (!hasElementContext) hiddenPathPrefixes.add('ctx.element');
80
565
 
81
566
  const isHiddenByPaths = (label: string) => {
82
567
  if (!label || typeof label !== 'string') return false;
@@ -201,6 +686,8 @@ export async function buildRunJSCompletions(
201
686
  const insertText =
202
687
  completionSpec?.insertText ??
203
688
  (root === 'popup' ? `await ctx.getVar('${ctxLabel}')` : isCallable ? `${ctxLabel}()` : undefined);
689
+ if (!satisfiesCompletionRequirements(completionSpec, { element: hasElementContext })) continue;
690
+ if (!hasElementContext && usesCtxElement(insertText)) continue;
204
691
  const apply = insertText
205
692
  ? (view: EditorView, _completion: Completion, from: number, to: number) => {
206
693
  view.dispatch({
@@ -235,12 +722,6 @@ export async function buildRunJSCompletions(
235
722
  entries = [];
236
723
  }
237
724
 
238
- const requestedScenes = Array.isArray(scene)
239
- ? scene.filter((s): s is string => typeof s === 'string' && s.trim().length > 0)
240
- : scene
241
- ? [scene]
242
- : [];
243
-
244
725
  const filteredEntries = requestedScenes.length
245
726
  ? entries.filter((entry) => {
246
727
  if (entry.scenes?.length) {
@@ -76,10 +76,15 @@ function getCollectionFromFormBlockModel(model: any): CollectionLike | null {
76
76
  return collection && typeof collection === 'object' ? (collection as CollectionLike) : null;
77
77
  }
78
78
 
79
- function buildRootOptionsFromCollection(
79
+ export function buildFieldAssignCascaderOptionsFromCollection(
80
80
  collection: CollectionLike | null,
81
81
  t: (key: string) => string,
82
+ options: {
83
+ associationDepth?: number;
84
+ maxAssociationFieldDepth?: number;
85
+ } = {},
82
86
  ): FieldAssignCascaderOption[] {
87
+ const { associationDepth = 0, maxAssociationFieldDepth = 2 } = options;
83
88
  const fields = typeof collection?.getFields === 'function' ? collection.getFields() || [] : [];
84
89
  const out: FieldAssignCascaderOption[] = [];
85
90
  for (const rawField of fields) {
@@ -88,6 +93,7 @@ function buildRootOptionsFromCollection(
88
93
  name?: unknown;
89
94
  title?: unknown;
90
95
  interface?: unknown;
96
+ target?: unknown;
91
97
  isAssociationField?: () => boolean;
92
98
  targetCollection?: any;
93
99
  };
@@ -99,8 +105,11 @@ function buildRootOptionsFromCollection(
99
105
  if (!name) continue;
100
106
  const title = t(typeof f.title === 'string' ? f.title : name);
101
107
 
102
- const isAssoc = !!f.isAssociationField?.();
108
+ const isAssoc = !!(f.isAssociationField?.() || f.target || f.targetCollection);
103
109
  const hasTarget = !!f.targetCollection;
110
+ if (isAssoc && associationDepth >= maxAssociationFieldDepth) {
111
+ continue;
112
+ }
104
113
 
105
114
  out.push({
106
115
  label: title,
@@ -138,8 +147,9 @@ export function collectFieldAssignCascaderOptions(options: {
138
147
  t: (key: string) => string;
139
148
  /** 子表单模型递归深度(FormItemModel 层级);默认不限制(只受实际配置与循环引用约束) */
140
149
  maxFormItemDepth?: number;
150
+ maxAssociationFieldDepth?: number;
141
151
  }): FieldAssignCascaderOption[] {
142
- const { formBlockModel, t, maxFormItemDepth = Number.POSITIVE_INFINITY } = options;
152
+ const { formBlockModel, t, maxFormItemDepth = Number.POSITIVE_INFINITY, maxAssociationFieldDepth = 2 } = options;
143
153
 
144
154
  const rootItems = formBlockModel?.subModels?.grid?.subModels?.items || [];
145
155
  const rootCollection = getCollectionFromFormBlockModel(formBlockModel);
@@ -167,9 +177,14 @@ export function collectFieldAssignCascaderOptions(options: {
167
177
 
168
178
  const fieldModel = item?.subModels?.field;
169
179
  const childItems = fieldModel?.subModels?.grid?.subModels?.items;
170
- const cf: CollectionField | undefined = fieldModel?.context?.collectionField;
171
- const isAssociation = !!cf?.isAssociationField?.();
180
+ const cf: (CollectionField & { target?: unknown; targetCollection?: unknown }) | undefined =
181
+ fieldModel?.context?.collectionField;
182
+ const isAssociation = !!(cf?.isAssociationField?.() || cf?.target || cf?.targetCollection);
172
183
  const hasTargetCollection = !!cf?.targetCollection;
184
+ const associationDepth = isAssociation ? targetPath.split('.').filter(Boolean).length : 0;
185
+ if (isAssociation && associationDepth > maxAssociationFieldDepth) {
186
+ continue;
187
+ }
173
188
 
174
189
  // 1) 子表单/子表单列表:递归展开已配置字段(支持无限深度)。
175
190
  //
@@ -207,6 +222,8 @@ export function collectFieldAssignCascaderOptions(options: {
207
222
  };
208
223
 
209
224
  const configuredOptions = walkItems(rootItems, 1);
210
- const allFieldOptions = buildRootOptionsFromCollection(rootCollection, t);
225
+ const allFieldOptions = buildFieldAssignCascaderOptionsFromCollection(rootCollection, t, {
226
+ maxAssociationFieldDepth,
227
+ });
211
228
  return mergeRootOptions(configuredOptions, allFieldOptions);
212
229
  }