@authhero/widget 0.9.0 → 0.11.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/p-145b5ecd.entry.js +1 -0
- package/dist/authhero-widget/p-695138d6.entry.js +1 -0
- package/dist/cjs/authhero-node.cjs.entry.js +26 -96
- package/dist/cjs/authhero-widget.cjs.entry.js +139 -1
- package/dist/collection/components/authhero-node/authhero-node.js +26 -96
- package/dist/collection/components/authhero-widget/authhero-widget.js +7 -1
- package/dist/collection/utils/sanitize-html.js +132 -0
- package/dist/components/authhero-node.js +1 -1
- package/dist/components/authhero-widget.js +1 -1
- package/dist/components/p-BMoS6Gag.js +1 -0
- package/dist/esm/authhero-node.entry.js +26 -96
- package/dist/esm/authhero-widget.entry.js +139 -1
- package/dist/types/components/authhero-node/authhero-node.d.ts +5 -0
- package/dist/types/utils/sanitize-html.d.ts +20 -0
- package/hydrate/index.js +165 -97
- package/hydrate/index.mjs +165 -97
- package/package.json +1 -1
- package/dist/authhero-widget/p-30808298.entry.js +0 -1
- package/dist/authhero-widget/p-3ae71c86.entry.js +0 -1
- package/dist/components/p-DITKGXA_.js +0 -1
|
@@ -288,6 +288,139 @@ function applyCssVars(element, vars) {
|
|
|
288
288
|
});
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Sanitize HTML to only allow safe formatting tags
|
|
293
|
+
*
|
|
294
|
+
* Allowed tags:
|
|
295
|
+
* - <br>, <br/> - Line breaks
|
|
296
|
+
* - <em>, <i> - Italic
|
|
297
|
+
* - <strong>, <b> - Bold
|
|
298
|
+
* - <u> - Underline
|
|
299
|
+
* - <span> - Generic inline container (for styling)
|
|
300
|
+
* - <a> - Links (href attribute only, with target="_blank" and rel="noopener")
|
|
301
|
+
*
|
|
302
|
+
* All other tags and attributes are stripped.
|
|
303
|
+
*/
|
|
304
|
+
// Allowed tags and their allowed attributes
|
|
305
|
+
const ALLOWED_TAGS = {
|
|
306
|
+
br: [],
|
|
307
|
+
em: [],
|
|
308
|
+
i: [],
|
|
309
|
+
strong: [],
|
|
310
|
+
b: [],
|
|
311
|
+
u: [],
|
|
312
|
+
span: ["class"],
|
|
313
|
+
a: ["href", "class"],
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* Sanitize HTML string to only allow safe formatting tags
|
|
317
|
+
*
|
|
318
|
+
* @param html - The HTML string to sanitize
|
|
319
|
+
* @returns Sanitized HTML string safe for innerHTML
|
|
320
|
+
*/
|
|
321
|
+
function sanitizeHtml(html) {
|
|
322
|
+
if (!html)
|
|
323
|
+
return "";
|
|
324
|
+
// If no < character present, return as-is (optimization)
|
|
325
|
+
// Must check for any < to prevent bypassing sanitization with malformed tags
|
|
326
|
+
// like "<img src=x onerror=..." which forgiving HTML parsers may still execute
|
|
327
|
+
if (!html.includes("<")) {
|
|
328
|
+
return html;
|
|
329
|
+
}
|
|
330
|
+
// Use a simple regex-based approach that's safe for our limited use case
|
|
331
|
+
// This avoids needing DOMParser which may not be available in all environments
|
|
332
|
+
let result = html;
|
|
333
|
+
// First, escape all HTML
|
|
334
|
+
result = result
|
|
335
|
+
.replace(/&/g, "&")
|
|
336
|
+
.replace(/</g, "<")
|
|
337
|
+
.replace(/>/g, ">")
|
|
338
|
+
.replace(/"/g, """)
|
|
339
|
+
.replace(/'/g, "'");
|
|
340
|
+
// Then selectively re-enable allowed tags
|
|
341
|
+
for (const [tag, allowedAttrs] of Object.entries(ALLOWED_TAGS)) {
|
|
342
|
+
// Self-closing tags (like <br> and <br/>)
|
|
343
|
+
if (tag === "br") {
|
|
344
|
+
result = result.replace(/<br\s*\/?>/gi, "<br>");
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
// Opening tags with optional attributes
|
|
348
|
+
const openingPattern = new RegExp(`<${tag}((?:\\s+[a-z-]+(?:="[^&]*"|='[^&]*')?)*)\\s*>`, "gi");
|
|
349
|
+
result = result.replace(openingPattern, (_match, attrsStr) => {
|
|
350
|
+
// Parse and filter attributes
|
|
351
|
+
const filteredAttrs = [];
|
|
352
|
+
if (attrsStr) {
|
|
353
|
+
// Unescape the attributes string for parsing
|
|
354
|
+
const unescapedAttrs = attrsStr
|
|
355
|
+
.replace(/"/g, '"')
|
|
356
|
+
.replace(/'/g, "'")
|
|
357
|
+
.replace(/&/g, "&")
|
|
358
|
+
.replace(/</g, "<")
|
|
359
|
+
.replace(/>/g, ">");
|
|
360
|
+
// Extract attributes
|
|
361
|
+
const attrPattern = /([a-z-]+)=["']([^"']*)["']/gi;
|
|
362
|
+
let attrMatch;
|
|
363
|
+
while ((attrMatch = attrPattern.exec(unescapedAttrs)) !== null) {
|
|
364
|
+
const [, attrName, attrValue] = attrMatch;
|
|
365
|
+
if (attrName && allowedAttrs.includes(attrName.toLowerCase())) {
|
|
366
|
+
// For href, validate it's a safe URL
|
|
367
|
+
if (attrName.toLowerCase() === "href") {
|
|
368
|
+
if (isSafeUrl(attrValue || "")) {
|
|
369
|
+
filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// For <a> tags, always add security attributes
|
|
379
|
+
if (tag === "a") {
|
|
380
|
+
filteredAttrs.push('target="_blank"');
|
|
381
|
+
filteredAttrs.push('rel="noopener noreferrer"');
|
|
382
|
+
}
|
|
383
|
+
const attrsOutput = filteredAttrs.length
|
|
384
|
+
? " " + filteredAttrs.join(" ")
|
|
385
|
+
: "";
|
|
386
|
+
return `<${tag}${attrsOutput}>`;
|
|
387
|
+
});
|
|
388
|
+
// Closing tags
|
|
389
|
+
const closingPattern = new RegExp(`</${tag}>`, "gi");
|
|
390
|
+
result = result.replace(closingPattern, `</${tag}>`);
|
|
391
|
+
}
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Check if a URL is safe (http, https, or relative)
|
|
396
|
+
*/
|
|
397
|
+
function isSafeUrl(url) {
|
|
398
|
+
if (!url)
|
|
399
|
+
return false;
|
|
400
|
+
// Allow relative URLs
|
|
401
|
+
if (url.startsWith("/") || url.startsWith("#") || url.startsWith("?")) {
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
// Allow http and https
|
|
405
|
+
try {
|
|
406
|
+
const parsed = new URL(url, "https://example.com");
|
|
407
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Escape attribute value
|
|
415
|
+
*/
|
|
416
|
+
function escapeAttr(value) {
|
|
417
|
+
return value
|
|
418
|
+
.replace(/&/g, "&")
|
|
419
|
+
.replace(/"/g, """)
|
|
420
|
+
.replace(/</g, "<")
|
|
421
|
+
.replace(/>/g, ">");
|
|
422
|
+
}
|
|
423
|
+
|
|
291
424
|
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, 14px);line-height:var(--ah-line-height-base, 1.5);color:var(--ah-color-text, #1e212a);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.widget-container{max-width:var(--ah-widget-max-width, 400px);width:100%;margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);box-shadow:var(--ah-widget-shadow, 0 4px 22px 0 rgba(0, 0, 0, 0.11));box-sizing:border-box}.widget-header{padding:var(--ah-header-padding, 40px 48px 24px)}.widget-body{padding:var(--ah-body-padding, 0 48px 40px)}.logo-wrapper{display:var(--ah-logo-display, flex);justify-content:var(--ah-logo-align, center);margin-bottom:8px}.logo{display:block;height:var(--ah-logo-height, 52px);max-width:100%;width:auto;object-fit:contain}.title{font-size:var(--ah-font-size-title, 24px);font-weight:var(--ah-font-weight-title, 700);text-align:var(--ah-title-align, center);margin:var(--ah-title-margin, 24px 0 8px);color:var(--ah-color-header, #1e212a);line-height:1.2}.description{font-size:var(--ah-font-size-description, 14px);text-align:var(--ah-title-align, center);margin:var(--ah-description-margin, 0 0 8px);color:var(--ah-color-text, #1e212a);line-height:1.5}.message{padding:12px 16px;border-radius:4px;margin-bottom:16px;font-size:14px;line-height:1.5}.message-error{background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38)}.message-success{background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769)}form{display:flex;flex-direction:column}.form-content{display:flex;flex-direction:column}.social-section{display:flex;flex-direction:column;gap:8px;order:var(--ah-social-order, 2)}.fields-section{display:flex;flex-direction:column;order:var(--ah-fields-order, 0)}.divider{display:flex;align-items:center;text-align:center;margin:16px 0;order:var(--ah-divider-order, 1)}.divider::before,.divider::after{content:'';flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0}.links{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:16px}.link-wrapper{font-size:14px;color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400);transition:color 150ms ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.loading-spinner{width:32px;height:32px;margin:24px auto;border:3px solid var(--ah-color-border-muted, #e0e1e3);border-top-color:var(--ah-color-primary, #635dff);border-radius:50%;animation:spin 0.8s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.error-message{text-align:center;color:var(--ah-color-error, #d03c38);padding:16px;font-size:14px}@media (max-width: 480px){:host{display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}.widget-container{box-shadow:none;border-radius:0;max-width:none;width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}`;
|
|
292
425
|
|
|
293
426
|
const AuthheroWidget = class {
|
|
@@ -713,7 +846,12 @@ const AuthheroWidget = class {
|
|
|
713
846
|
if (result.screenId) {
|
|
714
847
|
this.screenId = result.screenId;
|
|
715
848
|
}
|
|
849
|
+
// Persist state (especially for session storage mode)
|
|
716
850
|
this.persistState();
|
|
851
|
+
// Update URL path if navigateUrl is provided (client-side navigation)
|
|
852
|
+
if (result.navigateUrl && this.shouldAutoNavigate) {
|
|
853
|
+
window.history.pushState({ screen: result.screenId, state: this.state }, "", result.navigateUrl);
|
|
854
|
+
}
|
|
717
855
|
// Apply branding if included
|
|
718
856
|
if (result.branding) {
|
|
719
857
|
this._branding = result.branding;
|
|
@@ -886,7 +1024,7 @@ const AuthheroWidget = class {
|
|
|
886
1024
|
const hasDivider = components.some((c) => this.isDividerComponent(c));
|
|
887
1025
|
// Get logo URL from theme.widget (takes precedence) or branding
|
|
888
1026
|
const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
|
|
889
|
-
return (h("div", { class: "widget-container", part: "container" }, 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" }))), this._screen.title && (h("h1", { class: "title", part: "title"
|
|
1027
|
+
return (h("div", { class: "widget-container", part: "container" }, 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" }))), this._screen.title && (h("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(this._screen.title) })), this._screen.description && (h("p", { class: "description", part: "description", innerHTML: sanitizeHtml(this._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, part: "form" }, 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 }))))), socialComponents.length > 0 &&
|
|
890
1028
|
fieldComponents.length > 0 &&
|
|
891
1029
|
hasDivider && (h("div", { class: "divider", part: "divider" }, h("span", { class: "divider-text" }, "Or"))), h("div", { class: "fields-section", part: "fields-section" }, 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 })))))), this._screen.links && this._screen.links.length > 0 && (h("div", { class: "links", part: "links" }, this._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, {
|
|
892
1030
|
id: link.id,
|
|
@@ -35,6 +35,11 @@ export declare class AuthheroNode {
|
|
|
35
35
|
}>;
|
|
36
36
|
private handleInput;
|
|
37
37
|
private handleCheckbox;
|
|
38
|
+
/**
|
|
39
|
+
* Sanitize a string for use in CSS class names and part tokens.
|
|
40
|
+
* Replaces spaces and special characters with hyphens, converts to lowercase.
|
|
41
|
+
*/
|
|
42
|
+
private sanitizeForCssToken;
|
|
38
43
|
private handleButtonClick;
|
|
39
44
|
private togglePasswordVisibility;
|
|
40
45
|
/**
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize HTML to only allow safe formatting tags
|
|
3
|
+
*
|
|
4
|
+
* Allowed tags:
|
|
5
|
+
* - <br>, <br/> - Line breaks
|
|
6
|
+
* - <em>, <i> - Italic
|
|
7
|
+
* - <strong>, <b> - Bold
|
|
8
|
+
* - <u> - Underline
|
|
9
|
+
* - <span> - Generic inline container (for styling)
|
|
10
|
+
* - <a> - Links (href attribute only, with target="_blank" and rel="noopener")
|
|
11
|
+
*
|
|
12
|
+
* All other tags and attributes are stripped.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Sanitize HTML string to only allow safe formatting tags
|
|
16
|
+
*
|
|
17
|
+
* @param html - The HTML string to sanitize
|
|
18
|
+
* @returns Sanitized HTML string safe for innerHTML
|
|
19
|
+
*/
|
|
20
|
+
export declare function sanitizeHtml(html: string | undefined | null): string;
|
package/hydrate/index.js
CHANGED
|
@@ -5015,6 +5015,17 @@ class AuthheroNode {
|
|
|
5015
5015
|
value: target.checked ? "true" : "false",
|
|
5016
5016
|
});
|
|
5017
5017
|
};
|
|
5018
|
+
/**
|
|
5019
|
+
* Sanitize a string for use in CSS class names and part tokens.
|
|
5020
|
+
* Replaces spaces and special characters with hyphens, converts to lowercase.
|
|
5021
|
+
*/
|
|
5022
|
+
sanitizeForCssToken(value) {
|
|
5023
|
+
return value
|
|
5024
|
+
.toLowerCase()
|
|
5025
|
+
.replace(/[^a-z0-9-]/g, "-") // Replace non-alphanumeric chars with hyphen
|
|
5026
|
+
.replace(/-+/g, "-") // Collapse multiple hyphens
|
|
5027
|
+
.replace(/^-|-$/g, ""); // Remove leading/trailing hyphens
|
|
5028
|
+
}
|
|
5018
5029
|
handleButtonClick = (e, type, value) => {
|
|
5019
5030
|
if (type !== "submit") {
|
|
5020
5031
|
e.preventDefault();
|
|
@@ -5177,113 +5188,32 @@ class AuthheroNode {
|
|
|
5177
5188
|
const providerDetails = component.config?.provider_details;
|
|
5178
5189
|
// Create a map of provider details for quick lookup
|
|
5179
5190
|
const detailsMap = new Map(providerDetails?.map((d) => [d.name, d]) ?? []);
|
|
5180
|
-
//
|
|
5181
|
-
const
|
|
5182
|
-
"google-oauth2",
|
|
5183
|
-
"google",
|
|
5184
|
-
"facebook",
|
|
5185
|
-
"apple",
|
|
5186
|
-
"github",
|
|
5187
|
-
"microsoft",
|
|
5188
|
-
"windowslive",
|
|
5189
|
-
"linkedin",
|
|
5190
|
-
"vipps",
|
|
5191
|
-
];
|
|
5192
|
-
// Find matching known provider from name or strategy
|
|
5193
|
-
const findKnownProvider = (name, strategy) => {
|
|
5194
|
-
const nameLower = name.toLowerCase();
|
|
5195
|
-
const strategyLower = strategy?.toLowerCase();
|
|
5196
|
-
// First check exact match on strategy
|
|
5197
|
-
if (strategyLower && knownProviders.includes(strategyLower)) {
|
|
5198
|
-
return strategyLower;
|
|
5199
|
-
}
|
|
5200
|
-
// Then check exact match on name
|
|
5201
|
-
if (knownProviders.includes(nameLower)) {
|
|
5202
|
-
return nameLower;
|
|
5203
|
-
}
|
|
5204
|
-
// Check if name contains a known provider (e.g., "Vipps Login" contains "vipps")
|
|
5205
|
-
for (const known of knownProviders) {
|
|
5206
|
-
if (nameLower.includes(known)) {
|
|
5207
|
-
return known;
|
|
5208
|
-
}
|
|
5209
|
-
}
|
|
5210
|
-
return null;
|
|
5211
|
-
};
|
|
5212
|
-
// Map provider IDs to display names
|
|
5213
|
-
const getProviderDisplayName = (provider) => {
|
|
5214
|
-
// First check provider_details
|
|
5191
|
+
// Get button text from provider_details (already contains the full button text from server)
|
|
5192
|
+
const getButtonText = (provider) => {
|
|
5215
5193
|
const details = detailsMap.get(provider);
|
|
5216
5194
|
if (details?.display_name) {
|
|
5217
5195
|
return details.display_name;
|
|
5218
5196
|
}
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
linkedin: "LinkedIn",
|
|
5225
|
-
apple: "Apple",
|
|
5226
|
-
microsoft: "Microsoft",
|
|
5227
|
-
windowslive: "Microsoft",
|
|
5228
|
-
amazon: "Amazon",
|
|
5229
|
-
dropbox: "Dropbox",
|
|
5230
|
-
bitbucket: "Bitbucket",
|
|
5231
|
-
spotify: "Spotify",
|
|
5232
|
-
slack: "Slack",
|
|
5233
|
-
discord: "Discord",
|
|
5234
|
-
twitch: "Twitch",
|
|
5235
|
-
line: "LINE",
|
|
5236
|
-
shopify: "Shopify",
|
|
5237
|
-
paypal: "PayPal",
|
|
5238
|
-
"paypal-sandbox": "PayPal",
|
|
5239
|
-
box: "Box",
|
|
5240
|
-
salesforce: "Salesforce",
|
|
5241
|
-
"salesforce-sandbox": "Salesforce",
|
|
5242
|
-
yahoo: "Yahoo",
|
|
5243
|
-
auth0: "Auth0",
|
|
5244
|
-
vipps: "Vipps",
|
|
5245
|
-
};
|
|
5246
|
-
return (displayNames[provider.toLowerCase()] ||
|
|
5247
|
-
provider
|
|
5248
|
-
.split("-")
|
|
5249
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
5250
|
-
.join(" "));
|
|
5197
|
+
// Fallback: use provider name with basic formatting
|
|
5198
|
+
return provider
|
|
5199
|
+
.split("-")
|
|
5200
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
5201
|
+
.join(" ");
|
|
5251
5202
|
};
|
|
5252
|
-
// Get provider icon
|
|
5203
|
+
// Get provider icon from provider_details icon_url
|
|
5253
5204
|
const getProviderIcon = (provider) => {
|
|
5254
|
-
// First check if we have a custom icon URL from provider_details
|
|
5255
5205
|
const details = detailsMap.get(provider);
|
|
5256
5206
|
if (details?.icon_url) {
|
|
5257
5207
|
return (hAsync("img", { class: "social-icon", src: details.icon_url, alt: details.display_name || provider }));
|
|
5258
5208
|
}
|
|
5259
|
-
//
|
|
5260
|
-
|
|
5261
|
-
const p = knownProvider || provider.toLowerCase();
|
|
5262
|
-
if (p === "google-oauth2" || p === "google") {
|
|
5263
|
-
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("path", { d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z", fill: "#4285F4" }), hAsync("path", { d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z", fill: "#34A853" }), hAsync("path", { d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z", fill: "#FBBC05" }), hAsync("path", { d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z", fill: "#EA4335" })));
|
|
5264
|
-
}
|
|
5265
|
-
if (p === "facebook") {
|
|
5266
|
-
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("path", { d: "M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z", fill: "#1877F2" })));
|
|
5267
|
-
}
|
|
5268
|
-
if (p === "apple") {
|
|
5269
|
-
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("path", { d: "M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z", fill: "#000000" })));
|
|
5270
|
-
}
|
|
5271
|
-
if (p === "github") {
|
|
5272
|
-
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("path", { d: "M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z", fill: "#181717" })));
|
|
5273
|
-
}
|
|
5274
|
-
if (p === "microsoft" || p === "windowslive") {
|
|
5275
|
-
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("path", { d: "M0 0h11.377v11.372H0V0z", fill: "#f25022" }), hAsync("path", { d: "M12.623 0H24v11.372H12.623V0z", fill: "#7fba00" }), hAsync("path", { d: "M0 12.623h11.377V24H0v-11.377z", fill: "#00a4ef" }), hAsync("path", { d: "M12.623 12.623H24V24H12.623v-11.377z", fill: "#ffb900" })));
|
|
5276
|
-
}
|
|
5277
|
-
if (p === "linkedin") {
|
|
5278
|
-
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("path", { d: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z", fill: "#0A66C2" })));
|
|
5279
|
-
}
|
|
5280
|
-
if (p === "vipps") {
|
|
5281
|
-
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 48 48", xmlns: "http://www.w3.org/2000/svg" }, hAsync("path", { fill: "#FF5B24", d: "M3.5,8h41c1.9,0,3.5,1.6,3.5,3.5v25c0,1.9-1.6,3.5-3.5,3.5h-41C1.6,40,0,38.4,0,36.5v-25C0,9.6,1.6,8,3.5,8z" }), hAsync("path", { fill: "#FFFFFF", d: "M27.9,20.3c1.4,0,2.6-1,2.6-2.5c0-1.5-1.2-2.5-2.6-2.5c-1.4,0-2.6,1-2.6,2.5C25.3,19.2,26.5,20.3,27.9,20.3z" }), hAsync("path", { fill: "#FFFFFF", d: "M31.2,24.4c-1.7,2.2-3.5,3.8-6.7,3.8c-3.2,0-5.8-2-7.7-4.8c-0.8-1.2-2-1.4-2.9-0.8c-0.8,0.6-1,1.8-0.3,2.9c2.7,4.1,6.5,6.6,10.9,6.6c4,0,7.2-2,9.6-5.2c0.9-1.2,0.9-2.5,0-3.1C33.3,22.9,32.1,23.2,31.2,24.4z" })));
|
|
5282
|
-
}
|
|
5283
|
-
// Default: generic globe icon for unknown providers
|
|
5284
|
-
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("circle", { cx: "12", cy: "12", r: "10", fill: "none", stroke: "#666", "stroke-width": "2" }), hAsync("path", { d: "M2 12h20M12 2c-2.5 2.5-4 5.5-4 10s1.5 7.5 4 10c2.5-2.5 4-5.5 4-10s-1.5-7.5-4-10z", fill: "none", stroke: "#666", "stroke-width": "2" })));
|
|
5209
|
+
// No icon provided - return null (button will just show text)
|
|
5210
|
+
return null;
|
|
5285
5211
|
};
|
|
5286
|
-
return (hAsync("div", { class: "social-buttons", part: "social-buttons" }, providers.map((provider) =>
|
|
5212
|
+
return (hAsync("div", { class: "social-buttons", part: "social-buttons" }, providers.map((provider) => {
|
|
5213
|
+
const safeProvider = this.sanitizeForCssToken(provider);
|
|
5214
|
+
const icon = getProviderIcon(provider);
|
|
5215
|
+
return (hAsync("button", { type: "button", class: `btn btn-secondary btn-social btn-social-${safeProvider}${icon ? "" : " no-icon"}`, part: `button button-secondary button-social button-social-${safeProvider}`, "data-provider": provider, disabled: this.disabled, onClick: (e) => this.handleButtonClick(e, "SOCIAL", provider), key: provider }, icon, hAsync("span", { part: "button-social-text" }, getButtonText(provider))));
|
|
5216
|
+
})));
|
|
5287
5217
|
}
|
|
5288
5218
|
// ===========================================================================
|
|
5289
5219
|
// Main Render
|
|
@@ -5661,6 +5591,139 @@ function applyCssVars(element, vars) {
|
|
|
5661
5591
|
});
|
|
5662
5592
|
}
|
|
5663
5593
|
|
|
5594
|
+
/**
|
|
5595
|
+
* Sanitize HTML to only allow safe formatting tags
|
|
5596
|
+
*
|
|
5597
|
+
* Allowed tags:
|
|
5598
|
+
* - <br>, <br/> - Line breaks
|
|
5599
|
+
* - <em>, <i> - Italic
|
|
5600
|
+
* - <strong>, <b> - Bold
|
|
5601
|
+
* - <u> - Underline
|
|
5602
|
+
* - <span> - Generic inline container (for styling)
|
|
5603
|
+
* - <a> - Links (href attribute only, with target="_blank" and rel="noopener")
|
|
5604
|
+
*
|
|
5605
|
+
* All other tags and attributes are stripped.
|
|
5606
|
+
*/
|
|
5607
|
+
// Allowed tags and their allowed attributes
|
|
5608
|
+
const ALLOWED_TAGS = {
|
|
5609
|
+
br: [],
|
|
5610
|
+
em: [],
|
|
5611
|
+
i: [],
|
|
5612
|
+
strong: [],
|
|
5613
|
+
b: [],
|
|
5614
|
+
u: [],
|
|
5615
|
+
span: ["class"],
|
|
5616
|
+
a: ["href", "class"],
|
|
5617
|
+
};
|
|
5618
|
+
/**
|
|
5619
|
+
* Sanitize HTML string to only allow safe formatting tags
|
|
5620
|
+
*
|
|
5621
|
+
* @param html - The HTML string to sanitize
|
|
5622
|
+
* @returns Sanitized HTML string safe for innerHTML
|
|
5623
|
+
*/
|
|
5624
|
+
function sanitizeHtml(html) {
|
|
5625
|
+
if (!html)
|
|
5626
|
+
return "";
|
|
5627
|
+
// If no < character present, return as-is (optimization)
|
|
5628
|
+
// Must check for any < to prevent bypassing sanitization with malformed tags
|
|
5629
|
+
// like "<img src=x onerror=..." which forgiving HTML parsers may still execute
|
|
5630
|
+
if (!html.includes("<")) {
|
|
5631
|
+
return html;
|
|
5632
|
+
}
|
|
5633
|
+
// Use a simple regex-based approach that's safe for our limited use case
|
|
5634
|
+
// This avoids needing DOMParser which may not be available in all environments
|
|
5635
|
+
let result = html;
|
|
5636
|
+
// First, escape all HTML
|
|
5637
|
+
result = result
|
|
5638
|
+
.replace(/&/g, "&")
|
|
5639
|
+
.replace(/</g, "<")
|
|
5640
|
+
.replace(/>/g, ">")
|
|
5641
|
+
.replace(/"/g, """)
|
|
5642
|
+
.replace(/'/g, "'");
|
|
5643
|
+
// Then selectively re-enable allowed tags
|
|
5644
|
+
for (const [tag, allowedAttrs] of Object.entries(ALLOWED_TAGS)) {
|
|
5645
|
+
// Self-closing tags (like <br> and <br/>)
|
|
5646
|
+
if (tag === "br") {
|
|
5647
|
+
result = result.replace(/<br\s*\/?>/gi, "<br>");
|
|
5648
|
+
continue;
|
|
5649
|
+
}
|
|
5650
|
+
// Opening tags with optional attributes
|
|
5651
|
+
const openingPattern = new RegExp(`<${tag}((?:\\s+[a-z-]+(?:="[^&]*"|='[^&]*')?)*)\\s*>`, "gi");
|
|
5652
|
+
result = result.replace(openingPattern, (_match, attrsStr) => {
|
|
5653
|
+
// Parse and filter attributes
|
|
5654
|
+
const filteredAttrs = [];
|
|
5655
|
+
if (attrsStr) {
|
|
5656
|
+
// Unescape the attributes string for parsing
|
|
5657
|
+
const unescapedAttrs = attrsStr
|
|
5658
|
+
.replace(/"/g, '"')
|
|
5659
|
+
.replace(/'/g, "'")
|
|
5660
|
+
.replace(/&/g, "&")
|
|
5661
|
+
.replace(/</g, "<")
|
|
5662
|
+
.replace(/>/g, ">");
|
|
5663
|
+
// Extract attributes
|
|
5664
|
+
const attrPattern = /([a-z-]+)=["']([^"']*)["']/gi;
|
|
5665
|
+
let attrMatch;
|
|
5666
|
+
while ((attrMatch = attrPattern.exec(unescapedAttrs)) !== null) {
|
|
5667
|
+
const [, attrName, attrValue] = attrMatch;
|
|
5668
|
+
if (attrName && allowedAttrs.includes(attrName.toLowerCase())) {
|
|
5669
|
+
// For href, validate it's a safe URL
|
|
5670
|
+
if (attrName.toLowerCase() === "href") {
|
|
5671
|
+
if (isSafeUrl(attrValue || "")) {
|
|
5672
|
+
filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
else {
|
|
5676
|
+
filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
|
|
5677
|
+
}
|
|
5678
|
+
}
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5681
|
+
// For <a> tags, always add security attributes
|
|
5682
|
+
if (tag === "a") {
|
|
5683
|
+
filteredAttrs.push('target="_blank"');
|
|
5684
|
+
filteredAttrs.push('rel="noopener noreferrer"');
|
|
5685
|
+
}
|
|
5686
|
+
const attrsOutput = filteredAttrs.length
|
|
5687
|
+
? " " + filteredAttrs.join(" ")
|
|
5688
|
+
: "";
|
|
5689
|
+
return `<${tag}${attrsOutput}>`;
|
|
5690
|
+
});
|
|
5691
|
+
// Closing tags
|
|
5692
|
+
const closingPattern = new RegExp(`</${tag}>`, "gi");
|
|
5693
|
+
result = result.replace(closingPattern, `</${tag}>`);
|
|
5694
|
+
}
|
|
5695
|
+
return result;
|
|
5696
|
+
}
|
|
5697
|
+
/**
|
|
5698
|
+
* Check if a URL is safe (http, https, or relative)
|
|
5699
|
+
*/
|
|
5700
|
+
function isSafeUrl(url) {
|
|
5701
|
+
if (!url)
|
|
5702
|
+
return false;
|
|
5703
|
+
// Allow relative URLs
|
|
5704
|
+
if (url.startsWith("/") || url.startsWith("#") || url.startsWith("?")) {
|
|
5705
|
+
return true;
|
|
5706
|
+
}
|
|
5707
|
+
// Allow http and https
|
|
5708
|
+
try {
|
|
5709
|
+
const parsed = new URL(url, "https://example.com");
|
|
5710
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
5711
|
+
}
|
|
5712
|
+
catch {
|
|
5713
|
+
return false;
|
|
5714
|
+
}
|
|
5715
|
+
}
|
|
5716
|
+
/**
|
|
5717
|
+
* Escape attribute value
|
|
5718
|
+
*/
|
|
5719
|
+
function escapeAttr(value) {
|
|
5720
|
+
return value
|
|
5721
|
+
.replace(/&/g, "&")
|
|
5722
|
+
.replace(/"/g, """)
|
|
5723
|
+
.replace(/</g, "<")
|
|
5724
|
+
.replace(/>/g, ">");
|
|
5725
|
+
}
|
|
5726
|
+
|
|
5664
5727
|
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, 14px);line-height:var(--ah-line-height-base, 1.5);color:var(--ah-color-text, #1e212a);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.widget-container{max-width:var(--ah-widget-max-width, 400px);width:100%;margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);box-shadow:var(--ah-widget-shadow, 0 4px 22px 0 rgba(0, 0, 0, 0.11));box-sizing:border-box}.widget-header{padding:var(--ah-header-padding, 40px 48px 24px)}.widget-body{padding:var(--ah-body-padding, 0 48px 40px)}.logo-wrapper{display:var(--ah-logo-display, flex);justify-content:var(--ah-logo-align, center);margin-bottom:8px}.logo{display:block;height:var(--ah-logo-height, 52px);max-width:100%;width:auto;object-fit:contain}.title{font-size:var(--ah-font-size-title, 24px);font-weight:var(--ah-font-weight-title, 700);text-align:var(--ah-title-align, center);margin:var(--ah-title-margin, 24px 0 8px);color:var(--ah-color-header, #1e212a);line-height:1.2}.description{font-size:var(--ah-font-size-description, 14px);text-align:var(--ah-title-align, center);margin:var(--ah-description-margin, 0 0 8px);color:var(--ah-color-text, #1e212a);line-height:1.5}.message{padding:12px 16px;border-radius:4px;margin-bottom:16px;font-size:14px;line-height:1.5}.message-error{background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38)}.message-success{background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769)}form{display:flex;flex-direction:column}.form-content{display:flex;flex-direction:column}.social-section{display:flex;flex-direction:column;gap:8px;order:var(--ah-social-order, 2)}.fields-section{display:flex;flex-direction:column;order:var(--ah-fields-order, 0)}.divider{display:flex;align-items:center;text-align:center;margin:16px 0;order:var(--ah-divider-order, 1)}.divider::before,.divider::after{content:'';flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0}.links{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:16px}.link-wrapper{font-size:14px;color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400);transition:color 150ms ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.loading-spinner{width:32px;height:32px;margin:24px auto;border:3px solid var(--ah-color-border-muted, #e0e1e3);border-top-color:var(--ah-color-primary, #635dff);border-radius:50%;animation:spin 0.8s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.error-message{text-align:center;color:var(--ah-color-error, #d03c38);padding:16px;font-size:14px}@media (max-width: 480px){:host{display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}.widget-container{box-shadow:none;border-radius:0;max-width:none;width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}`;
|
|
5665
5728
|
|
|
5666
5729
|
class AuthheroWidget {
|
|
@@ -6086,7 +6149,12 @@ class AuthheroWidget {
|
|
|
6086
6149
|
if (result.screenId) {
|
|
6087
6150
|
this.screenId = result.screenId;
|
|
6088
6151
|
}
|
|
6152
|
+
// Persist state (especially for session storage mode)
|
|
6089
6153
|
this.persistState();
|
|
6154
|
+
// Update URL path if navigateUrl is provided (client-side navigation)
|
|
6155
|
+
if (result.navigateUrl && this.shouldAutoNavigate) {
|
|
6156
|
+
window.history.pushState({ screen: result.screenId, state: this.state }, "", result.navigateUrl);
|
|
6157
|
+
}
|
|
6090
6158
|
// Apply branding if included
|
|
6091
6159
|
if (result.branding) {
|
|
6092
6160
|
this._branding = result.branding;
|
|
@@ -6259,7 +6327,7 @@ class AuthheroWidget {
|
|
|
6259
6327
|
const hasDivider = components.some((c) => this.isDividerComponent(c));
|
|
6260
6328
|
// Get logo URL from theme.widget (takes precedence) or branding
|
|
6261
6329
|
const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
|
|
6262
|
-
return (hAsync("div", { class: "widget-container", part: "container" }, 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" }))), this._screen.title && (hAsync("h1", { class: "title", part: "title"
|
|
6330
|
+
return (hAsync("div", { class: "widget-container", part: "container" }, 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" }))), this._screen.title && (hAsync("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(this._screen.title) })), this._screen.description && (hAsync("p", { class: "description", part: "description", innerHTML: sanitizeHtml(this._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, part: "form" }, 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, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading }))))), socialComponents.length > 0 &&
|
|
6263
6331
|
fieldComponents.length > 0 &&
|
|
6264
6332
|
hasDivider && (hAsync("div", { class: "divider", part: "divider" }, hAsync("span", { class: "divider-text" }, "Or"))), hAsync("div", { class: "fields-section", part: "fields-section" }, fieldComponents.map((component) => (hAsync("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 })))))), this._screen.links && this._screen.links.length > 0 && (hAsync("div", { class: "links", part: "links" }, this._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, {
|
|
6265
6333
|
id: link.id,
|