diff --git a/packages/core/src/util/resolvers.ts b/packages/core/src/util/resolvers.ts index a415e252df..92880f6a23 100644 --- a/packages/core/src/util/resolvers.ts +++ b/packages/core/src/util/resolvers.ts @@ -127,7 +127,8 @@ const resolveSchemaWithSegments = ( return undefined; } - if (schema.$ref) { + // use typeof because schema can by of any type - check singleSegmentResolveSchema below + if (typeof schema.$ref === 'string') { schema = resolveSchema(rootSchema, schema.$ref, rootSchema); } diff --git a/packages/vue-vuetify/dev/components/ExampleAppBar.vue b/packages/vue-vuetify/dev/components/ExampleAppBar.vue index fe05ff7e1c..8cf4612c24 100644 --- a/packages/vue-vuetify/dev/components/ExampleAppBar.vue +++ b/packages/vue-vuetify/dev/components/ExampleAppBar.vue @@ -1,6 +1,6 @@ @@ -26,8 +36,14 @@ const handleExampleClick = (exampleName: string) => { + (); -const resolvedSchema = reactive({ +const resolvedSchema = shallowReactive({ schema: undefined, resolved: false, error: undefined, diff --git a/packages/vue-vuetify/dev/components/ExampleSettings.vue b/packages/vue-vuetify/dev/components/ExampleSettings.vue index 660cb81f4b..049a1bb356 100644 --- a/packages/vue-vuetify/dev/components/ExampleSettings.vue +++ b/packages/vue-vuetify/dev/components/ExampleSettings.vue @@ -1,5 +1,5 @@ @@ -174,6 +184,25 @@ const iconsets = [ + + Demo Layout + + + + + + + + + Blueprints (need browser reload) diff --git a/packages/vue-vuetify/dev/store/index.ts b/packages/vue-vuetify/dev/store/index.ts index cacfe6a4bd..5672e370f1 100644 --- a/packages/vue-vuetify/dev/store/index.ts +++ b/packages/vue-vuetify/dev/store/index.ts @@ -1,19 +1,24 @@ import type { ValidationMode } from '@jsonforms/core'; -import { reactive, ref, watch } from 'vue'; +import { reactive, ref, watch, type Ref, type UnwrapRef } from 'vue'; + +export const appstoreLayouts = ['', 'demo-and-data'] as const; +export type AppstoreLayouts = (typeof appstoreLayouts)[number]; const appstore = reactive({ exampleName: useHistoryHash(''), rtl: false, - formOnly: false, + layout: useLocalStorage('vuetify-example-layout', ''), + formOnly: useHistoryHashQuery('form-only', false as boolean), + activeTab: useHistoryHashQuery('active-tab', 0 as number), dark: useLocalStorage('vuetify-example-dark', false), theme: useLocalStorage('vuetify-example-theme', 'light'), - drawer: true, + drawer: useHistoryHashQuery('drawer', true as boolean), settings: false, variant: useLocalStorage('vuetify-example-variant', ''), iconset: useLocalStorage('vuetify-example-iconset', 'mdi'), blueprint: useLocalStorage('vuetify-example-blueprint', 'md1'), jsonforms: { - readonly: false, + readonly: useHistoryHashQuery('read-only', false as boolean), validationMode: 'ValidateAndShow' as ValidationMode, config: { restrict: true, @@ -26,7 +31,6 @@ const appstore = reactive({ hideAvatar: false, hideArraySummaryValidation: false, enableFilterErrorsBeforeTouch: false, - vuetify: {}, }, locale: useLocalStorage('vuetify-example-locale', 'en'), }, @@ -36,12 +40,13 @@ export const useAppStore = () => { return appstore; }; -export function useHistoryHash(initialValue: string) { +function useHistoryHash(initialValue: string) { const data = ref(initialValue); // Function to update data based on URL hash const updateDataFromHash = () => { - const hash = window.location.hash.slice(1); + const hashAndQuery = window.location.hash.slice(1); // Remove the leading '#' + const [hash, _] = hashAndQuery.split('?'); // Split hash and query string if (hash) { try { data.value = decodeURIComponent(hash); @@ -51,17 +56,83 @@ export function useHistoryHash(initialValue: string) { } }; - // Update data from URL hash on component mount + // Initial update from URL hash updateDataFromHash(); - watch( - data, - (newValue) => { - const encodedData = encodeURIComponent(newValue); - window.history.replaceState(null, '', `#${encodedData}`); - }, - { deep: true }, - ); + watch(data, (newValue) => { + const encodedData = encodeURIComponent(newValue); + + const currentHash = window.location.hash.slice(1); + const [, currentQueryString] = currentHash.split('?'); // Extract the query part after ? + + window.history.replaceState( + null, + '', + `#${encodedData}${currentQueryString ? '?' + currentQueryString : ''}`, // Keep the query parameters intact + ); + }); + + return data; +} + +function useHistoryHashQuery( + queryParam: string, + initialValue: T, +) { + const data: Ref> = ref(initialValue); + + // Function to update data based on URL hash + const updateDataFromHash = () => { + const hashAndQuery = window.location.hash.slice(1); // Remove the leading '#' + const [_, query] = hashAndQuery.split('?'); // Split hash and query string + + const searchParams = new URLSearchParams(query); + if (searchParams) { + try { + const value = searchParams.has(queryParam) + ? searchParams.get(queryParam) + : `${initialValue}`; + + // Convert the value based on the type of initialValue + if (typeof initialValue === 'boolean') { + // Handle boolean conversion + data.value = (value === 'true') as UnwrapRef; + } else if (typeof initialValue === 'number') { + data.value = (value ? parseFloat(value) : 0) as UnwrapRef; + } else if (typeof initialValue === 'string') { + // Handle string conversion + data.value = value as UnwrapRef; + } + } catch (error) { + console.error('Error parsing hash:', error); + } + } + }; + + // Initial update from URL hash + updateDataFromHash(); + + watch(data, (newValue) => { + const encodedData = encodeURIComponent(newValue); + + const hashAndQuery = window.location.hash.slice(1); // Remove the leading '#' + const [hash, query] = hashAndQuery.split('?'); // Split hash and query string + + const searchParams = new URLSearchParams(query); + + if (newValue === initialValue) { + // it is the default value so no need to preserve the query paramter + searchParams.delete(queryParam); + } else { + searchParams.set(queryParam, encodedData); + } + + window.history.replaceState( + null, + '', + `#${hash}${searchParams.size > 0 ? '?' + searchParams : ''}`, // Keep the query parameters intact + ); + }); return data; } diff --git a/packages/vue-vuetify/dev/views/ExampleView.vue b/packages/vue-vuetify/dev/views/ExampleView.vue index c93eb9420d..e4274ab3d1 100644 --- a/packages/vue-vuetify/dev/views/ExampleView.vue +++ b/packages/vue-vuetify/dev/views/ExampleView.vue @@ -8,8 +8,8 @@ import { markRaw, onMounted, provide, - reactive, ref, + shallowReactive, shallowRef, watch, type ShallowRef, @@ -29,7 +29,9 @@ import examples from '../examples'; import { useAppStore } from '../store'; import { createAjv } from '../validate'; -// dynamically import renderers so vite vue will not do tree shaking and removing the renderer functions from our components in production mode +import { Pane, Splitpanes } from 'splitpanes'; +import 'splitpanes/dist/splitpanes.css'; + const { extendedVuetifyRenderers } = await import('../../src'); const props = defineProps<{ @@ -45,7 +47,6 @@ const myStyles = mergeStyles(defaultStyles, { provide('styles', myStyles); const ajv = createAjv(); -const activeTab = ref(0); const errors = ref< ErrorObject, unknown>[] | undefined >(undefined); @@ -60,7 +61,6 @@ const uischemaModel = shallowRef( undefined, ); const dataModel = shallowRef(undefined); -//const i18nModel = shallowRef(undefined); const initialState = (exampleProp: ExampleDescription) => { const example = cloneDeep(exampleProp); @@ -83,14 +83,15 @@ const initialState = (exampleProp: ExampleDescription) => { middleware: undefined, }; }; -const state = reactive(initialState(props.example)); +const state = shallowReactive(initialState(props.example)); const onChange = (event: JsonFormsChangeEvent): void => { if (props.example.name) { dataModel.value = getMonacoModelForUri( monaco.Uri.parse(toDataUri(props.example.name)), - event.data ? JSON.stringify(event.data, null, 2) : '', + event.data !== undefined ? JSON.stringify(event.data, null, 2) : '', ); + state.data = event.data; } errors.value = event.errors; }; @@ -160,7 +161,7 @@ const reloadMonacoData = () => { if (example) { dataModel.value = getMonacoModelForUri( monaco.Uri.parse(toDataUri(example.name)), - example.data ? JSON.stringify(example.data, null, 2) : '', + example.data !== undefined ? JSON.stringify(example.data, null, 2) : '', ); toast('Original example data loaded. Apply it to take effect.'); } @@ -170,62 +171,12 @@ const saveMonacoData = () => { saveMonacoModel( dataModel, (modelValue) => { - if (state.schema?.type === 'number' || state.schema?.type === 'integer') { - try { - state.data = parseFloat(modelValue); - } catch { - // not able to convert the value - invalid data - state.data = modelValue; - } - } else if (state.schema?.type === 'boolean') { - state.data = modelValue == 'true'; - } else if ( - state.schema?.type === 'object' || - state.schema?.type === 'array' - ) { - try { - state.data = JSON.parse(modelValue); - } catch { - // not able to convert the value - invalid data - state.data = modelValue; - } - } else { - state.data = modelValue; - } + state.data = modelValue === '' ? undefined : JSON.parse(modelValue); }, 'New data applied', ); }; -// const reloadMonacoI18N = () => { -// const example = find( -// examples, -// (example) => example.name === appStore.exampleName, -// ); - -// if (example) { -// i18nModel.value = getMonacoModelForUri( -// monaco.Uri.parse(toI18NUri(example.name)), -// Array.isArray(example.data) || typeof example.data === 'object' -// ? JSON.stringify(example.data, null, 2) -// : `${example.data}`, -// ); -// toast('Original example i18n loaded. Apply it to take effect.'); -// } -// }; - -// const saveMonacoI18N = () => { -// saveMonacoModel( -// i18nModel, -// (modelValue) => -// (example.value = { -// ...example.value, -// i18n: JSON.parse(modelValue), -// } as ExampleDescription), -// 'New i18n applied', -// ); -// }; - const saveMonacoModel = ( model: ShallowRef, apply: (value: string) => void, @@ -276,15 +227,8 @@ const updateMonacoModels = (example: ExampleDescription) => { dataModel.value = getMonacoModelForUri( monaco.Uri.parse(toDataUri(example.name)), - Array.isArray(example.data) || typeof example.data === 'object' - ? JSON.stringify(example.data, null, 2) - : `${example.data}`, + example.data !== undefined ? JSON.stringify(example.data, null, 2) : '', ); - - // i18nModel.value = getMonacoModelForUri( - // monaco.Uri.parse(toI18NUri(example.name)), - // example.i18n ? JSON.stringify(example.i18n, null, 2) : '', - // ); }; const toSchemaUri = (id: string): string => { @@ -317,15 +261,6 @@ watch( }, ); -watch( - () => appStore.jsonforms, - (value) => { - // reset state when store jsonforms changes - Object.assign(state, initialState(props.example)); - }, - { deep: true }, -); - watch( () => appStore.formOnly, (value) => { @@ -354,9 +289,10 @@ const handleAction = (action: Action) => { {{ example.label }} - + Demo{{ appStore.layout == 'demo-and-data' ? 'Demo and Data' : 'Demo' + }} { Schema UI Schema - Data - + Data - + @@ -387,7 +324,67 @@ const handleAction = (action: Action) => { - + + + + + + Demo + + + + + + + + + + + + Data + + + + + $reload + + + {{ `Reload Example Data` }} + + + + + $save + + + {{ `Apply Change To Example Data` }} + + + + + + + + + + @@ -490,40 +487,6 @@ const handleAction = (action: Action) => { > - - - TODO - - - @@ -538,3 +501,28 @@ const handleAction = (action: Action) => { + diff --git a/packages/vue-vuetify/package.json b/packages/vue-vuetify/package.json index b44da41313..d857be825f 100644 --- a/packages/vue-vuetify/package.json +++ b/packages/vue-vuetify/package.json @@ -78,6 +78,7 @@ "@types/jsdom": "^21.1.6", "@types/lodash": "^4.14.172", "@types/node": "^20.12.5", + "@types/splitpanes": "^2.2.6", "@vitejs/plugin-vue": "^5.0.4", "@vitest/coverage-v8": "^1.6.0", "@vue/eslint-config-prettier": "^9.0.0", @@ -102,6 +103,7 @@ "resize-observer-polyfill": "^1.5.1", "rimraf": "^4.4.1", "rollup-plugin-postcss": "^4.0.2", + "splitpanes": "^3.1.5", "typedoc": "~0.25.3", "typescript": "~5.4.0", "vite": "^5.2.8", diff --git a/packages/vue-vuetify/src/complex/EnumArrayRenderer.vue b/packages/vue-vuetify/src/complex/EnumArrayRenderer.vue index 4faf9142ae..6edec0418c 100644 --- a/packages/vue-vuetify/src/complex/EnumArrayRenderer.vue +++ b/packages/vue-vuetify/src/complex/EnumArrayRenderer.vue @@ -1,47 +1,73 @@ - - - + + + toggle(o.value)" + @focus="handleFocus" + @blur="handleBlur" > - - - + + +