Skip to content

Commit

Permalink
feat: #399 enable onRenderContextMenu when the editor is readOnly (#411)
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

The `onRenderContextMenu` callback now also triggers when the editor is `readOnly`,
so you now have to handle that case in the callback.
  • Loading branch information
1pone committed Mar 8, 2024
1 parent dca87f0 commit db3fb57
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 198 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -517,10 +517,10 @@ A menu item `MenuItem` can be one of the following types:
#### onRenderContextMenu
```ts
onRenderContextMenu(items: ContextMenuItem[], context: { mode: 'tree' | 'text' | 'table', modal: boolean, selection: JSONEditorSelection | null }) : ContextMenuItem[] | undefined
onRenderContextMenu(items: ContextMenuItem[], context: { mode: 'tree' | 'text' | 'table', modal: boolean, selection: JSONEditorSelection | null }) : ContextMenuItem[] | false | undefined
```
Callback which can be used to make changes to the context menu items. New items can be added, or existing items can be removed or reorganized. When the function returns `undefined`, the original `items` will be applied. Using the context values `mode`, `modal` and `selection`, different actions can be taken depending on the mode of the editor, whether the editor is rendered inside a modal or not and the path of selection.
Callback which can be used to make changes to the context menu items. New items can be added, or existing items can be removed or reorganized. When the function returns `undefined`, the original `items` will be applied and the context menu will be displayed when `readOnly` is `false`. When the function returns `false`, the context menu will never be displayed. Using the context values `mode`, `modal` and `selection`, different actions can be taken depending on the mode of the editor, whether the editor is rendered inside a modal or not and the path of selection.
A menu item `ContextMenuItem` can be one of the following types:
Expand Down
5 changes: 4 additions & 1 deletion src/lib/components/modes/JSONEditorRoot.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@
$: handleRenderContextMenu = (items: ContextMenuItem[]) => {
const itemsOriginal = cloneDeep(items) // the user may change items in the callback
return onRenderContextMenu(items, { mode, modal: insideModal, selection }) || itemsOriginal
return (
onRenderContextMenu(items, { mode, modal: insideModal, selection }) ??
(readOnly ? false : itemsOriginal)
)
}
export function patch(operations: JSONPatchDocument): JSONPatchResult {
Expand Down
32 changes: 20 additions & 12 deletions src/lib/components/modes/tablemode/TableMode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,15 @@
} from '$lib/logic/actions.js'
import JSONRepairModal from '../../modals/JSONRepairModal.svelte'
import { resizeObserver } from '$lib/actions/resizeObserver.js'
import TableContextMenu from '../../../components/modes/tablemode/contextmenu/TableContextMenu.svelte'
import CopyPasteModal from '../../../components/modals/CopyPasteModal.svelte'
import ContextMenuPointer from '../../../components/controls/contextmenu/ContextMenuPointer.svelte'
import TableModeWelcome from './TableModeWelcome.svelte'
import JSONPreview from '../../controls/JSONPreview.svelte'
import RefreshColumnHeader from './RefreshColumnHeader.svelte'
import type { Context } from 'svelte-simple-modal'
import type { ContextMenuItem } from '$lib/types'
import createTableContextMenuItems from './contextmenu/createTableContextMenuItems'
import ContextMenu from '../../controls/contextmenu/ContextMenu.svelte'
const debug = createDebug('jsoneditor:TableMode')
const { open } = getContext<Context>('simple-modal')
Expand Down Expand Up @@ -921,11 +923,10 @@
offsetLeft,
showTip
}: AbsolutePopupOptions) {
const props = {
const defaultItems: ContextMenuItem[] = createTableContextMenuItems({
json,
documentState: documentState,
documentState,
parser,
showTip,
onEditValue: handleEditValue,
onEditRow: handleEditRow,
Expand All @@ -937,9 +938,20 @@
onDuplicateRow: handleDuplicateRow,
onInsertBeforeRow: handleInsertBeforeRow,
onInsertAfterRow: handleInsertAfterRow,
onRemoveRow: handleRemoveRow,
onRemoveRow: handleRemoveRow
})
const items = onRenderContextMenu(defaultItems)
if (items === false) {
return
}
onRenderContextMenu,
const props = {
tip: showTip
? 'Tip: you can open this context menu via right-click or with Ctrl+Q'
: undefined,
items,
onCloseContextMenu: function () {
closeAbsolutePopup(popupId)
focus()
Expand All @@ -948,7 +960,7 @@
modalOpen = true
const popupId = openAbsolutePopup(TableContextMenu, props, {
const popupId = openAbsolutePopup(ContextMenu, props, {
left,
top,
offsetTop,
Expand All @@ -965,7 +977,7 @@
}
function handleContextMenu(event: Event) {
if (readOnly || isEditingSelection(documentState.selection)) {
if (isEditingSelection(documentState.selection)) {
return
}
Expand Down Expand Up @@ -1014,10 +1026,6 @@
}
function handleContextMenuFromTableMenu(event: MouseEvent) {
if (readOnly) {
return
}
openContextMenu({
anchor: findParentWithNodeName(event.target as HTMLElement, 'BUTTON'),
offsetTop: 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,68 +1,67 @@
<svelte:options immutable={true} />
import type { ContextMenuItem, DocumentState, JSONParser } from 'svelte-jsoneditor'
import {
faCheckSquare,
faClone,
faCopy,
faCut,
faPaste,
faPen,
faPlus,
faSquare,
faTrashCan
} from '@fortawesome/free-solid-svg-icons'
import { isKeySelection, isMultiSelection, isValueSelection } from 'svelte-jsoneditor'
import { compileJSONPointer, getIn } from 'immutable-json-patch'
import { getFocusPath, singleItemSelected } from '$lib/logic/selection'
import { isObjectOrArray } from '$lib/utils/typeUtils'
import { getEnforceString } from '$lib/logic/documentState'

<script lang="ts">
import {
faClone,
faCopy,
faCut,
faPaste,
faPen,
faPlus,
faTrashCan
} from '@fortawesome/free-solid-svg-icons'
import { compileJSONPointer, getIn } from 'immutable-json-patch'
import {
getFocusPath,
isKeySelection,
isMultiSelection,
isValueSelection,
singleItemSelected
} from '$lib/logic/selection.js'
import { isObjectOrArray } from '$lib/utils/typeUtils.js'
import { faCheckSquare, faSquare } from '@fortawesome/free-regular-svg-icons'
import type {
ContextMenuItem,
DocumentState,
JSONParser,
OnRenderContextMenuInternal
} from '$lib/types'
import { getEnforceString } from '$lib/logic/documentState.js'
import ContextMenu from '../../../../components/controls/contextmenu/ContextMenu.svelte'
export default function ({
json,
documentState,
parser,
onEditValue,
onEditRow,
onToggleEnforceString,
onCut,
onCopy,
onPaste,
onRemove,
onDuplicateRow,
onInsertBeforeRow,
onInsertAfterRow,
onRemoveRow
}: {
json: unknown | undefined
documentState: DocumentState
parser: JSONParser
onEditValue: () => void
onEditRow: () => void
onToggleEnforceString: () => void
onCut: (indent: boolean) => void
onCopy: (indent: boolean) => void
onPaste: () => void
onRemove: () => void
onDuplicateRow: () => void
onInsertBeforeRow: () => void
onInsertAfterRow: () => void
onRemoveRow: () => void
}): ContextMenuItem[] {
const selection = documentState.selection

export let json: unknown | undefined
export let documentState: DocumentState
export let parser: JSONParser
const hasJson = json !== undefined
const hasSelection = !!selection
const focusValue =
json !== undefined && selection ? getIn(json, getFocusPath(selection)) : undefined

export let showTip: boolean
export let onCloseContextMenu: () => void
export let onRenderContextMenu: OnRenderContextMenuInternal
export let onEditValue: () => void
export let onEditRow: () => void
export let onToggleEnforceString: () => void
export let onCut: (indent: boolean) => void
export let onCopy: (indent: boolean) => void
export let onPaste: () => void
export let onRemove: () => void
export let onDuplicateRow: () => void
export let onInsertBeforeRow: () => void
export let onInsertAfterRow: () => void
export let onRemoveRow: () => void
$: selection = documentState.selection
$: hasJson = json !== undefined
$: hasSelection = !!selection
$: focusValue = json !== undefined && selection ? getIn(json, getFocusPath(selection)) : undefined
$: hasSelectionContents =
const hasSelectionContents =
hasJson &&
(isMultiSelection(selection) || isKeySelection(selection) || isValueSelection(selection))

$: canEditValue = hasJson && selection != null && singleItemSelected(selection)
$: canEnforceString = canEditValue && !isObjectOrArray(focusValue)
const canEditValue = hasJson && selection != null && singleItemSelected(selection)
const canEnforceString = canEditValue && !isObjectOrArray(focusValue)

$: enforceString =
const enforceString =
selection != null && focusValue !== undefined
? getEnforceString(
focusValue,
Expand All @@ -72,8 +71,7 @@
)
: false

let defaultItems: ContextMenuItem[]
$: defaultItems = [
return [
{ type: 'separator' },
{
type: 'row',
Expand Down Expand Up @@ -239,12 +237,4 @@
]
}
]
$: items = onRenderContextMenu(defaultItems)
</script>

<ContextMenu
{items}
{onCloseContextMenu}
tip={showTip ? 'Tip: you can open this context menu via right-click or with Ctrl+Q' : undefined}
/>
}

0 comments on commit db3fb57

Please sign in to comment.