visbug-editor 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # visbug-editor
2
2
 
3
- Core editing features extracted from [VisBug](https://github.com/GoogleChromeLabs/ProjectVisBug) for use in web applications.
3
+ Framework-agnostic visual editing library extracted from [VisBug](https://github.com/GoogleChromeLabs/ProjectVisBug).
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/visbug-editor.svg)](https://www.npmjs.com/package/visbug-editor)
6
6
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
@@ -18,27 +18,11 @@ All operations include full undo/redo support with smart change batching.
18
18
 
19
19
  ## Features
20
20
 
21
- **Visual Editing Tools**
22
- - Position Tool - Drag elements to reposition, use arrow keys for precise movement
23
- - Text Tool - Inline text editing
24
- - Font Tool - Typography controls (size, spacing, weight, style)
25
- - Image Swap - Drag-and-drop image replacement
26
-
27
- **Built-in Components**
28
- - Selection overlay with resize handles (8 grips: corners + edges)
29
- - Hover indicators with element labels
30
- - Visual feedback for all interactions
31
- - Dynamic overlay updates when elements change
32
-
33
- **History Management**
34
- - Full undo/redo support
35
- - Smart change batching and merging
36
- - Event-based change notifications
37
-
38
- **Framework Agnostic**
39
- - Works with vanilla JS, React, Vue, Angular, etc.
40
- - Shadow DOM or regular DOM mode
41
- - TypeScript definitions included
21
+ - **Tools**: Position (drag/arrow keys), Text (contenteditable), Font (typography), Image (drag-drop replacement)
22
+ - **Components**: Selection overlay with resize handles, hover indicators, visual feedback
23
+ - **History**: Full undo/redo with smart change batching
24
+ - **Flexible**: Works with vanilla JS, React, Vue, Angular; Shadow DOM or regular DOM
25
+ - **TypeScript**: Complete type definitions included
42
26
 
43
27
  ## Installation
44
28
 
@@ -57,21 +41,21 @@ yarn add visbug-editor
57
41
  ### Basic Usage
58
42
 
59
43
  ```javascript
60
- import { VisBugEditor } from 'visbug-editor';
44
+ import { VisBugEditor } from "visbug-editor";
61
45
 
62
46
  const editor = new VisBugEditor({
63
- container: document.getElementById('editable-area'),
64
- initialTool: 'position',
65
- onToolChange: (tool) => console.log('Tool changed:', tool),
66
- onSelectionChange: (elements) => console.log('Selected:', elements),
47
+ container: document.getElementById("editable-area"),
48
+ initialTool: "position",
49
+ onToolChange: (tool) => console.log("Tool changed:", tool),
50
+ onSelectionChange: (elements) => console.log("Selected:", elements),
67
51
  onChange: ({ canUndo, canRedo }) => {
68
- console.log('Can undo:', canUndo, 'Can redo:', canRedo);
69
- }
52
+ console.log("Can undo:", canUndo, "Can redo:", canRedo);
53
+ },
70
54
  });
71
55
 
72
56
  // Switch tools
73
- editor.activateTool('text');
74
- editor.activateTool('font');
57
+ editor.activateTool("text");
58
+ editor.activateTool("font");
75
59
 
76
60
  // Undo/Redo
77
61
  editor.undo();
@@ -79,7 +63,7 @@ editor.redo();
79
63
 
80
64
  // Get/Set content
81
65
  const html = editor.getContent();
82
- editor.setContent('<h1>New content</h1>');
66
+ editor.setContent("<h1>New content</h1>");
83
67
 
84
68
  // Clean up
85
69
  editor.destroy();
@@ -90,15 +74,15 @@ editor.destroy();
90
74
  Full TypeScript support with complete type definitions:
91
75
 
92
76
  ```typescript
93
- import { VisBugEditor, VisBugEditorOptions } from 'visbug-editor';
77
+ import { VisBugEditor, VisBugEditorOptions } from "visbug-editor";
94
78
 
95
79
  const options: VisBugEditorOptions = {
96
- container: document.getElementById('app')!,
97
- mode: 'shadowDOM',
98
- initialTool: 'position',
80
+ container: document.getElementById("app")!,
81
+ mode: "shadowDOM",
82
+ initialTool: "position",
99
83
  onToolChange: (tool: string) => {
100
- console.log('Tool:', tool);
101
- }
84
+ console.log("Tool:", tool);
85
+ },
102
86
  };
