@naoki85/visual-html-builder
v0.0.3
Published
A modern, TypeScript-based visual HTML builder with drag-and-drop functionality
Downloads
25
Maintainers
Readme
Visual HTML Builder
A modern visual HTML builder for browsers. A lightweight TypeScript library that provides intuitive drag-and-drop HTML editing, complete style isolation, and real-time preview functionality.
🌐 Demo
Live Demo - Try the Visual HTML Builder in action
🚀 Quick Start
Installation
# Install via npm
npm install @naoki85/visual-html-builder
# Or via yarn
yarn add @naoki85/visual-html-builder
# Via CDN (ESM)
<script type="module">
import VisualHtmlBuilder from "https://unpkg.com/@naoki85/visual-html-builder@latest/dist/index.js";
</script>Development/Contribution Setup
# Clone the project
git clone https://github.com/naoki85/visual-html-builder.git
cd visual-html-builder
# Install dependencies
npm installBasic Usage
import VisualHtmlBuilder from '@naoki85/visual-html-builder';
// Prepare HTML container
// <div id="editor-container"></div>
// Basic initialization
const editor = new VisualHtmlBuilder('editor-container', {
theme: 'default',
enabledElements: ['title', 'text', 'image', 'list'] // Default element types
});
editor.render();CDN Usage (ESM)
<body>
<div id="editor-container"></div>
<script type="module">
import VisualHtmlBuilder from "https://unpkg.com/@naoki85/visual-html-builder@latest/dist/index.js";
const editor = new VisualHtmlBuilder('editor-container', {
enabledElements: ['title', 'text', 'image', 'list']
});
editor.render();
</script>
</body>Advanced Configuration
// Full customization example
const editor = new VisualHtmlBuilder('editor-container', {
theme: 'default',
enabledElements: ['title', 'text', 'image', 'list'],
iframePreviewOptions: {
enableDragDrop: true,
enableElementSelection: true,
customStyles: `
body {
font-family: 'Helvetica Neue', sans-serif;
background: #f5f5f5;
}
`
},
htmlTemplate: {
doctype: '<!DOCTYPE html>',
htmlAttributes: { lang: 'en' },
head: {
title: 'Generated Page',
meta: [
{ charset: 'UTF-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' },
{ name: 'description', content: 'Generated by Visual HTML Builder' }
],
links: [
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap' }
]
},
bodyAttributes: { class: 'generated-content' }
}
});HTML Retrieval and API
// Body HTML only (existing API - maintains backward compatibility)
const bodyHTML = editor.getHTML();
// Complete HTML document (new API)
const fullHTML = editor.getFullHTML();
// Template management
const currentTemplate = editor.getHTMLTemplate();
editor.updateHTMLTemplate({
head: {
title: 'Updated Title'
}
});
// Element management
const elements = editor.getElements();
editor.setElements([
{ id: 1, type: 'title', props: { text: 'Welcome!', level: 1 }}
]);
// Adding custom elements
editor.registerElementType('custom-button', {
name: 'Button',
icon: '🔲',
defaultProps: { text: 'Click Me', color: '#007bff' },
render: (props) => `<button style="background-color: ${props.color};">${props.text}</button>`,
renderEditor: (props) => `
<div class="property-group">
<label>Button Text:</label>
<input type="text" value="${props.text}"
class="property-input" data-property="text">
</div>
<div class="property-group">
<label>Color:</label>
<input type="color" value="${props.color}"
class="property-input" data-property="color">
</div>
`,
validate: (props) => props.text.trim() ? null : 'Button text is required'
});📦 Development
# Install dependencies
npm install
# Start development server
npm run dev
# Build
npm run build
# Linting
npm run lint
# Code formatting
npm run format🧪 Testing
This project includes a comprehensive test suite using Vitest.
Running Tests
# Basic test run (watch mode)
npm run test
# One-shot execution
npm run test:run
# Open test UI
npm run test:ui
# Watch mode (monitor file changes)
npm run test:watch
# Generate coverage report
npm run test:coverageTesting Tech Stack
- Vitest 3.2.4: Fast test runner (Jest API compatible)
- jsdom: DOM environment simulation
- c8: Coverage report generation
- TypeScript: Complete type safety
🏗️ Architecture
Core Class
- VisualHtmlBuilder (
src/VisualHtmlBuilder.ts): Main class (478 lines)- Handles DOM management, event processing, element selection, and preview updates
- Combines various helpers to provide functionality
Helper Module Structure
src/helpers/
├── HTMLTemplateHelper.ts # 279 lines - Complete HTML document generation
├── IframePreviewHelper.ts # 443 lines - Style isolation preview functionality
├── ElementTypesHelper.ts # 196 lines - HTML element type definition & management
├── StylesHelper.ts # 580 lines - CSS management & injection
├── NotificationHelper.ts # 266 lines - Notification system
├── DragDropHelper.ts # 297 lines - Drag & drop functionality
└── UtilityHelpers.ts # 29 lines - General utilities🔧 Tech Stack
Development & Build
- TypeScript 5.8.3: Strict type checking, ES2020 target
- Vite 7.0.4: Fast build tool, ES Modules output
- ESLint + Prettier: Code quality management, unified formatting
Testing
- Vitest 3.2.4: Fast test runner, Jest API compatible
- jsdom: DOM environment simulation
- c8: Coverage report generation
Runtime Environment
- Node.js: >=20.0.0
- npm: >=8.0.0
📋 Use Cases
🎯 Intended Usage Scenarios
- HTML Editor in Web Applications: CMS, blog editors, email creation features
- Landing Page Builders: Code-free page creation tools
- Educational Tools: Visual editors for HTML learning
- Prototyping: Mockup creation for designers and planners
🛠️ Technical Features
- Framework Agnostic: Can be combined with any framework like React, Vue, Angular
- Modular Design: Select and use only the features you need
- Type Safety: Complete type support with TypeScript
- Lightweight: Minimal dependencies with 44kB lightweight implementation
- Default Element Types: Includes 4 built-in element types:
title,text,image,list
🧩 Custom Element Development
ElementType Interface
interface ElementType {
name: string; // Display name
icon: string; // Icon (emoji or text)
defaultProps: Record<string, any>; // Default properties
render: (props: Record<string, any>) => string; // HTML generation
renderEditor: (props: Record<string, any>, onChange?: (key: string, value: any) => void) => string; // Property editor
validate: (props: Record<string, any>) => string | null; // Validation
}Basic Custom Element Example
// Simple button element
editor.registerElementType('custom-button', {
name: 'Button',
icon: '🔲',
defaultProps: {
text: 'Click Me',
color: '#007bff',
size: 'medium'
},
render: (props) =>
`<button style="background-color: ${props.color}; padding: ${props.size === 'large' ? '12px 24px' : '8px 16px'};">${props.text}</button>`,
renderEditor: (props) => `
<div class="property-group">
<label>Button Text:</label>
<input type="text" value="${props.text}"
class="property-input" data-property="text">
</div>
<div class="property-group">
<label>Color:</label>
<input type="color" value="${props.color}"
class="property-input" data-property="color">
</div>
<div class="property-group">
<label>Size:</label>
<select class="property-input" data-property="size">
<option value="small" ${props.size === 'small' ? 'selected' : ''}>Small</option>
<option value="medium" ${props.size === 'medium' ? 'selected' : ''}>Medium</option>
<option value="large" ${props.size === 'large' ? 'selected' : ''}>Large</option>
</select>
</div>
`,
validate: (props) => props.text.trim() ? null : 'Button text is required'
});Overriding Default Element Types
The built-in element types (title, text, image, list) can be completely overridden by registering custom elements with the same names. This allows you to customize the behavior and appearance of default elements while maintaining the same API.
// Override the default 'title' element
editor.registerElementType('title', {
name: 'Custom Title',
icon: '📝',
defaultProps: {
text: 'Custom Title',
level: 1,
color: '#007bff',
alignment: 'left'
},
render: (props) => `
<h${props.level} style="color: ${props.color}; text-align: ${props.alignment};">
${props.text}
</h${props.level}>
`,
renderEditor: (props) => `
<div class="property-group">
<label>Title Text:</label>
<input type="text" value="${props.text}"
class="property-input" data-property="text">
</div>
<div class="property-group">
<label>Heading Level:</label>
<select class="property-input" data-property="level" data-value-type="int">
<option value="1" ${props.level === 1 ? 'selected' : ''}>H1</option>
<option value="2" ${props.level === 2 ? 'selected' : ''}>H2</option>
<option value="3" ${props.level === 3 ? 'selected' : ''}>H3</option>
<option value="4" ${props.level === 4 ? 'selected' : ''}>H4</option>
<option value="5" ${props.level === 5 ? 'selected' : ''}>H5</option>
<option value="6" ${props.level === 6 ? 'selected' : ''}>H6</option>
</select>
</div>
<div class="property-group">
<label>Text Color:</label>
<input type="color" value="${props.color}"
class="property-input" data-property="color">
</div>
<div class="property-group">
<label>Alignment:</label>
<select class="property-input" data-property="alignment">
<option value="left" ${props.alignment === 'left' ? 'selected' : ''}>Left</option>
<option value="center" ${props.alignment === 'center' ? 'selected' : ''}>Center</option>
<option value="right" ${props.alignment === 'right' ? 'selected' : ''}>Right</option>
</select>
</div>
`,
validate: (props) => props.text.trim() ? null : 'Title text is required'
});Required Event Handling Attributes
When creating custom elements with renderEditor, you must use specific class names and data attributes for proper event handling:
1. Required CSS Class
class="property-input": Essential for automatic event binding- This class is used by the library to detect input elements and attach change event listeners
2. Required Data Attributes
data-property="propertyName": Specifies which property this input controls- The property name must match a key in your element's
defaultProps
3. Optional Type Conversion Attributes
data-value-type="int": Converts string values to integersdata-value-type="boolean": Converts checkbox states to boolean values- Without this attribute, values remain as strings
4. List Element Classes(for list-type elements)
list-editor- Container for list editing interfacelist-item-editor- Wrapper for individual list item editinglist-item-input- Input field for list items (requiresdata-indexattribute)remove-list-item- Button to remove list items (requiresdata-indexattribute)add-list-item- Button to add new list items
5. List Element Data Atributes(for list-type elements)
data-index- Required for list item inputs and remove buttons
6. Other prepared CSS Classes
property-group- Recommended wrapper for styling property sections
Event Handling Examples
// ✅ Correct: All required attributes present
renderEditor: (props) => `
<div class="property-group">
<label>Text Input:</label>
<input type="text" value="${props.text}"
class="property-input" data-property="text">
</div>
<div class="property-group">
<label>Number Input:</label>
<select class="property-input" data-property="level" data-value-type="int">
<option value="1">Level 1</option>
<option value="2">Level 2</option>
</select>
</div>
<div class="property-group">
<label>
<input type="checkbox" ${props.enabled ? 'checked' : ''}
class="property-input" data-property="enabled" data-value-type="boolean">
Enable Feature
</label>
</div>
`
// ✅ Correct: List element with all required classes
renderEditor: (props) => `
<div class="property-group">
<label>Items:</label>
<div class="list-editor">
${props.items.map((item, index) => `
<div class="list-item-editor">
<input type="text" value="${item}"
class="list-item-input" data-index="${index}">
<button type="button" class="remove-list-item" data-index="${index}">×</button>
</div>
`).join('')}
<button type="button" class="add-list-item">+ Add Item</button>
</div>
</div>
`
// ❌ Incorrect: Missing required attributes
renderEditor: (props) => `
<input type="text" value="${props.text}"> <!-- Missing class and data-property -->
<select> <!-- Missing class and data-property -->
<option value="1">Level 1</option>
</select>
`Usage Notes
- Security: Prevent XSS with
UtilityHelpers.escapeHtml() - Element Activation: Need to add custom element name to
enabledElementsarray - Event Handling: Always use
class="property-input"anddata-propertyattributes - Type Safety: Use
data-value-typefor automatic type conversion - Validation: Input validation with appropriate error messages
- Accessibility: Consider HTML structure and aria attributes
📄 License
MIT License - See LICENSE file for details
📧 Contact
- GitHub Issues: Bug reports & feature requests
