diff --git a/.eslintrc.js b/.eslintrc.js
index 28d00289..00655afe 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -43,5 +43,6 @@ module.exports = {
'react-hooks/exhaustive-deps': 0,
'@typescript-eslint/prefer-nullish-coalescing': 0,
'@typescript-eslint/no-unsafe-argument': 'warn',
+ '@typescript-eslint/indent': 0,
},
}
diff --git a/README.md b/README.md
index f84e04c6..4f353d4f 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,7 @@ Features include:
- [Icons](#icons)
- [Localisation](#localisation)
- [Custom Nodes](#custom-nodes)
+ - [Custom Collection nodes](#custom-collection-nodes)
- [Active hyperlinks](#active-hyperlinks)
- [Custom Text](#custom-text)
- [Undo functionality](#undo-functionality)
@@ -423,10 +424,12 @@ Custom nodes are provided in the `customNodeDefinitions` prop, as an array of ob
showEditTools // boolean, default true
name // string (appears in Types selector)
showInTypesSelector, // boolean (optional), default false
+ // Only affects Collection nodes:
+ showCollectionWrapper // boolean (optional), default true
}
```
-The `condition` is just a [Filter function](#filter-functions), with the same input parameters (`key`, `path`, `level`, `value`, `size`), and `element` is a React component. Every node in the data structure will be run through each condition function, and any that match will be replaced by your custom component. Note that if a node matches more than one custom definition conditions (from multiple components), the *first* one will be used, so place them in the array in priority order.
+The `condition` is just a [Filter function](#filter-functions), with the same input parameters (`key`, `path`, `value`, etc.), and `element` is a React component. Every node in the data structure will be run through each condition function, and any that match will be replaced by your custom component. Note that if a node matches more than one custom definition conditions (from multiple components), the *first* one will be used, so place them in the array in priority order.
The component will receive *all* the same props as a standard node component (see codebase), but you can pass additional props to your component if required through the `customNodeProps` object. A thorough example of a custom Date picker is used in the demo (along with a couple of other more basic presentational ones), which you can inspect to see how to utilise the standard props and a couple of custom props. View the source code [here](https://github.com/CarlosNZ/json-edit-react/blob/main/demo/src/customComponents/DateTimePicker.tsx)
@@ -436,6 +439,12 @@ Also, by default, your component will be treated as a "display" element, i.e. it
You can allow users to create new instances of your special nodes by selecting them as a "Type" in the types selector when editing/adding values. Set `showInTypesSelector: true` to enable this. However, if this is enabled you need to also provide a `name` (which is what the user will see in the selector) and a `defaultValue` which is the data that is inserted when the user selects this "type". (The `defaultValue` must return `true` if passed through the `condition` function in order for it to be immediately displayed using your custom component.)
+### Custom Collection nodes
+
+In most cases it will be preferable to create custom nodes to match *value* nodes (i.e. not `array` or `object` *collection* nodes). However, if you do wish to replace a whole collection, there are a couple of other things to know:
+- The descendants of this node can still be displayed using the [React `children`](https://react.dev/learn/passing-props-to-a-component#passing-jsx-as-children) property, it just becomes your component's responsibility to handle it.
+- There is one additional prop, `showCollectionWrapper` (default `true`), which, when set to `false`, hides the surrounding "wrapper", namely the hide/show chevron and the brackets. In this case, you would have to provide your own hide/show mechanism in your component.
+
### Active hyperlinks
A simple custom component and definition to turn url strings into clickable links is provided with the main package for you to use out of the box. Just import and use like so:
diff --git a/demo/src/App.tsx b/demo/src/App.tsx
index cce935b7..e7656044 100644
--- a/demo/src/App.tsx
+++ b/demo/src/App.tsx
@@ -87,19 +87,25 @@ function App() {
const restrictEdit: FilterFunction | boolean = (() => {
const customRestrictor = demoData[selectedData]?.restrictEdit
- if (customRestrictor) return (input) => !allowEdit || customRestrictor(input)
+ if (typeof customRestrictor === 'function')
+ return (input) => !allowEdit || customRestrictor(input)
+ if (customRestrictor !== undefined) return customRestrictor
return !allowEdit
})()
const restrictDelete: FilterFunction | boolean = (() => {
const customRestrictor = demoData[selectedData]?.restrictDelete
- if (customRestrictor) return (input) => !allowDelete || customRestrictor(input)
+ if (typeof customRestrictor === 'function')
+ return (input) => !allowDelete || customRestrictor(input)
+ if (customRestrictor !== undefined) return customRestrictor
return !allowDelete
})()
const restrictAdd: FilterFunction | boolean = (() => {
const customRestrictor = demoData[selectedData]?.restrictAdd
- if (customRestrictor) return (input) => !allowAdd || customRestrictor(input)
+ if (typeof customRestrictor === 'function')
+ return (input) => !allowAdd || customRestrictor(input)
+ if (customRestrictor !== undefined) return customRestrictor
return !allowAdd
})()
@@ -277,7 +283,7 @@ function App() {
keySort={sortKeys}
defaultValue={demoData[selectedData]?.defaultValue ?? defaultNewValue}
showArrayIndices={showIndices}
- minWidth={450}
+ minWidth={'min(500px, 95vw)'}
maxWidth="min(650px, 90vw)"
className="block-shadow"
stringTruncate={90}
@@ -434,6 +440,7 @@ function App() {
setAllowEdit(!allowEdit)}
w="50%"
>
@@ -441,6 +448,7 @@ function App() {
setAllowDelete(!allowDelete)}
w="50%"
>
@@ -448,7 +456,12 @@ function App() {
- setAllowAdd(!allowAdd)} w="50%">
+ setAllowAdd(!allowAdd)}
+ w="50%"
+ >
Allow Add
setDefaultNewValue(e.target.value)}
diff --git a/demo/src/JsonEditImport.ts b/demo/src/JsonEditImport.ts
index aba25b71..233596c8 100644
--- a/demo/src/JsonEditImport.ts
+++ b/demo/src/JsonEditImport.ts
@@ -1,33 +1,12 @@
-import {
- JsonEditor,
- themes,
- Theme,
- ThemeName,
- ThemeInput,
- CustomNodeProps,
- CustomNodeDefinition,
- CustomTextDefinitions,
- FilterFunction,
- LinkCustomComponent,
- LinkCustomNodeDefinition,
- matchNode,
- assign,
- // } from './json-edit-react/src'
-} from 'json-edit-react'
-// } from './package'
+/**
+ * Quickly switch between importing from local src or installed package
+ */
-export {
- JsonEditor,
- themes,
- type Theme,
- type ThemeName,
- type ThemeInput,
- type CustomNodeProps,
- type CustomNodeDefinition,
- type CustomTextDefinitions,
- type FilterFunction,
- LinkCustomComponent,
- LinkCustomNodeDefinition,
- matchNode,
- assign,
-}
+/* Installed package */
+// export * from 'json-edit-react'
+
+/* Local src */
+export * from './json-edit-react/src'
+
+/* Compiled local package */
+// export * from './package'
diff --git a/demo/src/demoData/dataDefinitions.tsx b/demo/src/demoData/dataDefinitions.tsx
index 138879e9..d00644cf 100644
--- a/demo/src/demoData/dataDefinitions.tsx
+++ b/demo/src/demoData/dataDefinitions.tsx
@@ -26,9 +26,9 @@ interface DemoData {
data: object
rootName?: string
collapse?: number
- restrictEdit?: FilterFunction
- restrictDelete?: FilterFunction
- restrictAdd?: FilterFunction
+ restrictEdit?: boolean | FilterFunction
+ restrictDelete?: boolean | FilterFunction
+ restrictAdd?: boolean | FilterFunction
restrictTypeSelection?: boolean | DataType[]
searchFilter?: 'key' | 'value' | 'all' | SearchFilterFunction
searchPlaceholder?: string
@@ -438,6 +438,9 @@ export const demoData: Record = {
},
searchPlaceholder: 'Search by character name',
data: data.customNodes,
+ restrictEdit: ({ level }) => level > 0,
+ restrictAdd: true,
+ restrictDelete: true,
customNodeDefinitions: [
{
condition: ({ key, value }) =>
@@ -480,6 +483,23 @@ export const demoData: Record = {
},
hideKey: true,
},
+ // Uncomment to test a custom Collection node
+ // {
+ // condition: ({ key }) => key === 'portrayedBy',
+ // element: ({ nodeData, data, getStyles }) => {
+ // const styles = getStyles('string', nodeData)
+ // return (
+ //
+ // {data.map((val) => (
+ //
{val}
+ // ))}
+ //
+ // )
+ // },
+ // showEditTools: true,
+ // // hideKey: true,
+ // // showCollectionWrapper: false,
+ // },
{
...dateNodeDefinition,
showOnView: true,
@@ -491,15 +511,15 @@ export const demoData: Record = {
ITEM_SINGLE: ({ key, value, size }) => {
if (value instanceof Object && 'name' in value)
return `${value.name} (${(value as any)?.publisher ?? ''})`
- if (key === 'aliases' && Array.isArray(value))
- return `${size} ${size === 1 ? 'name' : 'names'}`
+ if (key === 'aliases' && Array.isArray(value)) return `One name`
+ if (key === 'portrayedBy' && Array.isArray(value)) return `One actor`
return null
},
ITEMS_MULTIPLE: ({ key, value, size }) => {
if (value instanceof Object && 'name' in value)
return `${value.name} (${(value as any)?.publisher ?? ''})`
- if (key === 'aliases' && Array.isArray(value))
- return `${size} ${size === 1 ? 'name' : 'names'}`
+ if (key === 'aliases' && Array.isArray(value)) return `${size} names`
+ if (key === 'portrayedBy' && Array.isArray(value)) return `${size} actors`
return null
},
},
@@ -507,86 +527,4 @@ export const demoData: Record = {
string: ({ key }) => (key === 'name' ? { fontWeight: 'bold', fontSize: '120%' } : null),
},
},
- // Enable to test more complex features of Custom nodes
- // testCustomNodes: {
- // name: '🔧 Custom Nodes',
- // description: (
- //
- //
- // This data set shows Custom Nodes — you can provide your own components to
- // present specialised data in a unique way, or provide a more complex editing mechanism for
- // a specialised data structure, say.
- //
- //
- // In this example, compare the raw JSON (edit the data root) with what is presented here.
- //
- //
- // See the{' '}
- // Custom Nodes{' '}
- // section of the documentation for more info.
- //
- //
- // ),
- // rootName: 'Superheroes',
- // collapse: 2,
- // data: data.customNodes,
- // customNodeDefinitions: [
- // {
- // condition: ({ key, value }) =>
- // key === 'logo' &&
- // typeof value === 'string' &&
- // value.startsWith('http') &&
- // value.endsWith('.png'),
- // element: ({ data }) => {
- // const truncate = (string: string, length = 50) =>
- // string.length < length ? string : `${string.slice(0, length - 2).trim()}...`
- // return (
- //