103
87
 
104
88
  const editor = new VisBugEditor(options);
@@ -106,24 +90,12 @@ const editor = new VisBugEditor(options);
106
90
 
107
91
  ## Container Concept
108
92
 
109
- When you pass a container to the editor, **the children of the container become editable**, not the container itself:
110
-
111
- ```html
112
- <div id="my-container">
113
- <!-- This h1 is editable -->
114
- <h1>Editable Heading</h1>
115
-
116
- <!-- This paragraph is editable -->
117
- <p>You can edit this text</p>
118
-
119
- <!-- This image is swappable -->
120
- <img src="image.jpg" />
121
- </div>
122
- ```
93
+ The container's **children** become editable, not the container itself:
123
94
 
124
95
  ```javascript
125
96
  const editor = new VisBugEditor({
126
- container: document.getElementById('my-container')
97
+ container: document.getElementById("my-container"),
98
+ // Children of my-container are now editable
127
99
  });
128
100
  ```
129
101
 
@@ -135,53 +107,57 @@ The container acts as the "editing canvas" boundary.
135
107
 
136
108
  ```typescript
137
109
  interface VisBugEditorOptions {
138
- // Required
139
- container: HTMLElement; // Container whose children will be editable
140
-
141
- // Optional
142
- mode?: 'shadowDOM' | 'div'; // Default: 'shadowDOM'
143
- initialTool?: 'position' | 'text' | 'font'; // Default: 'position'
144
-
145
- // Callbacks
110
+ container: HTMLElement; // Required: editing canvas
111
+ mode?: "inside"; // Where to append UI elements
112
+ initialTool?: "position" | "text" | "font";
146
113
  onToolChange?: (tool: string) => void;
147
114
  onSelectionChange?: (elements: HTMLElement[]) => void;
148
115
  onChange?: (state: { canUndo: boolean; canRedo: boolean }) => void;
149
116
  onImageUpload?: (file: File) => Promise<string>;
150
-
151
- // Customization
152
117
  styles?: Record<string, string>;
153
-
154
- // Behavior
155
- clearHistoryOnSetContent?: boolean; // Default: true
118
+ clearHistoryOnSetContent?: boolean;
156
119
  }
157
120
  ```
158
121
 
159
- ### Methods
122
+ ### Mode Option
160
123
 
161
- **Tool Management**
162
- - `activateTool(toolName)` - Switch to a different tool ('position', 'text', 'font')
163
- - `getCurrentTool()` - Get the currently active tool
124
+ The `mode` option controls where editor UI elements (labels, handles, overlays) are appended:
164
125
 
165
- **Selection**
166
- - `selectElement(element)` - Select a single element
167
- - `selectElements(elements)` - Select multiple elements
168
- - `getSelectedElements()` - Get currently selected elements
169
- - `clearSelection()` - Clear current selection
126
+ **Undefined (default)** - Append to `document.body`
170
127
 
