@salesforcedevs/arch-components 1.20.17-alpha5 → 1.20.17-alpha6

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.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/src/{modules/common → common}/context/context.ts +21 -21
  3. package/src/common/effectAdapter/__tests__/effectAdapter.test.ts +12 -0
  4. package/src/common/effectAdapter/effectAdapter.html +1 -0
  5. package/src/common/effectAdapter/effectAdapter.ts +18 -0
  6. package/src/common/reflectedElement/__tests__/modules/test/select/select.html +3 -0
  7. package/src/common/reflectedElement/__tests__/modules/test/select/select.ts +7 -0
  8. package/src/common/reflectedElement/__tests__/modules/test/selectTransform/selectTransform.html +3 -0
  9. package/src/common/reflectedElement/__tests__/modules/test/selectTransform/selectTransform.ts +18 -0
  10. package/src/common/reflectedElement/__tests__/reflectedElement.test.ts +75 -0
  11. package/src/common/reflectedElement/reflectedElement.ts +50 -0
  12. package/src/common/slot/__tests__/slot.test.ts +96 -0
  13. package/src/common/slot/slot.ts +20 -0
  14. package/src/sa/coverage/coverage.css +35 -0
  15. package/src/sa/coverage/coverage.html +15 -0
  16. package/src/sa/coverage/coverage.ts +404 -0
  17. package/src/sa/coverage/types.d.ts +22 -0
  18. package/src/sa/expandableSection/expandableSection.css +24 -0
  19. package/src/sa/expandableSection/expandableSection.html +20 -0
  20. package/src/sa/expandableSection/expandableSection.stories.ts +37 -0
  21. package/src/sa/expandableSection/expandableSection.ts +24 -0
  22. package/src/sa/explorer/explorer.css +303 -0
  23. package/src/sa/explorer/explorer.html +403 -0
  24. package/src/sa/explorer/explorer.ts +664 -0
  25. package/src/sa/explorer/types.d.ts +60 -0
  26. package/src/sa/gallery/gallery.css +358 -0
  27. package/src/sa/gallery/gallery.html +65 -0
  28. package/src/sa/gallery/gallery.ts +300 -0
  29. package/src/sa/gallery/types.d.ts +35 -0
  30. package/src/sa/socialShare/socialShare.css +49 -0
  31. package/src/sa/socialShare/socialShare.html +56 -0
  32. package/src/sa/socialShare/socialShare.ts +29 -0
  33. /package/src/{modules/common → common}/context/context.html +0 -0
