@authhero/widget 0.13.3 → 0.15.0

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.
package/hydrate/index.js CHANGED
@@ -4947,7 +4947,7 @@ var setScopedSSR = (opts) => {
4947
4947
  var needsScopedSSR = () => scopedSSR;
4948
4948
  var scopedSSR = false;
4949
4949
 
4950
- const authheroNodeCss = () => `:host{display:block}.input-wrapper{display:flex;flex-direction:column;position:relative;margin-bottom:16px}.input-container{position:relative;width:100%}.input-label{position:absolute;left:16px;top:50%;transform:translateY(-50%);font-size:16px;font-weight:var(--ah-font-weight-label, 400);color:var(--ah-color-text-muted, #65676e);pointer-events:none;transition:all 0.15s ease-out;background-color:transparent;padding:0;z-index:1}.input-label.floating,.input-field:focus+.input-label,.input-field:not(:placeholder-shown)+.input-label{top:-8px;transform:translateY(0);font-size:12px;background-color:var(--ah-color-bg, #ffffff);padding:0 4px;left:12px;color:var(--ah-color-text-muted, #65676e)}.input-field:focus+.input-label{color:var(--ah-color-primary, #635dff)}.required{color:var(--ah-color-error, #d03c38);margin-left:2px}.input-field{width:100%;padding:16px;font-size:16px;font-family:inherit;color:var(--ah-color-text, #1e212a);background-color:var(--ah-color-input-bg, #ffffff);border:1px solid var(--ah-color-border, #c9cace);border-radius:var(--ah-input-radius, 3px);outline:none;transition:border-color 0.15s ease-out,\\n box-shadow 0.15s ease-out;box-sizing:border-box}.input-field::placeholder{color:transparent}.input-field:hover{border-color:var(--ah-color-border-hover, #65676e)}.input-field:focus{border-color:var(--ah-color-primary, #635dff);box-shadow:inset 0 0 0 1px var(--ah-color-primary, #635dff)}.input-field.has-error{border-color:var(--ah-color-error, #d03c38)}.input-field.has-error:focus{box-shadow:inset 0 0 0 1px var(--ah-color-error, #d03c38)}.input-field:disabled{background-color:var(--ah-color-bg-disabled, #f5f5f5);border-color:var(--ah-color-border-disabled, #e0e1e3);cursor:not-allowed;opacity:0.7}.password-container{position:relative;display:flex;align-items:center}.password-container .input-field{padding-right:48px}.password-toggle{position:absolute;right:12px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;color:var(--ah-color-text-muted, #65676e);transition:color 0.15s ease}.password-toggle:hover{color:var(--ah-color-text, #1e212a)}.password-toggle svg{width:20px;height:20px}.error-text{font-size:12px;color:var(--ah-color-error, #d03c38);margin-top:4px;line-height:1.4}.helper-text{font-size:12px;color:var(--ah-color-text-muted, #65676e);margin-top:4px;line-height:1.4}.field-link{display:block;text-align:left;margin-top:8px;margin-bottom:16px}.field-link a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400)}.field-link a:hover{text-decoration:underline}.checkbox-wrapper{display:flex;align-items:flex-start;gap:10px;cursor:pointer;margin-bottom:16px}.checkbox-wrapper input[type="checkbox"]{width:18px;height:18px;margin:0;accent-color:var(--ah-color-primary, #635dff);cursor:pointer;flex-shrink:0}.checkbox-label{font-size:14px;color:var(--ah-color-text, #1e212a);line-height:1.5}.btn{display:inline-flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:14px 20px;font-size:16px;font-weight:var(--ah-font-weight-btn, 400);font-family:inherit;line-height:1.25;text-align:center;text-decoration:none;border:none;border-radius:var(--ah-btn-radius, 3px);cursor:pointer;transition:background-color 0.15s ease,\\n border-color 0.15s ease,\\n transform 0.1s ease;box-sizing:border-box}.btn:disabled{opacity:0.6;cursor:not-allowed}.btn:not(:disabled):active{transform:scale(0.98)}.btn:focus-visible{outline:2px solid var(--ah-color-primary, #635dff);outline-offset:2px}.btn-primary{background-color:var(--ah-color-primary, #635dff);color:var(--ah-color-text-on-primary, #ffffff);margin-top:12px}.btn-primary:not(:disabled):hover{filter:brightness(0.85)}.btn-secondary{background-color:var(--ah-color-bg, #ffffff);color:var(--ah-color-text, #1e212a);border:1px solid var(--ah-color-border, #c9cace)}.btn-secondary:not(:disabled):hover{background-color:var(--ah-color-bg-hover, #f5f5f5);border-color:var(--ah-color-border-hover, #65676e)}.btn-link{background:none;border:none;color:var(--ah-color-link, #635dff);padding:8px 0;font-weight:var(--ah-font-weight-link, 400);text-decoration:none}.btn-link:hover{text-decoration:underline}.social-buttons{display:flex;flex-direction:column;gap:12px}.btn-social{display:flex;align-items:center;justify-content:center;gap:12px}.btn-social-content{display:flex;flex-direction:column;align-items:center;text-align:center}.btn-social-subtitle{font-size:12px;font-style:italic;opacity:0.8;margin-top:2px}.btn-social-subtitle:empty{display:none}.social-icon{width:20px;height:20px;flex-shrink:0}@media (max-width: 480px){.social-buttons:has(.btn-social:nth-child(3)){flex-direction:row;flex-wrap:nowrap;justify-content:stretch;gap:8px}.social-buttons:has(.btn-social:nth-child(3)) .btn-social{width:auto;min-width:0;padding:12px;flex:1 1 0}.social-buttons:has(.btn-social:nth-child(3)) .btn-social span{display:none}.social-buttons:has(.btn-social:nth-child(3)) .social-icon{width:24px;height:24px}}.btn-icon{width:20px;height:20px;flex-shrink:0}.btn-icon img{width:100%;height:100%;object-fit:contain}.text-title{font-size:20px;font-weight:400;color:var(--ah-color-text, #1e212a);margin:8px 0;line-height:1.3}.text-title.text-success{color:var(--ah-color-success, #13a769)}.text-description{font-size:14px;color:var(--ah-color-text-muted, #65676e);margin:4px 0;line-height:1.5}.image{display:block;max-width:100%;height:auto;border-radius:4px}.image-centered{margin:0 auto 16px;width:52px;height:52px;object-fit:contain}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;transition:color 0.15s ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.node-error{padding:12px 16px;background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38);border-radius:0;font-size:14px;margin-bottom:16px}.node-success{padding:12px 16px;background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769);border-radius:0;font-size:14px;margin-bottom:16px}.divider{display:flex;align-items:center;text-align:center;margin:16px 0}.divider::before,.divider::after{content:"";flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0.5px}.rich-text{font-size:14px;line-height:1.5;color:var(--ah-color-text, #1e212a)}.rich-text a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);transition:color 0.15s ease}.rich-text a:hover{text-decoration:underline}.rich-text .terms-text{margin-top:16px;text-align:center;font-size:12px;color:var(--ah-color-text-muted, #65676e)}.rich-text .terms-text a{font-size:12px}.rich-text .signup-link{margin-top:16px;text-align:center;font-size:14px;color:var(--ah-color-text, #1e212a)}`;
4950
+ const authheroNodeCss = () => `:host{display:block}.input-wrapper{display:flex;flex-direction:column;position:relative;margin-bottom:16px}.input-container{position:relative;width:100%}.input-label{position:absolute;left:16px;top:50%;transform:translateY(-50%);font-size:16px;font-weight:var(--ah-font-weight-label, 400);color:var(--ah-color-text-muted, #65676e);pointer-events:none;transition:all 0.15s ease-out;background-color:transparent;padding:0;z-index:1}.input-label.floating,.input-field:focus+.input-label,.input-field:not(:placeholder-shown)+.input-label,select.input-field+.input-label,input[type="date"].input-field+.input-label{top:-8px;transform:translateY(0);font-size:12px;background-color:var(--ah-color-bg, #ffffff);padding:0 4px;left:12px;color:var(--ah-color-text-muted, #65676e)}.input-field:focus+.input-label{color:var(--ah-color-primary, #635dff)}.required{color:var(--ah-color-error, #d03c38);margin-left:2px}.input-field{width:100%;padding:16px;font-size:16px;font-family:inherit;color:var(--ah-color-text, #1e212a);background-color:var(--ah-color-input-bg, #ffffff);border:1px solid var(--ah-color-border, #c9cace);border-radius:var(--ah-input-radius, 3px);outline:none;transition:border-color 0.15s ease-out,\\n box-shadow 0.15s ease-out;box-sizing:border-box}.input-field::placeholder{color:transparent}.input-field:hover{border-color:var(--ah-color-border-hover, #65676e)}.input-field:focus{border-color:var(--ah-color-primary, #635dff);box-shadow:inset 0 0 0 1px var(--ah-color-primary, #635dff)}.input-field.has-error{border-color:var(--ah-color-error, #d03c38)}.input-field.has-error:focus{box-shadow:inset 0 0 0 1px var(--ah-color-error, #d03c38)}.input-field:disabled{background-color:var(--ah-color-bg-disabled, #f5f5f5);border-color:var(--ah-color-border-disabled, #e0e1e3);cursor:not-allowed;opacity:0.7}.password-container{position:relative;display:flex;align-items:center}.password-container .input-field{padding-right:48px}.password-toggle{position:absolute;right:12px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;color:var(--ah-color-text-muted, #65676e);transition:color 0.15s ease}.password-toggle:hover{color:var(--ah-color-text, #1e212a)}.password-toggle svg{width:20px;height:20px}.error-text{font-size:12px;color:var(--ah-color-error, #d03c38);margin-top:4px;line-height:1.4}.helper-text{font-size:12px;color:var(--ah-color-text-muted, #65676e);margin-top:4px;line-height:1.4}.field-link{display:block;text-align:left;margin-top:8px;margin-bottom:16px}.field-link a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400)}.field-link a:hover{text-decoration:underline}.checkbox-wrapper{display:flex;align-items:flex-start;gap:10px;cursor:pointer;margin-bottom:16px}.checkbox-wrapper input[type="checkbox"]{width:18px;height:18px;margin:0;accent-color:var(--ah-color-primary, #635dff);cursor:pointer;flex-shrink:0}.checkbox-label{font-size:14px;color:var(--ah-color-text, #1e212a);line-height:1.5}.btn{display:inline-flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:14px 20px;font-size:16px;font-weight:var(--ah-font-weight-btn, 400);font-family:inherit;line-height:1.25;text-align:center;text-decoration:none;border:none;border-radius:var(--ah-btn-radius, 3px);cursor:pointer;transition:background-color 0.15s ease,\\n border-color 0.15s ease,\\n transform 0.1s ease;box-sizing:border-box}.btn:disabled{opacity:0.6;cursor:not-allowed}.btn:not(:disabled):active{transform:scale(0.98)}.btn:focus-visible{outline:2px solid var(--ah-color-primary, #635dff);outline-offset:2px}.btn-primary{background-color:var(--ah-color-primary, #635dff);color:var(--ah-color-text-on-primary, #ffffff);margin-top:12px}.btn-primary:not(:disabled):hover{filter:brightness(0.85)}.btn-secondary{background-color:var(--ah-color-bg, #ffffff);color:var(--ah-color-text, #1e212a);border:1px solid var(--ah-color-border, #c9cace)}.btn-secondary:not(:disabled):hover{background-color:var(--ah-color-bg-hover, #f5f5f5);border-color:var(--ah-color-border-hover, #65676e)}.btn-link{background:none;border:none;color:var(--ah-color-link, #635dff);padding:8px 0;font-weight:var(--ah-font-weight-link, 400);text-decoration:none}.btn-link:hover{text-decoration:underline}.social-buttons{display:flex;flex-direction:column;gap:12px}.btn-social{display:flex;align-items:center;justify-content:center;gap:12px}.btn-social-content{display:flex;flex-direction:column;align-items:center;text-align:center}.btn-social-subtitle{font-size:12px;font-style:italic;opacity:0.8;margin-top:2px}.btn-social-subtitle:empty{display:none}.social-icon{width:20px;height:20px;flex-shrink:0}@media (max-width: 480px){.social-buttons:has(.btn-social:nth-child(3)){flex-direction:row;flex-wrap:nowrap;justify-content:stretch;gap:8px}.social-buttons:has(.btn-social:nth-child(3)) .btn-social{width:auto;min-width:0;padding:12px;flex:1 1 0}.social-buttons:has(.btn-social:nth-child(3)) .btn-social span{display:none}.social-buttons:has(.btn-social:nth-child(3)) .social-icon{width:24px;height:24px}}.btn-icon{width:20px;height:20px;flex-shrink:0}.btn-icon img{width:100%;height:100%;object-fit:contain}.text-title{font-size:20px;font-weight:400;color:var(--ah-color-text, #1e212a);margin:8px 0;line-height:1.3}.text-title.text-success{color:var(--ah-color-success, #13a769)}.text-description{font-size:14px;color:var(--ah-color-text-muted, #65676e);margin:4px 0;line-height:1.5}.image{display:block;max-width:100%;height:auto;border-radius:4px}.image-centered{margin:0 auto 16px;width:52px;height:52px;object-fit:contain}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;transition:color 0.15s ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.node-error{padding:12px 16px;background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38);border-radius:0;font-size:14px;margin-bottom:16px}.node-success{padding:12px 16px;background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769);border-radius:0;font-size:14px;margin-bottom:16px}.divider{display:flex;align-items:center;text-align:center;margin:16px 0}.divider::before,.divider::after{content:"";flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0.5px}.rich-text{font-size:14px;line-height:1.5;color:var(--ah-color-text, #1e212a)}.rich-text a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);transition:color 0.15s ease}.rich-text a:hover{text-decoration:underline}.rich-text .terms-text{margin-top:16px;text-align:center;font-size:12px;color:var(--ah-color-text-muted, #65676e)}.rich-text .terms-text a{font-size:12px}.rich-text .forgot-password-link{text-align:right;font-size:13px;margin-top:4px}.rich-text .forgot-password-link a{font-size:13px;color:var(--ah-color-link, #635dff)}.rich-text .signup-link{margin-top:16px;text-align:center;font-size:14px;color:var(--ah-color-text, #1e212a)}`;
4951
4951
 
4952
4952
  class AuthheroNode {
4953
4953
  constructor(hostRef) {
@@ -4997,6 +4997,23 @@ class AuthheroNode {
4997
4997
  value: target.checked ? "true" : "false",
4998
4998
  });
4999
4999
  };
5000
+ /**
5001
+ * Returns the effective value for the field: uses `this.value` if set,
5002
+ * otherwise falls back to `config.default_value` (resolved by the server).
5003
+ */
5004
+ getEffectiveValue() {
5005
+ if (this.value !== undefined && this.value !== null) {
5006
+ return this.value;
5007
+ }
5008
+ const comp = this.component;
5009
+ if (comp.config && "default_value" in comp.config) {
5010
+ const dv = comp.config.default_value;
5011
+ if (typeof dv === "string" && dv !== "") {
5012
+ return dv;
5013
+ }
5014
+ }
5015
+ return undefined;
5016
+ }
5000
5017
  /**
5001
5018
  * Sanitize a string for use in CSS class names and part tokens.
5002
5019
  * Replaces spaces and special characters with hyphens, converts to lowercase.
@@ -5030,7 +5047,9 @@ class AuthheroNode {
5030
5047
  renderFloatingLabel(text, inputId, required, hasValue) {
5031
5048
  if (!text)
5032
5049
  return null;
5033
- return (hAsync("label", { class: { "input-label": true, floating: !!hasValue }, part: "label", htmlFor: inputId }, text, required && hAsync("span", { class: "required" }, "*")));
5050
+ // Use string class instead of object to avoid hydration mismatch
5051
+ const labelClass = hasValue ? "input-label floating" : "input-label";
5052
+ return (hAsync("label", { class: labelClass, part: "label", htmlFor: inputId }, text, required && hAsync("span", { class: "required" }, "*")));
5034
5053
  }
5035
5054
  /**
5036
5055
  * Render a label for a field (non-floating version for checkboxes etc).
@@ -5040,6 +5059,13 @@ class AuthheroNode {
5040
5059
  return null;
5041
5060
  return (hAsync("label", { class: "input-label", part: "label", htmlFor: inputId }, text, required && hAsync("span", { class: "required" }, "*")));
5042
5061
  }
5062
+ /**
5063
+ * Get the input field class string.
5064
+ * Uses string instead of object to avoid hydration mismatch.
5065
+ */
5066
+ getInputFieldClass(hasError) {
5067
+ return hasError ? "input-field has-error" : "input-field";
5068
+ }
5043
5069
  /**
5044
5070
  * Render the eye icon for password visibility toggle.
5045
5071
  */
@@ -5103,46 +5129,55 @@ class AuthheroNode {
5103
5129
  const inputId = `input-${component.id}`;
5104
5130
  const errors = this.getErrors();
5105
5131
  const { multiline, max_length } = component.config ?? {};
5106
- const hasValue = !!(this.value && this.value.length > 0);
5132
+ const effectiveValue = this.getEffectiveValue();
5133
+ const hasValue = !!(effectiveValue && effectiveValue.length > 0);
5107
5134
  if (multiline) {
5108
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("textarea", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input textarea", name: component.id, placeholder: " ", required: component.required, disabled: this.disabled, maxLength: max_length, onInput: this.handleInput }, this.value ?? ""), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5135
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("textarea", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input textarea", name: component.id, placeholder: " ", required: component.required, disabled: this.disabled, maxLength: max_length, onInput: this.handleInput }, effectiveValue ?? ""), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5109
5136
  }
5110
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: component.sensitive ? "password" : "text", name: component.id, "data-input-name": component.id, value: this.value ?? "", placeholder: " ", required: component.required, disabled: this.disabled, maxLength: max_length, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5137
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: component.sensitive ? "password" : "text", name: component.id, "data-input-name": component.id, value: effectiveValue ?? "", placeholder: " ", required: component.required, disabled: this.disabled, maxLength: max_length, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5111
5138
  }
5112
5139
  renderEmailField(component) {
5113
5140
  const inputId = `input-${component.id}`;
5114
5141
  const errors = this.getErrors();
5115
- const hasValue = !!(this.value && this.value.length > 0);
5116
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: "email", name: component.id, "data-input-name": component.id, value: this.value ?? "", placeholder: " ", required: component.required, disabled: this.disabled, autocomplete: "email", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5142
+ const effectiveValue = this.getEffectiveValue();
5143
+ const hasValue = !!(effectiveValue && effectiveValue.length > 0);
5144
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: "email", name: component.id, "data-input-name": component.id, value: effectiveValue ?? "", placeholder: " ", required: component.required, disabled: this.disabled, autocomplete: "email", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5117
5145
  }
5118
5146
  renderPasswordField(component) {
5119
5147
  const inputId = `input-${component.id}`;
5120
5148
  const errors = this.getErrors();
5121
- const hasValue = !!(this.value && this.value.length > 0);
5149
+ const effectiveValue = this.getEffectiveValue();
5150
+ const hasValue = !!(effectiveValue && effectiveValue.length > 0);
5122
5151
  const forgotPasswordLink = component.config?.forgot_password_link;
5123
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container password-container" }, hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: this.passwordVisible ? "text" : "password", name: component.id, "data-input-name": component.id, value: this.value ?? "", placeholder: " ", required: component.required, disabled: this.disabled, minLength: component.config?.min_length, autocomplete: "current-password", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue), hAsync("button", { type: "button", class: "password-toggle", part: "password-toggle", onClick: this.togglePasswordVisibility, "aria-label": "Toggle password visibility", "aria-pressed": this.passwordVisible ? "true" : "false" }, this.renderPasswordToggle())), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint), forgotPasswordLink && (hAsync("div", { class: "field-link", part: "field-link" }, hAsync("a", { href: forgotPasswordLink, class: "link", part: "link" }, "Forgot password?")))));
5152
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container password-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: this.passwordVisible ? "text" : "password", name: component.id, "data-input-name": component.id, value: effectiveValue ?? "", placeholder: " ", required: component.required, disabled: this.disabled, minLength: component.config?.min_length, autocomplete: "current-password", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue), hAsync("button", { type: "button", class: "password-toggle", part: "password-toggle", onClick: this.togglePasswordVisibility, "aria-label": "Toggle password visibility", "aria-pressed": this.passwordVisible ? "true" : "false" }, this.renderPasswordToggle())), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint), forgotPasswordLink && (hAsync("div", { class: "field-link", part: "field-link" }, hAsync("a", { href: forgotPasswordLink, class: "link", part: "link" }, "Forgot password?")))));
5124
5153
  }
