@authhero/widget 0.20.0 → 0.21.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
@@ -5360,7 +5360,9 @@ class AuthheroNode {
5360
5360
  const dialLocal = this.detectDialCodeFromInput(value);
5361
5361
  if (dialLocal !== null) {
5362
5362
  // Dial code matched — strip it from the input and show only local part
5363
- const cleanedLocal = dialLocal.replace(/[^+\d\s\-()]/g, "").replace(/\+/g, "");
5363
+ const cleanedLocal = dialLocal
5364
+ .replace(/[^+\d\s\-()]/g, "")
5365
+ .replace(/\+/g, "");
5364
5366
  target.value = cleanedLocal;
5365
5367
  this.localPhoneNumber = cleanedLocal;
5366
5368
  const fullNumber = `${this.selectedCountry.dialCode}${cleanedLocal}`;
@@ -5596,7 +5598,7 @@ class AuthheroNode {
5596
5598
  // Calculate dynamic width: flag + space + dial code + small padding for dropdown arrow
5597
5599
  const selectedText = `${this.selectedCountry.flag} ${this.selectedCountry.dialCode}`;
5598
5600
  const selectWidth = `${selectedText.length + 1}ch`;
5599
- const countrySelect = showCountryPicker ? (hAsync("select", { class: "country-select", part: "country-select", style: { width: selectWidth, minWidth: "0" }, onChange: this.handleCountryChange, disabled: this.disabled, "aria-label": "Country code" }, countries.map((country) => (hAsync("option", { value: country.code, selected: this.selectedCountry.code === country.code, key: country.code }, country.flag, " ", country.dialCode))))) : null;
5601
+ const countrySelect = showCountryPicker ? (hAsync("select", { class: "country-select", part: "country-select", style: { width: selectWidth, minWidth: "0" }, onChange: this.handleCountryChange, disabled: this.disabled, "aria-label": "Country code" }, countries.map((country) => (hAsync("option", { value: country.code, selected: this.selectedCountry.code === country.code, key: country.code }, `${country.flag} ${country.dialCode}`))))) : null;
5600
5602
  const inputType = allowEmail && this.telEmailMode ? "text" : "tel";
5601
5603
  return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: showCountryPicker ? "phone-input-wrapper" : "", part: "phone-input-wrapper" }, countrySelect, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: inputType, name: component.id, "data-input-name": component.id, value: this.localPhoneNumber, placeholder: " ", required: component.required, disabled: this.disabled, autocomplete: allowEmail && this.telEmailMode ? "email" : "tel-national", onInput: this.handlePhoneInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue))), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5602
5604
  }
@@ -5675,7 +5677,21 @@ class AuthheroNode {
5675
5677
  const safeProvider = this.sanitizeForCssToken(provider);
5676
5678
  const strategy = getProviderStrategy(provider);
5677
5679
  const icon = getProviderIcon(provider);
5678
- return (hAsync("button", { type: "button", class: `btn btn-secondary btn-social btn-social-${safeProvider}${icon ? "" : " no-icon"}`, part: `button button-secondary button-social button-social-${safeProvider}`, "data-connection-name": provider, "data-strategy": strategy, disabled: this.disabled, onClick: (e) => this.handleButtonClick(e, "SOCIAL", provider), key: provider }, icon, hAsync("span", { class: "btn-social-content", part: `button-social-content button-social-content-${safeProvider}` }, hAsync("span", { part: `button-social-text button-social-text-${safeProvider}` }, getButtonText(provider)), hAsync("span", { class: "btn-social-subtitle", part: `button-social-subtitle button-social-subtitle-${safeProvider}` }))));
5680
+ const details = detailsMap.get(provider);
5681
+ const btnClass = `btn btn-secondary btn-social btn-social-${safeProvider}${icon ? "" : " no-icon"}`;
5682
+ const btnPart = `button button-secondary button-social button-social-${safeProvider}`;
5683
+ const content = [
5684
+ icon,
5685
+ hAsync("span", { class: "btn-social-content", part: `button-social-content button-social-content-${safeProvider}` }, hAsync("span", { part: `button-social-text button-social-text-${safeProvider}` }, getButtonText(provider)), hAsync("span", { class: "btn-social-subtitle", part: `button-social-subtitle button-social-subtitle-${safeProvider}` })),
5686
+ ];
5687
+ if (details?.href) {
5688
+ return (hAsync("a", { href: this.disabled ? undefined : details.href, class: btnClass, part: btnPart, "data-connection-name": provider, "data-strategy": strategy, key: provider, "aria-disabled": this.disabled ? "true" : undefined, tabindex: this.disabled ? -1 : undefined, onClick: (e) => {
5689
+ if (this.disabled) {
5690
+ e.preventDefault();
5691
+ }
5692
+ } }, content));
5693
+ }
5694
+ return (hAsync("button", { type: "button", class: btnClass, part: btnPart, "data-connection-name": provider, "data-strategy": strategy, disabled: this.disabled, onClick: (e) => this.handleButtonClick(e, "SOCIAL", provider), key: provider }, content));
5679
5695
  })));
5680
5696
  }
5681
5697
  // ===========================================================================
