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' ;
29import { useScriptTag } from ' @vueuse/core' ;
10+ import type { ErrorObject } from ' ajv' ;
11+ import isPlainObject from ' lodash/isPlainObject' ;
312import {
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' ;
1426import { 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-
3928export 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 >
0 commit comments