grapper-editor
Version:
Grapper Source Code Editor
293 lines (202 loc) • 9.3 kB
Markdown
# <br/>Code editor web component
## `<g-editor>`
A standalone code editor component that edits a `<grapper-view>` in real time and keeps its
attributes/internal markup synchronized.
## Features
* Inline editor with copy/save/reload/rearrange controls
* Works against:
* A `<grapper-view>` component (keeps attributes and content synchronized)
* Inline code provided in the light DOM (comment node or `<textarea>`)
* An `<iframe>`’s `document.body`
* Read-only, show, and edit modes
* Optional line highlighting
* Theme switching (light/dark)
* Diagnostics event stream for Grapper View validation
## Installation
You can install the component via npm:
```bash
npm install --save grapper-editor
```
Then import it in your app from CDN:
```html
<script src="https://cdn.jsdelivr.net/npm/grapper-editor@1.0.0-beta.1/dist/editor.js"></script>
```
> The component registers as `<g-editor>`.
## Working with the Grapper view (`<grapper-view>`)
When the `href` points at a `<grapper-view>`, the editor switches to **composer mode**:
```html
<grapper-view id="composer"></grapper-view>
<g-editor title="Composer" href="#composer" mode="edit"></g-editor>
```
In composer mode:
* The edited markup is applied to the composer’s **inner content**.
* All **attributes** on the composer are kept in sync (added/removed/updated) to match the edited
source.
* Diagnostics will be computed against the live `<grapper-view>` and surfaced via the `diagnostic`
event and the built-in panel (if your provider UI shows it).
## Inline content
You can provide the initial code as a comment node or a `<textarea>` in the light DOM:
```html
<g-editor title="My template" mode="edit">
<!--
<svg width="300" height="80">
<text x="10" y="40">Hello Graphane!</text>
</svg>
-->
</g-editor>
```
Or:
```html
<g-editor title="My template" mode="edit">
<textarea>
<svg width="300" height="80">
<text x="10" y="40">Hello Graphane!</text>
</svg>
</textarea>
</g-editor>
```
## Editing an existing element with `href`
Point the editor at any element in the same document (or an `<iframe>`):
```html
<div id="preview"></div>
<g-editor title="Section editor" href="#preview" mode="edit"></g-editor>
```
* If `href` targets an `<iframe>`, the editor waits for it to load and uses its
`contentDocument.body`.
* When you edit, the target element’s `innerHTML` is updated in real time.
## Attributes
| Attribute | Type | Default | Description |
|-------------------|-----------|-----------|--------------------------------------------------------------------------------------------------------------------|
| `title` | `string` | `""` | UI title displayed in the editor header. |
| `href` | `string` | `""` | CSS selector for the element to edit. Supports elements and `<iframe>`. When empty, uses inline light-DOM content. |
| `keep-format` | `boolean` | `false` | Preserve original formatting when re-loading code into the model. |
| `options` | `object` | `{}` | Editor/model options (pass JSON string in HTML; a plain object when set programmatically). |
| `theme` | `string` | `"light"` | Editor theme. Set to `"dark"` for dark mode. Also responds to a global `themeChanged` event (see **Theming**). |
| `mode` | `string` | `"show"` | One of: `"readonly"`, `"show"`, `"edit"`. Controls editability and which header actions are visible. |
| `lines-highlight` | `string` | `""` | Line ranges to highlight (e.g. `"1-3;8;12-14"`). See **Line highlighting**. |
> All attributes are reactive and can be changed at runtime.
## Line highlighting
Use `lines-highlight` with a semicolon-separated list of single lines or inclusive ranges:
* Single lines: `5`
* Ranges: `10-15`
* Combined: `1-3;5;12-14`
The string is parsed into numeric lines and applied by the editor provider.
## UI & header actions
Depending on `mode`, the header shows:
* **Edit / Show** toggles
* **Reload** — discard unsaved edits and restore the original code
* **Rearrange** — re-parse and re-format the current code using the model’s loader
* **Copy** — copy current code to clipboard
* **Save** — trigger a download of the current code (uses the provided `saveFile` helper)
> In `readonly` mode, all editing controls are hidden and the editor is non-editable.
## Events
### `diagnostic`
Emitted whenever diagnostics are computed (typically after edits), especially useful with
`<grapper-view>`.
```js
const editor = document.querySelector('g-editor');
editor.addEventListener('diagnostic', (evt) => {
// evt.detail is the diagnostic payload produced by your diagnostic pipeline
console.log('Diagnostic:', evt.detail);
});
```
Perfect — with that `BASE.md` content I can now integrate the **public events from grapper-core** into the README for `<g-editor>`.
Here’s the **updated README** section with the **public events** clearly documented.
### Lifecycle Events
These are automatically emitted during the component's lifecycle:
| Event | When it fires |
| ------------- | --------------------------------------------------------------------------- |
| **`ready`** | When the component has been instantiated and its constructor has run. |
| **`render`** | After the component has built its initial UI structure in the shadow DOM. |
| **`refresh`** | After the component has updated its content without a full re-render. |
| **`update`** | Each time the component’s content is updated (may fire multiple times). |
## Programmatic API
Access the component instance and call methods/properties directly:
```js
const editor = document.querySelector('g-editor');
// Read or replace the entire code buffer
console.log(editor.code);
editor.code = '<svg><text>Hello</text></svg>';
// Update without changing "original" snapshot
editor.update('<div>New content</div>');
// Is the buffer different from the original snapshot?
if (editor.isChanged) {
// Revert to original snapshot
await editor.discardChanges();
}
// Re-parse/normalize current code via the model
await editor.rearrange();
// Utilities
await editor.toClipboard();
await editor.toDisk(); // triggers a file save
editor.resetChange(); // sets "original" = current buffer
```
**Properties**
* `code: string` — get/set current buffer
* `isChanged: boolean` — whether `code` differs from the original snapshot captured on load or after
`resetChange()`
**Methods (async when noted)**
* `update(v: string): Promise<void>`
* `discardChanges(): Promise<void>`
* `rearrange(): Promise<void>`
* `toClipboard(): Promise<void>`
* `toDisk(): Promise<void>`
* `resetChange(): void`
## Theming
Set `theme="light"` or `theme="dark"` directly, or broadcast a global theme change:
```js
document.documentElement.dispatchEvent(
new CustomEvent('themeChanged', {detail : {theme : 'dark'}})
);
```
The editor listens for `themeChanged` on `document.documentElement` and updates itself.
## Examples
### 1) Grapper View
```html
<grapper-view id="chart">
<tempplate>
<svg viewBox="0 0 200 100" width="200px" height="100px">
<g stroke-width="12" stroke-linecap="round">
<g g-for="(record, index) of data">
<line x1="22"
:x2="record.value"
:y1="index * 20 + 30"
:y2="index * 20 + 30"
:stroke="record.color"
></line>
</g>
</g>
</svg>
</tempplate>
<script type="data">
[
{"color": "#D80000", "value": 130},
{"color": "#00D800", "value": 170},
{"color": "#0000D8", "value": 100}
]
</script>
</grapper-view>
<g-editor title="Basic example" href="#chart"></g-editor>
```
### 2) Read-only viewer
```html
<g-editor title="Spec preview" mode="readonly" lines-highlight="1-5;12">
<!--
<article>
<h1>Spec</h1>
<p>…</p>
</article>
-->
</g-editor>
```
### 3) Editing inside an iframe
```html
<iframe id="sandbox" src="/playground.html"></iframe>
<g-editor title="Iframe body editor" href="#sandbox" mode="edit"></g-editor>
```
## Tips & gotchas
* `keep-format` helps preserve the original formatting when re-loading code into the model. If you
prefer normalized output, leave it `false`.
* If your `href` selector doesn’t match anything, the editor logs an error and renders nothing.
* For `<grapper-view>`, both **attributes** and **inner content** are source-of-truth from the
editor; manual changes to the live element may be overwritten by subsequent edits.