@@ -12,6 +12,8 @@ import { resolveProperties } from "./pages/guided-mode/data/utils";
1212import { JSONSchemaInput } from "./JSONSchemaInput" ;
1313import { InspectorListItem } from "./preview/inspector/InspectorList" ;
1414
15+ const selfRequiredSymbol = Symbol ( ) ;
16+
1517const componentCSS = `
1618
1719 * {
@@ -153,7 +155,7 @@ export class JSONSchemaForm extends LitElement {
153155 } ;
154156 }
155157
156- # base = [ ] ;
158+ base = [ ] ;
157159 #nestedForms = { } ;
158160 tables = { } ;
159161 #nErrors = 0 ;
@@ -205,7 +207,7 @@ export class JSONSchemaForm extends LitElement {
205207
206208 if ( props . onStatusChange ) this . onStatusChange = props . onStatusChange ;
207209
208- if ( props . base ) this . # base = props . base ;
210+ if ( props . base ) this . base = props . base ;
209211 }
210212
211213 getTable = ( path ) => {
@@ -242,8 +244,8 @@ export class JSONSchemaForm extends LitElement {
242244 }
243245
244246 // Track resolved values for the form (data only)
245- updateData ( fullPath , value ) {
246- const path = [ ...fullPath ] ;
247+ updateData ( localPath , value ) {
248+ const path = [ ...localPath ] ;
247249 const name = path . pop ( ) ;
248250
249251 const reducer = ( acc , key ) => ( key in acc ? acc [ key ] : ( acc [ key ] = { } ) ) ; // NOTE: Create nested objects if required to set a new path
@@ -261,7 +263,7 @@ export class JSONSchemaForm extends LitElement {
261263 resolvedParent [ name ] = value ;
262264 }
263265
264- if ( hasUpdate ) this . onUpdate ( fullPath , value ) ; // Ensure the value has actually changed
266+ if ( hasUpdate ) this . onUpdate ( localPath , value ) ; // Ensure the value has actually changed
265267 }
266268
267269 #addMessage = ( name , message , type ) => {
@@ -271,9 +273,9 @@ export class JSONSchemaForm extends LitElement {
271273 container . appendChild ( item ) ;
272274 } ;
273275
274- #clearMessages = ( fullPath , type ) => {
275- if ( Array . isArray ( fullPath ) ) fullPath = fullPath . join ( "-" ) ; // Convert array to string
276- const container = this . shadowRoot . querySelector ( `#${ fullPath } .${ type } ` ) ;
276+ #clearMessages = ( localPath , type ) => {
277+ if ( Array . isArray ( localPath ) ) localPath = localPath . join ( "-" ) ; // Convert array to string
278+ const container = this . shadowRoot . querySelector ( `#${ localPath } .${ type } ` ) ;
277279
278280 if ( container ) {
279281 const nChildren = container . children . length ;
@@ -348,15 +350,15 @@ export class JSONSchemaForm extends LitElement {
348350 } ;
349351
350352 #get = ( path , object = this . resolved ) => {
351- // path = path.slice(this.# base.length); // Correct for base path
353+ // path = path.slice(this.base.length); // Correct for base path
352354 return path . reduce ( ( acc , curr ) => ( acc = acc [ curr ] ) , object ) ;
353355 } ;
354356
355- #checkRequiredAfterChange = async ( fullPath ) => {
356- const path = [ ...fullPath ] ;
357+ #checkRequiredAfterChange = async ( localPath ) => {
358+ const path = [ ...localPath ] ;
357359 const name = path . pop ( ) ;
358360 const element = this . shadowRoot
359- . querySelector ( `#${ fullPath . join ( "-" ) } ` )
361+ . querySelector ( `#${ localPath . join ( "-" ) } ` )
360362 . querySelector ( "jsonschema-input" )
361363 . getElement ( ) ;
362364 const isValid = await this . triggerValidation ( name , element , path , false ) ;
@@ -367,13 +369,13 @@ export class JSONSchemaForm extends LitElement {
367369 if ( typeof path === "string" ) path = path . split ( "." ) ;
368370
369371 // NOTE: Still must correct for the base here
370- if ( this . # base. length ) {
371- const base = this . # base. slice ( - 1 ) [ 0 ] ;
372+ if ( this . base . length ) {
373+ const base = this . base . slice ( - 1 ) [ 0 ] ;
372374 const indexOf = path . indexOf ( base ) ;
373375 if ( indexOf !== - 1 ) path = path . slice ( indexOf + 1 ) ;
374376 }
375377
376- const resolved = path . reduce ( ( acc , curr ) => ( acc = acc [ curr ] ) , schema ) ;
378+ const resolved = this . #get ( path , schema ) ;
377379 if ( resolved [ "$ref" ] ) return this . getSchema ( resolved [ "$ref" ] . split ( "/" ) . slice ( 1 ) ) ; // NOTE: This assumes reference to the root of the schema
378380
379381 return resolved ;
@@ -382,8 +384,8 @@ export class JSONSchemaForm extends LitElement {
382384 #renderInteractiveElement = ( name , info , required , path = [ ] ) => {
383385 let isRequired = required [ name ] ;
384386
385- const fullPath = [ ...path , name ] ;
386- const externalPath = [ ...this . # base, ...fullPath ] ;
387+ const localPath = [ ...path , name ] ;
388+ const externalPath = [ ...this . base , ...localPath ] ;
387389
388390 const resolved = this . #get( path , this . resolved ) ;
389391 const value = resolved [ name ] ;
@@ -392,11 +394,11 @@ export class JSONSchemaForm extends LitElement {
392394
393395 if ( isConditional && ! isRequired )
394396 isRequired = required [ name ] = async ( ) => {
395- const isRequiredAfterChange = await this . #checkRequiredAfterChange( fullPath ) ;
397+ const isRequiredAfterChange = await this . #checkRequiredAfterChange( localPath ) ;
396398 if ( isRequiredAfterChange ) {
397399 return true ;
398400 } else {
399- const linkResults = await this . #applyToLinkedProperties( this . #checkRequiredAfterChange, fullPath ) ; // Check links
401+ const linkResults = await this . #applyToLinkedProperties( this . #checkRequiredAfterChange, localPath ) ; // Check links
400402 if ( linkResults . includes ( true ) ) return true ;
401403 // Handle updates when no longer required
402404 else return false ;
@@ -405,7 +407,7 @@ export class JSONSchemaForm extends LitElement {
405407
406408 const interactiveInput = new JSONSchemaInput ( {
407409 info,
408- path : fullPath ,
410+ path : localPath ,
409411 value,
410412 form : this ,
411413 required : isRequired ,
@@ -425,7 +427,7 @@ export class JSONSchemaForm extends LitElement {
425427
426428 return html `
427429 < div
428- id =${ fullPath . join ( "-" ) }
430+ id =${ localPath . join ( "-" ) }
429431 class ="form-section ${ isRequired || isConditional ? "required" : "" } ${ isConditional
430432 ? "conditional"
431433 : "" } "
@@ -459,6 +461,10 @@ export class JSONSchemaForm extends LitElement {
459461
460462 for ( let name in requirements ) {
461463 let isRequired = requirements [ name ] ;
464+
465+ // // NOTE: Uncomment to block checking requirements inside optional properties
466+ // if (!requirements[name][selfRequiredSymbol] && !resolved[name]) continue; // Do not continue checking requirements if absent and not required
467+
462468 if ( typeof isRequired === "function" ) isRequired = await isRequired . call ( this . resolved ) ;
463469 if ( isRequired ) {
464470 let path = parentPath ? `${ parentPath } -${ name } ` : name ;
@@ -511,7 +517,7 @@ export class JSONSchemaForm extends LitElement {
511517 if ( this . ignore . includes ( key ) ) return false ;
512518 if ( this . showLevelOverride >= path . length ) return isRenderable ( key , value ) ;
513519 if ( required [ key ] ) return isRenderable ( key , value ) ;
514- if ( this . #getLink( [ ...this . # base, ...path , key ] ) ) return isRenderable ( key , value ) ;
520+ if ( this . #getLink( [ ...this . base , ...path , key ] ) ) return isRenderable ( key , value ) ;
515521 if ( ! this . onlyRequired ) return isRenderable ( key , value ) ;
516522 return false ;
517523 } )
@@ -549,7 +555,7 @@ export class JSONSchemaForm extends LitElement {
549555 #isLinkResolved = async ( pathArr ) => {
550556 return (
551557 await this . #applyToLinkedProperties( ( link ) => {
552- const isRequired = this . #isRequired( link . slice ( ( this . # base ?? [ ] ) . length ) ) ;
558+ const isRequired = this . #isRequired( link . slice ( ( this . base ?? [ ] ) . length ) ) ;
553559 if ( typeof isRequired === "function" ) return ! isRequired . call ( this . resolved ) ;
554560 else return ! isRequired ;
555561 } , pathArr )
@@ -558,8 +564,11 @@ export class JSONSchemaForm extends LitElement {
558564
559565 #isRequired = ( path ) => {
560566 if ( typeof path === "string" ) path = path . split ( "-" ) ;
561- // path = path.slice(this.#base.length); // Remove base path
562- return path . reduce ( ( obj , key ) => obj && obj [ key ] , this . #requirements) ;
567+ // path = path.slice(this.base.length); // Remove base path
568+ const res = path . reduce ( ( obj , key ) => obj && obj [ key ] , this . #requirements) ;
569+
570+ if ( typeof res === "object" ) res = res [ selfRequiredSymbol ] ;
571+ return res ;
563572 } ;
564573
565574 #getLinkElement = ( externalPath ) => {
@@ -572,15 +581,17 @@ export class JSONSchemaForm extends LitElement {
572581 triggerValidation = async ( name , element , path = [ ] , checkLinks = true ) => {
573582 const parent = this . #get( path , this . resolved ) ;
574583
584+ const pathToValidate = [ ...( this . base ?? [ ] ) , ...path ] ;
585+
575586 const valid =
576587 ! this . validateEmptyValues && ! ( name in parent )
577588 ? true
578- : await this . validateOnChange ( name , parent , [ ... ( this . #base ?? [ ] ) , ... path ] ) ;
589+ : await this . validateOnChange ( name , parent , pathToValidate ) ;
579590
580- const fullPath = [ ...path , name ] ; // Use basePath to augment the validation
581- const externalPath = [ ...this . # base, name ] ;
591+ const localPath = [ ...path , name ] ; // Use basePath to augment the validation
592+ const externalPath = [ ...this . base , name ] ;
582593
583- const isRequired = this . #isRequired( fullPath ) ;
594+ const isRequired = this . #isRequired( localPath ) ;
584595 let warnings = Array . isArray ( valid )
585596 ? valid . filter ( ( info ) => info . type === "warning" && ( ! isRequired || ! info . missing ) )
586597 : [ ] ;
@@ -599,7 +610,7 @@ export class JSONSchemaForm extends LitElement {
599610
600611 // Clear old errors and warnings on linked properties
601612 this . #applyToLinkedProperties( ( path ) => {
602- const internalPath = path . slice ( ( this . # base ?? [ ] ) . length ) ;
613+ const internalPath = path . slice ( ( this . base ?? [ ] ) . length ) ;
603614 this . #clearMessages( internalPath , "errors" ) ;
604615 this . #clearMessages( internalPath , "warnings" ) ;
605616 } , externalPath ) ;
@@ -613,9 +624,9 @@ export class JSONSchemaForm extends LitElement {
613624 }
614625
615626 // Clear old errors and warnings
616- this . #clearMessages( fullPath , "errors" ) ;
617- this . #clearMessages( fullPath , "warnings" ) ;
618- this . #clearMessages( fullPath , "info" ) ;
627+ this . #clearMessages( localPath , "errors" ) ;
628+ this . #clearMessages( localPath , "warnings" ) ;
629+ this . #clearMessages( localPath , "info" ) ;
619630
620631 const isFunction = typeof valid === "function" ;
621632 const isValid =
@@ -632,8 +643,8 @@ export class JSONSchemaForm extends LitElement {
632643 this . checkStatus ( ) ;
633644
634645 // Show aggregated errors and warnings (if any)
635- warnings . forEach ( ( info ) => this . #addMessage( fullPath , info , "warnings" ) ) ;
636- info . forEach ( ( info ) => this . #addMessage( fullPath , info , "info" ) ) ;
646+ warnings . forEach ( ( info ) => this . #addMessage( localPath , info , "warnings" ) ) ;
647+ info . forEach ( ( info ) => this . #addMessage( localPath , info , "info" ) ) ;
637648
638649 if ( isValid && errors . length === 0 ) {
639650 element . classList . remove ( "invalid" ) ;
@@ -643,7 +654,7 @@ export class JSONSchemaForm extends LitElement {
643654
644655 await this . #applyToLinkedProperties( ( path , element ) => {
645656 element . classList . remove ( "required" , "conditional" ) ; // Links manage their own error and validity states, but only one needs to be valid
646- } , fullPath ) ;
657+ } , localPath ) ;
647658
648659 if ( isFunction ) valid ( ) ; // Run if returned value is a function
649660
@@ -661,7 +672,7 @@ export class JSONSchemaForm extends LitElement {
661672 [ ...path , name ]
662673 ) ;
663674
664- errors . forEach ( ( info ) => this . #addMessage( fullPath , info , "errors" ) ) ;
675+ errors . forEach ( ( info ) => this . #addMessage( localPath , info , "errors" ) ) ;
665676 // element.title = errors.map((info) => info.message).join("\n"); // Set all errors to show on hover
666677
667678 return false ;
@@ -679,7 +690,7 @@ export class JSONSchemaForm extends LitElement {
679690 if ( renderable . length === 0 ) return html `< p > No properties to render</ p > ` ;
680691
681692 let renderableWithLinks = renderable . reduce ( ( acc , [ name , info ] ) => {
682- const externalPath = [ ...this . # base, ...path , name ] ;
693+ const externalPath = [ ...this . base , ...path , name ] ;
683694 const link = this . #getLink( externalPath ) ; // Use the base path to find a link
684695
685696 if ( link ) {
@@ -740,7 +751,7 @@ export class JSONSchemaForm extends LitElement {
740751 // Render linked properties
741752 if ( entry [ isLink ] ) {
742753 const linkedProperties = info . properties . map ( ( path ) => {
743- const pathCopy = [ ...path ] . slice ( ( this . # base ?? [ ] ) . length ) ;
754+ const pathCopy = [ ...path ] . slice ( ( this . base ?? [ ] ) . length ) ;
744755 const name = pathCopy . pop ( ) ;
745756 return this . #renderInteractiveElement( name , schema . properties [ name ] , required , pathCopy ) ;
746757 } ) ;
@@ -756,7 +767,7 @@ export class JSONSchemaForm extends LitElement {
756767
757768 const hasMany = renderable . length > 1 ; // How many siblings?
758769
759- const fullPath = [ ...path , name ] ;
770+ const localPath = [ ...path , name ] ;
760771
761772 if ( this . mode === "accordion" && hasMany ) {
762773 const headerName = header ( name ) ;
@@ -767,8 +778,10 @@ export class JSONSchemaForm extends LitElement {
767778 results : { ...results [ name ] } ,
768779 globals : this . globals ?. [ name ] ,
769780
781+ mode : this . mode ,
782+
770783 onUpdate : ( internalPath , value ) => {
771- const path = [ ...fullPath , ...internalPath ] ;
784+ const path = [ ...localPath , ...internalPath ] ;
772785 this . updateData ( path , value ) ;
773786 } ,
774787
@@ -793,13 +806,13 @@ export class JSONSchemaForm extends LitElement {
793806 this . checkAllLoaded ( ) ;
794807 } ,
795808 renderTable : ( ...args ) => this . renderTable ( ...args ) ,
796- base : fullPath ,
809+ base : [ ... this . base , ... localPath ] ,
797810 } ) ;
798811
799812 const accordion = new Accordion ( {
800813 sections : {
801814 [ headerName ] : {
802- subtitle : `${ this . #getRenderable( info , required [ name ] , fullPath , true ) . length } fields` ,
815+ subtitle : `${ this . #getRenderable( info , required [ name ] , localPath , true ) . length } fields` ,
803816 content : this . #nestedForms[ name ] ,
804817 } ,
805818 } ,
@@ -811,7 +824,7 @@ export class JSONSchemaForm extends LitElement {
811824 }
812825
813826 // Render properties in the sub-schema
814- const rendered = this . #render( info , results ?. [ name ] , required [ name ] , fullPath ) ;
827+ const rendered = this . #render( info , results ?. [ name ] , required [ name ] , localPath ) ;
815828 return hasMany || path . length > 1
816829 ? html `
817830 < div style ="margin-top: 40px; ">
@@ -834,7 +847,9 @@ export class JSONSchemaForm extends LitElement {
834847 Object . entries ( schema . properties ) . forEach ( ( [ key , value ] ) => {
835848 if ( value . properties ) {
836849 let nextAccumulator = acc [ key ] ;
837- if ( ! nextAccumulator || typeof nextAccumulator !== "object" ) nextAccumulator = acc [ key ] = { } ;
850+ const isNotObject = typeof nextAccumulator !== "object" ;
851+ if ( ! nextAccumulator || isNotObject )
852+ nextAccumulator = acc [ key ] = { [ selfRequiredSymbol ] : ! ! nextAccumulator } ;
838853 this . #registerRequirements( value , requirements [ key ] , nextAccumulator ) ;
839854 }
840855 } ) ;
0 commit comments