Skip to content

Commit

Permalink
feat: implemented a powerful API for expand and collapse (#458)
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

The API of the `expand` function is changed from `expand(callback)` to `expand(path, callback)`, 
and can't be used anymore for collapsing nodes. Instead, use the `collapse(path)` method for that.
  • Loading branch information
josdejong authored Jul 5, 2024
1 parent f257b24 commit 1e1e85c
Show file tree
Hide file tree
Showing 12 changed files with 707 additions and 373 deletions.
40 changes: 32 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ Invoked when the mode is changed.
#### onClassName
```ts
onClassName(path: Path, value: any): string | undefined
onClassName(path: JSONPath, value: any): string | undefined
```
Add a custom class name to specific nodes, based on their path and/or value. Note that in the custom class, you can override CSS variables like `--jse-contents-background-color` to change the styling of a node, like the background color. Relevant variables are:
Expand Down Expand Up @@ -712,14 +712,33 @@ editor.updateProps({
#### expand
```ts
JSONEditor.prototype.expand([callback: (path: Path) => boolean]): Promise<void>
JSONEditor.prototype.expand(path: JSONPath, callback?: (relativePath: JSONPath) => boolean = expandSelf): Promise<void>
```
Expand or collapse paths in the editor. The `callback` determines which paths will be expanded. If no `callback` is provided, all paths will be expanded. It is only possible to expand a path when all of its parent paths are expanded too. Examples:
Expand or collapse paths in the editor. All nodes along the provided `path` will be expanded and become visible (rendered). So for example collapsed sections of an array will be expanded. Using the optional `callback`, the node itself and some or all of its nested child nodes can be expanded too. The `callback` function only iterates over the visible sections of an array and not over any of the collapsed sections. By default, the first 100 items of an array are visible and rendered.
- `editor.expand(path => true)` expand all
- `editor.expand(path => false)` collapse all
- `editor.expand(path => path.length < 2)` expand all paths up to 2 levels deep
Examples:
- `editor.expand([], () => true)` expand all
- `editor.expand([], relativePath => relativePath.length < 2)` expand all paths up to 2 levels deep
- `editor.expand(['array', '204'])` expand the root object, the array in this object, and the 204th item in the array.
- `editor.expand(['array', '204'], () => false)` expand the root object, the array in this object, but not the 204th item itself.
- `editor.expand(['array', '204'], relativePath => relativePath.length < 2)` expand the root object, the array in this object, and expand the 204th array item and all of its child's up to a depth of max 2 levels.
The library exports a couple of utility functions for commonly used `callback` functions:
- `expandAll`: recursively expand all nested objects and arrays.
- `expandNone`: expand nothing, also not the root object or array.
- `expandSelf`: expand the root array or object. This is the default for the `callback` parameter.
- `expandMinimal`: expand the root array or object, and in case of an array, expand the first array item.
### collapse
```ts
JSONEditor.prototype.collapse(path: JSONPath, recursive?: boolean = false): Promise<void>
```
Collapse a path in the editor. When `recursive` is `true`, all nested objects and arrays will be collapsed too. The default value of `recursive` is `false`.
#### transform
Expand All @@ -732,15 +751,15 @@ Programmatically trigger clicking of the transform button in the main menu, open
#### scrollTo
```ts
JSONEditor.prototype.scrollTo(path: Path): Promise<void>
JSONEditor.prototype.scrollTo(path: JSONPath): Promise<void>
```
Scroll the editor vertically such that the specified path comes into view. Only applicable to modes `tree` and `table`. The path will be expanded when needed. The returned Promise is resolved after scrolling is finished.
#### findElement
```ts
JSONEditor.prototype.findElement(path: Path)
JSONEditor.prototype.findElement(path: JSONPath)
```
Find the DOM element of a given path. Returns `null` when not found.
Expand Down Expand Up @@ -821,6 +840,11 @@ The library exports a set of utility functions. The exact definitions of those f
- `toTextContent`
- `toJSONContent`
- `estimateSerializedSize`
- Expand:
- `expandAll`
- `expandMinimal`
- `expandNone`
- `expandSelf`
- Selection:
- `isValueSelection`
- `isKeySelection`
Expand Down
10 changes: 8 additions & 2 deletions src/lib/components/JSONEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,14 @@
await tick() // await rerender
}
export async function expand(callback?: OnExpand): Promise<void> {
refJSONEditorRoot.expand(callback)
export async function expand(path: JSONPath, callback?: OnExpand): Promise<void> {
refJSONEditorRoot.expand(path, callback)
await tick() // await rerender
}
export async function collapse(path: JSONPath, recursive = false): Promise<void> {
refJSONEditorRoot.collapse(path, recursive)
await tick() // await rerender
}
Expand Down
12 changes: 10 additions & 2 deletions src/lib/components/modes/JSONEditorRoot.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,22 @@
throw new Error(`Method patch is not available in mode "${mode}"`)
}
export function expand(callback?: OnExpand): void {
export function expand(path: JSONPath, callback?: OnExpand): void {
if (refTreeMode) {
return refTreeMode.expand(callback)
return refTreeMode.expand(path, callback)
} else {
throw new Error(`Method expand is not available in mode "${mode}"`)
}
}
export function collapse(path: JSONPath, recursive: boolean): void {
if (refTreeMode) {
return refTreeMode.collapse(path, recursive)
} else {
throw new Error(`Method collapse is not available in mode "${mode}"`)
}
}
/**
* Open the transform modal
*/
Expand Down
23 changes: 3 additions & 20 deletions src/lib/components/modes/tablemode/TableMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@
import {
createDocumentState,
documentStatePatch,
expandMinimal,
expandWithCallback,
getEnforceString,
getInRecursiveState,
setInDocumentState,
Expand Down Expand Up @@ -1369,12 +1367,7 @@
const previousContent = { json, text }
const previousState = { json, documentState, selection, sortedColumn, text, textIsRepaired }
const updatedState = expandWithCallback(
json,
syncDocumentState(updatedJson, documentState),
[],
expandMinimal
)
const updatedState = syncDocumentState(updatedJson, documentState)
const callback =
typeof afterPatch === 'function'
Expand Down Expand Up @@ -1411,24 +1404,14 @@
try {
json = parseMemoizeOne(updatedText)
documentState = expandWithCallback(
json,
syncDocumentState(json, documentState),
[],
expandMinimal
)
documentState = syncDocumentState(json, documentState)
text = undefined
textIsRepaired = false
parseError = undefined
} catch (err) {
try {
json = parseMemoizeOne(jsonrepair(updatedText))
documentState = expandWithCallback(
json,
syncDocumentState(json, documentState),
[],
expandMinimal
)
documentState = syncDocumentState(json, documentState)
text = updatedText
textIsRepaired = true
parseError = undefined
Expand Down
78 changes: 33 additions & 45 deletions src/lib/components/modes/treemode/TreeMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@
documentStatePatch,
expandAll,
expandMinimal,
expandNone,
expandPath,
expandRecursive,
expandSection,
expandSingleItem,
expandWithCallback,
getDefaultExpand,
expandSelf,
expandSmart,
getEnforceString,
setInDocumentState,
syncDocumentState
Expand Down Expand Up @@ -136,13 +135,13 @@
OnTransformModal,
ParseError,
PastedJson,
SearchResults,
ValidationErrors,
SearchResultDetails,
SearchResults,
Section,
TransformModalOptions,
TreeModeContext,
ValidationError,
ValidationErrors,
Validator,
ValueNormalization
} from '$lib/types'
Expand Down Expand Up @@ -301,7 +300,7 @@
}
async function handleFocusSearch(path: JSONPath) {
documentState = expandPath(json, documentState, path)
documentState = expandPath(json, documentState, path, expandNone)
await scrollTo(path)
}
Expand All @@ -325,11 +324,22 @@
})
let historyState = history.getState()
export function expand(callback: OnExpand = expandAll) {
export function expand(path: JSONPath, callback: OnExpand = expandSelf) {
debug('expand')
// FIXME: clear the expanded state and visible sections (else you can't collapse anything using the callback)
documentState = expandWithCallback(json, documentState, [], callback)
documentState = expandPath(json, documentState, path, callback)
}
export function collapse(path: JSONPath, recursive: boolean) {
documentState = collapsePath(json, documentState, path, recursive)
if (selection) {
// check whether the selection is still visible and not collapsed
if (isSelectionInsidePath(selection, path)) {
// remove selection when not visible anymore
updateSelection(undefined)
}
}
}
// two-way binding of externalContent and internal json and text (
Expand Down Expand Up @@ -511,7 +521,7 @@
function expandWhenNotInitialized(json: unknown) {
if (!documentStateInitialized) {
documentStateInitialized = true
documentState = createDocumentState({ json, expand: getDefaultExpand(json) })
documentState = expandSmart(json, documentState, [])
}
}
Expand Down Expand Up @@ -843,7 +853,7 @@
// expand extracted object/array
const path: JSONPath = []
return {
state: expandRecursive(patchedJson, patchedState, path)
state: expandSmart(patchedJson, patchedState, path)
}
}
Expand Down Expand Up @@ -911,7 +921,7 @@
// expand converted object/array
return {
state: selection
? expandRecursive(patchedJson, patchedState, getFocusPath(selection))
? expandSmart(patchedJson, patchedState, getFocusPath(selection))
: documentState
}
})
Expand Down Expand Up @@ -1074,7 +1084,7 @@
handlePatch(operations, (patchedJson, patchedState) => ({
// expand the newly replaced array and select it
state: expandRecursive(patchedJson, patchedState, rootPath),
state: expandSmart(patchedJson, patchedState, rootPath),
selection: createValueSelection(rootPath, false)
}))
},
Expand Down Expand Up @@ -1128,7 +1138,7 @@
handlePatch(operations, (patchedJson, patchedState) => ({
// expand the newly replaced array and select it
state: expandRecursive(patchedJson, patchedState, rootPath),
state: expandSmart(patchedJson, patchedState, rootPath),
selection: createValueSelection(rootPath, false)
}))
}
Expand Down Expand Up @@ -1184,7 +1194,7 @@
* Expand the path when needed.
*/
export async function scrollTo(path: JSONPath, scrollToWhenVisible = true): Promise<void> {
documentState = expandPath(json, documentState, path)
documentState = expandPath(json, documentState, path, expandNone)
await tick() // await rerender (else the element we want to scroll to does not yet exist)
const elem = findElement(path)
Expand Down Expand Up @@ -1302,7 +1312,7 @@
const previousContent = { json, text }
const previousState = { documentState, selection, json, text, textIsRepaired }
const updatedState = expandWithCallback(
const updatedState = expandPath(
json,
syncDocumentState(updatedJson, documentState),
[],
Expand Down Expand Up @@ -1342,24 +1352,14 @@
try {
json = parseMemoizeOne(updatedText)
documentState = expandWithCallback(
json,
syncDocumentState(json, documentState),
[],
expandMinimal
)
documentState = expandPath(json, syncDocumentState(json, documentState), [], expandMinimal)
text = undefined
textIsRepaired = false
parseError = undefined
} catch (err) {
try {
json = parseMemoizeOne(jsonrepair(updatedText))
documentState = expandWithCallback(
json,
syncDocumentState(json, documentState),
[],
expandMinimal
)
documentState = expandPath(json, syncDocumentState(json, documentState), [], expandMinimal)
text = updatedText
textIsRepaired = true
parseError = undefined
Expand Down Expand Up @@ -1398,28 +1398,16 @@
/**
* Toggle expanded state of a node
* @param path The path to be expanded
* @param expanded True to expand, false to collapse
* @param expanded True if currently expanded, false when currently collapsed
* @param [recursive=false] Only applicable when expanding
*/
function handleExpand(path: JSONPath, expanded: boolean, recursive = false): void {
debug('handleExpand', { path, expanded, recursive })
if (expanded) {
if (recursive) {
documentState = expandWithCallback(json, documentState, path, expandAll)
} else {
documentState = expandSingleItem(json, documentState, path)
}
expand(path, recursive ? expandAll : expandSelf)
} else {
documentState = collapsePath(json, documentState, path)
if (selection) {
// check whether the selection is still visible and not collapsed
if (isSelectionInsidePath(selection, path)) {
// remove selection when not visible anymore
updateSelection(undefined)
}
}
collapse(path, recursive)
}
// set focus to the hidden input, so we can capture quick keys like Ctrl+X, Ctrl+C, Ctrl+V
Expand Down Expand Up @@ -1802,7 +1790,7 @@
handlePatch(operations, (patchedJson, patchedState) => {
return {
state: expandRecursive(patchedJson, patchedState, path)
state: expandSmart(patchedJson, patchedState, path)
}
})
Expand Down
3 changes: 3 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export {
estimateSerializedSize
} from './utils/jsonUtils.js'

// expand
export { expandAll, expandMinimal, expandNone, expandSelf } from './logic/documentState'

// selection
export {
isValueSelection,
Expand Down
Loading

0 comments on commit 1e1e85c

Please sign in to comment.