@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/dist/authhero-widget/authhero-widget.esm.js +1 -1
- package/dist/authhero-widget/index.esm.js +1 -1
- package/dist/authhero-widget/p-c03e5b57.entry.js +1 -0
- package/dist/authhero-widget/{p-59cbb196.entry.js → p-f3a92b50.entry.js} +1 -1
- package/dist/cjs/authhero-node.cjs.entry.js +19 -3
- package/dist/cjs/authhero-widget.cjs.entry.js +127 -16
- package/dist/cjs/index.cjs.js +1 -1
- package/dist/collection/components/authhero-node/authhero-node.js +22 -6
- package/dist/collection/components/authhero-widget/authhero-widget.js +31 -2
- package/dist/collection/utils/branding.js +98 -16
- package/dist/components/authhero-node.js +1 -1
- package/dist/components/authhero-widget.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/{p-_nTdqfom.js → p-D9Clv9VJ.js} +1 -1
- package/dist/esm/authhero-node.entry.js +19 -3
- package/dist/esm/authhero-widget.entry.js +127 -16
- package/dist/esm/index.js +1 -1
- package/dist/types/components/authhero-widget/authhero-widget.d.ts +5 -0
- package/hydrate/index.js +146 -19
- package/hydrate/index.mjs +146 -19
- package/package.json +2 -2
- package/dist/authhero-widget/p-a92fa18e.entry.js +0 -1
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
|
|
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
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");for(const[t,i]of Object.entries(a)){if("br"===t){e=e.replace(/<br\s*\/?>/gi,"<br>");continue}const s=new RegExp(`<${t}((?:\\s+[a-z-]+(?:="[^&]*"|='[^&]*')?)*)\\s*>`,"gi");e=e.replace(s,((e,s)=>{const o=[];if(s){const t=s.replace(/"/g,'"').replace(/'/g,"'").replace(/&/g,"&").replace(/</g,"<").replace(/>/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(`</${t}>`,"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,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}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}
|