@uportal/form-builder 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,39 +3,91 @@
3
3
  * Copyright 2019 Google LLC
4
4
  * SPDX-License-Identifier: BSD-3-Clause
5
5
  */
6
- const t$2=globalThis,e$3=t$2.ShadowRoot&&(void 0===t$2.ShadyCSS||t$2.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s$2=Symbol(),o$4=new WeakMap;let n$3 = class n{constructor(t,e,o){if(this._$cssResult$=true,o!==s$2)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e;}get styleSheet(){let t=this.o;const s=this.t;if(e$3&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=o$4.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&o$4.set(s,t));}return t}toString(){return this.cssText}};const r$3=t=>new n$3("string"==typeof t?t:t+"",void 0,s$2),i$3=(t,...e)=>{const o=1===t.length?t[0]:e.reduce(((e,s,o)=>e+(t=>{if(true===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+t[o+1]),t[0]);return new n$3(o,t,s$2)},S$1=(s,o)=>{if(e$3)s.adoptedStyleSheets=o.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet));else for(const e of o){const o=document.createElement("style"),n=t$2.litNonce;void 0!==n&&o.setAttribute("nonce",n),o.textContent=e.cssText,s.appendChild(o);}},c$2=e$3?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return r$3(e)})(t):t;
6
+ const t$1=globalThis,e$2=t$1.ShadowRoot&&(void 0===t$1.ShadyCSS||t$1.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s$2=Symbol(),o$3=new WeakMap;let n$2 = class n{constructor(t,e,o){if(this._$cssResult$=true,o!==s$2)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e;}get styleSheet(){let t=this.o;const s=this.t;if(e$2&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=o$3.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&o$3.set(s,t));}return t}toString(){return this.cssText}};const r$2=t=>new n$2("string"==typeof t?t:t+"",void 0,s$2),i$3=(t,...e)=>{const o=1===t.length?t[0]:e.reduce(((e,s,o)=>e+(t=>{if(true===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+t[o+1]),t[0]);return new n$2(o,t,s$2)},S$1=(s,o)=>{if(e$2)s.adoptedStyleSheets=o.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet));else for(const e of o){const o=document.createElement("style"),n=t$1.litNonce;void 0!==n&&o.setAttribute("nonce",n),o.textContent=e.cssText,s.appendChild(o);}},c$2=e$2?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return r$2(e)})(t):t;
7
7
 
