AsukaDesigner JavaScript API - v1.0.0

AsukaDesigner v1

AsukaDesigner v1 is a modular design editor for the web, built with Fabric, TypeScript, and a runtime-first API.

It supports both:

  • package-based integrations through createAsukaDesigner(...)
  • browser / vanilla integrations through the generated product ESM distribution

Core capabilities include:

  • a configurable multi-zone layout
  • responsive mobile/tablet layout adaptations
  • built-in modules such as Views, Designs, Text, Images, Shapes, Layers, Zoom, Ruler, and Download
  • a contextual selection menu with popover support
  • a runtime font registry for system, Google, and custom fonts
  • state export, import, and local persistence helpers
  • configurable upload flows for local object URLs, Base64 data URLs, or remote HTTP endpoints
  • runtime theme helpers
  • runtime custom module registration with reusable UI helpers
  • pricing helpers for rule-based totals and formatted price output

This repository uses two complementary documentation layers:

Recommended starting points:

npm install
npm run dev
npm run build
npm run build:lib
npm run build:product
npm run build:product-single
npm run build:browser-global
npm run build:release-artifact
npm run docs

npm run build:product emits four required runtime files:

  • asukadesigner.min.js: API, UI and shared workflows
  • asuka-fabric.min.js: Fabric runtime, loaded on demand
  • asuka-babylon.min.js: Babylon runtime and glTF support, loaded on demand
  • asukadesigner.min.css: shared styles

It also emits optional licensed add-ons: studio-addon.js, pricing-addon.js, pricing-rules-addon.js, marker-addon.js, drawing-areas-addon.js, and model-optimizer-addon.js. The runtime requests an add-on only when the active license grants its feature. A missing optional file is treated as unavailable and its module is not registered or displayed.

Only the main JavaScript file and stylesheet are referenced by the host page. Renderer and add-on URLs are resolved relative to the main module; no manifest is required.

npm run check:package rebuilds every published target and inspects the real npm pack manifest. Publication is allowlisted to dist/ artifacts only; source files, demos such as test3d.html, tests, the license server, license records, and signing keys are rejected automatically. The same check runs before an npm publication through prepublishOnly.

npm run check:release runs the complete delivery gate: lint, type checking, contract audits, unit tests, bundle checks, browser workflows, package-content validation, a high-severity audit of the complete installed dependency graph, and generation of the versioned browser artifact under dist/release/.

The official first-beta delivery format is the versioned dist/release/asuka-designer-<version>/ directory rather than npm. It contains the ten runtime files, manifest.json, SHA256SUMS, a minimal initialization page and server deployment notes. See guides/release-and-rollback.md.

The commercial API and license base URL is permanently https://api.asukadev.com. Integrations only pass a license key or a signed token; licensing, AI, and paid-export endpoints are not configurable through the public initialization API. That origin is machine-only: public demos and documentation live on https://designer.asukadev.com, while accounts, licensing, billing, and support live on https://portal.asukadev.com.

Development pages start in free mode when no local license is configured. To exercise Premium locally, create a TEST license in the Symfony administration, copy demo-license.local.example.js to the ignored demo-license.local.js, and set its key. The exported object is passed directly to the public license initialization option. No endpoint discovers a key, and no URL parameter unlocks Premium. Use ?free-demo=1 only to force free mode while testing. The local file is excluded from Git and from the published package.

import { createAsukaDesigner, type AsukaDesignerOptions } from 'asuka-designer';

const options: AsukaDesignerOptions = {
layout: {
top: { collapsed: true },
},
};

const designer = createAsukaDesigner('#app', options);

designer.api.addText({ text: 'Hello AsukaDesigner' });

Element variations use the same API in Fabric and Babylon:

const result = await designer.variations.apply({
elementId: 'demo-product-3d',
partId: 'handle',
variationId: 'demo-blue-part-handle',
});

See Element variations for catalog queries, object-scoped mutations, events, and the structured result.

<div id="app"></div>
<link rel="stylesheet" href="./dist/product/asukadesigner.min.css" />
<script type="module">
import { createAsukaDesigner } from './dist/product/asukadesigner.min.js';