5125
5154
  renderNumberField(component) {
5126
5155
  const inputId = `input-${component.id}`;
5127
5156
  const errors = this.getErrors();
5128
5157
  const { placeholder, min, max, step } = component.config ?? {};
5129
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: "number", name: component.id, value: this.value ?? "", placeholder: placeholder, required: component.required, disabled: this.disabled, min: min, max: max, step: step, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5158
+ const effectiveValue = this.getEffectiveValue();
5159
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: "number", name: component.id, value: effectiveValue ?? "", placeholder: placeholder, required: component.required, disabled: this.disabled, min: min, max: max, step: step, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5130
5160
  }
5131
5161
  renderTelField(component) {
5132
5162
  const inputId = `input-${component.id}`;
5133
5163
  const errors = this.getErrors();
5134
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: "tel", name: component.id, value: this.value ?? "", placeholder: component.config?.placeholder, required: component.required, disabled: this.disabled, autocomplete: "tel", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5164
+ const effectiveValue = this.getEffectiveValue();
5165
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: "tel", name: component.id, value: effectiveValue ?? "", placeholder: component.config?.placeholder, required: component.required, disabled: this.disabled, autocomplete: "tel", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5135
5166
  }
5136
5167
  renderUrlField(component) {
5137
5168
  const inputId = `input-${component.id}`;
5138
5169
  const errors = this.getErrors();
5139
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: "url", name: component.id, value: this.value ?? "", placeholder: component.config?.placeholder, required: component.required, disabled: this.disabled, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5170
+ const effectiveValue = this.getEffectiveValue();
5171
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: "url", name: component.id, value: effectiveValue ?? "", placeholder: component.config?.placeholder, required: component.required, disabled: this.disabled, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5140
5172
  }
