@bagelink/blox
v1.9.156
Published
Blox page builder library for drag-and-drop page building and static data management
Downloads
5,288
Readme
@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.
Features
- 🎨 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
Installation
npm install @bagelink/blox
# or
pnpm add @bagelink/blox
# or
yarn add @bagelink/blox@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.
Features
- 🎨 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
Installation
npm install @bagelink/blox vue-router
# or
pnpm add @bagelink/blox vue-router
# or
yarn add @bagelink/blox vue-routerQuick Start (Recommended)
The simplest way to set up Blox in your Vue application:
// 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
With Custom Components
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)Available Built-in Component Configs
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.
Alternative Setup (Legacy)
1. Register Base Components
import { createApp } from 'vue'
import { registerBaseComponents } from '@bagelink/blox'
const app = createApp(App)
// Register all base components
registerBaseComponents(app)
app.mount('#app')2. Use the RenderPage Component
<template>
<RenderPage :page-data="pageData" />
</template>
<script setup lang="ts">
import { RenderPage } from '@bagelink/blox'
const pageData = {
components: [...],
pageSettings: {...}
}
</script>3. Use ExternalPreview for Page Builder
<template>
<ExternalPreview :origin="editorOrigin" />
</template>
<script setup lang="ts">
import { ExternalPreview } from '@bagelink/blox'
const editorOrigin = 'https://your-editor.com'
</script>Core Concepts
Component Registry
Register your custom components to make them available in the page builder:
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,
})Communication System
Blox includes a built-in communication system for coordinating between the builder and preview:
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'] })Styling Utilities
Use the built-in styling utilities to apply consistent styles:
import { generateBlockStyles, getResponsiveClasses } from '@bagelink/blox'
const styles = generateBlockStyles(blockData, false)
const classes = getResponsiveClasses(blockData)Base Components
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
API Reference
Registry API
registerComponent(type: string, component: ComponentConstructor)
Register a single component.
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.
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.
const ButtonComponent = getComponent('Button')hasComponent(type: string): boolean
Check if a component type is registered.
if (hasComponent('Button')) {
// Button is registered
}getAllComponents(): ComponentRegistry
Get all registered components.
const allComponents = getAllComponents()unregisterComponent(type: string)
Remove a component from the registry.
unregisterComponent('Button')clearRegistry()
Remove all registered components.
clearRegistry()getRegisteredTypes(): string[]
Get array of all registered type names.
const types = getRegisteredTypes()
// ['Button', 'Hero', 'Text']createNamespacedRegistry(namespace: string)
Create a namespaced registry for multi-tenant scenarios.
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
Communication API
createCommunicationBridge(config: CommunicationConfig): CommunicationBridge
Create a communication bridge for postMessage communication.
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.
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.
bridge.off('update', myHandler)CommunicationBridge.send(type: MessageType, message?: any, data?: any)
Send a message to the target window.
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.
bridge.destroy()sendMessage(type, message?, targetWindow?, origin?)
Send a one-off message without creating a bridge.
import { sendMessage } from '@bagelink/blox'
sendMessage('ready', { components: ['Button', 'Hero'] })Renderer API
initializePage(pageData: PageData): Promise<void>
Initialize a page with all settings and assets.
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.
import { injectResponsiveCSS } from '@bagelink/blox'
injectResponsiveCSS()injectCode(code: string | undefined, target: 'head' | 'body')
Inject custom code into head or body.
injectCode('<script>console.log("Hello")</script>', 'head')loadGoogleFont(fontName: string): Promise<void>
Load a Google Font.
await loadGoogleFont('Roboto')loadComponentFonts(components: ComponentData[]): Promise<void>
Load all fonts used in components.
await loadComponentFonts(pageData.components)applyGlobalFont(fontName: string)
Apply a font globally to the page.
applyGlobalFont('Inter')applyPageSettings(pageData: PageData)
Apply all page settings to the document.
applyPageSettings(pageData)Styles API
generateBlockStyles(data: Record<string, any>, isMobile?: boolean): Record<string, string>
Generate CSS styles from block data.
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<string, any>): string[]
Get responsive CSS classes for a block.
const classes = getResponsiveClasses(blockData)
// ['blox-block', 'responsive-colors', 'full-width-mobile']getResponsiveCSS(): string
Get the responsive CSS to inject.
const css = getResponsiveCSS()Normalizer API
normalizeComponentData(data: Record<string, any>): Record<string, any>
Normalize component data (convert string booleans, numbers, etc.).
import { normalizeComponentData } from '@bagelink/blox'
const normalized = normalizeComponentData({
width: '800',
fullWidth: 'true',
title: 'Hello',
})
// { width: 800, fullWidth: true, title: 'Hello' }deepClone<T>(obj: T): T
Deep clone an object.
const cloned = deepClone(originalData)deepMerge(target: any, source: any): any
Deep merge two objects.
const merged = deepMerge(defaults, customSettings)Components
ExternalPreview
Preview component for external sites.
<ExternalPreview
:origin="editorOrigin"
: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
RenderPage
Production render component.
<RenderPage
:page-data="pageData"
:mobile-breakpoint="910"
/>Props:
pageData: PageData- Required page data to rendermobileBreakpoint?: number- Window width for mobile (default: 910)
TypeScript Types
ComponentData
interface ComponentData {
id: string
type: string
data: Record<string, any>
}PageData
interface PageData {
id?: string
components: ComponentData[]
pageSettings?: PageSettings
header_code?: string
body_code?: string
language?: Record<string, any>
}PageSettings
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
}GlobalSettings
interface GlobalSettings {
selectedGoogleFont?: string
pageLanguage?: string
pageDirection?: 'ltr' | 'rtl'
globalHeadCode?: string
globalBodyCode?: string
favicon?: string
ogImage?: string
siteKeywords?: string
googleAnalyticsId?: string
facebookPixelId?: string
}MessageType
type MessageType =
| 'ready'
| 'update'
| 'focus'
| 'highlight'
| 'preview'
| 'meta'
| 'delete'
| 'disableLinks'
| 'newBlock'EditorMessage
interface EditorMessage {
type: MessageType
message?: any
data?: any
isMobile?: boolean
}BlockProps
interface BlockProps {
isMobile?: boolean
[key: string]: any
}StyleConfig
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
}Message Protocol
Editor → Preview
update
Update components data.
{
type: 'update',
data: ComponentData[],
isMobile: boolean
}focus
Focus a specific block.
{
type: 'focus',
message: 'block-id-123'
}highlight
Highlight a specific block.
{
type: 'highlight',
message: 'block-id-123'
}preview
Toggle preview mode.
{
type: 'preview',
message: true | false
}disableLinks
Disable link navigation.
{
type: 'disableLinks',
message: true | false
}Preview → Editor
ready
Preview is ready.
{
type: 'ready',
message: {
registeredTypes: string[]
}
}focus
User focused a block.
{
type: 'focus',
message: 'block-id-123'
}highlight
User highlighted a block.
{
type: 'highlight',
message: 'block-id-123'
}delete
User wants to delete a block.
{
type: 'delete',
message: 'block-id-123'
}Contributing
Contributions are welcome! Please read our Contributing Guide for details.
License
MIT © Bagel Studio