@@ -0,0 +1,300 @@
1
+ import { LightningElement, api, track } from 'lwc';
2
+ import { Card } from 'tm/card';
3
+ import { FilterOption } from './types';
4
+ import { sendInteractionEvent, InteractionEventTypes } from 'shared/helpers';
5
+ import { debounce } from 'shared/debounce';
6
+
7
+ // function to get the cards array from the json payload
8
+ async function getGridCards(data, imageHash) {
9
+ const parsedData = JSON.parse(data);
10
+ // Sort the data based on the `sort` value
11
+ parsedData.sort((a, b) => a.sort - b.sort);
12
+ // load the gallery cards
13
+ return await parsedData.map((item) => {
14
+ const card: Card = {
15
+ assistiveText: "Click here to go to this resource",
16
+ //ctaTitle: "Read More",
17
+ ctaTarget: null,
18
+ pretitleHref: null,
19
+ pretitleHrefText: null
20
+ }
21
+
22
+ // Dynamically set all other properties based on the data object
23
+ for (const prop in item) {
24
+ if (item.hasOwnProperty(prop)) {
25
+ switch (prop) {
26
+ case "url":
27
+ card.ctaHref = item[prop];
28
+ break;
29
+ default:
30
+ card[prop] = item[prop];
31
+ }
32
+ }
33
+
34
+ // Set imgSrc to thumbnail if it exists, otherwise use image
35
+ if (item.thumbnail) {
36
+ card.imgSrc = `/1/asset/immutable/s/${imageHash}${item.thumbnail}`;
37
+ } else if (item.image) {
38
+ card.imgSrc = item.image;
39
+ }
40
+ }
41
+
42
+ return card;
43
+ });
44
+ }
45
+ export default class gallery extends LightningElement {
46
+
47
+ // get the inputs from the config
48
+ _content: string | null = null;
49
+ _filterData;
50
+ _filterProperty;
51
+ _filterLabel;
52
+ _searchTitle;
53
+ _imageHash: string | null = null;
54
+ @api containerId: string;
55
+
56
+ // second filter drop down is optional and pulled from the json data set
57
+ _showMetaFilter = false;
58
+ _metaFilterProperty;
59
+ _metaFilterLabel;
60
+
61
+ // get the inputs from the form
62
+ _selectFilter: string = '';
63
+ _keywordFilter: string = '';
64
+ _selectMetaFilter: string = '';
65
+
66
+ // set up the outputs
67
+ allCards: Card[] = [];
68
+ filteredCards: Card[] = [];
69
+ shareUrl = window.location.href;
70
+
71
+ // prep the filters options
72
+ filterOptions: FilterOption[] = [];
73
+ metaFilterOptions: FilterOption[] = [];
74
+
75
+ // populate the filter drop down
76
+ connectedCallback() {
77
+ // Check if we're on the data model gallery page
78
+ if (this.containerId === 'data-model-gallery' && window.location.hash === '#data-model-gallery') {
79
+ window.location.href = 'https://developer.salesforce.com/docs/platform/data-models/guide/get-started.html';
80
+ }
81
+ }
82
+ get socialTitle(){
83
+ //Search title is plurar, removing last letter to make the title singular (should be a letter S)
84
+ return `${this._searchTitle.substring(0, this._searchTitle.length - 1)} Gallery`;
85
+ }
86
+
87
+ // fetch the filter data from a url
88
+ async fetchFilterData() {
89
+ try {
90
+ const response = await fetch(this._filterData);
91
+ const data = await response.json();
92
+ this.filterOptions = data as FilterOption[];
93
+ } catch (error) {
94
+ console.error("An error occurred getting the filter list:", error.message);
95
+ }
96
+ }
97
+
98
+ // handle the filter selection change
99
+ filterChange(evt: { detail: string }) {
100
+ this._selectFilter = evt.detail;
101
+ this.applyFilters();
102
+ }
103
+
104
+ private sendInteractionEvent = debounce((event,target)=>{
105
+ sendInteractionEvent("Gallery Search",InteractionEventTypes.INPUT_CHANGE,event,target);
106
+ },1000);
107
+
108
+ // handle typing in the search box
109
+ keywordFilterChange(evt: InputEvent) {
110
+ const target = (evt.currentTarget as HTMLInputElement);
111
+ this.sendInteractionEvent(evt,target);
112
+ this._keywordFilter = (evt.currentTarget as HTMLInputElement).value;
113
+ this.applyFilters();
114
+ }
115
+
116
+ // handle the filter selection change
117
+ metaFilterChange(evt: { detail: string }) {
118
+ this._selectMetaFilter = evt.detail;
119
+ this.applyFilters();
120
+ }
121
+
122
+ // update the gallery based on filtering
123
+ async applyFilters() {
124
+ this.filteredCards = this.allCards.filter((card) => {
125
+ let isFilterMatch = (this._selectFilter === '' || card[this._filterProperty]?.toLowerCase().includes(this._selectFilter.toLocaleLowerCase()));
126
+ let isMetaMatch = ((this._showMetaFilter) ? ((this._selectMetaFilter === '' || card[this._metaFilterProperty]?.toLowerCase().includes(this._selectMetaFilter.toLocaleLowerCase()))) : true);
127
+ let isKeywordMatch = (this._keywordFilter === ''
128
+ || (card.pretitle && card.pretitle.toLowerCase().includes(this._keywordFilter.toLowerCase()))
129
+ || (card.title && card.title.toLowerCase().includes(this._keywordFilter.toLowerCase()))
130
+ || (card.description && card.description.toLowerCase().includes(this._keywordFilter.toLowerCase()))
131
+ );
132
+
133
+ if (isFilterMatch && isMetaMatch && isKeywordMatch)
134
+ return card;
135
+ });
136
+
137
+ // Update page state so that we track filters in URL params
138
+ const params = new URLSearchParams();
139
+
140
+ if (this._selectFilter !== '')
141
+ params.set(this._filterLabel, this._selectFilter);
142
+ if (this._showMetaFilter && this._selectMetaFilter !== '')
143
+ params.set(this._metaFilterLabel, this._selectMetaFilter);
144
+ if (this._keywordFilter !== '')
145
+ params.set('keywords', this._keywordFilter);
146
+
147
+ const paramsString = params.toString();
148
+ let updatedUrl = window.location.pathname;
149
+
150
+ // Update the hash to the containerId if it's been filtered or from the URL on load
151
+ const hash = this.containerId ? `#${this.containerId}` : window.location.hash.split('?')[0];
152
+
153
+ if (hash)
154
+ updatedUrl += hash;
155
+ if (paramsString)
156
+ updatedUrl += `?${paramsString}`;
157
+
158
+ history.replaceState(history.state, document.title, updatedUrl);
159
+ this.shareUrl = `${window.location.protocol}//${window.location.hostname}${updatedUrl}`;
160
+
161
+ // Scrolls to container element to keep new data in focus.
162
+ if (hash) {
163
+ const targetElement = document.querySelector(`#${this.containerId}`);
164
+ // Scroll to the target element if it exists
165
+ if (targetElement) {
166
+ targetElement.scrollIntoView({ behavior: "smooth" });
167
+ }
168
+ }
169
+ }
170
+
171
+ checkURLParams() {
172
+ // Load filters from URL params
173
+ let requireFilterUpdate = false;
174
+ const url = new URL(window.location.href.replace('#', ''));
175
+ const params = url.searchParams;
176
+ for (const [key, value] of params.entries()) {
177
+ switch (key) {
178
+ case this._filterLabel:
179
+ this._selectFilter = value;
180
+ requireFilterUpdate = true;
181
+ break;
182
+ case this._metaFilterLabel:
183
+ this._selectMetaFilter = value;
184
+ requireFilterUpdate = true;
185
+ break;
186
+ case 'keywords':
187
+ this._keywordFilter = value;
188
+ requireFilterUpdate = true;
189
+ break;
190
+ default:
191
+ break;
192
+ }
193
+ };
194
+
195
+ return requireFilterUpdate;
196
+ }
197
+
198
+ handleResetFiltersClick() {
199
+ this._selectFilter = '';
200
+ (this._showMetaFilter) ? this._selectMetaFilter = '' : true;
201
+ this._keywordFilter = '';
202
+ this.applyFilters();
203
+ }
204
+
205
+ private updateCards() {
206
+ if (this._content && this._imageHash) {
207
+ try {
208
+ getGridCards(this._content, this._imageHash).then((res) => {
209
+ this.allCards = this.filteredCards = res;
210
+ if (this._showMetaFilter) {
211
+ let typesArr = [...new Set(this.allCards.map(item => item.type))];
212
+ typesArr.sort((a, b) => a.localeCompare(b));
213
+ this.metaFilterOptions = typesArr.map(type => ({ label: type, value: type })) as FilterOption[];
214
+ }
215
+ if (this.checkURLParams())
216
+ this.applyFilters();
217
+ });
218
+ } catch (error) {
219
+ console.error("An error occurred getting the cards:", error.message);
220
+ }
221
+ }
222
+ }
223
+
224
+ // bind the config to the variables
225
+ @api
226
+ set searchtitle(value) {
227
+ if (this._searchTitle !== value) {
228
+ this._searchTitle = value;
229
+ }
230
+ }
231
+ get searchtitle() {
232
+ return this._searchTitle;
233
+ }
234
+
235
+ @api
236
+ set imageHash(value) {
237
+ this._imageHash = value;
238
+ this.updateCards();
239
+ }
240
+ get imageHash() {
241
+ return this._imageHash;
242
+ }
243
+
244
+ @api
245
+ set content(value) {
246
+ this._content = value;
247
+ this.updateCards();
248
+ }
249
+ get content() {
250
+ return this._content;
251
+ }
252
+
253
+ @api
254
+ set filter(value) {
255
+ if (this._filterData !== value) {
256
+ this._filterData = value;
257
+ if (!this._filterData.endsWith('.json')) {
258
+ this.filterOptions = JSON.parse(this._filterData) as FilterOption[];
259
+ } else {
260
+ this.fetchFilterData();
261
+ }
262
+ }
263
+ }
264
+ get filter() {
265
+ return this._filterData;
266
+ }
267
+
268
+ @api
269
+ set property(value) {
270
+ if (this._filterProperty !== value) {
271
+ this._filterProperty = value;
272
+ }
273
+ }
274
+ get property() {
275
+ return this._filterProperty;
276
+ }
277
+
278
+ @api
279
+ set label(value) {
280
+ if (this._filterLabel !== value) {
281
+ this._filterLabel = value;
282
+ }
283
+ }
284
+ get label() {
285
+ return this._filterLabel;
286
+ }
287
+
288
+ // below is for getting and setting the optional second filter
289
+ @api
290
+ set metafilters(value) {
291
+ if (this._metaFilterProperty !== value) {
292
+ this._showMetaFilter = true;
293
+ this._metaFilterProperty = value;
294
+ this._metaFilterLabel = value.charAt(0).toUpperCase() + value.slice(1);
295
+ }
296
+ }
297
+ get metafilters() {
298
+ return this._metaFilterProperty;
299
+ }
300
+ }
@@ -0,0 +1,35 @@
1
+ export type GalleryCard = {
2
+ id?: string;
3
+ pretitle: string | null;
4
+ title: string;
5
+ description: string;
6
+ image: string | null;
7
+ status?: string | null;
8
+ sort: number | null;
9
+ updated?: Date | null;
10
+ productAreas: string[];
11
+ url: string | null;
12
+ };
13
+
14
+ export type CardAttribute = string | null;
15
+ export type Card = {
16
+ assistiveText: CardAttribute;
17
+ ctaHref: CardAttribute;
18
+ ctaTarget: CardAttribute;
19
+ ctaTitle: CardAttribute;
20
+ description: CardAttribute;
21
+ imgSrc: CardAttribute;
22
+ pretitle: CardAttribute;
23
+ pretitleHref: CardAttribute;
24
+ pretitleHrefText: CardAttribute;
25
+ title: CardAttribute;
26
+ };
27
+
28
+ export type GalleryCards = {
29
+ items: Card[];
30
+ }
31
+
32
+ export type FilterOption = {
33
+ label: string;
34
+ value: string;
35
+ };
@@ -0,0 +1,49 @@
1
+ @import 'tds/reset';
2
+
3
+ :host {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: space-between;
7
+ }
8
+
9
+ .actions {
10
+ align-items: center;
11
+ display: grid;
12
+ grid-auto-flow: column;
13
+ grid-column-gap: var(--tds-spacing-2);
14
+ }
15
+
16
+ @media screen and (max-width: 800px) {
17
+ .actions {
18
+ width: 120px;
19
+ }
20
+ }
21
+
22
+ .actions a {
23
+ margin-right: 1rem;
24
+ }
25
+ .actions a:last-of-type {
26
+ margin-right: 0;
27
+ }
28
+
29
+ .actions svg {
30
+ height: 1rem;
31
+ }
32
+
33
+ .actions svg.fill {
34
+ fill: var(--tds-color-meteorite);
35
+ }
36
+
37
+ .actions svg.fill:hover {
38
+ fill: var(--tds-color-brand);
39
+ }
40
+
41
+ .actions svg.stroke {
42
+ stroke: var(--tds-color-meteorite);
43
+ fill: var(--tds-color-meteorite);
44
+ }
45
+
46
+ .actions svg.stroke:hover {
47
+ stroke: var(--tds-color-brand);
48
+ fill: var(--tds-color-brand);
49
+ }
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <div class="actions">
3
+ <a
4
+ onclick={handleClick}
5
+ href={shareTwitter}
6
+ target="_blank"
7
+ rel="noopener"
8
+ id="Share-on-Twitter"
9
+ name="Share on Twitter"
10
+ aria-label="Share on Twitter (opens new tab)"
11
+ class="fill"
12
+ ><svg
13
+ viewBox="0 0 18 14"
14
+ class="fill"
15
+ xmlns="http://www.w3.org/2000/svg"
16
+ >
17
+ <path
18
+ d="M17.308 2.325a7.333 7.333 0 01-2.041.52 3.396 3.396 0 001.565-1.836 7.374 7.374 0 01-2.258.808c-.33-.33-.73-.595-1.176-.775a3.767 3.767 0 00-1.412-.274 3.75 3.75 0 00-2.518.995A3.218 3.218 0 008.43 4.108c.002.254.033.507.094.755A10.658 10.658 0 014.46 3.847a9.912 9.912 0 01-3.256-2.471 3.128 3.128 0 00-.39 2.419c.205.823.738 1.544 1.494 2.017A3.678 3.678 0 01.72 5.39a.124.124 0 000 .053c.005.767.294 1.509.82 2.101.525.592 1.254 1 2.065 1.153-.326.071-.66.1-.995.087a4.39 4.39 0 01-.671 0 3.36 3.36 0 001.265 1.638c.594.412 1.31.645 2.052.666a7.475 7.475 0 01-4.413 1.423 8.92 8.92 0 01-.844-.08 10.571 10.571 0 005.445 1.49c6.526 0 10.096-5.057 10.096-9.446v-.434a6.933 6.933 0 001.767-1.717z"
19
+ ></path></svg>
20
+ </a>
21
+ <a
22
+ onclick={handleClick}
23
+ href={shareLinkedIn}
24
+ target="_blank"
25
+ rel="noopener"
26
+ id="Share-on-LinkedIn"
27
+ name="Share on LinkedIn"
28
+ aria-label="Share on LinkedIn (opens new tab)"
29
+ ><svg
30
+ viewBox="0 0 16 17"
31
+ class="fill"
32
+ xmlns="http://www.w3.org/2000/svg"
33
+ >
34
+ <path
35
+ d="M15.923 10.187v6.428H12.51v-5.998c0-1.506-.493-2.535-1.729-2.535-.943 0-1.504.693-1.75 1.364-.09.24-.114.573-.114.908v6.261H5.503s.046-10.158 0-11.21h3.414v1.588l-.023.037h.023v-.037c.453-.762 1.263-1.852 3.076-1.852 2.246 0 3.93 1.602 3.93 5.046zM1.932 0C.764 0 0 .837 0 1.937c0 1.076.742 1.938 1.887 1.938h.022c1.19 0 1.931-.862 1.931-1.938C3.818.837 3.1 0 1.932 0zM.202 16.615h3.413V5.405H.203v11.21z"
36
+ ></path></svg>
37
+ </a>
38
+ <a
39
+ onclick={handleClick}
40
+ href={shareFacebook}
41
+ target="_blank"
42
+ rel="noopener"
43
+ id="Share-on-Facebook"
44
+ name="Share on Facebook"
45
+ aria-label="Share on Facebook (opens new tab)"
46
+ ><svg
47
+ viewBox="0 0 9 18"
48
+ class="stroke"
49
+ xmlns="http://www.w3.org/2000/svg"
50
+ >
51
+ <path
52
+ d="M6.115 0c-2.173 0-3.66 1.5-3.66 4.23v2.363H0v3.195h2.455V18h2.926V9.788h2.45l.37-3.195H5.38V4.545c0-.922.236-1.545 1.42-1.545h1.507V.128A18.2 18.2 0 006.115 0z"
53
+ ></path></svg>
54
+ </a>
55
+ </div>
56
+ </template>
@@ -0,0 +1,29 @@
1
+ import { LightningElement, api } from 'lwc';
2
+ import { sendInteractionEvent, InteractionEventTypes } from 'shared/helpers';
3
+
4
+ export default class extends LightningElement {
5
+ @api title: string | null = '';
6
+ @api url: string = window.location.href;
7
+
8
+ private get shareTwitter() {
9
+ return `https://twitter.com/intent/tweet/?text=${encodeURIComponent(
10
+ this.title
11
+ )}&url=${encodeURIComponent(this.url)}&via=SalesforceArchs`;
12
+ }
13
+
14
+ private get shareLinkedIn() {
15
+ return `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURI(
16
+ this.url
17
+ )}`;
18
+ }
19
+
20
+ private get shareFacebook() {
21
+ return `https://www.facebook.com/sharer/sharer.php?u=${encodeURI(
22
+ this.url
23
+ )}`;
24
+ }
25
+ handleClick(e){
26
+ sendInteractionEvent('Social Share Click', InteractionEventTypes.CLICK, e, e.currentTarget);
27
+
28
+ }
29
+ }