@salesforcedevs/dx-components 0.8.4 → 0.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/dx-components",
3
- "version": "0.8.4",
3
+ "version": "0.10.0",
4
4
  "description": "DX Lightning web components",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -29,5 +29,5 @@
29
29
  "@types/debounce": "^1.2.0",
30
30
  "@types/lodash.get": "^4.4.6"
31
31
  },
32
- "gitHead": "a181e7459fdf5411a42f3769908d24aff4675b2d"
32
+ "gitHead": "f91a2edc71e7a19cbc26ccfd6396637d3a706f56"
33
33
  }
@@ -18,7 +18,7 @@
18
18
  icon-size="large"
19
19
  icon-symbol="search"
20
20
  onchange={onInputChange}
21
- onescape={onInputEscape}
21
+ onclear={onInputEscape}
22
22
  onsubmit={onInputSubmit}
23
23
  placeholder="Search"
24
24
  size="override"
@@ -52,7 +52,7 @@
52
52
  aria-label="Record Search Term"
53
53
  icon-symbol="search"
54
54
  onchange={onInputChange}
55
- onescape={onInputEscape}
55
+ onclear={onInputEscape}
56
56
  onsubmit={onInputSubmit}
57
57
  onfocus={openPopover}
58
58
  placeholder="Search"
@@ -320,4 +320,49 @@ describe(TAG, () => {
320
320
 
321
321
  return Promise.resolve().then(() => assertErrorMissing(element));
322
322
  });
323
+
324
+ it("shows clear button if value is nonempty and clearable is true", () => {
325
+ const element = render({
326
+ value: "test",
327
+ clearable: true
328
+ });
329
+
330
+ const clearButton: HTMLButtonElement = element.shadowRoot.querySelector(
331
+ ".clear-button"
332
+ );
333
+ expect(clearButton).not.toBeNull();
334
+ });
335
+
336
+ it("doesn't show clear button if no value", () => {
337
+ const element = render({
338
+ value: "",
339
+ clearable: true
340
+ });
341
+
342
+ const clearButton: HTMLButtonElement = element.shadowRoot.querySelector(
343
+ ".clear-button"
344
+ );
345
+ expect(clearButton).toBeNull();
346
+ });
347
+
348
+ it("clears input value on click of clear button", async () => {
349
+ const element = render({
350
+ value: "test",
351
+ clearable: true
352
+ });
353
+
354
+ const input: HTMLInputElement = element.shadowRoot.querySelector(
355
+ "input"
356
+ );
357
+ expect(input.value).toEqual("test");
358
+
359
+ const clearButton: HTMLButtonElement = element.shadowRoot.querySelector(
360
+ ".clear-button"
361
+ );
362
+ clearButton.dispatchEvent(new MouseEvent("click"));
363
+
364
+ await Promise.resolve();
365
+
366
+ expect(input.value).toEqual("");
367
+ });
323
368
  });
@@ -51,7 +51,7 @@
51
51
  flex-grow: 1;
52
52
  }
53
53
 
54
- .input-container.has-shortcut {
54
+ .input-container.has-floating-item {
55
55
  padding-right: var(--dx-g-spacing-sm);
56
56
  }
57
57
 
@@ -150,3 +150,15 @@ input::placeholder {
150
150
  margin-top: var(--dx-g-spacing-2xs);
151
151
  display: block;
152
152
  }
153
+
154
+ .clear-button > dx-icon {
155
+ --dx-c-icon-color: var(--sds-g-gray-9);
156
+ }
157
+
158
+ .clear-button > dx-icon::part(svg) {
159
+ transition: var(--dx-g-transition-hue-1x);
160
+ }
161
+
162
+ .clear-button:hover > dx-icon {
163
+ --dx-c-icon-color: var(--dx-g-blue-vibrant-30);
164
+ }
@@ -25,7 +25,15 @@
25
25
  type={type}
26
26
  value={value}
27
27
  />
28
- <div class="shortcut" if:true={shortcutKey}>
28
+ <button
29
+ class="clear-button"
30
+ aria-label="Clear"
31
+ if:true={showClearButton}
32
+ onclick={clear}
33
+ >
34
+ <dx-icon symbol="clear" size="medium"></dx-icon>
35
+ </button>
36
+ <div class="shortcut" if:true={showShortcutKey}>
29
37
  <img src={shortcutImgSrc} alt={shortcutImgAlt} />
30
38
  <span>{shortcutKey}</span>
31
39
  </div>
@@ -19,8 +19,11 @@ export const Base = () => html`
19
19
  margin-bottom: 25px;
20
20
  margin-top: 25px;
21
21
  `)}
