@improba/page-builder 0.1.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/LICENSE +21 -0
- package/README.md +316 -0
- package/dist/index.cjs +33 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1 -0
- package/dist/index.js +4477 -0
- package/dist/index.js.map +1 -0
- package/package.json +93 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Improba
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# @improba/page-builder
|
|
2
|
+
|
|
3
|
+
Bibliothèque Vue 3 pour construire et afficher des pages à partir d’un arbre JSON. Elle fournit un **mode lecture** (rendu statique, compatible SSR) et un **mode édition** (éditeur WYSIWYG avec palette de composants, panneau de propriétés, glisser-déposer, undo/redo). Le backend envoie un seul contrat JSON (`IPageData`) ; le frontend le rend et, en mode édition, permet de le modifier visuellement.
|
|
4
|
+
|
|
5
|
+
**En bref :** installez le plugin Vue, fournissez des données `IPageData`, et utilisez `<PageBuilder>` en `mode="read"` pour l’affichage ou `mode="edit"` pour l’édition. Vous pouvez enregistrer vos propres composants (hero, cartes, etc.) et les utiliser comme blocs dans l’arbre.
|
|
6
|
+
|
|
7
|
+
## Aperçu
|
|
8
|
+
|
|
9
|
+
**Mode édition** — Éditeur WYSIWYG avec palette de composants, panneau de propriétés et prévisualisation responsive.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
**Mode lecture** — Rendu de la page sans interface d’édition (compatible SSR).
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
*Pour régénérer les captures : `docker compose -f docker/docker-compose.yml run --rm e2e sh -lc "npm install && npm run docs:screenshots"`.*
|
|
18
|
+
|
|
19
|
+
## Fonctionnalités
|
|
20
|
+
|
|
21
|
+
- **Mode lecture** — Rendu du contenu à partir d’un arbre JSON, compatible SSR. Intégrable dans Nuxt ou toute app Vue 3.
|
|
22
|
+
- **Mode édition** — Éditeur WYSIWYG avec palette de composants, panneau de propriétés, glisser-déposer, undo/redo et prévisualisation responsive (desktop / tablette / mobile).
|
|
23
|
+
- **Registre de composants** — Enregistrement de composants Vue personnalisés (props typées, slots, métadonnées d’édition). Livré avec des composants de mise en page et de contenu (PbColumn, PbRow, PbText, PbImage, etc.).
|
|
24
|
+
- **Contrat JSON unique** — Le backend envoie un seul payload `IPageData` ; le frontend le rend et l’édite. Séparation claire des responsabilités.
|
|
25
|
+
|
|
26
|
+
## Démarrage rapide
|
|
27
|
+
|
|
28
|
+
Pour un guide pas à pas (installation, premier rendu, mode édition, composants personnalisés), voir **[Quick Start](./docs/quickstart.md)**.
|
|
29
|
+
|
|
30
|
+
Résumé minimal :
|
|
31
|
+
|
|
32
|
+
### Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @improba/page-builder
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Setup
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { createApp } from 'vue';
|
|
42
|
+
import { PageBuilderPlugin } from '@improba/page-builder';
|
|
43
|
+
import '@improba/page-builder/style.css';
|
|
44
|
+
import App from './App.vue';
|
|
45
|
+
|
|
46
|
+
const app = createApp(App);
|
|
47
|
+
app.use(PageBuilderPlugin);
|
|
48
|
+
app.mount('#app');
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Usage
|
|
52
|
+
|
|
53
|
+
```vue
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
import { PageBuilder } from '@improba/page-builder';
|
|
56
|
+
import type { IPageData } from '@improba/page-builder';
|
|
57
|
+
|
|
58
|
+
const pageData: IPageData = {
|
|
59
|
+
meta: { id: '1', name: 'Home', url: '/', status: 'published' },
|
|
60
|
+
content: {
|
|
61
|
+
id: 0,
|
|
62
|
+
name: 'PbColumn',
|
|
63
|
+
slot: null,
|
|
64
|
+
props: { gap: '16px' },
|
|
65
|
+
children: [
|
|
66
|
+
{
|
|
67
|
+
id: 1,
|
|
68
|
+
name: 'PbText',
|
|
69
|
+
slot: 'default',
|
|
70
|
+
props: { content: '<h1>Hello World</h1>' },
|
|
71
|
+
children: [],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
layout: { id: 100, name: 'PbContainer', slot: null, props: {}, children: [] },
|
|
76
|
+
maxId: 100,
|
|
77
|
+
variables: {},
|
|
78
|
+
};
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<PageBuilder :page-data="pageData" mode="read" />
|
|
83
|
+
</template>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Edit Mode
|
|
87
|
+
|
|
88
|
+
```vue
|
|
89
|
+
<template>
|
|
90
|
+
<PageBuilder
|
|
91
|
+
:page-data="pageData"
|
|
92
|
+
mode="edit"
|
|
93
|
+
@save="handleSave"
|
|
94
|
+
@change="handleChange"
|
|
95
|
+
/>
|
|
96
|
+
</template>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Custom Components
|
|
100
|
+
|
|
101
|
+
Register your own components for the page builder:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { registerComponent } from '@improba/page-builder';
|
|
105
|
+
import type { IComponentDefinition } from '@improba/page-builder';
|
|
106
|
+
import MyHero from './MyHero.vue';
|
|
107
|
+
|
|
108
|
+
const myHero: IComponentDefinition = {
|
|
109
|
+
name: 'MyHero',
|
|
110
|
+
label: 'Hero Banner',
|
|
111
|
+
description: 'Full-width hero section with title and CTA.',
|
|
112
|
+
category: 'content',
|
|
113
|
+
component: MyHero,
|
|
114
|
+
slots: [{ name: 'default', label: 'Content' }],
|
|
115
|
+
editableProps: [
|
|
116
|
+
{ key: 'title', label: 'Title', type: 'text', required: true },
|
|
117
|
+
{ key: 'backgroundImage', label: 'Background', type: 'image' },
|
|
118
|
+
],
|
|
119
|
+
defaultProps: { title: 'Hero Title' },
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
registerComponent(myHero);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Built-in Components
|
|
126
|
+
|
|
127
|
+
| Component | Category | Description |
|
|
128
|
+
|-----------|----------|-------------|
|
|
129
|
+
| `PbColumn` | layout | Vertical flex container |
|
|
130
|
+
| `PbRow` | layout | Horizontal flex container |
|
|
131
|
+
| `PbSection` | layout | Full-width section with background |
|
|
132
|
+
| `PbContainer` | layout | Centered max-width container |
|
|
133
|
+
| `PbText` | content | Text/HTML block |
|
|
134
|
+
| `PbImage` | media | Image with sizing options |
|
|
135
|
+
|
|
136
|
+
## JSON Format
|
|
137
|
+
|
|
138
|
+
The page builder consumes a single `IPageData` JSON:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
interface IPageData {
|
|
142
|
+
meta: { id: string; name: string; url: string; status: string };
|
|
143
|
+
content: INode; // The page content tree
|
|
144
|
+
layout: INode; // The page layout wrapper
|
|
145
|
+
maxId: number; // For generating unique IDs
|
|
146
|
+
variables: Record<string, string>; // Template variables
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface INode {
|
|
150
|
+
id: number;
|
|
151
|
+
name: string; // Must match a registered component
|
|
152
|
+
slot: string | null; // Target slot in parent
|
|
153
|
+
props: Record<string, unknown>;
|
|
154
|
+
children: INode[];
|
|
155
|
+
readonly?: boolean;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Props support template variables: `{{ PAGE_NAME }}` is replaced at render time.
|
|
160
|
+
|
|
161
|
+
## Development
|
|
162
|
+
|
|
163
|
+
**All commands run through Docker** (see [AGENTS.md](./AGENTS.md) for details):
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Start dev server with hot reload
|
|
167
|
+
docker compose -f docker/docker-compose.yml up dev
|
|
168
|
+
|
|
169
|
+
# Run tests
|
|
170
|
+
docker compose -f docker/docker-compose.yml run --rm test
|
|
171
|
+
|
|
172
|
+
# Run Playwright end-to-end tests
|
|
173
|
+
docker compose -f docker/docker-compose.yml run --rm e2e sh -lc "npm install && npm run test:e2e"
|
|
174
|
+
|
|
175
|
+
# Build the library
|
|
176
|
+
docker compose -f docker/docker-compose.yml run --rm build
|
|
177
|
+
|
|
178
|
+
# Generate API reference docs (TypeDoc)
|
|
179
|
+
docker compose -f docker/docker-compose.yml run --rm dev npm run docs:api
|
|
180
|
+
|
|
181
|
+
# Install a new dependency
|
|
182
|
+
docker compose -f docker/docker-compose.yml run --rm dev npm install <package>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The dev server starts a Vite playground at `http://localhost:5173` with a demo page for testing components.
|
|
186
|
+
|
|
187
|
+
### End-to-End Tests (Playwright)
|
|
188
|
+
|
|
189
|
+
E2E tests live in `tests/e2e/` and run against the playground via Playwright's `webServer` integration.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Full E2E suite in Docker/CI
|
|
193
|
+
docker compose -f docker/docker-compose.yml run --rm e2e sh -lc "npm install && npm run test:e2e"
|
|
194
|
+
|
|
195
|
+
# Smoke workflow only (mode switch -> node selection -> prop edit -> save)
|
|
196
|
+
docker compose -f docker/docker-compose.yml run --rm e2e sh -lc "npm install && npm run test:e2e:smoke"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The `e2e` Docker image already includes Playwright browsers. If you need to (re)install browser binaries explicitly, run:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
docker compose -f docker/docker-compose.yml run --rm e2e npm run e2e:install
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Documentation
|
|
206
|
+
|
|
207
|
+
Toute la documentation se trouve dans `docs/` :
|
|
208
|
+
|
|
209
|
+
| Document | Description |
|
|
210
|
+
|----------|-------------|
|
|
211
|
+
| **[Quick Start](./docs/quickstart.md)** | Démarrer rapidement : installation, configuration, premier rendu, mode édition, API |
|
|
212
|
+
| **[Intégration backend](./docs/backend-integration.md)** | Routes attendues, contrats (IPageData, IPageSavePayload), validation, médias, sécurité |
|
|
213
|
+
| **[Architecture](./docs/architecture/)** | Vue d’ensemble, schéma JSON, système de composants, pipeline de rendu, architecture du mode édition |
|
|
214
|
+
| **[Fonctionnalités](./docs/features/)** | Mode lecture, mode édition, registre de composants, format JSON |
|
|
215
|
+
| **[Conventions](./docs/conventions/)** | Style de code, workflow git |
|
|
216
|
+
| **[Roadmap](./docs/plans/roadmap.md)** | Phases et jalons |
|
|
217
|
+
| **[Référence API](./docs/api/)** | Sortie TypeDoc (types et fonctions publics) |
|
|
218
|
+
|
|
219
|
+
Pour régénérer la référence API :
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
docker compose -f docker/docker-compose.yml run --rm dev npm run docs:api
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Releases
|
|
226
|
+
|
|
227
|
+
Releases are **tag-based**. Pushing a tag `release-vX.Y.Z` triggers the GitHub Actions workflow (quality gate + publish to npm).
|
|
228
|
+
|
|
229
|
+
### Creating a release
|
|
230
|
+
|
|
231
|
+
From the repo root, run the release script with the desired bump (`patch` is the default):
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
./scripts/release.sh [major|minor|patch]
|
|
235
|
+
# Examples:
|
|
236
|
+
./scripts/release.sh # 0.1.0 → 0.1.1 (patch)
|
|
237
|
+
./scripts/release.sh minor # 0.1.1 → 0.2.0
|
|
238
|
+
./scripts/release.sh major # 0.2.0 → 1.0.0
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The script bumps the version in `package.json`, commits, creates the tag `release-vX.Y.Z`, and pushes the branch and tag. The CI then runs the quality gate and publishes to npm. See [Git Workflow — Releases](./docs/conventions/git-workflow.md#releases) for details.
|
|
242
|
+
|
|
243
|
+
### Required repository secrets
|
|
244
|
+
|
|
245
|
+
- `NPM_TOKEN` (npm automation token with publish permission on `@improba/page-builder`)
|
|
246
|
+
|
|
247
|
+
### Local release verification and manual publish (Docker)
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# Full release safety gate (typecheck + tests + build + types + docs)
|
|
251
|
+
docker compose -f docker/docker-compose.yml run --rm dev npm run release:prepare
|
|
252
|
+
|
|
253
|
+
# Inspect package contents before publish
|
|
254
|
+
docker compose -f docker/docker-compose.yml run --rm dev npm run release:dry-run
|
|
255
|
+
|
|
256
|
+
# Publish to npm manually (requires NPM_TOKEN in .env at project root)
|
|
257
|
+
source .env && docker compose -f docker/docker-compose.yml run --rm \
|
|
258
|
+
-e NPM_TOKEN="$NPM_TOKEN" \
|
|
259
|
+
dev sh -lc 'printf "//registry.npmjs.org/:_authToken=%s\n" "$NPM_TOKEN" > /tmp/.npmrc && npm publish --userconfig /tmp/.npmrc --access public'
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
See [Git Workflow — Releases](./docs/conventions/git-workflow.md#releases) for the full release process (tag-based CI and manual publish).
|
|
263
|
+
|
|
264
|
+
## API Reference
|
|
265
|
+
|
|
266
|
+
### Vue Plugin
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
app.use(PageBuilderPlugin, {
|
|
270
|
+
components: [], // Additional IComponentDefinition[]
|
|
271
|
+
registerBuiltIn: true, // Register PbColumn, PbRow, etc.
|
|
272
|
+
globalName: 'PageBuilder', // Global component name (false to skip)
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Registry Functions
|
|
277
|
+
|
|
278
|
+
| Function | Description |
|
|
279
|
+
|----------|-------------|
|
|
280
|
+
| `registerComponent(def)` | Register a single component |
|
|
281
|
+
| `registerComponents(defs)` | Register multiple components |
|
|
282
|
+
| `replaceComponent(def)` | Override an existing registration |
|
|
283
|
+
| `unregisterComponent(name)` | Remove a registration |
|
|
284
|
+
| `getComponent(name)` | Get definition by name |
|
|
285
|
+
| `resolveComponent(name)` | Get Vue component (throws if missing) |
|
|
286
|
+
| `getRegisteredComponents()` | Get all definitions |
|
|
287
|
+
| `getComponentsByCategory()` | Get definitions grouped by category |
|
|
288
|
+
| `hasComponent(name)` | Check if registered |
|
|
289
|
+
| `clearRegistry()` | Remove all (testing) |
|
|
290
|
+
|
|
291
|
+
### Tree Utilities
|
|
292
|
+
|
|
293
|
+
| Function | Description |
|
|
294
|
+
|----------|-------------|
|
|
295
|
+
| `findNodeById(root, id)` | Find node in tree |
|
|
296
|
+
| `findParent(root, childId)` | Find parent of node |
|
|
297
|
+
| `removeNode(root, id)` | Remove node from tree |
|
|
298
|
+
| `insertNode(root, parentId, node, index, slot)` | Insert node |
|
|
299
|
+
| `moveNode(root, nodeId, parentId, index, slot)` | Move node |
|
|
300
|
+
| `createNode(id, name, options)` | Create new node |
|
|
301
|
+
| `walkTree(root, visitor)` | Depth-first traversal |
|
|
302
|
+
| `cloneTree(node)` | Deep clone |
|
|
303
|
+
| `interpolateProps(props, vars)` | Replace template variables |
|
|
304
|
+
|
|
305
|
+
### Composables
|
|
306
|
+
|
|
307
|
+
| Composable | Purpose |
|
|
308
|
+
|------------|---------|
|
|
309
|
+
| `usePageBuilder(options)` | Core state management (mode, content, history) |
|
|
310
|
+
| `useEditor()` | Editor UI state (selection, drawers, viewport) |
|
|
311
|
+
| `useNodeTree(options)` | Tree mutation operations |
|
|
312
|
+
| `useDragDrop()` | Drag-and-drop interaction state |
|
|
313
|
+
|
|
314
|
+
## License
|
|
315
|
+
|
|
316
|
+
MIT
|