const designer = createAsukaDesigner('#app', {
layout: {
top: { collapsed: true },
},
});

designer.api.addText({ text: 'Hello AsukaDesigner' });
</script>

For a 3D or mixed product, the production entry automatically loads the Babylon chunk when the first 3D view becomes active:

import { createAsukaDesigner } from './dist/product/asukadesigner.min.js';

const designer = createAsukaDesigner('#app', {
engine: '3d',
});

loadAsuka3DEngineAddon remains available for low-level builds and for integrations that intentionally provide a custom renderer loader.

The built-in Upload / Images flow can now be configured at startup. Supported strategies are:

  • objectUrl for immediate local previews
  • base64 for data-URL-based persistence flows
  • remote for custom server uploads such as PHP endpoints

With no upload configuration, files are converted to Base64. Providing an endpoint or a handler automatically selects the remote strategy, so strategy: 'remote' is optional in that case. Use objectUrl explicitly only when a temporary browser-session URL is desired.

The same upload policy is also reused by remote sources inside the Images module when possible (Pixabay, Pexels, Facebook), with a safe fallback to direct URL insertion if the browser cannot fetch the remote file.

Official remote upload snippet:

const designer = createAsukaDesigner('#app', {
moduleConfig: {
upload: {
strategy: 'remote',
endpoint: '/upload.php',
method: 'POST',
credentials: 'include',
mapResponseToUrl: (json) => json.url,
},
},
});

For advanced integrations, use handler to upload directly to WordPress, Symfony, or any custom backend:

const designer = createAsukaDesigner('#app', {
moduleConfig: {
upload: {
strategy: 'remote',
handler: async (file, context) => {
const body = new FormData();
body.append('image', file, file.name);

const response = await fetch('/api/asuka/upload', { method: 'POST', body });
const json = await response.json();
return json.url;
},
},
},
});

For WordPress admin integrations or any custom media library, pass a selectHandler. AsukaDesigner calls it, awaits the promise, normalizes the returned media, registers the asset in the Upload module list, and applies it to the correct target automatically:

const designer = createAsukaDesigner('#app', {
moduleConfig: {
upload: {
selectHandler: async (context) => {
const selected = await openWordPressMediaLibraryFromYourHost({
multiple: context.multiple,
title: context.config.pickerTitle || 'Choose an image',
buttonText: context.config.pickerButtonText || 'Use this file',
});

return selected.map((item) => ({
url: item.url,
name: item.filename || item.name,
source: 'wordpress-media',
attachmentId: item.id,
metadata: item,
}));
},
},
},
});

The handler can return a File, a URL string, an array, objects such as { url, name, metadata }, or containers such as { items: [...] }, { data: [...] }, or { result: [...] }.

The same upload policy is used by canvas image uploads, remote image providers when possible, and file inputs such as MediaInput, ImageField, and IconField.

AsukaDesigner exposes a simple pricing flow at runtime:

const total = designer.api.getPrice(29.99);
const formatted = designer.util.formatPrice(total);

Use getPrice(...) when you want to start from an external base price and let AsukaDesigner apply:

  • object-level standard prices
  • printing-method surcharges
  • color-based surcharges
  • dynamic pricing rules

Use getPriceUiBreakdown(...) when you also want a UI-friendly breakdown:

const breakdown = designer.api.getPriceUiBreakdown(29.99);

Use designer.util.formatPrice(...) to format the final number with the active price format configuration.

For one JSON payload that can restore the full product-designer session, use exportFull().

const fullState = designer.util.state.exportFull();
await saveToDatabase(fullState);

const restored = createAsukaDesigner('#app', {
initialState: fullState,
});

The full state includes layout, modules, document/canvas state, views, drawing areas, pricing rules, tools, configuration, appearance, permissions, and other serializable app state. Runtime objects such as Fabric instances, DOM nodes, handlers, and active network requests are intentionally excluded.

Import/export one section without replacing the rest of the editor:

const template = designer.util.state.exportPart('template');
designer.util.state.importPart('template', template);

const theme = designer.util.state.exportPart('theme');
designer.util.state.importPart('theme', theme);

