@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
|
@@ -7,8 +7,11 @@ import type { WidgetBranding, WidgetTheme } from "../../utils/branding";
|
|
|
7
7
|
export interface SubmitEventDetail {
|
|
8
8
|
/** The current screen configuration */
|
|
9
9
|
screen: UiScreen;
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Form field values keyed by component ID. Prompt-partial `ulp-*` fields
|
|
12
|
+
* that submit several values (checkbox groups, multi-selects) are arrays.
|
|
13
|
+
*/
|
|
14
|
+
data: Record<string, string | string[]>;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* Button click event detail - emitted when a non-submit button is clicked
|
|
@@ -272,6 +275,13 @@ export declare class AuthheroWidget {
|
|
|
272
275
|
* actually in the DOM before measuring its height.
|
|
273
276
|
*/
|
|
274
277
|
private pendingRenderResolvers;
|
|
278
|
+
/**
|
|
279
|
+
* Number of `swapScreen` calls still running (rendering or animating the
|
|
280
|
+
* card height). A counter rather than a flag because a newer swap can start
|
|
281
|
+
* before a superseded one has settled.
|
|
282
|
+
*/
|
|
283
|
+
private activeSwaps;
|
|
284
|
+
private updateReadyAttribute;
|
|
275
285
|
componentDidRender(): void;
|
|
276
286
|
/**
|
|
277
287
|
* Resolves after Stencil flushes the next render, so `swapScreen` waits for
|
|
@@ -292,6 +302,7 @@ export declare class AuthheroWidget {
|
|
|
292
302
|
* or the user prefers reduced motion.
|
|
293
303
|
*/
|
|
294
304
|
private swapScreen;
|
|
305
|
+
private animateSwap;
|
|
295
306
|
/**
|
|
296
307
|
* Get the effective autoNavigate value (defaults to autoSubmit if not set)
|
|
297
308
|
*/
|
|
@@ -320,6 +331,15 @@ export declare class AuthheroWidget {
|
|
|
320
331
|
*/
|
|
321
332
|
private readJsonScript;
|
|
322
333
|
componentWillLoad(): Promise<void>;
|
|
334
|
+
/**
|
|
335
|
+
* Carry over text the user (or a password manager, or a test driver) put
|
|
336
|
+
* into the server-rendered inputs before the client bundle loaded. Those
|
|
337
|
+
* inputs had no `onInput` handler yet, so `formData` never saw the value:
|
|
338
|
+
* the first client render would reset them to "" and keep the submit
|
|
339
|
+
* button disabled. The declarative shadow roots are still the SSR ones
|
|
340
|
+
* here — `componentWillLoad` runs before the first client render.
|
|
341
|
+
*/
|
|
342
|
+
private adoptPrehydrationValues;
|
|
323
343
|
/**
|
|
324
344
|
* Fetch screen configuration from the API
|
|
325
345
|
* @param screenIdOverride Optional screen ID to fetch (overrides this.screenId)
|
|
@@ -411,5 +431,7 @@ export declare class AuthheroWidget {
|
|
|
411
431
|
* Visible label of a choice button, used to filter searchable choice lists.
|
|
412
432
|
*/
|
|
413
433
|
private getChoiceButtonText;
|
|
434
|
+
private renderPromptPartial;
|
|
435
|
+
private renderScreenLinks;
|
|
414
436
|
render(): any;
|
|
415
437
|
}
|
package/hydrate/index.js
CHANGED
|
@@ -7236,7 +7236,7 @@ function escapeAttr(value) {
|
|
|
7236
7236
|
.replace(/>/g, ">");
|
|
7237
7237
|
}
|
|
7238
7238
|
|
|
7239
|
-
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}}`;
|
|
7239
|
+
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}}`;
|
|
7240
7240
|
|
|
7241
7241
|
/**
|
|
7242
7242
|
* When a screen's field section is made up entirely of choice buttons
|
|
@@ -7251,6 +7251,19 @@ const CHOICE_LIST_SEARCH_THRESHOLD = 5;
|
|
|
7251
7251
|
* (CARDS, FILE, RECAPTCHA, …) can carry `required` in the schema too, and
|
|
7252
7252
|
* gating on them would leave Continue disabled with no way to satisfy it.
|
|
7253
7253
|
*/
|
|
7254
|
+
/**
|
|
7255
|
+
* Field types rendered as a single plain text input whose value maps 1:1 to
|
|
7256
|
+
* formData. Composite fields (TEL, DATE) and choice/checkbox fields keep
|
|
7257
|
+
* internal state, so they're not adopted from pre-hydration markup.
|
|
7258
|
+
*/
|
|
7259
|
+
const PREHYDRATION_TEXT_TYPES = new Set([
|
|
7260
|
+
"TEXT",
|
|
7261
|
+
"EMAIL",
|
|
7262
|
+
"CODE",
|
|
7263
|
+
"PASSWORD",
|
|
7264
|
+
"NUMBER",
|
|
7265
|
+
"URL",
|
|
7266
|
+
]);
|
|
7254
7267
|
const FILLABLE_FIELD_TYPES = new Set([
|
|
7255
7268
|
"TEXT",
|
|
7256
7269
|
"EMAIL",
|
|
@@ -7631,7 +7644,21 @@ class AuthheroWidget {
|
|
|
7631
7644
|
* actually in the DOM before measuring its height.
|
|
7632
7645
|
*/
|
|
7633
7646
|
pendingRenderResolvers = [];
|
|
7647
|
+
/**
|
|
7648
|
+
* Number of `swapScreen` calls still running (rendering or animating the
|
|
7649
|
+
* card height). A counter rather than a flag because a newer swap can start
|
|
7650
|
+
* before a superseded one has settled.
|
|
7651
|
+
*/
|
|
7652
|
+
activeSwaps = 0;
|
|
7653
|
+
updateReadyAttribute() {
|
|
7654
|
+
}
|
|
7634
7655
|
componentDidRender() {
|
|
7656
|
+
// `data-ready` marks the widget as interactive: hydrated in the browser
|
|
7657
|
+
// (the SSR markup already carries Stencil's `hydrated` class, so that
|
|
7658
|
+
// can't tell server and client apart) and not mid-submit or mid-swap.
|
|
7659
|
+
// Stencil defers this hook until child nodes have rendered, so the
|
|
7660
|
+
// fields are live too. E2E tests wait on `authhero-widget[data-ready]`.
|
|
7661
|
+
this.updateReadyAttribute();
|
|
7635
7662
|
if (this.pendingRenderResolvers.length === 0)
|
|
7636
7663
|
return;
|
|
7637
7664
|
const resolvers = this.pendingRenderResolvers;
|
|
@@ -7679,6 +7706,18 @@ class AuthheroWidget {
|
|
|
7679
7706
|
apply();
|
|
7680
7707
|
return;
|
|
7681
7708
|
}
|
|
7709
|
+
// Hold `data-ready` off until the new screen has rendered and the height
|
|
7710
|
+
// animation has finished; `apply()` clears `loading` before either.
|
|
7711
|
+
this.activeSwaps++;
|
|
7712
|
+
try {
|
|
7713
|
+
await this.animateSwap(card, apply);
|
|
7714
|
+
}
|
|
7715
|
+
finally {
|
|
7716
|
+
this.activeSwaps--;
|
|
7717
|
+
this.updateReadyAttribute();
|
|
7718
|
+
}
|
|
7719
|
+
}
|
|
7720
|
+
async animateSwap(card, apply) {
|
|
7682
7721
|
// Lock the current height and clip, so swapping the content doesn't jump
|
|
7683
7722
|
// before the animation runs.
|
|
7684
7723
|
const startHeight = card.getBoundingClientRect().height;
|
|
@@ -7850,6 +7889,41 @@ class AuthheroWidget {
|
|
|
7850
7889
|
await this.fetchScreen(this.screenId);
|
|
7851
7890
|
}
|
|
7852
7891
|
}
|
|
7892
|
+
/**
|
|
7893
|
+
* Carry over text the user (or a password manager, or a test driver) put
|
|
7894
|
+
* into the server-rendered inputs before the client bundle loaded. Those
|
|
7895
|
+
* inputs had no `onInput` handler yet, so `formData` never saw the value:
|
|
7896
|
+
* the first client render would reset them to "" and keep the submit
|
|
7897
|
+
* button disabled. The declarative shadow roots are still the SSR ones
|
|
7898
|
+
* here — `componentWillLoad` runs before the first client render.
|
|
7899
|
+
*/
|
|
7900
|
+
adoptPrehydrationValues(screen) {
|
|
7901
|
+
const root = this.el.shadowRoot;
|
|
7902
|
+
if (!root)
|
|
7903
|
+
return;
|
|
7904
|
+
const roots = [root];
|
|
7905
|
+
root.querySelectorAll("authhero-node").forEach((node) => {
|
|
7906
|
+
if (node.shadowRoot)
|
|
7907
|
+
roots.push(node.shadowRoot);
|
|
7908
|
+
});
|
|
7909
|
+
const adopted = {};
|
|
7910
|
+
for (const component of screen.components ?? []) {
|
|
7911
|
+
if (!PREHYDRATION_TEXT_TYPES.has(component.type))
|
|
7912
|
+
continue;
|
|
7913
|
+
for (const r of roots) {
|
|
7914
|
+
const input = Array.from(r.querySelectorAll("input[name]")).find((el) => el.name === component.id && el.type !== "hidden");
|
|
7915
|
+
// Adopt "" too: a prefilled default the user cleared before
|
|
7916
|
+
// hydration must not be restored by the client render.
|
|
7917
|
+
if (input) {
|
|
7918
|
+
adopted[component.id] = input.value;
|
|
7919
|
+
break;
|
|
7920
|
+
}
|
|
7921
|
+
}
|
|
7922
|
+
}
|
|
7923
|
+
if (Object.keys(adopted).length > 0) {
|
|
7924
|
+
this.formData = { ...this.formData, ...adopted };
|
|
7925
|
+
}
|
|
7926
|
+
}
|
|
7853
7927
|
/**
|
|
7854
7928
|
* Fetch screen configuration from the API
|
|
7855
7929
|
* @param screenIdOverride Optional screen ID to fetch (overrides this.screenId)
|
|
@@ -7951,14 +8025,44 @@ class AuthheroWidget {
|
|
|
7951
8025
|
e.preventDefault();
|
|
7952
8026
|
if (!this._screen || this.loading)
|
|
7953
8027
|
return;
|
|
7954
|
-
let submitData = { ...this.formData, ...(overrideData || {}) };
|
|
7955
|
-
// Merge hidden input values from DOM (may have been set programmatically
|
|
7956
|
-
// by inline scripts, e.g. passkey management buttons)
|
|
7957
8028
|
const form = this.el.shadowRoot?.querySelector("form");
|
|
8029
|
+
if (this._screen.partials &&
|
|
8030
|
+
form?.reportValidity &&
|
|
8031
|
+
!form.reportValidity()) {
|
|
8032
|
+
return;
|
|
8033
|
+
}
|
|
8034
|
+
const submitData = {
|
|
8035
|
+
...this.formData,
|
|
8036
|
+
...(overrideData || {}),
|
|
8037
|
+
};
|
|
8038
|
+
// Native fields in prompt partials are direct descendants of this form,
|
|
8039
|
+
// unlike authhero-node inputs in nested shadow roots. Read them through
|
|
8040
|
+
// FormData so standard HTML controls and successful-control semantics are
|
|
8041
|
+
// preserved (disabled controls are skipped, repeated names are kept as
|
|
8042
|
+
// arrays). FormData also covers enabled hidden inputs for ulp-* names.
|
|
8043
|
+
const nativeNames = new Set();
|
|
8044
|
+
if (form && this._screen.partials) {
|
|
8045
|
+
const nativeValues = {};
|
|
8046
|
+
new FormData(form).forEach((value, key) => {
|
|
8047
|
+
if (typeof value === "string" && key.startsWith("ulp-")) {
|
|
8048
|
+
(nativeValues[key] ??= []).push(value);
|
|
8049
|
+
}
|
|
8050
|
+
});
|
|
8051
|
+
for (const [key, values] of Object.entries(nativeValues)) {
|
|
8052
|
+
nativeNames.add(key);
|
|
8053
|
+
submitData[key] = values.length > 1 ? values : values[0];
|
|
8054
|
+
}
|
|
8055
|
+
}
|
|
8056
|
+
// Legacy behaviour for inline scripts that fill hidden inputs: merge
|
|
8057
|
+
// enabled ones, but never overwrite what FormData already collected.
|
|
7958
8058
|
if (form) {
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
8059
|
+
form
|
|
8060
|
+
.querySelectorAll('input[type="hidden"]')
|
|
8061
|
+
.forEach((input) => {
|
|
8062
|
+
if (input.name &&
|
|
8063
|
+
input.value &&
|
|
8064
|
+
!input.disabled &&
|
|
8065
|
+
!nativeNames.has(input.name)) {
|
|
7962
8066
|
submitData[input.name] = input.value;
|
|
7963
8067
|
}
|
|
7964
8068
|
});
|
|
@@ -8112,6 +8216,11 @@ class AuthheroWidget {
|
|
|
8112
8216
|
if (!form)
|
|
8113
8217
|
return;
|
|
8114
8218
|
form.submit = () => {
|
|
8219
|
+
if (this._screen?.partials &&
|
|
8220
|
+
form.reportValidity &&
|
|
8221
|
+
!form.reportValidity()) {
|
|
8222
|
+
return;
|
|
8223
|
+
}
|
|
8115
8224
|
const formData = new FormData(form);
|
|
8116
8225
|
const data = {};
|
|
8117
8226
|
formData.forEach((value, key) => {
|
|
@@ -8716,6 +8825,27 @@ class AuthheroWidget {
|
|
|
8716
8825
|
const config = component.config;
|
|
8717
8826
|
return config?.text ?? "";
|
|
8718
8827
|
}
|
|
8828
|
+
renderPromptPartial(screen, insertionPoint) {
|
|
8829
|
+
const html = screen.partials?.[insertionPoint];
|
|
8830
|
+
if (!html)
|
|
8831
|
+
return null;
|
|
8832
|
+
// Prompt partials intentionally contain tenant-authored form controls, so
|
|
8833
|
+
// the widget must keep their input attributes intact.
|
|
8834
|
+
return (hAsync("div", { class: `prompt-partial prompt-partial-${insertionPoint}`, part: `prompt-partial prompt-partial-${insertionPoint}`, "data-insertion-point": insertionPoint, innerHTML: html }));
|
|
8835
|
+
}
|
|
8836
|
+
renderScreenLinks(screen) {
|
|
8837
|
+
if (!screen.links?.length)
|
|
8838
|
+
return null;
|
|
8839
|
+
return (hAsync("div", { class: "links", part: "links" }, screen.links.map((link) => (hAsync("span", { class: "link-wrapper", part: "link-wrapper", key: link.id ?? link.href }, link.linkText ? (hAsync("span", null, link.text, " ", hAsync("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
|
|
8840
|
+
id: link.id,
|
|
8841
|
+
href: link.href,
|
|
8842
|
+
text: link.linkText || link.text,
|
|
8843
|
+
}) }, link.linkText))) : (hAsync("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
|
|
8844
|
+
id: link.id,
|
|
8845
|
+
href: link.href,
|
|
8846
|
+
text: link.text,
|
|
8847
|
+
}) }, link.text)))))));
|
|
8848
|
+
}
|
|
8719
8849
|
render() {
|
|
8720
8850
|
const screen = this._screen;
|
|
8721
8851
|
if (this.loading && !screen) {
|
|
@@ -8742,13 +8872,20 @@ class AuthheroWidget {
|
|
|
8742
8872
|
? components.find((c) => this.isDividerComponent(c))
|
|
8743
8873
|
: undefined;
|
|
8744
8874
|
const fieldComponents = components.filter((c) => !this.isSocialComponent(c) && c !== dividerComponent);
|
|
8875
|
+
const hasPromptPartials = Object.values(screen.partials ?? {}).some((html) => !!html);
|
|
8876
|
+
const footerComponents = hasPromptPartials
|
|
8877
|
+
? fieldComponents.filter((component) => component.id === "submit" && component.type === "NEXT_BUTTON")
|
|
8878
|
+
: [];
|
|
8879
|
+
const contentFieldComponents = hasPromptPartials
|
|
8880
|
+
? fieldComponents.filter((component) => !footerComponents.includes(component))
|
|
8881
|
+
: fieldComponents;
|
|
8745
8882
|
const hasDivider = !!dividerComponent;
|
|
8746
8883
|
const dividerText = dividerComponent?.config?.text || "Or";
|
|
8747
8884
|
// A "choice list" screen is one whose entire field section is pick-one
|
|
8748
8885
|
// buttons (e.g. tenant selection). When the list is long, render it as a
|
|
8749
8886
|
// searchable, scrollable list instead of a tall column of buttons.
|
|
8750
|
-
const choiceButtons =
|
|
8751
|
-
const isSearchableChoiceList = choiceButtons.length ===
|
|
8887
|
+
const choiceButtons = contentFieldComponents.filter((c) => c.type === "NEXT_BUTTON");
|
|
8888
|
+
const isSearchableChoiceList = choiceButtons.length === contentFieldComponents.length &&
|
|
8752
8889
|
choiceButtons.length > CHOICE_LIST_SEARCH_THRESHOLD;
|
|
8753
8890
|
// Hold the primary action button disabled until every required field has
|
|
8754
8891
|
// a value.
|
|
@@ -8789,7 +8926,7 @@ class AuthheroWidget {
|
|
|
8789
8926
|
};
|
|
8790
8927
|
// Get logo URL from theme.widget (takes precedence) or branding
|
|
8791
8928
|
const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
|
|
8792
|
-
return (hAsync("div", { class: "widget-container", part: "container", "data-authstack-container": true }, hAsync("header", { class: "widget-header", part: "header" }, logoUrl && (hAsync("div", { class: "logo-wrapper", part: "logo-wrapper" }, hAsync("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), screen.title && (hAsync("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(screen.title) })), screen.description && (hAsync("p", { class: "description", part: "description", innerHTML: sanitizeHtml(screen.description) }))), hAsync("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (hAsync("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (hAsync("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), hAsync("form", { onSubmit: this.handleSubmit, action: screen.action, method: screen.method || "POST", part: "form" }, hiddenComponents.map((c) => (hAsync("input", { type: "hidden", name: c.id, id: c.id, key: c.id, value: this.formData[c.id] || "" }))), hAsync("div", { class: "form-content" }, socialComponents.length > 0 && (hAsync("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (hAsync("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 &&
|
|
8929
|
+
return (hAsync("div", { class: "widget-container", part: "container", "data-authstack-container": true }, hAsync("header", { class: "widget-header", part: "header" }, logoUrl && (hAsync("div", { class: "logo-wrapper", part: "logo-wrapper" }, hAsync("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), screen.title && (hAsync("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(screen.title) })), screen.description && (hAsync("p", { class: "description", part: "description", innerHTML: sanitizeHtml(screen.description) }))), hAsync("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (hAsync("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (hAsync("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), hAsync("form", { onSubmit: this.handleSubmit, action: screen.action, method: screen.method || "POST", part: "form" }, hiddenComponents.map((c) => (hAsync("input", { type: "hidden", name: c.id, id: c.id, key: c.id, value: this.formData[c.id] || "" }))), hAsync("div", { class: "form-content" }, this.renderPromptPartial(screen, "form-content-start"), socialComponents.length > 0 && (hAsync("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (hAsync("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 &&
|
|
8793
8930
|
fieldComponents.length > 0 &&
|
|
8794
8931
|
hasDivider && (hAsync("div", { class: "divider", part: "divider" }, hAsync("span", { class: "divider-text" }, dividerText))), hAsync("div", { class: "fields-section", part: "fields-section" }, isSearchableChoiceList && (hAsync("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) => {
|
|
8795
8932
|
// The search box lives inside the form; Enter would
|
|
@@ -8802,19 +8939,14 @@ class AuthheroWidget {
|
|
|
8802
8939
|
? "fields-list fields-list-scroll"
|
|
8803
8940
|
: "fields-list" }, (isSearchableChoiceList
|
|
8804
8941
|
? visibleChoiceButtons
|
|
8805
|
-
:
|
|
8942
|
+
: contentFieldComponents).map((component) => (hAsync("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 ||
|
|
8806
8943
|
(component.type === "NEXT_BUTTON" &&
|
|
8807
8944
|
component.config?.skip_validation !== true &&
|
|
8808
8945
|
requiredFieldsMissing) }))), isSearchableChoiceList &&
|
|
8809
|
-
visibleChoiceButtons.length === 0 && (hAsync("div", { class: "choice-list-empty", part: "choice-list-empty" }, "No matches"))))
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
}) }, link.linkText))) : (hAsync("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
|
|
8814
|
-
id: link.id,
|
|
8815
|
-
href: link.href,
|
|
8816
|
-
text: link.text,
|
|
8817
|
-
}) }, link.text))))))), screen.footer && (hAsync("div", { class: "widget-footer", part: "footer", innerHTML: sanitizeHtml(screen.footer) })))));
|
|
8946
|
+
visibleChoiceButtons.length === 0 && (hAsync("div", { class: "choice-list-empty", part: "choice-list-empty" }, "No matches")))), this.renderPromptPartial(screen, "form-content-end"), hasPromptPartials && (hAsync("div", { class: "form-footer", part: "form-footer" }, this.renderPromptPartial(screen, "form-footer-start"), footerComponents.map((component) => (hAsync("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 ||
|
|
8947
|
+
(component.type === "NEXT_BUTTON" &&
|
|
8948
|
+
component.config?.skip_validation !== true &&
|
|
8949
|
+
requiredFieldsMissing) }))), this.renderPromptPartial(screen, "form-footer-end")))), hasPromptPartials && (hAsync("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 && (hAsync("div", { class: "widget-footer", part: "footer", innerHTML: sanitizeHtml(screen.footer) })))));
|
|
8818
8950
|
}
|
|
8819
8951
|
static get watchers() { return {
|
|
8820
8952
|
"screenId": [{
|