8
8
  /**
9
9
  * @license
10
10
  * Copyright 2017 Google LLC
11
11
  * SPDX-License-Identifier: BSD-3-Clause
12
- */const{is:i$2,defineProperty:e$2,getOwnPropertyDescriptor:h$1,getOwnPropertyNames:r$2,getOwnPropertySymbols:o$3,getPrototypeOf:n$2}=Object,a$1=globalThis,c$1=a$1.trustedTypes,l$1=c$1?c$1.emptyScript:"",p$1=a$1.reactiveElementPolyfillSupport,d$1=(t,s)=>t,u$1={toAttribute(t,s){switch(s){case Boolean:t=t?l$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,s){let i=t;switch(s){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t);}catch(t){i=null;}}return i}},f$1=(t,s)=>!i$2(t,s),b={attribute:true,type:String,converter:u$1,reflect:false,useDefault:false,hasChanged:f$1};Symbol.metadata??=Symbol("metadata"),a$1.litPropertyMetadata??=new WeakMap;let y$1 = class y extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t);}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,s=b){if(s.state&&(s.attribute=false),this._$Ei(),this.prototype.hasOwnProperty(t)&&((s=Object.create(s)).wrapped=true),this.elementProperties.set(t,s),!s.noAccessor){const i=Symbol(),h=this.getPropertyDescriptor(t,i,s);void 0!==h&&e$2(this.prototype,t,h);}}static getPropertyDescriptor(t,s,i){const{get:e,set:r}=h$1(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t;}};return {get:e,set(s){const h=e?.call(this);r?.call(this,s),this.requestUpdate(t,h,i);},configurable:true,enumerable:true}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(d$1("elementProperties")))return;const t=n$2(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties);}static finalize(){if(this.hasOwnProperty(d$1("finalized")))return;if(this.finalized=true,this._$Ei(),this.hasOwnProperty(d$1("properties"))){const t=this.properties,s=[...r$2(t),...o$3(t)];for(const i of s)this.createProperty(i,t[i]);}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,i]of s)this.elementProperties.set(t,i);}this._$Eh=new Map;for(const[t,s]of this.elementProperties){const i=this._$Eu(t,s);void 0!==i&&this._$Eh.set(i,t);}this.elementStyles=this.finalizeStyles(this.styles);}static finalizeStyles(s){const i=[];if(Array.isArray(s)){const e=new Set(s.flat(1/0).reverse());for(const s of e)i.unshift(c$2(s));}else void 0!==s&&i.push(c$2(s));return i}static _$Eu(t,s){const i=s.attribute;return false===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=false,this.hasUpdated=false,this._$Em=null,this._$Ev();}_$Ev(){this._$ES=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach((t=>t(this)));}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.();}removeController(t){this._$EO?.delete(t);}_$E_(){const t=new Map,s=this.constructor.elementProperties;for(const i of s.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t);}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return S$1(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(true),this._$EO?.forEach((t=>t.hostConnected?.()));}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach((t=>t.hostDisconnected?.()));}attributeChangedCallback(t,s,i){this._$AK(t,i);}_$ET(t,s){const i=this.constructor.elementProperties.get(t),e=this.constructor._$Eu(t,i);if(void 0!==e&&true===i.reflect){const h=(void 0!==i.converter?.toAttribute?i.converter:u$1).toAttribute(s,i.type);this._$Em=t,null==h?this.removeAttribute(e):this.setAttribute(e,h),this._$Em=null;}}_$AK(t,s){const i=this.constructor,e=i._$Eh.get(t);if(void 0!==e&&this._$Em!==e){const t=i.getPropertyOptions(e),h="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:u$1;this._$Em=e;const r=h.fromAttribute(s,t.type);this[e]=r??this._$Ej?.get(e)??r,this._$Em=null;}}requestUpdate(t,s,i){if(void 0!==t){const e=this.constructor,h=this[t];if(i??=e.getPropertyOptions(t),!((i.hasChanged??f$1)(h,s)||i.useDefault&&i.reflect&&h===this._$Ej?.get(t)&&!this.hasAttribute(e._$Eu(t,i))))return;this.C(t,s,i);} false===this.isUpdatePending&&(this._$ES=this._$EP());}C(t,s,{useDefault:i,reflect:e,wrapped:h},r){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,r??s??this[t]),true!==h||void 0!==r)||(this._$AL.has(t)||(this.hasUpdated||i||(s=void 0),this._$AL.set(t,s)),true===e&&this._$Em!==t&&(this._$Eq??=new Set).add(t));}async _$EP(){this.isUpdatePending=true;try{await this._$ES;}catch(t){Promise.reject(t);}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,s]of this._$Ep)this[t]=s;this._$Ep=void 0;}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,i]of t){const{wrapped:t}=i,e=this[s];true!==t||this._$AL.has(s)||void 0===e||this.C(s,void 0,i,e);}}let t=false;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$EO?.forEach((t=>t.hostUpdate?.())),this.update(s)):this._$EM();}catch(s){throw t=false,this._$EM(),s}t&&this._$AE(s);}willUpdate(t){}_$AE(t){this._$EO?.forEach((t=>t.hostUpdated?.())),this.hasUpdated||(this.hasUpdated=true,this.firstUpdated(t)),this.updated(t);}_$EM(){this._$AL=new Map,this.isUpdatePending=false;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return true}update(t){this._$Eq&&=this._$Eq.forEach((t=>this._$ET(t,this[t]))),this._$EM();}updated(t){}firstUpdated(t){}};y$1.elementStyles=[],y$1.shadowRootOptions={mode:"open"},y$1[d$1("elementProperties")]=new Map,y$1[d$1("finalized")]=new Map,p$1?.({ReactiveElement:y$1}),(a$1.reactiveElementVersions??=[]).push("2.1.1");
12
+ */const{is:i$2,defineProperty:e$1,getOwnPropertyDescriptor:h$1,getOwnPropertyNames:r$1,getOwnPropertySymbols:o$2,getPrototypeOf:n$1}=Object,a$1=globalThis,c$1=a$1.trustedTypes,l$1=c$1?c$1.emptyScript:"",p$1=a$1.reactiveElementPolyfillSupport,d$1=(t,s)=>t,u$1={toAttribute(t,s){switch(s){case Boolean:t=t?l$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,s){let i=t;switch(s){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t);}catch(t){i=null;}}return i}},f$1=(t,s)=>!i$2(t,s),b={attribute:true,type:String,converter:u$1,reflect:false,useDefault:false,hasChanged:f$1};Symbol.metadata??=Symbol("metadata"),a$1.litPropertyMetadata??=new WeakMap;let y$1 = class y extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t);}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,s=b){if(s.state&&(s.attribute=false),this._$Ei(),this.prototype.hasOwnProperty(t)&&((s=Object.create(s)).wrapped=true),this.elementProperties.set(t,s),!s.noAccessor){const i=Symbol(),h=this.getPropertyDescriptor(t,i,s);void 0!==h&&e$1(this.prototype,t,h);}}static getPropertyDescriptor(t,s,i){const{get:e,set:r}=h$1(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t;}};return {get:e,set(s){const h=e?.call(this);r?.call(this,s),this.requestUpdate(t,h,i);},configurable:true,enumerable:true}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(d$1("elementProperties")))return;const t=n$1(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties);}static finalize(){if(this.hasOwnProperty(d$1("finalized")))return;if(this.finalized=true,this._$Ei(),this.hasOwnProperty(d$1("properties"))){const t=this.properties,s=[...r$1(t),...o$2(t)];for(const i of s)this.createProperty(i,t[i]);}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,i]of s)this.elementProperties.set(t,i);}this._$Eh=new Map;for(const[t,s]of this.elementProperties){const i=this._$Eu(t,s);void 0!==i&&this._$Eh.set(i,t);}this.elementStyles=this.finalizeStyles(this.styles);}static finalizeStyles(s){const i=[];if(Array.isArray(s)){const e=new Set(s.flat(1/0).reverse());for(const s of e)i.unshift(c$2(s));}else void 0!==s&&i.push(c$2(s));return i}static _$Eu(t,s){const i=s.attribute;return false===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=false,this.hasUpdated=false,this._$Em=null,this._$Ev();}_$Ev(){this._$ES=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach((t=>t(this)));}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.();}removeController(t){this._$EO?.delete(t);}_$E_(){const t=new Map,s=this.constructor.elementProperties;for(const i of s.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t);}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return S$1(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(true),this._$EO?.forEach((t=>t.hostConnected?.()));}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach((t=>t.hostDisconnected?.()));}attributeChangedCallback(t,s,i){this._$AK(t,i);}_$ET(t,s){const i=this.constructor.elementProperties.get(t),e=this.constructor._$Eu(t,i);if(void 0!==e&&true===i.reflect){const h=(void 0!==i.converter?.toAttribute?i.converter:u$1).toAttribute(s,i.type);this._$Em=t,null==h?this.removeAttribute(e):this.setAttribute(e,h),this._$Em=null;}}_$AK(t,s){const i=this.constructor,e=i._$Eh.get(t);if(void 0!==e&&this._$Em!==e){const t=i.getPropertyOptions(e),h="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:u$1;this._$Em=e;const r=h.fromAttribute(s,t.type);this[e]=r??this._$Ej?.get(e)??r,this._$Em=null;}}requestUpdate(t,s,i){if(void 0!==t){const e=this.constructor,h=this[t];if(i??=e.getPropertyOptions(t),!((i.hasChanged??f$1)(h,s)||i.useDefault&&i.reflect&&h===this._$Ej?.get(t)&&!this.hasAttribute(e._$Eu(t,i))))return;this.C(t,s,i);} false===this.isUpdatePending&&(this._$ES=this._$EP());}C(t,s,{useDefault:i,reflect:e,wrapped:h},r){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,r??s??this[t]),true!==h||void 0!==r)||(this._$AL.has(t)||(this.hasUpdated||i||(s=void 0),this._$AL.set(t,s)),true===e&&this._$Em!==t&&(this._$Eq??=new Set).add(t));}async _$EP(){this.isUpdatePending=true;try{await this._$ES;}catch(t){Promise.reject(t);}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,s]of this._$Ep)this[t]=s;this._$Ep=void 0;}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,i]of t){const{wrapped:t}=i,e=this[s];true!==t||this._$AL.has(s)||void 0===e||this.C(s,void 0,i,e);}}let t=false;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$EO?.forEach((t=>t.hostUpdate?.())),this.update(s)):this._$EM();}catch(s){throw t=false,this._$EM(),s}t&&this._$AE(s);}willUpdate(t){}_$AE(t){this._$EO?.forEach((t=>t.hostUpdated?.())),this.hasUpdated||(this.hasUpdated=true,this.firstUpdated(t)),this.updated(t);}_$EM(){this._$AL=new Map,this.isUpdatePending=false;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return true}update(t){this._$Eq&&=this._$Eq.forEach((t=>this._$ET(t,this[t]))),this._$EM();}updated(t){}firstUpdated(t){}};y$1.elementStyles=[],y$1.shadowRootOptions={mode:"open"},y$1[d$1("elementProperties")]=new Map,y$1[d$1("finalized")]=new Map,p$1?.({ReactiveElement:y$1}),(a$1.reactiveElementVersions??=[]).push("2.1.1");
13
13
 
14
14
  /**
15
15
  * @license
16
16
  * Copyright 2017 Google LLC
17
17
  * SPDX-License-Identifier: BSD-3-Clause
18
18
  */
19
- const t$1=globalThis,i$1=t$1.trustedTypes,s$1=i$1?i$1.createPolicy("lit-html",{createHTML:t=>t}):void 0,e$1="$lit$",h=`lit$${Math.random().toFixed(9).slice(2)}$`,o$2="?"+h,n$1=`<${o$2}>`,r$1=document,l=()=>r$1.createComment(""),c=t=>null===t||"object"!=typeof t&&"function"!=typeof t,a=Array.isArray,u=t=>a(t)||"function"==typeof t?.[Symbol.iterator],d="[ \t\n\f\r]",f=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,v=/-->/g,_=/>/g,m=RegExp(`>|${d}(?:([^\\s"'>=/]+)(${d}*=${d}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),p=/'/g,g=/"/g,$=/^(?:script|style|textarea|title)$/i,y=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),x=y(1),T=Symbol.for("lit-noChange"),E=Symbol.for("lit-nothing"),A=new WeakMap,C=r$1.createTreeWalker(r$1,129);function P(t,i){if(!a(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==s$1?s$1.createHTML(i):i}const V=(t,i)=>{const s=t.length-1,o=[];let r,l=2===i?"<svg>":3===i?"<math>":"",c=f;for(let i=0;i<s;i++){const s=t[i];let a,u,d=-1,y=0;for(;y<s.length&&(c.lastIndex=y,u=c.exec(s),null!==u);)y=c.lastIndex,c===f?"!--"===u[1]?c=v:void 0!==u[1]?c=_:void 0!==u[2]?($.test(u[2])&&(r=RegExp("</"+u[2],"g")),c=m):void 0!==u[3]&&(c=m):c===m?">"===u[0]?(c=r??f,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?m:'"'===u[3]?g:p):c===g||c===p?c=m:c===v||c===_?c=f:(c=m,r=void 0);const x=c===m&&t[i+1].startsWith("/>")?" ":"";l+=c===f?s+n$1:d>=0?(o.push(a),s.slice(0,d)+e$1+s.slice(d)+h+x):s+h+(-2===d?i:x);}return [P(t,l+(t[s]||"<?>")+(2===i?"</svg>":3===i?"</math>":"")),o]};class N{constructor({strings:t,_$litType$:s},n){let r;this.parts=[];let c=0,a=0;const u=t.length-1,d=this.parts,[f,v]=V(t,s);if(this.el=N.createElement(f,n),C.currentNode=this.el.content,2===s||3===s){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes);}for(;null!==(r=C.nextNode())&&d.length<u;){if(1===r.nodeType){if(r.hasAttributes())for(const t of r.getAttributeNames())if(t.endsWith(e$1)){const i=v[a++],s=r.getAttribute(t).split(h),e=/([.?@])?(.*)/.exec(i);d.push({type:1,index:c,name:e[2],strings:s,ctor:"."===e[1]?H:"?"===e[1]?I:"@"===e[1]?L:k}),r.removeAttribute(t);}else t.startsWith(h)&&(d.push({type:6,index:c}),r.removeAttribute(t));if($.test(r.tagName)){const t=r.textContent.split(h),s=t.length-1;if(s>0){r.textContent=i$1?i$1.emptyScript:"";for(let i=0;i<s;i++)r.append(t[i],l()),C.nextNode(),d.push({type:2,index:++c});r.append(t[s],l());}}}else if(8===r.nodeType)if(r.data===o$2)d.push({type:2,index:c});else {let t=-1;for(;-1!==(t=r.data.indexOf(h,t+1));)d.push({type:7,index:c}),t+=h.length-1;}c++;}}static createElement(t,i){const s=r$1.createElement("template");return s.innerHTML=t,s}}function S(t,i,s=t,e){if(i===T)return i;let h=void 0!==e?s._$Co?.[e]:s._$Cl;const o=c(i)?void 0:i._$litDirective$;return h?.constructor!==o&&(h?._$AO?.(false),void 0===o?h=void 0:(h=new o(t),h._$AT(t,s,e)),void 0!==e?(s._$Co??=[])[e]=h:s._$Cl=h),void 0!==h&&(i=S(t,h._$AS(t,i.values),h,e)),i}class M{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:i},parts:s}=this._$AD,e=(t?.creationScope??r$1).importNode(i,true);C.currentNode=e;let h=C.nextNode(),o=0,n=0,l=s[0];for(;void 0!==l;){if(o===l.index){let i;2===l.type?i=new R(h,h.nextSibling,this,t):1===l.type?i=new l.ctor(h,l.name,l.strings,this,t):6===l.type&&(i=new z(h,this,t)),this._$AV.push(i),l=s[++n];}o!==l?.index&&(h=C.nextNode(),o++);}return C.currentNode=r$1,e}p(t){let i=0;for(const s of this._$AV) void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++;}}class R{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,i,s,e){this.type=2,this._$AH=E,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cv=e?.isConnected??true;}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===t?.nodeType&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=S(this,t,i),c(t)?t===E||null==t||""===t?(this._$AH!==E&&this._$AR(),this._$AH=E):t!==this._$AH&&t!==T&&this._(t):void 0!==t._$litType$?this.$(t):void 0!==t.nodeType?this.T(t):u(t)?this.k(t):this._(t);}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t));}_(t){this._$AH!==E&&c(this._$AH)?this._$AA.nextSibling.data=t:this.T(r$1.createTextNode(t)),this._$AH=t;}$(t){const{values:i,_$litType$:s}=t,e="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=N.createElement(P(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===e)this._$AH.p(i);else {const t=new M(e,this),s=t.u(this.options);t.p(i),this.T(s),this._$AH=t;}}_$AC(t){let i=A.get(t.strings);return void 0===i&&A.set(t.strings,i=new N(t)),i}k(t){a(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const h of t)e===i.length?i.push(s=new R(this.O(l()),this.O(l()),this,this.options)):s=i[e],s._$AI(h),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e);}_$AR(t=this._$AA.nextSibling,i){for(this._$AP?.(false,true,i);t!==this._$AB;){const i=t.nextSibling;t.remove(),t=i;}}setConnected(t){ void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t));}}class k{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,i,s,e,h){this.type=1,this._$AH=E,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=h,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=E;}_$AI(t,i=this,s,e){const h=this.strings;let o=false;if(void 0===h)t=S(this,t,i,0),o=!c(t)||t!==this._$AH&&t!==T,o&&(this._$AH=t);else {const e=t;let n,r;for(t=h[0],n=0;n<h.length-1;n++)r=S(this,e[s+n],i,n),r===T&&(r=this._$AH[n]),o||=!c(r)||r!==this._$AH[n],r===E?t=E:t!==E&&(t+=(r??"")+h[n+1]),this._$AH[n]=r;}o&&!e&&this.j(t);}j(t){t===E?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"");}}class H extends k{constructor(){super(...arguments),this.type=3;}j(t){this.element[this.name]=t===E?void 0:t;}}class I extends k{constructor(){super(...arguments),this.type=4;}j(t){this.element.toggleAttribute(this.name,!!t&&t!==E);}}class L extends k{constructor(t,i,s,e,h){super(t,i,s,e,h),this.type=5;}_$AI(t,i=this){if((t=S(this,t,i,0)??E)===T)return;const s=this._$AH,e=t===E&&s!==E||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,h=t!==E&&(s===E||e);e&&this.element.removeEventListener(this.name,this,s),h&&this.element.addEventListener(this.name,this,t),this._$AH=t;}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t);}}class z{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s;}get _$AU(){return this._$AM._$AU}_$AI(t){S(this,t);}}const j=t$1.litHtmlPolyfillSupport;j?.(N,R),(t$1.litHtmlVersions??=[]).push("3.3.1");const B=(t,i,s)=>{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new R(i.insertBefore(l(),t),t,void 0,s??{});}return h._$AI(t),h};
19
+ const t=globalThis,i$1=t.trustedTypes,s$1=i$1?i$1.createPolicy("lit-html",{createHTML:t=>t}):void 0,e="$lit$",h=`lit$${Math.random().toFixed(9).slice(2)}$`,o$1="?"+h,n=`<${o$1}>`,r=document,l=()=>r.createComment(""),c=t=>null===t||"object"!=typeof t&&"function"!=typeof t,a=Array.isArray,u=t=>a(t)||"function"==typeof t?.[Symbol.iterator],d="[ \t\n\f\r]",f=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,v=/-->/g,_=/>/g,m=RegExp(`>|${d}(?:([^\\s"'>=/]+)(${d}*=${d}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),p=/'/g,g=/"/g,$=/^(?:script|style|textarea|title)$/i,y=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),x=y(1),T=Symbol.for("lit-noChange"),E=Symbol.for("lit-nothing"),A=new WeakMap,C=r.createTreeWalker(r,129);function P(t,i){if(!a(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==s$1?s$1.createHTML(i):i}const V=(t,i)=>{const s=t.length-1,o=[];let r,l=2===i?"<svg>":3===i?"<math>":"",c=f;for(let i=0;i<s;i++){const s=t[i];let a,u,d=-1,y=0;for(;y<s.length&&(c.lastIndex=y,u=c.exec(s),null!==u);)y=c.lastIndex,c===f?"!--"===u[1]?c=v:void 0!==u[1]?c=_:void 0!==u[2]?($.test(u[2])&&(r=RegExp("</"+u[2],"g")),c=m):void 0!==u[3]&&(c=m):c===m?">"===u[0]?(c=r??f,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?m:'"'===u[3]?g:p):c===g||c===p?c=m:c===v||c===_?c=f:(c=m,r=void 0);const x=c===m&&t[i+1].startsWith("/>")?" ":"";l+=c===f?s+n:d>=0?(o.push(a),s.slice(0,d)+e+s.slice(d)+h+x):s+h+(-2===d?i:x);}return [P(t,l+(t[s]||"<?>")+(2===i?"</svg>":3===i?"</math>":"")),o]};class N{constructor({strings:t,_$litType$:s},n){let r;this.parts=[];let c=0,a=0;const u=t.length-1,d=this.parts,[f,v]=V(t,s);if(this.el=N.createElement(f,n),C.currentNode=this.el.content,2===s||3===s){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes);}for(;null!==(r=C.nextNode())&&d.length<u;){if(1===r.nodeType){if(r.hasAttributes())for(const t of r.getAttributeNames())if(t.endsWith(e)){const i=v[a++],s=r.getAttribute(t).split(h),e=/([.?@])?(.*)/.exec(i);d.push({type:1,index:c,name:e[2],strings:s,ctor:"."===e[1]?H:"?"===e[1]?I:"@"===e[1]?L:k}),r.removeAttribute(t);}else t.startsWith(h)&&(d.push({type:6,index:c}),r.removeAttribute(t));if($.test(r.tagName)){const t=r.textContent.split(h),s=t.length-1;if(s>0){r.textContent=i$1?i$1.emptyScript:"";for(let i=0;i<s;i++)r.append(t[i],l()),C.nextNode(),d.push({type:2,index:++c});r.append(t[s],l());}}}else if(8===r.nodeType)if(r.data===o$1)d.push({type:2,index:c});else {let t=-1;for(;-1!==(t=r.data.indexOf(h,t+1));)d.push({type:7,index:c}),t+=h.length-1;}c++;}}static createElement(t,i){const s=r.createElement("template");return s.innerHTML=t,s}}function S(t,i,s=t,e){if(i===T)return i;let h=void 0!==e?s._$Co?.[e]:s._$Cl;const o=c(i)?void 0:i._$litDirective$;return h?.constructor!==o&&(h?._$AO?.(false),void 0===o?h=void 0:(h=new o(t),h._$AT(t,s,e)),void 0!==e?(s._$Co??=[])[e]=h:s._$Cl=h),void 0!==h&&(i=S(t,h._$AS(t,i.values),h,e)),i}class M{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:i},parts:s}=this._$AD,e=(t?.creationScope??r).importNode(i,true);C.currentNode=e;let h=C.nextNode(),o=0,n=0,l=s[0];for(;void 0!==l;){if(o===l.index){let i;2===l.type?i=new R(h,h.nextSibling,this,t):1===l.type?i=new l.ctor(h,l.name,l.strings,this,t):6===l.type&&(i=new z(h,this,t)),this._$AV.push(i),l=s[++n];}o!==l?.index&&(h=C.nextNode(),o++);}return C.currentNode=r,e}p(t){let i=0;for(const s of this._$AV) void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++;}}class R{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,i,s,e){this.type=2,this._$AH=E,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cv=e?.isConnected??true;}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===t?.nodeType&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=S(this,t,i),c(t)?t===E||null==t||""===t?(this._$AH!==E&&this._$AR(),this._$AH=E):t!==this._$AH&&t!==T&&this._(t):void 0!==t._$litType$?this.$(t):void 0!==t.nodeType?this.T(t):u(t)?this.k(t):this._(t);}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t));}_(t){this._$AH!==E&&c(this._$AH)?this._$AA.nextSibling.data=t:this.T(r.createTextNode(t)),this._$AH=t;}$(t){const{values:i,_$litType$:s}=t,e="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=N.createElement(P(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===e)this._$AH.p(i);else {const t=new M(e,this),s=t.u(this.options);t.p(i),this.T(s),this._$AH=t;}}_$AC(t){let i=A.get(t.strings);return void 0===i&&A.set(t.strings,i=new N(t)),i}k(t){a(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const h of t)e===i.length?i.push(s=new R(this.O(l()),this.O(l()),this,this.options)):s=i[e],s._$AI(h),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e);}_$AR(t=this._$AA.nextSibling,i){for(this._$AP?.(false,true,i);t!==this._$AB;){const i=t.nextSibling;t.remove(),t=i;}}setConnected(t){ void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t));}}class k{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,i,s,e,h){this.type=1,this._$AH=E,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=h,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=E;}_$AI(t,i=this,s,e){const h=this.strings;let o=false;if(void 0===h)t=S(this,t,i,0),o=!c(t)||t!==this._$AH&&t!==T,o&&(this._$AH=t);else {const e=t;let n,r;for(t=h[0],n=0;n<h.length-1;n++)r=S(this,e[s+n],i,n),r===T&&(r=this._$AH[n]),o||=!c(r)||r!==this._$AH[n],r===E?t=E:t!==E&&(t+=(r??"")+h[n+1]),this._$AH[n]=r;}o&&!e&&this.j(t);}j(t){t===E?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"");}}class H extends k{constructor(){super(...arguments),this.type=3;}j(t){this.element[this.name]=t===E?void 0:t;}}class I extends k{constructor(){super(...arguments),this.type=4;}j(t){this.element.toggleAttribute(this.name,!!t&&t!==E);}}class L extends k{constructor(t,i,s,e,h){super(t,i,s,e,h),this.type=5;}_$AI(t,i=this){if((t=S(this,t,i,0)??E)===T)return;const s=this._$AH,e=t===E&&s!==E||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,h=t!==E&&(s===E||e);e&&this.element.removeEventListener(this.name,this,s),h&&this.element.addEventListener(this.name,this,t),this._$AH=t;}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t);}}class z{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s;}get _$AU(){return this._$AM._$AU}_$AI(t){S(this,t);}}const j=t.litHtmlPolyfillSupport;j?.(N,R),(t.litHtmlVersions??=[]).push("3.3.1");const B=(t,i,s)=>{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new R(i.insertBefore(l(),t),t,void 0,s??{});}return h._$AI(t),h};
20
20
 
21
21
  /**
22
22
  * @license
23
23
  * Copyright 2017 Google LLC
24
24
  * SPDX-License-Identifier: BSD-3-Clause
25
- */const s=globalThis;class i extends y$1{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const r=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=B(r,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(true);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(false);}render(){return T}}i._$litElement$=true,i["finalized"]=true,s.litElementHydrateSupport?.({LitElement:i});const o$1=s.litElementPolyfillSupport;o$1?.({LitElement:i});(s.litElementVersions??=[]).push("4.2.1");
25
+ */const s=globalThis;class i extends y$1{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const r=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=B(r,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(true);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(false);}render(){return T}}i._$litElement$=true,i["finalized"]=true,s.litElementHydrateSupport?.({LitElement:i});const o=s.litElementPolyfillSupport;o?.({LitElement:i});(s.litElementVersions??=[]).push("4.2.1");
26
26
 
27
- function e(e){this.message=e;}e.prototype=new Error,e.prototype.name="InvalidCharacterError";var r="undefined"!=typeof window&&window.atob&&window.atob.bind(window)||function(r){var t=String(r).replace(/=+$/,"");if(t.length%4==1)throw new e("'atob' failed: The string to be decoded is not correctly encoded.");for(var n,o,a=0,i=0,c="";o=t.charAt(i++);~o&&(n=a%4?64*n+o:o,a++%4)?c+=String.fromCharCode(255&n>>(-2*a&6)):0)o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(o);return c};function t(e){var t=e.replace(/-/g,"+").replace(/_/g,"/");switch(t.length%4){case 0:break;case 2:t+="==";break;case 3:t+="=";break;default:throw "Illegal base64url string!"}try{return function(e){return decodeURIComponent(r(e).replace(/(.)/g,(function(e,r){var t=r.charCodeAt(0).toString(16).toUpperCase();return t.length<2&&(t="0"+t),"%"+t})))}(t)}catch(e){return r(t)}}function n(e){this.message=e;}function o(e,r){if("string"!=typeof e)throw new n("Invalid token specified");var o=true===(r=r||{}).header?0:1;try{return JSON.parse(t(e.split(".")[o]))}catch(e){throw new n("Invalid token specified: "+e.message)}}n.prototype=new Error,n.prototype.name="InvalidTokenError";
28
-
29
- function delay(ms) {
30
- return new Promise(resolve => setTimeout(resolve, ms));
27
+ class InvalidTokenError extends Error {
28
+ }
29
+ InvalidTokenError.prototype.name = "InvalidTokenError";
30
+ function b64DecodeUnicode(str) {
31
+ return decodeURIComponent(atob(str).replace(/(.)/g, (m, p) => {
32
+ let code = p.charCodeAt(0).toString(16).toUpperCase();
33
+ if (code.length < 2) {
34
+ code = "0" + code;
35
+ }
36
+ return "%" + code;
37
+ }));
38
+ }
39
+ function base64UrlDecode(str) {
40
+ let output = str.replace(/-/g, "+").replace(/_/g, "/");
41
+ switch (output.length % 4) {
42
+ case 0:
43
+ break;
44
+ case 2:
45
+ output += "==";
46
+ break;
47
+ case 3:
48
+ output += "=";
49
+ break;
50
+ default:
51
+ throw new Error("base64 string is not of the correct length");
52
+ }
53
+ try {
54
+ return b64DecodeUnicode(output);
55
+ }
56
+ catch (err) {
57
+ return atob(output);
58
+ }
59
+ }
60
+ function jwtDecode(token, options) {
61
+ if (typeof token !== "string") {
62
+ throw new InvalidTokenError("Invalid token specified: must be a string");
63
+ }
64
+ options || (options = {});
65
+ const pos = options.header === true ? 0 : 1;
66
+ const part = token.split(".")[pos];
67
+ if (typeof part !== "string") {
68
+ throw new InvalidTokenError(`Invalid token specified: missing part #${pos + 1}`);
69
+ }
70
+ let decoded;
71
+ try {
72
+ decoded = base64UrlDecode(part);
73
+ }
74
+ catch (e) {
75
+ throw new InvalidTokenError(`Invalid token specified: invalid base64 for part #${pos + 1} (${e.message})`);
76
+ }
77
+ try {
78
+ return JSON.parse(decoded);
79
+ }
80
+ catch (e) {
81
+ throw new InvalidTokenError(`Invalid token specified: invalid json for part #${pos + 1} (${e.message})`);
82
+ }
31
83
  }
32
84
 
33
85
  /**
34
86
  * Dynamic Form Builder Web Component
35
87
  * Fetches JSON schema and form data, then renders a dynamic form
36
- *
88
+ *
37
89
  * @element form-builder
38
- *
90
+ *
39
91
  * @attr {string} fbms-base-url - Base URL of the form builder microservice
40
92
  * @attr {string} fbms-form-fname - Form name to fetch
41
93
  * @attr {string} oidc-url - OpenID Connect URL for authentication
@@ -50,7 +102,7 @@ class FormBuilder extends i {
50
102
 
51
103
  // Internal state
52
104
  schema: { type: Object, state: true },
53
- formData: { type: Object, state: true },
105
+ _formData: { type: Object, state: true },
54
106
  uiSchema: { type: Object, state: true },
55
107
  fbmsFormVersion: { type: String, state: true },
56
108
  loading: { type: Boolean, state: true },
@@ -58,12 +110,20 @@ class FormBuilder extends i {
58
110
  error: { type: String, state: true },
59
111
  token: { type: String, state: true },
60
112
  decoded: { type: Object, state: true },
113
+ submitSuccess: { type: Boolean, state: true },
114
+ validationFailed: { type: Boolean, state: true },
115
+ initialFormData: { type: Object, state: true },
116
+ hasChanges: { type: Boolean, state: true },
117
+ submissionStatus: { type: Object, state: true },
118
+ formCompleted: { type: Boolean, state: true },
119
+ submissionError: { type: String, state: true },
61
120
  };
62
121
 
63
122
  static styles = i$3`
64
123
  :host {
65
124
  display: block;
66
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
125
+ font-family:
126
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
67
127
  }
68
128
 
69
129
  .container {
@@ -96,7 +156,27 @@ class FormBuilder extends i {
96
156
  .form-group {
97
157
  display: flex;
98
158
  flex-direction: column;
99
- gap: 8px;
159
+ gap: 4px;
160
+ margin: 14px 0px;
161
+ }
162
+
163
+ .nested-object {
164
+ margin-left: 20px;
165
+ padding-left: 20px;
166
+ border-left: 2px solid #e0e0e0;
167
+ margin-top: 10px;
168
+ }
169
+
170
+ .nested-object-title {
171
+ font-weight: 600;
172
+ color: #333;
173
+ margin-bottom: 10px;
174
+ }
175
+
176
+ .nested-object-description {
177
+ font-size: 0.875rem;
178
+ color: #666;
179
+ margin-bottom: 15px;
100
180
  }
101
181
 
102
182
  label {
@@ -115,11 +195,11 @@ class FormBuilder extends i {
115
195
  margin-top: 4px;
116
196
  }
117
197
 
118
- input[type="text"],
119
- input[type="email"],
120
- input[type="number"],
121
- input[type="date"],
122
- input[type="tel"],
198
+ input[type='text'],
199
+ input[type='email'],
200
+ input[type='number'],
201
+ input[type='date'],
202
+ input[type='tel'],
123
203
  textarea,
124
204
  select {
125
205
  padding: 8px 12px;
@@ -139,16 +219,40 @@ class FormBuilder extends i {
139
219
  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);
140
220
  }
141
221
 
222
+ select[multiple] {
223
+ min-height: 120px;
224
+ padding: 4px;
225
+ }
226
+
227
+ select[multiple] option {
228
+ padding: 4px 8px;
229
+ }
230
+
142
231
  textarea {
143
232
  min-height: 100px;
144
233
  resize: vertical;
145
234
  }
146
235
 
147
- input[type="checkbox"],
148
- input[type="radio"] {
236
+ input[type='checkbox'],
237
+ input[type='radio'] {
149
238
  margin-right: 8px;
150
239
  }
151
240
 
241
+ fieldset {
242
+ border: none;
243
+ padding: 0;
244
+ margin: 0;
245
+ min-width: 0; /* Fix for some browsers */
246
+ }
247
+
248
+ legend {
249
+ font-weight: 700;
250
+ color: #333;
251
+ padding: 0;
252
+ margin-bottom: 8px;
253
+ font-size: 1rem;
254
+ }
255
+
152
256
  .checkbox-group,
153
257
  .radio-group {
154
258
  display: flex;
@@ -162,6 +266,18 @@ class FormBuilder extends i {
162
266
  align-items: center;
163
267
  }
164
268
 
269
+ .checkbox-group.inline,
270
+ .radio-group.inline {
271
+ flex-direction: row;
272
+ flex-wrap: wrap;
273
+ gap: 16px;
274
+ }
275
+
276
+ .checkbox-group.inline .checkbox-item,
277
+ .radio-group.inline .radio-item {
278
+ margin-right: 0;
279
+ }
280
+
165
281
  .error-message {
166
282
  color: #c00;
167
283
  font-size: 0.875rem;
@@ -184,21 +300,21 @@ class FormBuilder extends i {
184
300
  transition: background-color 0.2s;
185
301
  }
186
302
 
187
- button[type="submit"] {
303
+ button[type='submit'] {
188
304
  background-color: #0066cc;
189
305
  color: white;
190
306
  }
191
307
 
192
- button[type="submit"]:hover {
308
+ button[type='submit']:hover {
193
309
  background-color: #0052a3;
194
310
  }
195
311
 
196
- button[type="button"] {
312
+ button[type='button'] {
197
313
  background-color: #c0c0c0;
198
314
  color: #333;
199
315
  }
200
316
 
201
- button[type="button"]:hover {
317
+ button[type='button']:hover {
202
318
  background-color: #e0e0e0;
203
319
  }
204
320
 
@@ -220,7 +336,9 @@ class FormBuilder extends i {
220
336
  }
221
337
 
222
338
  @keyframes spin {
223
- to { transform: rotate(360deg); }
339
+ to {
340
+ transform: rotate(360deg);
341
+ }
224
342
  }
225
343
 
226
344
  .button-content {
@@ -228,20 +346,117 @@ class FormBuilder extends i {
228
346
  align-items: center;
229
347
  justify-content: center;
230
348
  }
349
+
350
+ .status-message {
351
+ padding: 12px 16px;
352
+ border-radius: 4px;
353
+ margin-bottom: 20px;
354
+ font-weight: 500;
355
+ }
356
+
357
+ .status-message.success {
358
+ background-color: #d4edda;
359
+ border: 1px solid #c3e6cb;
360
+ color: #155724;
361
+ }
362
+
363
+ .status-message.validation-error {
364
+ background-color: #fff3cd;
365
+ border: 1px solid #ffeaa7;
366
+ color: #856404;
367
+ }
368
+
369
+ .status-message.error {
370
+ background-color: #f8d7da;
371
+ border: 1px solid #f5c6cb;
372
+ color: #721c24;
373
+ }
374
+
375
+ .status-message ul {
376
+ margin: 8px 0 0 0;
377
+ padding-left: 20px;
378
+ }
379
+
380
+ .status-message li {
381
+ margin: 4px 0;
382
+ }
383
+
384
+ form.submitting input,
385
+ form.submitting textarea,
386
+ form.submitting select,
387
+ form.submitting button:not([type='submit']) {
388
+ opacity: 0.6;
389
+ pointer-events: none;
390
+ cursor: not-allowed;
391
+ }
392
+
393
+ .info-only {
394
+ padding: 20px 0;
395
+ }
396
+
397
+ .info-only p {
398
+ line-height: 1.6;
399
+ color: #333;
400
+ }
401
+
402
+ .info-label {
403
+ font-weight: 500;
404
+ color: #333;
405
+ display: block;
406
+ }
231
407
  `;
232
408
 
409
+ // Getter and setter for formData
410
+ get formData() {
411
+ return this._formData;
412
+ }
413
+
414
+ set formData(value) {
415
+ const oldValue = this._formData;
416
+ this._formData = value;
417
+ this.requestUpdate('formData', oldValue);
418
+ this.updateStateFlags();
419
+ }
420
+
421
+ /**
422
+ * Get custom error message from schema if available
423
+ * Follows the pattern: schema.properties.fieldName.messages.ruleName
424
+ * For nested fields: schema.properties.parent.properties.child.messages.ruleName
425
+ * Returns null if field, messages, or rule doesn't exist
426
+ */
427
+ getCustomErrorMessage(fieldPath, ruleName) {
428
+ const pathParts = fieldPath.split('.');
429
+ let current = this.schema;
430
+
431
+ // Navigate to the field schema
432
+ for (const part of pathParts) {
433
+ current = current?.properties?.[part];
434
+ if (!current) return null;
435
+ }
436
+
437
+ // Check for custom message
438
+ return current?.messages?.[ruleName] ?? null;
439
+ }
440
+
233
441
  constructor() {
234
442
  super();
235
443
  this.loading = true;
236
444
  this.submitting = false;
237
445
  this.error = null;
238
446
  this.schema = null;
239
- this.formData = {};
447
+ this._formData = {};
240
448
  this.uiSchema = null;
241
449
  this.fbmsFormVersion = null;
242
450
  this.token = null;
243
- this.decoded = {sub: 'unknown'};
451
+ this.decoded = { sub: 'unknown' };
244
452
  this.fieldErrors = {};
453
+ this.submitSuccess = false;
454
+ this.validationFailed = false;
455
+ this.initialFormData = {};
456
+ this.hasChanges = false;
457
+ this.submissionStatus = null;
458
+ this.formCompleted = false;
459
+ this.submissionError = null;
245
460
  }
246
461
 
247
462
  async connectedCallback() {
@@ -260,10 +475,7 @@ class FormBuilder extends i {
260
475
  }
261
476
 
262
477
  // Fetch form schema and data
263
- await Promise.all([
264
- this.fetchSchema(),
265
- this.fetchFormData(),
266
- ]);
478
+ await Promise.all([this.fetchSchema(), this.fetchFormData()]);
267
479
 
268
480
  this.loading = false;
269
481
  } catch (err) {
@@ -272,6 +484,80 @@ class FormBuilder extends i {
272
484
  }
273
485
  }
274
486
 
487
+ /**
488
+ * Deep clone an object, handling Dates and other types
489
+ * Uses structuredClone if available, otherwise falls back to manual recursive
490
+ */
491
+ deepClone(obj) {
492
+ if (obj === null || obj === undefined) return obj;
493
+
494
+ // Use structuredClone if available (modern browsers)
495
+ if (typeof structuredClone === 'function') {
496
+ try {
497
+ return structuredClone(obj);
498
+ } catch (err) {
499
+ console.warn('structuredClone failed, falling back to manual clone:', err);
500
+ }
501
+ }
502
+
503
+ // Fallback: manual deep clone handling common types
504
+ if (obj instanceof Date) {
505
+ return new Date(obj.getTime());
506
+ }
507
+
508
+ if (Array.isArray(obj)) {
509
+ return obj.map((item) => this.deepClone(item));
510
+ }
511
+
512
+ if (typeof obj === 'object') {
513
+ const cloned = {};
514
+ for (const key in obj) {
515
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
516
+ cloned[key] = this.deepClone(obj[key]);
517
+ }
518
+ }
519
+ return cloned;
520
+ }
521
+
522
+ // Primitives
523
+ return obj;
524
+ }
525
+
526
+ /**
527
+ * Deep equality check, handling Dates and other types
528
+ */
529
+ deepEqual(obj1, obj2) {
530
+ if (obj1 === obj2) return true;
531
+
532
+ if (obj1 === null || obj2 === null) return false;
533
+ if (obj1 === undefined || obj2 === undefined) return false;
534
+
535
+ if (obj1 instanceof Date && obj2 instanceof Date) {
536
+ return obj1.getTime() === obj2.getTime();
537
+ }
538
+
539
+ // If only one is a Date, they are not equal
540
+ if (obj1 instanceof Date || obj2 instanceof Date) {
541
+ return false;
542
+ }
543
+
544
+ if (Array.isArray(obj1) && Array.isArray(obj2)) {
545
+ if (obj1.length !== obj2.length) return false;
546
+ return obj1.every((item, index) => this.deepEqual(item, obj2[index]));
547
+ }
548
+
549
+ if (typeof obj1 === 'object' && typeof obj2 === 'object') {
550
+ const keys1 = Object.keys(obj1);
551
+ const keys2 = Object.keys(obj2);
552
+
553
+ if (keys1.length !== keys2.length) return false;
554
+
555
+ return keys1.every((key) => this.deepEqual(obj1[key], obj2[key]));
556
+ }
557
+
558
+ return false;
559
+ }
560
+
275
561
  async fetchToken() {
276
562
  try {
277
563
  const response = await fetch(this.oidcUrl, {
@@ -284,7 +570,13 @@ class FormBuilder extends i {
284
570
 
285
571
  const data = await response.text();
286
572
  this.token = data;
287
- this.decoded = o(this.token);
573
+ try {
574
+ this.decoded = jwtDecode(this.token);
575
+ } catch (_err) {
576
+ // Only need this to get the name, so warn
577
+ console.warn('Security Token failed to decode -- setting user to unknown');
578
+ this.decoded = { sub: 'unknown' };
579
+ }
288
580
  } catch (err) {
289
581
  console.error('Token fetch error:', err);
290
582
  throw new Error('Authentication failed');
@@ -334,66 +626,250 @@ class FormBuilder extends i {
334
626
 
335
627
  if (response.ok) {
336
628
  const payload = await response.json();
337
- this.formData = payload.answers;
629
+ this._formData = payload?.answers ?? {}; // Use private property
630
+ this.initialFormData = this.deepClone(this._formData); // Use deepClone
338
631
  } else {
339
- // It's OK if there's no existing data
340
- this.formData = {};
632
+ this._formData = {};
633
+ this.initialFormData = {};
341
634
  }
635
+ this.hasChanges = false;
636
+ this.requestUpdate();
342
637
  } catch (err) {
343
638
  // Non-critical error
344
639
  console.warn('Could not fetch form data:', err);
345
- this.formData = {};
640
+ this._formData = {};
641
+ this.initialFormData = {};
642
+ this.hasChanges = false;
643
+ this.requestUpdate();
346
644
  }
347
645
  }
348
646
 
349
- handleInputChange(fieldName, event) {
350
- const { type, value, checked } = event.target;
647
+ updateStateFlags() {
648
+ // Clear status messages when user makes changes
649
+ this.submitSuccess = false;
650
+ this.validationFailed = false;
651
+ this.submissionError = null;
351
652
 
352
- this.formData = {
353
- ...this.formData,
354
- [fieldName]: type === 'checkbox' ? checked : value,
355
- };
653
+ // Check if form data has changed from initial state
654
+ this.hasChanges = !this.deepEqual(this.formData, this.initialFormData);
655
+ }
656
+
657
+ /**
658
+ * Get nested value from formData using dot notation path
659
+ * e.g., "contact_information.email" => formData.contact_information.email
660
+ */
661
+ getNestedValue(path) {
662
+ if (!path || typeof path !== 'string') return undefined;
663
+
664
+ const parts = path.split('.').filter((part) => part.length > 0);
665
+ if (parts.length === 0) return undefined;
666
+
667
+ let value = this.formData;
668
+ for (const part of parts) {
669
+ value = value?.[part];
670
+ }
671
+ return value;
672
+ }
673
+
674
+ /**
675
+ * Sanitize a string for use as an HTML ID
676
+ * Replaces spaces and special characters with hyphens and collapses consecutive hyphens
677
+ * Ensures the ID starts with a letter by adding a prefix if necessary
678
+ */
679
+ sanitizeId(str) {
680
+ if (typeof str !== 'string') {
681
+ str = String(str ?? '');
682
+ }
683
+
684
+ // Replace invalid characters and collapse multiple hyphens
685
+ let sanitized = str.replace(/[^a-zA-Z0-9-_.]/g, '-').replace(/-+/g, '-');
686
+
687
+ // Trim leading/trailing hyphens that may have been introduced
688
+ sanitized = sanitized.replace(/^-+/, '').replace(/-+$/, '');
689
+
690
+ // Ensure we have some content
691
+ if (!sanitized) {
692
+ sanitized = 'id';
693
+ }
694
+
695
+ // Ensure the ID starts with a letter
696
+ if (!/^[A-Za-z]/.test(sanitized)) {
697
+ sanitized = 'id-' + sanitized;
698
+ }
699
+
700
+ return sanitized;
701
+ }
702
+
703
+ /**
704
+ * Set nested value in formData using dot notation path
705
+ */
706
+ setNestedValue(path, value) {
707
+ if (!path || typeof path !== 'string') return;
708
+
709
+ const parts = path.split('.').filter((part) => part.length > 0);
710
+ if (parts.length === 0) return;
711
+
712
+ const newData = { ...this.formData };
713
+ let current = newData;
714
+
715
+ for (let i = 0; i < parts.length - 1; i++) {
716
+ const part = parts[i];
717
+ const existing = current[part];
718
+ // Note: Arrays are not currently supported in schemas, but we preserve them
719
+ // to maintain data integrity. Setting properties on arrays may produce unexpected results.
720
+ if (Array.isArray(existing)) {
721
+ current[part] = [...existing];
722
+ } else if (!existing || typeof existing !== 'object') {
723
+ current[part] = {};
724
+ } else {
725
+ current[part] = { ...existing };
726
+ }
727
+ current = current[part];
728
+ }
729
+
730
+ current[parts[parts.length - 1]] = value;
731
+ this.formData = newData;
732
+ }
733
+
734
+ /**
735
+ * Get the schema object at a given path
736
+ * e.g., "contact_information" => schema.properties.contact_information
737
+ */
738
+ getSchemaAtPath(path) {
739
+ if (!path) return this.schema; // Handle empty string/null/undefined
740
+
741
+ const parts = path.split('.').filter((part) => part.length > 0);
742
+ if (parts.length === 0) return this.schema; // All segments were empty
743
+
744
+ let schema = this.schema;
745
+
746
+ for (const part of parts) {
747
+ schema = schema.properties?.[part];
748
+ if (!schema) return null;
749
+ }
750
+
751
+ return schema;
752
+ }
753
+
754
+ handleInputChange(fieldPath, event) {
755
+ const { type, value, checked } = event.target;
756
+ this.setNestedValue(fieldPath, type === 'checkbox' ? checked : value);
356
757
 
357
758
  // Clear field error on change
358
- if (this.fieldErrors[fieldName]) {
759
+ if (this.fieldErrors[fieldPath]) {
359
760
  this.fieldErrors = { ...this.fieldErrors };
360
- delete this.fieldErrors[fieldName];
761
+ delete this.fieldErrors[fieldPath];
361
762
  }
763
+
764
+ this.updateStateFlags();
362
765
  }
363
766
 
364
- handleArrayChange(fieldName, index, event) {
365
- const currentArray = this.formData[fieldName] || [];
767
+ handleArrayChange(fieldPath, index, event) {
768
+ const currentArray = this.getNestedValue(fieldPath) || [];
366
769
  const newArray = [...currentArray];
367
770
  newArray[index] = event.target.value;
771
+ this.setNestedValue(fieldPath, newArray);
368
772
 
369
- this.formData = {
370
- ...this.formData,
371
- [fieldName]: newArray,
372
- };
773
+ this.updateStateFlags();
373
774
  }
374
775
 
375
- validateForm() {
776
+ handleMultiSelectChange(fieldPath, event) {
777
+ const selectedOptions = Array.from(event.target.selectedOptions);
778
+ const values = selectedOptions.map((option) => option.value);
779
+
780
+ this.setNestedValue(fieldPath, values);
781
+
782
+ // Clear field error on change
783
+ if (this.fieldErrors[fieldPath]) {
784
+ this.fieldErrors = { ...this.fieldErrors };
785
+ delete this.fieldErrors[fieldPath];
786
+ }
787
+
788
+ this.updateStateFlags();
789
+ }
790
+
791
+ handleCheckboxArrayChange(fieldPath, optionValue, event) {
792
+ const { checked } = event.target;
793
+ const currentArray = this.getNestedValue(fieldPath) || [];
794
+
795
+ let newArray;
796
+ if (checked) {
797
+ // Add to array if not already present
798
+ newArray = currentArray.includes(optionValue) ? currentArray : [...currentArray, optionValue];
799
+ } else {
800
+ // Remove from array
801
+ newArray = currentArray.filter((v) => v !== optionValue);
802
+ }
803
+
804
+ this.setNestedValue(fieldPath, newArray);
805
+
806
+ // Clear field error on change
807
+ if (this.fieldErrors[fieldPath]) {
808
+ this.fieldErrors = { ...this.fieldErrors };
809
+ delete this.fieldErrors[fieldPath];
810
+ }
811
+
812
+ this.updateStateFlags();
813
+ }
814
+
815
+ /**
816
+ * Recursively validate form fields including nested objects
817
+ */
818
+ validateFormFields(properties, required = [], basePath = '', depth = 0) {
819
+ const MAX_DEPTH = 10;
820
+ if (depth > MAX_DEPTH) {
821
+ console.warn(`Schema nesting exceeds maximum depth of ${MAX_DEPTH} at path: ${basePath}`);
822
+ return {};
823
+ }
824
+
376
825
  const errors = {};
377
- const { properties = {}, required = [] } = this.schema;
378
826
 
379
827
  // Check required fields
380
- required.forEach(fieldName => {
381
- const value = this.formData[fieldName];
828
+ required.forEach((fieldName) => {
829
+ const fieldPath = basePath ? `${basePath}.${fieldName}` : fieldName;
830
+ const value = this.getNestedValue(fieldPath);
382
831
  if (value === undefined || value === null || value === '') {
383
- errors[fieldName] = 'This field is required';
832
+ const customMsg = this.getCustomErrorMessage(fieldPath, 'required');
833
+ errors[fieldPath] = customMsg || 'This field is required';
384
834
  }
385
835
  });
386
836
 
387
837
  // Type validation
388
838
  Object.entries(properties).forEach(([fieldName, fieldSchema]) => {
389
- const value = this.formData[fieldName];
839
+ const fieldPath = basePath ? `${basePath}.${fieldName}` : fieldName;
840
+ const value = this.getNestedValue(fieldPath);
841
+
842
+ // Handle nested objects recursively
843
+ if (fieldSchema.type === 'object' && fieldSchema.properties) {
844
+ const nestedErrors = this.validateFormFields(
845
+ fieldSchema.properties,
846
+ fieldSchema.required || [],
847
+ fieldPath,
848
+ depth + 1
849
+ );
850
+ Object.assign(errors, nestedErrors);
851
+ return;
852
+ }
390
853
 
391
854
  if (value !== undefined && value !== null && value !== '') {
392
855
  // Email validation
393
856
  if (fieldSchema.format === 'email') {
394
857
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
395
858
  if (!emailRegex.test(value)) {
396
- errors[fieldName] = 'Invalid email address';
859
+ // Support both 'format' (generic) and 'email' (specific) custom message keys
860
+ const customMsg =
861
+ this.getCustomErrorMessage(fieldPath, 'format') ||
862
+ this.getCustomErrorMessage(fieldPath, 'email');
863
+ errors[fieldPath] = customMsg || 'Invalid email address';
864
+ }
865
+ }
866
+
867
+ // Pattern validation
868
+ if (fieldSchema.pattern) {
869
+ const regex = new RegExp(fieldSchema.pattern);
870
+ if (!regex.test(value)) {
871
+ const customMsg = this.getCustomErrorMessage(fieldPath, 'pattern');
872
+ errors[fieldPath] = customMsg || 'Invalid format';
397
873
  }
398
874
  }
399
875
 
@@ -401,13 +877,16 @@ class FormBuilder extends i {
401
877
  if (fieldSchema.type === 'number' || fieldSchema.type === 'integer') {
402
878
  const num = Number(value);
403
879
  if (isNaN(num)) {
404
- errors[fieldName] = 'Must be a number';
880
+ const customMsg = this.getCustomErrorMessage(fieldPath, 'type');
881
+ errors[fieldPath] = customMsg || 'Must be a number';
405
882
  } else {
406
883
  if (fieldSchema.minimum !== undefined && num < fieldSchema.minimum) {
407
- errors[fieldName] = `Must be at least ${fieldSchema.minimum}`;
884
+ const customMsg = this.getCustomErrorMessage(fieldPath, 'minimum');
885
+ errors[fieldPath] = customMsg || `Must be at least ${fieldSchema.minimum}`;
408
886
  }
409
887
  if (fieldSchema.maximum !== undefined && num > fieldSchema.maximum) {
410
- errors[fieldName] = `Must be at most ${fieldSchema.maximum}`;
888
+ const customMsg = this.getCustomErrorMessage(fieldPath, 'maximum');
889
+ errors[fieldPath] = customMsg || `Must be at most ${fieldSchema.maximum}`;
411
890
  }
412
891
  }
413
892
  }
@@ -415,24 +894,41 @@ class FormBuilder extends i {
415
894
  // String length validation
416
895
  if (fieldSchema.type === 'string') {
417
896
  if (fieldSchema.minLength && value.length < fieldSchema.minLength) {
418
- errors[fieldName] = `Must be at least ${fieldSchema.minLength} characters`;
897
+ const customMsg = this.getCustomErrorMessage(fieldPath, 'minLength');
898
+ errors[fieldPath] = customMsg || `Must be at least ${fieldSchema.minLength} characters`;
419
899
  }
420
900
  if (fieldSchema.maxLength && value.length > fieldSchema.maxLength) {
421
- errors[fieldName] = `Must be at most ${fieldSchema.maxLength} characters`;
901
+ const customMsg = this.getCustomErrorMessage(fieldPath, 'maxLength');
902
+ errors[fieldPath] = customMsg || `Must be at most ${fieldSchema.maxLength} characters`;
422
903
  }
423
904
  }
424
905
  }
425
906
  });
426
907
 
427
- this.fieldErrors = errors;
428
- return Object.keys(errors).length === 0;
908
+ return errors;
909
+ }
910
+
911
+ validateForm() {
912
+ const { properties = {}, required = [] } = this.schema;
913
+ this.fieldErrors = this.validateFormFields(properties, required);
914
+ return Object.keys(this.fieldErrors).length === 0;
429
915
  }
430
916
 
431
917
  async handleSubmit(event) {
432
918
  event.preventDefault();
433
919
 
920
+ // Clear previous status messages
921
+ this.submitSuccess = false;
922
+ this.validationFailed = false;
434
923
  if (!this.validateForm()) {
435
- this.requestUpdate();
924
+ this.validationFailed = true;
925
+ await this.updateComplete; // Wait for render to complete
926
+
927
+ // Scroll to validation warning banner at top of form
928
+ const validationWarning = this.shadowRoot.querySelector('.status-message.validation-error');
929
+ if (validationWarning) {
930
+ validationWarning.scrollIntoView({ behavior: 'smooth', block: 'start' });
931
+ }
436
932
  return;
437
933
  }
438
934
 
@@ -441,17 +937,25 @@ class FormBuilder extends i {
441
937
  return;
442
938
  }
443
939
 
940
+ await this.submitWithRetry(false);
941
+ }
942
+
943
+ async submitWithRetry(isRetry = false) {
444
944
  try {
445
- this.submitting = true;
446
- this.error = null;
945
+ // Only set submitting on the first call, not on retry
946
+ if (!isRetry) {
947
+ this.submitting = true;
948
+ }
949
+ this.submissionError = null;
950
+ this.submissionStatus = null; // Clear previous messages
951
+
447
952
  const body = {
448
953
  username: this.decoded.sub,
449
954
  formFname: this.fbmsFormFname,
450
955
  formVersion: this.fbmsFormVersion,
451
956
  timestamp: Date.now(),
452
- answers: this.formData
957
+ answers: this.formData,
453
958
  };
454
- await delay(300);
455
959
 
456
960
  const url = `${this.fbmsBaseUrl}/api/v1/submissions/${this.fbmsFormFname}`;
457
961
  const headers = {
@@ -469,27 +973,121 @@ class FormBuilder extends i {
469
973
  body: JSON.stringify(body),
470
974
  });
471
975
 
976
+ // Try to parse response body for messages (even on error)
977
+ let responseData = null;
978
+ try {
979
+ responseData = await response.json();
980
+ this.submissionStatus = responseData;
981
+ } catch (jsonErr) {
982
+ // Response might not be JSON
983
+ console.warn('Could not parse response as JSON:', jsonErr);
984
+ }
985
+
986
+ // Handle 403 - token may be stale
987
+ if (response.status === 403 && !isRetry) {
988
+ console.warn('Received 403, attempting to refresh token and retry...');
989
+
990
+ // Re-fetch token if OIDC URL is configured
991
+ if (this.oidcUrl) {
992
+ try {
993
+ await this.fetchToken();
994
+ console.warn('Token refreshed successfully, retrying submission...');
995
+
996
+ // Retry once with new token (submitting flag stays true)
997
+ return await this.submitWithRetry(true);
998
+ } catch (tokenError) {
999
+ console.error('Failed to refresh token:', tokenError);
1000
+ // Fall through to handle the original 403 error
1001
+ throw new Error('Authentication failed: Unable to refresh token');
1002
+ }
1003
+ } else {
1004
+ console.warn('OIDC URL is not configured; cannot refresh token. Skipping retry.');
1005
+ // Fall through to handle the 403 error normally
1006
+ }
1007
+ }
1008
+
472
1009
  if (!response.ok) {
473
- throw new Error(`Failed to submit form: ${response.statusText}`);
1010
+ // Provide specific error for 403 after retry
1011
+ if (response.status === 403 && isRetry) {
1012
+ throw new Error(
1013
+ 'Authorization failed: Access denied even after token refresh. You may not have permission to submit this form.'
1014
+ );
1015
+ }
1016
+
1017
+ // Use server error message if available
1018
+ const errorMessage =
1019
+ responseData?.messageHeader ||
1020
+ responseData?.message ||
1021
+ `Failed to submit form: ${response.statusText}`;
1022
+ throw new Error(errorMessage);
474
1023
  }
475
1024
 
476
- // Dispatch success event
477
- this.dispatchEvent(new CustomEvent('form-submit-success', {
478
- detail: { data: body },
479
- bubbles: true,
480
- composed: true,
481
- }));
1025
+ // Check for form forwarding header (safely handle missing headers object)
1026
+ const formForward = response.headers?.get ? response.headers.get('x-fbms-formforward') : null;
1027
+ if (formForward) {
1028
+ // eslint-disable-next-line no-console
1029
+ console.info(`Form submitted successfully. Forwarding to next form: ${formForward}`);
1030
+ this.fbmsFormFname = formForward;
1031
+
1032
+ // Keep success state and messages visible for the forwarded form
1033
+ this.submitSuccess = true;
1034
+ // Note: submissionStatus is preserved to show server messages on the next form
1035
+ this.formCompleted = false;
1036
+
1037
+ // Re-initialize with the new form
1038
+ this.loading = true;
1039
+ try {
1040
+ await this.initialize();
1041
+ return; // Exit early, don't show success message for intermediate form
1042
+ // Note: finally block will set submitting = false
1043
+ } catch (forwardingError) {
1044
+ console.error('Failed to load forwarded form:', forwardingError);
1045
+ this.loading = false;
1046
+ this.submissionError =
1047
+ forwardingError?.message || 'Form was submitted, but loading the next form failed.';
1048
+ }
1049
+ }
482
1050
 
483
- // Optional: Reset or show success message
484
- this.error = null;
1051
+ // Dispatch success event
1052
+ this.dispatchEvent(
1053
+ new CustomEvent('form-submit-success', {
1054
+ detail: { data: body },
1055
+ bubbles: true,
1056
+ composed: true,
1057
+ })
1058
+ );
1059
+
1060
+ // No form forward - this is the final form completion
1061
+ this.formCompleted = true;
1062
+ this.submitSuccess = true;
1063
+ this.submissionError = null;
1064
+ this.initialFormData = this.deepClone(this.formData);
1065
+ this.hasChanges = false;
1066
+
1067
+ await this.updateComplete;
1068
+
1069
+ // Scroll to success message
1070
+ const successMsg = this.shadowRoot.querySelector('.status-message.success');
1071
+ if (successMsg) {
1072
+ successMsg.scrollIntoView({ behavior: 'smooth', block: 'start' });
1073
+ }
485
1074
  } catch (err) {
486
- this.error = err.message || 'Failed to submit form';
487
-
488
- this.dispatchEvent(new CustomEvent('form-submit-error', {
489
- detail: { error: err.message },
490
- bubbles: true,
491
- composed: true,
492
- }));
1075
+ this.submissionError = err.message || 'Failed to submit form';
1076
+
1077
+ this.dispatchEvent(
1078
+ new CustomEvent('form-submit-error', {
1079
+ detail: { error: err.message },
1080
+ bubbles: true,
1081
+ composed: true,
1082
+ })
1083
+ );
1084
+
1085
+ // Scroll to error message at top of form
1086
+ await this.updateComplete;
1087
+ const errorMsg = this.shadowRoot.querySelector('.status-message.error');
1088
+ if (errorMsg) {
1089
+ errorMsg.scrollIntoView({ behavior: 'smooth', block: 'start' });
1090
+ }
493
1091
  } finally {
494
1092
  this.submitting = false;
495
1093
  }
@@ -501,49 +1099,211 @@ class FormBuilder extends i {
501
1099
  this.requestUpdate();
502
1100
  }
503
1101
 
504
- renderField(fieldName, fieldSchema) {
505
- const value = this.formData[fieldName];
506
- const error = this.fieldErrors[fieldName];
507
- const required = this.schema.required?.includes(fieldName);
508
- const uiOptions = this.uiSchema?.[fieldName] || {};
1102
+ /**
1103
+ * Render a field - can be a simple input or a nested object
1104
+ */
1105
+ renderField(fieldName, fieldSchema, basePath = '', depth = 0) {
1106
+ const MAX_DEPTH = 10;
1107
+ if (depth > MAX_DEPTH) {
1108
+ console.warn(`Schema nesting exceeds maximum depth of ${MAX_DEPTH}`);
1109
+ return x`<div class="error">Schema too deeply nested</div>`;
1110
+ }
1111
+
1112
+ const fieldPath = basePath ? `${basePath}.${fieldName}` : fieldName;
1113
+
1114
+ // Handle nested objects with properties
1115
+ if (fieldSchema.type === 'object' && fieldSchema.properties) {
1116
+ return this.renderNestedObject(fieldName, fieldSchema, basePath, depth);
1117
+ }
1118
+
1119
+ // Single-value enum - render as informational text only (title serves as the message)
1120
+ if (fieldSchema.enum && fieldSchema.enum.length === 1) {
1121
+ return x`
1122
+ <div class="form-group">
1123
+ <span class="info-label">${fieldSchema.title || fieldName}</span>
1124
+ ${fieldSchema.description
1125
+ ? x`<span class="description">${fieldSchema.description}</span>`
1126
+ : ''}
1127
+ </div>
1128
+ `;
1129
+ }
1130
+
1131
+ // Regular field
1132
+ const value = this.getNestedValue(fieldPath);
1133
+ const error = this.fieldErrors[fieldPath];
1134
+ // For nested fields, check the parent schema's required array
1135
+ const parentSchema = basePath ? this.getSchemaAtPath(basePath) : this.schema;
1136
+ const required = parentSchema?.required?.includes(fieldName) ?? false;
1137
+ const uiSchemaPath = fieldPath.split('.');
1138
+ let uiOptions = this.uiSchema;
1139
+ for (const part of uiSchemaPath) {
1140
+ uiOptions = uiOptions?.[part];
1141
+ }
1142
+ uiOptions = uiOptions || {};
1143
+
1144
+ const widget = uiOptions['ui:widget'];
1145
+ const isGroupedInput = widget === 'radio' || widget === 'checkboxes';
509
1146
 
510
1147
  return x`
511
1148
  <div class="form-group">
512
- <label class="${required ? 'required' : ''}" for="${fieldName}">
513
- ${fieldSchema.title || fieldName}
514
- </label>
515
-
516
- ${fieldSchema.description ? x`
517
- <span class="description">${fieldSchema.description}</span>
518
- ` : ''}
1149
+ ${!isGroupedInput
1150
+ ? x`
1151
+ <label class="${required ? 'required' : ''}" for="${fieldPath}">
1152
+ ${fieldSchema.title || fieldName}
1153
+ </label>
1154
+ `
1155
+ : ''}
1156
+ ${fieldSchema.description && !isGroupedInput
1157
+ ? x` <span class="description">${fieldSchema.description}</span> `
1158
+ : ''}
1159
+ ${this.renderInput(fieldPath, fieldSchema, value, uiOptions)}
1160
+ ${error ? x` <span class="error-message">${error}</span> ` : ''}
1161
+ </div>
1162
+ `;
1163
+ }
519
1164
 
520
- ${this.renderInput(fieldName, fieldSchema, value, uiOptions)}
1165
+ /**
1166
+ * Render a nested object with its own properties
1167
+ */
1168
+ renderNestedObject(fieldName, fieldSchema, basePath = '', depth = 0) {
1169
+ const fieldPath = basePath ? `${basePath}.${fieldName}` : fieldName;
521
1170
 
522
- ${error ? x`
523
- <span class="error-message">${error}</span>
524
- ` : ''}
1171
+ return x`
1172
+ <div class="nested-object">
1173
+ ${fieldSchema.title
1174
+ ? x`<div class="nested-object-title">${fieldSchema.title}</div>`
1175
+ : ''}
1176
+ ${fieldSchema.description
1177
+ ? x`<div class="nested-object-description">${fieldSchema.description}</div>`
1178
+ : ''}
1179
+ ${Object.entries(fieldSchema.properties).map(([nestedFieldName, nestedFieldSchema]) =>
1180
+ this.renderField(nestedFieldName, nestedFieldSchema, fieldPath, depth + 1)
1181
+ )}
525
1182
  </div>
526
1183
  `;
527
1184
  }
528
1185
 
529
- renderInput(fieldName, fieldSchema, value, uiOptions) {
530
- const { type, enum: enumValues, format } = fieldSchema;
1186
+ renderInput(fieldPath, fieldSchema, value, uiOptions) {
1187
+ const { type, enum: enumValues, format, items } = fieldSchema;
1188
+ const widget = uiOptions['ui:widget'];
1189
+ const isInline = uiOptions['ui:options']?.inline;
1190
+
1191
+ // Single-value enum - no input needed, title/label already displays the message
1192
+ if (enumValues && enumValues.length === 1) {
1193
+ return x``;
1194
+ }
1195
+
1196
+ // Array of enums with checkboxes widget - render as checkboxes
1197
+ if (type === 'array' && items?.enum && widget === 'checkboxes') {
1198
+ const selectedValues = Array.isArray(value) ? value : [];
1199
+ const containerClass = isInline ? 'checkbox-group inline' : 'checkbox-group';
1200
+
1201
+ // Extract basePath and fieldName from fieldPath
1202
+ const pathParts = fieldPath.split('.');
1203
+ const fieldName = pathParts[pathParts.length - 1];
1204
+ const basePath = pathParts.slice(0, -1).join('.');
1205
+
1206
+ const parentSchema = basePath ? this.getSchemaAtPath(basePath) : this.schema;
1207
+ const isRequired = parentSchema?.required?.includes(fieldName) ?? false;
1208
+
1209
+ return x`
1210
+ <fieldset class="${containerClass}">
1211
+ <legend class="${isRequired ? 'required' : ''}">${fieldSchema.title || fieldName}</legend>
1212
+ ${fieldSchema.description
1213
+ ? x`<span class="description">${fieldSchema.description}</span>`
1214
+ : ''}
1215
+ ${items.enum.map((opt) => {
1216
+ const sanitizedId = this.sanitizeId(`${fieldPath}-${opt}`);
1217
+ return x`
1218
+ <div class="checkbox-item">
1219
+ <input
1220
+ type="checkbox"
1221
+ id="${sanitizedId}"
1222
+ name="${fieldPath}"
1223
+ value="${opt}"
1224
+ .checked="${selectedValues.includes(opt)}"
1225
+ @change="${(e) => this.handleCheckboxArrayChange(fieldPath, opt, e)}"
1226
+ />
1227
+ <label for="${sanitizedId}">${opt}</label>
1228
+ </div>
1229
+ `;
1230
+ })}
1231
+ </fieldset>
1232
+ `;
1233
+ }
1234
+
1235
+ // Array of enums without widget - render as multi-select dropdown (default)
1236
+ if (type === 'array' && items?.enum) {
1237
+ const selectedValues = Array.isArray(value) ? value : [];
531
1238
 
532
- // Enum - render as select
1239
+ return x`
1240
+ <select
1241
+ id="${fieldPath}"
1242
+ name="${fieldPath}"
1243
+ multiple
1244
+ size="5"
1245
+ @change="${(e) => this.handleMultiSelectChange(fieldPath, e)}"
1246
+ >
1247
+ ${items.enum.map(
1248
+ (opt) => x`
1249
+ <option value="${opt}" ?selected="${selectedValues.includes(opt)}">${opt}</option>
1250
+ `
1251
+ )}
1252
+ </select>
1253
+ `;
1254
+ }
1255
+
1256
+ // Enum with radio widget - render as radio buttons
1257
+ if (enumValues && widget === 'radio') {
1258
+ const containerClass = isInline ? 'radio-group inline' : 'radio-group';
1259
+
1260
+ // Extract basePath and fieldName from fieldPath
1261
+ const pathParts = fieldPath.split('.');
1262
+ const fieldName = pathParts[pathParts.length - 1];
1263
+ const basePath = pathParts.slice(0, -1).join('.');
1264
+
1265
+ const parentSchema = basePath ? this.getSchemaAtPath(basePath) : this.schema;
1266
+ const isRequired = parentSchema?.required?.includes(fieldName) ?? false;
1267
+
1268
+ return x`
1269
+ <fieldset class="${containerClass}">
1270
+ <legend class="${isRequired ? 'required' : ''}">${fieldSchema.title || fieldName}</legend>
1271
+ ${fieldSchema.description
1272
+ ? x`<span class="description">${fieldSchema.description}</span>`
1273
+ : ''}
1274
+ ${enumValues.map((opt) => {
1275
+ const sanitizedId = this.sanitizeId(`${fieldPath}-${opt}`);
1276
+ return x`
1277
+ <div class="radio-item">
1278
+ <input
1279
+ type="radio"
1280
+ id="${sanitizedId}"
1281
+ name="${fieldPath}"
1282
+ value="${opt}"
1283
+ .checked="${value === opt}"
1284
+ @change="${(e) => this.handleInputChange(fieldPath, e)}"
1285
+ />
1286
+ <label for="${sanitizedId}">${opt}</label>
1287
+ </div>
1288
+ `;
1289
+ })}
1290
+ </fieldset>
1291
+ `;
1292
+ }
1293
+
1294
+ // Enum - render as select (default)
533
1295
  if (enumValues) {
534
1296
  return x`
535
1297
  <select
536
- id="${fieldName}"
537
- name="${fieldName}"
1298
+ id="${fieldPath}"
1299
+ name="${fieldPath}"
538
1300
  .value="${value || ''}"
539
- @change="${(e) => this.handleInputChange(fieldName, e)}"
1301
+ @change="${(e) => this.handleInputChange(fieldPath, e)}"
540
1302
  >
541
1303
  <option value="">-- Select --</option>
542
- ${enumValues.map(opt => x`
543
- <option value="${opt}" ?selected="${value === opt}">
544
- ${opt}
545
- </option>
546
- `)}
1304
+ ${enumValues.map(
1305
+ (opt) => x` <option value="${opt}" ?selected="${value === opt}">${opt}</option> `
1306
+ )}
547
1307
  </select>
548
1308
  `;
549
1309
  }
@@ -554,12 +1314,12 @@ class FormBuilder extends i {
554
1314
  <div class="checkbox-item">
555
1315
  <input
556
1316
  type="checkbox"
557
- id="${fieldName}"
558
- name="${fieldName}"
1317
+ id="${fieldPath}"
1318
+ name="${fieldPath}"
559
1319
  .checked="${!!value}"
560
- @change="${(e) => this.handleInputChange(fieldName, e)}"
1320
+ @change="${(e) => this.handleInputChange(fieldPath, e)}"
561
1321
  />
562
- <label for="${fieldName}">${fieldSchema.title || fieldName}</label>
1322
+ <label for="${fieldPath}">${fieldSchema.title || fieldPath.split('.').pop()}</label>
563
1323
  </div>
564
1324
  `;
565
1325
  }
@@ -570,10 +1330,10 @@ class FormBuilder extends i {
570
1330
  return x`
571
1331
  <input
572
1332
  type="email"
573
- id="${fieldName}"
574
- name="${fieldName}"
1333
+ id="${fieldPath}"
1334
+ name="${fieldPath}"
575
1335
  .value="${value || ''}"
576
- @input="${(e) => this.handleInputChange(fieldName, e)}"
1336
+ @input="${(e) => this.handleInputChange(fieldPath, e)}"
577
1337
  />
578
1338
  `;
579
1339
  }
@@ -582,10 +1342,10 @@ class FormBuilder extends i {
582
1342
  return x`
583
1343
  <input
584
1344
  type="date"
585
- id="${fieldName}"
586
- name="${fieldName}"
1345
+ id="${fieldPath}"
1346
+ name="${fieldPath}"
587
1347
  .value="${value || ''}"
588
- @input="${(e) => this.handleInputChange(fieldName, e)}"
1348
+ @input="${(e) => this.handleInputChange(fieldPath, e)}"
589
1349
  />
590
1350
  `;
591
1351
  }
@@ -593,10 +1353,10 @@ class FormBuilder extends i {
593
1353
  if (uiOptions['ui:widget'] === 'textarea') {
594
1354
  return x`
595
1355
  <textarea
596
- id="${fieldName}"
597
- name="${fieldName}"
1356
+ id="${fieldPath}"
1357
+ name="${fieldPath}"
598
1358
  .value="${value || ''}"
599
- @input="${(e) => this.handleInputChange(fieldName, e)}"
1359
+ @input="${(e) => this.handleInputChange(fieldPath, e)}"
600
1360
  ></textarea>
601
1361
  `;
602
1362
  }
@@ -605,10 +1365,10 @@ class FormBuilder extends i {
605
1365
  return x`
606
1366
  <input
607
1367
  type="text"
608
- id="${fieldName}"
609
- name="${fieldName}"
1368
+ id="${fieldPath}"
1369
+ name="${fieldPath}"
610
1370
  .value="${value || ''}"
611
- @input="${(e) => this.handleInputChange(fieldName, e)}"
1371
+ @input="${(e) => this.handleInputChange(fieldPath, e)}"
612
1372
  />
613
1373
  `;
614
1374
  }
@@ -618,11 +1378,11 @@ class FormBuilder extends i {
618
1378
  return x`
619
1379
  <input
620
1380
  type="number"
621
- id="${fieldName}"
622
- name="${fieldName}"
1381
+ id="${fieldPath}"
1382
+ name="${fieldPath}"
623
1383
  .value="${value || ''}"
624
1384
  step="${type === 'integer' ? '1' : 'any'}"
625
- @input="${(e) => this.handleInputChange(fieldName, e)}"
1385
+ @input="${(e) => this.handleInputChange(fieldPath, e)}"
626
1386
  />
627
1387
  `;
628
1388
  }
@@ -631,29 +1391,27 @@ class FormBuilder extends i {
631
1391
  return x`
632
1392
  <input
633
1393
  type="text"
634
- id="${fieldName}"
635
- name="${fieldName}"
1394
+ id="${fieldPath}"
1395
+ name="${fieldPath}"
636
1396
  .value="${value || ''}"
637
- @input="${(e) => this.handleInputChange(fieldName, e)}"
1397
+ @input="${(e) => this.handleInputChange(fieldPath, e)}"
638
1398
  />
639
1399
  `;
640
1400
  }
641
1401
 
642
1402
  render() {
643
- if (this.loading) {
1403
+ if (this.error) {
644
1404
  return x`
645
1405
  <div class="container">
646
- <div class="loading">Loading form...</div>
1406
+ <div class="error"><strong>Error:</strong> ${this.error}</div>
647
1407
  </div>
648
1408
  `;
649
1409
  }
650
1410
 
651
- if (this.error) {
1411
+ if (this.loading) {
652
1412
  return x`
653
1413
  <div class="container">
654
- <div class="error">
655
- <strong>Error:</strong> ${this.error}
656
- </div>
1414
+ <div class="loading">Loading form...</div>
657
1415
  </div>
658
1416
  `;
659
1417
  }
@@ -666,30 +1424,106 @@ class FormBuilder extends i {
666
1424
  `;
667
1425
  }
668
1426
 
669
- return x`
670
- ${this.customStyles ? x`<style>${this.customStyles}</style>` : ''}
1427
+ // NEW: Success-only view when form is completed
1428
+ if (this.formCompleted) {
1429
+ return x`
1430
+ ${this.customStyles
1431
+ ? x`<style>
1432
+ ${this.customStyles}
1433
+ </style>`
1434
+ : ''}
671
1435
 
672
- <div class="container">
673
- <form @submit="${this.handleSubmit}">
674
- ${this.schema.title ? x`<h2>${this.schema.title}</h2>` : ''}
675
- ${this.schema.description ? x`<p>${this.schema.description}</p>` : ''}
1436
+ <div class="container">
1437
+ <div class="status-message success">
1438
+ <h2>✓ Form submitted successfully!</h2>
1439
+ ${this.submissionStatus?.messages?.length > 0
1440
+ ? x`
1441
+ <ul>
1442
+ ${this.submissionStatus.messages.map((msg) => x`<li>${msg}</li>`)}
1443
+ </ul>
1444
+ `
1445
+ : ''}
1446
+ </div>
1447
+ </div>
1448
+ `;
1449
+ }
676
1450
 
677
- ${Object.entries(this.schema.properties).map(([fieldName, fieldSchema]) =>
678
- this.renderField(fieldName, fieldSchema)
679
- )}
1451
+ // Regular form view (rest of existing render code)
1452
+ const hasFields = Object.keys(this.schema.properties).length > 0;
680
1453
 
681
- <div class="buttons">
682
- <button type="submit" ?disabled="${this.submitting}">
683
- <span class="button-content">
684
- ${this.submitting ? x`<span class="spinner"></span>` : ''}
685
- ${this.submitting ? 'Submitting...' : 'Submit'}
686
- </span>
687
- </button>
688
- <button type="button" @click="${this.handleReset}" ?disabled="${this.submitting}">
689
- Reset
690
- </button>
691
- </div>
692
- </form>
1454
+ return x`
1455
+ ${this.customStyles
1456
+ ? x`<style>
1457
+ ${this.customStyles}
1458
+ </style>`
1459
+ : ''}
1460
+
1461
+ <div class="container">
1462
+ ${this.submitSuccess
1463
+ ? x`
1464
+ <div class="status-message success">
1465
+ ✓ Your form was successfully submitted.
1466
+ ${this.submissionStatus?.messages?.length > 0
1467
+ ? x`
1468
+ <ul>
1469
+ ${this.submissionStatus.messages.map((msg) => x`<li>${msg}</li>`)}
1470
+ </ul>
1471
+ `
1472
+ : ''}
1473
+ </div>
1474
+ `
1475
+ : ''}
1476
+ ${hasFields
1477
+ ? x`
1478
+ <form @submit="${this.handleSubmit}" class="${this.submitting ? 'submitting' : ''}">
1479
+ ${this.schema.title ? x`<h2>${this.schema.title}</h2>` : ''}
1480
+ ${this.schema.description ? x`<p>${this.schema.description}</p>` : ''}
1481
+ ${this.validationFailed
1482
+ ? x`
1483
+ <div class="status-message validation-error">
1484
+ ⚠ Please correct the errors below before submitting.
1485
+ </div>
1486
+ `
1487
+ : ''}
1488
+ ${this.submissionError
1489
+ ? x`
1490
+ <div class="status-message error">
1491
+ <strong>Error:</strong> ${this.submissionError}
1492
+ ${this.submissionStatus?.messages?.length > 0
1493
+ ? x`
1494
+ <ul>
1495
+ ${this.submissionStatus.messages.map(
1496
+ (msg) => x`<li>${msg}</li>`
1497
+ )}
1498
+ </ul>
1499
+ `
1500
+ : ''}
1501
+ </div>
1502
+ `
1503
+ : ''}
1504
+ ${Object.entries(this.schema.properties).map(([fieldName, fieldSchema]) =>
1505
+ this.renderField(fieldName, fieldSchema)
1506
+ )}
1507
+
1508
+ <div class="buttons">
1509
+ <button type="submit" ?disabled="${this.submitting}">
1510
+ <span class="button-content">
1511
+ ${this.submitting ? x`<span class="spinner"></span>` : ''}
1512
+ ${this.submitting ? 'Submitting...' : 'Submit'}
1513
+ </span>
1514
+ </button>
1515
+ <button type="button" @click="${this.handleReset}" ?disabled="${this.submitting}">
1516
+ Reset
1517
+ </button>
1518
+ </div>
1519
+ </form>
1520
+ `
1521
+ : x`
1522
+ <div class="info-only">
1523
+ ${this.schema.title ? x`<h2>${this.schema.title}</h2>` : ''}
1524
+ ${this.schema.description ? x`<p>${this.schema.description}</p>` : ''}
1525
+ </div>
1526
+ `}
693
1527
  </div>
694
1528
  `;
695
1529
  }