diff --git a/README.md b/README.md index 98a01783..fa69cafe 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,7 @@ The only *required* value is `data` (although you will need to provide a `setDat | `jsonStringify` | `(data: JsonData) => string` | `(data) => JSON.stringify(data, null, 2)` | Similarly, you can override the default presentation of the JSON string when starting editing JSON. You can supply different formatting parameters to the native `JSON.stringify()`, or provide a third-party option, like the aforementioned JSON5. | | `errorMessageTimeout` | `number` | `2500` | Time (in milliseconds) to display the error message in the UI. | | | `keyboardControls` | `KeyboardControls` | As explained [above](#usage) | Override some or all of the keyboard controls. See [Keyboard customisation](#keyboard-customisation) for details. | | +| `insertAtTop` | `boolean\| "object \| "array"` | `false` | If `true`, inserts new values at the *top* rather than bottom. Can set the behaviour just for arrays or objects by setting to `"object"` or `"array"` respectively. | | ## Managing state @@ -750,7 +751,9 @@ This component is heavily inspired by [react-json-view](https://github.com/mac-s ## Changelog -- **1.18.0**: Ability to [customise keyboard controls](#keyboard-customisation) +- **1.18.0**: + - Ability to [customise keyboard controls](#keyboard-customisation) + - Option to insert new values at the top - **1.17.0**: `defaultValue` function takes the new `key` as second parameter - **1.16.0**: Extend the "click" zone for collapsing nodes to the header bar and left margin (not just the collapse icon) - **1.15.12**: diff --git a/demo/src/App.tsx b/demo/src/App.tsx index fd14189c..e7b74d96 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -395,6 +395,7 @@ function App() { // collapseModifier: 'Control', // booleanConfirm: 'Enter', // }} + // insertAtBeginning="object" /> diff --git a/package.json b/package.json index f48f4324..d5a98a98 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,8 @@ "wrap-ansi": "7.0.0" }, "dependencies": { - "object-property-assigner": "^1.3.1", - "object-property-extractor": "^1.0.12" + "object-property-assigner": "^1.3.5", + "object-property-extractor": "^1.0.13" }, "devDependencies": { "@rollup/plugin-terser": "^0.4.4", diff --git a/src/CollectionNode.tsx b/src/CollectionNode.tsx index 6413f4a7..a83eab39 100644 --- a/src/CollectionNode.tsx +++ b/src/CollectionNode.tsx @@ -46,6 +46,7 @@ export const CollectionNode: React.FC = (props) => { jsonStringify, keyboardControls, handleKeyboard, + insertAtTop, } = props const [stringifiedValue, setStringifiedValue] = useState(jsonStringify(data)) @@ -171,13 +172,16 @@ export const CollectionNode: React.FC = (props) => { animateCollapse(false) const newValue = getDefaultNewValue(nodeData, key) if (collectionType === 'array') { - onAdd(newValue, [...path, (data as unknown[]).length]).then((error) => { + const index = insertAtTop.array ? 0 : (data as unknown[]).length + const options = insertAtTop.array ? { insert: true } : {} + onAdd(newValue, [...path, index], options).then((error) => { if (error) onError({ code: 'ADD_ERROR', message: error }, newValue as CollectionData) }) } else if (key in data) { onError({ code: 'KEY_EXISTS', message: translate('ERROR_KEY_EXISTS', nodeData) }, key) } else { - onAdd(newValue, [...path, key]).then((error) => { + const options = insertAtTop.object ? { insertBefore: 0 } : {} + onAdd(newValue, [...path, key], options).then((error) => { if (error) onError({ code: 'ADD_ERROR', message: error }, newValue as CollectionData) }) } diff --git a/src/JsonEditor.tsx b/src/JsonEditor.tsx index 2cb283bf..c0cadc21 100644 --- a/src/JsonEditor.tsx +++ b/src/JsonEditor.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react' -import assign, { type Input } from 'object-property-assigner' +import assign, { type Options as AssignOptions, type Input } from 'object-property-assigner' import extract from 'object-property-extractor' import { CollectionNode } from './CollectionNode' import { @@ -72,6 +72,7 @@ const Editor: React.FC = ({ jsonStringify = (data: JsonData) => JSON.stringify(data, null, 2), errorMessageTimeout = 2500, keyboardControls = {}, + insertAtTop = false, }) => { const { getStyles } = useTheme() const collapseFilter = useCallback(getFilterFunction(collapse), [collapse]) @@ -166,12 +167,13 @@ const Editor: React.FC = ({ }) } - const onAdd: InternalUpdateFunction = async (value, path) => { + const onAdd: InternalUpdateFunction = async (value, path, options) => { const { currentData, newData, currentValue, newValue } = updateDataObject( data, path, value, - 'add' + 'add', + options ) return await handleEdit(srcAdd, { @@ -237,7 +239,7 @@ const Editor: React.FC = ({ [...targetPath, targetKey], currentValue, 'add', - insertOptions as AssignOptions + insertOptions as UpdateOptions ) return await handleEdit(srcEdit, { @@ -304,6 +306,10 @@ const Editor: React.FC = ({ errorMessageTimeout, handleKeyboard: handleKeyboardCallback, keyboardControls: fullKeyboardControls, + insertAtTop: { + object: insertAtTop === true || insertAtTop === 'object', + array: insertAtTop === true || insertAtTop === 'array', + }, } const mainContainerStyles = { ...getStyles('container', nodeData), minWidth, maxWidth } @@ -335,11 +341,11 @@ export const JsonEditor: React.FC = (props) => { ) } -interface AssignOptions { +interface UpdateOptions { remove?: boolean - insert?: true - insertBefore?: string - insertAfter?: string + insert?: boolean + insertBefore?: string | number + insertAfter?: string | number } const updateDataObject = ( @@ -347,7 +353,7 @@ const updateDataObject = ( path: Array, newValue: unknown, action: 'update' | 'delete' | 'add', - insertOptions: { insert?: true; insertBefore?: string; insertAfter?: string } = {} + insertOptions: AssignOptions = {} ) => { if (path.length === 0) { return { @@ -358,7 +364,7 @@ const updateDataObject = ( } } - const assignOptions: AssignOptions = { + const assignOptions: UpdateOptions = { remove: action === 'delete', ...insertOptions, } diff --git a/src/types.ts b/src/types.ts index 2b39c1d3..49cc6084 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,4 @@ +import { type Options as AssignOptions } from 'object-property-assigner' import { type LocalisedStrings, type TranslateFunction } from './localisation' export type JsonData = CollectionData | ValueData @@ -46,6 +47,7 @@ export interface JsonEditorProps { jsonStringify?: (input: JsonData) => string errorMessageTimeout?: number // ms keyboardControls?: KeyboardControls + insertAtTop?: boolean | 'array' | 'object' } const ValueDataTypes = ['string', 'number', 'boolean', 'null'] as const @@ -140,7 +142,8 @@ export type CompareFunction = (a: string, b: string) => number // Internal update export type InternalUpdateFunction = ( value: unknown, - path: CollectionKey[] + path: CollectionKey[], + options?: AssignOptions ) => Promise // For drag-n-drop @@ -236,6 +239,7 @@ export interface CollectionNodeProps extends BaseNodeProps { defaultValue: unknown jsonParse: (input: string) => JsonData jsonStringify: (data: JsonData) => string + insertAtTop: { object: boolean; array: boolean } } export type ValueData = string | number | boolean diff --git a/yarn.lock b/yarn.lock index 63878271..72bad990 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2433,12 +2433,12 @@ object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-property-assigner@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/object-property-assigner/-/object-property-assigner-1.3.2.tgz#9630ab7cb39e8507c0a836771863b7eb3790b216" - integrity sha512-zhRYPERkZMJ2SATAFVIJLBTfWMd8r2dR4sgpAiObgEfL7YnsAhPPOQChH97pf+rfIwOWgtJsz1TDadOQQgjQzA== +object-property-assigner@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/object-property-assigner/-/object-property-assigner-1.3.5.tgz#f5037ff73b7b015a42e4059c3ff5c84017413ca2" + integrity sha512-DIzHzNSTnpoG8QPQCDNrHa6O3vLMhktK3Igirqpk523UYIVe8JNCKcn5C9WyLQxJc58EGsAIiiEu10gqPrud8w== -object-property-extractor@^1.0.12: +object-property-extractor@^1.0.13: version "1.0.13" resolved "https://registry.yarnpkg.com/object-property-extractor/-/object-property-extractor-1.0.13.tgz#fff188d6308ac5574eb2610221af906eb87b4f38" integrity sha512-9kgEjTWDhTPuPn7nyof+5mLmCKBPKdU0c7IVpTbOvYKYSdXQ5skH4Pa/8MPbZXeyXBGrqS82JyWecsh6tMxiLw==