A named part can also be imported from a complete full-state JSON. Explicit partial scopes are strict: omitted sections remain unchanged. See State and persistence. Historical implementation notes remain available in the engineering archive.

Overlay defaults are resolved by type, not by one global target. Drawers are contained inside the central designer content by default, while modals and tooltips stay viewport-level by default.

Override a specific module with mountTarget:

createAsukaDesigner('#app', {
layout: {
left: {
items: [
{
kind: 'download',
label: 'Download',
icon: 'download',
type: 'drawer',
mountTarget: '#download-drawer-host',
},
],
},
},
});

See guides/overlays.md.

The built-in English dictionary lives in src/core/i18n.ts. Generated translation references are included for translators:

The bottom view pagination bar can be kept automatic, forced to a visual mode, or hidden while keeping multi-view support active:

createAsukaDesigner('#app', {
ui: {
viewsPagination: {
enabled: true,
mode: 'auto', // 'auto' | 'thumbnails' | 'dots' | 'numbers'
},
},
});

createAsukaDesigner('#app', {
ui: {
viewsPagination: false,
},
});

See guides/layout.md and guides/api-overview.md.

The current recommended default layout is:

  • left: Views, Designs, Text, Images, Shapes, Layers
  • top: Magnifier, Ruler, Zoom, Download
  • top.collapsed = false
  • right: empty by default
  • bottom: empty by default
npm run docs

The generated output is written to docs/index.html.

The editorial guides live under guides/index.md.

AsukaDesigner exposes one strict JSON envelope: format: 'asuka-state' with schemaVersion: 2.

const fullState = designer.util.state.exportFull();
const documentState = designer.util.state.exportPart('document');
const themeState = designer.util.state.exportPart('theme');

designer.util.state.importFull(fullState);
designer.util.state.importPart('document', documentState);
designer.util.state.importPart('theme', themeState);

Restore either a full state or a named part directly at initialization through the same option:

const savedState = loadFromDatabase();

const designer = createAsukaDesigner('#app', {
initialState: savedState,
});

The active view id is part of the document state and is restored automatically. Older formats, generic config payloads and document wrappers are intentionally rejected.

See guides/state-and-persistence.md.

The outer designer shell no longer uses hardcoded maximum dimensions. You can optionally control its size at initialization through shell.width, shell.height, shell.maxWidth, and shell.maxHeight. All four values default to null.

createAsukaDesigner('#app', {
shell: {
width: '1200px',
height: '80vh',
maxWidth: '1400px',
maxHeight: '900px',
},
});

When the canvas is visually shrunk with CSS, you can keep editor-only visuals readable with the uiScaling option.

createAsukaDesigner('#app', {
uiScaling: {
keepControlsReadable: true,
keepHelpersReadable: true,
baseControlSize: 20,
baseTouchControlSize: 28,
minHelperStrokeWidth: 1,
maxCompensationMultiplier: 4,
},
});

This affects controls, selection borders, snap lines, and technical helper overlays only. It does not change the actual design layers or exported output.

AsukaDesigner supports fine-grained runtime permissions through permissions in createAsukaDesigner(...) and through designer.util.setPermissions(...).

Notable permission keys include:

  • canAddRect, canAddText, canAddImage, canAddShape, canDrawShape
  • canDuplicateObject, canDeleteObject, canMoveObject, canResizeObject, canRotateObject
  • canEditObjectColors, canEditObjectStudio, canEditObjectText
  • canViewLayers, canReorderLayers, canLockLayers, canHideLayers, canSelectLayers
  • canViewDesigns, canAddDesigns
  • canViewElements, canAddElements, canEditElements, canEditElementVariations, canDeleteElements
  • canOpenImages, canUploadImage, canUseExternalImageLibraries, canUseFacebookImages, canUsePixabayImages, canUsePexelsImages
  • canEditText, canEditFontFamily, canEditTextFormatting, canEditTextCurve
  • canUseColors, canEditColors, canUseStudio, canApplyStudioEffects
  • canUseZoom, canZoomIn, canZoomOut, canFitViewport, canResetViewport, canPanCanvas
  • canDownload, canDownloadCurrentView, canDownloadAllViews, canDownloadObjects, canDownloadDrawingZones, canExportJson, canImportJson
  • canViewViews, canCreateView, canEditView, canDeleteView
  • canViewDrawingAreas, canCreateDrawingAreas, canEditDrawingAreas, canDeleteDrawingAreas
  • canViewFixedSlots, canCreateFixedSlots, canEditFixedSlots, canDeleteFixedSlots
  • canUndo, canRedo, canReset, canUseStateTools, canUseMagnifier

