diff --git a/packages/core/src/actions/actions.ts b/packages/core/src/actions/actions.ts index c167dba198..78240ae597 100644 --- a/packages/core/src/actions/actions.ts +++ b/packages/core/src/actions/actions.ts @@ -56,6 +56,57 @@ export const UPDATE_I18N = 'jsonforms/UPDATE_I18N' as const; export const ADD_DEFAULT_DATA = 'jsonforms/ADD_DEFAULT_DATA' as const; export const REMOVE_DEFAULT_DATA = 'jsonforms/REMOVE_DEFAULT_DATA' as const; +export type UpdateArrayContext = + | { type: 'ADD'; values: any[] } + | { type: 'REMOVE'; indices: number[] } + | { type: 'MOVE'; moves: { from: number; to: number }[] }; + +export const isUpdateArrayContext = ( + context: object +): context is UpdateArrayContext => { + if (!('type' in context)) { + return false; + } + if (typeof context.type !== 'string') { + return false; + } + switch (context.type) { + case 'ADD': { + return ( + 'values' in context && + Array.isArray(context.values) && + context.values.length > 0 + ); + } + case 'REMOVE': { + return ( + 'indices' in context && + Array.isArray(context.indices) && + context.indices.length > 0 && + context.indices.every((i) => typeof i === 'number') + ); + } + case 'MOVE': { + return ( + 'moves' in context && + Array.isArray(context.moves) && + context.moves.length > 0 && + context.moves.every( + (m) => + typeof m === 'object' && + m !== null && + 'from' in m && + 'to' in m && + typeof m.from === 'number' && + typeof m.to === 'number' + ) + ); + } + default: + return false; + } +}; + export type CoreActions = | InitAction | UpdateCoreAction @@ -70,6 +121,7 @@ export interface UpdateAction { type: 'jsonforms/UPDATE'; path: string; updater(existingData?: any): any; + context?: object; } export interface UpdateErrorsAction { @@ -165,11 +217,13 @@ export const setAjv = (ajv: AJV) => ({ export const update = ( path: string, - updater: (existingData: any) => any + updater: (existingData: any) => any, + context?: object ): UpdateAction => ({ type: UPDATE_DATA, path, updater, + context, }); export const updateErrors = (errors: ErrorObject[]): UpdateErrorsAction => ({ diff --git a/packages/core/src/util/renderer.ts b/packages/core/src/util/renderer.ts index 1346efdef4..004e75a8ac 100644 --- a/packages/core/src/util/renderer.ts +++ b/packages/core/src/util/renderer.ts @@ -59,7 +59,7 @@ import { moveDown, moveUp } from './array'; import type { AnyAction, Dispatch } from './type'; import { Resolve, convertDateToString, hasType } from './util'; import { composePaths, composeWithUi } from './path'; -import { CoreActions, update } from '../actions'; +import { CoreActions, update, UpdateArrayContext } from '../actions'; import type { ErrorObject } from 'ajv'; import type { JsonFormsState } from '../store'; import { @@ -823,41 +823,63 @@ export const mapDispatchToArrayControlProps = ( ): DispatchPropsOfArrayControl => ({ addItem: (path: string, value: any) => () => { dispatch( - update(path, (array) => { - if (array === undefined || array === null) { - return [value]; - } - - array.push(value); - return array; - }) + update( + path, + (array) => { + if (array === undefined || array === null) { + return [value]; + } + + array.push(value); + return array; + }, + { type: 'ADD', values: [value] } as UpdateArrayContext + ) ); }, removeItems: (path: string, toDelete: number[]) => () => { dispatch( - update(path, (array) => { - toDelete - .sort((a, b) => a - b) - .reverse() - .forEach((s) => array.splice(s, 1)); - return array; - }) + update( + path, + (array) => { + toDelete + .sort((a, b) => a - b) + .reverse() + .forEach((s) => array.splice(s, 1)); + return array; + }, + { type: 'REMOVE', indices: toDelete } as UpdateArrayContext + ) ); }, moveUp: (path, toMove: number) => () => { dispatch( - update(path, (array) => { - moveUp(array, toMove); - return array; - }) + update( + path, + (array) => { + moveUp(array, toMove); + return array; + }, + { + type: 'MOVE', + moves: [{ from: toMove, to: toMove - 1 }], + } as UpdateArrayContext + ) ); }, moveDown: (path, toMove: number) => () => { dispatch( - update(path, (array) => { - moveDown(array, toMove); - return array; - }) + update( + path, + (array) => { + moveDown(array, toMove); + return array; + }, + { + type: 'MOVE', + moves: [{ from: toMove, to: toMove + 1 }], + } as UpdateArrayContext + ) ); }, }); diff --git a/packages/core/test/actions/actions.test.ts b/packages/core/test/actions/actions.test.ts index 35ffe3debd..86f7eb5850 100644 --- a/packages/core/test/actions/actions.test.ts +++ b/packages/core/test/actions/actions.test.ts @@ -91,3 +91,73 @@ test('Init Action generates ui schema when not valid', (t) => { ], } as UISchemaElement); }); + +test('isUpdateArrayContext correctly identifies ', (t) => { + t.deepEqual(Actions.isUpdateArrayContext({}), false); + t.deepEqual(Actions.isUpdateArrayContext({ type: 'ADD' }), false); + t.deepEqual(Actions.isUpdateArrayContext({ type: 'ADD', values: [] }), false); + t.deepEqual( + Actions.isUpdateArrayContext({ type: 'ADD', values: [0, ''] }), + true + ); + + t.deepEqual(Actions.isUpdateArrayContext({ type: 'REMOVE' }), false); + t.deepEqual( + Actions.isUpdateArrayContext({ type: 'REMOVE', indices: [] }), + false + ); + t.deepEqual( + Actions.isUpdateArrayContext({ type: 'REMOVE', indices: [0, ''] }), + false + ); + t.deepEqual( + Actions.isUpdateArrayContext({ type: 'REMOVE', indices: [0, 2] }), + true + ); + + t.deepEqual(Actions.isUpdateArrayContext({ type: 'MOVE' }), false); + t.deepEqual(Actions.isUpdateArrayContext({ type: 'MOVE', moves: [] }), false); + t.deepEqual( + Actions.isUpdateArrayContext({ type: 'MOVE', moves: [0] }), + false + ); + t.deepEqual( + Actions.isUpdateArrayContext({ type: 'MOVE', moves: [null] }), + false + ); + t.deepEqual( + Actions.isUpdateArrayContext({ + type: 'MOVE', + moves: [{ from: 0, to: 1 }, { from: 2 }], + }), + false + ); + t.deepEqual( + Actions.isUpdateArrayContext({ + type: 'MOVE', + moves: [{ from: 0, to: 1 }, { to: 0 }], + }), + false + ); + t.deepEqual( + Actions.isUpdateArrayContext({ + type: 'MOVE', + moves: [{ from: 0, to: '' }], + }), + false + ); + t.deepEqual( + Actions.isUpdateArrayContext({ + type: 'MOVE', + moves: [ + { from: 0, to: 1 }, + { from: 0, to: '' }, + ], + }), + false + ); + t.deepEqual( + Actions.isUpdateArrayContext({ type: 'MOVE', moves: [{ from: 0, to: 1 }] }), + true + ); +});