@salesforcedevs/dx-components 1.40.0-integration-alpha2 → 1.40.0-topic-alpha

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.0-integration-alpha2",
3
+ "version": "1.40.0-topic-alpha",
4
4
  "description": "DX Lightning web components",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -14,18 +14,30 @@
14
14
  display: none;
15
15
  }
16
16
 
17
- /* Opt-in fit-content menu: grows to content (capped at the popover's viewport
18
- max-width); long labels truncate instead of wrapping. */
17
+ /* Opt-in fit-content menu: hugs the labels (so it can be as narrow as the
18
+ shortest one) and stops at --dx-c-dropdown-menu-max-width. Defaults still
19
+ truncate; set --dx-c-dropdown-fit-white-space: normal to wrap instead. */
19
20
  .menu-fit-content::part(content) {
21
+ box-sizing: border-box;
20
22
  width: max-content;
23
+ max-width: var(
24
+ --dx-c-dropdown-menu-max-width,
25
+ calc(100vw - var(--dx-g-spacing-lg))
26
+ );
21
27
  }
22
28
 
23
29
  .menu-fit-content {
24
30
  --dx-c-dropdown-option-min-width: 0;
25
31
  --dx-c-dropdown-option-label-display: block;
26
- --dx-c-dropdown-option-white-space: nowrap;
27
- --dx-c-dropdown-option-overflow: hidden;
28
- --dx-c-dropdown-option-text-overflow: ellipsis;
32
+ --dx-c-dropdown-option-white-space: var(
33
+ --dx-c-dropdown-fit-white-space,
34
+ nowrap
35
+ );
36
+ --dx-c-dropdown-option-overflow: var(--dx-c-dropdown-fit-overflow, hidden);
37
+ --dx-c-dropdown-option-text-overflow: var(
38
+ --dx-c-dropdown-fit-text-overflow,
39
+ ellipsis
40
+ );
29
41
  }
30
42
 
31
43
  @media screen and (max-width: 1000px) {
@@ -8,7 +8,7 @@
8
8
  open-on-hover={openOnHover}
9
9
  open={open}
10
10
  page-padding={pagePadding}
11
- placement={placement}
11
+ placement={effectivePlacement}
12
12
  small={small}
13
13
  width={effectiveWidth}
14
14
  >
@@ -82,8 +82,14 @@ export default class Dropdown extends LightningElement {
82
82
  return this.areOptionsEmpty ? null : "menu";
83
83
  }
84
84
 
85
+ // "fit-end" is fit-content, aligned to the trigger's end edge.
86
+ // "full" matches the trigger width. "fixed" and "fit-content" are unchanged.
85
87
  private get isFitContent() {
86
- return this.widthMode === "fit-content";
88
+ return this.widthMode === "fit-content" || this.widthMode === "fit-end";
89
+ }
90
+
91
+ private get effectivePlacement(): PopperPlacement | undefined {
92
+ return this.widthMode === "fit-end" ? "bottom-end" : this.placement;
87
93
  }
88
94
 
89
95
  // fit-content takes precedence; the misuse is surfaced in className() below.
@@ -92,6 +98,9 @@ export default class Dropdown extends LightningElement {
92
98
  }
93
99
 
94
100
  private get effectiveFullWidth() {
101
+ if (this.widthMode === "full") {
102
+ return true;
103
+ }
95
104
  return this.isFitContent ? false : this.fullWidth;
96
105
  }
97
106
 
@@ -87,6 +87,7 @@
87
87
  Defaults preserve today's wrapping behavior. */
88
88
  white-space: var(--dx-c-dropdown-option-white-space, normal);
89
89
  overflow: var(--dx-c-dropdown-option-overflow, visible);
90
+ overflow-wrap: var(--dx-c-dropdown-option-overflow-wrap, normal);
90
91
  text-overflow: var(--dx-c-dropdown-option-text-overflow, clip);
91
92
  }
92
93
 