171
- **History**
172
- - `undo()` - Undo the last change
173
- - `redo()` - Redo the last undone change
174
- - `canUndo()` - Check if undo is available
175
- - `canRedo()` - Check if redo is available
176
- - `getHistory()` - Get the history array
177
- - `clearHistory()` - Clear the history
128
+ - UI overlays can extend beyond container boundaries
129
+ - Standard behavior for full-page editing
130
+ - Use when container might have `overflow: hidden` or positioning constraints
131
+
132
+ ```javascript
133
+ const editor = new VisBugEditor({
134
+ container: document.getElementById("app"),
135
+ // mode undefined - UI appends to document.body
136
+ });
137
+ ```
138
+
139
+ **'inside'** - Append to the container element
140
+
141
+ - UI stays within container bounds
142
+ - Useful for isolated editing areas or embedded editors
143
+ - Good for multiple editors on the same page
144
+
145
+ ```javascript
146
+ const editor = new VisBugEditor({
147
+ container: document.getElementById("app"),
148
+ mode: "inside", // UI appends to container
149
+ });
150
+ ```
178
151
 
179
- **Content**
180
- - `getContent()` - Get clean HTML without editor UI
181
- - `setContent(html)` - Set content and optionally clear history
152
+ ### Key Methods
182
153
 
183
- **Lifecycle**
184
- - `destroy()` - Clean up and destroy the editor
154
+ | Method | Purpose |
155
+ | ---------------------------------------- | ---------------------------------------- |
156
+ | `activateTool(name)` | Switch tools: 'position', 'text', 'font' |
157
+ | `undo()` / `redo()` | Undo/redo changes |
158
+ | `getContent()` / `setContent(html)` | Get/set HTML |
159
+ | `selectElement(el)` / `clearSelection()` | Manage selection |
160
+ | `destroy()` | Cleanup |
185
161
 
186
162
  ## Tools
187
163
 
@@ -204,287 +180,86 @@ Click on any element to edit its text content inline using contenteditable. Pres
204
180
  Typography controls with keyboard shortcuts:
205
181
 
206
182
  **Font Size**
183
+
207
184
  - `Cmd/Ctrl + Up` - Increase font size
208
185
  - `Cmd/Ctrl + Down` - Decrease font size
209
186
 
210
187
  **Letter Spacing (Kerning)**
188
+
211
189
  - `Cmd/Ctrl + Shift + Up` - Increase letter spacing
212
190
  - `Cmd/Ctrl + Shift + Down` - Decrease letter spacing
213
191
 
214
192
  **Line Height (Leading)**
193
+
215
194
  - `Alt + Up` - Increase line height
216
195
  - `Alt + Down` - Decrease line height
217
196
 
218
197
  **Font Weight**
198
+
219
199
  - `Cmd/Ctrl + B` - Toggle bold
220
200
 
221
201
  **Font Style**
202
+
222
203
  - `Cmd/Ctrl + I` - Toggle italic
223
204
 
224
205
  ### Image Swap (Always Active)
225
206
 
226
207
  Drag and drop images onto any `<img>` tag or element with a background image to replace it. Supports:
208
+
227
209
  - Direct image URL replacement
228
210
  - Custom upload handler via `onImageUpload` callback
229
211
 
230
212
  ## Examples
231
213
 
232
- ### React Integration
214
+ ### React
233
215
 
234
216
  ```jsx
235
- import { useEffect, useRef, useState } from 'react';
236
- import { VisBugEditor } from 'visbug-editor';
217
+ import { useEffect, useRef } from "react";
218
+ import { VisBugEditor } from "visbug-editor";
237
219
 
238
- function Editor() {
220
+ export default function Editor() {
239
221
  const containerRef = useRef(null);
240
222
  const editorRef = useRef(null);
241
- const [canUndo, setCanUndo] = useState(false);
242
- const [canRedo, setCanRedo] = useState(false);
243
223
 
244
224
  useEffect(() => {
245
- if (containerRef.current && !editorRef.current) {
246
- editorRef.current = new VisBugEditor({
247
- container: containerRef.current,
248
- onChange: ({ canUndo, canRedo }) => {
249
- setCanUndo(canUndo);
250
- setCanRedo(canRedo);
251
- }
252
- });
253
- }
254
-
255
- return () => {
256
- editorRef.current?.destroy();
257
- };
225
+ editorRef.current = new VisBugEditor({
226
+ container: containerRef.current,
227
+ });
228
+ return () => editorRef.current?.destroy();
258
229
  }, []);
259
230
 
260
231
  return (
261
- <div>
262
- <button onClick={() => editorRef.current?.undo()} disabled={!canUndo}>
263
- Undo
264
- </button>
265
- <button onClick={() => editorRef.current?.redo()} disabled={!canRedo}>
266
- Redo
267
- </button>
232
+ <>
233
+ <button onClick={() => editorRef.current?.undo()}>Undo</button>
268
234
  <div ref={containerRef}>
269
- <h1>Editable Content</h1>
235
+ <h1>Editable</h1>
270
236
  </div>
271
- </div>
237
+ </>
272
238
  );
273
239
  }
274
240
  ```
