diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index 11780f5..b8cb127 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -3,20 +3,20 @@ import { mount } from '@vue/test-utils' import { expect, test } from 'vitest' test('string value in text mode', async () => { - const wrapper = mount(JsonEditorVue, { - props: { - mode: 'text', - modelValue: 'abc', - }, - }) - expect(wrapper.get('.cm-activeLine').text()).toEqual('"abc"') + const wrapper = mount(JsonEditorVue, { + props: { + mode: 'text', + modelValue: 'abc', + }, + }) + expect(wrapper.get('.cm-activeLine').text()).toEqual('"abc"') }) test('string value in tree mode', async () => { - const wrapper = mount(JsonEditorVue, { - props: { - modelValue: 'abc', - }, - }) - expect(wrapper.get('.jse-value').text()).toEqual('abc') + const wrapper = mount(JsonEditorVue, { + props: { + modelValue: 'abc', + }, + }) + expect(wrapper.get('.jse-value').text()).toEqual('abc') }) diff --git a/rome.json b/rome.json index b24047a..6d9600e 100644 --- a/rome.json +++ b/rome.json @@ -10,6 +10,7 @@ } }, "formatter": { + "indentStyle": "space", "lineWidth": 120 }, "javascript": { diff --git a/src/Component.ts b/src/Component.ts index 32f1eab..5fc42f8 100644 --- a/src/Component.ts +++ b/src/Component.ts @@ -13,202 +13,202 @@ type ModelValueProp = 'modelValue' | 'value' const modelValueProp: ModelValueProp = isVue3 ? 'modelValue' : 'value' const updateModelValue = isVue3 ? 'update:modelValue' : 'input' const boolAttrs = [ - 'mainMenuBar', - 'navigationBar', - 'statusBar', - 'readOnly', - 'escapeControlCharacters', - 'escapeUnicodeCharacters', - 'flattenColumns', + 'mainMenuBar', + 'navigationBar', + 'statusBar', + 'readOnly', + 'escapeControlCharacters', + 'escapeUnicodeCharacters', + 'flattenColumns', ] as const export default defineComponent({ - name, - props: { - [modelValueProp]: {}, - mode: { - type: String as PropType, - }, - ...Object.fromEntries( - boolAttrs.map((boolAttr) => [ - boolAttr, - { - type: Boolean as PropType, - default: undefined, - }, - ]), - ), - } as { - [key in ModelValueProp]: {} - } & { mode: { type: PropType } } & { - [key in typeof boolAttrs[number]]: { - type: PropType - default: undefined - } - }, - emits: { - [updateModelValue](_payload: any) { - return true - }, - 'update:mode': function (_payload: Mode) { - return true - }, - }, - setup(props, { attrs, emit, expose }) { - const currentInstance = getCurrentInstance()?.proxy - const jsonEditor = ref() - - const preventUpdatingContent = ref(false) - const preventUpdatingModelValue = ref(false) - - const initialMode = conclude([props.mode, globalProps.mode], { - type: String as PropType, - }) - const initialValue = conclude([props[modelValueProp], globalProps[modelValueProp]]) - const initialBoolAttrs = Object.fromEntries( - Array.from(boolAttrs, (boolAttr) => [boolAttr, conclude([props[boolAttr], globalProps[boolAttr]])]).filter( - ([, v]) => v !== undefined, - ), - ) - - const onChange = debounce((updatedContent: Content) => { - if (preventUpdatingModelValue.value) { - preventUpdatingModelValue.value = false - return - } - preventUpdatingContent.value = true - emit( - updateModelValue, - (updatedContent as TextContent).text === undefined - ? (updatedContent as JSONContent).json - : (updatedContent as TextContent).text, - ) - }, 100) - - const onChangeMode = (mode: Mode) => { - emit('update:mode', mode) - } - - const mergeFunction = (previousValue: Function, currentValue: Function) => (...args: any) => { - previousValue(...args) - currentValue(...args) - } - - const initialAttrs = conclude( - [ - attrs, - globalAttrs, - { - // Both user input & setting value programmatically will trigger onChange - onChange, - onChangeMode, - mode: initialMode, - ...initialBoolAttrs, - ...(initialValue !== undefined && { - content: { - json: initialValue, - }, - }), - }, - ], - { - type: Object, - mergeFunction, - }, - ) - - watch( - () => props[modelValueProp], - (newModelValue: any) => { - if (preventUpdatingContent.value) { - preventUpdatingContent.value = false - return - } - preventUpdatingModelValue.value = true - // `jsonEditor.value` could be `undefined` in Vue 2.6 (dev environment) - jsonEditor.value?.update( - [undefined, ''].includes(newModelValue) - ? // `undefined` is not accepted by vanilla-jsoneditor - // The default value is `{ text: '' }` - // Only default value can clear the editor - { text: '' } - : { json: newModelValue }, - ) - }, - { - deep: true, - }, - ) - - watch( - () => props.mode, - (mode) => { - jsonEditor.value.updateProps({ - mode, - }) - }, - ) - - watch( - () => Array.from(boolAttrs, (boolAttr) => props[boolAttr]), - (values) => { - jsonEditor.value.updateProps( - Object.fromEntries(Array.from(values, (v, i) => [boolAttrs[i], v]).filter(([, v]) => v !== undefined)), - ) - }, - ) - - watch( - () => attrs, - (newAttrs) => { - // Functions need to be merged again - const defaultFunctionAttrs: { - onChange?: Function - onChangeMode?: Function - } = {} - if (newAttrs.onChange) { - defaultFunctionAttrs.onChange = onChange - } - if (newAttrs.onChangeMode) { - defaultFunctionAttrs.onChangeMode = onChangeMode - } - jsonEditor.value.updateProps( - Object.getOwnPropertyNames(defaultFunctionAttrs).length > 0 - ? conclude([newAttrs, defaultFunctionAttrs], { - type: Object, - mergeFunction, - }) - : newAttrs, - ) - }, - { - deep: true, - }, - ) - - expose?.({ jsonEditor }) - - onUnmounted(() => { - jsonEditor.value.destroy() - }) - - onMounted(() => { - jsonEditor.value = new JSONEditor({ - target: currentInstance?.$refs.jsonEditorRef as Element, - props: initialAttrs, - }) - - // There's no `expose` in @vue/composition-api - if (!expose) { - expose = (exposed: Record | undefined): void => { - for (const k in exposed) { - ;(currentInstance as any)[k] = unref(exposed[k]) - } - } - expose({ jsonEditor }) - } - }) - - return () => h('div', { ref: 'jsonEditorRef' }) - }, + name, + props: { + [modelValueProp]: {}, + mode: { + type: String as PropType, + }, + ...Object.fromEntries( + boolAttrs.map((boolAttr) => [ + boolAttr, + { + type: Boolean as PropType, + default: undefined, + }, + ]), + ), + } as { + [key in ModelValueProp]: {} + } & { mode: { type: PropType } } & { + [key in typeof boolAttrs[number]]: { + type: PropType + default: undefined + } + }, + emits: { + [updateModelValue](_payload: any) { + return true + }, + 'update:mode': function (_payload: Mode) { + return true + }, + }, + setup(props, { attrs, emit, expose }) { + const currentInstance = getCurrentInstance()?.proxy + const jsonEditor = ref() + + const preventUpdatingContent = ref(false) + const preventUpdatingModelValue = ref(false) + + const initialMode = conclude([props.mode, globalProps.mode], { + type: String as PropType, + }) + const initialValue = conclude([props[modelValueProp], globalProps[modelValueProp]]) + const initialBoolAttrs = Object.fromEntries( + Array.from(boolAttrs, (boolAttr) => [boolAttr, conclude([props[boolAttr], globalProps[boolAttr]])]).filter( + ([, v]) => v !== undefined, + ), + ) + + const onChange = debounce((updatedContent: Content) => { + if (preventUpdatingModelValue.value) { + preventUpdatingModelValue.value = false + return + } + preventUpdatingContent.value = true + emit( + updateModelValue, + (updatedContent as TextContent).text === undefined + ? (updatedContent as JSONContent).json + : (updatedContent as TextContent).text, + ) + }, 100) + + const onChangeMode = (mode: Mode) => { + emit('update:mode', mode) + } + + const mergeFunction = (previousValue: Function, currentValue: Function) => (...args: any) => { + previousValue(...args) + currentValue(...args) + } + + const initialAttrs = conclude( + [ + attrs, + globalAttrs, + { + // Both user input & setting value programmatically will trigger onChange + onChange, + onChangeMode, + mode: initialMode, + ...initialBoolAttrs, + ...(initialValue !== undefined && { + content: { + json: initialValue, + }, + }), + }, + ], + { + type: Object, + mergeFunction, + }, + ) + + watch( + () => props[modelValueProp], + (newModelValue: any) => { + if (preventUpdatingContent.value) { + preventUpdatingContent.value = false + return + } + preventUpdatingModelValue.value = true + // `jsonEditor.value` could be `undefined` in Vue 2.6 (dev environment) + jsonEditor.value?.update( + [undefined, ''].includes(newModelValue) + ? // `undefined` is not accepted by vanilla-jsoneditor + // The default value is `{ text: '' }` + // Only default value can clear the editor + { text: '' } + : { json: newModelValue }, + ) + }, + { + deep: true, + }, + ) + + watch( + () => props.mode, + (mode) => { + jsonEditor.value.updateProps({ + mode, + }) + }, + ) + + watch( + () => Array.from(boolAttrs, (boolAttr) => props[boolAttr]), + (values) => { + jsonEditor.value.updateProps( + Object.fromEntries(Array.from(values, (v, i) => [boolAttrs[i], v]).filter(([, v]) => v !== undefined)), + ) + }, + ) + + watch( + () => attrs, + (newAttrs) => { + // Functions need to be merged again + const defaultFunctionAttrs: { + onChange?: Function + onChangeMode?: Function + } = {} + if (newAttrs.onChange) { + defaultFunctionAttrs.onChange = onChange + } + if (newAttrs.onChangeMode) { + defaultFunctionAttrs.onChangeMode = onChangeMode + } + jsonEditor.value.updateProps( + Object.getOwnPropertyNames(defaultFunctionAttrs).length > 0 + ? conclude([newAttrs, defaultFunctionAttrs], { + type: Object, + mergeFunction, + }) + : newAttrs, + ) + }, + { + deep: true, + }, + ) + + expose?.({ jsonEditor }) + + onUnmounted(() => { + jsonEditor.value.destroy() + }) + + onMounted(() => { + jsonEditor.value = new JSONEditor({ + target: currentInstance?.$refs.jsonEditorRef as Element, + props: initialAttrs, + }) + + // There's no `expose` in @vue/composition-api + if (!expose) { + expose = (exposed: Record | undefined): void => { + for (const k in exposed) { + ;(currentInstance as any)[k] = unref(exposed[k]) + } + } + expose({ jsonEditor }) + } + }) + + return () => h('div', { ref: 'jsonEditorRef' }) + }, }) diff --git a/src/install.ts b/src/install.ts index 37d81f9..e801430 100644 --- a/src/install.ts +++ b/src/install.ts @@ -6,18 +6,18 @@ import type { Mode } from './Component' type SFCWithInstall = T & Plugin & { install: typeof install } const withInstall = >(main: T, extra?: E) => { - ;(main as SFCWithInstall).install = (app): void => { - for (const comp of [main, ...Object.values(extra ?? {})]) { - app?.component(comp.name, comp) - } - } + ;(main as SFCWithInstall).install = (app): void => { + for (const comp of [main, ...Object.values(extra ?? {})]) { + app?.component(comp.name, comp) + } + } - if (extra) { - for (const [key, comp] of Object.entries(extra)) { - ;(main as any)[key] = comp - } - } - return main as SFCWithInstall & E + if (extra) { + for (const [key, comp] of Object.entries(extra)) { + ;(main as any)[key] = comp + } + } + return main as SFCWithInstall & E } const globalProps: Record = {} @@ -26,10 +26,10 @@ const globalAttrs: Record = {} const ComponentWithInstall = withInstall(Component) ComponentWithInstall.install = (app: any, options = {}) => { - const { props, attrs } = resolveConfig(options, Component.props) - Object.assign(globalProps, props) - Object.assign(globalAttrs, attrs) - app.component(ComponentWithInstall.name, ComponentWithInstall) + const { props, attrs } = resolveConfig(options, Component.props) + Object.assign(globalProps, props) + Object.assign(globalAttrs, attrs) + app.component(ComponentWithInstall.name, ComponentWithInstall) } export { globalProps, globalAttrs, Mode }