A React-based visual editor for morphos mapping objects. Provides an editable
form whose value can be read back as a RootMapping, with optional destination and
source JSON Schemas to drive field suggestions, type inference, and dropdowns.
react is declared as an optional peer dependency. Install it (and react-dom) in your app:
npm install react react-domimport { useState } from 'react';
import { MappingEditor } from 'morphos/react';
import type { RootMapping } from 'morphos';
const initialMapping: RootMapping = {
code: 'UPC.substring(0, 5)',
qty: 'QTY',
lineItems: {
forEach: 'LINE_ITEMS',
map: { id: 'ID', qty: 'QTY' }
}
};
function Example() {
const [snapshot, setSnapshot] = useState<RootMapping>({});
return (
<>
<MappingEditor
onChange={setSnapshot}
defaultValue={initialMapping}
/>
<pre>{JSON.stringify(snapshot, null, 2)}</pre>
</>
);
}| Prop | Type | Description |
|---|---|---|
value |
RootMapping |
Controlled value. Reading from ref.current.value reflects current internal state on demand. |
defaultValue |
RootMapping |
Uncontrolled initial value. Used once on mount. |
onChange |
(next: RootMapping) => void |
Called after each edit with the complete current mapping object. |
schema |
JsonSchema |
Destination JSON Schema. Drives field labels, type-aware add bar, and required indicators. |
sourceSchema |
JsonSchema |
Source JSON Schema. Drives dropdown suggestions for value expressions, forEach, and from. |
components |
Partial<MappingEditorComponents> |
Override any of the built-in UI parts (see Theming). |
labels |
Partial<MappingEditorLabels> |
Override any of the built-in user-visible strings (see Localization). |
The component exposes a ref handle:
interface MappingEditorHandle {
readonly value: RootMapping;
}Use onChange for live previews, validation, or controlled state:
const [mapping, setMapping] = useState<RootMapping>({});
<MappingEditor value={mapping} onChange={setMapping} />Passing schema makes the editor list fields from the schema as drop-down options, infer the
mapping kind from each field's type, and show required-field markers.
const destinationSchema = {
type: 'object',
required: ['code', 'amount'],
properties: {
code: { type: 'string' },
qty: { type: 'number' },
lineItems: {
type: 'array',
items: { type: 'object', properties: { id: { type: 'string' } } }
}
}
};
<MappingEditor schema={destinationSchema} />Keys not present in the schema render as free-form text inputs ("custom" fields). Schema-bound
keys render as a dropdown with an Advanced… escape hatch — the same dropdown/input/reset
pattern used for values. When a mapping level has no sibling fields, it also exposes a
Current value option, which writes the wildcard key '*' and maps the value of the current
mapping level instead of one named property.
Each mapping level ends with a blank field selector. Selecting a field, choosing Advanced…,
or typing a custom key turns that line into a regular mapping row and adds a new blank selector
underneath it.
Passing sourceSchema populates dropdowns for value expressions, forEach, from, and
conditional when expressions:
- Field values suggest scalar paths from the source schema (e.g.
QTY,CUSTOMER.NAME). Name matches (qty≈QTY,lineItems≈LINE_ITEMS) are sorted to the top. They also include All nested fields, which writes the expression'*'and copies the current object fields or list elements into that destination field. forEachsuggests array paths.fromsuggests object paths.whensuggests scalar paths that can be used as truthy/falsy conditions.
Inside a List mapping, value dropdowns also include array-context expressions:
$index, $record, and $collection.
Each dropdown has an Advanced… option that switches to a free-form expression input, and
a back button that returns to the dropdown without losing the typed value.
When descending into a List or Object section, the editor resolves the parent
forEach/from expression against the source schema and uses the matching sub-schema for
nested suggestions (so inside lineItems.forEach = LINE_ITEMS, the id field suggests ID).
The editor is composed from small components. Override any of them via the components
prop. Each override receives the props for its slot and is responsible for rendering it.
import { MappingEditor, type KeyInputProps } from 'morphos/react';
const MyKeyInput = ({ value, onChange, placeholder }: KeyInputProps) => (
<input className="my-key" value={value} onChange={e => onChange(e.target.value)} placeholder={placeholder} />
);
const components = { KeyInput: MyKeyInput };
<MappingEditor components={components} />Built-in defaults render bare HTML with dm-mapping-* class hooks for styling.
| Name | Purpose |
|---|---|
Container |
Wraps the list of rows in a level (root or nested). |
Row |
Renders a key→expression row or blank field selector. Receives keyInput, typeSelector, value, reorder, remove slots. |
SectionRow |
Renders a key→section row (List / Object / Conditional / Concat). Receives value for the row control and section for the nested editor body. |
KeyInput |
Free-form key text input. |
KeyLabel |
Read-only key display when bound to a schema field. |
SuggestedKeyInput |
Schema-aware key dropdown + Advanced… fallback (used when destination schema is set). |
ValueInput |
Free-form JS-expression text input. |
SuggestedValueInput |
Source-schema-aware value dropdown + Advanced… fallback. |
RemoveButton |
Per-row delete button. |
Reorder |
Up/down buttons for moving a row within its parent list. |
TypeSelector |
Per-row mapping-kind selector (Value / List / Object / Conditional / Concat). |
AddElseButton |
Adds the optional fallback branch inside a Conditional mapping. |
AddItemButton |
Adds a new Value item to a Concat mapping. |
SchemaAddBar |
Legacy schema-fields dropdown (no longer rendered by default; kept for compatibility). |
Section |
Wraps a section's optional header + body. |
SectionHeader |
Renders the forEach, optional from, or when label next to its value input. |
Pre-built component sets are published as separate subpath exports — each comes with the classes for its target Bootstrap version:
import bootstrap34 from 'morphos/react/bootstrap34';
import bootstrap53 from 'morphos/react/bootstrap53';
<MappingEditor components={bootstrap34} />
<MappingEditor components={bootstrap53} />The themes only emit class names — you bring the CSS yourself (e.g. via the relevant Bootstrap CSS file or CDN link). Individual components are also re-exported from each theme in case you want to mix-and-match:
import { Row, SuggestedValueInput } from 'morphos/react/bootstrap53';If you want to extend the default rendering rather than replace it, import the
defaultComponents map and spread it:
import { defaultComponents, MappingEditor } from 'morphos/react';
const components = {
...defaultComponents,
AddItemButton: MyFancyAddItemButton
};
<MappingEditor components={components} />Every user-visible string lives in the MappingEditorLabels dictionary. Pass labels to
override any subset — the rest fall back to defaults.
const labels = {
field: 'Feld',
array: 'Liste',
object: 'Objekt',
conditional: 'Wenn / sonst',
concat: 'Verketten',
tuple: 'Feste Liste',
addItem: 'Element hinzufügen',
newField: 'Neues Feld',
selectPlaceholder: '— auswählen —',
advanced: 'Erweitert…',
useSuggestions: 'Vorschläge verwenden',
useSchemaFields: 'Schemafelder verwenden',
currentValue: 'Aktueller Wert',
forEach: 'fürJedes',
from: 'von',
when: 'wenn',
then: 'dann',
else: 'sonst',
concatItem: 'Element',
addElse: '+ sonst',
keyPlaceholder: 'Schlüssel',
expressionPlaceholder: 'JS-Ausdruck',
removeField: 'Feld entfernen',
moveUp: 'Nach oben',
moveDown: 'Nach unten'
};
<MappingEditor labels={labels} />| Key | Default | Where used |
|---|---|---|
field |
Value |
Type-selector option for expr kind. |
array |
List |
Type-selector option for array kind. |
object |
Object |
Type-selector option for object kind. |
conditional |
Conditional |
Type-selector option for conditional kind. |
concat |
Concat |
Type-selector option for concat kind. |
tuple |
Fixed List |
Type-selector option for tuple array kind. |
addItem |
Add Item |
Add button in Concat mappings. |
addSchemaField |
+ Add field… |
Legacy SchemaAddBar placeholder. |
newField |
New Field |
Blank custom-field input placeholder. |
selectPlaceholder |
— select — |
Placeholder or empty option in dropdowns. |
advanced |
Advanced… |
Switch-to-input option in dropdowns. |
useSuggestions |
Use suggestions |
Back-button aria/title on value inputs. |
useSchemaFields |
Use schema fields |
Back-button aria/title on key inputs. |
currentValue |
Current value |
Key dropdown option for wildcard '*' mapping. |
currentFields |
All nested fields |
Value dropdown option for copying current fields. |
forEach |
forEach |
Array section header label. |
from |
from |
Object section optional source selector label. |
when |
when |
Conditional section header label. |
then |
then |
Conditional truthy branch label. |
else |
else |
Conditional fallback branch label. |
concatItem |
item |
Concat branch row label. |
addElse |
+ else |
Add fallback branch button. |
keyPlaceholder |
key |
Free-form key input placeholder. |
expressionPlaceholder |
js expression |
Value input placeholder. |
removeField |
Remove field |
Remove-button aria-label. |
moveUp |
Move up |
Up-button aria-label. |
moveDown |
Move down |
Down-button aria-label. |
moveUpSymbol |
↑ |
Visible glyph in up-button (default + BS5). |
moveDownSymbol |
↓ |
Visible glyph in down-button (default + BS5). |
removeSymbol |
× |
Visible glyph in remove-button (default + BS5). |
useSuggestionsSymbol |
↩ |
Visible glyph in back-to-suggestions button. |
reorder |
Reorder |
Reorder group aria-label. |
mappingType |
Mapping type |
Type-selector aria-label. |
required |
required |
Required-marker aria-label. |
Bootstrap 3 keeps glyphicons (glyphicon-chevron-up, etc.) for icon-font reasons — the
*Symbol labels apply to the default and Bootstrap 5 themes.
Import defaultLabels if you need to spread-and-override or build a localized object:
import { defaultLabels, MappingEditor } from 'morphos/react';
const ru = { ...defaultLabels, field: 'Поле', array: 'Массив', /* … */ };
<MappingEditor labels={ru} />When writing your own components, read labels from the exported LabelsContext:
import { useContext } from 'react';
import { LabelsContext, type AddItemButtonProps } from 'morphos/react';
const MyAddItemButton = ({ onClick }: AddItemButtonProps) => {
const labels = useContext(LabelsContext);
return (
<button onClick={onClick}>{labels.addItem}</button>
);
};- Root-level tuple-form
PropertiesMap(ValueMap[]) inputs are currently ignored on load. - Schema features beyond
type,properties,items,required,title,descriptionare not interpreted (no$ref,oneOf,additionalProperties, etc.).