@authhero/widget 0.40.2 → 0.41.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-8b981cd0.entry.js +1 -0
- package/dist/cjs/authhero-widget.cjs.entry.js +158 -20
- package/dist/cjs/index.cjs.js +1 -1
- package/dist/collection/components/authhero-widget/authhero-widget.css +121 -0
- package/dist/collection/components/authhero-widget/authhero-widget.js +160 -22
- package/dist/components/authhero-widget.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/esm/authhero-widget.entry.js +158 -20
- package/dist/esm/index.js +1 -1
- package/dist/types/components/authhero-widget/authhero-widget.d.ts +24 -2
- package/hydrate/index.js +152 -20
- package/hydrate/index.mjs +152 -20
- package/package.json +3 -3
- package/dist/authhero-widget/p-36898e2d.entry.js +0 -1
|
@@ -515,7 +515,7 @@ function escapeAttr(value) {
|
|
|
515
515
|
.replace(/>/g, ">");
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
-
const authheroWidgetCss = () => `:host{display:block;font-family:var( --ah-font-family, "ulp-font", -apple-system, BlinkMacSystemFont, Roboto, Helvetica, sans-serif );font-size:var(--ah-font-size-base, 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{width:var(--ah-widget-max-width, 400px);margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);border:var(--ah-widget-border-width, 0) solid var(--ah-widget-border-color, transparent);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-text-header, var(--ah-color-header, #1e212a));line-height:1.2}.description{font-size:var(--ah-font-size-subtitle, 16px);font-weight:var(--ah-font-weight-subtitle, 400);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)}.fields-list{display:flex;flex-direction:column}.fields-list-scroll{max-height:var(--ah-choice-list-max-height, 320px);overflow-y:auto;gap:var(--ah-choice-list-gap, 8px);padding-right:4px}.choice-list-search{width:100%;padding:16px;margin-bottom:12px;font-size:16px;font-family:inherit;color:var(--ah-color-input-text, var(--ah-color-text, #1e212a));background-color:var(--ah-color-input-bg, #ffffff);border:var(--ah-input-border-width, 1px) solid var(--ah-color-border, #c9cace);border-radius:var(--ah-input-radius, 3px);outline:none;box-sizing:border-box;transition:border-color 0.15s ease-out, box-shadow 0.15s ease-out}.choice-list-search:hover{border-color:var(--ah-color-border-hover, #65676e)}.choice-list-search:focus{border-color:var(--ah-color-primary, var(--ah-color-link, #635dff))}.choice-list-empty{padding:16px;text-align:center;font-size:14px;color:var(--ah-color-text-muted, #65676e)}.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:var(--ah-font-size-body, 14px);font-weight:var(--ah-font-weight-body, 400);color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:var(--ah-font-size-link, 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-focus-ring, 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-focus-ring, 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(:not([floating])){display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}:host(:not([floating])) .widget-container{box-shadow:none;border-radius:0;border:0;width:100%;margin:0}:host([floating]) .widget-container{width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}`;
|
|
518
|
+
const authheroWidgetCss = () => `:host{display:block;font-family:var( --ah-font-family, "ulp-font", -apple-system, BlinkMacSystemFont, Roboto, Helvetica, sans-serif );font-size:var(--ah-font-size-base, 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{width:var(--ah-widget-max-width, 400px);margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);border:var(--ah-widget-border-width, 0) solid var(--ah-widget-border-color, transparent);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-text-header, var(--ah-color-header, #1e212a));line-height:1.2}.description{font-size:var(--ah-font-size-subtitle, 16px);font-weight:var(--ah-font-weight-subtitle, 400);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-footer,.secondary-actions,.prompt-partial{display:flex;flex-direction:column}.prompt-partial .ulp-field{display:flex;flex-direction:column;gap:6px;position:relative;margin-top:8px;margin-bottom:16px}.prompt-partial label{font-size:var(--ah-font-size-label, 14px);font-weight:var(--ah-font-weight-label, 400);color:var(--ah-color-text, #1e212a)}.prompt-partial .ulp-error input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),.prompt-partial .ulp-error select,.prompt-partial .ulp-error textarea{border-color:var(--ah-color-error, #d03c38)}.prompt-partial .ulp-error input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):focus,.prompt-partial .ulp-error select:focus,.prompt-partial .ulp-error textarea:focus{box-shadow:inset 0 0 0 1px var(--ah-color-error, #d03c38)}.prompt-partial .ulp-error-info{font-size:12px;color:var(--ah-color-error, #d03c38);margin-top:-2px;line-height:1.4}.prompt-partial .required{color:var(--ah-color-error, #d03c38);margin-left:2px}.prompt-partial .ulp-field>input+label,.prompt-partial .ulp-field>select+label,.prompt-partial .ulp-field>textarea+label{position:absolute;left:16px;top:50%;z-index:1;padding:0;background-color:transparent;color:var(--ah-color-text-muted, #65676e);pointer-events:none;transform:translateY(-50%);transition:all 0.15s ease-out}.prompt-partial .ulp-field>input:focus+label,.prompt-partial .ulp-field>input:not(:placeholder-shown)+label,.prompt-partial .ulp-field>select+label,.prompt-partial .ulp-field>textarea:focus+label,.prompt-partial .ulp-field>textarea:not(:placeholder-shown)+label{top:-8px;left:12px;padding:0 4px;background-color:var(--ah-color-bg, #ffffff);color:var(--ah-color-text-muted, #65676e);font-size:12px;transform:translateY(0)}.prompt-partial .ulp-field>input:focus+label,.prompt-partial .ulp-field>select:focus+label,.prompt-partial .ulp-field>textarea:focus+label{color:var(--ah-color-primary, #635dff)}.prompt-partial input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),.prompt-partial select,.prompt-partial textarea{width:100%;padding:16px;font-size:16px;font-family:inherit;color:var(--ah-color-input-text, var(--ah-color-text, #1e212a));background-color:var(--ah-color-input-bg, #ffffff);border:var(--ah-input-border-width, 1px) solid var(--ah-color-border, #c9cace);border-radius:var(--ah-input-radius, 3px);outline:none;box-sizing:border-box;transition:border-color 0.15s ease-out, box-shadow 0.15s ease-out}.prompt-partial input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):hover,.prompt-partial select:hover,.prompt-partial textarea:hover{border-color:var(--ah-color-border-hover, #65676e)}.prompt-partial input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):focus,.prompt-partial select:focus,.prompt-partial textarea:focus{border-color:var(--ah-color-primary, #635dff);box-shadow:inset 0 0 0 1px var(--ah-color-primary, #635dff)}.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)}.fields-list{display:flex;flex-direction:column}.fields-list-scroll{max-height:var(--ah-choice-list-max-height, 320px);overflow-y:auto;gap:var(--ah-choice-list-gap, 8px);padding-right:4px}.choice-list-search{width:100%;padding:16px;margin-bottom:12px;font-size:16px;font-family:inherit;color:var(--ah-color-input-text, var(--ah-color-text, #1e212a));background-color:var(--ah-color-input-bg, #ffffff);border:var(--ah-input-border-width, 1px) solid var(--ah-color-border, #c9cace);border-radius:var(--ah-input-radius, 3px);outline:none;box-sizing:border-box;transition:border-color 0.15s ease-out, box-shadow 0.15s ease-out}.choice-list-search:hover{border-color:var(--ah-color-border-hover, #65676e)}.choice-list-search:focus{border-color:var(--ah-color-primary, var(--ah-color-link, #635dff))}.choice-list-empty{padding:16px;text-align:center;font-size:14px;color:var(--ah-color-text-muted, #65676e)}.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:var(--ah-font-size-body, 14px);font-weight:var(--ah-font-weight-body, 400);color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:var(--ah-font-size-link, 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-focus-ring, 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-focus-ring, 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(:not([floating])){display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}:host(:not([floating])) .widget-container{box-shadow:none;border-radius:0;border:0;width:100%;margin:0}:host([floating]) .widget-container{width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}`;
|
|
519
519
|
|
|
520
520
|
/**
|
|
521
521
|
* When a screen's field section is made up entirely of choice buttons
|
|
@@ -530,6 +530,19 @@ const CHOICE_LIST_SEARCH_THRESHOLD = 5;
|
|
|
530
530
|
* (CARDS, FILE, RECAPTCHA, …) can carry `required` in the schema too, and
|
|
531
531
|
* gating on them would leave Continue disabled with no way to satisfy it.
|
|
532
532
|
*/
|
|
533
|
+
/**
|
|
534
|
+
* Field types rendered as a single plain text input whose value maps 1:1 to
|
|
535
|
+
* formData. Composite fields (TEL, DATE) and choice/checkbox fields keep
|
|
536
|
+
* internal state, so they're not adopted from pre-hydration markup.
|
|
537
|
+
*/
|
|
538
|
+
const PREHYDRATION_TEXT_TYPES = new Set([
|
|
539
|
+
"TEXT",
|
|
540
|
+
"EMAIL",
|
|
541
|
+
"CODE",
|
|
542
|
+
"PASSWORD",
|
|
543
|
+
"NUMBER",
|
|
544
|
+
"URL",
|
|
545
|
+
]);
|
|
533
546
|
const FILLABLE_FIELD_TYPES = new Set([
|
|
534
547
|
"TEXT",
|
|
535
548
|
"EMAIL",
|
|
@@ -910,7 +923,24 @@ const AuthheroWidget = class {
|
|
|
910
923
|
* actually in the DOM before measuring its height.
|
|
911
924
|
*/
|
|
912
925
|
pendingRenderResolvers = [];
|
|
926
|
+
/**
|
|
927
|
+
* Number of `swapScreen` calls still running (rendering or animating the
|
|
928
|
+
* card height). A counter rather than a flag because a newer swap can start
|
|
929
|
+
* before a superseded one has settled.
|
|
930
|
+
*/
|
|
931
|
+
activeSwaps = 0;
|
|
932
|
+
updateReadyAttribute() {
|
|
933
|
+
{
|
|
934
|
+
this.el.toggleAttribute("data-ready", !this.loading && this.activeSwaps === 0);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
913
937
|
componentDidRender() {
|
|
938
|
+
// `data-ready` marks the widget as interactive: hydrated in the browser
|
|
939
|
+
// (the SSR markup already carries Stencil's `hydrated` class, so that
|
|
940
|
+
// can't tell server and client apart) and not mid-submit or mid-swap.
|
|
941
|
+
// Stencil defers this hook until child nodes have rendered, so the
|
|
942
|
+
// fields are live too. E2E tests wait on `authhero-widget[data-ready]`.
|
|
943
|
+
this.updateReadyAttribute();
|
|
914
944
|
if (this.pendingRenderResolvers.length === 0)
|
|
915
945
|
return;
|
|
916
946
|
const resolvers = this.pendingRenderResolvers;
|
|
@@ -958,6 +988,18 @@ const AuthheroWidget = class {
|
|
|
958
988
|
apply();
|
|
959
989
|
return;
|
|
960
990
|
}
|
|
991
|
+
// Hold `data-ready` off until the new screen has rendered and the height
|
|
992
|
+
// animation has finished; `apply()` clears `loading` before either.
|
|
993
|
+
this.activeSwaps++;
|
|
994
|
+
try {
|
|
995
|
+
await this.animateSwap(card, apply);
|
|
996
|
+
}
|
|
997
|
+
finally {
|
|
998
|
+
this.activeSwaps--;
|
|
999
|
+
this.updateReadyAttribute();
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
async animateSwap(card, apply) {
|
|
961
1003
|
// Lock the current height and clip, so swapping the content doesn't jump
|
|
962
1004
|
// before the animation runs.
|
|
963
1005
|
const startHeight = card.getBoundingClientRect().height;
|
|
@@ -1122,6 +1164,9 @@ const AuthheroWidget = class {
|
|
|
1122
1164
|
if (!this._authParams) {
|
|
1123
1165
|
this.watchAuthParams(this.authParams ?? this.readJsonScript("auth-params"));
|
|
1124
1166
|
}
|
|
1167
|
+
if (this._screen) {
|
|
1168
|
+
this.adoptPrehydrationValues(this._screen);
|
|
1169
|
+
}
|
|
1125
1170
|
// Load persisted state if available
|
|
1126
1171
|
this.loadPersistedState();
|
|
1127
1172
|
// Fetch screen from API if URL provided and no screen prop
|
|
@@ -1129,6 +1174,41 @@ const AuthheroWidget = class {
|
|
|
1129
1174
|
await this.fetchScreen(this.screenId);
|
|
1130
1175
|
}
|
|
1131
1176
|
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Carry over text the user (or a password manager, or a test driver) put
|
|
1179
|
+
* into the server-rendered inputs before the client bundle loaded. Those
|
|
1180
|
+
* inputs had no `onInput` handler yet, so `formData` never saw the value:
|
|
1181
|
+
* the first client render would reset them to "" and keep the submit
|
|
1182
|
+
* button disabled. The declarative shadow roots are still the SSR ones
|
|
1183
|
+
* here — `componentWillLoad` runs before the first client render.
|
|
1184
|
+
*/
|
|
1185
|
+
adoptPrehydrationValues(screen) {
|
|
1186
|
+
const root = this.el.shadowRoot;
|
|
1187
|
+
if (!root)
|
|
1188
|
+
return;
|
|
1189
|
+
const roots = [root];
|
|
1190
|
+
root.querySelectorAll("authhero-node").forEach((node) => {
|
|
1191
|
+
if (node.shadowRoot)
|
|
1192
|
+
roots.push(node.shadowRoot);
|
|
1193
|
+
});
|
|
1194
|
+
const adopted = {};
|
|
1195
|
+
for (const component of screen.components ?? []) {
|
|
1196
|
+
if (!PREHYDRATION_TEXT_TYPES.has(component.type))
|
|
1197
|
+
continue;
|
|
1198
|
+
for (const r of roots) {
|
|
1199
|
+
const input = Array.from(r.querySelectorAll("input[name]")).find((el) => el.name === component.id && el.type !== "hidden");
|
|
1200
|
+
// Adopt "" too: a prefilled default the user cleared before
|
|
1201
|
+
// hydration must not be restored by the client render.
|
|
1202
|
+
if (input) {
|
|
1203
|
+
adopted[component.id] = input.value;
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
if (Object.keys(adopted).length > 0) {
|
|
1209
|
+
this.formData = { ...this.formData, ...adopted };
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1132
1212
|
/**
|
|
1133
1213
|
* Fetch screen configuration from the API
|
|
1134
1214
|
* @param screenIdOverride Optional screen ID to fetch (overrides this.screenId)
|
|
@@ -1230,14 +1310,44 @@ const AuthheroWidget = class {
|
|
|
1230
1310
|
e.preventDefault();
|
|
1231
1311
|
if (!this._screen || this.loading)
|
|
1232
1312
|
return;
|
|
1233
|
-
let submitData = { ...this.formData, ...(overrideData || {}) };
|
|
1234
|
-
// Merge hidden input values from DOM (may have been set programmatically
|
|
1235
|
-
// by inline scripts, e.g. passkey management buttons)
|
|
1236
1313
|
const form = this.el.shadowRoot?.querySelector("form");
|
|
1314
|
+
if (this._screen.partials &&
|
|
1315
|
+
form?.reportValidity &&
|
|
1316
|
+
!form.reportValidity()) {
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
const submitData = {
|
|
1320
|
+
...this.formData,
|
|
1321
|
+
...(overrideData || {}),
|
|
1322
|
+
};
|
|
1323
|
+
// Native fields in prompt partials are direct descendants of this form,
|
|
1324
|
+
// unlike authhero-node inputs in nested shadow roots. Read them through
|
|
1325
|
+
// FormData so standard HTML controls and successful-control semantics are
|
|
1326
|
+
// preserved (disabled controls are skipped, repeated names are kept as
|
|
1327
|
+
// arrays). FormData also covers enabled hidden inputs for ulp-* names.
|
|
1328
|
+
const nativeNames = new Set();
|
|
1329
|
+
if (form && this._screen.partials) {
|
|
1330
|
+
const nativeValues = {};
|
|
1331
|
+
new FormData(form).forEach((value, key) => {
|
|
1332
|
+
if (typeof value === "string" && key.startsWith("ulp-")) {
|
|
1333
|
+
(nativeValues[key] ??= []).push(value);
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
for (const [key, values] of Object.entries(nativeValues)) {
|
|
1337
|
+
nativeNames.add(key);
|
|
1338
|
+
submitData[key] = values.length > 1 ? values : values[0];
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
// Legacy behaviour for inline scripts that fill hidden inputs: merge
|
|
1342
|
+
// enabled ones, but never overwrite what FormData already collected.
|
|
1237
1343
|
if (form) {
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1344
|
+
form
|
|
1345
|
+
.querySelectorAll('input[type="hidden"]')
|
|
1346
|
+
.forEach((input) => {
|
|
1347
|
+
if (input.name &&
|
|
1348
|
+
input.value &&
|
|
1349
|
+
!input.disabled &&
|
|
1350
|
+
!nativeNames.has(input.name)) {
|
|
1241
1351
|
submitData[input.name] = input.value;
|
|
1242
1352
|
}
|
|
1243
1353
|
});
|
|
@@ -1391,6 +1501,11 @@ const AuthheroWidget = class {
|
|
|
1391
1501
|
if (!form)
|
|
1392
1502
|
return;
|
|
1393
1503
|
form.submit = () => {
|
|
1504
|
+
if (this._screen?.partials &&
|
|
1505
|
+
form.reportValidity &&
|
|
1506
|
+
!form.reportValidity()) {
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1394
1509
|
const formData = new FormData(form);
|
|
1395
1510
|
const data = {};
|
|
1396
1511
|
formData.forEach((value, key) => {
|
|
@@ -1995,6 +2110,27 @@ const AuthheroWidget = class {
|
|
|
1995
2110
|
const config = component.config;
|
|
1996
2111
|
return config?.text ?? "";
|
|
1997
2112
|
}
|
|
2113
|
+
renderPromptPartial(screen, insertionPoint) {
|
|
2114
|
+
const html = screen.partials?.[insertionPoint];
|
|
2115
|
+
if (!html)
|
|
2116
|
+
return null;
|
|
2117
|
+
// Prompt partials intentionally contain tenant-authored form controls, so
|
|
2118
|
+
// the widget must keep their input attributes intact.
|
|
2119
|
+
return (h("div", { class: `prompt-partial prompt-partial-${insertionPoint}`, part: `prompt-partial prompt-partial-${insertionPoint}`, "data-insertion-point": insertionPoint, innerHTML: html }));
|
|
2120
|
+
}
|
|
2121
|
+
renderScreenLinks(screen) {
|
|
2122
|
+
if (!screen.links?.length)
|
|
2123
|
+
return null;
|
|
2124
|
+
return (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, {
|
|
2125
|
+
id: link.id,
|
|
2126
|
+
href: link.href,
|
|
2127
|
+
text: link.linkText || link.text,
|
|
2128
|
+
}) }, link.linkText))) : (h("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
|
|
2129
|
+
id: link.id,
|
|
2130
|
+
href: link.href,
|
|
2131
|
+
text: link.text,
|
|
2132
|
+
}) }, link.text)))))));
|
|
2133
|
+
}
|
|
1998
2134
|
render() {
|
|
1999
2135
|
const screen = this._screen;
|
|
2000
2136
|
if (this.loading && !screen) {
|
|
@@ -2021,13 +2157,20 @@ const AuthheroWidget = class {
|
|
|
2021
2157
|
? components.find((c) => this.isDividerComponent(c))
|
|
2022
2158
|
: undefined;
|
|
2023
2159
|
const fieldComponents = components.filter((c) => !this.isSocialComponent(c) && c !== dividerComponent);
|
|
2160
|
+
const hasPromptPartials = Object.values(screen.partials ?? {}).some((html) => !!html);
|
|
2161
|
+
const footerComponents = hasPromptPartials
|
|
2162
|
+
? fieldComponents.filter((component) => component.id === "submit" && component.type === "NEXT_BUTTON")
|
|
2163
|
+
: [];
|
|
2164
|
+
const contentFieldComponents = hasPromptPartials
|
|
2165
|
+
? fieldComponents.filter((component) => !footerComponents.includes(component))
|
|
2166
|
+
: fieldComponents;
|
|
2024
2167
|
const hasDivider = !!dividerComponent;
|
|
2025
2168
|
const dividerText = dividerComponent?.config?.text || "Or";
|
|
2026
2169
|
// A "choice list" screen is one whose entire field section is pick-one
|
|
2027
2170
|
// buttons (e.g. tenant selection). When the list is long, render it as a
|
|
2028
2171
|
// searchable, scrollable list instead of a tall column of buttons.
|
|
2029
|
-
const choiceButtons =
|
|
2030
|
-
const isSearchableChoiceList = choiceButtons.length ===
|
|
2172
|
+
const choiceButtons = contentFieldComponents.filter((c) => c.type === "NEXT_BUTTON");
|
|
2173
|
+
const isSearchableChoiceList = choiceButtons.length === contentFieldComponents.length &&
|
|
2031
2174
|
choiceButtons.length > CHOICE_LIST_SEARCH_THRESHOLD;
|
|
2032
2175
|
// Hold the primary action button disabled until every required field has
|
|
2033
2176
|
// a value.
|
|
@@ -2068,7 +2211,7 @@ const AuthheroWidget = class {
|
|
|
2068
2211
|
};
|
|
2069
2212
|
// Get logo URL from theme.widget (takes precedence) or branding
|
|
2070
2213
|
const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
|
|
2071
|
-
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 &&
|
|
2214
|
+
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" }, this.renderPromptPartial(screen, "form-content-start"), 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 &&
|
|
2072
2215
|
fieldComponents.length > 0 &&
|
|
2073
2216
|
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) => {
|
|
2074
2217
|
// The search box lives inside the form; Enter would
|
|
@@ -2081,19 +2224,14 @@ const AuthheroWidget = class {
|
|
|
2081
2224
|
? "fields-list fields-list-scroll"
|
|
2082
2225
|
: "fields-list" }, (isSearchableChoiceList
|
|
2083
2226
|
? visibleChoiceButtons
|
|
2084
|
-
:
|
|
2227
|
+
: contentFieldComponents).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 ||
|
|
2085
2228
|
(component.type === "NEXT_BUTTON" &&
|
|
2086
2229
|
component.config?.skip_validation !== true &&
|
|
2087
2230
|
requiredFieldsMissing) }))), isSearchableChoiceList &&
|
|
2088
|
-
visibleChoiceButtons.length === 0 && (h("div", { class: "choice-list-empty", part: "choice-list-empty" }, "No matches"))))
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
}) }, link.linkText))) : (h("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
|
|
2093
|
-
id: link.id,
|
|
2094
|
-
href: link.href,
|
|
2095
|
-
text: link.text,
|
|
2096
|
-
}) }, link.text))))))), screen.footer && (h("div", { class: "widget-footer", part: "footer", innerHTML: sanitizeHtml(screen.footer) })))));
|
|
2231
|
+
visibleChoiceButtons.length === 0 && (h("div", { class: "choice-list-empty", part: "choice-list-empty" }, "No matches")))), this.renderPromptPartial(screen, "form-content-end"), hasPromptPartials && (h("div", { class: "form-footer", part: "form-footer" }, this.renderPromptPartial(screen, "form-footer-start"), footerComponents.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 ||
|
|
2232
|
+
(component.type === "NEXT_BUTTON" &&
|
|
2233
|
+
component.config?.skip_validation !== true &&
|
|
2234
|
+
requiredFieldsMissing) }))), this.renderPromptPartial(screen, "form-footer-end")))), hasPromptPartials && (h("div", { class: "secondary-actions", part: "secondary-actions" }, this.renderPromptPartial(screen, "secondary-actions-start"), this.renderScreenLinks(screen), this.renderPromptPartial(screen, "secondary-actions-end")))), !hasPromptPartials && this.renderScreenLinks(screen), screen.footer && (h("div", { class: "widget-footer", part: "footer", innerHTML: sanitizeHtml(screen.footer) })))));
|
|
2097
2235
|
}
|
|
2098
2236
|
static get watchers() { return {
|
|
2099
2237
|
"screenId": [{
|