Skip to content

Commit 5a7ae03

Browse files
committed
fix wrapper
1 parent 2b4f92c commit 5a7ae03

File tree

3 files changed

+149
-83
lines changed

3 files changed

+149
-83
lines changed
Lines changed: 119 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,73 @@
11
<script lang="ts">
2+
import type {
3+
JsonFormsUISchemaRegistryEntry,
4+
JsonSchema,
5+
UISchemaElement,
6+
ValidationMode,
7+
} from '@jsonforms/core';
8+
import type { MaybeReadonly } from '@jsonforms/vue';
29
import { useScriptTag } from '@vueuse/core';
10+
import type { ErrorObject } from 'ajv';
11+
import isPlainObject from 'lodash/isPlainObject';
312
import {
413
defineComponent,
514
h,
615
isRef,
716
nextTick,
17+
onBeforeUnmount,
818
ref,
919
toRaw,
1020
unref,
1121
useAttrs,
1222
watch,
23+
type PropType,
1324
} from 'vue';
25+
import type { VuetifyOptions } from 'vuetify';
1426
import { VProgressLinear } from 'vuetify/components';
1527
16-
const simpleProps = [
17-
'readonly',
18-
'validationMode',
19-
'locale',
20-
'dark',
21-
'rtl',
22-
'customStyle',
23-
'schemaUrl',
24-
];
25-
const complexProps = [
26-
'data',
27-
'schema',
28-
'uischema',
29-
'config',
30-
'uischemas',
31-
'translations',
32-
'additionalErrors',
33-
'uidata',
34-
'vuetifyOptions',
35-
'onChange',
36-
'onHandleAction',
37-
];
38-
3928
export default defineComponent({
4029
name: 'VuetifyJsonFormsWrapper',
4130
components: { VProgressLinear },
42-
setup(_, { slots }) {
43-
const attrs = useAttrs(); // collect all props/attrs
31+
emits: ['change', 'handle-action'],
32+
props: {
33+
data: { type: [String, Number, Boolean, Array, Object] as PropType<any> },
34+
schema: { type: [Object, Boolean] as PropType<JsonSchema> },
35+
uischema: { type: Object as PropType<UISchemaElement> },
36+
schemaUrl: { type: String },
37+
config: {
38+
type: Object as PropType<Record<string, unknown>>,
39+
validator: validateObj,
40+
},
41+
uischemas: {
42+
type: Array as PropType<MaybeReadonly<JsonFormsUISchemaRegistryEntry[]>>,
43+
},
44+
translations: {
45+
type: Object as PropType<Record<string, unknown>>,
46+
validator: validateObj,
47+
},
48+
additionalErrors: { type: Array as PropType<ErrorObject[]> },
49+
uidata: {
50+
type: Object as PropType<Record<string, unknown>>,
51+
validator: validateObj,
52+
},
53+
readonly: { type: Boolean, default: false },
54+
validationMode: {
55+
type: String as PropType<ValidationMode>,
56+
default: 'ValidateAndShow',
57+
validator: (v: string) =>
58+
['ValidateAndShow', 'ValidateAndHide', 'NoValidation'].includes(v),
59+
},
60+
locale: { type: String, default: 'en' },
61+
dark: { type: Boolean, default: undefined },
62+
rtl: { type: Boolean, default: false },
63+
vuetifyOptions: {
64+
type: Object as PropType<Partial<VuetifyOptions>>,
65+
validator: validateObj,
66+
},
67+
customStyle: { type: String },
68+
},
69+
setup(props, { slots, emit }) {
70+
const attrs = useAttrs();
4471
const loading = ref(true);
4572
const elRef = ref<HTMLElement | null>(null);
4673
@@ -50,52 +77,81 @@ export default defineComponent({
5077
return val;
5178
};
5279
53-
// Helper: assign props to the web component only if they differ
54-
const assignProps = (el: HTMLElement, props: Record<string, any>) => {
55-
Object.entries(props).forEach(([key, value]) => {
56-
const raw = normalize(value);
57-
if ((el as any)[key] !== raw) {
58-
(el as any)[key] = raw;
59-
}
60-
});
80+
const assignProp = (el: HTMLElement, key: string, value: any) => {
81+
const raw = normalize(value);
82+
if ((el as any)[key] !== raw) {
83+
(el as any)[key] = raw;
84+
}
85+
};
86+
87+
const assignAll = (el: HTMLElement, obj: Record<string, any>) => {
88+
Object.entries(obj).forEach(([k, v]) => assignProp(el, k, v));
6189
};
6290
63-
// Load the web component dynamically using useScriptTag
91+
const handleChange = (e: Event) => {
92+
const details = (e as CustomEvent).detail as any[];
93+
emit('change', details[0]);
94+
};
95+
const handleAction = (e: Event) => {
96+
const details = (e as CustomEvent).detail as any[];
97+
emit('handle-action', details[0]);
98+
};
99+
100+
// Load the web component
64101
useScriptTag(
65102
'./js/vuetify-json-forms.js',
66103
async () => {
67104
loading.value = false;
68105
69-
// Wait for DOM update to ensure the web component is mounted
70106
await nextTick();
71-
72107
if (elRef.value) {
73-
assignProps(elRef.value, attrs);
108+
assignAll(elRef.value, { ...attrs, ...props });
109+
110+
elRef.value.addEventListener('change', handleChange);
111+
elRef.value.addEventListener('handle-action', handleAction);
74112
}
75113
},
76114
{ type: 'module' },
77115
);
78116
79-
[...simpleProps, ...complexProps].forEach((propKey) => {
117+
// Watch each prop individually
118+
Object.keys(props).forEach((key) => {
80119
watch(
81-
() => (attrs as any)[propKey],
82-
(newVal) => {
83-
if (!elRef.value) return;
84-
85-
let raw = normalize(newVal);
86-
if (raw && complexProps.includes(propKey)) {
87-
if (Array.isArray(raw)) {
88-
raw = [...raw];
89-
} else if (typeof raw === 'object') {
90-
raw = { ...raw };
91-
}
120+
() => (props as any)[key],
121+
(val, oldVal) => {
122+
if (
123+
'data' === key &&
124+
JSON.stringify(val) === JSON.stringify(oldVal)
125+
) {
126+
return;
92127
}
93-
(elRef.value as any)[propKey] = raw;
128+
129+
let raw = normalize(val);
130+
if (raw != null && typeof raw === 'object') {
131+
raw = Array.isArray(raw) ? [...raw] : { ...raw };
132+
}
133+
134+
if (elRef.value) assignProp(elRef.value, key, raw);
94135
},
95136
{ deep: true },
96137
);
97138
});
98139
140+
// Watch attrs as a group (attrs is shallow, no individual keys)
141+
watch(
142+
() => ({ ...attrs }),
143+
(newAttrs) => {
144+
if (elRef.value) assignAll(elRef.value, newAttrs);
145+
},
146+
{ deep: true },
147+
);
148+
149+
onBeforeUnmount(() => {
150+
if (!elRef.value) return;
151+
elRef.value.removeEventListener('change', handleChange);
152+
elRef.value.removeEventListener('handle-action', handleAction);
153+
});
154+
99155
return () => {
100156
if (loading.value) {
101157
return h(VProgressLinear, {
@@ -107,14 +163,23 @@ export default defineComponent({
107163
108164
return h(
109165
'vuetify-json-forms',
110-
{ ref: elRef },
166+
{
167+
ref: elRef,
168+
},
111169
Object.keys(slots).length
112-
? Object.fromEntries(
113-
Object.entries(slots).map(([name, slotFn]) => [name, slotFn]),
114-
)
170+
? Object.fromEntries(Object.entries(slots))
115171
: undefined,
116172
);
117173
};
118174
},
119175
});
176+
177+
function validateObj(value: any) {
178+
try {
179+
const obj = typeof value === 'string' ? JSON.parse(value) : value;
180+
return obj == null || isPlainObject(obj);
181+
} catch {
182+
return false;
183+
}
184+
}
120185
</script>

packages/example/src/views/ExampleView.vue

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -144,24 +144,6 @@ const onHandleAction = (event: ActionEvent): void => {
144144
}
145145
};
146146
147-
const onWebComponentChange = (customEvent: CustomEvent): void => {
148-
const details = customEvent.detail as any[];
149-
if (details && details.length > 0) {
150-
const event: JsonFormsChangeEvent = details[0];
151-
152-
onChange(event);
153-
}
154-
};
155-
156-
const onWebComponentHandleAction = (customEvent: CustomEvent): void => {
157-
const details = customEvent.detail as any[];
158-
if (details && details.length > 0) {
159-
const event: ActionEvent = details[0];
160-
161-
onHandleAction(event);
162-
}
163-
};
164-
165147
const reloadMonacoSchema = () => {
166148
const example = find(
167149
appStore.examples,
@@ -541,12 +523,12 @@ const wrapperProps = computed(() => ({
541523
validationMode: state.validationMode,
542524
readonly: state.readonly,
543525
locale: state.i18n?.locale ?? 'en',
544-
rtl: String(appStore.rtl),
545-
dark: appStore.dark === undefined ? undefined : String(appStore.dark),
526+
rtl: appStore.rtl,
527+
dark: appStore.dark,
546528
translations: state.i18n?.translations,
547-
vuetifyOptions,
548-
onChange: onWebComponentChange,
549-
onHandleAction: onWebComponentHandleAction,
529+
vuetifyOptions: vuetifyOptions.value,
530+
onChange: onChange,
531+
onHandleAction: onHandleAction,
550532
}));
551533
</script>
552534

packages/jsonforms-vuetify-webcomponent/src/web-components/VuetifyJsonForms.ce.vue

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@
5757
</template>
5858

5959
<script lang="ts">
60-
import { isValidVuetifyOptions, type VuetifyOptions } from '@/plugins/options';
60+
import {
61+
defaultVuetifyOptions,
62+
isValidVuetifyOptions,
63+
type VuetifyOptions,
64+
} from '@/plugins/options';
6165
import buildVuetify from '@/plugins/vuetify';
6266
import { useAppStore } from '@/store';
6367
import {
@@ -107,6 +111,7 @@ import {
107111
VThemeProvider,
108112
} from 'vuetify/components';
109113
import { extractAndInjectFonts } from '../util/inject-fonts';
114+
import { createTheme } from 'vuetify/lib/composables/theme.mjs';
110115
111116
const DANGEROUS_TAGS = [
112117
'SCRIPT',
@@ -165,7 +170,7 @@ export default defineComponent({
165170
},
166171
emits: ['change', 'handle-action'],
167172
props: {
168-
data: { type: [Object, String, Number, Boolean, Array, null] as any },
173+
data: { type: [Object, String, Number, Array, null] as any }, // remove Boolean since it is a specially treated by the webcomponent
169174
schema: {
170175
type: [Object, String] as any,
171176
validator: (value: any) => {
@@ -391,16 +396,18 @@ export default defineComponent({
391396
return getLightDarkTheme(dark, defaultTheme, exists);
392397
});
393398
394-
const stylesheetId =
399+
const stylesheetId = computed(() =>
395400
typeof appStore.vuetifyOptions.theme === 'object'
396401
? (appStore.vuetifyOptions.theme.stylesheetId ??
397402
'vuetify-theme-stylesheet')
398-
: 'vuetify-theme-stylesheet';
403+
: 'vuetify-theme-stylesheet',
404+
);
399405
400-
const stylesheetNonce =
406+
const stylesheetNonce = computed(() =>
401407
typeof appStore.vuetifyOptions.theme === 'object'
402408
? appStore.vuetifyOptions.theme.cspNonce
403-
: undefined;
409+
: undefined,
410+
);
404411
405412
const customStyleToUse = computed(() => props.customStyle);
406413
const vuetifyThemeCss = computed(() => {
@@ -520,6 +527,18 @@ export default defineComponent({
520527
appStore.dark = false;
521528
}
522529
}
530+
531+
appStore.vuetifyOptions = {
532+
...defaultVuetifyOptions,
533+
...vuetifyOptions,
534+
};
535+
536+
if (vuetifyOptions.theme) {
537+
const newThemeInstance = createTheme(appStore.vuetifyOptions.theme);
538+
539+
themeInstance.themes.value = newThemeInstance.themes.value;
540+
themeInstance.global.name.value = theme.value;
541+
}
523542
},
524543
{ deep: true },
525544
);

0 commit comments

Comments
 (0)