@authhero/widget 0.37.0 → 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
|
@@ -523,6 +523,27 @@ const authheroWidgetCss = () => `:host{display:block;font-family:var(--ah-font-f
|
|
|
523
523
|
* buttons. Used by long pick-one screens such as tenant selection.
|
|
524
524
|
*/
|
|
525
525
|
const CHOICE_LIST_SEARCH_THRESHOLD = 5;
|
|
526
|
+
/**
|
|
527
|
+
* Component types the widget renders as something the user can actually fill
|
|
528
|
+
* in. Only these gate the primary action button: types that render nothing
|
|
529
|
+
* (CARDS, FILE, RECAPTCHA, …) can carry `required` in the schema too, and
|
|
530
|
+
* gating on them would leave Continue disabled with no way to satisfy it.
|
|
531
|
+
*/
|
|
532
|
+
const FILLABLE_FIELD_TYPES = new Set([
|
|
533
|
+
"TEXT",
|
|
534
|
+
"EMAIL",
|
|
535
|
+
"CODE",
|
|
536
|
+
"PASSWORD",
|
|
537
|
+
"NUMBER",
|
|
538
|
+
"TEL",
|
|
539
|
+
"URL",
|
|
540
|
+
"DATE",
|
|
541
|
+
"BOOLEAN",
|
|
542
|
+
"LEGAL",
|
|
543
|
+
"COUNTRY",
|
|
544
|
+
"DROPDOWN",
|
|
545
|
+
"CHOICE",
|
|
546
|
+
]);
|
|
526
547
|
const AuthheroWidget = class {
|
|
527
548
|
constructor(hostRef) {
|
|
528
549
|
registerInstance(this, hostRef);
|
|
@@ -615,6 +636,13 @@ const AuthheroWidget = class {
|
|
|
615
636
|
* @default false (same as autoSubmit when not specified)
|
|
616
637
|
*/
|
|
617
638
|
autoNavigate;
|
|
639
|
+
/**
|
|
640
|
+
* BCP-47 locale for locale-dependent field layout, e.g. whether a DATE
|
|
641
|
+
* field reads DD/MM/YYYY, MM/DD/YYYY or YYYY-MM-DD. Resolve it server-side
|
|
642
|
+
* and pass it in so the server-rendered markup and the hydrated one agree;
|
|
643
|
+
* screen text itself is already localized by the server.
|
|
644
|
+
*/
|
|
645
|
+
locale;
|
|
618
646
|
/**
|
|
619
647
|
* Internal parsed screen state.
|
|
620
648
|
*/
|
|
@@ -715,14 +743,19 @@ const AuthheroWidget = class {
|
|
|
715
743
|
initFormDataFromDefaults(screen) {
|
|
716
744
|
const defaults = {};
|
|
717
745
|
for (const comp of screen.components || []) {
|
|
718
|
-
if ("config" in comp
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
746
|
+
if (!("config" in comp) || !comp.config)
|
|
747
|
+
continue;
|
|
748
|
+
if (!("default_value" in comp.config))
|
|
749
|
+
continue;
|
|
750
|
+
const val = comp.config.default_value;
|
|
751
|
+
if (typeof val === "string" && val !== "") {
|
|
752
|
+
defaults[comp.id] = val;
|
|
753
|
+
}
|
|
754
|
+
else if (typeof val === "boolean") {
|
|
755
|
+
// A BOOLEAN's default decides whether the checkbox renders ticked, so
|
|
756
|
+
// seed the matching value — otherwise a field the user never touches
|
|
757
|
+
// submits nothing and the screen's state is lost.
|
|
758
|
+
defaults[comp.id] = val ? "true" : "false";
|
|
726
759
|
}
|
|
727
760
|
}
|
|
728
761
|
if (Object.keys(defaults).length > 0) {
|
|
@@ -1723,6 +1756,11 @@ const AuthheroWidget = class {
|
|
|
1723
1756
|
handleButtonClick = (detail) => {
|
|
1724
1757
|
// If this is a submit button click, trigger form submission
|
|
1725
1758
|
if (detail.type === "submit") {
|
|
1759
|
+
// Enter in a text field submits too, so the required-field gate has to
|
|
1760
|
+
// live here and not only on the button's disabled state.
|
|
1761
|
+
if (this._screen && this.hasUnfilledRequiredFields(this._screen)) {
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1726
1764
|
// For GET screens (or missing method), navigate directly — no form submission needed
|
|
1727
1765
|
if ((!this._screen?.method ||
|
|
1728
1766
|
this._screen.method.toUpperCase() === "GET") &&
|
|
@@ -1911,6 +1949,32 @@ const AuthheroWidget = class {
|
|
|
1911
1949
|
isDividerComponent(component) {
|
|
1912
1950
|
return component.type === "DIVIDER";
|
|
1913
1951
|
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Whether a required component holds a value the user has supplied.
|
|
1954
|
+
*/
|
|
1955
|
+
isRequiredFieldFilled(component) {
|
|
1956
|
+
const value = this.formData[component.id];
|
|
1957
|
+
// Checkboxes are only "filled" when ticked. A BOOLEAN's default_value is
|
|
1958
|
+
// seeded into formData, so it needs no separate handling here.
|
|
1959
|
+
if (component.type === "BOOLEAN" || component.type === "LEGAL") {
|
|
1960
|
+
return value === "true";
|
|
1961
|
+
}
|
|
1962
|
+
return typeof value === "string" && value.trim() !== "";
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Whether the screen still has required fields the user has not filled in.
|
|
1966
|
+
* Used to hold the primary action button disabled: the widget submits via
|
|
1967
|
+
* its own handler rather than a native form submit, so the browser's
|
|
1968
|
+
* constraint validation never runs and an empty required field would
|
|
1969
|
+
* otherwise only surface as a server error after a round trip.
|
|
1970
|
+
*/
|
|
1971
|
+
hasUnfilledRequiredFields(screen) {
|
|
1972
|
+
return (screen.components ?? []).some((component) => component.visible !== false &&
|
|
1973
|
+
"required" in component &&
|
|
1974
|
+
component.required === true &&
|
|
1975
|
+
FILLABLE_FIELD_TYPES.has(component.type) &&
|
|
1976
|
+
!this.isRequiredFieldFilled(component));
|
|
1977
|
+
}
|
|
1914
1978
|
/**
|
|
1915
1979
|
* Visible label of a choice button, used to filter searchable choice lists.
|
|
1916
1980
|
*/
|
|
@@ -1946,6 +2010,9 @@ const AuthheroWidget = class {
|
|
|
1946
2010
|
const choiceButtons = fieldComponents.filter((c) => c.type === "NEXT_BUTTON");
|
|
1947
2011
|
const isSearchableChoiceList = choiceButtons.length === fieldComponents.length &&
|
|
1948
2012
|
choiceButtons.length > CHOICE_LIST_SEARCH_THRESHOLD;
|
|
2013
|
+
// Hold the primary action button disabled until every required field has
|
|
2014
|
+
// a value.
|
|
2015
|
+
const requiredFieldsMissing = this.hasUnfilledRequiredFields(screen);
|
|
1949
2016
|
const filterQuery = this.listFilter.trim().toLowerCase();
|
|
1950
2017
|
const visibleChoiceButtons = isSearchableChoiceList && filterQuery
|
|
1951
2018
|
? choiceButtons.filter((c) => this.getChoiceButtonText(c).toLowerCase().includes(filterQuery))
|
|
@@ -1980,7 +2047,7 @@ const AuthheroWidget = class {
|
|
|
1980
2047
|
};
|
|
1981
2048
|
// Get logo URL from theme.widget (takes precedence) or branding
|
|
1982
2049
|
const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
|
|
1983
|
-
return (h("div", { class: "widget-container", part: "container", "data-authstack-container": true }, h("header", { class: "widget-header", part: "header" }, logoUrl && (h("div", { class: "logo-wrapper", part: "logo-wrapper" }, h("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), screen.title && (h("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(screen.title) })), screen.description && (h("p", { class: "description", part: "description", innerHTML: sanitizeHtml(screen.description) }))), h("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (h("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (h("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), h("form", { onSubmit: this.handleSubmit, action: screen.action, method: screen.method || "POST", part: "form" }, hiddenComponents.map((c) => (h("input", { type: "hidden", name: c.id, id: c.id, key: c.id, value: this.formData[c.id] || "" }))), h("div", { class: "form-content" }, socialComponents.length > 0 && (h("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (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 &&
|
|
2050
|
+
return (h("div", { class: "widget-container", part: "container", "data-authstack-container": true }, h("header", { class: "widget-header", part: "header" }, logoUrl && (h("div", { class: "logo-wrapper", part: "logo-wrapper" }, h("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), screen.title && (h("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(screen.title) })), screen.description && (h("p", { class: "description", part: "description", innerHTML: sanitizeHtml(screen.description) }))), h("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (h("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (h("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), h("form", { onSubmit: this.handleSubmit, action: screen.action, method: screen.method || "POST", part: "form" }, hiddenComponents.map((c) => (h("input", { type: "hidden", name: c.id, id: c.id, key: c.id, value: this.formData[c.id] || "" }))), h("div", { class: "form-content" }, socialComponents.length > 0 && (h("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (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 &&
|
|
1984
2051
|
fieldComponents.length > 0 &&
|
|
1985
2052
|
hasDivider && (h("div", { class: "divider", part: "divider" }, h("span", { class: "divider-text" }, dividerText))), h("div", { class: "fields-section", part: "fields-section" }, isSearchableChoiceList && (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) => {
|
|
1986
2053
|
// The search box lives inside the form; Enter would
|
|
@@ -1993,7 +2060,9 @@ const AuthheroWidget = class {
|
|
|
1993
2060
|
? "fields-list fields-list-scroll"
|
|
1994
2061
|
: "fields-list" }, (isSearchableChoiceList
|
|
1995
2062
|
? visibleChoiceButtons
|
|
1996
|
-
: fieldComponents).map((component) => (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
|
|
2063
|
+
: fieldComponents).map((component) => (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 ||
|
|
2064
|
+
(component.type === "NEXT_BUTTON" &&
|
|
2065
|
+
requiredFieldsMissing) }))), isSearchableChoiceList &&
|
|
1997
2066
|
visibleChoiceButtons.length === 0 && (h("div", { class: "choice-list-empty", part: "choice-list-empty" }, "No matches")))))), screen.links && screen.links.length > 0 && (h("div", { class: "links", part: "links" }, screen.links.map((link) => (h("span", { class: "link-wrapper", part: "link-wrapper", key: link.id ?? link.href }, link.linkText ? (h("span", null, link.text, " ", h("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
|
|
1998
2067
|
id: link.id,
|
|
1999
2068
|
href: link.href,
|
|
@@ -17,5 +17,5 @@ var patchBrowser = () => {
|
|
|
17
17
|
|
|
18
18
|
patchBrowser().then(async (options) => {
|
|
19
19
|
await globalScripts();
|
|
20
|
-
return bootstrapLazy([["authhero-node",[[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",[[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);
|
|
20
|
+
return bootstrapLazy([["authhero-node",[[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",[[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);
|
|
21
21
|
});
|