Blox page builder library for drag-and-drop page building and static data management
npm install @bagelink/bloxBlox is a Vue 3 library for building drag-and-drop page builders and managing static data for web applications. It provides a complete solution for creating external preview systems and rendering dynamic page content.
- 🎨 Page Builder Integration - Full support for drag-and-drop page building
- 📄 Static Page Management - Manage JSON-based static page data
- 🔌 Component Registry - Flexible component registration system
- 📡 Communication System - Built-in messaging for builder-preview communication
- 🎯 Type-Safe - Full TypeScript support
- 🖼️ Base Components - Pre-built components (Button, Text, Image, etc.)
- 📱 Responsive - Mobile and desktop support
``bash`
npm install @bagelink/bloxor
pnpm add @bagelink/bloxor
yarn add @bagelink/blox
Blox is a Vue 3 library for building drag-and-drop page builders and managing static data for web applications. It provides a complete solution for creating external preview systems and rendering dynamic page content.
- 🎨 Page Builder Integration - Full support for drag-and-drop page building
- 📄 Static Page Management - Manage JSON-based static page data
- 🔌 Component Registry - Flexible component registration system
- 📡 Communication System - Built-in messaging for builder-preview communication
- 🎯 Type-Safe - Full TypeScript support
- 🖼️ Base Components - Pre-built components (Button, Text, Image, etc.)
- 📱 Responsive - Mobile and desktop support
`bash`
npm install @bagelink/blox vue-routeror
pnpm add @bagelink/blox vue-routeror
yarn add @bagelink/blox vue-router
The simplest way to set up Blox in your Vue application:
`typescript
// main.ts
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createBlox, ButtonConfig, TextConfig, TitleConfig } from '@bagelink/blox'
import '@bagelink/blox/dist/blox.css'
import App from './App.vue'
const app = createApp(App)
const router = createRouter({
history: createWebHistory(),
routes: [
// Your app routes
],
})
// Create and configure Blox
const blox = createBlox()
blox
.registerComponents([ButtonConfig, TextConfig, TitleConfig])
.registerRoutes(router)
app.use(blox)
app.use(router)
app.mount('#app')
`
That's it! Preview routes are now available at:
- /blox/preview/:pageId? - External preview with editor communication/blox/render/:pageId?
- - Static page rendering
`typescript
import { createBlox, createComponentConfig, ButtonConfig } from '@bagelink/blox'
import MyHeroSection from './components/MyHeroSection.vue'
const MyHeroConfig = createComponentConfig({
id: 'MyHero',
label: 'Hero Section',
icon: 'hero_section',
component: MyHeroSection,
content: [
{
type: 'text',
key: 'title',
label: 'Hero Title',
defaultValue: 'Welcome!',
},
],
})
const blox = createBlox()
blox
.registerComponents([ButtonConfig, MyHeroConfig])
.registerRoutes(router)
app.use(blox)
`
Import these configs to register built-in components:
- ButtonConfig - Customizable button/linkTextConfig
- - Rich text contentTitleConfig
- - Heading with optional subtitleImageConfig
- - Responsive imageSpacerConfig
- - Vertical spacingContainerConfig
- - Content container with max-width
For more examples, see SETUP_EXAMPLE.md.
---
`typescript
import { createApp } from 'vue'
import { registerBaseComponents } from '@bagelink/blox'
const app = createApp(App)
// Register all base components
registerBaseComponents(app)
app.mount('#app')
`
`vue
`
`vue
`
Register your custom components to make them available in the page builder:
`typescript
import { registerComponent, registerComponents } from '@bagelink/blox'
import MyButton from './MyButton.vue'
// Register a single component
registerComponent('Button', MyButton)
// Or register multiple components at once
registerComponents({
Button: MyButton,
Hero: MyHero,
Text: MyText,
})
`
Blox includes a built-in communication system for coordinating between the builder and preview:
`typescript
import { createCommunicationBridge } from '@bagelink/blox'
// Create a communication bridge
const bridge = createCommunicationBridge({
origin: 'https://editor.com',
targetWindow: window.parent,
})
// Listen for messages
bridge.on('update', ({ data }) => {
console.log('Received update:', data)
})
// Send messages
bridge.send('ready', { components: ['Button', 'Hero'] })
`
Use the built-in styling utilities to apply consistent styles:
`typescript
import { generateBlockStyles, getResponsiveClasses } from '@bagelink/blox'
const styles = generateBlockStyles(blockData, false)
const classes = getResponsiveClasses(blockData)
`
The library includes these pre-built components:
- Button - Customizable button with variants
- Container - Layout container with spacing
- Image - Responsive image component
- Spacer - Vertical/horizontal spacing
- Text - Rich text content
- Title - Heading component
---
#### registerComponent(type: string, component: ComponentConstructor)
Register a single component.
`typescript
import { registerComponent } from '@bagelink/blox'
import MyButton from './MyButton.vue'
registerComponent('Button', MyButton)
`
Parameters:
- type - The block type identifier (e.g., 'Button', 'Hero')component
- - Vue component constructor or async import
---
#### registerComponents(components: ComponentRegistry)
Register multiple components at once.
`typescript
import { registerComponents } from '@bagelink/blox'
registerComponents({
Button: MyButton,
Hero: MyHero,
Text: MyText,
})
`
Parameters:
- components - Object mapping type names to components
---
#### getComponent(type: string): ComponentConstructor | undefined
Get a registered component by type.
`typescript`
const ButtonComponent = getComponent('Button')
---
#### hasComponent(type: string): boolean
Check if a component type is registered.
`typescript`
if (hasComponent('Button')) {
// Button is registered
}
---
#### getAllComponents(): ComponentRegistry
Get all registered components.
`typescript`
const allComponents = getAllComponents()
---
#### unregisterComponent(type: string)
Remove a component from the registry.
`typescript`
unregisterComponent('Button')
---
#### clearRegistry()
Remove all registered components.
`typescript`
clearRegistry()
---
#### getRegisteredTypes(): string[]
Get array of all registered type names.
`typescript`
const types = getRegisteredTypes()
// ['Button', 'Hero', 'Text']
---
#### createNamespacedRegistry(namespace: string)
Create a namespaced registry for multi-tenant scenarios.
`typescript
const siteA = createNamespacedRegistry('site-a')
siteA.register('Button', ButtonA)
const siteB = createNamespacedRegistry('site-b')
siteB.register('Button', ButtonB)
`
Returns: Object with methods:
- register(type, component) - Register to namespaceget(type)
- - Get from namespacegetAll()
- - Get all from namespace
---
#### createCommunicationBridge(config: CommunicationConfig): CommunicationBridge
Create a communication bridge for postMessage communication.
`typescript
import { createCommunicationBridge } from '@bagelink/blox'
const bridge = createCommunicationBridge({
origin: 'https://editor.com',
targetWindow: window.parent,
})
`
Parameters:
- config.origin - Allowed origin (string or '*')config.targetWindow
- - Target window (default: window.parent)config.onMessage
- - Optional global message handler
---
#### CommunicationBridge.on(type: MessageType, handler: MessageHandler): () => void
Register a message handler.
`typescript
const unsubscribe = bridge.on('update', ({ data }) => {
console.log('Received update:', data)
})
// Later: unsubscribe()
`
Parameters:
- type - Message type or '*' for all messageshandler
- - Handler function
Returns: Unsubscribe function
---
#### CommunicationBridge.off(type: MessageType, handler: MessageHandler)
Unregister a message handler.
`typescript`
bridge.off('update', myHandler)
---
#### CommunicationBridge.send(type: MessageType, message?: any, data?: any)
Send a message to the target window.
`typescript`
bridge.send('focus', 'block-123')
bridge.send('update', null, { components: [...] })
Parameters:
- type - Message typemessage
- - Optional message payloaddata
- - Optional data payload
---
#### CommunicationBridge.destroy()
Clean up and remove all listeners.
`typescript`
bridge.destroy()
---
#### sendMessage(type, message?, targetWindow?, origin?)
Send a one-off message without creating a bridge.
`typescript
import { sendMessage } from '@bagelink/blox'
sendMessage('ready', { components: ['Button', 'Hero'] })
`
---
#### initializePage(pageData: PageData): Promise
Initialize a page with all settings and assets.
`typescript
import { initializePage } from '@bagelink/blox'
await initializePage(pageData)
`
What it does:
- Injects responsive CSS
- Applies page settings
- Loads Google Fonts
- Injects custom code
---
#### injectResponsiveCSS()
Inject the responsive CSS system.
`typescript
import { injectResponsiveCSS } from '@bagelink/blox'
injectResponsiveCSS()
`
---
#### injectCode(code: string | undefined, target: 'head' | 'body')
Inject custom code into head or body.
`typescript`
injectCode('', 'head')
---
#### loadGoogleFont(fontName: string): Promise
Load a Google Font.
`typescript`
await loadGoogleFont('Roboto')
---
#### loadComponentFonts(components: ComponentData[]): Promise
Load all fonts used in components.
`typescript`
await loadComponentFonts(pageData.components)
---
#### applyGlobalFont(fontName: string)
Apply a font globally to the page.
`typescript`
applyGlobalFont('Inter')
---
#### applyPageSettings(pageData: PageData)
Apply all page settings to the document.
`typescript`
applyPageSettings(pageData)
---
#### generateBlockStyles(data: Record
Generate CSS styles from block data.
`typescript
import { generateBlockStyles } from '@bagelink/blox'
const styles = generateBlockStyles(blockData, false)
// { 'margin-top': '2rem', 'padding': '1rem', ... }
`
Parameters:
- data - Block data objectisMobile
- - Whether to use mobile overrides (default: false)
Returns: CSS styles object
---
#### getResponsiveClasses(data: Record
Get responsive CSS classes for a block.
`typescript`
const classes = getResponsiveClasses(blockData)
// ['blox-block', 'responsive-colors', 'full-width-mobile']
---
#### getResponsiveCSS(): string
Get the responsive CSS to inject.
`typescript`
const css = getResponsiveCSS()
---
#### normalizeComponentData(data: Record
Normalize component data (convert string booleans, numbers, etc.).
`typescript
import { normalizeComponentData } from '@bagelink/blox'
const normalized = normalizeComponentData({
width: '800',
fullWidth: 'true',
title: 'Hello',
})
// { width: 800, fullWidth: true, title: 'Hello' }
`
---
#### deepClone
Deep clone an object.
`typescript`
const cloned = deepClone(originalData)
---
#### deepMerge(target: any, source: any): any
Deep merge two objects.
`typescript`
const merged = deepMerge(defaults, customSettings)
---
Preview component for external sites.
`vue`
:initial-page-data="pageData"
/>
Props:
- origin?: string - Allowed editor origin (default: '*')initialPageData?: PageData
- - Optional initial page data
Events:
Communicates via postMessage with these message types:
- ready - Sent when preview is readyfocus
- - Sent when block is focusedhighlight
- - Sent when block is highlighted
---
Production render component.
`vue`
:mobile-breakpoint="910"
/>
Props:
- pageData: PageData - Required page data to rendermobileBreakpoint?: number
- - Window width for mobile (default: 910)
---
`typescript`
interface ComponentData {
id: string
type: string
data: Record
}
---
`typescript`
interface PageData {
id?: string
components: ComponentData[]
pageSettings?: PageSettings
header_code?: string
body_code?: string
language?: Record
}
---
`typescript`
interface PageSettings {
selectedGoogleFont?: string
pageLanguage?: string
pageDirection?: 'ltr' | 'rtl'
additionalHeadCode?: string
additionalBodyCode?: string
customFavicon?: string
customOgImage?: string
customKeywords?: string
customRobotsMeta?: string
customAnalyticsCode?: string
disableGlobalHeadCode?: boolean
disableGlobalBodyCode?: boolean
disableGoogleAnalytics?: boolean
disableFacebookPixel?: boolean
}
---
`typescript`
interface GlobalSettings {
selectedGoogleFont?: string
pageLanguage?: string
pageDirection?: 'ltr' | 'rtl'
globalHeadCode?: string
globalBodyCode?: string
favicon?: string
ogImage?: string
siteKeywords?: string
googleAnalyticsId?: string
facebookPixelId?: string
}
---
`typescript`
type MessageType =
| 'ready'
| 'update'
| 'focus'
| 'highlight'
| 'preview'
| 'meta'
| 'delete'
| 'disableLinks'
| 'newBlock'
---
`typescript`
interface EditorMessage {
type: MessageType
message?: any
data?: any
isMobile?: boolean
}
---
`typescript`
interface BlockProps {
isMobile?: boolean
[key: string]: any
}
---
`typescript
interface StyleConfig {
// Spacing
marginTop?: number
marginBottom?: number
marginTopMobile?: number
marginBottomMobile?: number
padding?: number
paddingMobile?: number
// Width
width?: number
widthMobile?: number
widthPercent?: number
widthPercentMobile?: number
fullWidth?: boolean
fullWidthMobile?: boolean
// Colors
backgroundColor?: string
backgroundColorMobile?: string
textColor?: string
textColorMobile?: string
// Border
borderWidth?: number
borderWidthMobile?: number
borderStyle?: string
borderStyleMobile?: string
borderColor?: string
borderColorMobile?: string
borderRadius?: number
borderRadiusMobile?: number
// Typography
fontFamily?: string
fontFamilyMobile?: string
center?: boolean
centerMobile?: boolean
// Effects
shadowType?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'custom'
shadowTypeMobile?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'custom'
zIndex?: number
zIndexMobile?: number
// Visibility
showDesktop?: boolean
showMobile?: boolean
// Custom
customId?: string
customCSS?: string
}
`
---
#### update
Update components data.
`typescript`
{
type: 'update',
data: ComponentData[],
isMobile: boolean
}
#### focus
Focus a specific block.
`typescript`
{
type: 'focus',
message: 'block-id-123'
}
#### highlight
Highlight a specific block.
`typescript`
{
type: 'highlight',
message: 'block-id-123'
}
#### preview
Toggle preview mode.
`typescript`
{
type: 'preview',
message: true | false
}
#### disableLinks
Disable link navigation.
`typescript`
{
type: 'disableLinks',
message: true | false
}
---
#### ready
Preview is ready.
`typescript`
{
type: 'ready',
message: {
registeredTypes: string[]
}
}
#### focus
User focused a block.
`typescript`
{
type: 'focus',
message: 'block-id-123'
}
#### highlight
User highlighted a block.
`typescript`
{
type: 'highlight',
message: 'block-id-123'
}
#### delete
User wants to delete a block.
`typescript``
{
type: 'delete',
message: 'block-id-123'
}
---
Contributions are welcome! Please read our Contributing Guide for details.
MIT © Bagel Studio