@n8n/frontend-module-sdk 0.8.1 → 0.9.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.
- package/package.json +10 -8
- package/src/index.ts +1 -0
- package/src/registries/parameterInputRegistry.test.ts +186 -0
- package/src/registries/parameterInputRegistry.ts +72 -0
- package/src/types/descriptor.test.ts +19 -0
- package/src/types/descriptor.ts +7 -0
- package/src/types/index.ts +1 -0
- package/src/types/parameterInput.ts +99 -0
package/package.json
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@n8n/frontend-module-sdk",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.0",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"import": "src/index.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.ts"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
+
"@n8n/api-types": "1.39.0",
|
|
12
|
+
"@n8n/design-system": "2.38.0",
|
|
13
|
+
"@n8n/utils": "1.47.0",
|
|
14
|
+
"n8n-workflow": "2.39.0",
|
|
11
15
|
"vue": "^3.5.13",
|
|
12
|
-
"vue-router": "^4.5.0"
|
|
13
|
-
"@n8n/api-types": "1.38.1",
|
|
14
|
-
"@n8n/design-system": "2.37.1"
|
|
16
|
+
"vue-router": "^4.5.0"
|
|
15
17
|
},
|
|
16
18
|
"devDependencies": {
|
|
19
|
+
"@n8n/eslint-config": "0.0.1",
|
|
20
|
+
"@n8n/typescript-config": "1.11.0",
|
|
21
|
+
"@n8n/vitest-config": "1.21.0",
|
|
17
22
|
"typescript": "6.0.2",
|
|
18
23
|
"unplugin-icons": "^23.0.1",
|
|
19
24
|
"vite": "^8.0.2",
|
|
20
25
|
"vitest": "^4.1.9",
|
|
21
|
-
"vue-tsc": "^2.2.8"
|
|
22
|
-
"@n8n/eslint-config": "0.0.1",
|
|
23
|
-
"@n8n/typescript-config": "1.11.0",
|
|
24
|
-
"@n8n/vitest-config": "1.21.0"
|
|
26
|
+
"vue-tsc": "^2.2.8"
|
|
25
27
|
},
|
|
26
28
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
27
29
|
"homepage": "https://n8n.io",
|
package/src/index.ts
CHANGED
|
@@ -6,3 +6,4 @@ export * as modalRegistry from './registries/modalRegistry';
|
|
|
6
6
|
export * from './registries/resourceRegistry';
|
|
7
7
|
export * as pushHandlerRegistry from './registries/pushHandlerRegistry';
|
|
8
8
|
export * as commandRegistry from './registries/commandRegistry';
|
|
9
|
+
export * as parameterInputRegistry from './registries/parameterInputRegistry';
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import type { Component } from 'vue';
|
|
4
|
+
|
|
5
|
+
import * as parameterInputRegistry from './parameterInputRegistry';
|
|
6
|
+
import type { ParameterInputContribution, ParameterInputType } from '../types/parameterInput';
|
|
7
|
+
|
|
8
|
+
describe('parameterInputRegistry', () => {
|
|
9
|
+
const resourceLocatorComponent = { name: 'TestResourceLocator' } as Component;
|
|
10
|
+
const workflowSelectorComponent = { name: 'TestWorkflowSelector' } as Component;
|
|
11
|
+
const asyncComponent = async (): Promise<Component> =>
|
|
12
|
+
await Promise.resolve({ name: 'AsyncTestInput' } as Component);
|
|
13
|
+
|
|
14
|
+
const resourceLocator: ParameterInputContribution = {
|
|
15
|
+
type: 'resourceLocator',
|
|
16
|
+
component: resourceLocatorComponent,
|
|
17
|
+
capabilities: { ownsExpressionRendering: true, ownsFromAiOverride: true, disableDrop: true },
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const workflowSelector: ParameterInputContribution = {
|
|
21
|
+
type: 'workflowSelector',
|
|
22
|
+
component: workflowSelectorComponent,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const lazyInput: ParameterInputContribution = {
|
|
26
|
+
type: 'filter',
|
|
27
|
+
component: asyncComponent,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
parameterInputRegistry.clear();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('register', () => {
|
|
35
|
+
it('should register a contribution under its parameter type', () => {
|
|
36
|
+
parameterInputRegistry.register(resourceLocator);
|
|
37
|
+
|
|
38
|
+
expect(parameterInputRegistry.has('resourceLocator')).toBe(true);
|
|
39
|
+
expect(parameterInputRegistry.get('resourceLocator')).toEqual(resourceLocator);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should register multiple contributions', () => {
|
|
43
|
+
parameterInputRegistry.register(resourceLocator);
|
|
44
|
+
parameterInputRegistry.register(workflowSelector);
|
|
45
|
+
|
|
46
|
+
expect(parameterInputRegistry.getAll().size).toBe(2);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should accept a lazy component factory', () => {
|
|
50
|
+
parameterInputRegistry.register(lazyInput);
|
|
51
|
+
|
|
52
|
+
expect(parameterInputRegistry.get('filter')?.component).toBe(asyncComponent);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should warn and skip when a different contribution claims a taken type', () => {
|
|
56
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
57
|
+
|
|
58
|
+
parameterInputRegistry.register(resourceLocator);
|
|
59
|
+
parameterInputRegistry.register({
|
|
60
|
+
type: 'resourceLocator',
|
|
61
|
+
component: workflowSelectorComponent,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
65
|
+
'Parameter input for type "resourceLocator" is already registered. Skipping.',
|
|
66
|
+
);
|
|
67
|
+
expect(parameterInputRegistry.get('resourceLocator')?.component).toBe(
|
|
68
|
+
resourceLocatorComponent,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
consoleSpy.mockRestore();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should treat a replay of the same contribution as a no-op', () => {
|
|
75
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
76
|
+
|
|
77
|
+
parameterInputRegistry.register(resourceLocator);
|
|
78
|
+
parameterInputRegistry.register(resourceLocator);
|
|
79
|
+
|
|
80
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
81
|
+
expect(parameterInputRegistry.getAll().size).toBe(1);
|
|
82
|
+
|
|
83
|
+
consoleSpy.mockRestore();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('get', () => {
|
|
88
|
+
it('should return undefined for an unregistered type', () => {
|
|
89
|
+
parameterInputRegistry.register(resourceLocator);
|
|
90
|
+
|
|
91
|
+
expect(parameterInputRegistry.get('string')).toBeUndefined();
|
|
92
|
+
expect(parameterInputRegistry.has('string')).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should return undefined once every contribution is cleared', () => {
|
|
96
|
+
parameterInputRegistry.register(resourceLocator);
|
|
97
|
+
parameterInputRegistry.clear();
|
|
98
|
+
|
|
99
|
+
expect(parameterInputRegistry.get('resourceLocator')).toBeUndefined();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('unregister', () => {
|
|
104
|
+
it('should remove a registered type', () => {
|
|
105
|
+
parameterInputRegistry.register(resourceLocator);
|
|
106
|
+
parameterInputRegistry.unregister('resourceLocator');
|
|
107
|
+
|
|
108
|
+
expect(parameterInputRegistry.has('resourceLocator')).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should let the type be claimed again after removal', () => {
|
|
112
|
+
parameterInputRegistry.register(resourceLocator);
|
|
113
|
+
parameterInputRegistry.unregister('resourceLocator');
|
|
114
|
+
parameterInputRegistry.register({
|
|
115
|
+
type: 'resourceLocator',
|
|
116
|
+
component: workflowSelectorComponent,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(parameterInputRegistry.get('resourceLocator')?.component).toBe(
|
|
120
|
+
workflowSelectorComponent,
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('getAll', () => {
|
|
126
|
+
it('should return a copy, so a caller cannot mutate the registry', () => {
|
|
127
|
+
parameterInputRegistry.register(resourceLocator);
|
|
128
|
+
|
|
129
|
+
parameterInputRegistry.getAll().delete('resourceLocator');
|
|
130
|
+
|
|
131
|
+
expect(parameterInputRegistry.has('resourceLocator')).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('reactivity', () => {
|
|
136
|
+
it('should re-evaluate a computed when a contribution is registered', () => {
|
|
137
|
+
const resolved = computed(() => parameterInputRegistry.get('resourceLocator'));
|
|
138
|
+
|
|
139
|
+
expect(resolved.value).toBeUndefined();
|
|
140
|
+
|
|
141
|
+
parameterInputRegistry.register(resourceLocator);
|
|
142
|
+
|
|
143
|
+
expect(resolved.value).toEqual(resourceLocator);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should not wrap the component in a reactive proxy', () => {
|
|
147
|
+
parameterInputRegistry.register(resourceLocator);
|
|
148
|
+
|
|
149
|
+
// Shallow registry: a reactive component logs "Vue received a Component
|
|
150
|
+
// that was made a reactive object" and breaks `<component :is>`.
|
|
151
|
+
expect(parameterInputRegistry.get('resourceLocator')?.component).toBe(
|
|
152
|
+
resourceLocatorComponent,
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('subscribe', () => {
|
|
158
|
+
it('should notify listeners on register and unregister', () => {
|
|
159
|
+
const listener =
|
|
160
|
+
vi.fn<(entries: Map<ParameterInputType, ParameterInputContribution>) => void>();
|
|
161
|
+
const unsubscribe = parameterInputRegistry.subscribe(listener);
|
|
162
|
+
|
|
163
|
+
parameterInputRegistry.register(resourceLocator);
|
|
164
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
165
|
+
expect(listener.mock.calls[0]?.[0].get('resourceLocator')).toEqual(resourceLocator);
|
|
166
|
+
|
|
167
|
+
parameterInputRegistry.unregister('resourceLocator');
|
|
168
|
+
expect(listener).toHaveBeenCalledTimes(2);
|
|
169
|
+
|
|
170
|
+
unsubscribe();
|
|
171
|
+
parameterInputRegistry.register(workflowSelector);
|
|
172
|
+
expect(listener).toHaveBeenCalledTimes(2);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should not notify when unregistering a type that was never claimed', () => {
|
|
176
|
+
const listener = vi.fn();
|
|
177
|
+
const unsubscribe = parameterInputRegistry.subscribe(listener);
|
|
178
|
+
|
|
179
|
+
parameterInputRegistry.unregister('string');
|
|
180
|
+
|
|
181
|
+
expect(listener).not.toHaveBeenCalled();
|
|
182
|
+
|
|
183
|
+
unsubscribe();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { shallowReactive } from 'vue';
|
|
2
|
+
|
|
3
|
+
import type { ParameterInputContribution, ParameterInputType } from '../types/parameterInput';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shallow-reactive so the render path can derive from the registry with a plain
|
|
7
|
+
* `computed`. Shallow on purpose: a contribution's `component` must not be
|
|
8
|
+
* turned into a reactive object.
|
|
9
|
+
*/
|
|
10
|
+
const parameterInputs = shallowReactive(new Map<ParameterInputType, ParameterInputContribution>());
|
|
11
|
+
const listeners = new Set<(entries: Map<ParameterInputType, ParameterInputContribution>) => void>();
|
|
12
|
+
|
|
13
|
+
export function getAll(): Map<ParameterInputType, ParameterInputContribution> {
|
|
14
|
+
return new Map(parameterInputs);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function notifyListeners(): void {
|
|
18
|
+
listeners.forEach((listener) => listener(getAll()));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Claim `contribution.type` for this component. One owner per type: the shell's
|
|
23
|
+
* built-in branch for that type is what an unclaimed type falls back to.
|
|
24
|
+
*/
|
|
25
|
+
export function register(contribution: ParameterInputContribution): void {
|
|
26
|
+
const existing = parameterInputs.get(contribution.type);
|
|
27
|
+
if (existing) {
|
|
28
|
+
// Replaying the same contribution is how a re-login re-runs registration —
|
|
29
|
+
// a no-op, not a collision. Only a different contribution claiming a taken
|
|
30
|
+
// type is worth warning about.
|
|
31
|
+
if (existing !== contribution) {
|
|
32
|
+
console.warn(
|
|
33
|
+
`Parameter input for type "${contribution.type}" is already registered. Skipping.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
parameterInputs.set(contribution.type, contribution);
|
|
40
|
+
notifyListeners();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function unregister(type: ParameterInputType): void {
|
|
44
|
+
if (parameterInputs.delete(type)) {
|
|
45
|
+
notifyListeners();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function get(type: ParameterInputType): ParameterInputContribution | undefined {
|
|
50
|
+
return parameterInputs.get(type);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function has(type: ParameterInputType): boolean {
|
|
54
|
+
return parameterInputs.has(type);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function subscribe(
|
|
58
|
+
listener: (entries: Map<ParameterInputType, ParameterInputContribution>) => void,
|
|
59
|
+
): () => void {
|
|
60
|
+
listeners.add(listener);
|
|
61
|
+
return () => {
|
|
62
|
+
listeners.delete(listener);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Remove all registered parameter inputs. Primarily for test isolation.
|
|
68
|
+
*/
|
|
69
|
+
export function clear(): void {
|
|
70
|
+
parameterInputs.clear();
|
|
71
|
+
notifyListeners();
|
|
72
|
+
}
|
|
@@ -43,4 +43,23 @@ describe('FrontendModuleDescription', () => {
|
|
|
43
43
|
expect(descriptor.commands?.[0]?.id).toBe('v2.open');
|
|
44
44
|
expect(descriptor.pushHandlers?.workflowActivated).toBeTypeOf('function');
|
|
45
45
|
});
|
|
46
|
+
|
|
47
|
+
it('accepts a parameter-input contribution with a lazy component', () => {
|
|
48
|
+
const descriptor: FrontendModuleDescription = {
|
|
49
|
+
id: 'inputs',
|
|
50
|
+
name: 'Inputs',
|
|
51
|
+
description: 'A descriptor contributing a parameter input',
|
|
52
|
+
icon: 'box',
|
|
53
|
+
parameterInputs: [
|
|
54
|
+
{
|
|
55
|
+
type: 'resourceLocator',
|
|
56
|
+
component: async () => await Promise.resolve({ name: 'ResourceLocator' }),
|
|
57
|
+
capabilities: { ownsExpressionRendering: true, disableDrop: true },
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
expect(descriptor.parameterInputs?.[0]?.type).toBe('resourceLocator');
|
|
63
|
+
expect(descriptor.parameterInputs?.[0]?.capabilities?.ownsFromAiOverride).toBeUndefined();
|
|
64
|
+
});
|
|
46
65
|
});
|
package/src/types/descriptor.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ModuleBanner } from './banner';
|
|
|
5
5
|
import type { CommandBarEntry } from './command';
|
|
6
6
|
import type { ModuleLocaleMessages } from './locale';
|
|
7
7
|
import type { ModalDefinition } from './modal';
|
|
8
|
+
import type { ParameterInputContribution } from './parameterInput';
|
|
8
9
|
import type { ModulePushHandlers } from './push';
|
|
9
10
|
import type { ResourceMetadata } from './resource';
|
|
10
11
|
import type { ModuleSetupContext } from './setup';
|
|
@@ -48,6 +49,12 @@ export type FrontendModuleDescription = {
|
|
|
48
49
|
commands?: CommandBarEntry[];
|
|
49
50
|
/** Global keyboard shortcuts. */
|
|
50
51
|
shortcuts?: ModuleShortcut[];
|
|
52
|
+
/**
|
|
53
|
+
* Custom parameter input components, resolved by `parameter.type` in the NDV
|
|
54
|
+
* parameter render path. One owner per type; an unclaimed type keeps the
|
|
55
|
+
* shell's built-in branch.
|
|
56
|
+
*/
|
|
57
|
+
parameterInputs?: ParameterInputContribution[];
|
|
51
58
|
/** Banners the module can contribute to the banner stack. */
|
|
52
59
|
banners?: ModuleBanner[];
|
|
53
60
|
/** Runs post-login, after the module is confirmed active. */
|
package/src/types/index.ts
CHANGED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { EventBus } from '@n8n/utils/event-bus';
|
|
2
|
+
import type {
|
|
3
|
+
INode,
|
|
4
|
+
INodeProperties,
|
|
5
|
+
NodeParameterValueType,
|
|
6
|
+
NodePropertyTypes,
|
|
7
|
+
} from 'n8n-workflow';
|
|
8
|
+
import type { Component } from 'vue';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The `parameter.type` value a module claims. Deliberately the closed
|
|
12
|
+
* `NodePropertyTypes` union: a key the frontend accepts but a node author cannot
|
|
13
|
+
* declare would be a false surface. Widening the union later is additive.
|
|
14
|
+
*/
|
|
15
|
+
export type ParameterInputType = NodePropertyTypes;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Props the shell passes to every contributed parameter input. The names and
|
|
19
|
+
* types match what `ParameterInput.vue` already passes to its built-in
|
|
20
|
+
* resource-locator branch, so an extracted component needs no adapter.
|
|
21
|
+
*/
|
|
22
|
+
export type ParameterInputProps = {
|
|
23
|
+
parameter: INodeProperties;
|
|
24
|
+
modelValue: NodeParameterValueType;
|
|
25
|
+
path: string;
|
|
26
|
+
node?: INode;
|
|
27
|
+
displayTitle: string;
|
|
28
|
+
isReadOnly: boolean;
|
|
29
|
+
isValueExpression: boolean;
|
|
30
|
+
expressionDisplayValue: string;
|
|
31
|
+
expressionComputedValue: unknown;
|
|
32
|
+
/** `undefined` until the async resolution of `loadOptionsDependsOn` settles. */
|
|
33
|
+
dependentParametersValues?: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* The parameter's validation issues, for an input that wants to place them
|
|
36
|
+
* itself. The shell also draws `ParameterIssues` below the input for every
|
|
37
|
+
* type outside the resource-locator family, so an input that renders these
|
|
38
|
+
* shows them twice.
|
|
39
|
+
*/
|
|
40
|
+
parameterIssues: string[];
|
|
41
|
+
droppable: boolean;
|
|
42
|
+
eventBus?: EventBus;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Events the shell listens for on a contributed parameter input. */
|
|
46
|
+
export type ParameterInputEmits = {
|
|
47
|
+
// Vue fixes the v-model event name; it cannot be camelCase.
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
49
|
+
'update:modelValue': [value: NodeParameterValueType];
|
|
50
|
+
modalOpenerClick: [];
|
|
51
|
+
focus: [];
|
|
52
|
+
blur: [];
|
|
53
|
+
drop: [value: string];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Capabilities the input takes over from `ParameterInputFull` / `ParameterInputWrapper`.
|
|
58
|
+
*
|
|
59
|
+
* These exist because `parameter.type` drives behaviour outside the render
|
|
60
|
+
* branch too — the expression toggle, the drop target and the from-AI override.
|
|
61
|
+
* An input that wins only the render branch would render correctly and still get
|
|
62
|
+
* the wrong capabilities. Every default keeps today's behaviour, so an input that
|
|
63
|
+
* needs none of it declares nothing.
|
|
64
|
+
*/
|
|
65
|
+
export type ParameterInputCapabilities = {
|
|
66
|
+
/**
|
|
67
|
+
* The input draws its own expression editor, so the shell does not swap in
|
|
68
|
+
* `ExpressionParameterInput` and does not show the expression selector for a
|
|
69
|
+
* parameter whose modes are list-only.
|
|
70
|
+
*/
|
|
71
|
+
ownsExpressionRendering?: boolean;
|
|
72
|
+
/** The input owns the from-AI override, so the shell hides its own toggle. */
|
|
73
|
+
ownsFromAiOverride?: boolean;
|
|
74
|
+
/** The shell refuses drag-and-drop onto the field. */
|
|
75
|
+
disableDrop?: boolean;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A component the shell can drive with `ParameterInputProps`. Typing the
|
|
80
|
+
* contribution against it makes the prop contract a compiler check rather than
|
|
81
|
+
* a comment: a component declaring an incompatible prop is rejected at the
|
|
82
|
+
* registry boundary.
|
|
83
|
+
*/
|
|
84
|
+
export type ParameterInputComponent = Component<ParameterInputProps>;
|
|
85
|
+
|
|
86
|
+
export type ParameterInputContribution = {
|
|
87
|
+
/** The `parameter.type` this entry renders. Also the registry key. */
|
|
88
|
+
type: ParameterInputType;
|
|
89
|
+
/**
|
|
90
|
+
* Lazy on purpose: a `() => import()` keeps the module's `*.module.ts`
|
|
91
|
+
* import-light (design §5.2) and keeps the component out of the shell chunk.
|
|
92
|
+
*
|
|
93
|
+
* A bare function here is read as the loader, following `ModalDefinition`. So a
|
|
94
|
+
* functional component must be wrapped (`defineComponent(fn)`) or it is called
|
|
95
|
+
* as a loader and never renders.
|
|
96
|
+
*/
|
|
97
|
+
component: ParameterInputComponent | (() => Promise<ParameterInputComponent>);
|
|
98
|
+
capabilities?: ParameterInputCapabilities;
|
|
99
|
+
};
|