5141
5173
  renderDateField(component) {
5142
5174
  const inputId = `input-${component.id}`;
5143
5175
  const errors = this.getErrors();
5144
5176
  const { min, max } = component.config ?? {};
5145
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: "date", name: component.id, value: this.value ?? "", required: component.required, disabled: this.disabled, min: min, max: max, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5177
+ // Date fields always have a value (even if placeholder format), so always float the label
5178
+ const hasValue = true;
5179
+ const effectiveValue = this.getEffectiveValue();
5180
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: "date", name: component.id, "data-input-name": component.id, value: effectiveValue ?? "", placeholder: " ", required: component.required, disabled: this.disabled, min: min, max: max, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5146
5181
  }
5147
5182
  renderBooleanField(component) {
5148
5183
  return (hAsync("label", { class: "checkbox-wrapper", part: "checkbox-wrapper" }, hAsync("input", { type: "checkbox", part: "checkbox", name: component.id, checked: this.value === "true" || component.config?.default_value === true, required: component.required, disabled: this.disabled, onChange: this.handleCheckbox }), hAsync("span", { class: "checkbox-label", part: "checkbox-label" }, component.label)));
@@ -5156,14 +5191,17 @@ class AuthheroNode {
5156
5191
  const inputId = `input-${component.id}`;
5157
5192
  const errors = this.getErrors();
5158
5193
  const { options, placeholder } = component.config ?? {};
5159
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("select", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input select", name: component.id, required: component.required, disabled: this.disabled, onChange: this.handleInput }, placeholder && (hAsync("option", { value: "", disabled: true, selected: !this.value }, placeholder)), options?.map((opt) => (hAsync("option", { value: opt.value, selected: this.value === opt.value, key: opt.value }, opt.label)))), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5194
+ // Dropdown always has visual content (selected option), so always float the label
5195
+ const hasValue = true;
5196
+ const effectiveValue = this.getEffectiveValue();
5197
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("select", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input select", name: component.id, required: component.required, disabled: this.disabled, onChange: this.handleInput }, placeholder && (hAsync("option", { value: "", disabled: true, selected: !effectiveValue }, placeholder)), options?.map((opt) => (hAsync("option", { value: opt.value, selected: effectiveValue === opt.value, key: opt.value }, opt.label)))), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5160
5198
  }
5161
5199
  renderChoiceField(component) {
5162
5200
  const errors = this.getErrors();
5163
5201
  const { options, display } = component.config ?? {};
5164
5202
  const isCheckbox = display === "checkbox";
5165
5203
  const inputType = isCheckbox ? "checkbox" : "radio";
5166
- return (hAsync("div", { class: "choice-wrapper", part: "choice-wrapper" }, component.label && (hAsync("span", { class: "choice-label", part: "choice-label" }, component.label, component.required && hAsync("span", { class: "required" }, "*"))), hAsync("div", { class: "choice-options", part: "choice-options" }, options?.map((opt) => (hAsync("label", { class: "choice-option", part: "choice-option", key: opt.value }, hAsync("input", { type: inputType, part: inputType, name: component.id, value: opt.value, checked: this.value === opt.value, required: component.required && !isCheckbox, disabled: this.disabled, onChange: this.handleInput }), hAsync("span", null, opt.label))))), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5204
+ return (hAsync("div", { class: "choice-wrapper", part: "choice-wrapper" }, component.label && (hAsync("span", { class: "choice-label", part: "choice-label" }, component.label, component.required && hAsync("span", { class: "required" }, "*"))), hAsync("div", { class: "choice-options", part: "choice-options" }, options?.map((opt) => (hAsync("label", { class: "choice-option", part: "choice-option", key: opt.value }, hAsync("input", { type: inputType, part: inputType, name: component.id, value: opt.value, checked: this.getEffectiveValue() === opt.value, required: component.required && !isCheckbox, disabled: this.disabled, onChange: this.handleInput }), hAsync("span", null, opt.label))))), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5167
5205
  }
5168
5206
  renderSocialField(component) {
5169
5207
  const providers = component.config?.providers ?? [];
@@ -5713,7 +5751,7 @@ function escapeAttr(value) {
5713
5751
  .replace(/>/g, ">");
5714
5752
  }
5715
5753
 
5716
- const authheroWidgetCss = () => `:host{display:block;font-family:var(--ah-font-family, 'ulp-font', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, sans-serif);font-size:var(--ah-font-size-base, 14px);line-height:var(--ah-line-height-base, 1.5);color:var(--ah-color-text, #1e212a);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.widget-container{max-width:var(--ah-widget-max-width, 400px);width:100%;margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);box-shadow:var(--ah-widget-shadow, 0 4px 22px 0 rgba(0, 0, 0, 0.11));box-sizing:border-box}.widget-header{padding:var(--ah-header-padding, 40px 48px 24px)}.widget-body{padding:var(--ah-body-padding, 0 48px 40px)}.logo-wrapper{display:var(--ah-logo-display, flex);justify-content:var(--ah-logo-align, center);margin-bottom:8px}.logo{display:block;height:var(--ah-logo-height, 52px);max-width:100%;width:auto;object-fit:contain}.title{font-size:var(--ah-font-size-title, 24px);font-weight:var(--ah-font-weight-title, 700);text-align:var(--ah-title-align, center);margin:var(--ah-title-margin, 24px 0 8px);color:var(--ah-color-header, #1e212a);line-height:1.2}.description{font-size:var(--ah-font-size-description, 14px);text-align:var(--ah-title-align, center);margin:var(--ah-description-margin, 0 0 8px);color:var(--ah-color-text, #1e212a);line-height:1.5}.message{padding:12px 16px;border-radius:4px;margin-bottom:16px;font-size:14px;line-height:1.5}.message-error{background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38)}.message-success{background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769)}form{display:flex;flex-direction:column}.form-content{display:flex;flex-direction:column}.social-section{display:flex;flex-direction:column;gap:8px;order:var(--ah-social-order, 2)}.fields-section{display:flex;flex-direction:column;order:var(--ah-fields-order, 0)}.divider{display:flex;align-items:center;text-align:center;margin:16px 0;order:var(--ah-divider-order, 1)}.divider::before,.divider::after{content:'';flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0}.links{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:16px}.link-wrapper{font-size:14px;color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400);transition:color 150ms ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.widget-footer{margin-top:16px;text-align:center;font-size:12px;color:var(--ah-color-text-muted, #65676e)}.widget-footer a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:12px;transition:color 150ms ease}.widget-footer a:hover{text-decoration:underline}.widget-footer a:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.loading-spinner{width:32px;height:32px;margin:24px auto;border:3px solid var(--ah-color-border-muted, #e0e1e3);border-top-color:var(--ah-color-primary, #635dff);border-radius:50%;animation:spin 0.8s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.error-message{text-align:center;color:var(--ah-color-error, #d03c38);padding:16px;font-size:14px}@media (max-width: 480px){:host{display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}.widget-container{box-shadow:none;border-radius:0;max-width:none;width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}`;
5754
+ const authheroWidgetCss = () => `:host{display:block;font-family:var(--ah-font-family, 'ulp-font', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, sans-serif);font-size:var(--ah-font-size-base, 14px);line-height:var(--ah-line-height-base, 1.5);color:var(--ah-color-text, #1e212a);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.widget-container{max-width:var(--ah-widget-max-width, 400px);width:100%;margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);box-shadow:var(--ah-widget-shadow, 0 4px 22px 0 rgba(0, 0, 0, 0.11));box-sizing:border-box}.widget-header{padding:var(--ah-header-padding, 40px 48px 24px)}.widget-body{padding:var(--ah-body-padding, 0 48px 40px)}.logo-wrapper{display:var(--ah-logo-display, flex);justify-content:var(--ah-logo-align, center);margin-bottom:8px}.logo{display:block;height:var(--ah-logo-height, 52px);max-width:100%;width:auto;object-fit:contain}.title{font-size:var(--ah-font-size-title, 24px);font-weight:var(--ah-font-weight-title, 700);text-align:var(--ah-title-align, center);margin:var(--ah-title-margin, 24px 0 24px);color:var(--ah-color-header, #1e212a);line-height:1.2}.description{font-size:var(--ah-font-size-description, 14px);text-align:var(--ah-title-align, center);margin:var(--ah-description-margin, 0 0 8px);color:var(--ah-color-text, #1e212a);line-height:1.5}.message{padding:12px 16px;border-radius:4px;margin-bottom:16px;font-size:14px;line-height:1.5}.message-error{background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38)}.message-success{background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769)}form{display:flex;flex-direction:column}.form-content{display:flex;flex-direction:column}.social-section{display:flex;flex-direction:column;gap:8px;order:var(--ah-social-order, 2)}.fields-section{display:flex;flex-direction:column;order:var(--ah-fields-order, 0)}.divider{display:flex;align-items:center;text-align:center;margin:16px 0;order:var(--ah-divider-order, 1)}.divider::before,.divider::after{content:'';flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0}.links{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:16px}.link-wrapper{font-size:14px;color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400);transition:color 150ms ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.widget-footer{margin-top:16px;text-align:center;font-size:12px;color:var(--ah-color-text-muted, #65676e)}.widget-footer a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:12px;transition:color 150ms ease}.widget-footer a:hover{text-decoration:underline}.widget-footer a:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.loading-spinner{width:32px;height:32px;margin:24px auto;border:3px solid var(--ah-color-border-muted, #e0e1e3);border-top-color:var(--ah-color-primary, #635dff);border-radius:50%;animation:spin 0.8s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.error-message{text-align:center;color:var(--ah-color-error, #d03c38);padding:16px;font-size:14px}@media (max-width: 480px){:host{display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}.widget-container{box-shadow:none;border-radius:0;max-width:none;width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}`;
5717
5755
 
5718
5756
  class AuthheroWidget {
5719
5757
  constructor(hostRef) {
@@ -5873,10 +5911,34 @@ class AuthheroWidget {
5873
5911
  this._screen = newValue;
5874
5912
  }
5875
5913
  if (this._screen) {
5914
+ this.formData = {};
5915
+ this.initFormDataFromDefaults(this._screen);
5876
5916
  this.screenChange.emit(this._screen);
5877
5917
  this.updateDataScreenAttribute();
5878
5918
  }
5879
5919
  }
5920
+ /**
5921
+ * Initialize formData from component default_value configs.
5922
+ * This pre-fills form fields with values resolved on the server
5923
+ * (e.g. from user profile context).
5924
+ */
5925
+ initFormDataFromDefaults(screen) {
5926
+ const defaults = {};
5927
+ for (const comp of screen.components || []) {
5928
+ if ("config" in comp &&
5929
+ comp.config &&
5930
+ "default_value" in comp.config &&
5931
+ comp.config.default_value) {
5932
+ const val = comp.config.default_value;
5933
+ if (typeof val === "string" && val !== "") {
5934
+ defaults[comp.id] = val;
5935
+ }
5936
+ }
5937
+ }
5938
+ if (Object.keys(defaults).length > 0) {
5939
+ this.formData = { ...defaults, ...this.formData };
5940
+ }
5941
+ }
5880
5942
  /**
5881
5943
  * Updates the data-screen attribute on the host element.
5882
5944
  * This allows external CSS to target different screens using attribute selectors.
@@ -6113,6 +6175,7 @@ class AuthheroWidget {
6113
6175
  if (currentScreenId && currentScreenId !== this.screenId) {
6114
6176
  this.screenId = currentScreenId;
6115
6177
  }
6178
+ this.initFormDataFromDefaults(this._screen);
6116
6179
  this.screenChange.emit(this._screen);
6117
6180
  this.updateDataScreenAttribute();
6118
6181
  this.persistState();
@@ -6183,10 +6246,19 @@ class AuthheroWidget {
6183
6246
  window.location.href = result.redirect;
6184
6247
  }
6185
6248
  }
6249
+ else if (!response.ok && result.screen) {
6250
+ // Handle validation errors (400 response) — preserve user input
6251
+ this._screen = result.screen;
6252
+ this.initFormDataFromDefaults(result.screen);
6253
+ this.screenChange.emit(result.screen);
6254
+ this.updateDataScreenAttribute();
6255
+ this.focusFirstInput();
6256
+ }
6186
6257
  else if (result.screen) {
6187
- // Next screen
6258
+ // Next screen (success)
6188
6259
  this._screen = result.screen;
6189
6260
  this.formData = {};
6261
+ this.initFormDataFromDefaults(result.screen);
6190
6262
  this.screenChange.emit(result.screen);
6191
6263
  this.updateDataScreenAttribute();
6192
6264
  // Update screenId if returned in response
@@ -6216,13 +6288,6 @@ class AuthheroWidget {
6216
6288
  // Flow complete without redirect
6217
6289
  this.flowComplete.emit({});
6218
6290
  }
6219
- // Handle validation errors (400 response)
6220
- if (!response.ok && result.screen) {
6221
- this._screen = result.screen;
6222
- this.screenChange.emit(result.screen);
6223
- this.updateDataScreenAttribute();
6224
- this.focusFirstInput();
6225
- }
6226
6291
  }
6227
6292
  }
6228
6293
  catch (err) {
package/hydrate/index.mjs CHANGED
@@ -4945,7 +4945,7 @@ var setScopedSSR = (opts) => {
4945
4945
  var needsScopedSSR = () => scopedSSR;
4946
4946
  var scopedSSR = false;
4947
4947
 
4948
- const authheroNodeCss = () => `:host{display:block}.input-wrapper{display:flex;flex-direction:column;position:relative;margin-bottom:16px}.input-container{position:relative;width:100%}.input-label{position:absolute;left:16px;top:50%;transform:translateY(-50%);font-size:16px;font-weight:var(--ah-font-weight-label, 400);color:var(--ah-color-text-muted, #65676e);pointer-events:none;transition:all 0.15s ease-out;background-color:transparent;padding:0;z-index:1}.input-label.floating,.input-field:focus+.input-label,.input-field:not(:placeholder-shown)+.input-label{top:-8px;transform:translateY(0);font-size:12px;background-color:var(--ah-color-bg, #ffffff);padding:0 4px;left:12px;color:var(--ah-color-text-muted, #65676e)}.input-field:focus+.input-label{color:var(--ah-color-primary, #635dff)}.required{color:var(--ah-color-error, #d03c38);margin-left:2px}.input-field{width:100%;padding:16px;font-size:16px;font-family:inherit;color:var(--ah-color-text, #1e212a);background-color:var(--ah-color-input-bg, #ffffff);border:1px solid var(--ah-color-border, #c9cace);border-radius:var(--ah-input-radius, 3px);outline:none;transition:border-color 0.15s ease-out,\\n box-shadow 0.15s ease-out;box-sizing:border-box}.input-field::placeholder{color:transparent}.input-field:hover{border-color:var(--ah-color-border-hover, #65676e)}.input-field:focus{border-color:var(--ah-color-primary, #635dff);box-shadow:inset 0 0 0 1px var(--ah-color-primary, #635dff)}.input-field.has-error{border-color:var(--ah-color-error, #d03c38)}.input-field.has-error:focus{box-shadow:inset 0 0 0 1px var(--ah-color-error, #d03c38)}.input-field:disabled{background-color:var(--ah-color-bg-disabled, #f5f5f5);border-color:var(--ah-color-border-disabled, #e0e1e3);cursor:not-allowed;opacity:0.7}.password-container{position:relative;display:flex;align-items:center}.password-container .input-field{padding-right:48px}.password-toggle{position:absolute;right:12px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;color:var(--ah-color-text-muted, #65676e);transition:color 0.15s ease}.password-toggle:hover{color:var(--ah-color-text, #1e212a)}.password-toggle svg{width:20px;height:20px}.error-text{font-size:12px;color:var(--ah-color-error, #d03c38);margin-top:4px;line-height:1.4}.helper-text{font-size:12px;color:var(--ah-color-text-muted, #65676e);margin-top:4px;line-height:1.4}.field-link{display:block;text-align:left;margin-top:8px;margin-bottom:16px}.field-link a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400)}.field-link a:hover{text-decoration:underline}.checkbox-wrapper{display:flex;align-items:flex-start;gap:10px;cursor:pointer;margin-bottom:16px}.checkbox-wrapper input[type="checkbox"]{width:18px;height:18px;margin:0;accent-color:var(--ah-color-primary, #635dff);cursor:pointer;flex-shrink:0}.checkbox-label{font-size:14px;color:var(--ah-color-text, #1e212a);line-height:1.5}.btn{display:inline-flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:14px 20px;font-size:16px;font-weight:var(--ah-font-weight-btn, 400);font-family:inherit;line-height:1.25;text-align:center;text-decoration:none;border:none;border-radius:var(--ah-btn-radius, 3px);cursor:pointer;transition:background-color 0.15s ease,\\n border-color 0.15s ease,\\n transform 0.1s ease;box-sizing:border-box}.btn:disabled{opacity:0.6;cursor:not-allowed}.btn:not(:disabled):active{transform:scale(0.98)}.btn:focus-visible{outline:2px solid var(--ah-color-primary, #635dff);outline-offset:2px}.btn-primary{background-color:var(--ah-color-primary, #635dff);color:var(--ah-color-text-on-primary, #ffffff);margin-top:12px}.btn-primary:not(:disabled):hover{filter:brightness(0.85)}.btn-secondary{background-color:var(--ah-color-bg, #ffffff);color:var(--ah-color-text, #1e212a);border:1px solid var(--ah-color-border, #c9cace)}.btn-secondary:not(:disabled):hover{background-color:var(--ah-color-bg-hover, #f5f5f5);border-color:var(--ah-color-border-hover, #65676e)}.btn-link{background:none;border:none;color:var(--ah-color-link, #635dff);padding:8px 0;font-weight:var(--ah-font-weight-link, 400);text-decoration:none}.btn-link:hover{text-decoration:underline}.social-buttons{display:flex;flex-direction:column;gap:12px}.btn-social{display:flex;align-items:center;justify-content:center;gap:12px}.btn-social-content{display:flex;flex-direction:column;align-items:center;text-align:center}.btn-social-subtitle{font-size:12px;font-style:italic;opacity:0.8;margin-top:2px}.btn-social-subtitle:empty{display:none}.social-icon{width:20px;height:20px;flex-shrink:0}@media (max-width: 480px){.social-buttons:has(.btn-social:nth-child(3)){flex-direction:row;flex-wrap:nowrap;justify-content:stretch;gap:8px}.social-buttons:has(.btn-social:nth-child(3)) .btn-social{width:auto;min-width:0;padding:12px;flex:1 1 0}.social-buttons:has(.btn-social:nth-child(3)) .btn-social span{display:none}.social-buttons:has(.btn-social:nth-child(3)) .social-icon{width:24px;height:24px}}.btn-icon{width:20px;height:20px;flex-shrink:0}.btn-icon img{width:100%;height:100%;object-fit:contain}.text-title{font-size:20px;font-weight:400;color:var(--ah-color-text, #1e212a);margin:8px 0;line-height:1.3}.text-title.text-success{color:var(--ah-color-success, #13a769)}.text-description{font-size:14px;color:var(--ah-color-text-muted, #65676e);margin:4px 0;line-height:1.5}.image{display:block;max-width:100%;height:auto;border-radius:4px}.image-centered{margin:0 auto 16px;width:52px;height:52px;object-fit:contain}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;transition:color 0.15s ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.node-error{padding:12px 16px;background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38);border-radius:0;font-size:14px;margin-bottom:16px}.node-success{padding:12px 16px;background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769);border-radius:0;font-size:14px;margin-bottom:16px}.divider{display:flex;align-items:center;text-align:center;margin:16px 0}.divider::before,.divider::after{content:"";flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0.5px}.rich-text{font-size:14px;line-height:1.5;color:var(--ah-color-text, #1e212a)}.rich-text a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);transition:color 0.15s ease}.rich-text a:hover{text-decoration:underline}.rich-text .terms-text{margin-top:16px;text-align:center;font-size:12px;color:var(--ah-color-text-muted, #65676e)}.rich-text .terms-text a{font-size:12px}.rich-text .signup-link{margin-top:16px;text-align:center;font-size:14px;color:var(--ah-color-text, #1e212a)}`;
4948
+ const authheroNodeCss = () => `:host{display:block}.input-wrapper{display:flex;flex-direction:column;position:relative;margin-bottom:16px}.input-container{position:relative;width:100%}.input-label{position:absolute;left:16px;top:50%;transform:translateY(-50%);font-size:16px;font-weight:var(--ah-font-weight-label, 400);color:var(--ah-color-text-muted, #65676e);pointer-events:none;transition:all 0.15s ease-out;background-color:transparent;padding:0;z-index:1}.input-label.floating,.input-field:focus+.input-label,.input-field:not(:placeholder-shown)+.input-label,select.input-field+.input-label,input[type="date"].input-field+.input-label{top:-8px;transform:translateY(0);font-size:12px;background-color:var(--ah-color-bg, #ffffff);padding:0 4px;left:12px;color:var(--ah-color-text-muted, #65676e)}.input-field:focus+.input-label{color:var(--ah-color-primary, #635dff)}.required{color:var(--ah-color-error, #d03c38);margin-left:2px}.input-field{width:100%;padding:16px;font-size:16px;font-family:inherit;color:var(--ah-color-text, #1e212a);background-color:var(--ah-color-input-bg, #ffffff);border:1px solid var(--ah-color-border, #c9cace);border-radius:var(--ah-input-radius, 3px);outline:none;transition:border-color 0.15s ease-out,\\n box-shadow 0.15s ease-out;box-sizing:border-box}.input-field::placeholder{color:transparent}.input-field:hover{border-color:var(--ah-color-border-hover, #65676e)}.input-field:focus{border-color:var(--ah-color-primary, #635dff);box-shadow:inset 0 0 0 1px var(--ah-color-primary, #635dff)}.input-field.has-error{border-color:var(--ah-color-error, #d03c38)}.input-field.has-error:focus{box-shadow:inset 0 0 0 1px var(--ah-color-error, #d03c38)}.input-field:disabled{background-color:var(--ah-color-bg-disabled, #f5f5f5);border-color:var(--ah-color-border-disabled, #e0e1e3);cursor:not-allowed;opacity:0.7}.password-container{position:relative;display:flex;align-items:center}.password-container .input-field{padding-right:48px}.password-toggle{position:absolute;right:12px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;color:var(--ah-color-text-muted, #65676e);transition:color 0.15s ease}.password-toggle:hover{color:var(--ah-color-text, #1e212a)}.password-toggle svg{width:20px;height:20px}.error-text{font-size:12px;color:var(--ah-color-error, #d03c38);margin-top:4px;line-height:1.4}.helper-text{font-size:12px;color:var(--ah-color-text-muted, #65676e);margin-top:4px;line-height:1.4}.field-link{display:block;text-align:left;margin-top:8px;margin-bottom:16px}.field-link a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400)}.field-link a:hover{text-decoration:underline}.checkbox-wrapper{display:flex;align-items:flex-start;gap:10px;cursor:pointer;margin-bottom:16px}.checkbox-wrapper input[type="checkbox"]{width:18px;height:18px;margin:0;accent-color:var(--ah-color-primary, #635dff);cursor:pointer;flex-shrink:0}.checkbox-label{font-size:14px;color:var(--ah-color-text, #1e212a);line-height:1.5}.btn{display:inline-flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:14px 20px;font-size:16px;font-weight:var(--ah-font-weight-btn, 400);font-family:inherit;line-height:1.25;text-align:center;text-decoration:none;border:none;border-radius:var(--ah-btn-radius, 3px);cursor:pointer;transition:background-color 0.15s ease,\\n border-color 0.15s ease,\\n transform 0.1s ease;box-sizing:border-box}.btn:disabled{opacity:0.6;cursor:not-allowed}.btn:not(:disabled):active{transform:scale(0.98)}.btn:focus-visible{outline:2px solid var(--ah-color-primary, #635dff);outline-offset:2px}.btn-primary{background-color:var(--ah-color-primary, #635dff);color:var(--ah-color-text-on-primary, #ffffff);margin-top:12px}.btn-primary:not(:disabled):hover{filter:brightness(0.85)}.btn-secondary{background-color:var(--ah-color-bg, #ffffff);color:var(--ah-color-text, #1e212a);border:1px solid var(--ah-color-border, #c9cace)}.btn-secondary:not(:disabled):hover{background-color:var(--ah-color-bg-hover, #f5f5f5);border-color:var(--ah-color-border-hover, #65676e)}.btn-link{background:none;border:none;color:var(--ah-color-link, #635dff);padding:8px 0;font-weight:var(--ah-font-weight-link, 400);text-decoration:none}.btn-link:hover{text-decoration:underline}.social-buttons{display:flex;flex-direction:column;gap:12px}.btn-social{display:flex;align-items:center;justify-content:center;gap:12px}.btn-social-content{display:flex;flex-direction:column;align-items:center;text-align:center}.btn-social-subtitle{font-size:12px;font-style:italic;opacity:0.8;margin-top:2px}.btn-social-subtitle:empty{display:none}.social-icon{width:20px;height:20px;flex-shrink:0}@media (max-width: 480px){.social-buttons:has(.btn-social:nth-child(3)){flex-direction:row;flex-wrap:nowrap;justify-content:stretch;gap:8px}.social-buttons:has(.btn-social:nth-child(3)) .btn-social{width:auto;min-width:0;padding:12px;flex:1 1 0}.social-buttons:has(.btn-social:nth-child(3)) .btn-social span{display:none}.social-buttons:has(.btn-social:nth-child(3)) .social-icon{width:24px;height:24px}}.btn-icon{width:20px;height:20px;flex-shrink:0}.btn-icon img{width:100%;height:100%;object-fit:contain}.text-title{font-size:20px;font-weight:400;color:var(--ah-color-text, #1e212a);margin:8px 0;line-height:1.3}.text-title.text-success{color:var(--ah-color-success, #13a769)}.text-description{font-size:14px;color:var(--ah-color-text-muted, #65676e);margin:4px 0;line-height:1.5}.image{display:block;max-width:100%;height:auto;border-radius:4px}.image-centered{margin:0 auto 16px;width:52px;height:52px;object-fit:contain}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;transition:color 0.15s ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.node-error{padding:12px 16px;background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38);border-radius:0;font-size:14px;margin-bottom:16px}.node-success{padding:12px 16px;background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769);border-radius:0;font-size:14px;margin-bottom:16px}.divider{display:flex;align-items:center;text-align:center;margin:16px 0}.divider::before,.divider::after{content:"";flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0.5px}.rich-text{font-size:14px;line-height:1.5;color:var(--ah-color-text, #1e212a)}.rich-text a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);transition:color 0.15s ease}.rich-text a:hover{text-decoration:underline}.rich-text .terms-text{margin-top:16px;text-align:center;font-size:12px;color:var(--ah-color-text-muted, #65676e)}.rich-text .terms-text a{font-size:12px}.rich-text .forgot-password-link{text-align:right;font-size:13px;margin-top:4px}.rich-text .forgot-password-link a{font-size:13px;color:var(--ah-color-link, #635dff)}.rich-text .signup-link{margin-top:16px;text-align:center;font-size:14px;color:var(--ah-color-text, #1e212a)}`;
4949
4949
 
4950
4950
  class AuthheroNode {
4951
4951
  constructor(hostRef) {
@@ -4995,6 +4995,23 @@ class AuthheroNode {
4995
4995
  value: target.checked ? "true" : "false",
4996
4996
  });
4997
4997
  };
4998
+ /**
4999
+ * Returns the effective value for the field: uses `this.value` if set,
5000
+ * otherwise falls back to `config.default_value` (resolved by the server).
5001
+ */
5002
+ getEffectiveValue() {
5003
+ if (this.value !== undefined && this.value !== null) {
5004
+ return this.value;
5005
+ }
5006
+ const comp = this.component;
5007
+ if (comp.config && "default_value" in comp.config) {
5008
+ const dv = comp.config.default_value;
5009
+ if (typeof dv === "string" && dv !== "") {
5010
+ return dv;
5011
+ }
5012
+ }
5013
+ return undefined;
5014
+ }
4998
5015
  /**
4999
5016
  * Sanitize a string for use in CSS class names and part tokens.
5000
5017
  * Replaces spaces and special characters with hyphens, converts to lowercase.
@@ -5028,7 +5045,9 @@ class AuthheroNode {
5028
5045
  renderFloatingLabel(text, inputId, required, hasValue) {
5029
5046
  if (!text)
5030
5047
  return null;
5031
- return (hAsync("label", { class: { "input-label": true, floating: !!hasValue }, part: "label", htmlFor: inputId }, text, required && hAsync("span", { class: "required" }, "*")));
5048
+ // Use string class instead of object to avoid hydration mismatch
5049
+ const labelClass = hasValue ? "input-label floating" : "input-label";
5050
+ return (hAsync("label", { class: labelClass, part: "label", htmlFor: inputId }, text, required && hAsync("span", { class: "required" }, "*")));
5032
5051
  }
5033
5052
  /**
5034
5053
  * Render a label for a field (non-floating version for checkboxes etc).
@@ -5038,6 +5057,13 @@ class AuthheroNode {
5038
5057
  return null;
5039
5058
  return (hAsync("label", { class: "input-label", part: "label", htmlFor: inputId }, text, required && hAsync("span", { class: "required" }, "*")));
5040
5059
  }
5060
+ /**
5061
+ * Get the input field class string.
5062
+ * Uses string instead of object to avoid hydration mismatch.
5063
+ */
5064
+ getInputFieldClass(hasError) {
5065
+ return hasError ? "input-field has-error" : "input-field";
5066
+ }
5041
5067
  /**
5042
5068
  * Render the eye icon for password visibility toggle.
5043
5069
  */
@@ -5101,46 +5127,55 @@ class AuthheroNode {
5101
5127
  const inputId = `input-${component.id}`;
5102
5128
  const errors = this.getErrors();
5103
5129
  const { multiline, max_length } = component.config ?? {};
5104
- const hasValue = !!(this.value && this.value.length > 0);
5130
+ const effectiveValue = this.getEffectiveValue();
5131
+ const hasValue = !!(effectiveValue && effectiveValue.length > 0);
5105
5132
  if (multiline) {
5106
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("textarea", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input textarea", name: component.id, placeholder: " ", required: component.required, disabled: this.disabled, maxLength: max_length, onInput: this.handleInput }, this.value ?? ""), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5133
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("textarea", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input textarea", name: component.id, placeholder: " ", required: component.required, disabled: this.disabled, maxLength: max_length, onInput: this.handleInput }, effectiveValue ?? ""), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5107
5134
  }
5108
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: component.sensitive ? "password" : "text", name: component.id, "data-input-name": component.id, value: this.value ?? "", placeholder: " ", required: component.required, disabled: this.disabled, maxLength: max_length, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5135
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: component.sensitive ? "password" : "text", name: component.id, "data-input-name": component.id, value: effectiveValue ?? "", placeholder: " ", required: component.required, disabled: this.disabled, maxLength: max_length, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5109
5136
  }
5110
5137
  renderEmailField(component) {
5111
5138
  const inputId = `input-${component.id}`;
5112
5139
  const errors = this.getErrors();
5113
- const hasValue = !!(this.value && this.value.length > 0);
5114
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: "email", name: component.id, "data-input-name": component.id, value: this.value ?? "", placeholder: " ", required: component.required, disabled: this.disabled, autocomplete: "email", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5140
+ const effectiveValue = this.getEffectiveValue();
5141
+ const hasValue = !!(effectiveValue && effectiveValue.length > 0);
5142
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: "email", name: component.id, "data-input-name": component.id, value: effectiveValue ?? "", placeholder: " ", required: component.required, disabled: this.disabled, autocomplete: "email", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5115
5143
  }
5116
5144
  renderPasswordField(component) {
5117
5145
  const inputId = `input-${component.id}`;
5118
5146
  const errors = this.getErrors();
5119
- const hasValue = !!(this.value && this.value.length > 0);
5147
+ const effectiveValue = this.getEffectiveValue();
5148
+ const hasValue = !!(effectiveValue && effectiveValue.length > 0);
5120
5149
  const forgotPasswordLink = component.config?.forgot_password_link;
5121
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container password-container" }, hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: this.passwordVisible ? "text" : "password", name: component.id, "data-input-name": component.id, value: this.value ?? "", placeholder: " ", required: component.required, disabled: this.disabled, minLength: component.config?.min_length, autocomplete: "current-password", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue), hAsync("button", { type: "button", class: "password-toggle", part: "password-toggle", onClick: this.togglePasswordVisibility, "aria-label": "Toggle password visibility", "aria-pressed": this.passwordVisible ? "true" : "false" }, this.renderPasswordToggle())), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint), forgotPasswordLink && (hAsync("div", { class: "field-link", part: "field-link" }, hAsync("a", { href: forgotPasswordLink, class: "link", part: "link" }, "Forgot password?")))));
5150
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container password-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: this.passwordVisible ? "text" : "password", name: component.id, "data-input-name": component.id, value: effectiveValue ?? "", placeholder: " ", required: component.required, disabled: this.disabled, minLength: component.config?.min_length, autocomplete: "current-password", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue), hAsync("button", { type: "button", class: "password-toggle", part: "password-toggle", onClick: this.togglePasswordVisibility, "aria-label": "Toggle password visibility", "aria-pressed": this.passwordVisible ? "true" : "false" }, this.renderPasswordToggle())), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint), forgotPasswordLink && (hAsync("div", { class: "field-link", part: "field-link" }, hAsync("a", { href: forgotPasswordLink, class: "link", part: "link" }, "Forgot password?")))));
5122
5151
  }
5123
5152
  renderNumberField(component) {
5124
5153
  const inputId = `input-${component.id}`;
5125
5154
  const errors = this.getErrors();
5126
5155
  const { placeholder, min, max, step } = component.config ?? {};
5127
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: "number", name: component.id, value: this.value ?? "", placeholder: placeholder, required: component.required, disabled: this.disabled, min: min, max: max, step: step, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5156
+ const effectiveValue = this.getEffectiveValue();
5157
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: "number", name: component.id, value: effectiveValue ?? "", placeholder: placeholder, required: component.required, disabled: this.disabled, min: min, max: max, step: step, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5128
5158
  }
5129
5159
  renderTelField(component) {
5130
5160
  const inputId = `input-${component.id}`;
5131
5161
  const errors = this.getErrors();
5132
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: "tel", name: component.id, value: this.value ?? "", placeholder: component.config?.placeholder, required: component.required, disabled: this.disabled, autocomplete: "tel", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5162
+ const effectiveValue = this.getEffectiveValue();
5163
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: "tel", name: component.id, value: effectiveValue ?? "", placeholder: component.config?.placeholder, required: component.required, disabled: this.disabled, autocomplete: "tel", onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5133
5164
  }
5134
5165
  renderUrlField(component) {
5135
5166
  const inputId = `input-${component.id}`;
5136
5167
  const errors = this.getErrors();
5137
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: "url", name: component.id, value: this.value ?? "", placeholder: component.config?.placeholder, required: component.required, disabled: this.disabled, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5168
+ const effectiveValue = this.getEffectiveValue();
5169
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: "url", name: component.id, value: effectiveValue ?? "", placeholder: component.config?.placeholder, required: component.required, disabled: this.disabled, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5138
5170
  }
5139
5171
  renderDateField(component) {
5140
5172
  const inputId = `input-${component.id}`;
5141
5173
  const errors = this.getErrors();
5142
5174
  const { min, max } = component.config ?? {};
5143
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("input", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input", type: "date", name: component.id, value: this.value ?? "", required: component.required, disabled: this.disabled, min: min, max: max, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5175
+ // Date fields always have a value (even if placeholder format), so always float the label
5176
+ const hasValue = true;
5177
+ const effectiveValue = this.getEffectiveValue();
5178
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: "date", name: component.id, "data-input-name": component.id, value: effectiveValue ?? "", placeholder: " ", required: component.required, disabled: this.disabled, min: min, max: max, onInput: this.handleInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5144
5179
  }
5145
5180
  renderBooleanField(component) {
5146
5181
  return (hAsync("label", { class: "checkbox-wrapper", part: "checkbox-wrapper" }, hAsync("input", { type: "checkbox", part: "checkbox", name: component.id, checked: this.value === "true" || component.config?.default_value === true, required: component.required, disabled: this.disabled, onChange: this.handleCheckbox }), hAsync("span", { class: "checkbox-label", part: "checkbox-label" }, component.label)));
@@ -5154,14 +5189,17 @@ class AuthheroNode {
5154
5189
  const inputId = `input-${component.id}`;
5155
5190
  const errors = this.getErrors();
5156
5191
  const { options, placeholder } = component.config ?? {};
5157
- return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, this.renderLabel(component.label, inputId, component.required), hAsync("select", { id: inputId, class: { "input-field": true, "has-error": errors.length > 0 }, part: "input select", name: component.id, required: component.required, disabled: this.disabled, onChange: this.handleInput }, placeholder && (hAsync("option", { value: "", disabled: true, selected: !this.value }, placeholder)), options?.map((opt) => (hAsync("option", { value: opt.value, selected: this.value === opt.value, key: opt.value }, opt.label)))), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5192
+ // Dropdown always has visual content (selected option), so always float the label
5193
+ const hasValue = true;
5194
+ const effectiveValue = this.getEffectiveValue();
5195
+ return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: "input-container" }, hAsync("select", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input select", name: component.id, required: component.required, disabled: this.disabled, onChange: this.handleInput }, placeholder && (hAsync("option", { value: "", disabled: true, selected: !effectiveValue }, placeholder)), options?.map((opt) => (hAsync("option", { value: opt.value, selected: effectiveValue === opt.value, key: opt.value }, opt.label)))), this.renderFloatingLabel(component.label, inputId, component.required, hasValue)), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5158
5196
  }
5159
5197
  renderChoiceField(component) {
5160
5198
  const errors = this.getErrors();
5161
5199
  const { options, display } = component.config ?? {};
5162
5200
  const isCheckbox = display === "checkbox";
5163
5201
  const inputType = isCheckbox ? "checkbox" : "radio";
5164
- return (hAsync("div", { class: "choice-wrapper", part: "choice-wrapper" }, component.label && (hAsync("span", { class: "choice-label", part: "choice-label" }, component.label, component.required && hAsync("span", { class: "required" }, "*"))), hAsync("div", { class: "choice-options", part: "choice-options" }, options?.map((opt) => (hAsync("label", { class: "choice-option", part: "choice-option", key: opt.value }, hAsync("input", { type: inputType, part: inputType, name: component.id, value: opt.value, checked: this.value === opt.value, required: component.required && !isCheckbox, disabled: this.disabled, onChange: this.handleInput }), hAsync("span", null, opt.label))))), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5202
+ return (hAsync("div", { class: "choice-wrapper", part: "choice-wrapper" }, component.label && (hAsync("span", { class: "choice-label", part: "choice-label" }, component.label, component.required && hAsync("span", { class: "required" }, "*"))), hAsync("div", { class: "choice-options", part: "choice-options" }, options?.map((opt) => (hAsync("label", { class: "choice-option", part: "choice-option", key: opt.value }, hAsync("input", { type: inputType, part: inputType, name: component.id, value: opt.value, checked: this.getEffectiveValue() === opt.value, required: component.required && !isCheckbox, disabled: this.disabled, onChange: this.handleInput }), hAsync("span", null, opt.label))))), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5165
5203
  }
5166
5204
  renderSocialField(component) {
5167
5205
  const providers = component.config?.providers ?? [];
@@ -5711,7 +5749,7 @@ function escapeAttr(value) {
5711
5749
  .replace(/>/g, ">");
5712
5750
  }
5713
5751
 
5714
- const authheroWidgetCss = () => `:host{display:block;font-family:var(--ah-font-family, 'ulp-font', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, sans-serif);font-size:var(--ah-font-size-base, 14px);line-height:var(--ah-line-height-base, 1.5);color:var(--ah-color-text, #1e212a);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.widget-container{max-width:var(--ah-widget-max-width, 400px);width:100%;margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);box-shadow:var(--ah-widget-shadow, 0 4px 22px 0 rgba(0, 0, 0, 0.11));box-sizing:border-box}.widget-header{padding:var(--ah-header-padding, 40px 48px 24px)}.widget-body{padding:var(--ah-body-padding, 0 48px 40px)}.logo-wrapper{display:var(--ah-logo-display, flex);justify-content:var(--ah-logo-align, center);margin-bottom:8px}.logo{display:block;height:var(--ah-logo-height, 52px);max-width:100%;width:auto;object-fit:contain}.title{font-size:var(--ah-font-size-title, 24px);font-weight:var(--ah-font-weight-title, 700);text-align:var(--ah-title-align, center);margin:var(--ah-title-margin, 24px 0 8px);color:var(--ah-color-header, #1e212a);line-height:1.2}.description{font-size:var(--ah-font-size-description, 14px);text-align:var(--ah-title-align, center);margin:var(--ah-description-margin, 0 0 8px);color:var(--ah-color-text, #1e212a);line-height:1.5}.message{padding:12px 16px;border-radius:4px;margin-bottom:16px;font-size:14px;line-height:1.5}.message-error{background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38)}.message-success{background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769)}form{display:flex;flex-direction:column}.form-content{display:flex;flex-direction:column}.social-section{display:flex;flex-direction:column;gap:8px;order:var(--ah-social-order, 2)}.fields-section{display:flex;flex-direction:column;order:var(--ah-fields-order, 0)}.divider{display:flex;align-items:center;text-align:center;margin:16px 0;order:var(--ah-divider-order, 1)}.divider::before,.divider::after{content:'';flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0}.links{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:16px}.link-wrapper{font-size:14px;color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400);transition:color 150ms ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.widget-footer{margin-top:16px;text-align:center;font-size:12px;color:var(--ah-color-text-muted, #65676e)}.widget-footer a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:12px;transition:color 150ms ease}.widget-footer a:hover{text-decoration:underline}.widget-footer a:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.loading-spinner{width:32px;height:32px;margin:24px auto;border:3px solid var(--ah-color-border-muted, #e0e1e3);border-top-color:var(--ah-color-primary, #635dff);border-radius:50%;animation:spin 0.8s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.error-message{text-align:center;color:var(--ah-color-error, #d03c38);padding:16px;font-size:14px}@media (max-width: 480px){:host{display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}.widget-container{box-shadow:none;border-radius:0;max-width:none;width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}`;
5752
+ const authheroWidgetCss = () => `:host{display:block;font-family:var(--ah-font-family, 'ulp-font', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, sans-serif);font-size:var(--ah-font-size-base, 14px);line-height:var(--ah-line-height-base, 1.5);color:var(--ah-color-text, #1e212a);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.widget-container{max-width:var(--ah-widget-max-width, 400px);width:100%;margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);box-shadow:var(--ah-widget-shadow, 0 4px 22px 0 rgba(0, 0, 0, 0.11));box-sizing:border-box}.widget-header{padding:var(--ah-header-padding, 40px 48px 24px)}.widget-body{padding:var(--ah-body-padding, 0 48px 40px)}.logo-wrapper{display:var(--ah-logo-display, flex);justify-content:var(--ah-logo-align, center);margin-bottom:8px}.logo{display:block;height:var(--ah-logo-height, 52px);max-width:100%;width:auto;object-fit:contain}.title{font-size:var(--ah-font-size-title, 24px);font-weight:var(--ah-font-weight-title, 700);text-align:var(--ah-title-align, center);margin:var(--ah-title-margin, 24px 0 24px);color:var(--ah-color-header, #1e212a);line-height:1.2}.description{font-size:var(--ah-font-size-description, 14px);text-align:var(--ah-title-align, center);margin:var(--ah-description-margin, 0 0 8px);color:var(--ah-color-text, #1e212a);line-height:1.5}.message{padding:12px 16px;border-radius:4px;margin-bottom:16px;font-size:14px;line-height:1.5}.message-error{background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38)}.message-success{background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769)}form{display:flex;flex-direction:column}.form-content{display:flex;flex-direction:column}.social-section{display:flex;flex-direction:column;gap:8px;order:var(--ah-social-order, 2)}.fields-section{display:flex;flex-direction:column;order:var(--ah-fields-order, 0)}.divider{display:flex;align-items:center;text-align:center;margin:16px 0;order:var(--ah-divider-order, 1)}.divider::before,.divider::after{content:'';flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0}.links{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:16px}.link-wrapper{font-size:14px;color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400);transition:color 150ms ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.widget-footer{margin-top:16px;text-align:center;font-size:12px;color:var(--ah-color-text-muted, #65676e)}.widget-footer a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:12px;transition:color 150ms ease}.widget-footer a:hover{text-decoration:underline}.widget-footer a:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.loading-spinner{width:32px;height:32px;margin:24px auto;border:3px solid var(--ah-color-border-muted, #e0e1e3);border-top-color:var(--ah-color-primary, #635dff);border-radius:50%;animation:spin 0.8s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.error-message{text-align:center;color:var(--ah-color-error, #d03c38);padding:16px;font-size:14px}@media (max-width: 480px){:host{display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}.widget-container{box-shadow:none;border-radius:0;max-width:none;width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}`;
5715
5753
 
5716
5754
  class AuthheroWidget {
5717
5755
  constructor(hostRef) {
@@ -5871,10 +5909,34 @@ class AuthheroWidget {
5871
5909
  this._screen = newValue;
5872
5910
  }
5873
5911
  if (this._screen) {
5912
+ this.formData = {};
5913
+ this.initFormDataFromDefaults(this._screen);
5874
5914
  this.screenChange.emit(this._screen);
5875
5915
  this.updateDataScreenAttribute();
5876
5916
  }
5877
5917
  }
5918
+ /**
5919
+ * Initialize formData from component default_value configs.
5920
+ * This pre-fills form fields with values resolved on the server
5921
+ * (e.g. from user profile context).
5922
+ */
5923
+ initFormDataFromDefaults(screen) {
5924
+ const defaults = {};
5925
+ for (const comp of screen.components || []) {
5926
+ if ("config" in comp &&
5927
+ comp.config &&
5928
+ "default_value" in comp.config &&
5929
+ comp.config.default_value) {
5930
+ const val = comp.config.default_value;
5931
+ if (typeof val === "string" && val !== "") {
5932
+ defaults[comp.id] = val;
5933
+ }
5934
+ }
5935
+ }
5936
+ if (Object.keys(defaults).length > 0) {
5937
+ this.formData = { ...defaults, ...this.formData };
5938
+ }
5939
+ }
5878
5940
  /**
5879
5941
  * Updates the data-screen attribute on the host element.
5880
5942
  * This allows external CSS to target different screens using attribute selectors.
@@ -6111,6 +6173,7 @@ class AuthheroWidget {
6111
6173
  if (currentScreenId && currentScreenId !== this.screenId) {
6112
6174
  this.screenId = currentScreenId;
6113
6175
  }
6176
+ this.initFormDataFromDefaults(this._screen);
6114
6177
  this.screenChange.emit(this._screen);
6115
6178
  this.updateDataScreenAttribute();
6116
6179
  this.persistState();
@@ -6181,10 +6244,19 @@ class AuthheroWidget {
6181
6244
  window.location.href = result.redirect;
6182
6245
  }
6183
6246
  }
6247
+ else if (!response.ok && result.screen) {
6248
+ // Handle validation errors (400 response) — preserve user input
6249
+ this._screen = result.screen;
6250
+ this.initFormDataFromDefaults(result.screen);
6251
+ this.screenChange.emit(result.screen);
6252
+ this.updateDataScreenAttribute();
6253
+ this.focusFirstInput();
6254
+ }
6184
6255
  else if (result.screen) {
6185
- // Next screen
6256
+ // Next screen (success)
6186
6257
  this._screen = result.screen;
6187
6258
  this.formData = {};
6259
+ this.initFormDataFromDefaults(result.screen);
6188
6260
  this.screenChange.emit(result.screen);
6189
6261
  this.updateDataScreenAttribute();
6190
6262
  // Update screenId if returned in response
@@ -6214,13 +6286,6 @@ class AuthheroWidget {
6214
6286
  // Flow complete without redirect
6215
6287
  this.flowComplete.emit({});
6216
6288
  }
6217
- // Handle validation errors (400 response)
6218
- if (!response.ok && result.screen) {
6219
- this._screen = result.screen;
6220
- this.screenChange.emit(result.screen);
6221
- this.updateDataScreenAttribute();
6222
- this.focusFirstInput();
6223
- }
6224
6289
  }
6225
6290
  }
6226
6291
  catch (err) {