This library provides Vue 3 components for editting values and undoing said edits.
You can install this library through npm like so:
$ npm install --save undoable-value-editor-vue
The main component for this library is the UndoableValueEditor
. You need only supply a model to use this, like so:
<UndoableValueEditor v-model="valueRef"/>
That will provide you a pair of disabled buttons and a type dropdown with all JSON schema type values, starting at "null". Picking one of those options will add the attach the appropriate sub-editor and enable the undo button. Clicking said button will roll you back to null and enable the redo button.
Note that this editor does support the disable
attribute, applying that to it's undo/redo buttons and subeditors automatically.
You can change the dropdown options by providing a schema
property to that component. By default this should be a JSON schema object, though it accepts any boolean or object value. See the corresponding section below for using other schema types.
By default the editor uses the JSONSchemaOptionsParser
from the schema-select library to convert subschemas into dropdown items. This means the following rules are in effect:
- If set to
true
, the default list of all value types is used. - If
enum
is present, each of those values will be wrapped in aconst
schema for the subschemas. - If
oneOf
oranyOf
, the subschema list is drawn from that property. - For all other schemas, the schema itself will wrapped in an array.
Should you want to use a different library instead, you can do so through the component's schemaParser
property. The provided schema parser should be a SchemaOptionsParser
as per the schema-select interface.
Should you want translation applied to the editor's text, you do so by providing a getTranslatedText
function, like so:
import { provide } from 'vue'
import { KeyedTextTranslator, PLACEHOLDER_TEXT } from 'undoable-value-editor-vue'
const translator = new KeyedTextTranslator(PLACEHOLDER_TEXT)
provide('getTranslatedText', (key: string | string[]) => translator.translate(key))
Note that this function is meant to be compatible with i18n
translate functions, making it easy to plug such libraries in here.
The above KeyedTextTranslator
is just a basic translation mapper we've provided with this library. PLACEHOLDER_TEXT
covers the few instances of text messages used by the editor. At present that's just error messages for entering an invalid or in use property name.
Note that that the above injection only applies to the editor's own text, not any labels generated by the schema parser. To localize those, you'll need to specify a schema parser with localization support. Take care when doing so as you'll need localization for all possible labels on any schemas the editor may recieve. At minimum, you'll likely want translations for all data type names. If you're expecting specialized schemas with their own names you'll want translations ready for those as well.
You can replace any of the icons used in the editor by providing a mapping of named icons, like so:
provide('namedIcons', {
add: `<u>+</u>`,
delete: `<u>-</u>`,
rename: "Rename",
revert: "Revert",
copy: "Copy",
cut: "Cut",
paste: "Paste",
alert: `<u>!</u>`,
undo: "Undo",
redo: "Redo"
})
Note that those mapping can be to strings or component references. If strings are provided, the icon will be treated as html text via "v-html".
Internally, this is handled by IconPlaceholder
components that reference that mapping using their type
property, using their slotted content if no component is found.
IconButton
components act as button wrapper for said placeholders, with the added effect of attaching their type property as a class name. For example, <IconButton type="add"/>
would attach the "add-button" class to the resulting button. These are added to make css styling easier.
Should you want to use one of you own components as a subeditor, you can do so by providing an editorFactory
property. Said factory is simply an object with the following callback:
process: (source: any, context?: boolean | Record<string, any>) => any
That callback will be passed the current value as the source and the selected subschema as the context. The results of said callback will then be used as the target subeditor component.
Note that the subeditor will be passed the model, schema parser, editor factory, transfer handler, and disabled status of the value editor, as well as the selected subschema.
We've provided the following factories with this library.
The TypeMappedValueFactory
accepts a mapping of type names to components. You can provide those mappings on creation, like so:
const defaultEditorResolver = new TypeMappedValueFactory({
array: ArrayEditor,
bigint: NumberEditor,
boolean: BooleanEditor,
integer: NumberEditor,
number: NumberEditor,
object: ObjectEditor,
string: StringEditor
})
The factory will them choose the subeditor based on the type of the target value. If it doesn't have an entry for that type, if will try to match against the type property of the provided schema instead.
LookupViaSchemaProperty
checks the target schema property, then uses said property as a key to a component map. For example, let's say you created one like this:
const editorFactory = new LookupViaSchemaProperty('format', { flag: BooleanEditor })
That would check the schema's "format" property, returning a BooleanEditor when the format is "flag".
The transfer handler is simply an object that supports sending out an undoable action for dragging and dropping values within the editor. A new instance will be created by default if none is provided, so you should always have access to one within subeditors via the transferHandler
property.
To use that simply call startTransfer
when you start a drag, like so:
if (props.transferHandler != null) {
props.transferHandler.startTransfer({
source: someObject,
key: propertyName
})
}
Then just have the drop target call completeTransfer
, like this:
if (props.transferHandler) {
const action = props.transferHandler.completeTransfer({
source: someObject,
key: propertyName
})
if (action != null) {
emit('undoableChange', action)
}
}
If transferring from or into an array, use index
instead of key
for the above calls.
Note that you can use standard drag and drop API calls. The transfer handler is simply there to wrap these transfers in an undoable action so they can be reverted easily.
You may have noticed the above example emits an undoableChange
event. The editor uses these to notify the undo and redo buttons that there's a change they can apply or revert. Should your custom editor involve modifying an object or array you'll probably want to send out those events so the main value editor can catch them and add them to the undo/redo track. Changes to primitives should already be caught as part of model update handling, but objects get special handling as they may have properties initialized without triggering an undoable action.
The value editor does allow other types of schema besides JSON schema. However, you will need to provide a few things to support that should you want to use a different type.
First, you'll need to pass in a custom schema parser. Said parser should be a SchemaOptionsParser
as per the schema-select library.
Second, the default sub-editors are based on expected JSON schema properties for the target type. As such, you'll likely need to provide an editor factory with subeditors that play nicely with your new schema type.