@salesforcedevs/dx-components 1.3.178 → 1.3.180

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 (30) hide show
  1. package/lwc.config.json +4 -0
  2. package/package.json +2 -2
  3. package/src/modules/dx/alert/alert.css +108 -0
  4. package/src/modules/dx/alert/alert.html +36 -0
  5. package/src/modules/dx/alert/alert.ts +137 -0
  6. package/src/modules/dx/button/button.css +4 -1
  7. package/src/modules/dx/button/button.ts +18 -2
  8. package/src/modules/dx/cardStep/cardStep.css +128 -0
  9. package/src/modules/dx/cardStep/cardStep.html +31 -0
  10. package/src/modules/dx/cardStep/cardStep.ts +55 -0
  11. package/src/modules/dx/cardStep/mockProps.ts +60 -0
  12. package/src/modules/dx/checkbox/checkbox.ts +26 -2
  13. package/src/modules/dx/featuresListHeader/featuresListHeader.css +6 -1
  14. package/src/modules/dx/featuresListHeader/featuresListHeader.html +3 -1
  15. package/src/modules/dx/featuresListHeader/featuresListHeader.ts +14 -1
  16. package/src/modules/dx/input/input.css +4 -0
  17. package/src/modules/dx/input/input.html +1 -0
  18. package/src/modules/dx/input/input.ts +56 -7
  19. package/src/modules/dx/scrollManager/scrollManager.ts +1 -0
  20. package/src/modules/dx/sectionBanner/sectionBanner.css +47 -1
  21. package/src/modules/dx/sectionBanner/sectionBanner.html +19 -5
  22. package/src/modules/dx/sectionBanner/sectionBanner.ts +96 -3
  23. package/src/modules/dx/select/select.ts +28 -5
  24. package/src/modules/dx/stepSequence/stepSequence.css +0 -4
  25. package/src/modules/dx/stepSequence/stepSequence.html +2 -1
  26. package/src/modules/dx/stepSequence/stepSequence.ts +139 -34
  27. package/src/modules/dxBaseElements/lightningElementWithState/lightningElementWithState.ts +93 -0
  28. package/src/modules/dxUtils/css/css.ts +4 -1
  29. package/src/modules/dxUtils/list/list.ts +11 -0
  30. package/src/modules/dxUtils/lwc/lwc.ts +15 -0
@@ -1,16 +1,17 @@
1
1
  import { LightningElement, api } from "lwc";
2
2
  import cx from "classnames";
3
+ import { reflectBooleanAttribute } from "dxUtils/lwc";
3
4
 
