@salesforcedevs/arch-components 1.28.1-banner-2 → 1.28.1
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/lwc.config.json +0 -1
- package/package.json +3 -3
- package/src/modules/arch/browserLocale/browserLocale.ts +104 -0
- package/src/modules/arch/icon/icon.ts +1 -1
- package/src/modules/arch/localePreference/localePreference.ts +72 -0
- package/src/modules/arch/searchList/searchList.css +6 -0
- package/src/modules/arch/searchList/searchList.html +18 -3
- package/src/modules/arch/searchList/searchList.ts +188 -41
- package/src/modules/arch/select/select.html +4 -3
- package/src/modules/arch/select/select.ts +2 -2
- package/src/modules/arch/content/__fixtures__/index.ts +0 -884
- package/src/modules/arch/content/content.css +0 -643
- package/src/modules/arch/content/content.html +0 -65
- package/src/modules/arch/content/content.stories.js +0 -21
- package/src/modules/arch/content/content.ts +0 -169
package/lwc.config.json
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforcedevs/arch-components",
|
|
3
|
-
"version": "1.28.1
|
|
3
|
+
"version": "1.28.1",
|
|
4
4
|
"description": "Architect Lightning web components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
7
|
-
"node": "
|
|
7
|
+
"node": "22.x"
|
|
8
8
|
},
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"eventsourcemock": "2.0.0",
|
|
45
45
|
"luxon": "3.4.4"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "e0db9e8eabf2d398d8b599163aeba4261866cb29"
|
|
48
48
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Resolves a browser's preferred language (BCP-47 tags from
|
|
3
|
+
* navigator.languages) to one of the site's supported locale ids
|
|
4
|
+
* (e.g. "en-us", "ja-jp", "zh-tw"). Used to pick a sensible default language
|
|
5
|
+
* for search when the page itself carries no locale.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const DEFAULT_LOCALE = "en-us";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Region/script-aware overrides for cases a plain language-only match can't
|
|
12
|
+
* resolve. Keys are normalized lowercase BCP-47 tags (or language-only codes);
|
|
13
|
+
* values are site locale ids. Order of checks: exact id -> these rules ->
|
|
14
|
+
* language-only first match -> default.
|
|
15
|
+
*
|
|
16
|
+
* Product decisions baked in (see search locale design):
|
|
17
|
+
* - bare "es" -> es-mx (languages.yml lists es-mx as the generic Spanish)
|
|
18
|
+
* - any pt-* -> pt-br (only Brazilian Portuguese is published)
|
|
19
|
+
* - no/nb/nn -> nb-no (only Norwegian id)
|
|
20
|
+
* - Chinese is matched on script/region, never collapsed to language-only.
|
|
21
|
+
*/
|
|
22
|
+
const TAG_OVERRIDES: Record<string, string> = {
|
|
23
|
+
// Chinese — Traditional
|
|
24
|
+
"zh-tw": "zh-tw",
|
|
25
|
+
"zh-hant": "zh-tw",
|
|
26
|
+
"zh-hk": "zh-tw",
|
|
27
|
+
"zh-mo": "zh-tw",
|
|
28
|
+
// Chinese — Simplified (and bare "zh")
|
|
29
|
+
zh: "zh-cn",
|
|
30
|
+
"zh-cn": "zh-cn",
|
|
31
|
+
"zh-hans": "zh-cn",
|
|
32
|
+
"zh-sg": "zh-cn",
|
|
33
|
+
// Spanish
|
|
34
|
+
es: "es-mx",
|
|
35
|
+
"es-es": "es-es",
|
|
36
|
+
// Portuguese (only pt-br exists)
|
|
37
|
+
pt: "pt-br",
|
|
38
|
+
"pt-pt": "pt-br",
|
|
39
|
+
// Norwegian (only nb-no exists)
|
|
40
|
+
no: "nb-no",
|
|
41
|
+
nb: "nb-no",
|
|
42
|
+
nn: "nb-no"
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Map a single language-only code (e.g. "ja", "de") to its site locale id by
|
|
47
|
+
* finding the first supported id whose language subtag matches.
|
|
48
|
+
*/
|
|
49
|
+
function languageOnlyMatch(
|
|
50
|
+
language: string,
|
|
51
|
+
supportedIds: string[]
|
|
52
|
+
): string | undefined {
|
|
53
|
+
return supportedIds.find((id) => id.split("-")[0] === language);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the best site locale id for the given browser languages.
|
|
58
|
+
* The first browser language that maps to a supported id wins; earlier entries
|
|
59
|
+
* take precedence over later ones (matching navigator.languages ordering).
|
|
60
|
+
* @param navigatorLanguages Ordered list of BCP-47 tags (navigator.languages).
|
|
61
|
+
* @param supportedIds Site locale ids actually offered (e.g. from the dropdown).
|
|
62
|
+
* @returns A supported locale id, or "en-us" if nothing matches.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveBrowserLocale(
|
|
65
|
+
navigatorLanguages: readonly string[] | undefined,
|
|
66
|
+
supportedIds: string[]
|
|
67
|
+
): string {
|
|
68
|
+
if (!navigatorLanguages || navigatorLanguages.length === 0) {
|
|
69
|
+
return DEFAULT_LOCALE;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const supported = new Set(supportedIds);
|
|
73
|
+
|
|
74
|
+
for (const raw of navigatorLanguages) {
|
|
75
|
+
if (!raw) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const tag = raw.toLowerCase();
|
|
79
|
+
|
|
80
|
+
// 1. Exact id match (e.g. "ja-jp" -> ja-jp).
|
|
81
|
+
if (supported.has(tag)) {
|
|
82
|
+
return tag;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 2. Region/script override (e.g. "zh-tw" -> zh-tw, "es" -> es-mx).
|
|
86
|
+
const overridden = TAG_OVERRIDES[tag];
|
|
87
|
+
if (overridden && supported.has(overridden)) {
|
|
88
|
+
return overridden;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 3. Language-only fall-through (e.g. "de-ch" -> de -> de-de).
|
|
92
|
+
const language = tag.split("-")[0];
|
|
93
|
+
const overriddenLang = TAG_OVERRIDES[language];
|
|
94
|
+
if (overriddenLang && supported.has(overriddenLang)) {
|
|
95
|
+
return overriddenLang;
|
|
96
|
+
}
|
|
97
|
+
const matched = languageOnlyMatch(language, supportedIds);
|
|
98
|
+
if (matched) {
|
|
99
|
+
return matched;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return DEFAULT_LOCALE;
|
|
104
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Per-device storage for the user's chosen search language, gated behind
|
|
3
|
+
* OneTrust functional-cookie consent.
|
|
4
|
+
*
|
|
5
|
+
* There is no shared consent util in the codebase; this mirrors the consent
|
|
6
|
+
* read used on the admin site (page-events-calendar.php's one_trust_notice),
|
|
7
|
+
* which parses the OptanonConsent cookie's `groups` field and inspects the
|
|
8
|
+
* functional category. OneTrust's standard functional group id is C0003;
|
|
9
|
+
* "C0003:1" means granted, "C0003:0" means denied.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const STORAGE_KEY = "arch-search-language";
|
|
13
|
+
const FUNCTIONAL_GROUP_ID = "C0003";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Read the OptanonConsent cookie and return whether the functional group is
|
|
17
|
+
* granted. Defensive: if OneTrust hasn't loaded or the cookie is absent
|
|
18
|
+
* (tests, pre-consent, non-prod), returns false so we never persist without
|
|
19
|
+
* an explicit grant.
|
|
20
|
+
*/
|
|
21
|
+
function hasFunctionalConsent(): boolean {
|
|
22
|
+
try {
|
|
23
|
+
const cookie = document.cookie
|
|
24
|
+
.split("; ")
|
|
25
|
+
.find((row) => row.startsWith("OptanonConsent="));
|
|
26
|
+
if (!cookie) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
// The cookie value is URL-encoded querystring-style, e.g.
|
|
30
|
+
// "...&groups=C0001:1,C0003:1,C0002:0&...".
|
|
31
|
+
const value = decodeURIComponent(cookie.split("=").slice(1).join("="));
|
|
32
|
+
const groups = new URLSearchParams(value).get("groups");
|
|
33
|
+
if (!groups) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return groups
|
|
37
|
+
.split(",")
|
|
38
|
+
.some((entry) => entry.trim() === `${FUNCTIONAL_GROUP_ID}:1`);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the stored language preference, or null if none / unavailable.
|
|
46
|
+
* Reading an already-stored preference is low-risk, so it is not consent-gated.
|
|
47
|
+
*/
|
|
48
|
+
export function getStoredLanguage(): string | null {
|
|
49
|
+
try {
|
|
50
|
+
return window.localStorage.getItem(STORAGE_KEY);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Persist the chosen language, but only if the user has granted functional
|
|
58
|
+
* consent. Returns true if the value was written, false if it was skipped
|
|
59
|
+
* (no consent) or failed. When skipped, the choice still applies for the
|
|
60
|
+
* current page/session via in-memory state and the ?lang= URL param.
|
|
61
|
+
*/
|
|
62
|
+
export function setStoredLanguage(language: string): boolean {
|
|
63
|
+
if (!language || !hasFunctionalConsent()) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
window.localStorage.setItem(STORAGE_KEY, language);
|
|
68
|
+
return true;
|
|
69
|
+
} catch (e) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<arch-section-a background="white" title={titleSearchResults}>
|
|
3
|
-
<template if
|
|
3
|
+
<template lwc:if={showLanguageSelector}>
|
|
4
|
+
<div class="search-language-selector">
|
|
5
|
+
<arch-select
|
|
6
|
+
assistive-text="Search language"
|
|
7
|
+
value={language}
|
|
8
|
+
onchange={handleLanguageChange}
|
|
9
|
+
>
|
|
10
|
+
<template for:each={localeOptions} for:item="locale">
|
|
11
|
+
<option key={locale.id} value={locale.id}>
|
|
12
|
+
{locale.displayText}
|
|
13
|
+
</option>
|
|
14
|
+
</template>
|
|
15
|
+
</arch-select>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
<template lwc:if={results}>
|
|
4
19
|
<template for:each={results} for:item="result">
|
|
5
|
-
<div key={result.
|
|
20
|
+
<div key={result.url}>
|
|
6
21
|
<arch-summary
|
|
7
22
|
description={result.description}
|
|
8
23
|
compact
|
|
@@ -19,7 +34,7 @@
|
|
|
19
34
|
</div>
|
|
20
35
|
</template>
|
|
21
36
|
</template>
|
|
22
|
-
<template if
|
|
37
|
+
<template lwc:if={showSearchSuggestions}>
|
|
23
38
|
<div style="display: block">
|
|
24
39
|
<center>
|
|
25
40
|
<img
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import { LightningElement, api
|
|
1
|
+
import { LightningElement, api } from "lwc";
|
|
2
2
|
import { track as trackEvent } from "arch/instrumentation";
|
|
3
|
+
import { resolveBrowserLocale } from "arch/browserLocale";
|
|
4
|
+
import { getStoredLanguage, setStoredLanguage } from "arch/localePreference";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_LANGUAGE = "en-us";
|
|
7
|
+
|
|
8
|
+
interface LocaleOption {
|
|
9
|
+
id: string;
|
|
10
|
+
displayText?: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
}
|
|
3
13
|
|
|
4
14
|
function decodeHtmlEntities(value: unknown): string {
|
|
5
15
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -23,52 +33,189 @@ export default class SearchList extends LightningElement {
|
|
|
23
33
|
@api algoliaAppId: string = "";
|
|
24
34
|
@api algoliaApiKey: string = "";
|
|
25
35
|
|
|
26
|
-
|
|
36
|
+
// The catalog of selectable locales, injected from njk as a JSON string
|
|
37
|
+
// (the doc-framework ALL_LANGUAGES global, sourced from languages.yml).
|
|
38
|
+
@api supportedLocales: string = "";
|
|
39
|
+
// The page's server-side locale, used as a fallback default.
|
|
40
|
+
@api defaultLanguage: string = DEFAULT_LANGUAGE;
|
|
41
|
+
|
|
42
|
+
// The effective search language. Resolved at runtime; not an @api so the
|
|
43
|
+
// dropdown can change it and re-query. These are reassigned wholesale (not
|
|
44
|
+
// mutated in place), so plain reactive fields suffice — no @track needed.
|
|
45
|
+
language: string = DEFAULT_LANGUAGE;
|
|
46
|
+
localeOptions: LocaleOption[] = [];
|
|
47
|
+
results: any;
|
|
48
|
+
|
|
49
|
+
private keywords: string = "";
|
|
50
|
+
private abortController?: AbortController;
|
|
27
51
|
|
|
28
52
|
connectedCallback() {
|
|
53
|
+
this.localeOptions = this.parseSupportedLocales();
|
|
54
|
+
this.language = this.resolveInitialLanguage();
|
|
55
|
+
|
|
29
56
|
const params = new URLSearchParams(window.location.search);
|
|
30
|
-
if (params
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`https://${this.algoliaAppId}-dsn.algolia.net/1/indexes/${this.algoliaIndex}?query=${keywords}&hitsPerPage=20`,
|
|
34
|
-
{
|
|
35
|
-
headers: {
|
|
36
|
-
"X-Algolia-Application-Id": this.algoliaAppId,
|
|
37
|
-
"X-Algolia-API-Key": this.algoliaApiKey
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
)
|
|
41
|
-
.then((result) => {
|
|
42
|
-
return result.json();
|
|
43
|
-
})
|
|
44
|
-
.then((json) => {
|
|
45
|
-
trackEvent(this.template.host, "search_executed", {
|
|
46
|
-
searchTerm: keywords,
|
|
47
|
-
resultCount: json.hits?.length || 0
|
|
48
|
-
});
|
|
49
|
-
if (json.hits && json.hits.length) {
|
|
50
|
-
this.titleSearchResults = `Search results for "${decodeURIComponent(
|
|
51
|
-
keywords
|
|
52
|
-
)}"`;
|
|
53
|
-
this.results = json.hits.map((hit: any) => ({
|
|
54
|
-
...hit,
|
|
55
|
-
description: decodeHtmlEntities(hit.description)
|
|
56
|
-
}));
|
|
57
|
-
this.showSearchSuggestions = false;
|
|
58
|
-
} else {
|
|
59
|
-
this.titleSearchResults = `No results for "${decodeURIComponent(
|
|
60
|
-
keywords
|
|
61
|
-
)}"`;
|
|
62
|
-
this.showSearchSuggestions = true;
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
.catch(() => {
|
|
66
|
-
this.titleSearchResults = `An error occured`;
|
|
67
|
-
this.showSearchSuggestions = false;
|
|
68
|
-
});
|
|
57
|
+
if (params.get("keywords")) {
|
|
58
|
+
this.keywords = params.get("keywords") || "";
|
|
59
|
+
this.runSearch();
|
|
69
60
|
} else {
|
|
70
61
|
this.titleSearchResults = "No keywords provided";
|
|
71
62
|
this.showSearchSuggestions = false;
|
|
72
63
|
}
|
|
73
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parse the injected locale catalog, restricting to entries with an id.
|
|
68
|
+
* Falls back to an empty list (which hides the dropdown) on bad input.
|
|
69
|
+
*/
|
|
70
|
+
private parseSupportedLocales(): LocaleOption[] {
|
|
71
|
+
if (!this.supportedLocales) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(this.supportedLocales);
|
|
76
|
+
if (!Array.isArray(parsed)) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
return parsed.filter(
|
|
80
|
+
(entry): entry is LocaleOption =>
|
|
81
|
+
!!entry && typeof entry.id === "string"
|
|
82
|
+
);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private get supportedIds(): string[] {
|
|
89
|
+
return this.localeOptions.map((option) => option.id);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the language to search in, most-explicit-wins:
|
|
94
|
+
* 1. ?lang= URL param (shared/bookmarked links)
|
|
95
|
+
* 2. stored preference (consented, from a prior visit)
|
|
96
|
+
* 3. browser preferred language mapped to a supported id
|
|
97
|
+
* 4. the page's server locale, else en-us
|
|
98
|
+
* Any candidate must be in the supported catalog (when one was provided).
|
|
99
|
+
*/
|
|
100
|
+
private resolveInitialLanguage(): string {
|
|
101
|
+
const ids = this.supportedIds;
|
|
102
|
+
const isSupported = (value: string | null | undefined): boolean =>
|
|
103
|
+
!!value && (ids.length === 0 || ids.includes(value));
|
|
104
|
+
|
|
105
|
+
const fromUrl = new URLSearchParams(window.location.search).get("lang");
|
|
106
|
+
if (isSupported(fromUrl)) {
|
|
107
|
+
return fromUrl as string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const stored = getStoredLanguage();
|
|
111
|
+
if (isSupported(stored)) {
|
|
112
|
+
return stored as string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (ids.length > 0) {
|
|
116
|
+
const detected = resolveBrowserLocale(
|
|
117
|
+
navigator.languages,
|
|
118
|
+
ids
|
|
119
|
+
);
|
|
120
|
+
if (isSupported(detected)) {
|
|
121
|
+
return detected;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return isSupported(this.defaultLanguage)
|
|
126
|
+
? this.defaultLanguage
|
|
127
|
+
: DEFAULT_LANGUAGE;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Run the Algolia query for the current keywords + language. Aborts any
|
|
132
|
+
* in-flight request so a fast language switch can't render stale results.
|
|
133
|
+
*/
|
|
134
|
+
private runSearch() {
|
|
135
|
+
this.abortController?.abort();
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
this.abortController = controller;
|
|
138
|
+
|
|
139
|
+
const keywords = this.keywords;
|
|
140
|
+
// Filter to the chosen language so results are isolated by locale
|
|
141
|
+
// (records are tagged with `language` by the indexer; `language` is
|
|
142
|
+
// registered as a filterOnly facet).
|
|
143
|
+
const languageFilter = encodeURIComponent(`language:${this.language}`);
|
|
144
|
+
fetch(
|
|
145
|
+
`https://${this.algoliaAppId}-dsn.algolia.net/1/indexes/${
|
|
146
|
+
this.algoliaIndex
|
|
147
|
+
}?query=${encodeURIComponent(
|
|
148
|
+
keywords
|
|
149
|
+
)}&filters=${languageFilter}&hitsPerPage=20`,
|
|
150
|
+
{
|
|
151
|
+
headers: {
|
|
152
|
+
"X-Algolia-Application-Id": this.algoliaAppId,
|
|
153
|
+
"X-Algolia-API-Key": this.algoliaApiKey
|
|
154
|
+
},
|
|
155
|
+
signal: controller.signal
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
.then((result) => result.json())
|
|
159
|
+
.then((json) => {
|
|
160
|
+
trackEvent(this.template.host, "search_executed", {
|
|
161
|
+
searchTerm: keywords,
|
|
162
|
+
language: this.language,
|
|
163
|
+
resultCount: json.hits?.length || 0
|
|
164
|
+
});
|
|
165
|
+
if (json.hits && json.hits.length) {
|
|
166
|
+
// keywords is already decoded by URLSearchParams.get.
|
|
167
|
+
this.titleSearchResults = `Search results for "${keywords}"`;
|
|
168
|
+
this.results = json.hits.map((hit: any) => ({
|
|
169
|
+
...hit,
|
|
170
|
+
description: decodeHtmlEntities(hit.description)
|
|
171
|
+
}));
|
|
172
|
+
this.showSearchSuggestions = false;
|
|
173
|
+
} else {
|
|
174
|
+
this.titleSearchResults = `No results for "${keywords}"`;
|
|
175
|
+
this.showSearchSuggestions = true;
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
.catch((error) => {
|
|
179
|
+
// Ignore aborts from a superseding language switch.
|
|
180
|
+
if (error?.name === "AbortError") {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// A 400 here usually means the index has not been reindexed
|
|
184
|
+
// with the filterOnly(language) facet yet (see deploy order).
|
|
185
|
+
console.error("Algolia search request failed:", error);
|
|
186
|
+
this.titleSearchResults = `An error occurred`;
|
|
187
|
+
this.showSearchSuggestions = false;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Dropdown change: switch language, persist (if consented), reflect in the
|
|
193
|
+
* URL for shareability, and re-run the same search.
|
|
194
|
+
*/
|
|
195
|
+
handleLanguageChange(event: CustomEvent) {
|
|
196
|
+
const selected = event.detail;
|
|
197
|
+
if (!selected || selected === this.language) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this.language = selected;
|
|
201
|
+
setStoredLanguage(selected);
|
|
202
|
+
|
|
203
|
+
const params = new URLSearchParams(window.location.search);
|
|
204
|
+
params.set("lang", selected);
|
|
205
|
+
window.history.replaceState(
|
|
206
|
+
{},
|
|
207
|
+
"",
|
|
208
|
+
`${window.location.pathname}?${params.toString()}${
|
|
209
|
+
window.location.hash
|
|
210
|
+
}`
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
if (this.keywords) {
|
|
214
|
+
this.runSearch();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
get showLanguageSelector(): boolean {
|
|
219
|
+
return this.localeOptions.length > 1;
|
|
220
|
+
}
|
|
74
221
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<label class={labelClassName} for="select">{labelText}</label>
|
|
3
3
|
<div class={containerClassName}>
|
|
4
4
|
<arch-icon
|
|
5
|
-
class="select-icon select-icon
|
|
5
|
+
class="select-icon select-icon-left"
|
|
6
6
|
if:true={iconSymbol}
|
|
7
7
|
size="medium"
|
|
8
8
|
sprite={iconSprite}
|
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
></arch-icon>
|
|
11
11
|
<select
|
|
12
12
|
name="arch-select"
|
|
13
|
-
id=
|
|
13
|
+
id="select"
|
|
14
|
+
aria-label={labelText}
|
|
14
15
|
lwc:dom="manual"
|
|
15
16
|
onchange={handleChange}
|
|
16
17
|
value={value}
|
|
17
18
|
></select>
|
|
18
19
|
<arch-icon
|
|
19
|
-
class="select-icon select-icon
|
|
20
|
+
class="select-icon select-icon-right"
|
|
20
21
|
size="small"
|
|
21
22
|
symbol="chevrondown"
|
|
22
23
|
></arch-icon>
|
|
@@ -13,7 +13,7 @@ export default class extends ReflectedElement {
|
|
|
13
13
|
|
|
14
14
|
private get containerClassName() {
|
|
15
15
|
return classnames("select-container", {
|
|
16
|
-
"select-container
|
|
16
|
+
"select-container-icon": this.iconSymbol
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -38,7 +38,7 @@ export default class extends ReflectedElement {
|
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
private handleChange(event) {
|
|
41
|
+
private handleChange(event: Event) {
|
|
42
42
|
this.dispatchEvent(
|
|
43
43
|
new CustomEvent("change", {
|
|
44
44
|
detail: this.contentElement.value
|