Example:

const designer = createAsukaDesigner('#app', {
permissions: {
canAddText: true,
canUploadImage: false,
canUseExternalImageLibraries: false,
canDeleteObject: false,
canReorderLayers: false,
canEditTextCurve: false,
canDownload: false,
},
});
  • The ruler overlay aligns its 0 origin with the visible canvas frame and scales ticks with both Fabric zoom and CSS display scaling.

When no colors are configured at initialization or via the colors API, the Colors module falls back to a native color picker instead of showing a default palette. If colors are configured directly, it shows a flat list. If printing methods with palettes are configured, it shows the printing-method-driven palette flow.

The built-in transform module exposes exact X/Y position and rotation controls for the selected object and can be used directly from the contextual selection menu.

You can update an existing layout menu item in place:

designer.layout.updateItem('views', {
label: 'My Views',
icon: 'puzzle',
type: 'modal',
});

updateItem(...) accepts either the block id or the item kind.

Useful layout runtime methods include:

designer.layout.addItem(zone, item, index?)
designer.layout.removeItem(blockIdOrKind)
designer.layout.updateItem(blockIdOrKind, patch)
designer.layout.moveBlock(blockId, targetZone, index?)

designer.layout.getZoneType(zone)
designer.layout.setZoneType(zone, type)
designer.layout.getZoneCollapsed(zone)
designer.layout.setZoneCollapsed(zone, value)
designer.layout.toggleZoneCollapsed(zone)
designer.layout.setZoneCollapsible(zone, value)

designer.layout.getBlockCollapsed(blockIdOrKind)
designer.layout.setBlockCollapsed(blockIdOrKind, value)
designer.layout.toggleBlockCollapsed(blockIdOrKind)
designer.layout.setBlockCollapsible(blockIdOrKind, value)

Example:

designer.layout.setBlockCollapsible('views', true);
designer.layout.setBlockCollapsed('views', false);
designer.layout.setZoneCollapsible('left', true);

Pricing calculations ignore technical helper objects and runtime overlays.

Pixabay, Pexels, and Facebook image sources are only shown when their corresponding integration credentials are configured. When an integration is not configured, its tab and module entry stay hidden. Empty providers also avoid rendering an empty image grid.

Menu labels can be translated dynamically through the i18n dictionary.

Resolution order:

  1. menu.<id>
  2. menu.<kind>
  3. fallback to the raw label

Example:

createAsukaDesigner('#app', {
i18n: {
locale: 'fr',
translations: {
fr: {
menu: {
views: 'Vues',
designs: 'Designs',
'block-views': 'Mes vues',
},
},
},
},
});

This allows a generic translation by kind, with an optional per-block override by id.

AsukaDesigner includes an extensible i18n layer with English built in and runtime translation injection support.

Menu labels can now be translated dynamically with this resolution order:

  1. menu.<id>
  2. menu.<kind>
  3. fallback to the raw label

Example:

const instance = createAsukaDesigner('#app', {
i18n: {
locale: 'fr',
fallbackLocale: 'en',
translations: {
fr: {
menu: {
views: 'Vues',
designs: 'Designs',
text: 'Texte',
image: 'Images',
shapes: 'Formes',
layers: 'Calques',
},
},
},
},
});

Translations can also be added later with instance.util.i18n.addTranslations(...) and switched at runtime with instance.util.i18n.setLocale(...).

Use the explicit boot API for full product/session replacement:

const next = replaceAsukaDesigner('#app', fullState);

For low-level control, you can also use:

const next = createAsukaDesigner('#app', {
boot: { mode: 'replace-product', state: fullState },
});

The boot API accepts the same canonical state payload:

createAsukaDesigner('#app', {
boot: {
mode: 'initialState',
state: fullState,
},
});

Supported modes are initialState and replace-product.

A structured debug mode is available for integrations:

createAsukaDesigner('#app', {
debug: {
boot: true,
state: true,
layout: true,
},
});

This helps inspect boot resolution, state import/export, and layout normalization.

AsukaDesigner distinguishes between:

  • zone types such as default, panels, and inline
  • item opening types such as floating, modal, drawer, popover, and inline

This is an important difference:

  • the zone type controls how an entire zone renders its blocks
  • the item type controls how a single module opens when clicked

For example, a right sidebar can render all of its module contents directly by setting:

createAsukaDesigner('#app', {
layout: {
right: {
type: 'panels',
items: [
{ kind: 'layers', label: 'Layers', icon: 'layers', type: 'floating' },
{ kind: 'views', label: 'Views', icon: 'views', type: 'floating' },
],
},
},
});

This is different from setting an item to type: 'inline', which affects the module/block itself rather than the whole zone rendering strategy.

See also: Render engines and 3D-ready views.

The current source fixes Product 3D restoration after undo/redo, prevents camera persistence from overwriting recent scene mutations, persists restored scenes centrally after history navigation, and closes the contextual toolbar when a selected 3D layer is deleted. See the archived implementation note.

Single-mesh GLB support is now isolated from the established multi-part variation pipeline. Multi-part products no longer receive synthetic Default children, while one-mesh products keep a flat variation tree and one optional native-material Default. Linked changes emit and commit history only once. See the archived implementation note.

The current source restores the functional Product 3D hover behavior (part-specific tooltip and temporary opacity) and adds opt-in structured timing logs for model loading, addition, hover, UI propagation, variations, history, rendering and camera input. Enable it with localStorage.setItem('asuka:3d-perf-debug', '1'), reload, reproduce the problem, then run __ASUKA_3D_PERF__.download(). See DIAGNOSTIC_3D_PERFORMANCE_LOGS.md.

The browser performance report showed that Babylon hover callbacks and rendering were fast, while application-level state rerenders and full-size thumbnail readbacks caused the visible stalls. The 3D hover tooltip now updates imperatively without rerendering AsukaRoot, camera motion no longer persists the full scene, 3D previews encode directly at thumbnail resolution, duplicate 3D facade events are removed, and render.frame-gap diagnostics now measure real main-thread blocking. See the archived performance note.

The built-in camera module controls orbit limits, exact camera placement, and camera presets attached to Product 3D variations. The same features are available without UI through designer.util.camera.

See CAMERA_MODULE_AND_API.md for layout configuration, API examples, persistence rules, and public types.

The mixed Fabric/Babylon demo applies its White and Blue material variations to a single logical whole-product part. Variation clicks now update the existing 3D instance and can no longer create duplicate products. See FIX_EXPORT_IMPORT_DEMO_VARIATION_DUPLICATION.md.

See the archived FIX_EXPORT_IMPORT_RUNTIME_RESTORE_AND_PARTS.md note for the runtime import guard and canonical Product 3D part-group normalization used by the Elements module.

Product 3D catalog elements can provide a product-independent parametric recipe. AsukaDesigner generates the controls, evaluates safe expressions and constraints, applies non-cumulative resize, transform and visibility rules, and persists only per-instance values. Historical details remain in PARAMETRIC_3D_FOUNDATION.md; the root index.html contains the maintained demo.

The product-parameters module applies numeric changes while typing. Intermediate input changes are transient and the final blur or Enter action creates one history entry. Babylon parametric baselines are rebuilt after asynchronous GLB restoration or mesh replacement, and imported quaternion rotations are preserved.

See the archived foundation and live-update notes.

A Product 3D element can expose dimensions directly inside element-variation-picker through contextual parametric.ui.variationControls.scopes. The Elements editor assigns each parameter to the whole product or to precise Product 3D parts, so selecting one part never displays unrelated controls. See the archived implementation note.