@@ -80,7 +80,11 @@ export default class Popover extends LightningElement {
80
80
  this.control.setAttribute("aria-expanded", "true");
81
81
 
82
82
  if (this.popover) {
83
- this.autoUpdateCleanup = autoUpdate(this.control, this.popover, this.setPosition);
83
+ this.autoUpdateCleanup = autoUpdate(
84
+ this.control,
85
+ this.popover,
86
+ this.setPosition
87
+ );
84
88
  }
85
89
 
86
90
  this.dispatchEvent(new CustomEvent("open"));
@@ -102,7 +106,11 @@ export default class Popover extends LightningElement {
102
106
  if (this.control) {
103
107
  this.control.setAttribute("aria-expanded", "false");
104
108
  }
105
- document.removeEventListener("click", this.interactOutsideListener);
109
+ document.removeEventListener(
110
+ "click",
111
+ this.interactOutsideListener,
112
+ true
113
+ );
106
114
  document.removeEventListener("focusout", this.interactOutsideListener);
107
115
  if (this.openOnHover) {
108
116
  document.removeEventListener(
@@ -128,7 +136,11 @@ export default class Popover extends LightningElement {
128
136
  // LIFECYCLE METHODS
129
137
 
130
138
  disconnectedCallback() {
131
- document.removeEventListener("click", this.interactOutsideListener);
139
+ document.removeEventListener(
140
+ "click",
141
+ this.interactOutsideListener,
142
+ true
143
+ );
132
144
  document.removeEventListener("mousemove", this.interactOutsideListener);
133
145
  document.removeEventListener("focusout", this.interactOutsideListener);
134
146
  this.removeControlListeners();
@@ -291,7 +303,11 @@ export default class Popover extends LightningElement {
291
303
  }
292
304
 
293
305
  private attachListenersTopopover() {
294
- document.addEventListener("click", this.interactOutsideListener);
306
+ // Capture so a click still counts as outside when a descendant
307
+ // calls stopPropagation (topic version pickers do, so the tree row
308
+ // does not navigate). Without this, opening a second picker leaves
309
+ // the first menu open.
310
+ document.addEventListener("click", this.interactOutsideListener, true);
295
311
  document.addEventListener("focusout", this.interactOutsideListener);
296
312
  if (this.openOnHover) {
297
313
  document.addEventListener(
@@ -378,31 +378,88 @@ export function getVersionPickerOptions(
378
378
  }));
379
379
  }
380
380
 
381
- export function resolveSelectedVersion(
381
+ /**
382
+ * Version whose page (or a descendant of it) is the current URL.
383
+ * Ignores the session-stored picker selection, which can remember an older
384
+ * version while the reader is still on the latest page.
385
+ */
386
+ export function versionForCurrentPage(
382
387
  node: VersionedNode | undefined
383
388
  ): TopicVersionOption | undefined {
384
389
  const versions = node?.versions;
385
- if (!node || !versions?.length) {
390
+ if (!node || !versions?.length || !isBrowser()) {
386
391
  return undefined;
387
392
  }
388
393
 
389
- const fromOwnUrl = versions.find((version) =>
390
- hrefMatchesCurrentPage(version.link?.href)
394
+ const hrefs = collectSubtreeCanonicalHrefs(node);
395
+ const matches = (version: TopicVersionOption) =>
396
+ hrefMatchesCurrentPage(version.link?.href) ||
397
+ hrefs.some((href) => hrefMatchesVersion(href, version, versions));
398
+
399
+ // Older versions first so a rewritten child URL is not mistaken for latest.
400
+ const older = versions.find(
401
+ (version) => version.id !== versions[0].id && matches(version)
391
402
  );
392
- if (fromOwnUrl) {
393
- return fromOwnUrl;
403
+ if (older) {
404
+ return older;
394
405
  }
395
406
 
396
- const descendantHrefs = collectSubtreeCanonicalHrefs(node, false);
397
- const fromDescendant = versions.find(
398
- (version) =>
399
- version.id !== versions[0].id &&
400
- descendantHrefs.some((href) =>
401
- hrefMatchesVersion(href, version, versions)
402
- )
403
- );
404
- if (fromDescendant) {
405
- return fromDescendant;
407
+ return matches(versions[0]) ? versions[0] : undefined;
408
+ }
409
+
410
+ /**
411
+ * Deepest versioned topic that contains the current page, with the version
412
+ * the URL selects. Nested versioned topics own their pages.
413
+ */
414
+ export function findTopicVersionForCurrentPage(
415
+ nodes: VersionedNode[] | undefined | null
416
+ ): { node: VersionedNode; version: TopicVersionOption } | undefined {
417
+ let match: { node: VersionedNode; version: TopicVersionOption } | undefined;
418
+ const walk = (list?: VersionedNode[] | null) => {
419
+ for (const node of list || []) {
420
+ const version = versionForCurrentPage(node);
421
+ if (version) {
422
+ match = { node, version };
423
+ }
424
+ walk(node.children);
425
+ }
426
+ };
427
+ walk(nodes);
428
+ return match;
429
+ }
430
+
431
+ /**
432
+ * Current page rewritten onto the latest topic version. Falls back to the
433
+ * latest switching URL when the path delta cannot be applied.
434
+ */
435
+ export function latestHrefForCurrentPage(
436
+ versions: TopicVersionOption[] | undefined
437
+ ): string | undefined {
438
+ const latest = versions?.[0];
439
+ if (!latest) {
440
+ return undefined;
441
+ }
442
+ if (!isBrowser()) {
443
+ return latest.link?.href;
444
+ }
445
+
446
+ const canonical = toCanonicalHref(window.location.pathname, versions);
447
+ return applyVersionToHref(canonical, latest, versions) || latest.link?.href;
448
+ }
449
+
450
+ export function resolveSelectedVersion(
451
+ node: VersionedNode | undefined
452
+ ): TopicVersionOption | undefined {
453
+ const versions = node?.versions;
454
+ if (!node || !versions?.length) {
455
+ return undefined;
456
+ }
457
+
458
+ // The open page wins, including a canonical child opened from "View Latest".
459
+ // Session is only for a picker whose topic is not the current page.
460
+ const fromUrl = versionForCurrentPage(node);
461
+ if (fromUrl) {
462
+ return fromUrl;
406
463
  }
407
464
 
408
465
  const storedId = readTopicVersionSelections()[getTopicVersionKey(node)];