From ec553088e72a310adf1098b05794d76099d8ed0d Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Wed, 24 Jan 2024 15:42:04 +0100 Subject: [PATCH 1/5] feat: implement support for vanilla JS components in `onRenderValue` (see #375) --- .../__snapshots__/JSONEditor.test.ts.snap | 10 ++++ .../modes/tablemode/JSONValue.svelte | 20 ++++++-- .../modes/treemode/JSONValue.svelte | 20 ++++++-- src/lib/typeguards.ts | 12 ++++- src/lib/types.ts | 10 +++- src/routes/components/Evaluator.ts | 47 +++++++++++++++++++ .../custom_value_renderer/+page.svelte | 22 ++++++++- 7 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 src/routes/components/Evaluator.ts diff --git a/src/lib/components/__snapshots__/JSONEditor.test.ts.snap b/src/lib/components/__snapshots__/JSONEditor.test.ts.snap index bed4450f..c5ec1c7c 100644 --- a/src/lib/components/__snapshots__/JSONEditor.test.ts.snap +++ b/src/lib/components/__snapshots__/JSONEditor.test.ts.snap @@ -300,6 +300,7 @@ exports[`JSONEditor > render table mode 1`] = ` + @@ -320,6 +321,7 @@ exports[`JSONEditor > render table mode 1`] = ` + @@ -352,6 +354,7 @@ exports[`JSONEditor > render table mode 1`] = ` + @@ -372,6 +375,7 @@ exports[`JSONEditor > render table mode 1`] = ` + @@ -404,6 +408,7 @@ exports[`JSONEditor > render table mode 1`] = ` + @@ -424,6 +429,7 @@ exports[`JSONEditor > render table mode 1`] = ` + @@ -1649,6 +1655,7 @@ exports[`JSONEditor > render tree mode 1`] = ` + @@ -1825,6 +1832,7 @@ exports[`JSONEditor > render tree mode 1`] = ` + @@ -1888,6 +1896,7 @@ exports[`JSONEditor > render tree mode 1`] = ` + @@ -2064,6 +2073,7 @@ exports[`JSONEditor > render tree mode 1`] = ` + diff --git a/src/lib/components/modes/tablemode/JSONValue.svelte b/src/lib/components/modes/tablemode/JSONValue.svelte index 7beeeb50..b24404c1 100644 --- a/src/lib/components/modes/tablemode/JSONValue.svelte +++ b/src/lib/components/modes/tablemode/JSONValue.svelte @@ -7,6 +7,7 @@ JSONSelection, SearchResultItem } from '$lib/types' + import { isSvelteActionRenderer } from '$lib/typeguards.js' import type { JSONPatchDocument, JSONPath } from 'immutable-json-patch' import { isEditingSelection, isValueSelection } from '$lib/logic/selection.js' import { createNestedValueOperations } from '$lib/logic/operations.js' @@ -47,7 +48,20 @@ {#each renderers as renderer} - {#key renderer.component} - - {/key} + {#if isSvelteActionRenderer(renderer)} + {@const action = renderer.action} + {#key renderer.action} +
+ {/key} + {:else} + {#key renderer.component} + + {/key} + {/if} {/each} diff --git a/src/lib/components/modes/treemode/JSONValue.svelte b/src/lib/components/modes/treemode/JSONValue.svelte index a8abd9db..4b804eca 100644 --- a/src/lib/components/modes/treemode/JSONValue.svelte +++ b/src/lib/components/modes/treemode/JSONValue.svelte @@ -2,6 +2,7 @@ {#each renderers as renderer} - {#key renderer.component} - - {/key} + {#if isSvelteActionRenderer(renderer)} + {@const action = renderer.action} + {#key renderer.action} +
+ {/key} + {:else} + {#key renderer.component} + + {/key} + {/if} {/each} diff --git a/src/lib/typeguards.ts b/src/lib/typeguards.ts index f3dd139c..c82a3905 100644 --- a/src/lib/typeguards.ts +++ b/src/lib/typeguards.ts @@ -9,7 +9,9 @@ import type { MenuSeparator, MenuSpace, ValidationError, - NestedValidationError + NestedValidationError, + SvelteActionRenderer, + SvelteComponentRenderer } from './types.js' import { isObject } from '$lib/utils/typeUtils.js' @@ -88,3 +90,11 @@ export function isValidationError(value: unknown): value is ValidationError { export function isNestedValidationError(value: unknown): value is NestedValidationError { return isObject(value) && isValidationError(value) && typeof value.isChildError === 'boolean' } + +export function isSvelteComponentRenderer(value: unknown): value is SvelteComponentRenderer { + return isObject(value) && 'component' in value && isObject(value.props) +} + +export function isSvelteActionRenderer(value: unknown): value is SvelteActionRenderer { + return isObject(value) && typeof value.action === 'function' && isObject(value.props) +} diff --git a/src/lib/types.ts b/src/lib/types.ts index 40b9c6a1..981b001a 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,6 +1,7 @@ import type { JSONPatchDocument, JSONPath, JSONPointer } from 'immutable-json-patch' import type { SvelteComponent } from 'svelte' import type { IconDefinition } from '@fortawesome/free-solid-svg-icons' +import type { Action } from 'svelte/action' export type TextContent = { text: string } @@ -541,11 +542,18 @@ export interface DraggingState { didMoveItems: boolean } -export interface RenderValueComponentDescription { +export type RenderValueComponentDescription = SvelteComponentRenderer | SvelteActionRenderer + +export interface SvelteComponentRenderer { component: typeof SvelteComponent props: Record } +export interface SvelteActionRenderer { + action: Action + props: Record +} + export interface TransformModalOptions { id?: string rootPath?: JSONPath diff --git a/src/routes/components/Evaluator.ts b/src/routes/components/Evaluator.ts new file mode 100644 index 00000000..3547e929 --- /dev/null +++ b/src/routes/components/Evaluator.ts @@ -0,0 +1,47 @@ +import { createValueSelection, type OnSelect } from 'svelte-jsoneditor' +import type { Action } from 'svelte/action' +import type { JSONPath } from 'immutable-json-patch' + +export interface EvaluatorProps { + value: unknown + path: JSONPath + readOnly: boolean + onSelect: OnSelect +} + +export const Evaluator: Action = (node, props) => { + function evaluate(expr: string) { + const result = expr + .split('+') + .map((value) => parseFloat(value.trim())) + .reduce((a, b) => a + b) + + return `The result of "${expr}" is "${result}" (double-click to edit)` + } + + function handleValueDoubleClick(event: MouseEvent) { + if (!props) { + return + } + + if (!props.readOnly) { + event.preventDefault() + event.stopPropagation() + + // open in edit mode + props.onSelect(createValueSelection(props.path, true)) + } + } + + node.addEventListener('dblclick', handleValueDoubleClick) + node.innerText = evaluate(String(props.value)) + + return { + update: (props) => { + node.innerText = evaluate(String(props.value)) + }, + destroy: () => { + node.removeEventListener('dblclick', handleValueDoubleClick) + } + } +} diff --git a/src/routes/examples/custom_value_renderer/+page.svelte b/src/routes/examples/custom_value_renderer/+page.svelte index f7c9b41e..e0073618 100644 --- a/src/routes/examples/custom_value_renderer/+page.svelte +++ b/src/routes/examples/custom_value_renderer/+page.svelte @@ -1,13 +1,15 @@ - Custom value renderer (password, enum) | svelte-jsoneditor + Custom value renderer (password, enum, action) | svelte-jsoneditor -

Custom value renderer (password, enum)

+

Custom value renderer (password, enum, action)

- Provide a custom onRenderValue method, which hides the value of all fields with the name - "password", and creates an enum for the fields with name "gender". The field named "evaluate" is rendered - using a vanilla JS component (action) which evaluates the value as an expression containing an addition - of two or more values. -

-

- EXPERIMENTAL! This API will most likely change in future versions. + Provide a custom onRenderValue method, which demonstrates three things:

+
    +
  1. It hides the value of all fields with the name "password" using a Svelte password component ReadonlyPassword
  2. +
  3. It creates an enum component for the fields with name "gender" using a Svelte component EnumValue.
  4. +
  5. The creates a custom component for the field named "evaluate" using a Svelte Action, + which evaluates the value as an expression containing an addition of two or more values. + This solution can be used when using svelte-jsoneditor in a Vanilla JS environment. +
  6. +
From 73cfc50638e4b2aac99c3ea99aeb28ab57bf4c40 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Fri, 2 Feb 2024 14:48:26 +0100 Subject: [PATCH 4/5] chore: fix linting issues --- README.md | 8 ++++---- .../custom_value_renderer/+page.svelte | 19 ++++++++++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b3828c92..ae45c03f 100644 --- a/README.md +++ b/README.md @@ -293,7 +293,7 @@ const editor = new JSONEditor({ return renderJSONSchemaEnum(props, schema, schemaDefinitions) || renderValue(props) } ``` - + The callback `onRenderValue` must return an array with one or multiple renderers. Each renderer can be either a Svelte component or a Svelte action: ```ts @@ -301,15 +301,15 @@ const editor = new JSONEditor({ component: typeof SvelteComponent props: Record } - + interface SvelteActionRenderer { action: Action // Svelte Action props: Record } ``` - + The `SvelteComponentRenderer` interface can be used to provide Svelte components like the `EnumValue` component mentioned above. The `SvelteActionRenderer` expects a [Svelte Action](https://svelte.dev/docs/svelte-action) as `action` property. Since this interface is a plain JavaScript interface, this allows to create custom components in a vanilla JS environment. Basically it is a function that gets a DOM node passed, and needs to return an object with `update` and `destroy` functions: - + ```js const myRendererAction = { action: (node) => { diff --git a/src/routes/examples/custom_value_renderer/+page.svelte b/src/routes/examples/custom_value_renderer/+page.svelte index 1a9d606d..5c0efcd5 100644 --- a/src/routes/examples/custom_value_renderer/+page.svelte +++ b/src/routes/examples/custom_value_renderer/+page.svelte @@ -84,11 +84,20 @@ Provide a custom onRenderValue method, which demonstrates three things:

    -
  1. It hides the value of all fields with the name "password" using a Svelte password component ReadonlyPassword
  2. -
  3. It creates an enum component for the fields with name "gender" using a Svelte component EnumValue.
  4. -
  5. The creates a custom component for the field named "evaluate" using a Svelte Action, - which evaluates the value as an expression containing an addition of two or more values. - This solution can be used when using svelte-jsoneditor in a Vanilla JS environment. +
  6. + It hides the value of all fields with the name "password" using a Svelte password component ReadonlyPassword +
  7. +
  8. + It creates an enum component for the fields with name "gender" using a Svelte component EnumValue. +
  9. +
  10. + The creates a custom component for the field named "evaluate" using a Svelte Action, which + evaluates the value as an expression containing an addition of two or more values. This solution + can be used when using svelte-jsoneditor in a Vanilla JS environment.
From e757845a6f3fca09dd4a39fdbe816d1f796c34b2 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Fri, 2 Feb 2024 16:03:01 +0100 Subject: [PATCH 5/5] fix: typescript issues --- .../modes/treemode/JSONValue.svelte | 2 +- src/lib/types.ts | 2 +- src/routes/components/EvaluatorAction.ts | 43 +++++++++++++------ .../custom_value_renderer/+page.svelte | 14 ++++-- 4 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/lib/components/modes/treemode/JSONValue.svelte b/src/lib/components/modes/treemode/JSONValue.svelte index 4b804eca..e0bffb4b 100644 --- a/src/lib/components/modes/treemode/JSONValue.svelte +++ b/src/lib/components/modes/treemode/JSONValue.svelte @@ -2,9 +2,9 @@