@salesforcedevs/arch-components 1.28.5-node22-1 → 1.29.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/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ Copyright (c) 2020, Salesforce.com, Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+
8
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
+
10
+ * Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/lwc.config.json CHANGED
@@ -15,7 +15,6 @@
15
15
  "arch/cardGridD",
16
16
  "arch/cardNew",
17
17
  "arch/children",
18
- "arch/content",
19
18
  "arch/contentIcon",
20
19
  "arch/context",
21
20
  "arch/contextAdapter",
@@ -31,6 +30,7 @@
31
30
  "arch/pageHeaderA",
32
31
  "arch/pill",
33
32
  "arch/reflectedElement",
33
+ "arch/searchHybrid",
34
34
  "arch/searchList",
35
35
  "arch/sectionA",
36
36
  "arch/select",
package/package.json CHANGED
@@ -1,47 +1,48 @@
1
1
  {
2
- "name": "@salesforcedevs/arch-components",
3
- "version": "1.28.5-node22-1",
4
- "description": "Architect Lightning web components",
5
- "license": "MIT",
6
- "engines": {
7
- "node": "22.x"
8
- },
9
- "publishConfig": {
10
- "access": "public"
11
- },
12
- "dependencies": {
13
- "@coveo/headless": "3.22.2",
14
- "@floating-ui/dom": "1.5.1",
15
- "@optimizely/optimizely-sdk": "5.3.0",
16
- "@shikijs/colorized-brackets": "^3.12.2",
17
- "@types/throttle-debounce": "5.0.2",
18
- "@vimeo/player": "2.22.0",
19
- "classnames": "2.5.1",
20
- "composed-offset-position": "0.0.4",
21
- "coveo-search-ui": "2.10113.0",
22
- "debounce": "1.2.1",
23
- "js-cookie": "3.0.5",
24
- "jspdf": "^2.5.1",
25
- "lodash.defaults": "4.2.0",
26
- "lodash.get": "4.4.2",
27
- "lodash.kebabcase": "4.1.1",
28
- "memoize-one": "6.0.0",
29
- "microtip": "0.2.2",
30
- "shiki": "^3.12.2",
31
- "throttle-debounce": "5.0.0",
32
- "uuid": "9.0.1"
33
- },
34
- "devDependencies": {
35
- "@types/classnames": "2.3.1",
36
- "@types/debounce": "1.2.4",
37
- "@types/js-cookie": "3.0.6",
38
- "@types/lodash.defaults": "4.2.9",
39
- "@types/lodash.get": "4.4.9",
40
- "@types/lodash.kebabcase": "4.1.9",
41
- "@types/luxon": "3.4.2",
42
- "@types/uuid": "8.3.4",
43
- "@types/vimeo__player": "2.18.3",
44
- "eventsourcemock": "2.0.0",
45
- "luxon": "3.4.4"
46
- }
47
- }
2
+ "name": "@salesforcedevs/arch-components",
3
+ "version": "1.29.0",
4
+ "description": "Architect Lightning web components",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": "22.x"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "dependencies": {
13
+ "@coveo/headless": "3.22.2",
14
+ "@floating-ui/dom": "1.5.1",
15
+ "@optimizely/optimizely-sdk": "5.3.0",
16
+ "@shikijs/colorized-brackets": "^3.12.2",
17
+ "@types/throttle-debounce": "5.0.2",
18
+ "@vimeo/player": "2.22.0",
19
+ "classnames": "2.5.1",
20
+ "composed-offset-position": "0.0.4",
21
+ "coveo-search-ui": "2.10113.0",
22
+ "debounce": "1.2.1",
23
+ "js-cookie": "3.0.5",
24
+ "jspdf": "^2.5.1",
25
+ "lodash.defaults": "4.2.0",
26
+ "lodash.get": "4.4.2",
27
+ "lodash.kebabcase": "4.1.1",
28
+ "memoize-one": "6.0.0",
29
+ "microtip": "0.2.2",
30
+ "shiki": "^3.12.2",
31
+ "throttle-debounce": "5.0.0",
32
+ "uuid": "9.0.1"
33
+ },
34
+ "devDependencies": {
35
+ "@types/classnames": "2.3.1",
36
+ "@types/debounce": "1.2.4",
37
+ "@types/js-cookie": "3.0.6",
38
+ "@types/lodash.defaults": "4.2.9",
39
+ "@types/lodash.get": "4.4.9",
40
+ "@types/lodash.kebabcase": "4.1.9",
41
+ "@types/luxon": "3.4.2",
42
+ "@types/uuid": "8.3.4",
43
+ "@types/vimeo__player": "2.18.3",
44
+ "eventsourcemock": "2.0.0",
45
+ "luxon": "3.4.4"
46
+ },
47
+ "gitHead": "5d072e2fa81d7eccadcbfb4134ee007ce2b0c7c4"
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
+ }
@@ -0,0 +1,113 @@
1
+ @import "arch/reset";
2
+
3
+ :host {
4
+ font-size: var(--arch-font-size-md);
5
+ position: relative;
6
+ width: 100%;
7
+ }
8
+
9
+ .search-toolbar {
10
+ display: flex;
11
+ gap: var(--arch-spacing-4);
12
+ justify-content: flex-end;
13
+ margin-bottom: var(--arch-spacing-4);
14
+ }
15
+
16
+ .search-language-selector,
17
+ .search-sort-selector {
18
+ display: flex;
19
+ }
20
+
21
+ .search-facets {
22
+ display: flex;
23
+ flex-wrap: wrap;
24
+ gap: var(--arch-spacing-2);
25
+ margin-bottom: var(--arch-spacing-4);
26
+ }
27
+
28
+ .search-facet-chip {
29
+ align-items: center;
30
+ background-color: white;
31
+ border: 1px solid var(--arch-color-fog);
32
+ border-radius: var(--arch-radius-lg);
33
+ color: var(--arch-color-black);
34
+ cursor: pointer;
35
+ display: inline-flex;
36
+ font-family: inherit;
37
+ font-size: var(--arch-font-size-sm);
38
+ gap: var(--arch-spacing-2);
39
+ padding: var(--arch-spacing-1) var(--arch-spacing-3);
40
+ }
41
+
42
+ .search-facet-chip[aria-pressed="true"] {
43
+ background-color: var(--arch-color-meteorite);
44
+ border-color: var(--arch-color-meteorite);
45
+ color: white;
46
+ }
47
+
48
+ .search-facet-count {
49
+ font-size: var(--arch-font-size-xs);
50
+ opacity: 0.8;
51
+ }
52
+
53
+ .search-summary {
54
+ color: var(--arch-color-granite);
55
+ font-size: var(--arch-font-size-sm);
56
+ margin-bottom: var(--arch-spacing-4);
57
+ }
58
+
59
+ /* Semantic results list (WCAG 1.3.1) — reset the list chrome so the visual
60
+ is identical to the prior div-based markup. */
61
+ .search-result-list {
62
+ list-style: none;
63
+ margin: 0;
64
+ padding: 0;
65
+ }
66
+
67
+ .search-result-type {
68
+ color: var(--arch-color-meteorite);
69
+ font-size: var(--arch-font-size-sm);
70
+ line-height: var(--arch-line-height-snug);
71
+ }
72
+
73
+ .search-state {
74
+ padding: var(--arch-spacing-6) 0;
75
+ text-align: center;
76
+ }
77
+
78
+ .search-state-error {
79
+ color: var(--arch-color-error, #c23934);
80
+ }
81
+
82
+ .search-pagination {
83
+ align-items: center;
84
+ display: flex;
85
+ flex-wrap: wrap;
86
+ gap: var(--arch-spacing-2);
87
+ justify-content: center;
88
+ margin-top: var(--arch-spacing-6);
89
+ }
90
+
91
+ .search-pagination button {
92
+ background-color: white;
93
+ border: 1px solid var(--arch-color-fog);
94
+ border-radius: var(--arch-radius-md);
95
+ color: var(--arch-color-black);
96
+ cursor: pointer;
97
+ font-family: inherit;
98
+ font-size: var(--arch-font-size-sm);
99
+ min-width: 2.5rem;
100
+ padding: var(--arch-spacing-1) var(--arch-spacing-3);
101
+ }
102
+
103
+ .search-pagination button[disabled] {
104
+ cursor: default;
105
+ opacity: 0.5;
106
+ }
107
+
108
+ .search-pagination button[aria-current="page"] {
109
+ background-color: var(--arch-color-meteorite);
110
+ border-color: var(--arch-color-meteorite);
111
+ color: white;
112
+ opacity: 1;
113
+ }
@@ -0,0 +1,164 @@
1
+ <template>
2
+ <arch-section-a background="white" title="Search">
3
+ <div class="search-toolbar">
4
+ <template lwc:if={showLanguageSelector}>
5
+ <div class="search-language-selector">
6
+ <arch-select
7
+ assistive-text="Search language"
8
+ value={language}
9
+ onchange={handleLanguageChange}
10
+ >
11
+ <template for:each={localeOptions} for:item="locale">
12
+ <option key={locale.id} value={locale.id}>
13
+ {locale.displayText}
14
+ </option>
15
+ </template>
16
+ </arch-select>
17
+ </div>
18
+ </template>
19
+ <div class="search-sort-selector">
20
+ <arch-select
21
+ assistive-text="Sort results"
22
+ value={sort}
23
+ onchange={handleSortChange}
24
+ >
25
+ <template for:each={sortChoices} for:item="choice">
26
+ <option key={choice.id} value={choice.id}>
27
+ {choice.displayText}
28
+ </option>
29
+ </template>
30
+ </arch-select>
31
+ </div>
32
+ </div>
33
+
34
+ <template lwc:if={showFacets}>
35
+ <div class="search-facets" role="group" aria-label="Filter by type">
36
+ <template for:each={contentTypeFacets} for:item="facet">
37
+ <button
38
+ key={facet.value}
39
+ type="button"
40
+ class="search-facet-chip"
41
+ data-value={facet.value}
42
+ aria-pressed={facet.active}
43
+ onclick={handleFacetClick}
44
+ >
45
+ {facet.label}
46
+ <span class="search-facet-count">{facet.count}</span>
47
+ </button>
48
+ </template>
49
+ </div>
50
+ </template>
51
+
52
+ <template lwc:if={isLoading}>
53
+ <div class="search-state search-state-loading">
54
+ <arch-spinner size="medium"></arch-spinner>
55
+ </div>
56
+ </template>
57
+
58
+ <template lwc:if={isIdle}>
59
+ <div class="search-state search-state-idle">
60
+ <p>Enter a search to get started.</p>
61
+ </div>
62
+ </template>
63
+
64
+ <template lwc:if={isError}>
65
+ <div class="search-state search-state-error" role="alert">
66
+ <p>{errorMessage}</p>
67
+ </div>
68
+ </template>
69
+
70
+ <template lwc:if={isEmpty}>
71
+ <div class="search-state search-state-empty">
72
+ <center>
73
+ <img
74
+ src="https://developer.salesforce.com/ns-assets/ruth_search.png"
75
+ alt="Search tips illustration"
76
+ />
77
+ <h4 style="padding-top: 8px">Here are some search tips</h4>
78
+ <span style="display: block">
79
+ Check the spelling of your keywords.
80
+ </span>
81
+ <span style="display: block">
82
+ Try using fewer, different, or more general words.
83
+ </span>
84
+ </center>
85
+ </div>
86
+ </template>
87
+
88
+ <template lwc:if={hasResults}>
89
+ <p class="search-summary">{summaryText}</p>
90
+ <ul class="search-result-list">
91
+ <template for:each={results} for:item="result">
92
+ <li class="search-result-item" key={result.key}>
93
+ <arch-summary
94
+ compact
95
+ display-mode="brick-tile"
96
+ path={result.url}
97
+ title={result.decodedTitle}
98
+ >
99
+ <div slot="pretitle">
100
+ <template lwc:if={result.hasContentType}>
101
+ <span class="search-result-type">
102
+ {result.contentType}
103
+ </span>
104
+ </template>
105
+ </div>
106
+ <span slot="meta-right">
107
+ <template lwc:if={result.hasDate}>
108
+ <time>{result.displayDate}</time>
109
+ </template>
110
+ </span>
111
+ <span slot="meta-left">
112
+ <template lwc:if={result.hasDescription}>
113
+ {result.decodedDescription}
114
+ </template>
115
+ </span>
116
+ </arch-summary>
117
+ <!-- prettier-ignore -->
118
+ <hr>
119
+ </li>
120
+ </template>
121
+ </ul>
122
+
123
+ <template lwc:if={showPagination}>
124
+ <nav
125
+ class="search-pagination"
126
+ aria-label="Search results pages"
127
+ >
128
+ <button
129
+ type="button"
130
+ class="search-page-prev"
131
+ aria-label="Previous page"
132
+ disabled={isFirstPage}
133
+ onclick={handlePrevPage}
134
+ >
135
+ Previous
136
+ </button>
137
+ <template for:each={pageButtons} for:item="pageButton">
138
+ <button
139
+ key={pageButton.page}
140
+ type="button"
141
+ class="search-page-number"
142
+ data-page={pageButton.page}
143
+ aria-label={pageButton.label}
144
+ aria-current={pageButton.ariaCurrent}
145
+ disabled={pageButton.isCurrent}
146
+ onclick={handlePageClick}
147
+ >
148
+ {pageButton.label}
149
+ </button>
150
+ </template>
151
+ <button
152
+ type="button"
153
+ class="search-page-next"
154
+ aria-label="Next page"
155
+ disabled={isLastPage}
156
+ onclick={handleNextPage}
157
+ >
158
+ Next
159
+ </button>
160
+ </nav>
161
+ </template>
162
+ </template>
163
+ </arch-section-a>
164
+ </template>