@dynamic-widget/core
v1.2.0
Published
Framework-neutral widget schema engine, layout, validation, and state.
Maintainers
Readme
@dynamic-widget/core
Framework-agnostic schema engine for Dynamic Widget — parse WidgetSchema, validate form state, render DOM trees, editable tabs, designer patches, charts, and the widget catalog. Use it from plain JavaScript or as the foundation for @dynamic-widget/react, @dynamic-widget/angular, and @dynamic-widget/js.
Current release: 1.2.0 — See CHANGELOG and monorepo CHANGELOG.
Live demo & docs: https://dynamic-widget-app.vercel.app/ — try the interactive demo, the visual schema designer, or read the @dynamic-widget/core API.
What's new in 1.2.0
- Charts: seven types (
bar,barHorizontal,line,area,pie,donut,gauge); legend toggle, keyboard focus, value formatting; tab designer chart data/options (tabsSetChartProps,tabsSetChartData). - Schema designer (headless):
createSchemaDesignerControllerwith templates (SCHEMA_DESIGNER_PRESETS), undo/redo, import merge/replace + diff summary (schemaImportSummaryLines), validation jump-to-field, plugin palette (buildPluginDashboardPaletteGroup). - Adapters: Angular
dw-schema-designer, ReactSchemaDesigner, JScreateSchemaDesigner— see framework READMEs.
Published as ESM (
"type": "module"). Requires Node 18+ for tooling; the runtime targets modern browsers with DOM APIs.
Keywords: dynamic-widget schema-driven forms dashboard widgets validation typescript i18n async-binding options-from markdown file-upload editable-tabs wizard designer virtual-table
Install
npm install @dynamic-widget/corePair with @dynamic-widget/themes for CSS, and an adapter package for your framework.
Usage
import { createWidgetApi, renderDomTree } from "@dynamic-widget/core";
const host = document.getElementById("app")!;
const api = createWidgetApi(
{
schema: { widgets: [{ id: "name", type: "input", field: "name", label: "Name" }] },
values: {},
onValuesChange: (values) => console.log(values),
onEvent: (event) => console.log(event),
},
() => paint()
);
const communityFlags = {
canChart: false,
canAdvancedWizard: false,
canRules: false,
canDesigner: false,
canPlugins: false,
canAi: false,
};
function paint() {
renderDomTree({
host,
engine: api.engine,
flags: communityFlags,
errors: api.validate(),
onRefresh: paint,
onEvent: (e) => console.log(e),
});
}
paint();i18n (locale strings)
Use labelKey, placeholderKey, and schema.locales so one schema supports multiple languages:
import { resolveSchemaStrings, createWidgetApi } from "@dynamic-widget/core";
const api = createWidgetApi({
schema: {
defaultLocale: "en",
locales: {
en: { "form.name": "Name" },
fr: { "form.name": "Nom" }
},
widgets: [{ id: "name", type: "input", field: "name", labelKey: "form.name" }]
},
locale: "fr"
});
// engine.getSchema().widgets[0].label === "Nom"Or resolve before passing the schema: resolveSchemaStrings(schema, "fr"). Arabic (ar) sets dir="rtl" on the host when schema.rtl is unset. Optional: validateSchemaI18n(schema, locale) for missing keys in dev.
Async data binding (optionsFrom + dataProvider)
Load select/radio options from HTTP without per-field glue:
import { createFetchDataProvider, createWidgetApi, renderDomTree } from "@dynamic-widget/core";
const bindingCache = new Map();
const dataProvider = createFetchDataProvider();
const schema = {
widgets: [
{
id: "country",
type: "select",
field: "country",
label: "Country",
optionsFrom: { url: "/api/options/countries", labelKey: "name", valueKey: "code" }
},
{
id: "city",
type: "select",
field: "city",
label: "City",
visibleWhen: { field: "country", notEquals: "" },
optionsFrom: {
url: "/api/options/cities?country={country}",
labelKey: "name",
valueKey: "code",
dependsOn: ["country"]
}
}
]
};
renderDomTree({
host,
engine: api.engine,
flags: communityFlags,
errors: [],
onRefresh: paint,
dataProvider,
bindingCache
});{field} tokens in URLs are replaced from current values. Pass a custom DataProvider for auth, GraphQL, or non-JSON backends.
Markdown (read-only, Community)
{ id: "notes", type: "markdown", label: "Release notes", props: { content: "## v1.1\n\n- **Binding** for selects" } }Safe subset: headings, lists, bold/italic, links — no raw HTML pass-through. See renderMarkdownToElement.
File upload (Community)
{
id: "resume",
type: "fileUpload",
field: "resume",
label: "Resume",
props: { accept: ".pdf", maxSizeBytes: 5_242_880 }
}Emits fileSelected / fileRejected on onEvent. multiple is enterprise-gated.
Charts (enterprise)
Chart widgets render SVG via createChartSpec / renderChartToSvg when flags.canChart is true. Types: bar, barHorizontal, line, area, pie, donut, gauge. Use tabsSetChartData / tabsSetChartProps in editable tab designer events. Slice colors use --dw-chart-slice-* from @dynamic-widget/themes.
Visual schema designer (headless)
Build a custom form builder UI on the controller API (no DOM required):
import {
createSchemaDesignerController,
DEFAULT_FORM_DESIGNER_PALETTE,
schemaImportSummaryLines,
} from "@dynamic-widget/core";
const ctrl = createSchemaDesignerController({ schema: mySchema });
ctrl.applyPreset("contact-form");
ctrl.addField(ctrl.createAddDraft());
ctrl.undo();
const preview = ctrl.previewImportSchemaJson(jsonText);
if (preview.ok) {
console.log(schemaImportSummaryLines(preview.summary));
ctrl.importSchemaJson(jsonText, "merge");
}Ship a full UI with @dynamic-widget/angular (dw-schema-designer), @dynamic-widget/react (SchemaDesigner), or @dynamic-widget/js (createSchemaDesigner). Guide: Schema designer.
Virtual tables (enterprise)
On table widgets, set props: { virtualize: true } with >15 rows and an enterprise licence (canDesigner). Renders a scroll slice for large datasets.
validateSchema
Check structural issues before render (duplicate ids, missing type):
import { validateSchema, isSchemaValid } from "@dynamic-widget/core";
const issues = validateSchema(schema);
if (!isSchemaValid(schema)) {
console.warn(issues);
}Editable tabs
Handle tab designer events and apply patches to your schema:
import { applyEditableTabsAction } from "@dynamic-widget/core";
const result = applyEditableTabsAction(schema, "main-tabs", {
type: "tabsAdd",
tabLabel: "New tab",
});
if (result) {
schema = result.schema;
}Tear down
Clear the host between navigations:
host.replaceChildren();
api.refresh();API
createWidgetApi(options, onRefresh?)
| Option | Type | Description |
| --- | --- | --- |
| schema | WidgetSchema | Required. Root widget tree. |
| locale | string | BCP 47-ish tag; resolves labelKey / placeholderKey via schema.locales. |
| dataProvider | DataProvider | Fetches JSON for optionsFrom URLs. |
| values | WidgetValues | Initial field values. |
| onValuesChange | (values) => void | Called when bound fields change. |
| onEvent | (event) => void | Tab designer, buttons, and widget actions. |
| rules | WidgetRule[] | Enterprise rules engine input. |
| aiInsightProvider | AiInsightProvider | Optional async AI insight hook. |
Returns WidgetApi: getValues, setValues, setValue, validate, isValid, exportState, applyState, refresh, and engine.
renderDomTree(options)
| Option | Type | Description |
| --- | --- | --- |
| host | HTMLElement | Mount target (re-rendered in place). |
| engine | WidgetEngine | From createWidgetApi. |
| flags | EnterpriseFeatureFlags | Gated UI from @dynamic-widget/enterprise. |
| errors | WidgetValidationError[] | Usually api.validate(). |
| dark | boolean | Dark theme class on host. |
| onRefresh | () => void | Re-paint after internal actions. |
| onEvent | (event) => void | Widget / tab events. |
| dataProvider | DataProvider | Required when schema uses optionsFrom. |
| bindingCache | BindingCache | Per-host Map for resolved select options (adapters create one). |
Key exports
createFetchDataProvider,refreshBindingCache,getResolvedSelectOptions,evaluateBindingExpr— async bindingrenderMarkdownToElement— safe markdown → DOMresolveSchemaStrings,validateSchemaI18n,pickLocaleBundle,isLocaleRtl— i18ncreateChartSpec,renderChartToSvg, chart helpers — enterprise-gated chart SVGapplyEditableTabsAction,widgetActionToDesignerPatch— tab layout editingapplyDesignerPatch,createEmptySchema— schema patchescreateSchemaDesignerController,DEFAULT_FORM_DESIGNER_PALETTE,SCHEMA_DESIGNER_PRESETS,buildNodeFromFieldDraft,schemaImportSummaryLines,summarizeSchemaImport,buildPluginDashboardPaletteGroup— headless form designerdefineWidgetPlugin,registerWidgetPlugin— custom widget types (palette whencanPlugins)BUILTIN_WIDGET_TYPES, widget catalog helpers —v1-roadmap,features
Browser support
Requires a modern browser with standard DOM APIs (Chrome, Edge, Firefox, Safari). Chart SVG rendering and file download utilities work in evergreen desktop and mobile browsers.
@dynamic-widget/core does not assume a framework. Import it in SSR bundles only if you guard renderDomTree behind a browser check — adapters handle that pattern for React and Angular.
Related packages
@dynamic-widget/themes— shared CSS (tabs, forms, tables).@dynamic-widget/enterprise— licensing and gated features.@dynamic-widget/react— React component on top of this core.@dynamic-widget/angular— Angular standalone component on top of this core.@dynamic-widget/js—createDynamicWidgetvanilla host.
License
Publishing @dynamic-widget/[email protected]
Use this checklist when cutting a monorepo release (repeat for @dynamic-widget/themes, adapters, and @dynamic-widget/enterprise with matching versions).
- Changelog — Confirm CHANGELOG
1.2.0section lists chart, schema-designer, and a11y changes. - Version — Set
"version": "1.2.0"inpackage.json; adapters should depend on"@dynamic-widget/core": "^1.2.0". - Build & test — From repo root:
npm run build -w @dynamic-widget/coreandnpm test -w @dynamic-widget/core. - Dry run —
npm pack -w @dynamic-widget/coreand inspect the tarball (dist/,README.md,CHANGELOG.md). - Publish —
npm publish -w @dynamic-widget/core --access public(npm OTP if enabled). - Verify —
npm view @dynamic-widget/core versionand smoke-install in a scratch app. - Adapters & themes — Publish
@dynamic-widget/[email protected]first or with core, thenreact,angular,js,enterpriseon the same tag; bump root CHANGELOG. - Docs site — Deploy
apps/after packages are on npm so demos match published APIs.
