@salesforcedevs/dx-components 0.53.1 → 0.53.2
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 -5
- package/package.json +2 -2
- package/src/modules/dx/contentArchive/contentArchive.ts +1 -0
- package/src/modules/dx/cardGrid/cardGrid.css +0 -89
- package/src/modules/dx/cardGrid/cardGrid.html +0 -46
- package/src/modules/dx/cardGrid/cardGrid.ts +0 -140
- package/src/modules/dx/featureGrid/featureGrid.html +0 -3
- package/src/modules/dx/featureGrid/featureGrid.ts +0 -15
- package/src/modules/dx/groupBio/groupBio.css +0 -49
- package/src/modules/dx/groupBio/groupBio.html +0 -29
- package/src/modules/dx/groupBio/groupBio.ts +0 -41
- package/src/modules/dx/headTags/headTags.html +0 -3
- package/src/modules/dx/headTags/headTags.ts +0 -39
- package/src/modules/dx/interactiveImage/interactiveImage.css +0 -59
- package/src/modules/dx/interactiveImage/interactiveImage.html +0 -32
- package/src/modules/dx/interactiveImage/interactiveImage.ts +0 -71
- package/src/modules/dx/lazy/lazy.html +0 -5
- package/src/modules/dx/lazy/lazy.ts +0 -163
package/lwc.config.json
CHANGED
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"dx/emptyState",
|
|
30
30
|
"dx/feature",
|
|
31
31
|
"dx/featuredContentHeader",
|
|
32
|
-
"dx/featureGrid",
|
|
33
32
|
"dx/featuresList",
|
|
34
33
|
"dx/filterMenu",
|
|
35
34
|
"dx/banner",
|
|
@@ -37,13 +36,11 @@
|
|
|
37
36
|
"dx/newsletterForm",
|
|
38
37
|
"dx/formattedDateTime",
|
|
39
38
|
"dx/grid",
|
|
40
|
-
"dx/groupBio",
|
|
41
39
|
"dx/groupText",
|
|
42
40
|
"dx/header",
|
|
43
41
|
"dx/headerMobileNavMenu",
|
|
44
42
|
"dx/headerNav",
|
|
45
43
|
"dx/headerSearch",
|
|
46
|
-
"dx/headTags",
|
|
47
44
|
"dx/helmet",
|
|
48
45
|
"dx/hr",
|
|
49
46
|
"dx/icon",
|
|
@@ -51,8 +48,6 @@
|
|
|
51
48
|
"dx/imageAndLabel",
|
|
52
49
|
"dx/input",
|
|
53
50
|
"dx/instrumentation",
|
|
54
|
-
"dx/interactiveImage",
|
|
55
|
-
"dx/lazy",
|
|
56
51
|
"dx/logo",
|
|
57
52
|
"dx/modalDrawer",
|
|
58
53
|
"dx/pagination",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforcedevs/dx-components",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.2",
|
|
4
4
|
"description": "DX Lightning web components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -28,5 +28,5 @@
|
|
|
28
28
|
"@types/lodash.get": "^4.4.6",
|
|
29
29
|
"@types/vimeo__player": "^2.16.2"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "37bfd27f1580498841761daaeb7da20f5a539d3e"
|
|
32
32
|
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
@import "helpers/reset";
|
|
2
|
-
@import "helpers/text";
|
|
3
|
-
|
|
4
|
-
.docs-action-bar {
|
|
5
|
-
display: flex;
|
|
6
|
-
justify-content: space-between;
|
|
7
|
-
align-items: flex-end;
|
|
8
|
-
padding: var(--dx-g-spacing-md) 0 var(--dx-g-spacing-lg);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.docs-action-bar > .records-container {
|
|
12
|
-
margin-right: auto;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.docs-action-bar .filters-container {
|
|
16
|
-
display: flex;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.filter-checkbox-group,
|
|
20
|
-
.filter-button::part(container) {
|
|
21
|
-
--dx-c-checkbox-text-transform: capitalize;
|
|
22
|
-
|
|
23
|
-
text-transform: var(--dx-c-checkbox-text-transform);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.docs-action-bar .filters-container > *:not(:first-child),
|
|
27
|
-
.docs-search-input {
|
|
28
|
-
margin-left: var(--dx-g-spacing-sm);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.docs-search-input {
|
|
32
|
-
--dx-c-input-width: 285px;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.docs-card {
|
|
36
|
-
--dx-c-body-max-lines: 4;
|
|
37
|
-
--dx-c-heading-max-lines: 2;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.pagination-container {
|
|
41
|
-
max-width: fit-content;
|
|
42
|
-
max-width: -moz-fit-content;
|
|
43
|
-
margin: var(--dx-g-spacing-lg) auto;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@media screen and (max-width: 1024px) {
|
|
47
|
-
.docs-card {
|
|
48
|
-
--dx-c-body-max-lines: 3;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
@media screen and (max-width: 768px) {
|
|
53
|
-
.docs-action-bar {
|
|
54
|
-
flex-direction: column-reverse;
|
|
55
|
-
width: 100%;
|
|
56
|
-
padding: 0;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.docs-search-input {
|
|
60
|
-
--dx-c-input-width: 100%;
|
|
61
|
-
|
|
62
|
-
width: 100%;
|
|
63
|
-
margin-bottom: var(--dx-g-spacing-md);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.docs-action-bar .records-container {
|
|
67
|
-
transform: translateY(-100%);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
@media screen and (max-width: 600px) {
|
|
72
|
-
.pagination-container {
|
|
73
|
-
width: 100%;
|
|
74
|
-
overflow: hidden;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.pagination-container > * {
|
|
78
|
-
position: absolute;
|
|
79
|
-
left: 50%;
|
|
80
|
-
transform: translateX(-50%);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
@media screen and (max-width: 460px) {
|
|
85
|
-
.docs-action-bar .records-container {
|
|
86
|
-
transform: initial;
|
|
87
|
-
padding: var(--dx-g-spacing-md) 0;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="card-grid">
|
|
3
|
-
<div class="docs-action-bar">
|
|
4
|
-
<div class="records-container">
|
|
5
|
-
<span if:true={endingRecord}>
|
|
6
|
-
Showing { startingRecord } to { endingRecord } of {
|
|
7
|
-
cards.length }
|
|
8
|
-
</span>
|
|
9
|
-
</div>
|
|
10
|
-
<div class="filters-container">
|
|
11
|
-
<slot name="filters"></slot>
|
|
12
|
-
</div>
|
|
13
|
-
<dx-input
|
|
14
|
-
size="small"
|
|
15
|
-
placeholder="Search..."
|
|
16
|
-
icon-symbol="search"
|
|
17
|
-
shortcut-key="j"
|
|
18
|
-
class="docs-search-input"
|
|
19
|
-
onchange={handleSearchChange}
|
|
20
|
-
value={searchTerm}
|
|
21
|
-
></dx-input>
|
|
22
|
-
</div>
|
|
23
|
-
<dx-grid
|
|
24
|
-
columns={tileColumns}
|
|
25
|
-
if:false={showEmptyState}
|
|
26
|
-
class="card-container"
|
|
27
|
-
></dx-grid>
|
|
28
|
-
<div style="display: none">
|
|
29
|
-
<slot name="cards" onslotchange={onSlotChange}></slot>
|
|
30
|
-
</div>
|
|
31
|
-
<div class="pagination-container" if:false={showEmptyState}>
|
|
32
|
-
<dx-pagination
|
|
33
|
-
current-page={page}
|
|
34
|
-
total-pages={totalPages}
|
|
35
|
-
pages-to-show={pagesToShow}
|
|
36
|
-
onpagechange={goToPage}
|
|
37
|
-
if:false={hidePagination}
|
|
38
|
-
></dx-pagination>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
<dx-empty-state
|
|
42
|
-
title="Beep boop. That did not compute."
|
|
43
|
-
subtitle={emptyStateSubtitle}
|
|
44
|
-
if:true={showEmptyState}
|
|
45
|
-
></dx-empty-state>
|
|
46
|
-
</template>
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { LightningElement, api } from "lwc";
|
|
2
|
-
|
|
3
|
-
export default class CardGrid extends LightningElement {
|
|
4
|
-
@api pageSize = 24;
|
|
5
|
-
@api searchDebounce = 500;
|
|
6
|
-
page = 1;
|
|
7
|
-
|
|
8
|
-
private searchTerm = "";
|
|
9
|
-
private matchDesktop!: MediaQueryList;
|
|
10
|
-
private matchMobile!: MediaQueryList;
|
|
11
|
-
private matchMobileSmall!: MediaQueryList;
|
|
12
|
-
private deviceType: "desktop" | "mobile" | "mobile-small" = "desktop";
|
|
13
|
-
private searchTimeout?: number;
|
|
14
|
-
private cards: any[] = [];
|
|
15
|
-
|
|
16
|
-
private get startingRecord() {
|
|
17
|
-
return this.page === 1
|
|
18
|
-
? this.page
|
|
19
|
-
: (this.page - 1) * this.pageSize + 1;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
private get endingRecord() {
|
|
23
|
-
const defaultEndingRecord = this.pageSize * this.page;
|
|
24
|
-
return defaultEndingRecord > this.cards.length
|
|
25
|
-
? this.cards.length
|
|
26
|
-
: defaultEndingRecord;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
private get totalPages() {
|
|
30
|
-
return Math.ceil(this.cards.length / this.pageSize);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private get hidePagination() {
|
|
34
|
-
return this.totalPages < 2;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
private get emptyStateSubtitle() {
|
|
38
|
-
return (
|
|
39
|
-
"No results" +
|
|
40
|
-
(this.searchTerm !== "" ? ` for "${this.searchTerm}"` : "")
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
private get showEmptyState() {
|
|
45
|
-
return !this.cards.length;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private get tileColumns() {
|
|
49
|
-
return this.deviceType === "desktop" ? "three" : "two";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private get pagesToShow() {
|
|
53
|
-
return this.deviceType === "mobile-small" ? 3 : 5;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
connectedCallback(): void {
|
|
57
|
-
this.matchDesktop = window.matchMedia("(min-width: 1024px)");
|
|
58
|
-
this.matchDesktop.addEventListener("change", this.handleDesktopView);
|
|
59
|
-
|
|
60
|
-
this.matchMobile = window.matchMedia(
|
|
61
|
-
"(min-width: 401px) and (max-width: 1023px)"
|
|
62
|
-
);
|
|
63
|
-
this.matchMobile.addEventListener("change", this.handleMobileView);
|
|
64
|
-
|
|
65
|
-
this.matchMobileSmall = window.matchMedia("(max-width: 340px)");
|
|
66
|
-
this.matchMobileSmall.addEventListener(
|
|
67
|
-
"change",
|
|
68
|
-
this.handleMobileSmallView
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
if (this.matchMobile.matches) {
|
|
72
|
-
this.deviceType = "mobile";
|
|
73
|
-
}
|
|
74
|
-
if (this.matchMobileSmall.matches) {
|
|
75
|
-
this.deviceType = "mobile-small";
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
disconnectedCallback(): void {
|
|
80
|
-
this.matchDesktop.removeEventListener("change", this.handleDesktopView);
|
|
81
|
-
this.matchMobile.removeEventListener("change", this.handleMobileView);
|
|
82
|
-
this.matchMobileSmall.removeEventListener(
|
|
83
|
-
"change",
|
|
84
|
-
this.handleMobileSmallView
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
renderedCallback(): void {
|
|
89
|
-
this.displayCurrentPage();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
onSlotChange() {
|
|
93
|
-
const slots = Array.from(
|
|
94
|
-
this.template.querySelectorAll("slot")
|
|
95
|
-
) as HTMLSlotElement[];
|
|
96
|
-
this.cards = Array.from(slots[1].assignedElements()[0].children);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
handleDesktopView = (e: MediaQueryListEvent | MediaQueryList) => {
|
|
100
|
-
if (e.matches) {
|
|
101
|
-
this.deviceType = "desktop";
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
handleMobileView = (e: MediaQueryListEvent | MediaQueryList) => {
|
|
106
|
-
if (e.matches) {
|
|
107
|
-
this.deviceType = "mobile";
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
handleMobileSmallView = (e: MediaQueryListEvent | MediaQueryList) => {
|
|
112
|
-
if (e.matches) {
|
|
113
|
-
this.deviceType = "mobile-small";
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
handleSearchChange(event: InputEvent): void {
|
|
118
|
-
window.clearTimeout(this.searchTimeout);
|
|
119
|
-
this.searchTimeout = window.setTimeout(() => {
|
|
120
|
-
this.dispatchEvent(
|
|
121
|
-
new CustomEvent("searchchange", { detail: event.detail })
|
|
122
|
-
);
|
|
123
|
-
}, this.searchDebounce);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
goToPage(e: CustomEvent) {
|
|
127
|
-
this.page = e.detail;
|
|
128
|
-
this.displayCurrentPage();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
displayCurrentPage() {
|
|
132
|
-
const cardContainer = this.template.querySelector(".card-container");
|
|
133
|
-
if (cardContainer) {
|
|
134
|
-
cardContainer.innerHTML = "";
|
|
135
|
-
this.cards
|
|
136
|
-
.slice(this.startingRecord - 1, this.endingRecord)
|
|
137
|
-
.map((node) => cardContainer?.appendChild(node));
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { LightningElement } from "lwc";
|
|
2
|
-
import { LightningSlotElement } from "typings/custom";
|
|
3
|
-
|
|
4
|
-
export default class FeatureGrid extends LightningElement {
|
|
5
|
-
private onSlotChange(e: LightningSlotElement) {
|
|
6
|
-
// @ts-ignore
|
|
7
|
-
const slot = e.target;
|
|
8
|
-
slot.assignedElements().forEach(
|
|
9
|
-
(featureElement: any, index: number) => {
|
|
10
|
-
featureElement.descriptionPosition =
|
|
11
|
-
index % 2 === 0 ? "left" : "right";
|
|
12
|
-
}
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
@import "helpers/reset";
|
|
2
|
-
@import "helpers/text";
|
|
3
|
-
|
|
4
|
-
.container {
|
|
5
|
-
display: flex;
|
|
6
|
-
flex-direction: column;
|
|
7
|
-
align-items: center;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.avatar {
|
|
11
|
-
border-radius: 9999px;
|
|
12
|
-
margin-bottom: var(--dx-g-spacing-md);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.name {
|
|
16
|
-
margin-bottom: var(--dx-g-spacing-sm);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.avatar.size-small {
|
|
20
|
-
width: 50px;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.avatar.size-medium {
|
|
24
|
-
width: 100px;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.avatar.size-large {
|
|
28
|
-
width: 200px;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.body {
|
|
32
|
-
text-align: center;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.position {
|
|
36
|
-
color: var(--dx-g-blue-vibrant-50);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.position.condensed {
|
|
40
|
-
color: inherit;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.descriptors {
|
|
44
|
-
margin-top: var(--dx-g-spacing-sm);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.descriptors > *:not(:last-child) {
|
|
48
|
-
margin-bottom: var(--dx-g-spacing-xs);
|
|
49
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="container">
|
|
3
|
-
<div if:true={imgSrc}>
|
|
4
|
-
<img class={imgClass} src={imgSrc} alt={imgAlt} />
|
|
5
|
-
</div>
|
|
6
|
-
<div class="body">
|
|
7
|
-
<p if:true={name} class={nameClass}>{name}</p>
|
|
8
|
-
<p if:true={position} class={positionClass}>{position}</p>
|
|
9
|
-
<div
|
|
10
|
-
if:false={isCondensedVariant}
|
|
11
|
-
class="descriptors dx-text-body-2"
|
|
12
|
-
>
|
|
13
|
-
<p if:true={descriptor1}>{descriptor1}</p>
|
|
14
|
-
<p if:true={descriptor2}>{descriptor2}</p>
|
|
15
|
-
<p if:true={descriptor3}>{descriptor3}</p>
|
|
16
|
-
<p if:true={profileHref}>
|
|
17
|
-
<dx-button
|
|
18
|
-
href={profileHref}
|
|
19
|
-
target={profileTarget}
|
|
20
|
-
rel={profileRel}
|
|
21
|
-
variant="inline-inherit"
|
|
22
|
-
>
|
|
23
|
-
{profileText}
|
|
24
|
-
</dx-button>
|
|
25
|
-
</p>
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
</template>
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { LightningElement, api } from "lwc";
|
|
2
|
-
|
|
3
|
-
export default class GroupBio extends LightningElement {
|
|
4
|
-
@api imgSrc: string = "";
|
|
5
|
-
@api imgAlt: string = "";
|
|
6
|
-
@api imgSize: "small" | "medium" | "large" = "large";
|
|
7
|
-
@api variant: "condensed" | "default" = "default";
|
|
8
|
-
@api name: string = "";
|
|
9
|
-
@api position: string = "";
|
|
10
|
-
@api descriptor1: string = "";
|
|
11
|
-
@api descriptor2: string = "";
|
|
12
|
-
@api descriptor3: string = "";
|
|
13
|
-
@api profileHref: string = "";
|
|
14
|
-
@api profileText: string = "";
|
|
15
|
-
@api profileTarget: string = "_blank";
|
|
16
|
-
@api profileRel: string = "noopener";
|
|
17
|
-
|
|
18
|
-
private get imgClass(): string {
|
|
19
|
-
return `avatar size-${this.imgSize}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
private get nameClass(): string {
|
|
23
|
-
return `name ${
|
|
24
|
-
this.variant === "condensed"
|
|
25
|
-
? "condensed dx-text-label-1"
|
|
26
|
-
: "dx-text-heading-4"
|
|
27
|
-
}`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
private get positionClass(): string {
|
|
31
|
-
return `position ${
|
|
32
|
-
this.variant === "condensed"
|
|
33
|
-
? "condensed dx-text-body-3"
|
|
34
|
-
: "dx-text-heading-5"
|
|
35
|
-
}`;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
private get isCondensedVariant(): boolean {
|
|
39
|
-
return this.variant === "condensed";
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { LightningElement } from "lwc";
|
|
2
|
-
|
|
3
|
-
const syncElementAttributes = (elementA: Element, elementB: Element) => {
|
|
4
|
-
if (elementB.textContent) {
|
|
5
|
-
elementA.textContent = elementB.textContent;
|
|
6
|
-
}
|
|
7
|
-
const names = elementB.getAttributeNames();
|
|
8
|
-
names.forEach((name: string) => {
|
|
9
|
-
const val = elementB.getAttribute(name);
|
|
10
|
-
if (val) {
|
|
11
|
-
elementA.setAttribute(name, val);
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export default class HeadTags extends LightningElement {
|
|
17
|
-
connectedCallback() {
|
|
18
|
-
const innerElements = this.querySelectorAll("*");
|
|
19
|
-
if (innerElements) {
|
|
20
|
-
Array.from(innerElements).forEach((innerElement) => {
|
|
21
|
-
const elementName = innerElement.getAttribute("name");
|
|
22
|
-
let existingElement = null;
|
|
23
|
-
if (elementName) {
|
|
24
|
-
existingElement = document.head.querySelector(
|
|
25
|
-
`meta[name="${elementName}"]`
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
if (innerElement.tagName === "TITLE") {
|
|
29
|
-
existingElement = document.head.querySelector("title");
|
|
30
|
-
}
|
|
31
|
-
if (existingElement) {
|
|
32
|
-
syncElementAttributes(existingElement, innerElement);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
document.head.appendChild(innerElement);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
@import "helpers/reset";
|
|
2
|
-
@import "helpers/text";
|
|
3
|
-
|
|
4
|
-
article {
|
|
5
|
-
border: 1px solid var(--dx-g-gray-90);
|
|
6
|
-
border-radius: 0.3125rem;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
header {
|
|
10
|
-
display: flex;
|
|
11
|
-
align-items: flex-start;
|
|
12
|
-
justify-content: space-between;
|
|
13
|
-
border-radius: 0.3125rem 0.3125rem 0 0;
|
|
14
|
-
background-color: var(--dx-g-gray-95);
|
|
15
|
-
font-family: var(--dx-g-font-sans);
|
|
16
|
-
color: var(--dx-g-gray-10);
|
|
17
|
-
padding: var(--dx-g-spacing-sm);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
header span {
|
|
21
|
-
padding: var(--dx-g-spacing-sm) var(--dx-g-spacing-md);
|
|
22
|
-
font-size: var(--dx-g-text-sm);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
footer {
|
|
26
|
-
border-radius: 0 0 0.3125rem 0.3125rem;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.body {
|
|
30
|
-
margin: var(--dx-g-spacing-md);
|
|
31
|
-
overflow-x: auto;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.body img {
|
|
35
|
-
min-width: 650px;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.controls {
|
|
39
|
-
display: flex;
|
|
40
|
-
align-items: center;
|
|
41
|
-
justify-content: flex-end;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/* Breakpoints for mobile */
|
|
45
|
-
|
|
46
|
-
@media screen and (max-width: 640px) {
|
|
47
|
-
header {
|
|
48
|
-
flex-direction: column;
|
|
49
|
-
align-items: flex-end;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@media screen and (max-width: 320px) {
|
|
54
|
-
.controls,
|
|
55
|
-
.controls dx-dropdown,
|
|
56
|
-
.controls dx-button {
|
|
57
|
-
width: 100%;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<article>
|
|
3
|
-
<header>
|
|
4
|
-
<span>{title}</span>
|
|
5
|
-
<div class="controls">
|
|
6
|
-
<template if:true={isSingleDownload}>
|
|
7
|
-
<dx-button
|
|
8
|
-
icon-symbol="download"
|
|
9
|
-
href={singleDownloadHref}
|
|
10
|
-
download={singleDownloadFilename}
|
|
11
|
-
>
|
|
12
|
-
{downloadButtonLabel}
|
|
13
|
-
</dx-button>
|
|
14
|
-
</template>
|
|
15
|
-
<template if:false={isSingleDownload}>
|
|
16
|
-
<dx-dropdown
|
|
17
|
-
options={urls}
|
|
18
|
-
onchange={handleDownloadOptionChange}
|
|
19
|
-
placement="bottom-end"
|
|
20
|
-
>
|
|
21
|
-
<dx-button variant="primary" icon-symbol="chevrondown">
|
|
22
|
-
{downloadButtonLabel}
|
|
23
|
-
</dx-button>
|
|
24
|
-
</dx-dropdown>
|
|
25
|
-
</template>
|
|
26
|
-
</div>
|
|
27
|
-
</header>
|
|
28
|
-
<div class="body">
|
|
29
|
-
<img src={featuredSrc} alt={title} />
|
|
30
|
-
</div>
|
|
31
|
-
</article>
|
|
32
|
-
</template>
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { LightningElement, api } from "lwc";
|
|
2
|
-
|
|
3
|
-
// Extracts filename and extension from a file URL
|
|
4
|
-
const FILENAME_FROM_URL_REGEX = /(?=\w+\.\w{3,4}$).+/;
|
|
5
|
-
const EXTENSION_FROM_URL_REGEX = /\.([^.]*)$/;
|
|
6
|
-
|
|
7
|
-
export default class InteractiveImage extends LightningElement {
|
|
8
|
-
@api title!: string;
|
|
9
|
-
@api featuredSrc!: string;
|
|
10
|
-
|
|
11
|
-
@api
|
|
12
|
-
set downloadUrls(values: any) {
|
|
13
|
-
// Normalize input values
|
|
14
|
-
let urls;
|
|
15
|
-
if (typeof values === "string") {
|
|
16
|
-
if (values.startsWith("[")) {
|
|
17
|
-
urls = JSON.parse(values);
|
|
18
|
-
} else {
|
|
19
|
-
urls = [values];
|
|
20
|
-
}
|
|
21
|
-
} else {
|
|
22
|
-
urls = values;
|
|
23
|
-
}
|
|
24
|
-
// Ensure that we have some URLs
|
|
25
|
-
if (!urls || urls.length === 0) {
|
|
26
|
-
throw new Error(
|
|
27
|
-
`No download url found for interactive image with title "${this.title}"`
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
// Prepare download options with labels
|
|
31
|
-
this.urls = urls.map((url: string) => {
|
|
32
|
-
return {
|
|
33
|
-
label: this.getDownloadLabelFromUrl(url),
|
|
34
|
-
id: url
|
|
35
|
-
};
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
get downloadUrls() {
|
|
39
|
-
return this.urls;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
private urls: { label: string; id: string }[] = [];
|
|
43
|
-
|
|
44
|
-
private get isSingleDownload() {
|
|
45
|
-
return this.urls.length === 1;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private get singleDownloadHref() {
|
|
49
|
-
return this.urls[0].id;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private get singleDownloadFilename() {
|
|
53
|
-
const pathParts = this.urls[0].id.match(FILENAME_FROM_URL_REGEX);
|
|
54
|
-
return pathParts ? pathParts[0] : "";
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
private get downloadButtonLabel() {
|
|
58
|
-
return this.isSingleDownload ? this.urls[0].label : "Download as...";
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private handleDownloadOptionChange(event: CustomEvent) {
|
|
62
|
-
window.open(event.detail, "_blank");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private getDownloadLabelFromUrl(url: string): string {
|
|
66
|
-
const urlParts = url.match(EXTENSION_FROM_URL_REGEX);
|
|
67
|
-
return urlParts
|
|
68
|
-
? `Download as ${urlParts[1].toUpperCase()}`
|
|
69
|
-
: "Download";
|
|
70
|
-
}
|
|
71
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { LightningElement, api } from "lwc";
|
|
2
|
-
|
|
3
|
-
import { LazyMode } from "typings/custom";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Defines a fallback for BackgroundTasksAPI for browsers that doesn't support it.
|
|
7
|
-
* Currently BackgroundTasksAPI is supported by all major browsers
|
|
8
|
-
* except in Safari where it can be enabled in Experimental (WebKit) Features.
|
|
9
|
-
* See also: https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API
|
|
10
|
-
*/
|
|
11
|
-
function defineBackgroundTasksAPI() {
|
|
12
|
-
// Fallback to using setTimeout
|
|
13
|
-
window.requestIdleCallback =
|
|
14
|
-
window.requestIdleCallback ||
|
|
15
|
-
function (handler: any) {
|
|
16
|
-
const startTime = Date.now();
|
|
17
|
-
|
|
18
|
-
return setTimeout(function () {
|
|
19
|
-
handler({
|
|
20
|
-
didTimeout: false,
|
|
21
|
-
timeRemaining: function () {
|
|
22
|
-
return Math.max(0, 50.0 - (Date.now() - startTime));
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}, 1);
|
|
26
|
-
};
|
|
27
|
-
window.cancelIdleCallback =
|
|
28
|
-
window.cancelIdleCallback ||
|
|
29
|
-
function (id: any) {
|
|
30
|
-
clearTimeout(id);
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default class Lazy extends LightningElement {
|
|
35
|
-
@api mode: LazyMode = "idle";
|
|
36
|
-
|
|
37
|
-
// intersection mode api
|
|
38
|
-
@api rootId: string | undefined;
|
|
39
|
-
@api rootMargin: string = "0px";
|
|
40
|
-
@api threshold: number = 0;
|
|
41
|
-
|
|
42
|
-
@api
|
|
43
|
-
get placeholderSize() {
|
|
44
|
-
return this._placeholderSize;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
set placeholderSize(value) {
|
|
48
|
-
this._placeholderSize = value;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 'rendered' marks the first call made to renderedCallback()
|
|
53
|
-
*/
|
|
54
|
-
private rendered: boolean = false;
|
|
55
|
-
/**
|
|
56
|
-
* 'renderSlot' controls whether the dx-lazy slot contents get rendered
|
|
57
|
-
*/
|
|
58
|
-
private renderSlot: boolean = false;
|
|
59
|
-
/**
|
|
60
|
-
* 'connectedCallbackCalled' marks the first call made to connectedCallback()
|
|
61
|
-
*/
|
|
62
|
-
private connectedCallbackCalled: boolean = false;
|
|
63
|
-
/**
|
|
64
|
-
* The handle as returned by calling window.requestIdleCallback()
|
|
65
|
-
*/
|
|
66
|
-
private idleCallbackHandle: any;
|
|
67
|
-
/**
|
|
68
|
-
* The Intersection Observer if instantiated, else undefined
|
|
69
|
-
*/
|
|
70
|
-
private observer: IntersectionObserver | undefined;
|
|
71
|
-
/**
|
|
72
|
-
* The size of the placeholder for the lazy container in pixels
|
|
73
|
-
*/
|
|
74
|
-
private _placeholderSize: number = 200;
|
|
75
|
-
|
|
76
|
-
constructor() {
|
|
77
|
-
super();
|
|
78
|
-
this.updateHostStyle();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
connectedCallback() {
|
|
82
|
-
if (!this.connectedCallbackCalled) {
|
|
83
|
-
this.connectedCallbackCalled = true;
|
|
84
|
-
if (this.mode === "idle") {
|
|
85
|
-
defineBackgroundTasksAPI();
|
|
86
|
-
this.waitForIdle();
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
renderedCallback() {
|
|
92
|
-
if (!this.rendered) {
|
|
93
|
-
this.rendered = true;
|
|
94
|
-
if (this.mode === "intersection") {
|
|
95
|
-
this.observeIntersection();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
disconnectedCallback() {
|
|
101
|
-
if (this.observer) {
|
|
102
|
-
this.observer.disconnect();
|
|
103
|
-
}
|
|
104
|
-
if (this.idleCallbackHandle) {
|
|
105
|
-
window.cancelIdleCallback(this.idleCallbackHandle);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private waitForIdle() {
|
|
110
|
-
this.idleCallbackHandle = window.requestIdleCallback(
|
|
111
|
-
this.idleCallback.bind(this),
|
|
112
|
-
{ timeout: 1000 }
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private idleCallback() {
|
|
117
|
-
this.doRender();
|
|
118
|
-
window.cancelIdleCallback(this.idleCallbackHandle);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private observeIntersection() {
|
|
122
|
-
let root = null; // root with a value of null means to use the viewport
|
|
123
|
-
if (this.rootId && typeof this.rootId === "string") {
|
|
124
|
-
root = document.querySelector(`#${this.rootId}`);
|
|
125
|
-
}
|
|
126
|
-
this.observer = new IntersectionObserver(
|
|
127
|
-
this.intersectionCallback.bind(this),
|
|
128
|
-
{
|
|
129
|
-
root,
|
|
130
|
-
rootMargin: this.rootMargin,
|
|
131
|
-
threshold: this.threshold
|
|
132
|
-
}
|
|
133
|
-
);
|
|
134
|
-
this.observer.observe(this.template.host);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
private intersectionCallback(
|
|
138
|
-
entries: IntersectionObserverEntry[],
|
|
139
|
-
observer: IntersectionObserver
|
|
140
|
-
) {
|
|
141
|
-
entries.forEach((entry: IntersectionObserverEntry) => {
|
|
142
|
-
if (entry.isIntersecting) {
|
|
143
|
-
this.doRender();
|
|
144
|
-
observer.disconnect();
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private updateHostStyle() {
|
|
150
|
-
if (this.renderSlot) {
|
|
151
|
-
this.template.host.style.display = "";
|
|
152
|
-
this.template.host.style.minHeight = "";
|
|
153
|
-
} else {
|
|
154
|
-
this.template.host.style.display = "block";
|
|
155
|
-
this.template.host.style.minHeight = `${this._placeholderSize}px`;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private doRender() {
|
|
160
|
-
this.renderSlot = true;
|
|
161
|
-
this.updateHostStyle();
|
|
162
|
-
}
|
|
163
|
-
}
|