Skip to content

Commit ce42184

Browse files
authored
feat: improve Vue Vuetify renderers
- Use Vuetify widgets for integer and number inputs - Fix oneOf renderer and enum array renderer - Pass slots in dispatch renderer Also updates the demo application.
1 parent d705e9d commit ce42184

26 files changed

+6081
-14454
lines changed

packages/core/src/util/resolvers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ const resolveSchemaWithSegments = (
127127
return undefined;
128128
}
129129

130-
if (schema.$ref) {
130+
// use typeof because schema can by of any type - check singleSegmentResolveSchema below
131+
if (typeof schema.$ref === 'string') {
131132
schema = resolveSchema(rootSchema, schema.$ref, rootSchema);
132133
}
133134

packages/vue-vuetify/dev/components/ExampleAppBar.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
2-
import { useAppStore } from '../store';
32
import JsonFormsLogo from '../assets/JsonFormsLogo.vue';
3+
import { useAppStore } from '../store';
44
import ThemeChanger from './ThemeChanger.vue';
55
66
const appStore = useAppStore();

packages/vue-vuetify/dev/components/ExampleDrawer.vue

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import { computed, ref } from 'vue';
23
import VuetifyLogo from '../assets/VuetifyLogo.vue';
34
import examples from '../examples';
45
import { useAppStore } from '../store';
@@ -8,6 +9,15 @@ const appStore = useAppStore();
89
const handleExampleClick = (exampleName: string) => {
910
appStore.exampleName = exampleName;
1011
};
12+
const search = ref(''); // Search term
13+
14+
const filteredExamples = computed(() => {
15+
return examples.filter(
16+
(example) =>
17+
example.name.toLowerCase().includes(search.value.toLowerCase()) ||
18+
example.label.toLowerCase().includes(search.value.toLowerCase()),
19+
);
20+
});
1121
</script>
1222

1323
<template>
@@ -26,8 +36,14 @@ const handleExampleClick = (exampleName: string) => {
2636
<v-divider></v-divider>
2737

2838
<v-list dense nav>
39+
<v-text-field
40+
v-model="search"
41+
append-inner-icon="mdi-magnify"
42+
density="compact"
43+
label="Search examples"
44+
/>
2945
<v-list-item
30-
v-for="example in examples"
46+
v-for="example in filteredExamples"
3147
:key="example.name"
3248
:value="example.name"
3349
link

packages/vue-vuetify/dev/components/ExampleForm.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from '@jsonforms/vue';
1717
import type { Ajv, ErrorObject } from 'ajv';
1818
import * as JsonRefs from 'json-refs';
19-
import { computed, onMounted, reactive, watch } from 'vue';
19+
import { computed, onMounted, shallowReactive, watch } from 'vue';
2020
2121
export type ResolvedSchema = {
2222
schema?: JsonSchema;
@@ -44,7 +44,7 @@ const props = defineProps<{
4444
state: JsonFormsProps;
4545
}>();
4646
47-
const resolvedSchema = reactive<ResolvedSchema>({
47+
const resolvedSchema = shallowReactive<ResolvedSchema>({
4848
schema: undefined,
4949
resolved: false,
5050
error: undefined,

packages/vue-vuetify/dev/components/ExampleSettings.vue

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { useAppStore } from '../store';
2+
import { appstoreLayouts, useAppStore, type AppstoreLayouts } from '../store';
33
44
const appStore = useAppStore();
55
@@ -53,6 +53,16 @@ const iconsets = [
5353
{ text: 'Material Design', value: 'mdi' },
5454
{ text: 'Font Awesome', value: 'fa' },
5555
];
56+
57+
const layoutMapping: Record<AppstoreLayouts, string> = {
58+
'': 'Default',
59+
'demo-and-data': 'Demo and Data',
60+
};
61+
62+
const layouts = appstoreLayouts.map((value: AppstoreLayouts) => ({
63+
text: layoutMapping[value] ?? value,
64+
value: value,
65+
}));
5666
</script>
5767

5868
<template>
@@ -174,6 +184,25 @@ const iconsets = [
174184

175185
<v-divider />
176186

187+
<v-container>
188+
<v-row><v-col>Demo Layout</v-col></v-row>
189+
<v-row>
190+
<v-col>
191+
<v-select
192+
outlined
193+
persistent-hint
194+
dense
195+
v-model="appStore.layout"
196+
:items="layouts"
197+
item-title="text"
198+
item-value="value"
199+
></v-select>
200+
</v-col>
201+
</v-row>
202+
</v-container>
203+
204+
<v-divider />
205+
177206
<v-container>
178207
<v-row><v-col>Blueprints (need browser reload)</v-col></v-row>
179208
<v-row>

packages/vue-vuetify/dev/store/index.ts

Lines changed: 87 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
import type { ValidationMode } from '@jsonforms/core';
2-
import { reactive, ref, watch } from 'vue';
2+
import { reactive, ref, watch, type Ref, type UnwrapRef } from 'vue';
3+
4+
export const appstoreLayouts = ['', 'demo-and-data'] as const;
5+
export type AppstoreLayouts = (typeof appstoreLayouts)[number];
36

47
const appstore = reactive({
58
exampleName: useHistoryHash(''),
69
rtl: false,
7-
formOnly: false,
10+
layout: useLocalStorage('vuetify-example-layout', ''),
11+
formOnly: useHistoryHashQuery('form-only', false as boolean),
12+
activeTab: useHistoryHashQuery('active-tab', 0 as number),
813
dark: useLocalStorage('vuetify-example-dark', false),
914
theme: useLocalStorage('vuetify-example-theme', 'light'),
10-
drawer: true,
15+
drawer: useHistoryHashQuery('drawer', true as boolean),
1116
settings: false,
1217
variant: useLocalStorage('vuetify-example-variant', ''),
1318
iconset: useLocalStorage('vuetify-example-iconset', 'mdi'),
1419
blueprint: useLocalStorage('vuetify-example-blueprint', 'md1'),
1520
jsonforms: {
16-
readonly: false,
21+
readonly: useHistoryHashQuery('read-only', false as boolean),
1722
validationMode: 'ValidateAndShow' as ValidationMode,
1823
config: {
1924
restrict: true,
@@ -26,7 +31,6 @@ const appstore = reactive({
2631
hideAvatar: false,
2732
hideArraySummaryValidation: false,
2833
enableFilterErrorsBeforeTouch: false,
29-
vuetify: {},
3034
},
3135
locale: useLocalStorage('vuetify-example-locale', 'en'),
3236
},
@@ -36,12 +40,13 @@ export const useAppStore = () => {
3640
return appstore;
3741
};
3842

39-
export function useHistoryHash(initialValue: string) {
43+
function useHistoryHash(initialValue: string) {
4044
const data = ref(initialValue);
4145

4246
// Function to update data based on URL hash
4347
const updateDataFromHash = () => {
44-
const hash = window.location.hash.slice(1);
48+
const hashAndQuery = window.location.hash.slice(1); // Remove the leading '#'
49+
const [hash, _] = hashAndQuery.split('?'); // Split hash and query string
4550
if (hash) {
4651
try {
4752
data.value = decodeURIComponent(hash);
@@ -51,17 +56,83 @@ export function useHistoryHash(initialValue: string) {
5156
}
5257
};
5358

54-
// Update data from URL hash on component mount
59+
// Initial update from URL hash
5560
updateDataFromHash();
5661

57-
watch(
58-
data,
59-
(newValue) => {
60-
const encodedData = encodeURIComponent(newValue);
61-
window.history.replaceState(null, '', `#${encodedData}`);
62-
},
63-
{ deep: true },
64-
);
62+
watch(data, (newValue) => {
63+
const encodedData = encodeURIComponent(newValue);
64+
65+
const currentHash = window.location.hash.slice(1);
66+
const [, currentQueryString] = currentHash.split('?'); // Extract the query part after ?
67+
68+
window.history.replaceState(
69+
null,
70+
'',
71+
`#${encodedData}${currentQueryString ? '?' + currentQueryString : ''}`, // Keep the query parameters intact
72+
);
73+
});
74+
75+
return data;
76+
}
77+
78+
function useHistoryHashQuery<T extends string | boolean | number>(
79+
queryParam: string,
80+
initialValue: T,
81+
) {
82+
const data: Ref<UnwrapRef<T>> = ref<T>(initialValue);
83+
84+
// Function to update data based on URL hash
85+
const updateDataFromHash = () => {
86+
const hashAndQuery = window.location.hash.slice(1); // Remove the leading '#'
87+
const [_, query] = hashAndQuery.split('?'); // Split hash and query string
88+
89+
const searchParams = new URLSearchParams(query);
90+
if (searchParams) {
91+
try {
92+
const value = searchParams.has(queryParam)
93+
? searchParams.get(queryParam)
94+
: `${initialValue}`;
95+
96+
// Convert the value based on the type of initialValue
97+
if (typeof initialValue === 'boolean') {
98+
// Handle boolean conversion
99+
data.value = (value === 'true') as UnwrapRef<T>;
100+
} else if (typeof initialValue === 'number') {
101+
data.value = (value ? parseFloat(value) : 0) as UnwrapRef<T>;
102+
} else if (typeof initialValue === 'string') {
103+
// Handle string conversion
104+
data.value = value as UnwrapRef<T>;
105+
}
106+
} catch (error) {
107+
console.error('Error parsing hash:', error);
108+
}
109+
}
110+
};
111+
112+
// Initial update from URL hash
113+
updateDataFromHash();
114+
115+
watch(data, (newValue) => {
116+
const encodedData = encodeURIComponent(newValue);
117+
118+
const hashAndQuery = window.location.hash.slice(1); // Remove the leading '#'
119+
const [hash, query] = hashAndQuery.split('?'); // Split hash and query string
120+
121+
const searchParams = new URLSearchParams(query);
122+
123+
if (newValue === initialValue) {
124+
// it is the default value so no need to preserve the query paramter
125+
searchParams.delete(queryParam);
126+
} else {
127+
searchParams.set(queryParam, encodedData);
128+
}
129+
130+
window.history.replaceState(
131+
null,
132+
'',
133+
`#${hash}${searchParams.size > 0 ? '?' + searchParams : ''}`, // Keep the query parameters intact
134+
);
135+
});
65136

66137
return data;
67138
}

0 commit comments

Comments
 (0)