22
+
22
23
  <dx-input placeholder="Example"></dx-input>
23
24
 
25
+ <dx-input placeholder="Example Clearable" clearable="true"></dx-input>
26
+
24
27
  <dx-input placeholder="Example Large" size="large"></dx-input>
25
28
 
26
29
  <dx-input placeholder="Example with Icon" icon-symbol="search"></dx-input>
@@ -20,6 +20,7 @@ const typeValidatorMap: ValidatorMap = {
20
20
  export default class Input extends LightningElement {
21
21
  @api ariaLabel!: string;
22
22
  @api autocomplete: string | null = null;
23
+ @api clearable: boolean = false;
23
24
  @api disabled: boolean = false;
24
25
  @api iconSize: IconSize = "medium";
25
26
  @api iconSymbol: IconSymbol | null = null;
@@ -84,6 +85,14 @@ export default class Input extends LightningElement {
84
85
  private _value: string | null = null;
85
86
  private _loading: boolean = false;
86
87
 
88
+ private get showClearButton(): boolean {
89
+ return this.clearable && !!this.value;
90
+ }
91
+
92
+ private get showShortcutKey(): boolean {
93
+ return !this.showClearButton && !!this.shortcutKey;
94
+ }
95
+
87
96
  private get ariaKeyShortcuts() {
88
97
  return this.shortcutKey ? `meta+${this.shortcutKey}` : null;
89
98
  }
@@ -113,7 +122,8 @@ export default class Input extends LightningElement {
113
122
  return cx(
114
123
  "input-container",
115
124
  this.focused && "state-focused",
116
- this.shortcutKey && "has-shortcut",
125
+ (this.showClearButton || this.showShortcutKey) &&
126
+ "has-floating-item",
117
127
  !!this.errorMessage && "has-error"
118
128
  );
119
129
  }
@@ -174,45 +184,46 @@ export default class Input extends LightningElement {
174
184
  if (!e || !e.target) {
175
185
  return;
176
186
  }
177
- this._value = (<HTMLInputElement>e.target).value || "";
178
- this.dispatchChange();
187
+ this.dispatchChange((<HTMLInputElement>e.target).value || "");
179
188
  }
180
189
 
181
190
  onButtonClick(e: Event) {
182
191
  if (!this.buttonDisabled) {
183
192
  e.stopPropagation();
184
193
  }
185
- this.onSubmit();
194
+ this.submit();
186
195
  }
187
196
 
188
- dispatchChange() {
197
+ dispatchChange(value: string) {
198
+ this._value = value;
189
199
  this.dispatchEvent(new CustomEvent("change", { detail: this.value }));
190
200
  }
191
201
 
192
- dispatchSubmit() {
193
- this.dispatchEvent(new CustomEvent("submit", { detail: this.value }));
202
+ clear(e?: Event) {
203
+ if (e) {
204
+ e.stopPropagation();
205
+ }
206
+ this.dispatchChange("");
207
+ this.dispatchEvent(new CustomEvent("clear"));
208
+ if (this.input) {
209
+ this.input.blur();
210
+ }
194
211
  }
195
212
 
196
- onSubmit = async () => {
213
+ submit = async () => {
197
214
  if (this.inputInvalid) {
198
215
  return;
199
216
  }
200
- this.dispatchSubmit();
217
+ this.dispatchEvent(new CustomEvent("submit", { detail: this.value }));
201
218
  };
202
219
 
203
220
  private onKeyDown(e: KeyboardEvent) {
204
221
  switch (e.key) {
205
222
  case "Escape":
206
- this.dispatchEvent(new CustomEvent("escape"));
207
- if (this.input) {
208
- this.input.blur();
209
- this._value = "";
210
- this.dispatchChange();
211
- }
223
+ this.clear();
212
224
  break;
213
225
  case "Enter":
214
- this.onSubmit();
215
- this.dispatchEvent(new CustomEvent("enter"));
226
+ this.submit();
216
227
  break;
217
228
  default:
218
229
  }
@@ -15,7 +15,6 @@
15
15
  placeholder={inputPlaceholder}
16
16
  submit-label={inputSubmitLabel}
17
17
  onsubmit={onSubmit}
18
- onenter={onSubmit}
19
18
  size={size}
20
19
  ></dx-input>
21
20
  </div>
@@ -170,7 +170,7 @@ describe(TAG, () => {
170
170
 
171
171
  expect(track).toBeCalledTimes(1);
172
172
  expect(track).toBeCalledWith(toggleButton, GTM_EVENT_NAME, {
173
- sidebarNavAction: "collapse",
173
+ sideNavAction: "collapse",
174
174
  clickText: TOGGLE_BUTTON_LABEL
175
175
  });
176
176
 
@@ -205,7 +205,7 @@ describe(TAG, () => {
205
205
  toggleButton,
206
206
  GTM_EVENT_NAME,
207
207
  {
208
- sidebarNavAction: "expand",
208
+ sideNavAction: "expand",
209
209
  clickText: TOGGLE_BUTTON_LABEL
210
210
  }
211
211
  );
@@ -363,7 +363,7 @@ describe(TAG, () => {
363
363
 
364
364
  expect(track).toBeCalledTimes(1);
365
365
  expect(track).toBeCalledWith(toggleDiv, GTM_EVENT_NAME, {
366
- sidebarNavAction: "expand",
366
+ sideNavAction: "expand",
367
367
  clickText: TOGGLE_BUTTON_LABEL
368
368
  });
369
369
 
@@ -418,7 +418,7 @@ describe(TAG, () => {
418
418
  toggleDiv,
419
419
  GTM_EVENT_NAME,
420
420
  {
421
- sidebarNavAction: "collapse",
421
+ sideNavAction: "collapse",
422
422
  clickText: TOGGLE_BUTTON_LABEL
423
423
  }
424
424
  );
@@ -94,11 +94,11 @@ export default class Sidebar extends LightningElement {
94
94
  private onToggleClick(event: Event) {
95
95
  this.expanded = !this.expanded;
96
96
  this._toggleHovered = false;
97
- const sidebarNavAction: SidebarGtmAction = this.expanded
97
+ const sideNavAction: SidebarGtmAction = this.expanded
98
98
  ? "expand"
99
99
  : "collapse";
100
100
  track(event.currentTarget!, "custEv_leftNavLinkClick", {
101
- sidebarNavAction,
101
+ sideNavAction,
102
102
  clickText: TOGGLE_BUTTON_LABEL
103
103
  });
104
104
  }
@@ -29,18 +29,10 @@ describe(TAG, () => {
29
29
  });
30
30
 
31
31
  const link = component.shadowRoot.querySelector("a");
32
- expect(link).toBeNull();
33
-
34
- const button: HTMLElement = component.shadowRoot.querySelector(
35
- "button"
36
- );
37
- expect(button).not.toBeNull();
38
- expect(button.getAttribute("aria-label")).toBe(
39
- NODE_WITH_CHILDREN.label
40
- );
32
+ expect(link).not.toBeNull();
41
33
 
42
34
  const tile: TreeTile = component.shadowRoot.querySelector(
43
- "button > dx-tree-tile"
35
+ "a > dx-tree-tile"
44
36
  );
45
37
  expect(tile).not.toBeNull();
46
38
  assertTile(tile, {
@@ -66,7 +58,7 @@ describe(TAG, () => {
66
58
  });
67
59
 
68
60
  const tile: TreeTile = component.shadowRoot.querySelector(
69
- "button > dx-tree-tile"
61
+ "a > dx-tree-tile"
70
62
  );
71
63
  expect(tile).not.toBeNull();
72
64
  assertTile(tile, {
@@ -197,7 +189,7 @@ describe(TAG, () => {
197
189
  expect(track).toBeCalledTimes(1);
198
190
  expect(track).toBeCalledWith(tile, GTM_EVENT_NAME, {
199
191
  clickText: NODE_WITH_CHILDREN.label,
200
- sidebarNavAction: "expand"
192
+ sideNavAction: "expand"
201
193
  });
202
194
 
203
195
  return Promise.resolve().then(() => {
@@ -247,7 +239,7 @@ describe(TAG, () => {
247
239
  expect(track).toBeCalledTimes(1);
248
240
  expect(track).toBeCalledWith(tile, GTM_EVENT_NAME, {
249
241
  clickText: NODE_WITH_CHILDREN.label,
250
- sidebarNavAction: "collapse"
242
+ sideNavAction: "collapse"
251
243
  });
252
244
 
253
245
  return Promise.resolve().then(() => {
@@ -280,16 +272,14 @@ describe(TAG, () => {
280
272
  component.addEventListener("tileselected", mockTileSelected);
281
273
  component.addEventListener("expandcollapse", mockExpandCollapse);
282
274
 
283
- const button: HTMLElement = component.shadowRoot.querySelector(
284
- "button"
285
- );
286
- expect(button).not.toBeNull();
287
- button.click();
275
+ const link: HTMLElement = component.shadowRoot.querySelector("a");
276
+ expect(link).not.toBeNull();
277
+ link.click();
288
278
 
289
279
  expect(track).toBeCalledTimes(1);
290
- expect(track).toBeCalledWith(button, GTM_EVENT_NAME, {
280
+ expect(track).toBeCalledWith(link, GTM_EVENT_NAME, {
291
281
  clickText: NODE_WITH_CHILDREN.label,
292
- sidebarNavAction: "click"
282
+ sideNavAction: "click"
293
283
  });
294
284
 
295
285
  return Promise.resolve().then(() => {
@@ -328,11 +318,9 @@ describe(TAG, () => {
328
318
  );
329
319
  expect(tile).toHaveProperty("isSelected", true);
330
320
 
331
- const button: HTMLElement = component.shadowRoot.querySelector(
332
- "button"
333
- );
334
- expect(button).not.toBeNull();
335
- button.click();
321
+ const link: HTMLElement = component.shadowRoot.querySelector("a");
322
+ expect(link).not.toBeNull();
323
+ link.click();
336
324
 
337
325
  return Promise.resolve().then(() => {
338
326
  expect(tile).toHaveProperty("isSelected", true);
@@ -405,7 +393,7 @@ describe(TAG, () => {
405
393
  expect(track).toBeCalledTimes(1);
406
394
  expect(track).toBeCalledWith(link, GTM_EVENT_NAME, {
407
395
  clickText: SINGLE_NODE_WITH_LINK.label,
408
- sidebarNavAction: "click"
396
+ sideNavAction: "click"
409
397
  });
410
398
  });
411
399
  });
@@ -1,16 +1,7 @@
1
1
  <template>
2
- <button if:false={href} onclick={onButtonClick} aria-label={treeNode.label}>
3
- <dx-tree-tile
4
- is-root={isRoot}
5
- tree-node={treeNode}
6
- is-expanded={isExpanded}
7
- has-children={hasChildren}
8
- is-selected={isSelected}
9
- oniconclick={onIconClick}
10
- ></dx-tree-tile>
11
- </button>
12
- <a if:true={href} href={href} target={target} onclick={onLinkClick}>
2
+ <a href={href} target={target} onclick={onLinkClick}>
13
3
  <dx-tree-tile
4
+ href={href}
14
5
  is-root={isRoot}
15
6
  tree-node={treeNode}
16
7
  is-expanded={isExpanded}
@@ -39,7 +39,11 @@ export default class TreeItem extends LightningElement {
39
39
  }
40
40
 
41
41
  private get href(): string | undefined {
42
- return this._treeNode.link && this._treeNode.link.href;
42
+ return (
43
+ (this._treeNode.link && this._treeNode.link.href) ||
44
+ this._treeNode.name ||
45
+ ""
46
+ );
43
47
  }
44
48
 
45
49
  private get target(): string {
@@ -62,7 +66,11 @@ export default class TreeItem extends LightningElement {
62
66
  this.sendGtm(event, this.isExpanded ? "expand" : "collapse");
63
67
  }
64
68
 
65
- private onButtonClick(event: Event): void {
69
+ private onLinkClick(event: Event): void {
70
+ if (!(this._treeNode.link && this._treeNode.link.href)) {
71
+ event.preventDefault();
72
+ }
73
+
66
74
  if (this.hasChildren) {
67
75
  this.doExpand();
68
76
  }
@@ -71,10 +79,6 @@ export default class TreeItem extends LightningElement {
71
79
  this.fireEvent("tileselected", { name: this._treeNode.name });
72
80
  }
73
81
 
74
- private onLinkClick(event: Event): void {
75
- this.sendGtm(event, "click");
76
- }
77
-
78
82
  private doExpand(): void {
79
83
  this.isExpanded = !this.isExpanded;
80
84
  this.fireEvent("expandcollapse");
@@ -97,10 +101,10 @@ export default class TreeItem extends LightningElement {
97
101
  this.isExpanded = value;
98
102
  }
99
103
 
100
- private sendGtm(event: Event, sidebarNavAction: SidebarGtmAction) {
104
+ private sendGtm(event: Event, sideNavAction: SidebarGtmAction) {
101
105
  track(event.currentTarget!, "custEv_leftNavLinkClick", {
102
106
  clickText: this._treeNode.label,
103
- sidebarNavAction
107
+ sideNavAction
104
108
  });
105
109
  }
106
110
  }
@@ -23,14 +23,15 @@ const assertLevelAndLabel = (container: HTMLElement) => {
23
23
  describe(TAG, () => {
24
24
  describe("render", () => {
25
25
  it("should render as a leaf tile", () => {
26
- const component = render({ treeNode: SINGLE_NODE });
27
- const tileDiv: HTMLElement = component.shadowRoot.querySelector(
28
- "div.tile"
26
+ const component = render({ treeNode: SINGLE_NODE, href: "/test" });
27
+ const tileAnchor: HTMLElement = component.shadowRoot.querySelector(
28
+ "a.tile"
29
29
  );
30
30
 
31
- expect(tileDiv.classList).toHaveLength(1);
32
- expect(tileDiv).not.toBeNull();
33
- assertLevelAndLabel(tileDiv);
31
+ expect(tileAnchor.getAttribute("href")).toBe("/test");
32
+ expect(tileAnchor.classList).toHaveLength(1);
33
+ expect(tileAnchor).not.toBeNull();
34
+ assertLevelAndLabel(tileAnchor);
34
35
 
35
36
  const arrowIcon: Icon = component.shadowRoot.querySelector(
36
37
  "dx-icon"
@@ -51,12 +52,12 @@ describe(TAG, () => {
51
52
  isRoot: true,
52
53
  hasChildren: true
53
54
  });
54
- const tileDiv: HTMLElement = component.shadowRoot.querySelector(
55
- "div.tile.tile-root"
55
+ const tileAnchor: HTMLElement = component.shadowRoot.querySelector(
56
+ "a.tile.tile-root"
56
57
  );
57
- expect(tileDiv).not.toBeNull();
58
- expect(tileDiv.classList).toHaveLength(2);
59
- assertLevelAndLabel(tileDiv);
58
+ expect(tileAnchor).not.toBeNull();
59
+ expect(tileAnchor.classList).toHaveLength(2);
60
+ assertLevelAndLabel(tileAnchor);
60
61
 
61
62
  const arrowIcon: Icon = component.shadowRoot.querySelector(
62
63
  "dx-icon"
@@ -79,13 +80,13 @@ describe(TAG, () => {
79
80
  hasChildren: true,
80
81
  isExpanded: true
81
82
  });
82
- const tileDiv: HTMLElement = component.shadowRoot.querySelector(
83
- "div.tile.tile-with-children"
83
+ const tileAnchor: HTMLElement = component.shadowRoot.querySelector(
84
+ "a.tile.tile-with-children"
84
85
  );
85
86
 
86
- expect(tileDiv).not.toBeNull();
87
- expect(tileDiv.classList).toHaveLength(2);
88
- assertLevelAndLabel(tileDiv);
87
+ expect(tileAnchor).not.toBeNull();
88
+ expect(tileAnchor.classList).toHaveLength(2);
89
+ assertLevelAndLabel(tileAnchor);
89
90
 
90
91
  const arrowIcon: Icon = component.shadowRoot.querySelector(
91
92
  "dx-icon"
@@ -107,11 +108,11 @@ describe(TAG, () => {
107
108
  treeNode: SINGLE_NODE,
108
109
  isSelected: true
109
110
  });
110
- const tileDiv: HTMLElement = component.shadowRoot.querySelector(
111
- "div.tile.tile-selected"
111
+ const tileAnchor: HTMLElement = component.shadowRoot.querySelector(
112
+ "a.tile.tile-selected"
112
113
  );
113
- expect(tileDiv).not.toBeNull();
114
- expect(tileDiv.classList).toHaveLength(2);
114
+ expect(tileAnchor).not.toBeNull();
115
+ expect(tileAnchor.classList).toHaveLength(2);
115
116
 
116
117
  return expect(component).toBeAccessible();
117
118
  });
@@ -34,6 +34,7 @@
34
34
  font-size: var(--dx-g-text-sm);
35
35
  font-weight: var(--dx-g-font-demi);
36
36
  color: var(--dx-g-blue-vibrant-30);
37
+ display: block;
37
38
  }
38
39
 
39
40
  .tile-selected {
@@ -188,3 +189,18 @@ dx-type-badge.delete {
188
189
  --dx-c-type-badge-color: var(--dx-g-red-vibrant-40);
189
190
  --dx-c-type-badge-background: var(--dx-g-hot-orange-vibrant-90);
190
191
  }
192
+
193
+ a {
194
+ display: block;
195
+ text-decoration: none;
196
+ }
197
+
198
+ a:link,
199
+ a:visited {
200
+ color: var(--dx-g-gray-10);
201
+ }
202
+
203
+ a:visited.tile-root,
204
+ a:link.tile-root {
205
+ color: var(--dx-g-blue-vibrant-30);
206
+ }
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class={tileClass}>
2
+ <a href={href} class={tileClass}>
3
3
  <div class="flex indentation" aria-level={tileAriaLevel}>
4
4
  <span class="tile-label truncate" title={treeNode.label}>
5
5
  {treeNode.label}
@@ -20,5 +20,5 @@
20
20
  value={methodText}
21
21
  ></dx-type-badge>
22
22
  </div>
23
- </div>
23
+ </a>
24
24
  </template>
@@ -18,6 +18,7 @@ export default class TreeTile extends LightningElement {
18
18
  @api isExpanded = false;
19
19
  @api hasChildren = false;
20
20
  @api isSelected = false;
21
+ @api href!: string;
21
22
 
22
23
  get tileAriaLevel() {
23
24
  return this.treeNode.level + 2;