import { composePaths, computeLabel, getFirstPrimitiveProp, isDescriptionHidden, JsonFormsSubStates, Resolve, } from '@jsonforms/core'; import cloneDeep from 'lodash/cloneDeep'; import debounce from 'lodash/debounce'; import merge from 'lodash/merge'; import get from 'lodash/get'; import isPlainObject from 'lodash/isPlainObject'; import { useStyles } from '../styles'; import { computed, ComputedRef, inject, ref, provide } from 'vue'; import Ajv, {ErrorObject} from 'ajv'; export const useControlAppliedOptions = ( input: I ) => { return computed(() => merge( {}, cloneDeep(input.control.value.config), cloneDeep(input.control.value.uischema.options) ) ); }; export const useComputedLabel = ( input: I, appliedOptions: ComputedRef ) => { return computed((): string => { return computeLabel( input.control.value.label, input.control.value.required, !!appliedOptions.value?.hideRequiredAsterisk ); }); }; /** * Adds styles, appliedOptions and vuetifyProps */ export const useVuetifyLabel = (input: I) => { const styles = useStyles(input.label.value.uischema); const appliedOptions = computed(() => merge( {}, cloneDeep(input.label.value.config), cloneDeep(input.label.value.uischema.options) ) ); const vuetifyProps = (path: string) => { const props = get(appliedOptions.value?.vuetify, path); return props && isPlainObject(props) ? props : {}; }; return { ...input, appliedOptions, vuetifyProps, styles, }; }; /** * Adds styles, isFocused, appliedOptions and onChange */ export const useVuetifyControl = < I extends { control: any; handleChange: any } >( input: I, adaptValue: (target: any) => any = (v) => v, debounceWait?: number ) => { const touched = ref(false); const changeEmitter = typeof debounceWait === 'number' ? debounce(input.handleChange, debounceWait) : input.handleChange; const onChange = (value: any) => { changeEmitter(input.control.value.path, adaptValue(value)); }; const appliedOptions = useControlAppliedOptions(input); const isFocused = ref(false); const handleBlur = () => { touched.value = true; isFocused.value = false; }; const filteredErrors = computed(() => { return (touched.value || !input.control.value.config.enableFilterErrorsBeforeTouch)? input.control.value.errors : ''; }); const persistentHint = (): boolean => { return !isDescriptionHidden( input.control.value.visible, input.control.value.description, isFocused.value, !!appliedOptions.value?.showUnfocusedDescription ); }; const computedLabel = useComputedLabel(input, appliedOptions); const controlWrapper = computed(() => { const { id, description, errors, label, visible, required } = input.control.value; return { id, description, errors, label, visible, required }; }); const styles = useStyles(input.control.value.uischema); const vuetifyProps = (path: string) => { const props = get(appliedOptions.value?.vuetify, path); return props && isPlainObject(props) ? props : {}; }; const overwrittenControl = computed(() => { return {...input.control.value, filteredErrors: filteredErrors.value, errors: filteredErrors.value, rawErrors: input.control.value.errors, rawChildErrors: input.control.value.childErrors, } }); const rawErrors = computed(() => input.control.value.errors); return { ...input, control: overwrittenControl, filteredErrors, rawErrors, styles, isFocused, appliedOptions, controlWrapper, onChange, vuetifyProps, persistentHint, computedLabel, touched, handleBlur, }; }; export const useTranslator = () => { const jsonforms = inject('jsonforms'); if (!jsonforms) { throw new Error( "'jsonforms couldn't be injected. Are you within JSON Forms?" ); } if (!jsonforms.i18n || !jsonforms.i18n.translate) { throw new Error( "'jsonforms i18n couldn't be injected. Are you within JSON Forms?" ); } const translate = computed(() => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return jsonforms.i18n!.translate!; }); return translate; }; /** * Adds styles and appliedOptions */ export const useVuetifyLayout = (input: I) => { const appliedOptions = computed(() => { return merge( {}, cloneDeep(input.layout.value.config), cloneDeep(input.layout.value.uischema.options) ); }); const vuetifyProps = (path: string) => { const props = get(appliedOptions.value?.vuetify, path); return props && isPlainObject(props) ? props : {}; }; return { ...input, styles: useStyles(input.layout.value.uischema), appliedOptions, vuetifyProps, }; }; /** * Adds styles, appliedOptions and childUiSchema */ export const useVuetifyArrayControl = ( input: I ) => { const appliedOptions = useControlAppliedOptions(input); const computedLabel = useComputedLabel(input, appliedOptions); const vuetifyProps = (path: string) => { const props = get(appliedOptions.value?.vuetify, path); return props && isPlainObject(props) ? props : {}; }; const childLabelForIndex = (index: number | null) => { if (index === null) { return ''; } const childLabelProp = input.control.value.uischema.options?.childLabelProp ?? getFirstPrimitiveProp(input.control.value.schema); if (!childLabelProp) { return `${index}`; } const labelValue = Resolve.data( input.control.value.data, composePaths(`${index}`, childLabelProp) ); if ( labelValue === undefined || labelValue === null || Number.isNaN(labelValue) ) { return ''; } return `${labelValue}`; }; const filteredChildErrors = computed(() => { // supress childErrors unless touch filtering is disabled // otherwise all child errors will show, irrespective of their control touch state const filtered: ErrorObject[] = appliedOptions.value?.enableFilterErrorsBeforeTouch ? [] : input.control.value.childErrors return filtered; }); const overwrittenControl = computed(() => { return {...input.control.value, childErrors: filteredChildErrors.value, rawChildErrors: input.control.value.childErrors, } }); return { ...input, control: overwrittenControl, styles: useStyles(input.control.value.uischema), appliedOptions, childLabelForIndex, computedLabel, vuetifyProps, }; }; /** * Adds styles and appliedOptions */ export const useVuetifyBasicControl = ( input: I ) => { const appliedOptions = useControlAppliedOptions(input); const vuetifyProps = (path: string) => { const props = get(appliedOptions.value?.vuetify, path); return props && isPlainObject(props) ? props : {}; }; return { ...input, styles: useStyles(input.control.value.uischema), appliedOptions, vuetifyProps, }; }; /** * Extracts Ajv from JSON Forms */ export const useAjv = () => { const jsonforms = inject('jsonforms'); if (!jsonforms) { throw new Error( "'jsonforms' couldn't be injected. Are you within JSON Forms?" ); } // should always exist return jsonforms.core?.ajv as Ajv; }; export interface NestedInfo { level: number; parentElement?: 'array' | 'object'; } export const useNested = (element: false | 'array' | 'object'): NestedInfo => { const nestedInfo = inject('jsonforms.nestedInfo', { level: 0 }); if (element) { provide('jsonforms.nestedInfo', { level: nestedInfo.level + 1, parentElement: element, }); } return nestedInfo; };