Skip to content

Commit

Permalink
fix: #163 implement method .select() and callback onSelect (#299)
Browse files Browse the repository at this point in the history
  • Loading branch information
josdejong committed Aug 14, 2023
1 parent 53e671d commit 69a9698
Show file tree
Hide file tree
Showing 35 changed files with 1,128 additions and 805 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ on:
push:
branches:
- '*'
tags:
pull_request:
branches: [develop, main]

jobs:
build-and-test:
Expand Down
138 changes: 9 additions & 129 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ const editor = new JSONEditor({

### properties

- `content: Content` Pass the JSON contents to be rendered in the JSONEditor. `Content` is an object containing a property `json` (a parsed JSON document) or `text` (a stringified JSON document). Only one of the two properties must be defined. You can pass both content types to the editor independent of in what mode it is.
- `content: Content` Pass the JSON contents to be rendered in the JSONEditor. `Content` is an object containing a property `json` (a parsed JSON document) or `text` (a stringified JSON document). Only one of the two properties must be defined. You can pass both content types to the editor independent of in what mode it is. You can use two-way binding via `bind:content`.
- `selection: JSONEditorSelection | null` The current selected contents. You can use two-way binding using `bind:selection`. The `tree` mode supports `MultiSelection`, `KeySelection`, `ValueSelection`, `InsideSelection`, or `AfterSelection`. The `table` mode supports `ValueSelection`, and `text` mode supports `TextSelection.`.
- `mode: 'tree' | 'text' | 'table'`. Open the editor in `'tree'` mode (default), `'table'` mode, or `'text'` mode (formerly: `code` mode).
- `mainMenuBar: boolean` Show the main menu bar. Default value is `true`.
- `navigationBar: boolean` Show the navigation bar with, where you can see the selected path and navigate through your document from there. Default value is `true`.
Expand Down Expand Up @@ -313,6 +314,10 @@ const editor = new JSONEditor({
}
```

- `onSelect: (selection: JSONEditorSelection | null) => void`

Callback invoked when the selection is changed. When the selection is removed, the callback is invoked with `undefined` as argument. In `text` mode, a `TextSelection` will be fired. In `tree` and `table` mode, a `JSONSelection` will be fired (which can be `MultiSelection`, `KeySelection`, `ValueSelection`, `InsideSelection`, or `AfterSelection`). Use typeguards like `isTextSelection` and `isValueSelection` to check what type the selection has.

- `queryLanguages: QueryLanguage[]`.
Configure one or multiple query language that can be used in the Transform modal. The library comes with three languages:

Expand Down Expand Up @@ -363,6 +368,7 @@ Note that most methods are asynchronous and will resolve after the editor is re-
- `acceptAutoRepair(): Promise<Content>` In tree mode, invalid JSON is automatically repaired when loaded. When the repair was successful, the repaired contents are rendered but not yet applied to the document itself until the user clicks "Ok" or starts editing the data. Instead of accepting the repair, the user can also click "Repair manually instead". Invoking `.acceptAutoRepair()` will programmatically accept the repair. This will trigger an update, and the method itself also returns the updated contents. In case of `text` mode or when the editor is not in an "accept auto repair" status, nothing will happen, and the contents will be returned as is.
- `refresh(): Promise<void>`. Refresh rendering of the contents, for example after changing the font size. This is only available in `text` mode.
- `validate() : ContentErrors | null`. Get all current parse errors and validation errors.
- `select(newSelection: JSONEditorSelection | null)` change the current selection. See also option `selection`.
- `focus(): Promise<void>`. Give the editor focus.
- `destroy(): Promise<void>`. Destroy the editor, remove it from the DOM.

Expand Down Expand Up @@ -432,135 +438,9 @@ Note that most methods are asynchronous and will resolve after the editor is re-

### Types

```ts
type JSONValue = { [key: string]: JSONValue } | JSONValue[] | string | number | boolean | null

type TextContent = { text: string }
type JSONContent = { json: JSONValue }
type Content = JSONContent | TextContent

type JSONParser = JSON

interface JSONPathParser {
parse: (pathStr) => JSONPath
stringify: (path: JSONPath) => string
}

type JSONPatchDocument = JSONPatchOperation[]

type JSONPatchOperation = {
op: 'add' | 'remove' | 'replace' | 'copy' | 'move' | 'test'
path: string
from?: string
value?: JSONValue
}

type JSONPatchResult = {
json: JSONValue
previousJson: JSONValue
undo: JSONPatchDocument
redo: JSONPatchDocument
}

interface ParseError {
position: number | null
line: number | null
column: number | null
message: string
}

interface ValidationError {
path: JSONPath
message: string
severity: ValidationSeverity
}

interface ContentParseError {
parseError: ParseError
isRepairable: boolean
}

interface ContentValidationErrors {
validationErrors: ValidationError[]
}
The TypeScript types (like `Content`, `JSONSelection`, and `JSONPatchOperation`) are defined in the following source file:

type ContentErrors = ContentParseError | ContentValidationErrors

interface QueryLanguage {
id: string
name: string
description: string
createQuery: (json: JSONValue, queryOptions: QueryLanguageOptions) => string
executeQuery: (json: JSONValue, query: string, parser: JSONParser) => JSONValue
}

interface QueryLanguageOptions {
filter?: {
path?: string[]
relation?: '==' | '!=' | '<' | '<=' | '>' | '>='
value?: string
}
sort?: {
path?: string[]
direction?: 'asc' | 'desc'
}
projection?: {
paths?: string[][]
}
}

interface RenderValuePropsOptional {
path?: Path
value?: JSONValue
readOnly?: boolean
enforceString?: boolean
selection?: Selection
searchResultItems?: SearchResultItem[]
isSelected?: boolean
isEditing?: boolean
normalization?: ValueNormalization
onPatch?: TreeModeContext['onPatch']
onPasteJson?: (pastedJson: { path: Path; contents: JSONValue }) => void
onSelect?: (selection: Selection) => void
onFind?: (findAndReplace: boolean) => void
focus?: () => void
}

interface RenderValueProps extends RenderValuePropsOptional {
path: Path
value: JSONValue
readOnly: boolean
enforceString: boolean | undefined
selection: Selection | undefined
searchResultItems: SearchResultItem[] | undefined
isSelected: boolean
isEditing: boolean
normalization: ValueNormalization
onPatch: (patch: JSONPatchDocument, afterPatch?: AfterPatchCallback) => JSONPatchResult
onPasteJson: (pastedJson: { path: Path; contents: JSONValue }) => void
onSelect: (selection: Selection) => void
onFind: (findAndReplace: boolean) => void
focus: () => void
}

type ValueNormalization = {
escapeValue: (any) => string
unescapeValue: (string) => string
}

type SearchResultItem = {
path: Path
field: Symbol
fieldIndex: number
start: number
end: number
}

interface RenderValueComponentDescription {
component: SvelteComponent
props: RenderValuePropsOptional
}
```
https://github.com/josdejong/svelte-jsoneditor/blob/main/src/lib/types.ts

## Styling

Expand Down
24 changes: 18 additions & 6 deletions src/lib/components/JSONEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
ContentErrors,
JSONEditorModalCallback,
JSONEditorPropsOptional,
JSONEditorSelection,
JSONParser,
JSONPatchResult,
JSONPathParser,
Expand All @@ -40,6 +41,7 @@
OnFocus,
OnRenderMenu,
OnRenderValue,
OnSelect,
QueryLanguage,
SortModalCallback,
TransformModalCallback,
Expand All @@ -60,6 +62,7 @@
const debug = createDebug('jsoneditor:JSONEditor')
export let content: Content = { text: '' }
export let selection: JSONEditorSelection | null = null
export let readOnly = false
export let indentation: number | string = 2
Expand All @@ -85,6 +88,7 @@
export let onChangeQueryLanguage: OnChangeQueryLanguage = noop
export let onChange: OnChange = null
export let onSelect: OnSelect = noop
export let onRenderValue: OnRenderValue = renderValue
export let onClassName: OnClassName = () => undefined
export let onRenderMenu: OnRenderMenu = noop
Expand Down Expand Up @@ -191,6 +195,12 @@
return result
}
export async function select(newSelection: JSONEditorSelection | null) {
selection = newSelection
await tick() // await rerender
}
export async function expand(callback?: OnExpand): Promise<void> {
refJSONEditorRoot.expand(callback)
Expand Down Expand Up @@ -269,6 +279,12 @@
}
}
function handleSelect(updatedSelection: JSONEditorSelection | null) {
selection = updatedSelection
onSelect(updatedSelection)
}
function handleFocus() {
hasFocus = true
if (onFocus) {
Expand Down Expand Up @@ -382,15 +398,9 @@
validator: undefined, // TODO: support partial JSON validation?
validationParser,
pathParser,
// TODO: verify whether we need wrapper functions for the next
// onChange, // TODO: cleanup when indeed not needed
onRenderValue,
onClassName,
onRenderMenu,
// onError, // TODO: cleanup when indeed not needed
// onFocus, // TODO: cleanup when indeed not needed
// onBlur, // TODO: cleanup when indeed not needed
onSortModal,
onTransformModal
}),
Expand Down Expand Up @@ -431,6 +441,7 @@
bind:this={refJSONEditorRoot}
{mode}
{content}
{selection}
{readOnly}
{indentation}
{tabSize}
Expand All @@ -450,6 +461,7 @@
{onError}
onChange={handleChange}
onChangeMode={toggleMode}
onSelect={handleSelect}
{onRenderValue}
{onClassName}
onFocus={handleFocus}
Expand Down
12 changes: 6 additions & 6 deletions src/lib/components/controls/navigationBar/NavigationBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import { existsIn, getIn } from 'immutable-json-patch'
import { range } from 'lodash-es'
import { isObject, isObjectOrArray } from '$lib/utils/typeUtils.js'
import { createMultiSelection } from '$lib/logic/selection.js'
import { createMultiSelection, getFocusPath } from '$lib/logic/selection.js'
import { createDebug } from '$lib/utils/debug.js'
import { caseInsensitiveNaturalCompare } from '$lib/logic/sort.js'
import type { DocumentState, JSONPathParser, OnError, OnSelect } from '$lib/types.js'
import type { JSONPathParser, JSONSelection, OnError, OnJSONSelect } from '$lib/types.js'
import Icon from 'svelte-awesome'
import { faClose, faEdit } from '@fortawesome/free-solid-svg-icons'
import NavigationBarItem from './NavigationBarItem.svelte'
Expand All @@ -17,16 +17,16 @@
const debug = createDebug('jsoneditor:NavigationBar')
export let json: JSONValue
export let documentState: DocumentState
export let onSelect: OnSelect
export let selection: JSONSelection | null
export let onSelect: OnJSONSelect
export let onError: OnError
export let pathParser: JSONPathParser
let refNavigationBar: Element | undefined
let refEditButton: HTMLButtonElement | undefined
let editing = false
$: path = documentState.selection ? documentState.selection.focusPath : []
$: path = selection ? getFocusPath(selection) : []
$: hasNextItem = isObjectOrArray(getIn(json, path))
// we have an unused parameter path to trigger scrollToLastItem when path changes,
Expand Down Expand Up @@ -73,7 +73,7 @@
function handleSelect(path: JSONPath) {
debug('select path', JSON.stringify(path))
onSelect(createMultiSelection(json, path, path))
onSelect(createMultiSelection(path, path))
}
function toggleEditing() {
Expand Down
13 changes: 11 additions & 2 deletions src/lib/components/modals/JSONEditorModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import type {
Content,
JSONEditorModalCallback,
JSONEditorSelection,
JSONParser,
JSONPathParser,
OnClassName,
Expand Down Expand Up @@ -72,6 +73,7 @@
relativePath: path
}
let stack: ModalState[] = [rootState]
let selection: JSONEditorSelection | null = null
$: absolutePath = stack.flatMap((state) => state.relativePath)
$: pathDescription = !isEmpty(absolutePath) ? stringifyJSONPath(absolutePath) : '(whole document)'
Expand All @@ -95,11 +97,13 @@
try {
error = undefined
const path = last(stack).relativePath
const content = last(stack).content
const operations: JSONPatchDocument = [
{
op: 'replace',
path: compileJSONPointer(last(stack).relativePath),
value: toJSONContent(last(stack).content, parser).json // this can throw an error
path: compileJSONPointer(path),
value: toJSONContent(content, parser).json // this can throw an error
}
]
Expand All @@ -112,6 +116,7 @@
// after successfully updated, remove from the stack and apply the change
stack = initial(stack)
selection = null // TODO: restore the selection, like selection = createValueSelection(path, false)
handleChange(updatedParentContent)
} else {
onPatch(operations)
Expand Down Expand Up @@ -201,6 +206,7 @@
<JSONEditorRoot
mode={last(stack).mode}
content={last(stack).content}
{selection}
{readOnly}
{indentation}
{tabSize}
Expand All @@ -220,6 +226,9 @@
onError={handleError}
onChange={handleChange}
onChangeMode={handleChangeMode}
onSelect={(newSelection) => {
selection = newSelection
}}
{onRenderValue}
{onClassName}
onFocus={noop}
Expand Down

0 comments on commit 69a9698

Please sign in to comment.