4
5
  export default class Checkbox extends LightningElement {
5
6
  @api disabled: boolean = false;
6
7
  @api errorMessage?: string = "Please check this box if you want to proceed";
7
8
  @api label?: string;
8
- @api name: string | null = null;
9
- @api required: boolean = false;
10
9
  @api type: "checkbox" | "radio" = "checkbox";
11
10
  @api value!: string;
12
11
 
13
12
  private _checked: boolean = false;
13
+ private _required: boolean = false;
14
+ private _name: string | null = null;
14
15
 
15
16
  @api
16
17
  get checked(): boolean {
@@ -20,6 +21,24 @@ export default class Checkbox extends LightningElement {
20
21
  this._checked = value;
21
22
  }
22
23
 
24
+ @api
25
+ get name(): string | null {
26
+ return this._name;
27
+ }
28
+ set name(value: string | null) {
29
+ this._name = value;
30
+ this.setAttribute("name", value); // reflect to HTML
31
+ }
32
+
33
+ @api
34
+ get required(): boolean {
35
+ return this._required;
36
+ }
37
+ set required(value: boolean) {
38
+ this._required = value;
39
+ reflectBooleanAttribute(this, "required", value);
40
+ }
41
+
23
42
  private showValidity = false;
24
43
 
25
44
  private get valid(): boolean {
@@ -40,6 +59,11 @@ export default class Checkbox extends LightningElement {
40
59
  return this.valid;
41
60
  }
42
61
 
62
+ @api
63
+ checkValidity(): boolean {
64
+ return this.valid;
65
+ }
66
+
43
67
  private onChange(e: InputEvent) {
44
68
  this._checked = ((e.currentTarget as HTMLInputElement) || {}).checked;
45
69
  this.dispatchEvent(
@@ -14,7 +14,7 @@
14
14
  flex-direction: column;
15
15
  justify-content: center;
16
16
  align-items: flex-start;
17
- padding: var(--dx-g-spacing-4xl) 0;
17
+ padding: var(--dx-g-spacing-2xl) 0 var(--dx-g-spacing-3xl) 0;
18
18
  min-height: 460px;
19
19
  text-align: left;
20
20
  background-position: center;
@@ -62,6 +62,11 @@ dx-button {
62
62
  margin-top: var(--dx-g-spacing-smd);
63
63
  }
64
64
 
65
+ img.desktop {
66
+ padding-left: 20px;
67
+ max-width: 50%; /* assumes desktop images are high-res */
68
+ }
69
+
65
70
  img.mobile {
66
71
  display: none;
67
72
  }
@@ -2,7 +2,9 @@
2
2
  <div class="container" style={style}>
3
3
  <div class={textStyle}>
4
4
  <h1 class="heading dx-text-display-2">
5
- <dx-formatted-rich-text value={title}></dx-formatted-rich-text>
5
+ <dx-formatted-rich-text
6
+ value={displayTitle}
7
+ ></dx-formatted-rich-text>
6
8
  </h1>
7
9
  <span if:true={subtitle} class="dx-text-display-5">{subtitle}</span>
8
10
  <div class="features-list">
@@ -2,8 +2,10 @@ import { LightningElement, api } from "lwc";
2
2
  import cx from "classnames";
3
3
  import { track } from "dxUtils/analytics";
4
4
  import { toJson } from "dxUtils/normalizers";
5
+ import { toDxColor } from "dxUtils/css";
5
6
 
6
7
  export default class FeaturesListHeader extends LightningElement {
8
+ private _displayTitle: string | null = null;
7
9
  @api title!: string;
8
10
  @api subtitle?: string;
9
11
  @api ctaLabel!: string;
@@ -25,10 +27,21 @@ export default class FeaturesListHeader extends LightningElement {
25
27
  this._featuresList = toJson(value);
26
28
  }
27
29
 
30
+ // For use when displayed title will have embed HTML, i.e., is "rich text"
31
+ @api
32
+ get displayTitle() {
33
+ return typeof this._displayTitle === "string"
34
+ ? this._displayTitle
35
+ : this.title;
36
+ }
37
+ set displayTitle(value: string | null) {
38
+ this._displayTitle = value;
39
+ }
40
+
28
41
  private get style() {
29
42
  return cx(
30
43
  this.backgroundColor &&
31
- `background-color: var(--dx-g-${this.backgroundColor});`
44
+ `background-color: ${toDxColor(this.backgroundColor)};`
32
45
  );
33
46
  }
34
47
 
@@ -87,6 +87,10 @@ input {
87
87
  text-overflow: ellipsis;
88
88
  transition: var(--dx-g-transition-hue-1x);
89
89
  outline: none;
90
+ }
91
+
92
+ /* Show the default system disabled background if disabled; otherwise show container background */
93
+ input:not([disabled]) {
90
94
  background: none;
91
95
  }
92
96
 
@@ -22,6 +22,7 @@
22
22
  oninput={onInputChange}
23
23
  onkeydown={onKeyDown}
24
24
  placeholder={placeholder}
25
+ readonly={readOnly}
25
26
  type={type}
26
27
  value={value}
27
28
  aria-labelledby="label description"
@@ -2,7 +2,7 @@ import { LightningElement, api } from "lwc";
2
2
  import cx from "classnames";
3
3
  import { IconSize, IconSymbol } from "typings/custom";
4
4
  import { isMac } from "dxUtils/devices";
5
- import { setContainerInnerHtml } from "dxUtils/lwc";
5
+ import { reflectBooleanAttribute, setContainerInnerHtml } from "dxUtils/lwc";
6
6
  import { isValidEmail } from "./validators";
7
7
 
8
8
  type ValidatorMap = {
@@ -24,7 +24,6 @@ export default class Input extends LightningElement {
24
24
  @api ariaLabel!: string;
25
25
  @api autocomplete: string | null = null;
26
26
  @api clearable: boolean = false;
27
- @api disabled: boolean = false;
28
27
  @api iconSize: IconSize = "medium";
29
28
  @api iconSymbol: IconSymbol | null = null;
30
29
  @api label?: string;
@@ -32,9 +31,8 @@ export default class Input extends LightningElement {
32
31
  @api loading: boolean = false;
33
32
  @api missingErrorMessage: string = "Complete this field";
34
33
  @api formatErrorMessage?: string;
35
- @api name?: string;
36
34
  @api placeholder!: string;
37
- @api required = false;
35
+ @api readOnly: boolean = false;
38
36
  @api role: string | null = null;
39
37
  @api shortcutKey: string | null = null;
40
38
  @api size: "small" | "large" | "override" = "small";
@@ -50,6 +48,41 @@ export default class Input extends LightningElement {
50
48
  this._value = value || "";
51
49
  }
52
50
 
51
+ @api
52
+ get disabled() {
53
+ return this._disabled;
54
+ }
55
+ set disabled(value: boolean) {
56
+ this._disabled = value;
57
+ reflectBooleanAttribute(
58
+ this as unknown as LightningElement,
59
+ "disabled",
60
+ value
61
+ );
62
+ }
63
+
64
+ @api
65
+ get name() {
66
+ return this._name;
67
+ }
68
+ set name(name: string | undefined) {
69
+ this._name = name;
70
+ this.setAttribute("name", name); // reflect to HTML
71
+ }
72
+
73
+ @api
74
+ get required(): boolean {
75
+ return this._required;
76
+ }
77
+ set required(value: boolean) {
78
+ this._required = value;
79
+ reflectBooleanAttribute(
80
+ this as unknown as LightningElement,
81
+ "required",
82
+ value
83
+ );
84
+ }
85
+
53
86
  @api
54
87
  get errorMessage() {
55
88
  return this._errorMessage;
@@ -67,7 +100,7 @@ export default class Input extends LightningElement {
67
100
 
68
101
  @api
69
102
  reportValidity(): boolean {
70
- if (this.isRequiredAndFilled) {
103
+ if (this.isRequiredButEmpty) {
71
104
  this._errorMessage = this.missingErrorMessage;
72
105
  return false;
73
106
  }
@@ -83,10 +116,26 @@ export default class Input extends LightningElement {
83
116
  return true;
84
117
  }
85
118
 
119
+ @api checkValidity(): boolean {
120
+ if (this.isRequiredButEmpty) {
121
+ return false;
122
+ }
123
+
124
+ const typeValidator = typeValidatorMap[this.type];
125
+ if (typeValidator && !typeValidator.validator(this.value)) {
126
+ return false;
127
+ }
128
+
129
+ return true;
130
+ }
131
+
86
132
  private _errorMessage: string = "";
87
133
  private focused: boolean = false;
88
134
  private input: HTMLInputElement | null = null;
89
135
  private _value: string | null = null;
136
+ private _disabled: boolean = false;
137
+ private _name?: string;
138
+ private _required: boolean = false;
90
139
  private _loading: boolean = false;
91
140
  private isMac: boolean = false;
92
141
 
@@ -109,7 +158,7 @@ export default class Input extends LightningElement {
109
158
  private get inputInvalid() {
110
159
  const typeValidator = typeValidatorMap[this.type];
111
160
  return (
112
- this.isRequiredAndFilled &&
161
+ this.isRequiredButEmpty &&
113
162
  typeValidator &&
114
163
  !typeValidator.validator(this.value)
115
164
  );
@@ -153,7 +202,7 @@ export default class Input extends LightningElement {
153
202
  return `${this.commandKey}-${this.shortcutKey} is the search shortcut`;
154
203
  }
155
204
 
156
- private get isRequiredAndFilled() {
205
+ private get isRequiredButEmpty() {
157
206
  return this.required && (!this.value || !this.value.trim());
158
207
  }
159
208
 
@@ -166,6 +166,7 @@ export default class ScrollManager extends LightningElement {
166
166
  saveScroll = throttle(100, () => {
167
167
  window.history.replaceState(
168
168
  {
169
+ ...window.history.state,
169
170
  scroll: {
170
171
  value: document.body.scrollTop,
171
172
  docSize: document.body.scrollHeight
@@ -6,12 +6,14 @@
6
6
  --padding-top: var(--dx-g-spacing-4xl);
7
7
  --margin-top: 80px;
8
8
  --dx-c-section-banner-content-align: flex-end;
9
+ --dx-c-section-banner-image-transform: none;
9
10
  }
10
11
 
11
12
  .container {
12
13
  position: relative;
13
14
  display: flex;
14
15
  justify-content: space-between;
16
+ overflow-x: hidden;
15
17
  padding-top: var(--padding-top);
16
18
  padding-left: var(--dx-g-page-padding-horizontal);
17
19
  }
@@ -46,8 +48,23 @@
46
48
  margin-top: var(--dx-g-spacing-md);
47
49
  }
48
50
 
49
- .foot-note {
51
+ .quote-attribution {
50
52
  margin-top: var(--dx-g-spacing-md);
53
+ position: relative;
54
+ }
55
+
56
+ .quote-attribution-character {
57
+ display: inline-block;
58
+ }
59
+
60
+ .quote-attribution-text {
61
+ left: 24px;
62
+ position: absolute;
63
+ }
64
+
65
+ .quote-attribution-subtext {
66
+ left: 24px;
67
+ position: relative;
51
68
  }
52
69
 
53
70
  .image {
@@ -97,3 +114,32 @@
97
114
  margin: 0 var(--dx-g-page-padding-horizontal) var(--dx-g-spacing-3xl) 0;
98
115
  }
99
116
  }
117
+
118
+ @media screen and (max-width: 480px) {
119
+ .content-body-container {
120
+ display: block;
121
+ }
122
+
123
+ .quote-content p {
124
+ font-size: var(--dx-g-text-lg);
125
+ line-height: 28px;
126
+ }
127
+
128
+ .quote-content p.quote-attribution {
129
+ font-size: var(--dx-g-text-md);
130
+ letter-spacing: unset;
131
+ line-height: 24px;
132
+ }
133
+
134
+ .quote-attribution-text {
135
+ left: 20px;
136
+ position: absolute;
137
+ }
138
+
139
+ .quote-attribution-subtext {
140
+ font-size: 12px;
141
+ left: 20px;
142
+ line-height: 20px;
143
+ position: relative;
144
+ }
145
+ }
@@ -14,14 +14,28 @@
14
14
  <img if:true={hasQuote} src={quoteGraphicSrc} alt="" />
15
15
  <div class={quoteContentStyle}>
16
16
  <p class={contentBodyStyle}>{body}</p>
17
- <p if:true={footNote} class="foot-note dx-text-display-7">
18
- <dx-formatted-rich-text
19
- value={footNote}
20
- ></dx-formatted-rich-text>
17
+ <p
18
+ if:true={quoteAttributionText}
19
+ class={quoteAttributionStyle}
20
+ >
21
+ <span class="quote-attribution-character">&#8212;</span>
22
+ <span class="quote-attribution-text">
23
+ <dx-formatted-rich-text
24
+ value={quoteAttributionText}
25
+ ></dx-formatted-rich-text>
26
+ </span>
27
+ <span
28
+ if:true={quoteAttributionSubtext}
29
+ class={quoteAttributionSubtextStyle}
30
+ >
31
+ <dx-formatted-rich-text
32
+ value={quoteAttributionSubtext}
33
+ ></dx-formatted-rich-text>
34
+ </span>
21
35
  </p>
22
36
  </div>
23
37
  </div>
24
38
  </div>
25
- <img class="image" src={imgSrc} alt={imgAlt} />
39
+ <img class="image" src={imgSrc} alt={imgAlt} part="banner-image" />
26
40
  </div>
27
41
  </template>
@@ -1,18 +1,76 @@
1
1
  import { api, LightningElement } from "lwc";
2
2
  import cx from "classnames";
3
+ import debounce from "debounce";
4
+ import { toJson } from "dxUtils/normalizers";
3
5
  import { toDxColor } from "dxUtils/css";
4
6
 
5
7
  export default class SectionBanner extends LightningElement {
8
+ private _images?: {
9
+ desktop: {
10
+ alt: string;
11
+ src: string;
12
+ };
13
+ mobile: {
14
+ alt: string;
15
+ src: string;
16
+ };
17
+ };
18
+ private _quoteAttribution?: {
19
+ desktop: {
20
+ text: string;
21
+ subtext?: string;
22
+ };
23
+ mobile: {
24
+ text: string;
25
+ subtext?: string;
26
+ };
27
+ };
6
28
  @api title!: string;
7
29
  @api body!: string;
8
- @api footNote?: string;
9
- @api imgSrc!: string;
10
- @api imgAlt!: string;
11
30
  @api hideTopGraphic = false;
12
31
  @api backgroundColor = "indigo-vibrant-20";
13
32
  @api hasQuote = false;
14
33
  @api quoteGraphicSrc?: string;
15
34
 
35
+ private isMobile = false;
36
+ private mediaQueryList?: MediaQueryList;
37
+
38
+ @api
39
+ get images() {
40
+ return this._images;
41
+ }
42
+ set images(value) {
43
+ this._images = value && toJson(value);
44
+ }
45
+
46
+ @api
47
+ get quoteAttribution() {
48
+ return this._quoteAttribution;
49
+ }
50
+ set quoteAttribution(value) {
51
+ this._quoteAttribution = value && toJson(value);
52
+ }
53
+
54
+ get environment() {
55
+ return this.isMobile ? "mobile" : "desktop";
56
+ }
57
+
58
+ get imgSrc() {
59
+ return this.images?.[this.environment]?.src || "";
60
+ }
61
+
62
+ get imgAlt() {
63
+ return this.images?.[this.environment]?.alt || "";
64
+ }
65
+
66
+ get quoteAttributionText() {
67
+ return this.quoteAttribution?.[this.environment]?.text || "";
68
+ }
69
+
70
+ get quoteAttributionSubtext() {
71
+ return this.quoteAttribution?.[this.environment]?.subtext || "";
72
+ }
73
+
16
74
  get containerStyle() {
17
75
  return cx("container", !this.hideTopGraphic && "top-margin");
18
76
  }
@@ -21,6 +79,19 @@ export default class SectionBanner extends LightningElement {
21
79
  return cx(this.hasQuote && "quote-content");
22
80
  }
23
81
 
82
+ get quoteAttributionStyle() {
83
+ return cx("quote-attribution", {
84
+ "dx-text-display-7": !this.isMobile,
85
+ "dx-text-display-8": this.isMobile
86
+ });
87
+ }
88
+
89
+ get quoteAttributionSubtextStyle() {
90
+ return cx("quote-attribution-subtext", {
91
+ "is-mobile": this.isMobile
92
+ });
93
+ }
94
+
24
95
  get contentBodyStyle() {
25
96
  return cx(this.title ? "content-body" : "dx-text-display-6");
26
97
  }
@@ -32,4 +103,26 @@ export default class SectionBanner extends LightningElement {
32
103
 
33
104
  return `${backgroundColor}`;
34
105
  }
106
+
107
+ private handleMediaQueryChange = debounce((evt: MediaQueryListEvent) => {
108
+ this.isMobile = evt.matches;
109
+ }, 64);
110
+
111
+ connectedCallback() {
112
+ this.mediaQueryList = window.matchMedia(
113
+ "screen and (max-width: 480px)"
114
+ );
115
+ this.isMobile = this.mediaQueryList.matches;
116
+ this.mediaQueryList.addEventListener(
117
+ "change",
118
+ this.handleMediaQueryChange
119
+ );
120
+ }
121
+
122
+ disconnectedCallback() {
123
+ this.mediaQueryList?.removeEventListener(
124
+ "change",
125
+ this.handleMediaQueryChange
126
+ );
127
+ }
35
128
  }
@@ -2,6 +2,7 @@ import cx from "classnames";
2
2
  import { LightningElement, api } from "lwc";
3
3
  import { SelectOption } from "typings/custom";
4
4
  import { toJson, normalizeBoolean } from "dxUtils/normalizers";
5
+ import { reflectBooleanAttribute } from "dxUtils/lwc";
5
6
 
6
7
  export const DEFAULT_MISSING_MESSAGE = "This field is required";
7
8
 
@@ -10,7 +11,6 @@ export default class Select extends LightningElement {
10
11
  @api autocomplete?: string;
11
12
  @api label?: string;
12
13
  @api messageWhenValueMissing: string = DEFAULT_MISSING_MESSAGE;
13
- @api name?: string;
14
14
  @api placeholder?: string;
15
15
  @api size?: string;
16
16
 
@@ -39,6 +39,11 @@ export default class Select extends LightningElement {
39
39
 
40
40
  set required(value) {
41
41
  this._required = normalizeBoolean(value);
42
+ reflectBooleanAttribute(
43
+ this as unknown as LightningElement,
44
+ "required",
45
+ value
46
+ );
42
47
  }
43
48
 
44
49
  @api
@@ -51,11 +56,22 @@ export default class Select extends LightningElement {
51
56
  this.updateSelectValue();
52
57
  }
53
58
 
59
+ @api
60
+ get name() {
61
+ return this._name;
62
+ }
63
+
64
+ set name(name) {
65
+ this._name = name;
66
+ this.setAttribute("name", name); // reflect to HTML
67
+ }
68
+
54
69
  _disabled = false;
55
70
  _size = "";
56
71
  _required = false;
57
72
  _options: Array<SelectOption> = [];
58
73
  _value: string = "";
74
+ _name?: string;
59
75
  _selectElement: HTMLSelectElement | null = null;
60
76
  helpMessage?: string = "";
61
77
 
@@ -79,13 +95,16 @@ export default class Select extends LightningElement {
79
95
 
80
96
  get selectElement() {
81
97
  if (!this._selectElement) {
82
- this._selectElement = this.template.querySelector<HTMLSelectElement>(
83
- "select"
84
- );
98
+ this._selectElement =
99
+ this.template.querySelector<HTMLSelectElement>("select");
85
100
  }
86
101
  return this._selectElement;
87
102
  }
88
103
 
104
+ get isRequiredButEmpty() {
105
+ return this.required && !this.value;
106
+ }
107
+
89
108
  @api
90
109
  blur(): void {
91
110
  if (this.selectElement) {
@@ -102,13 +121,17 @@ export default class Select extends LightningElement {
102
121
 
103
122
  @api
104
123
  reportValidity(): boolean {
105
- const isInvalid = this._required && !this._value;
124
+ const isInvalid = this.isRequiredButEmpty;
106
125
  this.helpMessage = isInvalid
107
126
  ? this.messageWhenValueMissing || DEFAULT_MISSING_MESSAGE
108
127
  : "";
109
128
  return !isInvalid;
110
129
  }
111
130
 
131
+ @api checkValidity(): boolean {
132
+ return !this.isRequiredButEmpty;
133
+ }
134
+
112
135
  handleBlur(): void {
113
136
  this.reportValidity();
114
137
  this.dispatchEvent(new CustomEvent("blur"));
@@ -9,10 +9,6 @@
9
9
  width: 100%;
10
10
  }
11
11
 
12
- .hidden {
13
- visibility: hidden;
14
- }
15
-
16
12
  .no-animations ::slotted(*) {
17
13
  display: none;
18
14
  }
@@ -1,6 +1,7 @@
1
1
  <template>
2
- <div class={containerClassName}>
2
+ <div class={containerClassName} role="region" aria-live="polite">
3
3
  <slot
4
+ class={slotClassName}
4
5
  onslotchange={handleSlotChange}
5
6
  onstepincrement={handleStepIncrement}
6
7
  onstepdecrement={handleStepDecrement}