@vc-shell/framework 1.1.82 → 1.1.83-alpha.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/CHANGELOG.md +10 -0
- package/core/plugins/extension-points/ExtensionSlot.vue +23 -0
- package/core/plugins/extension-points/README.md +406 -0
- package/core/plugins/extension-points/index.ts +8 -0
- package/core/plugins/extension-points/migration-examples.md +613 -0
- package/core/plugins/extension-points/simple-extensions.ts +148 -0
- package/core/plugins/index.ts +1 -0
- package/dist/core/plugins/extension-points/ExtensionSlot.vue.d.ts +6 -0
- package/dist/core/plugins/extension-points/ExtensionSlot.vue.d.ts.map +1 -0
- package/dist/core/plugins/extension-points/index.d.ts +3 -0
- package/dist/core/plugins/extension-points/index.d.ts.map +1 -0
- package/dist/core/plugins/extension-points/simple-extensions.d.ts +29 -0
- package/dist/core/plugins/extension-points/simple-extensions.d.ts.map +1 -0
- package/dist/core/plugins/index.d.ts +1 -0
- package/dist/core/plugins/index.d.ts.map +1 -1
- package/dist/framework.js +4430 -4366
- package/dist/shared/composables/useModificationTracker/index.d.ts +5 -0
- package/dist/shared/composables/useModificationTracker/index.d.ts.map +1 -1
- package/dist/shared/pages/LoginPage/components/login/Login.vue.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -5
- package/shared/composables/useModificationTracker/index.ts +6 -0
- package/shared/pages/LoginPage/components/login/Login.vue +5 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## [1.1.83-alpha.0](https://github.com/VirtoCommerce/vc-shell/compare/v1.1.82...v1.1.83-alpha.0) (2025-09-24)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **extension-points:** introduce new extension system with composables for slot management and data exchange ([b427cd4](https://github.com/VirtoCommerce/vc-shell/commit/b427cd4a141d4dc39c454b12259b2a8c566179b9))
|
|
7
|
+
* **useModificationTracker:** add pristineValue reference to track original state ([adaf67d](https://github.com/VirtoCommerce/vc-shell/commit/adaf67d34ceac0b7b490c2906a7b3f25b57c32e1))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
1
11
|
## [1.1.82](https://github.com/VirtoCommerce/vc-shell/compare/v1.1.81...v1.1.82) (2025-09-23)
|
|
2
12
|
|
|
3
13
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<template
|
|
3
|
+
v-for="extension in components"
|
|
4
|
+
:key="extension.id"
|
|
5
|
+
>
|
|
6
|
+
<component
|
|
7
|
+
:is="extension.component"
|
|
8
|
+
v-bind="extension.props || {}"
|
|
9
|
+
/>
|
|
10
|
+
</template>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
import { useExtensionSlot } from './simple-extensions';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const props = defineProps<Props>();
|
|
21
|
+
|
|
22
|
+
const { components } = useExtensionSlot(props.name);
|
|
23
|
+
</script>
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
# 🔌 Extension Points System
|
|
2
|
+
|
|
3
|
+
A simple and powerful extension system for VirtoCommerce Shell, built on Vue 3 composables.
|
|
4
|
+
|
|
5
|
+
## 📋 Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Quick Start](#-quick-start)
|
|
8
|
+
- [API Documentation](#-api-documentation)
|
|
9
|
+
- [Migration from Current System](#-migration-from-current-system)
|
|
10
|
+
- [Usage Examples](#-usage-examples)
|
|
11
|
+
- [Best Practices](#-best-practices)
|
|
12
|
+
|
|
13
|
+
## 🚀 Quick Start
|
|
14
|
+
|
|
15
|
+
### 1. Adding a slot to your component
|
|
16
|
+
|
|
17
|
+
```vue
|
|
18
|
+
<template>
|
|
19
|
+
<div>
|
|
20
|
+
<!-- Your content -->
|
|
21
|
+
<h1>Page Title</h1>
|
|
22
|
+
|
|
23
|
+
<!-- Extension point -->
|
|
24
|
+
<ExtensionSlot name="page-after-title" />
|
|
25
|
+
|
|
26
|
+
<!-- Rest of content -->
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import { ExtensionSlot } from '@vc-shell/framework/core/plugins/extension-points';
|
|
32
|
+
</script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Registering a component in a slot
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// In any module
|
|
39
|
+
import { useExtensionSlot } from '@vc-shell/framework/core/plugins/extension-points';
|
|
40
|
+
import MyComponent from './MyComponent.vue';
|
|
41
|
+
|
|
42
|
+
const { addComponent } = useExtensionSlot('page-after-title');
|
|
43
|
+
|
|
44
|
+
addComponent({
|
|
45
|
+
id: 'my-custom-component',
|
|
46
|
+
component: MyComponent,
|
|
47
|
+
props: { message: 'Hello from extension!' },
|
|
48
|
+
priority: 10,
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Data exchange between modules
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// Data provider module
|
|
56
|
+
import { useExtensionData } from '@vc-shell/framework/core/plugins/extension-points';
|
|
57
|
+
|
|
58
|
+
const { updateData } = useExtensionData('user-profile');
|
|
59
|
+
updateData({ userId: '123', userName: 'John Doe' });
|
|
60
|
+
|
|
61
|
+
// Data consumer module
|
|
62
|
+
const { data } = useExtensionData('user-profile');
|
|
63
|
+
console.log(data.value.userName); // 'John Doe'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 📚 API Documentation
|
|
67
|
+
|
|
68
|
+
### useExtensionSlot(slotName: string)
|
|
69
|
+
|
|
70
|
+
Composable for working with extension slots.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const {
|
|
74
|
+
components, // computed<ExtensionComponent[]> - list of components
|
|
75
|
+
addComponent, // (component: ExtensionComponent) => void
|
|
76
|
+
removeComponent, // (componentId: string) => void
|
|
77
|
+
hasComponents, // computed<boolean> - whether slot has components
|
|
78
|
+
} = useExtensionSlot('slot-name');
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### ExtensionComponent interface:
|
|
82
|
+
```typescript
|
|
83
|
+
interface ExtensionComponent {
|
|
84
|
+
id: string; // unique component ID
|
|
85
|
+
component: Component; // Vue component
|
|
86
|
+
props?: Record<string, unknown>; // props for component
|
|
87
|
+
priority?: number; // priority (lower = higher)
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### useExtensionData(namespace: string)
|
|
92
|
+
|
|
93
|
+
Composable for data exchange between modules.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const {
|
|
97
|
+
data, // computed<Record<string, any>> - reactive data
|
|
98
|
+
updateData, // (newData: Record<string, any>) => void - merge data
|
|
99
|
+
setData, // (newData: Record<string, any>) => void - replace data
|
|
100
|
+
getValue, // (key: string, defaultValue?: any) => any
|
|
101
|
+
setValue, // (key: string, value: any) => void
|
|
102
|
+
} = useExtensionData('namespace');
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### useExtensions()
|
|
106
|
+
|
|
107
|
+
Composable for global extension system management.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const {
|
|
111
|
+
getAllSlots, // () => string[] - all slots
|
|
112
|
+
getSlotComponents, // (slotName: string) => ExtensionComponent[]
|
|
113
|
+
getAllData, // () => Record<string, any> - all data
|
|
114
|
+
getNamespaceData, // (namespace: string) => Record<string, any>
|
|
115
|
+
clearSlot, // (slotName: string) => void
|
|
116
|
+
clearData, // (namespace: string) => void
|
|
117
|
+
} = useExtensions();
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 🔄 Migration from Current System
|
|
121
|
+
|
|
122
|
+
### Before (current system):
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// registration-module/index.ts - old system
|
|
126
|
+
export default {
|
|
127
|
+
install(app: App) {
|
|
128
|
+
// Complex setup...
|
|
129
|
+
},
|
|
130
|
+
extensions: {
|
|
131
|
+
inbound: {
|
|
132
|
+
"registration-form": useRegistrationForm(),
|
|
133
|
+
},
|
|
134
|
+
outbound: {
|
|
135
|
+
"login-after-form": [{ id: "RegistrationButton", component: RegistrationButton }],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```vue
|
|
142
|
+
<!-- Login.vue - old system -->
|
|
143
|
+
<template>
|
|
144
|
+
<!-- Extensions after form -->
|
|
145
|
+
<template
|
|
146
|
+
v-for="extension in afterLoginFormExtensions"
|
|
147
|
+
:key="extension.id"
|
|
148
|
+
>
|
|
149
|
+
<div class="vc-login-page__extension">
|
|
150
|
+
<component :is="extension.component" />
|
|
151
|
+
</div>
|
|
152
|
+
</template>
|
|
153
|
+
</template>
|
|
154
|
+
|
|
155
|
+
<script setup>
|
|
156
|
+
import { inject } from "vue";
|
|
157
|
+
import { extensionsHelperSymbol } from "../extensions-helper";
|
|
158
|
+
|
|
159
|
+
const extensionsHelper = inject(extensionsHelperSymbol);
|
|
160
|
+
const afterLoginFormExtensions = computed(
|
|
161
|
+
(): ExtensionPoint[] =>
|
|
162
|
+
(extensionsHelper?.getOutboundExtensions("login-after-form") as ExtensionPoint[]) || []
|
|
163
|
+
);
|
|
164
|
+
</script>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### After (new system):
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// registration-module/index.ts - new system
|
|
171
|
+
import { useExtensionSlot } from '@vc-shell/framework/core/plugins/extension-points';
|
|
172
|
+
import RegistrationButton from './RegistrationButton.vue';
|
|
173
|
+
|
|
174
|
+
export default {
|
|
175
|
+
install() {
|
|
176
|
+
const { addComponent } = useExtensionSlot('login-after-form');
|
|
177
|
+
|
|
178
|
+
addComponent({
|
|
179
|
+
id: 'registration-button',
|
|
180
|
+
component: RegistrationButton,
|
|
181
|
+
priority: 10,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```vue
|
|
188
|
+
<!-- Login.vue - new system -->
|
|
189
|
+
<template>
|
|
190
|
+
<!-- One simple component instead of complex logic -->
|
|
191
|
+
<ExtensionSlot name="login-after-form" />
|
|
192
|
+
</template>
|
|
193
|
+
|
|
194
|
+
<script setup>
|
|
195
|
+
import { ExtensionSlot } from '@vc-shell/framework/core/plugins/extension-points';
|
|
196
|
+
</script>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## 📖 Detailed Registration Migration Example
|
|
200
|
+
|
|
201
|
+
### Step 1: Update Login.vue
|
|
202
|
+
|
|
203
|
+
```vue
|
|
204
|
+
<!-- framework/shared/pages/LoginPage/components/login/Login.vue -->
|
|
205
|
+
<template>
|
|
206
|
+
<VcLoginForm>
|
|
207
|
+
<template v-if="isLogin">
|
|
208
|
+
<VcForm @submit.prevent="login">
|
|
209
|
+
<!-- Existing form fields -->
|
|
210
|
+
<Field v-slot="{ errorMessage, handleChange, errors }" ...>
|
|
211
|
+
<VcInput ... />
|
|
212
|
+
</Field>
|
|
213
|
+
<!-- ... other fields ... -->
|
|
214
|
+
</VcForm>
|
|
215
|
+
|
|
216
|
+
<!-- External providers -->
|
|
217
|
+
<div v-if="loginProviders && loginProviders.length" ...>
|
|
218
|
+
<ExternalProviders :providers="loginProviders" />
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<!-- 🎯 REPLACE complex system with simple slot -->
|
|
222
|
+
<ExtensionSlot name="login-after-form" />
|
|
223
|
+
</template>
|
|
224
|
+
</VcLoginForm>
|
|
225
|
+
</template>
|
|
226
|
+
|
|
227
|
+
<script setup lang="ts">
|
|
228
|
+
// Remove:
|
|
229
|
+
// import { extensionsHelperSymbol } from "../extensions-helper";
|
|
230
|
+
// const extensionsHelper = inject(extensionsHelperSymbol);
|
|
231
|
+
// const afterLoginFormExtensions = computed(...);
|
|
232
|
+
|
|
233
|
+
// Add:
|
|
234
|
+
import { ExtensionSlot } from '@vc-shell/framework/core/plugins/extension-points';
|
|
235
|
+
|
|
236
|
+
// Rest of the code remains unchanged
|
|
237
|
+
const { signIn, loading, user } = useUserManagement();
|
|
238
|
+
// ...
|
|
239
|
+
</script>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Step 2: Simplify Registration Module
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
// vc-module-marketplace-registration/src/.../registration/index.ts
|
|
246
|
+
|
|
247
|
+
// Before: Complex extensions object
|
|
248
|
+
export default {
|
|
249
|
+
extensions: {
|
|
250
|
+
inbound: {
|
|
251
|
+
"registration-form": useRegistrationForm(),
|
|
252
|
+
},
|
|
253
|
+
outbound: {
|
|
254
|
+
"login-after-form": [{ id: "RegistrationButton", component: RegistrationButton }],
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// After: Simple composable calls
|
|
260
|
+
import { useExtensionSlot, useExtensionData } from '@vc-shell/framework/core/plugins/extension-points';
|
|
261
|
+
import RegistrationButton from './components/RegistrationButton.vue';
|
|
262
|
+
|
|
263
|
+
export default {
|
|
264
|
+
install() {
|
|
265
|
+
// 🎯 NEW: Simple component registration
|
|
266
|
+
const { addComponent } = useExtensionSlot('login-after-form');
|
|
267
|
+
|
|
268
|
+
addComponent({
|
|
269
|
+
id: 'registration-button',
|
|
270
|
+
component: RegistrationButton,
|
|
271
|
+
props: {
|
|
272
|
+
text: 'Register',
|
|
273
|
+
},
|
|
274
|
+
priority: 10,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Provide registration form data
|
|
278
|
+
const { updateData } = useExtensionData('registration-form');
|
|
279
|
+
updateData({
|
|
280
|
+
fields: [
|
|
281
|
+
{
|
|
282
|
+
name: 'firstName',
|
|
283
|
+
type: 'text',
|
|
284
|
+
component: 'VcInput',
|
|
285
|
+
label: 'VCMP_VENDOR_REGISTRATION.LABELS.FIRST_NAME',
|
|
286
|
+
required: true,
|
|
287
|
+
priority: 10,
|
|
288
|
+
},
|
|
289
|
+
// ... other fields
|
|
290
|
+
],
|
|
291
|
+
});
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## 💡 Usage Examples
|
|
297
|
+
|
|
298
|
+
### Application-level Customization
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// main.ts or any app module file
|
|
302
|
+
import { useExtensionSlot, useExtensionData } from '@vc-shell/framework/core/plugins/extension-points';
|
|
303
|
+
import CustomButton from './components/CustomButton.vue';
|
|
304
|
+
|
|
305
|
+
// Replace registration button with custom one
|
|
306
|
+
const { addComponent } = useExtensionSlot('login-after-form');
|
|
307
|
+
addComponent({
|
|
308
|
+
id: 'registration-button', // same ID - will replace existing
|
|
309
|
+
component: CustomButton,
|
|
310
|
+
props: {
|
|
311
|
+
text: 'Create Account',
|
|
312
|
+
variant: 'primary',
|
|
313
|
+
showIcon: true,
|
|
314
|
+
},
|
|
315
|
+
priority: 10,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Add custom fields to registration form
|
|
319
|
+
const { data } = useExtensionData('registration-form');
|
|
320
|
+
const { extendForm } = useRegistrationForm();
|
|
321
|
+
|
|
322
|
+
extendForm([
|
|
323
|
+
{
|
|
324
|
+
name: 'companyTaxId',
|
|
325
|
+
type: 'text',
|
|
326
|
+
component: 'VcInput',
|
|
327
|
+
label: 'Company Tax ID',
|
|
328
|
+
required: true,
|
|
329
|
+
priority: 25, // between lastName (20) and organizationName (30)
|
|
330
|
+
}
|
|
331
|
+
]);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Adding Slots to Blades
|
|
335
|
+
|
|
336
|
+
```vue
|
|
337
|
+
<!-- seller-details-edit.vue -->
|
|
338
|
+
<template>
|
|
339
|
+
<VcBlade>
|
|
340
|
+
<VcContainer>
|
|
341
|
+
<!-- Main cards -->
|
|
342
|
+
<VcRow>
|
|
343
|
+
<VcCol><!-- Info card --></VcCol>
|
|
344
|
+
<VcCol><!-- Address card --></VcCol>
|
|
345
|
+
|
|
346
|
+
<!-- 🎯 Slot for additional cards -->
|
|
347
|
+
<ExtensionSlot name="seller-details-additional-cards" />
|
|
348
|
+
</VcRow>
|
|
349
|
+
|
|
350
|
+
<!-- 🎯 Slot for additional sections -->
|
|
351
|
+
<ExtensionSlot name="seller-details-sections" />
|
|
352
|
+
</VcContainer>
|
|
353
|
+
</VcBlade>
|
|
354
|
+
</template>
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## ✅ Best Practices
|
|
358
|
+
|
|
359
|
+
### 1. Slot Naming
|
|
360
|
+
- Use kebab-case: `seller-details-cards`
|
|
361
|
+
- Include context: `login-after-form`, `profile-before-save`
|
|
362
|
+
- Be descriptive: `product-listing-filters`
|
|
363
|
+
|
|
364
|
+
### 2. Priorities
|
|
365
|
+
- Use multiples of 10: 10, 20, 30, ...
|
|
366
|
+
- Leave room for insertions: between 10 and 30 you can insert 20
|
|
367
|
+
- Document priorities in comments
|
|
368
|
+
|
|
369
|
+
### 3. Component IDs
|
|
370
|
+
- Use unique IDs: `module-name-component-purpose`
|
|
371
|
+
- Avoid generic names: `button`, `form`
|
|
372
|
+
- Use for replacement: same ID = component replacement
|
|
373
|
+
|
|
374
|
+
### 4. Data Namespaces
|
|
375
|
+
- Group by functionality: `user-profile`, `order-details`
|
|
376
|
+
- Avoid conflicts: use module prefixes
|
|
377
|
+
- Document data structure
|
|
378
|
+
|
|
379
|
+
### 5. Performance
|
|
380
|
+
- Components are lazy-loaded by default
|
|
381
|
+
- Don't overuse number of slots
|
|
382
|
+
- Use `v-if` with `hasComponents` for conditional rendering
|
|
383
|
+
|
|
384
|
+
```vue
|
|
385
|
+
<template>
|
|
386
|
+
<div v-if="hasComponents" class="extensions-section">
|
|
387
|
+
<ExtensionSlot name="optional-content" />
|
|
388
|
+
</div>
|
|
389
|
+
</template>
|
|
390
|
+
|
|
391
|
+
<script setup>
|
|
392
|
+
const { hasComponents } = useExtensionSlot('optional-content');
|
|
393
|
+
</script>
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## 🎯 Conclusion
|
|
397
|
+
|
|
398
|
+
The new extension system provides:
|
|
399
|
+
|
|
400
|
+
- ✅ **Simplicity** - 3 main functions instead of complex hierarchy
|
|
401
|
+
- ✅ **Flexibility** - easy component replacement and extension
|
|
402
|
+
- ✅ **Performance** - minimal overhead, Vue 3 optimizations
|
|
403
|
+
- ✅ **TypeScript** - full type safety
|
|
404
|
+
- ✅ **Backward compatibility** - doesn't break existing code
|
|
405
|
+
|
|
406
|
+
The system is ready to use and allows easy migration of existing extensions!
|