275
241
 
276
- ### Vue Integration
242
+ ### Vue
277
243
 
278
244
  ```vue
279
245
  <template>
280
- <div>
281
- <button @click="undo" :disabled="!canUndo">Undo</button>
282
- <button @click="redo" :disabled="!canRedo">Redo</button>
283
- <div ref="container">
284
- <h1>Editable Content</h1>
285
- </div>
286
- </div>
246
+ <button @click="editor?.undo()">Undo</button>
247
+ <div ref="container"><h1>Editable</h1></div>
287
248
  </template>
288
249
 
289
250
  <script setup>
290
- import { ref, onMounted, onUnmounted } from 'vue';
291
- import { VisBugEditor } from 'visbug-editor';
251
+ import { ref, onMounted, onUnmounted } from "vue";
252
+ import { VisBugEditor } from "visbug-editor";
292
253
 
293
254
  const container = ref(null);
294
- const canUndo = ref(false);
295
- const canRedo = ref(false);
296
255
  let editor = null;
297
256
 
298
257
  onMounted(() => {
299
- editor = new VisBugEditor({
300
- container: container.value,
301
- onChange: ({ canUndo: cu, canRedo: cr }) => {
302
- canUndo.value = cu;
303
- canRedo.value = cr;
304
- }
305
- });
258
+ editor = new VisBugEditor({ container: container.value });
306
259
  });
307
260
 
308
261
  onUnmounted(() => {
309
262
  editor?.destroy();
310
263
  });
311
-
312
- const undo = () => editor?.undo();
313
- const redo = () => editor?.redo();
314
264
  </script>
315
265
  ```
