@salesforcedevs/dx-components 1.40.4-style-fix-alpha → 1.40.4-style-fix-alpha3

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": "1.40.4-style-fix-alpha",
3
+ "version": "1.40.4-style-fix-alpha3",
4
4
  "description": "DX Lightning web components",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -40,6 +40,34 @@
40
40
  );
41
41
  }
42
42
 
43
+ /* Nested fit-content menus (content action toolbar). Flat fit-content menus,
44
+ including the version picker, keep the option rules above. */
45
+ .menu-fit-content.menu-nested {
46
+ --dx-c-popover-width: max-content;
47
+ --dx-c-popover-padding: var(--dx-g-spacing-sm) 0;
48
+ }
49
+
50
+ .menu-fit-content.menu-nested .menu_list {
51
+ width: 100%;
52
+ align-items: flex-start;
53
+ gap: var(--dx-g-spacing-sm);
54
+ }
55
+
56
+ .menu-fit-content.menu-nested .menu_option:not(:first-child) {
57
+ border-top: var(--border);
58
+ margin-top: 0;
59
+ padding-top: var(--dx-g-spacing-sm);
60
+ }
61
+
62
+ .menu-fit-content .menu_heading {
63
+ margin: 0;
64
+ padding: var(--dx-g-spacing-smd) var(--dx-g-spacing-lg);
65
+ font-family: var(--dx-g-font-sans);
66
+ line-height: var(--dx-g-spacing-mlg);
67
+ text-transform: none;
68
+ color: var(--dx-g-gray-10);
69
+ }
70
+
43
71
  @media screen and (max-width: 1000px) {
44
72
  .menu-nested .menu_option:not(:first-child) {
45
73
  border-top: var(--border);
@@ -89,7 +89,15 @@ export default class Popover extends LightningElement {
89
89
 
90
90
  this.dispatchEvent(new CustomEvent("open"));
91
91
 
92
- setTimeout(() => {
92
+ this.clearOpenListenersTimer();
93
+ // Defer so the opening click is not treated as an outside click.
94
+ // Drop the callback if the popover closes before it runs, or the
95
+ // document listeners attach with nothing left to remove them.
96
+ this.openListenersTimer = setTimeout(() => {
97
+ this.openListenersTimer = undefined;
98
+ if (!this._open) {
99
+ return;
100
+ }
93
101
  this.setPosition();
94
102
  this.attachListenersTopopover();
95
103
  });
@@ -98,6 +106,7 @@ export default class Popover extends LightningElement {
98
106
 
99
107
  @api
100
108
  closePopover(focusControl: boolean = false) {
109
+ this.clearOpenListenersTimer();
101
110
  if (!this._open) {
102
111
  return;
103
112
  }
@@ -134,6 +143,7 @@ export default class Popover extends LightningElement {
134
143
 
135
144
  private controlled: boolean = false;
136
145
  private pointerOpened: boolean = false;
146
+ private openListenersTimer?: ReturnType<typeof setTimeout>;
137
147
  private _open: boolean = false;
138
148
  private _rendered: boolean = isPrerender();
139
149
  private _role: string | null = null;
@@ -146,6 +156,7 @@ export default class Popover extends LightningElement {
146
156
  // LIFECYCLE METHODS
147
157
 
148
158
  disconnectedCallback() {
159
+ this.clearOpenListenersTimer();
149
160
  document.removeEventListener(
150
161
  "click",
151
162
  this.interactOutsideListener,
@@ -262,13 +273,42 @@ export default class Popover extends LightningElement {
262
273
 
263
274
  // Capture on document: click-open leaves focus on the trigger, and slotted
264
275
  // menu options live outside this shadow tree, so the panel's onkeydown
265
- // never sees Escape.
276
+ // never sees Escape. Only the innermost popover in the event path handles
277
+ // it, so Escape in another control does not close this menu or cancel that
278
+ // control's own Escape behavior.
266
279
  private onDocumentKeydown = (e: KeyboardEvent) => {
267
- if (e.key === "Escape" || e.key === "Esc") {
268
- this.closeOnEscape(e);
280
+ if ((e.key !== "Escape" && e.key !== "Esc") || !this.handlesEscape(e)) {
281
+ return;
269
282
  }
283
+ this.closeOnEscape(e);
270
284
  };
271
285
 
286
+ private handlesEscape(e: KeyboardEvent): boolean {
287
+ if (!this._open || e.defaultPrevented) {
288
+ return false;
289
+ }
290
+ const host = this.template.host;
291
+ const path = e.composedPath();
292
+ for (const node of path) {
293
+ if (node instanceof Element && node.localName === "dx-popover") {
294
+ return node === host;
295
+ }
296
+ }
297
+ // A non-composed event fired in this shadow or on the trigger does
298
+ // not list the host. The panel and control listeners still apply.
299
+ return (
300
+ (this.popover !== null && path.includes(this.popover)) ||
301
+ (this.control !== null && path.includes(this.control))
302
+ );
303
+ }
304
+
305
+ private clearOpenListenersTimer() {
306
+ if (this.openListenersTimer !== undefined) {
307
+ clearTimeout(this.openListenersTimer);
308
+ this.openListenersTimer = undefined;
309
+ }
310
+ }
311
+
272
312
  private closeOnEscape(e: KeyboardEvent) {
273
313
  if (!this.open) {
274
314
  return;
@@ -300,7 +340,7 @@ export default class Popover extends LightningElement {
300
340
  }
301
341
  }
302
342
 
303
- private onControlClick = () => {
343
+ private onControlClick = (e: MouseEvent) => {
304
344
  if (this.controlled && this.open) {
305
345
  this.dispatchRequestCloseEvent("click-control");
306
346
  return;
@@ -321,7 +361,10 @@ export default class Popover extends LightningElement {
321
361
  return;
322
362
  }
323
363
 
324
- this.pointerOpened = true;
364
+ // Enter/Space on a button synthesizes a click with detail 0. Only a
365
+ // pointer click should blur the trigger on Escape; keyboard activation
366
+ // must leave focus on the control.
367
+ this.pointerOpened = e.detail > 0;
325
368
  this.openPopover();
326
369
  };
327
370
 
@@ -110,6 +110,7 @@ export class TreeHandler {
110
110
  link,
111
111
  method: node.method,
112
112
  showForwardArrow: node.showForwardArrow,
113
+ redirectToChild: node.redirectToChild,
113
114
  versions: node.versions
114
115
  };
115
116
 
@@ -122,7 +123,11 @@ export class TreeHandler {
122
123
 
123
124
  this._nodeMap[convertedNode.key] = mapItem;
124
125
  this.indexName(convertedNode.name, convertedNode.key);
125
- this.indexName(convertedNode.link?.href, convertedNode.key);
126
+ // A redirect parent can carry a child's href. This index runs after
127
+ // the child, so recording it would make the parent win selection.
128
+ if (!convertedNode.redirectToChild) {
129
+ this.indexName(convertedNode.link?.href, convertedNode.key);
130
+ }
126
131
  // Index this topic's own versioned URLs only. Parent-only switching
127
132
  // URLs point at a child with content and must not steal that child's
128
133
  // selection.
@@ -236,10 +236,14 @@ export default class TreeItem extends LightningElement {
236
236
  this.sendGtm(event);
237
237
  return;
238
238
  }
239
- const isSelectAction = false;
240
- this.doExpand(isSelectAction);
241
- this.sendGtm(event);
242
- return;
239
+ // No copied destination. A markdown descendant is selected below.
240
+ // A chain with no content page only expands.
241
+ if (!firstContentNode(this._treeNode)?.link?.href) {
242
+ const isSelectAction = false;
243
+ this.doExpand(isSelectAction);
244
+ this.sendGtm(event);
245
+ return;
246
+ }
243
247
  }
244
248
 
245
249
  if (this.isParent) {
@@ -45,18 +45,21 @@ export default class TreeTile extends LightningElement {
45
45
  * screen reader users (WCAG 2.4.4)
46
46
  */
47
47
  get linkAriaLabel(): string | null {
48
- if (!this.treeNode.redirectToChild || !this.treeNode.link?.href) {
48
+ if (!this.treeNode.redirectToChild) {
49
49
  return null;
50
50
  }
51
51
  /**
52
52
  * The redirect can target a deep descendant, so walk the first-child
53
53
  * chain to the node that owns the destination link and name it.
54
+ * The destination href may live on this anchor (parent-only topics do
55
+ * not copy it onto the parent node).
54
56
  */
55
57
  let destination = this.treeNode.children?.[0];
56
58
  while (destination?.redirectToChild) {
57
59
  destination = destination.children?.[0];
58
60
  }
59
- return destination?.link?.href === this.treeNode.link.href
61
+ const triggerHref = this.href || this.treeNode.link?.href;
62
+ return destination?.link?.href && destination.link.href === triggerHref
60
63
  ? `${this.treeNode.label}, opens ${destination.label}`
61
64
  : null;
62
65
  }