Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f05cf9e
feat(custom-ui): React-parity provider layer for 2.0.0-beta.2
goodlime Jun 20, 2026
45936e8
docs: custom-UI demo, README, and CHANGELOG for 2.0.0-beta.2
goodlime Jun 20, 2026
2ccdfaf
docs(demo): add redesign spec and reference mockup
goodlime Jun 20, 2026
d318797
style(demo): add tokenized design system (light + dark) to global sty…
goodlime Jun 20, 2026
0bf0285
style(demo): load Inter + JetBrains Mono web fonts
goodlime Jun 20, 2026
03a9376
feat(demo): add persisted light/dark theme toggle
goodlime Jun 20, 2026
1e5b007
style(demo): rewrite custom-UI chrome to consume design tokens
goodlime Jun 20, 2026
bdaa7eb
style(demo): theme GrapesJS default-mode panels to match custom UI
goodlime Jun 20, 2026
4e843e1
feat(demo): segmented mode switch and slide-up HTML export dock
goodlime Jun 20, 2026
055722d
fix(demo): persist project across Custom/Default switch + auto-select…
goodlime Jun 20, 2026
ecd78e6
feat(demo): make the Styles inspector editable + polish Selectors panel
goodlime Jun 20, 2026
5dabd77
feat(demo): icon block tiles in the custom-UI block panel
goodlime Jun 20, 2026
e319074
fix(demo): reset block-drag state on sorter:drag:end + ghost shows bl…
goodlime Jun 20, 2026
a03345c
fix(demo): hide textnode layers + use GrapesJS's real drop indicator
goodlime Jun 20, 2026
f28ad2d
feat(demo): typed style inspector (sliders, swatches, segmented align…
goodlime Jun 21, 2026
817f416
fix(demo): keep default-UI device selector inside its command bar
goodlime Jun 21, 2026
b551966
chore(demo): remove redesign scaffolding docs
goodlime Jun 21, 2026
a8fde5d
docs: date the 2.0.0-beta.2 changelog entry
goodlime Jun 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 74 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,78 @@
# Changelog

## 2.0.0-alpha.1
All notable changes to `@ilq/grapesjs-angular` will be documented in this file.

Initial release of `@ilq/grapesjs-angular` — a ground-up Angular 20+ wrapper for GrapesJS.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

- Standalone `<gjs-editor>` component with OnPush change detection
- `GrapesJsEditorService` with signals for editor state and all GrapesJS managers
- `provideGrapesJs()` for app-wide default configuration
- Zoneless-compatible (no Zone.js dependency)
- Inputs: `config`, `plugins`
- Outputs: `editorReady`, `projectSaved`, `projectLoaded`, `componentSelected`, `blockAdded`
- Service methods: `init()`, `destroy()`, `getHtml()`, `getCss()`, `getProjectData()`, `loadProjectData()`
## [Unreleased]

### Added

### Changed

### Fixed

### Removed

## [2.0.0-beta.2] — 2026-06-21

### Added

- `<gjs-canvas>` mount-point component enabling custom UI mode — when projected into `<gjs-editor>`, swaps the editor's default container and disables default panels.
- Nine provider components mirroring the `@grapesjs/react` render-props surface, each exposing typed manager state to a projected `<ng-template>`:
- `<gjs-blocks-provider>` — `BlocksState` (blocks, drag handlers, container, category map)
- `<gjs-layers-provider>` — `LayersState` (root component, container)
- `<gjs-selectors-provider>` — `SelectorsState` (selectors, states, targets, mutators, container)
- `<gjs-styles-provider>` — `StylesState` (visible sectors, container)
- `<gjs-traits-provider>` — `TraitsState` (current traits, container)
- `<gjs-pages-provider>` — `PagesState` (pages, selected, mutators)
- `<gjs-devices-provider>` — `DevicesState` (devices, selected, select)
- `<gjs-assets-provider>` — `AssetsState` (open, assets, types, select, close, container)
- `<gjs-modal-provider>` — `ModalState` (open, title, content, attributes, close)
- `*gjsContainer` directive for appending a GrapesJS-owned `HTMLElement` into a host element (Angular equivalent of React's portal `Container`).
- New component outputs: `editorCreated` (fires synchronously after `grapesjs.init()` returns), `projectUpdated` (fires on every `'update'` event with latest `ProjectData`).
- New component inputs: `grapesjsCss` (async `<link>` injection before init), `waitReady` (`boolean | TemplateRef<unknown>` — hide/replace the editor host until `editor.onReady()` fires).
- New service signal `isInitialized` — `true` after `grapesjs.init()` returns.
- Async CDN plugin loading — `plugins` input now accepts `{ id, src, options }` descriptors alongside function references and global names.
- Public types: `PluginToLoad`, `PluginTypeToLoad`, `GrapesPlugin`, `ProjectUpdatePayload`.

### Changed

- **BREAKING vs `2.0.0-beta.1`** — `editorReady` output semantics changed and the earlier-firing name was moved:
- Output `editorReady` (previously fired on GrapesJS `'load'`) is renamed to `editorLoaded` — same firing point, clearer name.
- Output `editorReady` now fires on `editor.onReady()` (post-mount, post-storage-load) to match `@grapesjs/react` `onReady` semantics.
- **BREAKING vs `2.0.0-beta.1`** — service signal `isReady` semantics changed:
- Signal `isReady` (previously flipped after init) is renamed to `isInitialized`.
- Signal `isReady` now reflects the post-`editor.onReady()` state.
- `plugins` input widened from `Plugin[]` to `PluginTypeToLoad[]` — source-compatible; existing `Plugin[]` assignments still compile.
- `<gjs-editor>` now defaults the GrapesJS init config to `height: '100%'` and `width: '100%'` so the editor fills its host element. GrapesJS's internal default of `height: 900px` was overflowing flex layouts. Consumer config still overrides.

### Fixed

- `<gjs-selectors-provider>` and `<gjs-styles-provider>` now render their projected template on init. In custom-UI mode GrapesJS skips its default panel render — the only place it fires the initial `selector:custom` / `style:custom` event — so these panels stayed empty until a component was selected. Both providers now self-trigger the event on `wire()`, matching `<gjs-traits-provider>`.
- SSR-safe: `<gjs-editor>` no longer calls `grapesjs.init()` or touches DOM APIs when `PLATFORM_ID === 'server'` — the host renders inert and `ngOnDestroy` short-circuits.

## [2.0.0-beta.1] — 2026-04-23

Initial public release on npm. Angular 20+ wrapper for [GrapesJS](https://grapesjs.com/), signals-based, standalone, zoneless-compatible.

- `<gjs-editor>` standalone component with `config` and `plugins` inputs, `editorReady` / `projectSaved` / `projectLoaded` / `componentSelected` / `blockAdded` outputs.
- `GrapesJsEditorService` exposing every GrapesJS manager as a signal, plus `isReady`, `selectedComponent`, and `getHtml` / `getCss` / `getProjectData` / `loadProjectData` helpers.
- `provideGrapesJs(config)` environment provider + `GRAPES_JS_DEFAULT_CONFIG` injection token.

## [2.0.0-alpha.1]

Pre-release development milestone — not published to npm.

- Standalone `<gjs-editor>` component with OnPush change detection.
- `GrapesJsEditorService` with signals for editor state and all GrapesJS managers.
- `provideGrapesJs()` for app-wide default configuration.
- Zoneless-compatible (no Zone.js dependency).

---

Keep the `[Unreleased]` section up-to-date as changes land; move its entries under a new version heading at release time.

[Unreleased]: https://github.com/internetliquid/grapesjs-angular/compare/v2.0.0-beta.2...HEAD
[2.0.0-beta.2]: https://github.com/internetliquid/grapesjs-angular/compare/v2.0.0-beta.1...v2.0.0-beta.2
[2.0.0-beta.1]: https://github.com/internetliquid/grapesjs-angular/releases/tag/v2.0.0-beta.1
150 changes: 138 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,63 @@ export class MyComponent {

### Inputs

| Input | Type | Default | Description |
| --------- | --------------- | ------- | -------------------------------------------------- |
| `config` | `GrapesJsConfig`| `{}` | GrapesJS editor config (merged with global config) |
| `plugins` | `Plugin[]` | `[]` | Additional plugins (appended to global plugins) |
| Input | Type | Default | Description |
| --- | --- | --- | --- |
| `config` | `GrapesJsConfig` | `{}` | GrapesJS editor config (merged with global config) |
| `plugins` | `PluginTypeToLoad[]` | `[]` | Plugins as functions, global names, or `{ id, src, options }` for async CDN loading |
| `grapesjsCss` | `string \| undefined` | — | URL of the GrapesJS core CSS to inject asynchronously before init |
| `waitReady` | `boolean \| TemplateRef<unknown>` | — | Hide the editor host until `editorReady` fires (`editor.onReady()`); pass a `TemplateRef` to show a placeholder |

### Outputs

| Output | Payload | Description |
| ------------------- | -------------- | ---------------------------------------- |
| `editorReady` | `Editor` | Emitted when the editor has loaded |
| `projectSaved` | `ProjectData` | Emitted after project storage completes |
| `projectLoaded` | `ProjectData` | Emitted after project data is loaded |
| `componentSelected` | `Component` | Emitted when a component is selected |
| `blockAdded` | `Block` | Emitted when a block is dropped on canvas|
| Output | Payload | Description |
| --- | --- | --- |
| `editorCreated` | `Editor` | Emitted synchronously after `grapesjs.init()` (pre-`load`) |
| `editorLoaded` | `Editor` | Emitted on the GrapesJS `'load'` event |
| `editorReady` | `Editor` | Emitted once `editor.onReady()` fires (post-mount, post-storage) |
| `projectUpdated` | `{ data: ProjectData; editor: Editor }` | Emitted on every editor `'update'` event |
| `projectSaved` | `ProjectData` | Emitted after project storage completes |
| `projectLoaded` | `ProjectData` | Emitted after project data is loaded |
| `componentSelected` | `Component` | Emitted when a component is selected |
| `blockAdded` | `Block` | Emitted when a block is dropped on canvas |

### Editor lifecycle

Three distinct phases — safe content manipulation should wait for `editorReady`:

1. **`editorCreated`** — the `Editor` instance exists; managers are accessible but the canvas hasn't loaded.
2. **`editorLoaded`** — GrapesJS `'load'` event; the core editor UI is up.
3. **`editorReady`** — `editor.onReady()` callback; mounted, panels built, storage data loaded. Safe to read or manipulate project content.

### Async plugin & CSS loading

Plugins can be a mix of function references, global names, and async CDN descriptors:

```typescript
@Component({
template: `
<gjs-editor
grapesjsCss="https://unpkg.com/grapesjs/dist/css/grapes.min.css"
[plugins]="editorPlugins">
</gjs-editor>
`,
})
export class MyEditor {
editorPlugins: PluginTypeToLoad[] = [
gjsBlocksBasic, // function reference
'grapesjs-plugin-forms', // global name
{ // CDN descriptor
id: 'grapesjs-preset-webpage',
src: 'https://unpkg.com/grapesjs-preset-webpage',
options: { /* ... */ },
},
];
}
```

### SSR

`<gjs-editor>` is SSR-safe: when rendered on the server (`PLATFORM_ID === 'server'`) it renders an inert host and never calls `grapesjs.init()`. Combine with `waitReady` to render a placeholder during server rendering and hydration.

## Service API

Expand All @@ -112,7 +155,8 @@ export class MyComponent {
| Signal | Type | Description |
| -------------------- | ------------------------------- | ------------------------------------ |
| `editor` | `Signal<Editor \| null>` | The GrapesJS editor instance |
| `isReady` | `Signal<boolean>` | Whether the editor is initialised |
| `isInitialized` | `Signal<boolean>` | Whether the editor is initialised |
| `isReady` | `Signal<boolean>` | Whether `editor.onReady()` has fired |
| `selectedComponent` | `Signal<Component \| null>` | Currently selected component |
| `blockManager` | `Signal<BlockManager \| null>` | Block manager |
| `styleManager` | `Signal<StyleManager \| null>` | Style manager |
Expand All @@ -136,6 +180,86 @@ export class MyComponent {
| `getProjectData()` | `ProjectData \| null`| Get the full project data |
| `loadProjectData(data)` | `void` | Load project data into the editor|

## Custom UI

By default `<gjs-editor>` renders the full GrapesJS UI (panels, block manager, style manager, etc.) inside its host element. For full control of the editor shell — composing your own sidebars and toolbars with Angular templates — project `<gjs-canvas>` plus one or more provider components as content children:

```typescript
import {
GrapesJsEditorComponent,
GjsCanvas,
GjsBlocksProvider,
GjsLayersProvider,
GjsPagesProvider,
GjsContainerDirective,
} from '@ilq/grapesjs-angular';

@Component({
imports: [
GrapesJsEditorComponent,
GjsCanvas,
GjsBlocksProvider,
GjsLayersProvider,
GjsPagesProvider,
GjsContainerDirective,
],
template: `
<gjs-editor>
<aside class="sidebar">
<gjs-blocks-provider>
<ng-template let-ctx>
@for (block of ctx.blocks; track block.getId()) {
<button (mousedown)="ctx.dragStart(block, $event)">
{{ block.getLabel() }}
</button>
}
</ng-template>
</gjs-blocks-provider>

<gjs-layers-provider>
<ng-template let-ctx>
<div [gjsContainer]="ctx.container"></div>
</ng-template>
</gjs-layers-provider>

<gjs-pages-provider>
<ng-template let-ctx>
@for (page of ctx.pages; track page.getId()) {
<button (click)="ctx.select(page)">
{{ page.get('name') ?? page.getId() }}
</button>
}
</ng-template>
</gjs-pages-provider>
</aside>
<gjs-canvas class="canvas"></gjs-canvas>
</gjs-editor>
`,
})
export class MyEditor {}
```

When `<gjs-canvas>` is present the editor uses it as the GrapesJS container, sets `customUI: true`, and disables the default panels. Each provider discovered via content projection flips the matching `custom*` flag in the init config, then subscribes to its manager's `custom` event and exposes the live state to your projected `<ng-template>`.

### Providers

| Component | State shape | Flips `custom` flag on | Notes |
| --- | --- | --- | --- |
| `<gjs-canvas>` | — | — | Swaps the editor's container and disables default panels |
| `<gjs-blocks-provider>` | `BlocksState` | `blockManager` | `{ blocks, dragStart, dragStop, container, mapCategoryBlocks }` |
| `<gjs-layers-provider>` | `LayersState` | `layerManager` | `{ root, container }` |
| `<gjs-selectors-provider>` | `SelectorsState` | `selectorManager` | `{ selectors, states, selectedState, targets, addSelector, removeSelector, setState, container }` |
| `<gjs-styles-provider>` | `StylesState` | `styleManager` | `{ sectors, container }` |
| `<gjs-traits-provider>` | `TraitsState` | `traitManager` | `{ traits, container }` |
| `<gjs-assets-provider>` | `AssetsState` | `assetManager` | `{ open, assets, types, select, close, container }` |
| `<gjs-modal-provider>` | `ModalState` | `modal` | `{ open, title, content, attributes, close }` |
| `<gjs-pages-provider>` | `PagesState` | — | `{ pages, selected, select, add, remove }` (observer-only) |
| `<gjs-devices-provider>` | `DevicesState` | — | `{ devices, selected, select }` (observer-only) |

### `*gjsContainer`

For providers that expose a `container` (the GrapesJS-owned default panel element), use `[gjsContainer]="ctx.container"` on any host element to mount the default panel inside your own layout. This is a one-directive alternative to building a portal system.

## Multi-Editor

`GrapesJsEditorService` is provided in root, so by default all `<gjs-editor>` instances share one service. For multiple independent editors, provide a separate service per editor:
Expand Down Expand Up @@ -172,6 +296,8 @@ Each instance of this component gets its own `GrapesJsEditorService`.

Inspired by the original [`@rakutentech/grapesjs-angular`](https://github.com/rakutentech/grapesjs-angular), now archived.

The API surface is informed by [`@grapesjs/react`](https://github.com/GrapesJS/react), the official React wrapper, and portions of the plugin and stylesheet loader utilities are adapted from it under the MIT license.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md).
Expand Down
4 changes: 2 additions & 2 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
"maximumWarning": "8kB",
"maximumError": "16kB"
}
],
"outputHashing": "all"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "grapesjs-angular",
"version": "0.0.0",
"name": "grapesjs-angular-workspace",
"version": "0.0.1",
"scripts": {
"ng": "ng",
"build:lib": "ng build grapesjs-angular && cp README.md LICENSE dist/grapesjs-angular/",
Expand Down
Loading