316
-
317
- ### Next.js Integration
318
-
319
- ```jsx
320
- "use client";
321
-
322
- import { useEffect, useRef, useState } from "react";
323
- import { VisBugEditor } from "visbug-editor";
324
-
325
- export default function EditableContent() {
326
- const containerRef = useRef(null);
327
- const editorRef = useRef(null);
328
- const [activeTool, setActiveTool] = useState("position");
329
- const [canUndo, setCanUndo] = useState(false);
330
- const [canRedo, setCanRedo] = useState(false);
331
-
332
- useEffect(() => {
333
- if (containerRef.current && !editorRef.current) {
334
- editorRef.current = new VisBugEditor({
335
- container: containerRef.current,
336
- mode: "shadowDOM",
337
- initialTool: "position",
338
- onToolChange: (tool) => setActiveTool(tool),
339
- onChange: (state) => {
340
- setCanUndo(state.canUndo);
341
- setCanRedo(state.canRedo);
342
- },
343
- });
344
- }
345
-
346
- return () => {
347
- editorRef.current?.destroy();
348
- editorRef.current = null;
349
- };
350
- }, []);
351
-
352
- return (
353
- <div className="editor-wrapper">
354
- <div className="toolbar">
355
- <button onClick={() => editorRef.current?.activateTool("position")}>
356
- Position
357
- </button>
358
- <button onClick={() => editorRef.current?.activateTool("text")}>
359
- Text
360
- </button>
361
- <button onClick={() => editorRef.current?.activateTool("font")}>
362
- Font
363
- </button>
364
- <button onClick={() => editorRef.current?.undo()} disabled={!canUndo}>
365
- Undo
366
- </button>
367
- <button onClick={() => editorRef.current?.redo()} disabled={!canRedo}>
368
- Redo
369
- </button>
370
- </div>
371
-
372
- <div ref={containerRef} className="editable-area">
373
- <h1>Edit Me!</h1>
374
- <p>This content is editable</p>
375
- </div>
376
- </div>
377
- );
378
- }
379
- ```
380
-
381
- ### Custom Image Upload
382
-
383
- ```javascript
384
- const editor = new VisBugEditor({
385
- container: document.getElementById('app'),
386
- onImageUpload: async (file) => {
387
- // Upload to your server
388
- const formData = new FormData();
389
- formData.append('image', file);
390
-
391
- const response = await fetch('/api/upload', {
392
- method: 'POST',
393
- body: formData
394
- });
395
-
396
- const { url } = await response.json();
397
- return url; // Return the uploaded image URL
398
- }
399
- });
400
- ```
401
-
402
- ## Shadow DOM vs Div Mode
403
-
404
- ### Shadow DOM (Recommended)
405
-
406
- **Pros:**
407
- - Style isolation between editor and content
408
- - DOM encapsulation
409
- - No style conflicts
410
-
411
- **Cons:**
412
- - Not supported in very old browsers
413
- - Slight complexity in event handling
414
-
415
- ### Div Mode (Fallback)
416
-
417
- **Pros:**
418
- - Universal browser support
419
- - Simpler event handling
420
-
421
- **Cons:**
422
- - Potential style conflicts
423
- - Editor styles may affect content
424
-
425
- The library automatically falls back to div mode if Shadow DOM is not supported.
426
-
427
- ## Content Persistence
428
-
429
- ### Saving Content
430
-
431
- ```javascript
432
- // Get clean HTML (without editor UI)
433
- const html = editor.getContent();
434
-
435
- // Save to backend
436
- await fetch("/api/save", {
437
- method: "POST",
438
- body: JSON.stringify({ html }),
439
- });
440
- ```
441
-
442
- ### Loading Content
443
-
444
- ```javascript
445
- // Load from backend
446
- const response = await fetch("/api/content/123");
447
- const { html } = await response.json();
448
-
449
- // Set content (clears selection and optionally history)
450
- editor.setContent(html);
451
- ```
452
-
453
- ## Browser Support
454
-
455
- - Chrome/Edge 80+
456
- - Firefox 75+
457
- - Safari 13.1+
458
-
459
- Requires support for:
460
- - ES6 Modules
461
- - Custom Elements
462
- - Shadow DOM (optional, falls back to regular DOM)
463
- - MutationObserver
464
-
465
- ## Bundle Size
466
-
467
- - ESM: ~144 KB (uncompressed)
468
- - Browser: ~153 KB (uncompressed)
469
- - CJS: ~144 KB (uncompressed)
470
- - UMD: ~144 KB (uncompressed)
471
-
472
- All builds include source maps for debugging.
473
-
474
- ## License
475
-
476
- Apache-2.0
477
-
478
- ## Credits
479
-
480
- Extracted from [VisBug](https://github.com/GoogleChromeLabs/ProjectVisBug) by Adam Argyle.
481
-
482
- ## Contributing
483
-
484
- Contributions are welcome! Please feel free to submit a Pull Request.
485
-
486
- ## Links
487
-
488
- - [GitHub Repository](https://github.com/GoogleChromeLabs/ProjectVisBug)
489
- - [Issue Tracker](https://github.com/GoogleChromeLabs/ProjectVisBug/issues)
490
- - [Original VisBug](https://github.com/GoogleChromeLabs/ProjectVisBug)