UNPKG

formvuelate

Version:
563 lines (468 loc) 16.2 kB
/** * formvuelate v3.3.1 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) : typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Formvuelate = {}, global.Vue)); }(this, (function (exports, vue) { 'use strict'; function useUniqueID () { let UUID = Math.floor(Math.random() * 1000000000); const UUIDBindings = new Map(); function getID (model) { if (UUIDBindings.has(model)) { return UUIDBindings.get(model) } else { UUID++; UUIDBindings.set(model, UUID); return UUID } } return { UUID, UUIDBindings, getID } } /** * Find the elements in the row that have a schema property * @param {Array} row * @returns */ const findSchemaElementsInRow = (row) => { return row.filter(el => el.schema) }; /** * Find the elements in the top level row of a schema * that are considered "schema" elements, aka. they have a schema prop * @param {Array} normalizedSchema * @returns */ const findSchemaElements = (normalizedSchema) => { for (const row of normalizedSchema) { const elements = findSchemaElementsInRow(row); if (elements.length) return elements } return [] }; /** * Traverse a schema recursively and find the schema element * that matches the given model * @param {String} model * @param {Array} normalizedSchema * @returns */ const findElementInSchema = (model, normalizedSchema) => { const schemaElements = findSchemaElements(normalizedSchema); const isCorrectElement = el => el?.model === model; if (!schemaElements.length) { return null } for (const el of schemaElements) { if (isCorrectElement(el)) return el // Check the subschemas recursively const subElement = findElementInSchema(model, normalizeSchema(el.schema)); if (isCorrectElement(subElement)) return subElement } return null }; /** * Parse a user given schema into FVL internal format * @param {Array|Object} schema * @returns */ const normalizeSchema = (schema) => { const arraySchema = Array.isArray(schema) ? schema : Object.keys(schema).map(model => ({ ...schema[model], model })); return arraySchema.map( field => Array.isArray(field) ? field : [field] ) }; function useParsedSchema (refSchema, model) { const { getID } = useUniqueID(); const parsedSchema = vue.computed(() => { const schema = vue.unref(refSchema); let normalizedSchema = normalizeSchema(schema); if (model) { /** * If the model is provided, it means a SchemaForm is trying to find * a subschema in the main schema that corresponds to its "model" in the * use provided schema. We dig into the sub schemas to find it and normalize it * before setting it as the returned parsed schema */ const element = findElementInSchema(model, normalizedSchema); if (element) { normalizedSchema = normalizeSchema(element.schema); } } return normalizedSchema.map(fieldGroup => { return fieldGroup.map(field => ({ ...field, uuid: getID(field.model) })) }) }); return { parsedSchema } } const KEY = 'fvl_'; const IS_SCHEMA_WIZARD = `${KEY}isSchemaWizard`; const PARENT_SCHEMA_EXISTS = `${KEY}parentSchemaExists`; const INJECTED_SCHEMA = `${KEY}injectedSchema`; const SCHEMA_MODEL_PATH = `${KEY}schemaModelPath`; const FORM_MODEL = `${KEY}formModel`; const FIND_NESTED_FORM_MODEL_PROP = `${KEY}findNestedFormModelProp`; const UPDATE_FORM_MODEL = `${KEY}updateFormModel`; const DELETE_FORM_MODEL_PROP = `${KEY}deleteFormModelProp`; var constants = /*#__PURE__*/Object.freeze({ __proto__: null, IS_SCHEMA_WIZARD: IS_SCHEMA_WIZARD, PARENT_SCHEMA_EXISTS: PARENT_SCHEMA_EXISTS, INJECTED_SCHEMA: INJECTED_SCHEMA, SCHEMA_MODEL_PATH: SCHEMA_MODEL_PATH, FORM_MODEL: FORM_MODEL, FIND_NESTED_FORM_MODEL_PROP: FIND_NESTED_FORM_MODEL_PROP, UPDATE_FORM_MODEL: UPDATE_FORM_MODEL, DELETE_FORM_MODEL_PROP: DELETE_FORM_MODEL_PROP }); var script$2 = { name: 'SchemaField', props: { field: { type: Object, required: true }, sharedConfig: { type: Object, default: () => ({}) }, preventModelCleanupOnSchemaChange: { type: Boolean, default: false } }, setup (props) { const binds = vue.computed(() => { return props.field.schema ? { // For sub SchemaForm elements ...props.field, nestedSchemaModel: props.field.model } : { ...props.sharedConfig, ...props.field } }); const formModel = vue.inject(FORM_MODEL, {}); const path = vue.inject(SCHEMA_MODEL_PATH, null); const findNestedFormModelProp = vue.inject(FIND_NESTED_FORM_MODEL_PROP); const fieldValue = vue.computed(() => { if (path) { return findNestedFormModelProp(path)[props.field.model] } return formModel.value[props.field.model] }); const updateFormModel = vue.inject(UPDATE_FORM_MODEL); const deleteFormModelProperty = vue.inject(DELETE_FORM_MODEL_PROP); const update = (value) => { updateFormModel(props.field.model, value, path); }; const schemaCondition = vue.computed(() => { const condition = props.field.condition; if (!condition || typeof condition !== 'function') return true return condition(formModel.value) }); vue.watch(schemaCondition, shouldDisplay => { if (shouldDisplay) return if (props.preventModelCleanupOnSchemaChange) return deleteFormModelProperty(props.field.model, path); }); return { binds, fieldValue, update, schemaCondition } } }; function render$2(_ctx, _cache, $props, $setup, $data, $options) { return ($setup.schemaCondition) ? (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent($props.field.component), vue.mergeProps({ key: 0 }, $setup.binds, { modelValue: $setup.fieldValue, "onUpdate:modelValue": $setup.update, class: "schema-col" }), null, 16 /* FULL_PROPS */, ["modelValue", "onUpdate:modelValue"])) : vue.createCommentVNode("v-if", true) } script$2.render = render$2; script$2.__file = "packages/formvuelate/src/SchemaField.vue"; function useParentSchema () { const isChildOfWizard = vue.inject(IS_SCHEMA_WIZARD, false); const hasParentSchema = vue.inject(PARENT_SCHEMA_EXISTS, false); if (!hasParentSchema) { vue.provide(PARENT_SCHEMA_EXISTS, true); } const behaveLikeParentSchema = vue.computed(() => (!isChildOfWizard && !hasParentSchema)); return { behaveLikeParentSchema, hasParentSchema, isChildOfWizard } } function useInjectedSchema (props, behaveLikeParentSchema) { const { schema } = vue.toRefs(props); let injectedSchema = vue.inject(INJECTED_SCHEMA, false); if (behaveLikeParentSchema) { // Only the top level schema form should inject the schema // That way we dont have to worry about injecting the prop down into // sub schemas vue.provide(INJECTED_SCHEMA, schema); injectedSchema = schema; } if (props.nestedSchemaModel) { // If the nestedSchemaModel prop is set it means this // component is a subschema, and we need to inform descendants // of the "path" for the model. ex. "info.family.parents" const path = vue.inject(SCHEMA_MODEL_PATH, ''); const currentPath = path ? `${path}.${props.nestedSchemaModel}` : props.nestedSchemaModel; vue.provide(SCHEMA_MODEL_PATH, currentPath); } return { schema: injectedSchema } } function useFormModel (props, parsedSchema) { const formModel = vue.inject(FORM_MODEL, {}); const cleanupModelChanges = (schema, oldSchema) => { if (props.preventModelCleanupOnSchemaChange) return const reducer = (acc, val) => { return acc.concat(val.map(i => i.model)) }; const newKeys = schema.reduce(reducer, []); const oldKeys = oldSchema.reduce(reducer, []); const diff = oldKeys.filter(i => !newKeys.includes(i)); if (!diff.length) return for (const key of diff) { delete formModel.value[key]; } }; vue.watch(parsedSchema, cleanupModelChanges); } var script$1 = { name: 'SchemaForm', components: { SchemaField: script$2 }, props: { schema: { type: [Object, Array], required: true, validator (schema) { if (!Array.isArray(schema)) return true return schema.filter(field => !Array.isArray(field) && (!field.model && !field.schema)).length === 0 } }, sharedConfig: { type: Object, default: () => ({}) }, preventModelCleanupOnSchemaChange: { type: Boolean, default: false }, nestedSchemaModel: { type: String, default: '' }, schemaRowClasses: { type: [String, Object, Array], default: null } }, emits: ['submit', 'update:modelValue'], setup (props, { emit, attrs }) { const { behaveLikeParentSchema, hasParentSchema } = useParentSchema(); const { schema } = useInjectedSchema(props, behaveLikeParentSchema); const { parsedSchema } = useParsedSchema(schema, attrs.model); useFormModel(props, parsedSchema); const formBinds = vue.computed(() => { if (hasParentSchema) return {} return { onSubmit: event => { event.preventDefault(); emit('submit', event); } } }); const slotBinds = vue.computed(() => { return {} }); return { behaveLikeParentSchema, parsedSchema, hasParentSchema, formBinds, slotBinds } } }; function render$1(_ctx, _cache, $props, $setup, $data, $options) { const _component_SchemaField = vue.resolveComponent("SchemaField"); return (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent($setup.behaveLikeParentSchema ? 'form' : 'div'), $setup.formBinds, { default: vue.withCtx(() => [ ($setup.behaveLikeParentSchema) ? vue.renderSlot(_ctx.$slots, "beforeForm", vue.mergeProps({ key: 0 }, $setup.slotBinds)) : vue.createCommentVNode("v-if", true), (vue.openBlock(true), vue.createBlock(vue.Fragment, null, vue.renderList($setup.parsedSchema, (fields, index) => { return (vue.openBlock(), vue.createBlock("div", { class: ['schema-row', $props.schemaRowClasses], key: index }, [ (vue.openBlock(true), vue.createBlock(vue.Fragment, null, vue.renderList(fields, (field) => { return (vue.openBlock(), vue.createBlock(_component_SchemaField, { key: field.model, field: field, sharedConfig: $props.sharedConfig, preventModelCleanupOnSchemaChange: $props.preventModelCleanupOnSchemaChange, class: "schema-col" }, null, 8 /* PROPS */, ["field", "sharedConfig", "preventModelCleanupOnSchemaChange"])) }), 128 /* KEYED_FRAGMENT */)) ], 2 /* CLASS */)) }), 128 /* KEYED_FRAGMENT */)), ($setup.behaveLikeParentSchema) ? vue.renderSlot(_ctx.$slots, "afterForm", vue.mergeProps({ key: 1 }, $setup.slotBinds)) : vue.createCommentVNode("v-if", true) ]), _: 3 /* FORWARDED */ }, 16 /* FULL_PROPS */)) } script$1.render = render$1; script$1.__file = "packages/formvuelate/src/SchemaForm.vue"; var script = { name: 'SchemaWizard', components: { SchemaForm: script$1 }, props: { schema: { type: Array, required: true }, step: { type: Number, required: true, default: 0 } }, emits: ['submit'], setup (props) { vue.provide(IS_SCHEMA_WIZARD, true); const currentSchema = vue.computed(() => { return props.schema[props.step] }); return { currentSchema } } }; function render(_ctx, _cache, $props, $setup, $data, $options) { const _component_SchemaForm = vue.resolveComponent("SchemaForm"); return (vue.openBlock(), vue.createBlock("form", { onSubmit: _cache[1] || (_cache[1] = vue.withModifiers($event => (_ctx.$emit('submit', $event)), ["prevent"])) }, [ vue.renderSlot(_ctx.$slots, "beforeForm"), vue.createVNode(_component_SchemaForm, { schema: $setup.currentSchema, preventModelCleanupOnSchemaChange: "" }, null, 8 /* PROPS */, ["schema"]), vue.renderSlot(_ctx.$slots, "afterForm") ], 32 /* HYDRATE_EVENTS */)) } script.render = render; script.__file = "packages/formvuelate/src/SchemaWizard.vue"; function SchemaFormFactory (plugins = [], components = null) { // Copy the original SchemaForm setup const originalSetup = script$1.setup; function setup (props, context) { // Call the original setup and preserve its results const baseSchemaFormReturns = originalSetup(props, context); if (!plugins.length) return baseSchemaFormReturns else { // Apply plugins on the data returned // by the original Schemaform return plugins.reduce( (schemaFormReturns, plugin) => { return plugin(schemaFormReturns, props, context) }, baseSchemaFormReturns ) } } const SchemaFieldWithComponents = { ...script$2, components: { ...components, ...script$2.components } }; return { ...script$1, components: { ...components, ...script$1.components, SchemaField: SchemaFieldWithComponents }, // Return a customized setup function with plugins // as the new SchemaForm setup setup } } /** * Find a key inside an object, or create it if it doesn't exist * @param {Object} model * @param {String} key * @returns */ const findOrCreateProp = (model, key) => { if (!model[key]) { model[key] = {}; } return model[key] }; function useSchemaForm (initialFormValue = {}) { const formModel = vue.isRef(initialFormValue) ? initialFormValue : vue.ref(initialFormValue); const findNestedFormModelProp = (path) => { const keys = path.split('.'); let nestedProp = findOrCreateProp(formModel.value, keys[0]); for (let i = 1; i < keys.length; i++) { nestedProp = findOrCreateProp(nestedProp, keys[i]); } return nestedProp }; const updateFormModel = (prop, value, path) => { if (!path) { formModel.value[prop] = value; return } findNestedFormModelProp(path)[prop] = value; }; const deleteFormModelProperty = (prop, path) => { if (!path) { delete formModel.value[prop]; return } delete findNestedFormModelProp(path)[prop]; }; vue.provide(UPDATE_FORM_MODEL, updateFormModel); vue.provide(DELETE_FORM_MODEL_PROP, deleteFormModelProperty); vue.provide(FIND_NESTED_FORM_MODEL_PROP, findNestedFormModelProp); vue.provide(FORM_MODEL, formModel); return { formModel } } exports.SchemaForm = script$1; exports.SchemaFormFactory = SchemaFormFactory; exports.SchemaWizard = script; exports.constants = constants; exports['default'] = script$1; exports.useSchemaForm = useSchemaForm; Object.defineProperty(exports, '__esModule', { value: true }); })));