@authhero/widget 0.37.1 → 0.38.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-b5f14fa6.entry.js +1 -0
- package/dist/authhero-widget/p-f94037cd.entry.js +1 -0
- package/dist/cjs/authhero-node.cjs.entry.js +458 -13
- package/dist/cjs/authhero-widget.cjs.entry.js +79 -10
- package/dist/cjs/authhero-widget.cjs.js +1 -1
- package/dist/cjs/index.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/authhero-node/authhero-node.css +94 -0
- package/dist/collection/components/authhero-node/authhero-node.js +302 -13
- package/dist/collection/components/authhero-widget/authhero-widget.js +98 -10
- package/dist/collection/utils/date-format.js +176 -0
- 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-C9ZfIQiS.js +1 -0
- package/dist/esm/authhero-node.entry.js +458 -13
- package/dist/esm/authhero-widget.entry.js +79 -10
- package/dist/esm/authhero-widget.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/types/components/authhero-node/authhero-node.d.ts +83 -0
- package/dist/types/components/authhero-widget/authhero-widget.d.ts +19 -0
- package/dist/types/components.d.ts +18 -0
- package/dist/types/utils/date-format.d.ts +52 -0
- package/hydrate/index.js +541 -24
- package/hydrate/index.mjs +541 -24
- package/package.json +2 -2
- package/dist/authhero-widget/p-cdfc4555.entry.js +0 -1
- package/dist/authhero-widget/p-e95c436f.entry.js +0 -1
- package/dist/components/p-BP46GHTc.js +0 -1
|
@@ -525,6 +525,27 @@ const authheroWidgetCss = () => `:host{display:block;font-family:var(--ah-font-f
|
|
|
525
525
|
* buttons. Used by long pick-one screens such as tenant selection.
|
|
526
526
|
*/
|
|
527
527
|
const CHOICE_LIST_SEARCH_THRESHOLD = 5;
|
|
528
|
+
/**
|
|
529
|
+
* Component types the widget renders as something the user can actually fill
|
|
530
|
+
* in. Only these gate the primary action button: types that render nothing
|
|
531
|
+
* (CARDS, FILE, RECAPTCHA, …) can carry `required` in the schema too, and
|
|
532
|
+
* gating on them would leave Continue disabled with no way to satisfy it.
|
|
533
|
+
*/
|
|
534
|
+
const FILLABLE_FIELD_TYPES = new Set([
|
|
535
|
+
"TEXT",
|
|
536
|
+
"EMAIL",
|
|
537
|
+
"CODE",
|
|
538
|
+
"PASSWORD",
|
|
539
|
+
"NUMBER",
|
|
540
|
+
"TEL",
|
|
541
|
+
"URL",
|
|
542
|
+
"DATE",
|
|
543
|
+
"BOOLEAN",
|
|
544
|
+
"LEGAL",
|
|
545
|
+
"COUNTRY",
|
|
546
|
+
"DROPDOWN",
|
|
547
|
+
"CHOICE",
|
|
548
|
+
]);
|
|
528
549
|
const AuthheroWidget = class {
|
|
529
550
|
constructor(hostRef) {
|
|
530
551
|
index.registerInstance(this, hostRef);
|
|
@@ -617,6 +638,13 @@ const AuthheroWidget = class {
|
|
|
617
638
|
* @default false (same as autoSubmit when not specified)
|
|
618
639
|
*/
|
|
619
640
|
autoNavigate;
|
|
641
|
+
/**
|
|
642
|
+
* BCP-47 locale for locale-dependent field layout, e.g. whether a DATE
|
|
643
|
+
* field reads DD/MM/YYYY, MM/DD/YYYY or YYYY-MM-DD. Resolve it server-side
|
|
644
|
+
* and pass it in so the server-rendered markup and the hydrated one agree;
|
|
645
|
+
* screen text itself is already localized by the server.
|
|
646
|
+
*/
|
|
647
|
+
locale;
|
|
620
648
|
/**
|
|
621
649
|
* Internal parsed screen state.
|
|
622
650
|
*/
|
|
@@ -717,14 +745,19 @@ const AuthheroWidget = class {
|
|
|
717
745
|
initFormDataFromDefaults(screen) {
|
|
718
746
|
const defaults = {};
|
|
719
747
|
for (const comp of screen.components || []) {
|
|
720
|
-
if ("config" in comp
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
748
|
+
if (!("config" in comp) || !comp.config)
|
|
749
|
+
continue;
|
|
750
|
+
if (!("default_value" in comp.config))
|
|
751
|
+
continue;
|
|
752
|
+
const val = comp.config.default_value;
|
|
753
|
+
if (typeof val === "string" && val !== "") {
|
|
754
|
+
defaults[comp.id] = val;
|
|
755
|
+
}
|
|
756
|
+
else if (typeof val === "boolean") {
|
|
757
|
+
// A BOOLEAN's default decides whether the checkbox renders ticked, so
|
|
758
|
+
// seed the matching value — otherwise a field the user never touches
|
|
759
|
+
// submits nothing and the screen's state is lost.
|
|
760
|
+
defaults[comp.id] = val ? "true" : "false";
|
|
728
761
|
}
|
|
729
762
|
}
|
|
730
763
|
if (Object.keys(defaults).length > 0) {
|
|
@@ -1725,6 +1758,11 @@ const AuthheroWidget = class {
|
|
|
1725
1758
|
handleButtonClick = (detail) => {
|
|
1726
1759
|
// If this is a submit button click, trigger form submission
|
|
1727
1760
|
if (detail.type === "submit") {
|
|
1761
|
+
// Enter in a text field submits too, so the required-field gate has to
|
|
1762
|
+
// live here and not only on the button's disabled state.
|
|
1763
|
+
if (this._screen && this.hasUnfilledRequiredFields(this._screen)) {
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1728
1766
|
// For GET screens (or missing method), navigate directly — no form submission needed
|
|
1729
1767
|
if ((!this._screen?.method ||
|
|
1730
1768
|
this._screen.method.toUpperCase() === "GET") &&
|
|
@@ -1913,6 +1951,32 @@ const AuthheroWidget = class {
|
|
|
1913
1951
|
isDividerComponent(component) {
|
|
1914
1952
|
return component.type === "DIVIDER";
|
|
1915
1953
|
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Whether a required component holds a value the user has supplied.
|
|
1956
|
+
*/
|
|
1957
|
+
isRequiredFieldFilled(component) {
|
|
1958
|
+
const value = this.formData[component.id];
|
|
1959
|
+
// Checkboxes are only "filled" when ticked. A BOOLEAN's default_value is
|
|
1960
|
+
// seeded into formData, so it needs no separate handling here.
|
|
1961
|
+
if (component.type === "BOOLEAN" || component.type === "LEGAL") {
|
|
1962
|
+
return value === "true";
|
|
1963
|
+
}
|
|
1964
|
+
return typeof value === "string" && value.trim() !== "";
|
|
1965
|
+
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Whether the screen still has required fields the user has not filled in.
|
|
1968
|
+
* Used to hold the primary action button disabled: the widget submits via
|
|
1969
|
+
* its own handler rather than a native form submit, so the browser's
|
|
1970
|
+
* constraint validation never runs and an empty required field would
|
|
1971
|
+
* otherwise only surface as a server error after a round trip.
|
|
1972
|
+
*/
|
|
1973
|
+
hasUnfilledRequiredFields(screen) {
|
|
1974
|
+
return (screen.components ?? []).some((component) => component.visible !== false &&
|
|
1975
|
+
"required" in component &&
|
|
1976
|
+
component.required === true &&
|
|
1977
|
+
FILLABLE_FIELD_TYPES.has(component.type) &&
|
|
1978
|
+
!this.isRequiredFieldFilled(component));
|
|
1979
|
+
}
|
|
1916
1980
|
/**
|
|
1917
1981
|
* Visible label of a choice button, used to filter searchable choice lists.
|
|
1918
1982
|
*/
|
|
@@ -1948,6 +2012,9 @@ const AuthheroWidget = class {
|
|
|
1948
2012
|
const choiceButtons = fieldComponents.filter((c) => c.type === "NEXT_BUTTON");
|
|
1949
2013
|
const isSearchableChoiceList = choiceButtons.length === fieldComponents.length &&
|
|
1950
2014
|
choiceButtons.length > CHOICE_LIST_SEARCH_THRESHOLD;
|
|
2015
|
+
// Hold the primary action button disabled until every required field has
|
|
2016
|
+
// a value.
|
|
2017
|
+
const requiredFieldsMissing = this.hasUnfilledRequiredFields(screen);
|
|
1951
2018
|
const filterQuery = this.listFilter.trim().toLowerCase();
|
|
1952
2019
|
const visibleChoiceButtons = isSearchableChoiceList && filterQuery
|
|
1953
2020
|
? choiceButtons.filter((c) => this.getChoiceButtonText(c).toLowerCase().includes(filterQuery))
|
|
@@ -1982,7 +2049,7 @@ const AuthheroWidget = class {
|
|
|
1982
2049
|
};
|
|
1983
2050
|
// Get logo URL from theme.widget (takes precedence) or branding
|
|
1984
2051
|
const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
|
|
1985
|
-
return (index.h("div", { class: "widget-container", part: "container", "data-authstack-container": true }, index.h("header", { class: "widget-header", part: "header" }, logoUrl && (index.h("div", { class: "logo-wrapper", part: "logo-wrapper" }, index.h("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), screen.title && (index.h("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(screen.title) })), screen.description && (index.h("p", { class: "description", part: "description", innerHTML: sanitizeHtml(screen.description) }))), index.h("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (index.h("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (index.h("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), index.h("form", { onSubmit: this.handleSubmit, action: screen.action, method: screen.method || "POST", part: "form" }, hiddenComponents.map((c) => (index.h("input", { type: "hidden", name: c.id, id: c.id, key: c.id, value: this.formData[c.id] || "" }))), index.h("div", { class: "form-content" }, socialComponents.length > 0 && (index.h("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (index.h("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading, exportparts: getExportParts(component) }))))), socialComponents.length > 0 &&
|
|
2052
|
+
return (index.h("div", { class: "widget-container", part: "container", "data-authstack-container": true }, index.h("header", { class: "widget-header", part: "header" }, logoUrl && (index.h("div", { class: "logo-wrapper", part: "logo-wrapper" }, index.h("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), screen.title && (index.h("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(screen.title) })), screen.description && (index.h("p", { class: "description", part: "description", innerHTML: sanitizeHtml(screen.description) }))), index.h("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (index.h("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (index.h("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), index.h("form", { onSubmit: this.handleSubmit, action: screen.action, method: screen.method || "POST", part: "form" }, hiddenComponents.map((c) => (index.h("input", { type: "hidden", name: c.id, id: c.id, key: c.id, value: this.formData[c.id] || "" }))), index.h("div", { class: "form-content" }, socialComponents.length > 0 && (index.h("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (index.h("authhero-node", { key: component.id, component: component, locale: this.locale, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading, exportparts: getExportParts(component) }))))), socialComponents.length > 0 &&
|
|
1986
2053
|
fieldComponents.length > 0 &&
|
|
1987
2054
|
hasDivider && (index.h("div", { class: "divider", part: "divider" }, index.h("span", { class: "divider-text" }, dividerText))), index.h("div", { class: "fields-section", part: "fields-section" }, isSearchableChoiceList && (index.h("input", { type: "text", class: "choice-list-search", part: "choice-list-search", placeholder: "Search", "aria-label": "Search", autocomplete: "off", value: this.listFilter, onInput: (e) => (this.listFilter = e.target.value), onKeyDown: (e) => {
|
|
1988
2055
|
// The search box lives inside the form; Enter would
|
|
@@ -1995,7 +2062,9 @@ const AuthheroWidget = class {
|
|
|
1995
2062
|
? "fields-list fields-list-scroll"
|
|
1996
2063
|
: "fields-list" }, (isSearchableChoiceList
|
|
1997
2064
|
? visibleChoiceButtons
|
|
1998
|
-
: fieldComponents).map((component) => (index.h("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading
|
|
2065
|
+
: fieldComponents).map((component) => (index.h("authhero-node", { key: component.id, component: component, locale: this.locale, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading ||
|
|
2066
|
+
(component.type === "NEXT_BUTTON" &&
|
|
2067
|
+
requiredFieldsMissing) }))), isSearchableChoiceList &&
|
|
1999
2068
|
visibleChoiceButtons.length === 0 && (index.h("div", { class: "choice-list-empty", part: "choice-list-empty" }, "No matches")))))), screen.links && screen.links.length > 0 && (index.h("div", { class: "links", part: "links" }, screen.links.map((link) => (index.h("span", { class: "link-wrapper", part: "link-wrapper", key: link.id ?? link.href }, link.linkText ? (index.h("span", null, link.text, " ", index.h("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
|
|
2000
2069
|
id: link.id,
|
|
2001
2070
|
href: link.href,
|
|
@@ -19,7 +19,7 @@ var patchBrowser = () => {
|
|
|
19
19
|
|
|
20
20
|
patchBrowser().then(async (options) => {
|
|
21
21
|
await appGlobals.globalScripts();
|
|
22
|
-
return index.bootstrapLazy([["authhero-node.cjs",[[513,"authhero-node",{"component":[16],"value":[1],"disabled":[4],"passwordVisible":[32],"selectedCountry":[32],"localPhoneNumber":[32],"countryDropdownOpen":[32],"telEmailMode":[32]},null,{"component":[{"componentChanged":0}],"value":[{"valueChanged":0}]}]]],["authhero-widget.cjs",[[513,"authhero-widget",{"screen":[1],"apiUrl":[1,"api-url"],"baseUrl":[1,"base-url"],"state":[1025],"screenId":[1025,"screen-id"],"authParams":[1,"auth-params"],"statePersistence":[1,"state-persistence"],"storageKey":[1,"storage-key"],"branding":[1],"theme":[1],"loading":[1028],"autoSubmit":[4,"auto-submit"],"autoNavigate":[4,"auto-navigate"],"_screen":[32],"_authParams":[32],"_branding":[32],"_theme":[32],"formData":[32],"listFilter":[32]},null,{"screenId":[{"watchScreenId":0}],"screen":[{"watchScreen":0}],"branding":[{"watchBranding":0}],"theme":[{"watchTheme":0}],"authParams":[{"watchAuthParams":0}]}]]]], options);
|
|
22
|
+
return index.bootstrapLazy([["authhero-node.cjs",[[513,"authhero-node",{"component":[16],"value":[1],"disabled":[4],"locale":[1],"passwordVisible":[32],"selectedCountry":[32],"localPhoneNumber":[32],"countryDropdownOpen":[32],"telEmailMode":[32],"dateSegments":[32]},null,{"component":[{"componentChanged":0}],"value":[{"valueChanged":0}]}]]],["authhero-widget.cjs",[[513,"authhero-widget",{"screen":[1],"apiUrl":[1,"api-url"],"baseUrl":[1,"base-url"],"state":[1025],"screenId":[1025,"screen-id"],"authParams":[1,"auth-params"],"statePersistence":[1,"state-persistence"],"storageKey":[1,"storage-key"],"branding":[1],"theme":[1],"loading":[1028],"autoSubmit":[4,"auto-submit"],"autoNavigate":[4,"auto-navigate"],"locale":[1],"_screen":[32],"_authParams":[32],"_branding":[32],"_theme":[32],"formData":[32],"listFilter":[32]},null,{"screenId":[{"watchScreenId":0}],"screen":[{"watchScreen":0}],"branding":[{"watchBranding":0}],"theme":[{"watchTheme":0}],"authParams":[{"watchAuthParams":0}]}]]]], options);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
exports.setNonce = index.setNonce;
|