@@ -5783,6 +5799,76 @@ class AuthheroNode {
5783
5799
  *
5784
5800
  * Converts AuthHero branding and theme configurations to CSS custom properties.
5785
5801
  */
5802
+ // --- Inline WCAG contrast utilities (small footprint, no external deps) ---
5803
+ function parseHexColor(hex) {
5804
+ const match = hex.match(/^#([0-9a-f]{3})$/i) || hex.match(/^#([0-9a-f]{6})$/i);
5805
+ if (!match)
5806
+ return null;
5807
+ let clean = match[1];
5808
+ if (clean.length === 3) {
5809
+ clean = clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
5810
+ }
5811
+ const num = parseInt(clean, 16);
5812
+ return [(num >> 16) & 255, (num >> 8) & 255, num & 255];
5813
+ }
5814
+ function srgbLuminance(hex) {
5815
+ const rgb = parseHexColor(hex);
5816
+ if (!rgb)
5817
+ return NaN;
5818
+ const [r, g, b] = rgb.map((c) => {
5819
+ const s = c / 255;
5820
+ return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
5821
+ });
5822
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
5823
+ }
5824
+ function wcagContrastRatio(hex1, hex2) {
5825
+ const l1 = srgbLuminance(hex1);
5826
+ const l2 = srgbLuminance(hex2);
5827
+ if (isNaN(l1) || isNaN(l2))
5828
+ return NaN;
5829
+ return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
5830
+ }
5831
+ function autoTextColor(backgroundHex, mode = "light") {
5832
+ const blackContrast = wcagContrastRatio(backgroundHex, "#000000");
5833
+ const whiteContrast = wcagContrastRatio(backgroundHex, "#ffffff");
5834
+ const BIAS = 1.35;
5835
+ if (mode === "light") {
5836
+ return blackContrast > whiteContrast * BIAS ? "#000000" : "#ffffff";
5837
+ }
5838
+ return blackContrast * BIAS > whiteContrast ? "#000000" : "#ffffff";
5839
+ }
5840
+ function darkenHex(hex, percent) {
5841
+ const rgb = parseHexColor(hex);
5842
+ if (!rgb)
5843
+ return hex;
5844
+ const [r, g, b] = rgb;
5845
+ const toHex = (v) => Math.max(0, Math.round(v * (1 - percent)))
5846
+ .toString(16)
5847
+ .padStart(2, "0");
5848
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
5849
+ }
5850
+ function lightenHex(hex, percent) {
5851
+ const rgb = parseHexColor(hex);
5852
+ if (!rgb)
5853
+ return hex;
5854
+ const [r, g, b] = rgb;
5855
+ const toHex = (v) => Math.min(255, Math.round(v + (255 - v) * percent))
5856
+ .toString(16)
5857
+ .padStart(2, "0");
5858
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
5859
+ }
5860
+ function ensureContrastColor(fg, bg, minRatio = 4.5) {
5861
+ if (wcagContrastRatio(fg, bg) >= minRatio)
5862
+ return fg;
5863
+ const shouldDarken = srgbLuminance(bg) > 0.5;
5864
+ let adjusted = fg;
5865
+ for (let i = 1; i <= 10; i++) {
5866
+ adjusted = shouldDarken ? darkenHex(fg, i * 0.1) : lightenHex(fg, i * 0.1);
5867
+ if (wcagContrastRatio(adjusted, bg) >= minRatio)
5868
+ return adjusted;
5869
+ }
5870
+ return shouldDarken ? "#000000" : "#ffffff";
5871
+ }
5786
5872
  /**
5787
5873
  * Convert a style enum to border radius value
5788
5874
  */
@@ -5879,9 +5965,22 @@ function themeToCssVars(theme) {
5879
5965
  if (c.primary_button) {
5880
5966
  vars["--ah-color-primary"] = c.primary_button;
5881
5967
  vars["--ah-color-primary-hover"] = c.primary_button;
5968
+ // Auto-compute text-on-primary for contrast
5969
+ const hasGoodExplicit = c.primary_button_label &&
5970
+ wcagContrastRatio(c.primary_button_label, c.primary_button) >= 4.5;
5971
+ if (hasGoodExplicit) {
5972
+ vars["--ah-color-text-on-primary"] = c.primary_button_label;
5973
+ }
5974
+ else {
5975
+ vars["--ah-color-text-on-primary"] = autoTextColor(c.primary_button, "light");
5976
+ const darkText = autoTextColor(c.primary_button, "dark");
5977
+ if (darkText !== vars["--ah-color-text-on-primary"]) {
5978
+ vars["--ah-color-text-on-primary-dark"] = darkText;
5979
+ }
5980
+ }
5882
5981
  }
5883
- if (c.primary_button_label) {
5884
- vars["--ah-btn-primary-text"] = c.primary_button_label;
5982
+ else if (c.primary_button_label) {
5983
+ vars["--ah-color-text-on-primary"] = c.primary_button_label;
5885
5984
  }
5886
5985
  // Secondary button
5887
5986
  if (c.secondary_button_border) {
@@ -5918,9 +6017,10 @@ function themeToCssVars(theme) {
5918
6017
  if (c.input_border) {
5919
6018
  vars["--ah-color-border"] = c.input_border;
5920
6019
  }
5921
- // Links
6020
+ // Links — ensure contrast against widget background
5922
6021
  if (c.links_focused_components) {
5923
- vars["--ah-color-link"] = c.links_focused_components;
6022
+ const widgetBg = c.widget_background || "#ffffff";
6023
+ vars["--ah-color-link"] = ensureContrastColor(c.links_focused_components, widgetBg);
5924
6024
  }
5925
6025
  // Focus/hover
5926
6026
  if (c.base_focus_color) {
@@ -5946,6 +6046,10 @@ function themeToCssVars(theme) {
5946
6046
  const f = theme.fonts;
5947
6047
  // reference_text_size is the base font size in pixels (default 16px)
5948
6048
  const baseSize = f.reference_text_size || 16;
6049
+ // Font sizes can be stored as percentages (Auth0 convention, e.g. 150 = 150% of base)
6050
+ // or as absolute pixel values (e.g. 24 = 24px). Values >= 50 are treated as
6051
+ // percentages; values < 50 are treated as direct pixel values.
6052
+ const fontSizePx = (size) => size >= 50 ? Math.round((size / 100) * baseSize) : size;
5949
6053
  if (f.font_url) {
5950
6054
  vars["--ah-font-url"] = f.font_url;
5951
6055
  }
@@ -5954,28 +6058,22 @@ function themeToCssVars(theme) {
5954
6058
  }
5955
6059
  // Title, subtitle, etc. sizes are percentages of the base size
5956
6060
  if (f.title?.size) {
5957
- const titlePx = Math.round((f.title.size / 100) * baseSize);
5958
- vars["--ah-font-size-title"] = `${titlePx}px`;
6061
+ vars["--ah-font-size-title"] = `${fontSizePx(f.title.size)}px`;
5959
6062
  }
5960
6063
  if (f.subtitle?.size) {
5961
- const subtitlePx = Math.round((f.subtitle.size / 100) * baseSize);
5962
- vars["--ah-font-size-subtitle"] = `${subtitlePx}px`;
6064
+ vars["--ah-font-size-subtitle"] = `${fontSizePx(f.subtitle.size)}px`;
5963
6065
  }
5964
6066
  if (f.body_text?.size) {
5965
- const bodyPx = Math.round((f.body_text.size / 100) * baseSize);
5966
- vars["--ah-font-size-body"] = `${bodyPx}px`;
6067
+ vars["--ah-font-size-body"] = `${fontSizePx(f.body_text.size)}px`;
5967
6068
  }
5968
6069
  if (f.input_labels?.size) {
5969
- const labelPx = Math.round((f.input_labels.size / 100) * baseSize);
5970
- vars["--ah-font-size-label"] = `${labelPx}px`;
6070
+ vars["--ah-font-size-label"] = `${fontSizePx(f.input_labels.size)}px`;
5971
6071
  }
5972
6072
  if (f.buttons_text?.size) {
5973
- const btnPx = Math.round((f.buttons_text.size / 100) * baseSize);
5974
- vars["--ah-font-size-btn"] = `${btnPx}px`;
6073
+ vars["--ah-font-size-btn"] = `${fontSizePx(f.buttons_text.size)}px`;
5975
6074
  }
5976
6075
  if (f.links?.size) {
5977
- const linkPx = Math.round((f.links.size / 100) * baseSize);
5978
- vars["--ah-font-size-link"] = `${linkPx}px`;
6076
+ vars["--ah-font-size-link"] = `${fontSizePx(f.links.size)}px`;
5979
6077
  }
5980
6078
  if (f.links_style === "underlined") {
5981
6079
  vars["--ah-link-decoration"] = "underline";
@@ -6803,6 +6901,18 @@ class AuthheroWidget {
6803
6901
  this.buttonClick.emit(detail);
6804
6902
  // Handle social login if autoNavigate is enabled
6805
6903
  if (detail.type === "SOCIAL" && detail.value && this.shouldAutoNavigate) {
6904
+ // Check if this provider has an href (e.g. passwordless connections)
6905
+ const providerHref = this.getProviderHref(detail.value);
6906
+ if (providerHref) {
6907
+ const screenId = this.extractScreenIdFromHref(providerHref);
6908
+ if (screenId && this.apiUrl) {
6909
+ this.navigateToScreen(screenId, providerHref);
6910
+ }
6911
+ else {
6912
+ window.location.href = providerHref;
6913
+ }
6914
+ return;
6915
+ }
6806
6916
  this.handleSocialLogin(detail.value);
6807
6917
  return;
6808
6918
  }
@@ -6926,6 +7036,23 @@ class AuthheroWidget {
6926
7036
  window.location.href = displayUrl;
6927
7037
  }
6928
7038
  }
7039
+ /**
7040
+ * Look up the href for a social provider from the current screen's SOCIAL components.
7041
+ * Returns the href if found (e.g. for passwordless connections), or null.
7042
+ */
7043
+ getProviderHref(connectionName) {
7044
+ if (!this._screen)
7045
+ return null;
7046
+ for (const comp of this._screen.components) {
7047
+ const c = comp;
7048
+ if (c.type === "SOCIAL" && c.config?.provider_details) {
7049
+ const match = c.config.provider_details.find((d) => d.name === connectionName);
7050
+ if (match?.href)
7051
+ return match.href;
7052
+ }
7053
+ }
7054
+ return null;
7055
+ }
6929
7056
  /**
6930
7057
  * Check if a component is a social button.
6931
7058
  */
package/hydrate/index.mjs CHANGED
@@ -5358,7 +5358,9 @@ class AuthheroNode {
5358
5358
  const dialLocal = this.detectDialCodeFromInput(value);
5359
5359
  if (dialLocal !== null) {
5360
5360
  // Dial code matched — strip it from the input and show only local part
5361
- const cleanedLocal = dialLocal.replace(/[^+\d\s\-()]/g, "").replace(/\+/g, "");
5361
+ const cleanedLocal = dialLocal
5362
+ .replace(/[^+\d\s\-()]/g, "")
5363
+ .replace(/\+/g, "");
5362
5364
  target.value = cleanedLocal;
5363
5365
  this.localPhoneNumber = cleanedLocal;
5364
5366
  const fullNumber = `${this.selectedCountry.dialCode}${cleanedLocal}`;
@@ -5594,7 +5596,7 @@ class AuthheroNode {
5594
5596
  // Calculate dynamic width: flag + space + dial code + small padding for dropdown arrow
5595
5597
  const selectedText = `${this.selectedCountry.flag} ${this.selectedCountry.dialCode}`;
5596
5598
  const selectWidth = `${selectedText.length + 1}ch`;
5597
- const countrySelect = showCountryPicker ? (hAsync("select", { class: "country-select", part: "country-select", style: { width: selectWidth, minWidth: "0" }, onChange: this.handleCountryChange, disabled: this.disabled, "aria-label": "Country code" }, countries.map((country) => (hAsync("option", { value: country.code, selected: this.selectedCountry.code === country.code, key: country.code }, country.flag, " ", country.dialCode))))) : null;
5599
+ const countrySelect = showCountryPicker ? (hAsync("select", { class: "country-select", part: "country-select", style: { width: selectWidth, minWidth: "0" }, onChange: this.handleCountryChange, disabled: this.disabled, "aria-label": "Country code" }, countries.map((country) => (hAsync("option", { value: country.code, selected: this.selectedCountry.code === country.code, key: country.code }, `${country.flag} ${country.dialCode}`))))) : null;
5598
5600
  const inputType = allowEmail && this.telEmailMode ? "text" : "tel";
5599
5601
  return (hAsync("div", { class: "input-wrapper", part: "input-wrapper" }, hAsync("div", { class: showCountryPicker ? "phone-input-wrapper" : "", part: "phone-input-wrapper" }, countrySelect, hAsync("div", { class: "input-container" }, hAsync("input", { id: inputId, class: this.getInputFieldClass(errors.length > 0), part: "input", type: inputType, name: component.id, "data-input-name": component.id, value: this.localPhoneNumber, placeholder: " ", required: component.required, disabled: this.disabled, autocomplete: allowEmail && this.telEmailMode ? "email" : "tel-national", onInput: this.handlePhoneInput, onKeyDown: this.handleKeyDown }), this.renderFloatingLabel(component.label, inputId, component.required, hasValue))), this.renderErrors(), errors.length === 0 && this.renderHint(component.hint)));
5600
5602
  }
@@ -5673,7 +5675,21 @@ class AuthheroNode {
5673
5675
  const safeProvider = this.sanitizeForCssToken(provider);
5674
5676
  const strategy = getProviderStrategy(provider);
5675
5677
  const icon = getProviderIcon(provider);
5676
- return (hAsync("button", { type: "button", class: `btn btn-secondary btn-social btn-social-${safeProvider}${icon ? "" : " no-icon"}`, part: `button button-secondary button-social button-social-${safeProvider}`, "data-connection-name": provider, "data-strategy": strategy, disabled: this.disabled, onClick: (e) => this.handleButtonClick(e, "SOCIAL", provider), key: provider }, icon, hAsync("span", { class: "btn-social-content", part: `button-social-content button-social-content-${safeProvider}` }, hAsync("span", { part: `button-social-text button-social-text-${safeProvider}` }, getButtonText(provider)), hAsync("span", { class: "btn-social-subtitle", part: `button-social-subtitle button-social-subtitle-${safeProvider}` }))));
5678
+ const details = detailsMap.get(provider);
5679
+ const btnClass = `btn btn-secondary btn-social btn-social-${safeProvider}${icon ? "" : " no-icon"}`;
5680
+ const btnPart = `button button-secondary button-social button-social-${safeProvider}`;
5681
+ const content = [
5682
+ icon,
5683
+ hAsync("span", { class: "btn-social-content", part: `button-social-content button-social-content-${safeProvider}` }, hAsync("span", { part: `button-social-text button-social-text-${safeProvider}` }, getButtonText(provider)), hAsync("span", { class: "btn-social-subtitle", part: `button-social-subtitle button-social-subtitle-${safeProvider}` })),
5684
+ ];
5685
+ if (details?.href) {
5686
+ return (hAsync("a", { href: this.disabled ? undefined : details.href, class: btnClass, part: btnPart, "data-connection-name": provider, "data-strategy": strategy, key: provider, "aria-disabled": this.disabled ? "true" : undefined, tabindex: this.disabled ? -1 : undefined, onClick: (e) => {
5687
+ if (this.disabled) {
5688
+ e.preventDefault();
5689
+ }
5690
+ } }, content));
5691
+ }
5692
+ return (hAsync("button", { type: "button", class: btnClass, part: btnPart, "data-connection-name": provider, "data-strategy": strategy, disabled: this.disabled, onClick: (e) => this.handleButtonClick(e, "SOCIAL", provider), key: provider }, content));
5677
5693
  })));
5678
5694
  }
5679
5695
  // ===========================================================================
@@ -5781,6 +5797,76 @@ class AuthheroNode {
5781
5797
  *
5782
5798
  * Converts AuthHero branding and theme configurations to CSS custom properties.
5783
5799
  */
5800
+ // --- Inline WCAG contrast utilities (small footprint, no external deps) ---
5801
+ function parseHexColor(hex) {
5802
+ const match = hex.match(/^#([0-9a-f]{3})$/i) || hex.match(/^#([0-9a-f]{6})$/i);
5803
+ if (!match)
5804
+ return null;
5805
+ let clean = match[1];
5806
+ if (clean.length === 3) {
5807
+ clean = clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
5808
+ }
5809
+ const num = parseInt(clean, 16);
5810
+ return [(num >> 16) & 255, (num >> 8) & 255, num & 255];
5811
+ }
5812
+ function srgbLuminance(hex) {
5813
+ const rgb = parseHexColor(hex);
5814
+ if (!rgb)
5815
+ return NaN;
5816
+ const [r, g, b] = rgb.map((c) => {
5817
+ const s = c / 255;
5818
+ return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
5819
+ });
5820
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
5821
+ }
5822
+ function wcagContrastRatio(hex1, hex2) {
5823
+ const l1 = srgbLuminance(hex1);
5824
+ const l2 = srgbLuminance(hex2);
5825
+ if (isNaN(l1) || isNaN(l2))
5826
+ return NaN;
5827
+ return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
5828
+ }
5829
+ function autoTextColor(backgroundHex, mode = "light") {
5830
+ const blackContrast = wcagContrastRatio(backgroundHex, "#000000");
5831
+ const whiteContrast = wcagContrastRatio(backgroundHex, "#ffffff");
5832
+ const BIAS = 1.35;
5833
+ if (mode === "light") {
5834
+ return blackContrast > whiteContrast * BIAS ? "#000000" : "#ffffff";
5835
+ }
5836
+ return blackContrast * BIAS > whiteContrast ? "#000000" : "#ffffff";
5837
+ }
5838
+ function darkenHex(hex, percent) {
5839
+ const rgb = parseHexColor(hex);
5840
+ if (!rgb)
5841
+ return hex;
5842
+ const [r, g, b] = rgb;
5843
+ const toHex = (v) => Math.max(0, Math.round(v * (1 - percent)))
5844
+ .toString(16)
5845
+ .padStart(2, "0");
5846
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
5847
+ }
5848
+ function lightenHex(hex, percent) {
5849
+ const rgb = parseHexColor(hex);
5850
+ if (!rgb)
5851
+ return hex;
5852
+ const [r, g, b] = rgb;
5853
+ const toHex = (v) => Math.min(255, Math.round(v + (255 - v) * percent))
5854
+ .toString(16)
5855
+ .padStart(2, "0");
5856
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
5857
+ }
5858
+ function ensureContrastColor(fg, bg, minRatio = 4.5) {
5859
+ if (wcagContrastRatio(fg, bg) >= minRatio)
5860
+ return fg;
5861
+ const shouldDarken = srgbLuminance(bg) > 0.5;
5862
+ let adjusted = fg;
5863
+ for (let i = 1; i <= 10; i++) {
5864
+ adjusted = shouldDarken ? darkenHex(fg, i * 0.1) : lightenHex(fg, i * 0.1);
5865
+ if (wcagContrastRatio(adjusted, bg) >= minRatio)
5866
+ return adjusted;
5867
+ }
5868
+ return shouldDarken ? "#000000" : "#ffffff";
5869
+ }
5784
5870
  /**
5785
5871
  * Convert a style enum to border radius value
5786
5872
  */
@@ -5877,9 +5963,22 @@ function themeToCssVars(theme) {
5877
5963
  if (c.primary_button) {
5878
5964
  vars["--ah-color-primary"] = c.primary_button;
5879
5965
  vars["--ah-color-primary-hover"] = c.primary_button;
5966
+ // Auto-compute text-on-primary for contrast
5967
+ const hasGoodExplicit = c.primary_button_label &&
5968
+ wcagContrastRatio(c.primary_button_label, c.primary_button) >= 4.5;
5969
+ if (hasGoodExplicit) {
5970
+ vars["--ah-color-text-on-primary"] = c.primary_button_label;
5971
+ }
5972
+ else {
5973
+ vars["--ah-color-text-on-primary"] = autoTextColor(c.primary_button, "light");
5974
+ const darkText = autoTextColor(c.primary_button, "dark");
5975
+ if (darkText !== vars["--ah-color-text-on-primary"]) {
5976
+ vars["--ah-color-text-on-primary-dark"] = darkText;
5977
+ }
5978
+ }
5880
5979
  }
5881
- if (c.primary_button_label) {
5882
- vars["--ah-btn-primary-text"] = c.primary_button_label;
5980
+ else if (c.primary_button_label) {
5981
+ vars["--ah-color-text-on-primary"] = c.primary_button_label;
5883
5982
  }
5884
5983
  // Secondary button
5885
5984
  if (c.secondary_button_border) {
@@ -5916,9 +6015,10 @@ function themeToCssVars(theme) {
5916
6015
  if (c.input_border) {
5917
6016
  vars["--ah-color-border"] = c.input_border;
5918
6017
  }
5919
- // Links
6018
+ // Links — ensure contrast against widget background
5920
6019
  if (c.links_focused_components) {
5921
- vars["--ah-color-link"] = c.links_focused_components;
6020
+ const widgetBg = c.widget_background || "#ffffff";
6021
+ vars["--ah-color-link"] = ensureContrastColor(c.links_focused_components, widgetBg);
5922
6022
  }
5923
6023
  // Focus/hover
5924
6024
  if (c.base_focus_color) {
@@ -5944,6 +6044,10 @@ function themeToCssVars(theme) {
5944
6044
  const f = theme.fonts;
5945
6045
  // reference_text_size is the base font size in pixels (default 16px)
5946
6046
  const baseSize = f.reference_text_size || 16;
6047
+ // Font sizes can be stored as percentages (Auth0 convention, e.g. 150 = 150% of base)
6048
+ // or as absolute pixel values (e.g. 24 = 24px). Values >= 50 are treated as
6049
+ // percentages; values < 50 are treated as direct pixel values.
6050
+ const fontSizePx = (size) => size >= 50 ? Math.round((size / 100) * baseSize) : size;
5947
6051
  if (f.font_url) {
5948
6052
  vars["--ah-font-url"] = f.font_url;
5949
6053
  }
@@ -5952,28 +6056,22 @@ function themeToCssVars(theme) {
5952
6056
  }
5953
6057
  // Title, subtitle, etc. sizes are percentages of the base size
5954
6058
  if (f.title?.size) {
5955
- const titlePx = Math.round((f.title.size / 100) * baseSize);
5956
- vars["--ah-font-size-title"] = `${titlePx}px`;
6059
+ vars["--ah-font-size-title"] = `${fontSizePx(f.title.size)}px`;
5957
6060
  }
5958
6061
  if (f.subtitle?.size) {
5959
- const subtitlePx = Math.round((f.subtitle.size / 100) * baseSize);
5960
- vars["--ah-font-size-subtitle"] = `${subtitlePx}px`;
6062
+ vars["--ah-font-size-subtitle"] = `${fontSizePx(f.subtitle.size)}px`;
5961
6063
  }
5962
6064
  if (f.body_text?.size) {
5963
- const bodyPx = Math.round((f.body_text.size / 100) * baseSize);
5964
- vars["--ah-font-size-body"] = `${bodyPx}px`;
6065
+ vars["--ah-font-size-body"] = `${fontSizePx(f.body_text.size)}px`;
5965
6066
  }
5966
6067
  if (f.input_labels?.size) {
5967
- const labelPx = Math.round((f.input_labels.size / 100) * baseSize);
5968
- vars["--ah-font-size-label"] = `${labelPx}px`;
6068
+ vars["--ah-font-size-label"] = `${fontSizePx(f.input_labels.size)}px`;
5969
6069
  }
5970
6070
  if (f.buttons_text?.size) {
5971
- const btnPx = Math.round((f.buttons_text.size / 100) * baseSize);
5972
- vars["--ah-font-size-btn"] = `${btnPx}px`;
6071
+ vars["--ah-font-size-btn"] = `${fontSizePx(f.buttons_text.size)}px`;
5973
6072
  }
5974
6073
  if (f.links?.size) {
5975
- const linkPx = Math.round((f.links.size / 100) * baseSize);
5976
- vars["--ah-font-size-link"] = `${linkPx}px`;
6074
+ vars["--ah-font-size-link"] = `${fontSizePx(f.links.size)}px`;
5977
6075
  }
5978
6076
  if (f.links_style === "underlined") {
5979
6077
  vars["--ah-link-decoration"] = "underline";
@@ -6801,6 +6899,18 @@ class AuthheroWidget {
6801
6899
  this.buttonClick.emit(detail);
6802
6900
  // Handle social login if autoNavigate is enabled
6803
6901
  if (detail.type === "SOCIAL" && detail.value && this.shouldAutoNavigate) {
6902
+ // Check if this provider has an href (e.g. passwordless connections)
6903
+ const providerHref = this.getProviderHref(detail.value);
6904
+ if (providerHref) {
6905
+ const screenId = this.extractScreenIdFromHref(providerHref);
6906
+ if (screenId && this.apiUrl) {
6907
+ this.navigateToScreen(screenId, providerHref);
6908
+ }
6909
+ else {
6910
+ window.location.href = providerHref;
6911
+ }
6912
+ return;
6913
+ }
6804
6914
  this.handleSocialLogin(detail.value);
6805
6915
  return;
6806
6916
  }
@@ -6924,6 +7034,23 @@ class AuthheroWidget {
6924
7034
  window.location.href = displayUrl;
6925
7035
  }
6926
7036
  }
7037
+ /**
7038
+ * Look up the href for a social provider from the current screen's SOCIAL components.
7039
+ * Returns the href if found (e.g. for passwordless connections), or null.
7040
+ */
7041
+ getProviderHref(connectionName) {
7042
+ if (!this._screen)
7043
+ return null;
7044
+ for (const comp of this._screen.components) {
7045
+ const c = comp;
7046
+ if (c.type === "SOCIAL" && c.config?.provider_details) {
7047
+ const match = c.config.provider_details.find((d) => d.name === connectionName);
7048
+ if (match?.href)
7049
+ return match.href;
7050
+ }
7051
+ }
7052
+ return null;
7053
+ }
6927
7054
  /**
6928
7055
  * Check if a component is a social button.
6929
7056
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authhero/widget",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Server-Driven UI widget for AuthHero authentication flows",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,7 +37,7 @@
37
37
  }
38
38
  },
39
39
  "dependencies": {
40
- "@authhero/adapter-interfaces": "0.146.0"
40
+ "@authhero/adapter-interfaces": "0.147.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@hono/node-server": "^1.14.1",
@@ -1 +0,0 @@
1
- import{r as t,c as e,g as i,h as s}from"./p-BFP_5sHV.js";function o(t,e){if(void 0!==e)return`${e}px`;switch(t){case"pill":return"9999px";case"rounded":return"8px";case"sharp":return"0";default:return}}function r(t){if(!t)return{};const e={};if(t.colors?.primary&&(e["--ah-color-primary"]=t.colors.primary,e["--ah-color-primary-hover"]=t.colors.primary),t.colors?.page_background){const i=t.colors.page_background;"solid"===i.type&&i.start?e["--ah-page-bg"]=i.start:"gradient"===i.type&&i.start&&i.end&&(e["--ah-page-bg"]=`linear-gradient(${i.angle_deg??180}deg, ${i.start}, ${i.end})`)}return t.logo_url&&(e["--ah-logo-url"]=`url(${t.logo_url})`),t.font?.url&&(e["--ah-font-url"]=t.font.url),e}function n(t){if(!t)return{};const e={};if(t.borders){const i=t.borders;void 0!==i.widget_corner_radius&&(e["--ah-widget-radius"]=`${i.widget_corner_radius}px`),void 0!==i.widget_border_weight&&(e["--ah-widget-border-width"]=`${i.widget_border_weight}px`),!1===i.show_widget_shadow&&(e["--ah-widget-shadow"]="none");const s=o(i.buttons_style,i.button_border_radius);s&&(e["--ah-btn-radius"]=s),void 0!==i.button_border_weight&&(e["--ah-btn-border-width"]=`${i.button_border_weight}px`);const r=o(i.inputs_style,i.input_border_radius);r&&(e["--ah-input-radius"]=r),void 0!==i.input_border_weight&&(e["--ah-input-border-width"]=`${i.input_border_weight}px`)}if(t.colors){const i=t.colors;i.primary_button&&(e["--ah-color-primary"]=i.primary_button,e["--ah-color-primary-hover"]=i.primary_button),i.primary_button_label&&(e["--ah-btn-primary-text"]=i.primary_button_label),i.secondary_button_border&&(e["--ah-btn-secondary-border"]=i.secondary_button_border),i.secondary_button_label&&(e["--ah-btn-secondary-text"]=i.secondary_button_label),i.body_text&&(e["--ah-color-text"]=i.body_text),i.header&&(e["--ah-color-text-header"]=i.header),i.input_labels_placeholders&&(e["--ah-color-text-label"]=i.input_labels_placeholders,e["--ah-color-text-muted"]=i.input_labels_placeholders),i.input_filled_text&&(e["--ah-color-input-text"]=i.input_filled_text),i.widget_background&&(e["--ah-color-bg"]=i.widget_background),i.input_background&&(e["--ah-color-input-bg"]=i.input_background),i.widget_border&&(e["--ah-widget-border-color"]=i.widget_border),i.input_border&&(e["--ah-color-border"]=i.input_border),i.links_focused_components&&(e["--ah-color-link"]=i.links_focused_components),i.base_focus_color&&(e["--ah-color-focus-ring"]=i.base_focus_color),i.base_hover_color&&(e["--ah-color-primary-hover"]=i.base_hover_color),i.error&&(e["--ah-color-error"]=i.error),i.success&&(e["--ah-color-success"]=i.success),i.icons&&(e["--ah-color-icon"]=i.icons)}if(t.fonts){const i=t.fonts,s=i.reference_text_size||16;if(i.font_url&&(e["--ah-font-url"]=i.font_url),i.reference_text_size&&(e["--ah-font-size-base"]=`${i.reference_text_size}px`),i.title?.size){const t=Math.round(i.title.size/100*s);e["--ah-font-size-title"]=`${t}px`}if(i.subtitle?.size){const t=Math.round(i.subtitle.size/100*s);e["--ah-font-size-subtitle"]=`${t}px`}if(i.body_text?.size){const t=Math.round(i.body_text.size/100*s);e["--ah-font-size-body"]=`${t}px`}if(i.input_labels?.size){const t=Math.round(i.input_labels.size/100*s);e["--ah-font-size-label"]=`${t}px`}if(i.buttons_text?.size){const t=Math.round(i.buttons_text.size/100*s);e["--ah-font-size-btn"]=`${t}px`}if(i.links?.size){const t=Math.round(i.links.size/100*s);e["--ah-font-size-link"]=`${t}px`}"underlined"===i.links_style&&(e["--ah-link-decoration"]="underline"),void 0!==i.title?.bold&&(e["--ah-font-weight-title"]=i.title.bold?"700":"400"),void 0!==i.subtitle?.bold&&(e["--ah-font-weight-subtitle"]=i.subtitle.bold?"700":"400"),void 0!==i.body_text?.bold&&(e["--ah-font-weight-body"]=i.body_text.bold?"700":"400"),void 0!==i.input_labels?.bold&&(e["--ah-font-weight-label"]=i.input_labels.bold?"700":"400"),void 0!==i.buttons_text?.bold&&(e["--ah-font-weight-btn"]=i.buttons_text.bold?"600":"400"),void 0!==i.links?.bold&&(e["--ah-font-weight-link"]=i.links.bold?"700":"400")}if(t.widget){const i=t.widget;if(i.header_text_alignment&&(e["--ah-title-align"]=i.header_text_alignment),i.logo_height&&(e["--ah-logo-height"]=`${i.logo_height}px`),i.logo_position){const t={center:"center",left:"flex-start",right:"flex-end"};"none"===i.logo_position?e["--ah-logo-display"]="none":e["--ah-logo-align"]=t[i.logo_position]??"center"}i.social_buttons_layout&&("top"===i.social_buttons_layout?(e["--ah-social-order"]="0",e["--ah-divider-order"]="1",e["--ah-fields-order"]="2"):(e["--ah-social-order"]="2",e["--ah-divider-order"]="1",e["--ah-fields-order"]="0"))}if(t.page_background){const i=t.page_background;i.background_color&&(e["--ah-page-bg"]=i.background_color),i.background_image_url&&(e["--ah-page-bg-image"]=`url(${i.background_image_url})`)}return e}const a={br:[],em:[],i:[],strong:[],b:[],u:[],span:["class"],a:["href","class"]};function c(t){if(!t)return"";if(!t.includes("<"))return t;let e=t;e=e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;");for(const[t,i]of Object.entries(a)){if("br"===t){e=e.replace(/&lt;br\s*\/?&gt;/gi,"<br>");continue}const s=new RegExp(`&lt;${t}((?:\\s+[a-z-]+(?:=&quot;[^&]*&quot;|=&#39;[^&]*&#39;)?)*)\\s*&gt;`,"gi");e=e.replace(s,((e,s)=>{const o=[];if(s){const t=s.replace(/&quot;/g,'"').replace(/&#39;/g,"'").replace(/&amp;/g,"&").replace(/&lt;/g,"<").replace(/&gt;/g,">"),e=/([a-z-]+)=["']([^"']*)["']/gi;let r;for(;null!==(r=e.exec(t));){const[,t,e]=r;t&&i.includes(t.toLowerCase())&&("href"===t.toLowerCase()?h(e||"")&&o.push(`${t}="${l(e||"")}"`):o.push(`${t}="${l(e||"")}"`))}}"a"===t&&(o.push('target="_blank"'),o.push('rel="noopener noreferrer"'));const r=o.length?" "+o.join(" "):"";return`<${t}${r}>`}));const o=new RegExp(`&lt;/${t}&gt;`,"gi");e=e.replace(o,`</${t}>`)}return e}function h(t){if(!t)return!1;if(t.startsWith("/")||t.startsWith("#")||t.startsWith("?"))return!0;try{const e=new URL(t,"https://example.com");return"http:"===e.protocol||"https:"===e.protocol}catch{return!1}}function l(t){return t.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}const d=class{constructor(i){t(this,i),this.formSubmit=e(this,"formSubmit"),this.buttonClick=e(this,"buttonClick"),this.linkClick=e(this,"linkClick"),this.navigate=e(this,"navigate"),this.flowComplete=e(this,"flowComplete"),this.flowError=e(this,"flowError"),this.screenChange=e(this,"screenChange")}get el(){return i(this)}screen;apiUrl;baseUrl;state;screenId;watchScreenId(){this.updateDataScreenAttribute()}authParams;statePersistence="memory";storageKey="authhero_widget";branding;theme;loading=!1;autoSubmit=!1;autoNavigate;_screen;_authParams;_branding;_theme;formData={};formSubmit;buttonClick;linkClick;navigate;flowComplete;flowError;screenChange;watchScreen(t){if("string"==typeof t)try{this._screen=JSON.parse(t)}catch{console.error("Failed to parse screen JSON")}else this._screen=t;this._screen&&(this.formData={},this.initFormDataFromDefaults(this._screen),this.screenChange.emit(this._screen),this.updateDataScreenAttribute())}initFormDataFromDefaults(t){const e={};for(const i of t.components||[])if("config"in i&&i.config&&"default_value"in i.config&&i.config.default_value){const t=i.config.default_value;"string"==typeof t&&""!==t&&(e[i.id]=t)}Object.keys(e).length>0&&(this.formData={...e,...this.formData})}updateDataScreenAttribute(){const t=this._screen?.name||this.screenId;t?this.el.setAttribute("data-screen",t):this.el.removeAttribute("data-screen");const e=this.el.closest("[data-authhero-widget-container]");e&&(t?e.setAttribute("data-screen",t):e.removeAttribute("data-screen"))}watchBranding(t){if("string"==typeof t)try{this._branding=JSON.parse(t)}catch{console.error("Failed to parse branding JSON")}else this._branding=t;this.applyThemeStyles()}watchTheme(t){if("string"==typeof t)try{this._theme=JSON.parse(t)}catch{console.error("Failed to parse theme JSON")}else this._theme=t;this.applyThemeStyles()}watchAuthParams(t){if("string"==typeof t)try{this._authParams=JSON.parse(t)}catch{console.error("Failed to parse authParams JSON")}else this._authParams=t}applyThemeStyles(){const t=(e=this._theme,{...r(this._branding),...n(e)});var e;!function(t,e){Object.entries(e).forEach((([e,i])=>{t.style.setProperty(e,i)}))}(this.el,t)}focusFirstInput(){requestAnimationFrame((()=>{const t=this.el.shadowRoot;if(!t)return;const e=t.querySelectorAll("authhero-node");for(const t of Array.from(e)){const e=t.shadowRoot;if(e){const t=e.querySelector('input:not([type="hidden"]):not([type="checkbox"]):not([disabled]), textarea:not([disabled])');if(t)return void t.focus()}}}))}get shouldAutoNavigate(){return this.autoNavigate??this.autoSubmit}buildUrl(t){return this.baseUrl?new URL(t,this.baseUrl).toString():t}loadPersistedState(){if("url"===this.statePersistence){const t=new URL(window.location.href).searchParams.get("state");t&&!this.state&&(this.state=t)}else if("session"===this.statePersistence)try{const t=sessionStorage.getItem(`${this.storageKey}_state`);t&&!this.state&&(this.state=t);const e=sessionStorage.getItem(`${this.storageKey}_screenId`);e&&!this.screenId&&(this.screenId=e)}catch{}}persistState(){if("url"===this.statePersistence){const t=new URL(window.location.href);this.state&&t.searchParams.set("state",this.state),this.screenId&&t.searchParams.set("screen",this.screenId),window.history.replaceState({},"",t.toString())}else if("session"===this.statePersistence)try{this.state&&sessionStorage.setItem(`${this.storageKey}_state`,this.state),this.screenId&&sessionStorage.setItem(`${this.storageKey}_screenId`,this.screenId)}catch{}}handlePopState=t=>{if(!this.apiUrl)return;t.state?.state&&(this.state=t.state.state);const e=t.state?.screen??this.extractScreenIdFromHref(location.href);e&&this.fetchScreen(e)};connectedCallback(){window.addEventListener("popstate",this.handlePopState)}disconnectedCallback(){window.removeEventListener("popstate",this.handlePopState)}async componentWillLoad(){if(!this._screen){const t=this.screen||this.el?.getAttribute("screen");t&&this.watchScreen(t)}this._branding||this.watchBranding(this.branding),this._theme||this.watchTheme(this.theme),this._authParams||this.watchAuthParams(this.authParams),this.loadPersistedState(),this.apiUrl&&!this._screen&&await this.fetchScreen(this.screenId)}async fetchScreen(t,e){if(!this.apiUrl)return!1;const i=t||this.screenId;let s=this.apiUrl;i&&s.includes("{screenId}")&&(s=s.replace("{screenId}",encodeURIComponent(i)));const o=new URL(s,this.baseUrl||window.location.origin);this.state&&o.searchParams.set("state",this.state),e&&o.searchParams.set("nodeId",e),this.loading=!0;try{const t=await fetch(this.buildUrl(o.pathname+o.search),{credentials:"include",headers:{Accept:"application/json"}});if(t.ok){const e=await t.json();if(e.screen?(this._screen=e.screen,e.branding&&(this._branding=e.branding,this.applyThemeStyles()),e.state&&(this.state=e.state),e.screenId&&(this.screenId=e.screenId)):this._screen=e,this._screen)return i&&i!==this.screenId&&(this.screenId=i),this.initFormDataFromDefaults(this._screen),this.screenChange.emit(this._screen),this.updateDataScreenAttribute(),this.persistState(),this.focusFirstInput(),!0}else{const e=await t.json().catch((()=>({message:"Failed to load screen"})));this.flowError.emit({message:e.message||"Failed to load screen"})}}catch(t){console.error("Failed to fetch screen:",t),this.flowError.emit({message:t instanceof Error?t.message:"Failed to fetch screen"})}finally{this.loading=!1}return!1}handleInputChange=(t,e)=>{this.formData={...this.formData,[t]:e}};handleSubmit=async t=>{if(t.preventDefault(),this._screen&&(this.formSubmit.emit({screen:this._screen,data:this.formData}),this.autoSubmit)){this.loading=!0;try{const t=await fetch(this.buildUrl(this._screen.action),{method:this._screen.method,credentials:"include",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({data:this.formData})}),e=t.headers.get("content-type");if(e?.includes("application/json")){const e=await t.json();e.redirect?(this.flowComplete.emit({redirectUrl:e.redirect}),this.navigate.emit({url:e.redirect}),this.shouldAutoNavigate&&(window.location.href=e.redirect)):!t.ok&&e.screen?(this._screen=e.screen,this.initFormDataFromDefaults(e.screen),this.screenChange.emit(e.screen),this.updateDataScreenAttribute(),this.focusFirstInput()):e.screen?(this._screen=e.screen,this.formData={},this.initFormDataFromDefaults(e.screen),this.screenChange.emit(e.screen),this.updateDataScreenAttribute(),e.screenId&&(this.screenId=e.screenId),this.persistState(),e.navigateUrl&&this.shouldAutoNavigate&&window.history.pushState({screen:e.screenId,state:this.state},"",e.navigateUrl),e.branding&&(this._branding=e.branding,this.applyThemeStyles()),e.state&&(this.state=e.state,this.persistState()),this.focusFirstInput()):e.complete&&this.flowComplete.emit({})}}catch(t){console.error("Form submission failed:",t),this.flowError.emit({message:t instanceof Error?t.message:"Form submission failed"})}finally{this.loading=!1}}};handleButtonClick=t=>{"submit"!==t.type?(this.buttonClick.emit(t),"SOCIAL"===t.type&&t.value&&this.shouldAutoNavigate?this.handleSocialLogin(t.value):"RESEND_BUTTON"===t.type&&this.shouldAutoNavigate&&this.handleResend()):this.handleSubmit({preventDefault:()=>{}})};handleSocialLogin(t){const e=this._authParams||{},i={connection:t};this.state?i.state=this.state:e.state&&(i.state=e.state),e.client_id&&(i.client_id=e.client_id),e.redirect_uri&&(i.redirect_uri=e.redirect_uri),e.scope&&(i.scope=e.scope),e.audience&&(i.audience=e.audience),e.nonce&&(i.nonce=e.nonce),e.response_type&&(i.response_type=e.response_type);const s=this.buildUrl("/authorize?"+new URLSearchParams(i).toString());this.navigate.emit({url:s}),window.location.href=s}async handleResend(){if(this._screen?.action)try{const t=this._screen.action+(this._screen.action.includes("?")?"&":"?")+"action=resend";await fetch(this.buildUrl(t),{method:"POST",credentials:"include"})}catch(t){console.error("Resend failed:",t)}}extractScreenIdFromHref(t){try{const e=new URL(t,window.location.origin).pathname,i=e.match(/\/u2\/login\/([^/]+)$/);if(i)return i[1];const s=e.match(/\/u2\/([^/]+)$/);return s&&"login"!==s[1]&&"screen"!==s[1]?s[1]:null}catch{return null}}handleLinkClick=(t,e)=>{if(this.linkClick.emit({id:e.id,href:e.href,text:e.text}),!this.shouldAutoNavigate)return void t.preventDefault();const i=this.extractScreenIdFromHref(e.href);return i&&this.apiUrl?(t.preventDefault(),void this.navigateToScreen(i,e.href)):void 0};async navigateToScreen(t,e){await this.fetchScreen(t)?window.history.pushState({screen:t,state:this.state},"",e):window.location.href=e}isSocialComponent(t){return"SOCIAL"===t.type}isDividerComponent(t){return"DIVIDER"===t.type}render(){const t=this._screen;if(this.loading&&!t)return s("div",{class:"widget-container"},s("div",{class:"loading-spinner"}));if(!t)return s("div",{class:"widget-container"},s("div",{class:"error-message"},"No screen configuration provided"));const e=t.messages?.filter((t=>"error"===t.type))||[],i=t.messages?.filter((t=>"success"===t.type))||[],o=[...t.components??[]].filter((t=>!1!==t.visible)).sort(((t,e)=>(t.order??0)-(e.order??0))),r=o.filter((t=>this.isSocialComponent(t))),n=o.filter((t=>!this.isSocialComponent(t)&&!this.isDividerComponent(t))),a=o.find((t=>this.isDividerComponent(t))),h=!!a,l=a?.config?.text||"Or",d=t=>{const e=t.config;return["social-buttons","button","button-secondary","button-social","button-social-content","button-social-text","button-social-subtitle","social-icon",...(e?.providers??[]).flatMap((t=>{const e=t.replace(/[^a-zA-Z0-9-]/g,"-");return[`button-social-${e}`,`button-social-content-${e}`,`button-social-text-${e}`,`button-social-subtitle-${e}`,`social-icon-${e}`]}))].join(", ")},p=this._theme?.widget?.logo_url||this._branding?.logo_url;return s("div",{class:"widget-container",part:"container","data-authstack-container":!0},s("header",{class:"widget-header",part:"header"},p&&s("div",{class:"logo-wrapper",part:"logo-wrapper"},s("img",{class:"logo",part:"logo",src:p,alt:"Logo"})),t.title&&s("h1",{class:"title",part:"title",innerHTML:c(t.title)}),t.description&&s("p",{class:"description",part:"description",innerHTML:c(t.description)})),s("div",{class:"widget-body",part:"body"},e.map((t=>s("div",{class:"message message-error",part:"message message-error",key:t.id??t.text},t.text))),i.map((t=>s("div",{class:"message message-success",part:"message message-success",key:t.id??t.text},t.text))),s("form",{onSubmit:this.handleSubmit,part:"form"},s("div",{class:"form-content"},r.length>0&&s("div",{class:"social-section",part:"social-section"},r.map((t=>s("authhero-node",{key:t.id,component:t,value:this.formData[t.id],onFieldChange:t=>this.handleInputChange(t.detail.id,t.detail.value),onButtonClick:t=>this.handleButtonClick(t.detail),disabled:this.loading,exportparts:d(t)})))),r.length>0&&n.length>0&&h&&s("div",{class:"divider",part:"divider"},s("span",{class:"divider-text"},l)),s("div",{class:"fields-section",part:"fields-section"},n.map((t=>s("authhero-node",{key:t.id,component:t,value:this.formData[t.id],onFieldChange:t=>this.handleInputChange(t.detail.id,t.detail.value),onButtonClick:t=>this.handleButtonClick(t.detail),disabled:this.loading})))))),t.links&&t.links.length>0&&s("div",{class:"links",part:"links"},t.links.map((t=>s("span",{class:"link-wrapper",part:"link-wrapper",key:t.id??t.href},t.linkText?s("span",null,t.text," ",s("a",{href:t.href,class:"link",part:"link",onClick:e=>this.handleLinkClick(e,{id:t.id,href:t.href,text:t.linkText||t.text})},t.linkText)):s("a",{href:t.href,class:"link",part:"link",onClick:e=>this.handleLinkClick(e,{id:t.id,href:t.href,text:t.text})},t.text))))),t.footer&&s("div",{class:"widget-footer",part:"footer",innerHTML:c(t.footer)})))}static get watchers(){return{screenId:[{watchScreenId:0}],screen:[{watchScreen:0}],branding:[{watchBranding:0}],theme:[{watchTheme:0}],authParams:[{watchAuthParams:0}]}}};d.style=":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, 16px);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-subtitle, 16px);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}}";export{d as authhero_widget}