@salesforcedevs/docs-components 0.0.0 → 0.0.1-superscript

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 (83) hide show
  1. package/lwc.config.json +12 -2
  2. package/package.json +29 -27
  3. package/src/modules/doc/{amfReference/utils.ts → amfModelParser/amfModelParser.ts} +10 -5
  4. package/src/modules/doc/amfReference/amfReference.css +11 -3
  5. package/src/modules/doc/amfReference/amfReference.html +35 -26
  6. package/src/modules/doc/amfReference/amfReference.ts +239 -95
  7. package/src/modules/doc/amfReference/types.ts +3 -11
  8. package/src/modules/doc/amfTopic/amfTopic.css +20 -0
  9. package/src/modules/doc/amfTopic/amfTopic.ts +59 -18
  10. package/src/modules/doc/amfTopic/types.ts +15 -13
  11. package/src/modules/doc/amfTopic/utils.ts +12 -6
  12. package/src/modules/doc/breadcrumbItem/breadcrumbItem.ts +17 -10
  13. package/src/modules/doc/breadcrumbs/breadcrumbs.html +6 -9
  14. package/src/modules/doc/breadcrumbs/breadcrumbs.ts +30 -34
  15. package/src/modules/doc/componentPlayground/componentPlayground.css +30 -0
  16. package/src/modules/doc/componentPlayground/componentPlayground.html +20 -0
  17. package/src/modules/doc/componentPlayground/componentPlayground.ts +97 -0
  18. package/src/modules/doc/content/content.css +70 -76
  19. package/src/modules/doc/content/content.html +1 -0
  20. package/src/modules/doc/content/content.ts +24 -46
  21. package/src/modules/doc/contentCallout/contentCallout.css +11 -7
  22. package/src/modules/doc/contentCallout/contentCallout.html +11 -4
  23. package/src/modules/doc/contentCallout/contentCallout.ts +8 -1
  24. package/src/modules/doc/contentLayout/contentLayout.css +1 -98
  25. package/src/modules/doc/contentLayout/contentLayout.html +46 -31
  26. package/src/modules/doc/contentLayout/contentLayout.ts +337 -89
  27. package/src/modules/doc/contentMedia/contentMedia.css +1 -1
  28. package/src/modules/doc/doDont/doDont.css +47 -0
  29. package/src/modules/doc/doDont/doDont.html +27 -0
  30. package/src/modules/doc/doDont/doDont.ts +17 -0
  31. package/src/modules/doc/header/header.css +65 -36
  32. package/src/modules/doc/header/header.html +44 -138
  33. package/src/modules/doc/header/header.ts +62 -74
  34. package/src/modules/doc/heading/heading.css +16 -37
  35. package/src/modules/doc/heading/heading.html +4 -4
  36. package/src/modules/doc/heading/heading.ts +12 -10
  37. package/src/modules/doc/headingAnchor/headingAnchor.css +2 -2
  38. package/src/modules/doc/headingAnchor/headingAnchor.ts +2 -2
  39. package/src/modules/doc/headingContent/headingContent.css +1 -1
  40. package/src/modules/doc/headingContent/headingContent.html +2 -2
  41. package/src/modules/doc/headingContent/headingContent.ts +2 -2
  42. package/src/modules/doc/lwcContentLayout/lwcContentLayout.css +9 -0
  43. package/src/modules/doc/lwcContentLayout/lwcContentLayout.html +64 -0
  44. package/src/modules/doc/lwcContentLayout/lwcContentLayout.ts +269 -0
  45. package/src/modules/doc/overview/overview.css +40 -0
  46. package/src/modules/doc/overview/overview.html +34 -0
  47. package/src/modules/doc/overview/overview.ts +12 -0
  48. package/src/modules/doc/phase/phase.css +11 -3
  49. package/src/modules/doc/phase/phase.html +12 -2
  50. package/src/modules/doc/phase/phase.ts +44 -8
  51. package/src/modules/doc/redocReference/redocReference.css +7 -0
  52. package/src/modules/doc/redocReference/redocReference.html +13 -0
  53. package/src/modules/doc/redocReference/redocReference.ts +425 -0
  54. package/src/modules/doc/specificationContent/specificationContent.css +36 -0
  55. package/src/modules/doc/specificationContent/specificationContent.html +177 -0
  56. package/src/modules/doc/specificationContent/specificationContent.ts +166 -0
  57. package/src/modules/doc/sprigSurvey/sprigSurvey.html +20 -0
  58. package/src/modules/doc/sprigSurvey/sprigSurvey.scoped.css +16 -0
  59. package/src/modules/doc/sprigSurvey/sprigSurvey.ts +16 -0
  60. package/src/modules/doc/superscriptSubscript/superscriptSubscript.html +8 -0
  61. package/src/modules/doc/superscriptSubscript/superscriptSubscript.ts +16 -0
  62. package/src/modules/doc/toc/toc.html +1 -3
  63. package/src/modules/doc/toc/toc.ts +1 -1
  64. package/src/modules/doc/toolbar/toolbar.ts +6 -6
  65. package/src/modules/doc/versionPicker/versionPicker.css +64 -0
  66. package/src/modules/doc/versionPicker/versionPicker.html +40 -0
  67. package/src/modules/doc/versionPicker/versionPicker.ts +65 -0
  68. package/src/modules/doc/xmlContent/types.ts +9 -3
  69. package/src/modules/doc/xmlContent/utils.ts +3 -1
  70. package/src/modules/doc/xmlContent/xmlContent.css +16 -4
  71. package/src/modules/doc/xmlContent/xmlContent.html +36 -21
  72. package/src/modules/doc/xmlContent/xmlContent.ts +295 -120
  73. package/src/modules/docHelpers/amfStyle/amfStyle.css +6 -8
  74. package/src/modules/docHelpers/contentLayoutStyle/contentLayoutStyle.css +162 -0
  75. package/src/modules/docHelpers/imgStyle/imgStyle.css +59 -0
  76. package/src/modules/docHelpers/status/status.css +1 -1
  77. package/src/modules/docUtils/{SearchSyncer/SearchSyncer.ts → searchSyncer/searchSyncer.ts} +1 -0
  78. package/src/modules/docUtils/utils/__mocks__/coveo.analytics.ts +16 -0
  79. package/src/modules/docUtils/utils/coveo.analytics.d.ts +10 -0
  80. package/src/modules/docUtils/utils/utils.ts +32 -0
  81. package/LICENSE +0 -12
  82. package/src/modules/docBaseElements/lightningElementWithState/lightningElementWithState.ts +0 -93
  83. package/src/modules/docHelpers/phaseContentLayout/phaseContentLayout.css +0 -39
@@ -1,4 +1,4 @@
1
- import { Json } from "typings/custom";
1
+ import { Json, DocPhaseInfo } from "typings/custom";
2
2
 
3
3
  export interface AmfTopicType {
4
4
  referenceId: string;
@@ -46,14 +46,6 @@ export interface AmfModelRecord {
46
46
  parsedModel: Json;
47
47
  }
48
48
 
49
- export type DocPhase = "pilot" | "dev-preview" | "beta";
50
-
51
- export type DocPhaseEntry = {
52
- phase: DocPhase;
53
- title: string;
54
- body: string;
55
- };
56
-
57
49
  export type ReferenceType = "markdown" | "rest-raml" | "rest-oa2" | "rest-oa3";
58
50
 
59
51
  /**
@@ -73,7 +65,7 @@ export interface ParsedMarkdownTopic {
73
65
  export interface AmfConfig {
74
66
  id: string;
75
67
  version?: string;
76
- docPhase?: DocPhaseEntry;
68
+ docPhase?: DocPhaseInfo;
77
69
  title: string;
78
70
  href: string;
79
71
  referenceType: ReferenceType;
@@ -103,7 +95,7 @@ export interface ParsedTopicModel {
103
95
  export interface TopicModel {
104
96
  id: string;
105
97
  type: string;
106
- amf: AmfModel;
98
+ amf: string;
107
99
  parser: AmfParser;
108
100
  }
109
101
 
@@ -1 +1,21 @@
1
1
  @import "docHelpers/amfStyle";
2
+
3
+ :host {
4
+ --reference-container-margin-top: var(--dx-g-spacing-sm);
5
+ --api-documentation-margin-top: var(--dx-g-spacing-3xl);
6
+ }
7
+
8
+ /**
9
+ * 1. We need to scroll to top from the tablet size as side nav bar and content in side by side from tablet size
10
+ * 2. Consider global nav height, doc header height and content margins to scroll to the right position
11
+ */
12
+
13
+ @media screen and (min-width: 769px) {
14
+ .topic-container {
15
+ scroll-margin-top: calc(
16
+ var(--dx-g-global-header-height) + var(--dx-g-doc-header-height) +
17
+ var(--reference-container-margin-top) +
18
+ var(--api-documentation-margin-top)
19
+ );
20
+ }
21
+ }
@@ -8,14 +8,27 @@ import {
8
8
  createTypeElement
9
9
  } from "./utils";
10
10
  import type { TopicModel } from "./types";
11
+ import { Json } from "typings/custom";
12
+ import { buildDocLinkClickHandler } from "dxUtils/analytics";
13
+
14
+ const TABLE_SIZE_MATCH = "769px";
11
15
 
12
16
  export default class AmfTopic extends LightningElement {
13
- private _model;
14
- private amf;
15
- private type;
17
+ private _model: TopicModel | undefined;
18
+ private amf: Json;
19
+ private type: string | undefined;
20
+ private handleLinkClick = buildDocLinkClickHandler();
21
+
22
+ disconnectedCallback(): void {
23
+ // Remove link click handler
24
+ const container = this.template.querySelector(".topic-container");
25
+ if (container) {
26
+ container.removeEventListener("click", this.handleLinkClick);
27
+ }
28
+ }
16
29
 
17
30
  @api
18
- get model(): TopicModel {
31
+ get model(): TopicModel | undefined {
19
32
  return this._model;
20
33
  }
21
34
 
@@ -24,13 +37,17 @@ export default class AmfTopic extends LightningElement {
24
37
  !this.amf ||
25
38
  (value && this._model && value.amf !== this._model?.amf)
26
39
  ) {
27
- this.amf = value && clone(value.amf);
40
+ try {
41
+ this.amf = value && JSON.parse(value.amf);
42
+ } catch (error) {
43
+ console.error(`Error parsing amf model: ${error}`);
44
+ }
28
45
  }
29
46
  if (
30
47
  !this.type ||
31
48
  (value && this._model && value.type !== this._model?.type)
32
49
  ) {
33
- this.type = value && clone(value.type);
50
+ this.type = value && value.type;
34
51
  }
35
52
 
36
53
  this._model = value;
@@ -41,7 +58,11 @@ export default class AmfTopic extends LightningElement {
41
58
  }
42
59
 
43
60
  update(): void {
44
- const container = this.template.querySelector("div.topic-container");
61
+ if (!this.model) {
62
+ throw new Error("Amf TopicModel undefined when trying to update");
63
+ }
64
+
65
+ const container = this.template.querySelector(".topic-container")!;
45
66
  const { id } = this.model;
46
67
  const type = this.type;
47
68
  const amf = this.amf;
@@ -76,19 +97,39 @@ export default class AmfTopic extends LightningElement {
76
97
  element = createDocumentationElement(amf, docsModel);
77
98
  }
78
99
 
79
- if (container.firstChild) {
100
+ if (container?.firstChild) {
80
101
  container.firstChild.remove();
81
102
  }
82
- container.appendChild(element);
103
+ container?.appendChild(element as Node);
104
+
105
+ // Add click handler for all links in the dynamically loaded content
106
+ if (container) {
107
+ if (this.isLinkTrackingHandledByAncestor()) {
108
+ container.removeEventListener("click", this.handleLinkClick);
109
+ } else {
110
+ container.addEventListener("click", this.handleLinkClick);
111
+ }
112
+ }
113
+
114
+ const isTabletOrDesktop = window.matchMedia(
115
+ `(min-width: ${TABLE_SIZE_MATCH})`
116
+ ).matches;
117
+
118
+ if (isTabletOrDesktop) {
119
+ window.scrollTo(0, 0);
120
+ }
121
+ }
122
+
123
+ renderedCallback(): void {
124
+ try {
125
+ this.update();
126
+ } catch (error) {
127
+ console.error(error);
128
+ }
83
129
  }
84
- }
85
130
 
86
- /**
87
- * The underlying web components we use from api-console mutate their models we pass in.
88
- * Since LWC makes them Read Only, we need to copy them before passing to the Web Component.
89
- * @param value JSON Serializable object to clone.
90
- * @returns A copy of Value. One that has been serialized and parsed via JSON. (Functions, Regex, etc are not preserved.)
91
- */
92
- function clone(value): object {
93
- return JSON.parse(JSON.stringify(value));
131
+ private isLinkTrackingHandledByAncestor(): boolean {
132
+ const host = this.template.host as HTMLElement | undefined;
133
+ return !!host?.closest?.("doc-content-layout");
134
+ }
94
135
  }
@@ -1,4 +1,11 @@
1
1
  import { Json } from "typings/custom";
2
+ import { AmfModelParser } from "doc/amfModelParser";
3
+ import {
4
+ DomainElement,
5
+ EndPoint,
6
+ Operation,
7
+ Shape
8
+ } from "@api-components/amf-helper-mixin";
2
9
 
3
10
  export type ApiSummaryElement = HTMLElement & {
4
11
  amf: Json;
@@ -11,44 +18,39 @@ export type ApiEndpointElement = HTMLElement & {
11
18
  inlineMethods: boolean;
12
19
  noNavigation: boolean;
13
20
  selected: string;
14
- endpoint: Json;
21
+ endpoint: EndPoint;
15
22
  noTryIt: boolean;
16
23
  };
17
24
 
18
25
  export type ApiMethodElement = HTMLElement & {
19
26
  amf: Json;
20
27
  noNavigation: boolean;
21
- endpoint: Json;
22
- method: Json;
28
+ endpoint: EndPoint | undefined;
29
+ method: Operation | undefined;
23
30
  noTryIt: boolean;
24
31
  };
25
32
 
26
33
  export type ApiSecurityElement = HTMLElement & {
27
34
  amf: Json;
28
- security: Json;
35
+ security: DomainElement | undefined;
29
36
  };
30
37
 
31
38
  export type ApiTypeElement = HTMLElement & {
32
39
  amf: Json;
33
- type: Json;
40
+ type?: Shape;
34
41
  mediaTypes: Json;
35
42
  };
36
43
 
37
44
  export type ApiDocElement = HTMLElement & {
38
45
  amf: Json;
39
- shape: Json;
46
+ shape: DomainElement | undefined;
40
47
  };
41
48
 
42
49
  export type AmfModel = Json;
43
50
 
44
- export interface AmfParser {
45
- parse(): void;
46
- parsedModel: any;
47
- }
48
-
49
51
  export interface TopicModel {
50
52
  id: string;
51
53
  type: string;
52
- amf: AmfModel;
53
- parser: AmfParser;
54
+ amf: string;
55
+ parser: AmfModelParser;
54
56
  }
@@ -1,3 +1,9 @@
1
+ import {
2
+ DomainElement,
3
+ EndPoint,
4
+ Operation,
5
+ Shape
6
+ } from "@api-components/amf-helper-mixin";
1
7
  import type {
2
8
  ApiDocElement,
3
9
  ApiEndpointElement,
@@ -37,7 +43,7 @@ export function createSummaryElement(amf: Json): HTMLElement {
37
43
  */
38
44
  export function createEndpointElement(
39
45
  amf: Json,
40
- endpointModel: Json,
46
+ endpointModel: EndPoint,
41
47
  selected: string
42
48
  ): HTMLElement {
43
49
  const el: ApiEndpointElement = document.createElement(
@@ -57,8 +63,8 @@ export function createEndpointElement(
57
63
  */
58
64
  export function createMethodElement(
59
65
  amf: Json,
60
- endpointMethodModel: Json,
61
- methodModel: Json
66
+ endpointMethodModel: EndPoint | undefined,
67
+ methodModel: Operation | undefined
62
68
  ): HTMLElement {
63
69
  const el: ApiMethodElement = document.createElement(
64
70
  "api-method-documentation"
@@ -79,7 +85,7 @@ export function createMethodElement(
79
85
  */
80
86
  export function createSecurityElement(
81
87
  amf: Json,
82
- securityModel: Json
88
+ securityModel: DomainElement | undefined
83
89
  ): HTMLElement {
84
90
  const el: ApiSecurityElement = document.createElement(
85
91
  "api-security-documentation"
@@ -98,7 +104,7 @@ export function createSecurityElement(
98
104
  */
99
105
  export function createTypeElement(
100
106
  amf: Json,
101
- typeModel: Json,
107
+ typeModel: Shape | undefined,
102
108
  mediaTypes: Json
103
109
  ): HTMLElement {
104
110
  const el: ApiTypeElement = document.createElement(
@@ -119,7 +125,7 @@ export function createTypeElement(
119
125
  */
120
126
  export function createDocumentationElement(
121
127
  amf: Json,
122
- docsModel: Json
128
+ docsModel: DomainElement | undefined
123
129
  ): HTMLElement {
124
130
  const el: ApiDocElement = document.createElement(
125
131
  "api-documentation-document"
@@ -1,6 +1,6 @@
1
1
  import { track } from "dxUtils/analytics";
2
2
  import { LightningElement, api } from "lwc";
3
- import { AnalyticsPayload, BreadcrumbItemVariant } from "typings/custom";
3
+ import { BreadcrumbItemVariant } from "typings/custom";
4
4
 
5
5
  const BREADCRUMB_LONG = "breadcrumb_long";
6
6
  const BREADCRUMB_BACK_ARROW = "breadcrumb_back-arrow";
@@ -8,8 +8,8 @@ const BREADCRUMB_BACK_ARROW = "breadcrumb_back-arrow";
8
8
  const LONG_LABEL_NUMBER = 30;
9
9
  export default class BreadcrumbItem extends LightningElement {
10
10
  @api href?: string;
11
- @api analyticsEvent!: string;
12
- @api analyticsBasePayload!: AnalyticsPayload;
11
+ @api level?: string;
12
+ @api breadcrumbLabels?: string;
13
13
 
14
14
  @api
15
15
  get label() {
@@ -51,14 +51,21 @@ export default class BreadcrumbItem extends LightningElement {
51
51
  }
52
52
 
53
53
  private onLinkClick(event: Event): void {
54
- if (!this.analyticsEvent) {
55
- return;
56
- }
54
+ track(event.target!, "custEv_breadcrumbClick", {
55
+ click_text: this.label,
56
+ click_url: `${window.location.origin}${this.href}`,
57
+ element_type: "link",
58
+ nav_type: "breadcrumb",
59
+ nav_level: this.level ? this.level + 1 : 1,
60
+ nav_item: this.breadcrumbLabels
61
+ });
57
62
 
58
- track(event.target!, this.analyticsEvent, {
59
- ...this.analyticsBasePayload,
60
- clickText: this.label,
61
- clickUrl: this.href
63
+ track(event.target!, "custEv_linkClick", {
64
+ click_text: this.label,
65
+ click_url: `${window.location.origin}${this.href}`,
66
+ element_title: this.label,
67
+ element_type: "link",
68
+ content_category: "cta"
62
69
  });
63
70
  }
64
71
  }
@@ -3,21 +3,18 @@
3
3
  <template if:true={displayCrumbs}>
4
4
  <template if:false={renderSmallVariant}>
5
5
  <doc-breadcrumb-item
6
- analytics-event={analyticsEventName}
7
- analytics-base-payload={analyticsBasePayload}
8
6
  href={firstCrumb.href}
9
7
  label={firstCrumb.label}
8
+ breadcrumb-labels={breadcrumbLabels}
10
9
  ></doc-breadcrumb-item>
11
10
  <span class="breadcrumb-item_slash">/</span>
12
11
  <template if:true={renderDropdown}>
13
12
  <dx-dropdown
13
+ analytics-event="custEv_breadcrumbClick"
14
14
  if:true={renderDropdown}
15
- analytics-event={analyticsEventName}
16
- analytics-base-payload={analyticsBasePayload}
17
15
  options={dropdownOptions}
18
16
  open-on-hover
19
17
  placement="bottom"
20
- suppress-gtm-nav-headings
21
18
  variant="indented"
22
19
  width="fit-content"
23
20
  >
@@ -32,11 +29,11 @@
32
29
  </template>
33
30
  <template for:each={breadcrumbItems} for:item="breadcrumb">
34
31
  <doc-breadcrumb-item
35
- analytics-event={analyticsEventName}
36
- analytics-base-payload={analyticsBasePayload}
37
32
  href={breadcrumb.href}
38
33
  key={breadcrumb.id}
39
34
  label={breadcrumb.label}
35
+ level={breadcrumb.level}
36
+ breadcrumb-labels={breadcrumbLabels}
40
37
  ></doc-breadcrumb-item>
41
38
  <span class="breadcrumb-item_slash" key={breadcrumb.label}>
42
39
  /
@@ -44,14 +41,14 @@
44
41
  </template>
45
42
  <doc-breadcrumb-item
46
43
  label={lastCrumb.label}
44
+ breadcrumb-labels={breadcrumbLabels}
47
45
  ></doc-breadcrumb-item>
48
46
  </template>
49
47
  <template if:true={renderSmallVariant}>
50
48
  <doc-breadcrumb-item
51
- analytics-event={analyticsEventName}
52
- analytics-base-payload={analyticsBasePayload}
53
49
  href={lastLinkCrump.href}
54
50
  label={lastLinkCrump.label}
51
+ breadcrumb-labels={breadcrumbLabels}
55
52
  variant="back-arrow"
56
53
  ></doc-breadcrumb-item>
57
54
  </template>
@@ -17,13 +17,6 @@ const CONSTANTS = {
17
17
  dropdownWidth: 32
18
18
  };
19
19
 
20
- export const ANALYTICS_EVENT_NAME = "custEv_breadcrumbClick";
21
- export const ANALYTICS_BASE_PAYLOAD = {
22
- elementType: "breadcrumb",
23
- locationOnPage: "breadcrumb",
24
- ctaClick: true
25
- };
26
-
27
20
  export default class Breadcrumbs extends LightningElement {
28
21
  @api ariaLabel: string = "Documentation Breadcrumbs";
29
22
 
@@ -35,8 +28,8 @@ export default class Breadcrumbs extends LightningElement {
35
28
  set breadcrumbs(value) {
36
29
  this.normalizeAndAssignBreadcrumbs(value);
37
30
  this.calculateBreadcrumbsConfigs();
38
- if (this.observer) {
39
- this.updateDropdownOptionAmount();
31
+ if (this.isRendered) {
32
+ this.onWidthOrContentChange();
40
33
  }
41
34
  }
42
35
 
@@ -52,9 +45,9 @@ export default class Breadcrumbs extends LightningElement {
52
45
  private _breadcrumbs: Breadcrumb[] = [];
53
46
  private _pixelPerCharacter = CONSTANTS.pixelPerCharacter;
54
47
  private navWidth = 0;
55
- private observer: ResizeObserver | null = null;
56
48
  private breadcrumbConfigs: BreadcrumbConfig[] = [];
57
49
  private dropdownOptionAmount? = 0;
50
+ private isRendered = false;
58
51
 
59
52
  private get renderSmallVariant(): boolean {
60
53
  return (
@@ -100,38 +93,38 @@ export default class Breadcrumbs extends LightningElement {
100
93
  return this.breadcrumbs[0];
101
94
  }
102
95
 
103
- private get lastCrumb(): Breadcrumb {
104
- return this.breadcrumbs[this.breadcrumbs.length - 1];
96
+ private get breadcrumbLabels(): string {
97
+ return this.breadcrumbs.map((crumb) => crumb.label).join(":");
105
98
  }
106
99
 
107
- private get analyticsEventName() {
108
- return ANALYTICS_EVENT_NAME;
100
+ private get lastCrumb(): Breadcrumb {
101
+ return this.breadcrumbs[this.breadcrumbs.length - 1];
109
102
  }
110
103
 
111
- private get analyticsBasePayload() {
104
+ // this payload is only used for breadcrumb dropdown
105
+ private get ANALYTICS_PAYLOAD() {
112
106
  return {
113
- ...ANALYTICS_BASE_PAYLOAD,
114
- itemTitle: this.breadcrumbs.map((crumb) => crumb.label).join("/")
107
+ element_type: "link",
108
+ nav_type: "breadcrumb",
109
+ nav_level: 1
115
110
  };
116
111
  }
117
112
 
113
+ private onWidthOrContentChange = () => {
114
+ this.navWidth = this.template
115
+ .querySelector("nav")!
116
+ .getBoundingClientRect().width;
117
+ this.updateDropdownOptionAmount();
118
+ };
119
+
118
120
  renderedCallback(): void {
119
- if (!this.observer) {
120
- this.observer = new ResizeObserver((entries) => {
121
- const [nav] = entries;
122
- if (this.navWidth === nav.contentRect.width) {
123
- return;
124
- }
125
- this.navWidth = nav.contentRect.width;
126
- this.updateDropdownOptionAmount();
127
- });
128
-
129
- this.observer.observe(this.template.querySelector("nav")!);
130
- }
121
+ this.isRendered = true;
122
+ this.onWidthOrContentChange();
123
+ window.addEventListener("resize", this.onWidthOrContentChange);
131
124
  }
132
125
 
133
126
  disconnectedCallback(): void {
134
- this.observer?.disconnect();
127
+ window.removeEventListener("resize", this.onWidthOrContentChange);
135
128
  }
136
129
 
137
130
  private normalizeAndAssignBreadcrumbs(breadcrumbs?: Breadcrumb[] | string) {
@@ -139,10 +132,13 @@ export default class Breadcrumbs extends LightningElement {
139
132
  return;
140
133
  }
141
134
 
142
- this._breadcrumbs = toJson(breadcrumbs).map((crumb: Breadcrumb) => ({
143
- ...crumb,
144
- id: crumb.id || crumb.href
145
- }));
135
+ this._breadcrumbs = toJson(breadcrumbs).map(
136
+ (crumb: Breadcrumb, index: number) => ({
137
+ ...crumb,
138
+ id: crumb.id || crumb.href,
139
+ level: index
140
+ })
141
+ );
146
142
  }
147
143
 
148
144
  private updateDropdownOptionAmount(): void {
@@ -0,0 +1,30 @@
1
+ @import "dxHelpers/reset";
2
+
3
+ /**
4
+ * Designs link - https://www.figma.com/design/9SalRPlJmtRDZHq03o8dL1/One-Doc-Site-Visionary-Mocks?node-id=9968-366397&m=dev
5
+ * Preview padding: 24px
6
+ * Example Selector height: 122px
7
+ * Example Preview min height: 150px
8
+ * Tab height with border: 46px(45px height and 1px border)
9
+ * Codeblock height: 264px(224px for codeblock and 40px for panel)
10
+ * Total: 606px
11
+ **/
12
+
13
+ :host {
14
+ --playground-iframe-min-height: 500px;
15
+ --playground-iframe-max-height: 5000px;
16
+
17
+ margin-top: var(--dx-g-spacing-md) !important;
18
+ display: block !important;
19
+ }
20
+
21
+ iframe {
22
+ width: 100%;
23
+ height: 606px;
24
+ min-height: var(--playground-iframe-min-height);
25
+ max-height: var(--playground-iframe-max-height);
26
+ }
27
+
28
+ .playground-container {
29
+ position: relative;
30
+ }
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <div class="playground-container" lwc:if={playgroundAvailable}>
3
+ <dx-spinner
4
+ size="large"
5
+ variant="brand"
6
+ if:true={isLoading}
7
+ ></dx-spinner>
8
+ <iframe
9
+ src={playgroundSrc}
10
+ onload={handleIframeLoad}
11
+ title={playgroundTitle}
12
+ allow="clipboard-write"
13
+ ></iframe>
14
+ </div>
15
+ <dx-error-fallback
16
+ lwc:else
17
+ title="Code Examples Unavailable"
18
+ description="This component's code examples are currently unavailable. Please check again later."
19
+ ></dx-error-fallback>
20
+ </template>
@@ -0,0 +1,97 @@
1
+ import { LightningElement, api } from "lwc";
2
+
3
+ const PLAYGROUND_HEIGHT_MESSAGE = "PLAYGROUND_HEIGHT_CHANGE";
4
+ const LOADER_TIMEOUT_MS = 2000;
5
+
6
+ export default class ComponentPlayground extends LightningElement {
7
+ @api model!: string;
8
+ @api namespace!: string;
9
+ @api component!: string;
10
+ @api playgroundAppUrl!: string;
11
+
12
+ isLoading = true;
13
+
14
+ /**
15
+ * Returns a formatted title for the component playground
16
+ * Maps 'aura' model to 'Aura' and 'lwc' model to 'Lightning'
17
+ * Capitalizes the first letter of the component name
18
+ * @returns {string} Formatted playground title
19
+ */
20
+ get playgroundTitle() {
21
+ const modelName = this.model === "aura" ? "Aura" : "Lightning";
22
+ const componentName =
23
+ this.component.charAt(0).toUpperCase() + this.component.slice(1);
24
+ return `Example previews and code for ${modelName} ${componentName} component`;
25
+ }
26
+
27
+ get playgroundAvailable() {
28
+ return (
29
+ this.playgroundAppUrl &&
30
+ this.model &&
31
+ this.namespace &&
32
+ this.component
33
+ );
34
+ }
35
+
36
+ get playgroundSrc(): string {
37
+ return `${this.playgroundAppUrl}/playground/${this.model}/${
38
+ this.namespace
39
+ }/${this.component.toLowerCase()}.html`;
40
+ }
41
+
42
+ connectedCallback() {
43
+ window.addEventListener("message", this.handleIframeMessage);
44
+ }
45
+
46
+ disconnectedCallback() {
47
+ window.removeEventListener("message", this.handleIframeMessage);
48
+ }
49
+
50
+ handleIframeLoad() {
51
+ if (!window.ResizeObserver) {
52
+ // If ResizeObserver not supported, hide loader immediately (no height messages will come)
53
+ this.isLoading = false;
54
+ } else {
55
+ // Hide loader after 2 seconds if no height messages received
56
+ // This handles cases where iframe fails to load, crashes, or height reporting fails
57
+ setTimeout(() => {
58
+ this.isLoading = false;
59
+ }, LOADER_TIMEOUT_MS);
60
+ }
61
+ }
62
+
63
+ handleIframeMessage = (event: MessageEvent) => {
64
+ // Only handle height change messages from our iframe
65
+ if (event.data?.type !== PLAYGROUND_HEIGHT_MESSAGE) {
66
+ return;
67
+ }
68
+
69
+ const iframe = this.template.querySelector(
70
+ "iframe"
71
+ ) as HTMLIFrameElement;
72
+ if (!iframe || event.source !== iframe.contentWindow) {
73
+ return;
74
+ }
75
+
76
+ const height = event.data?.height;
77
+ if (typeof height === "number" && height > 0) {
78
+ const minHeight = parseInt(
79
+ getComputedStyle(this.template.host).getPropertyValue(
80
+ "--playground-iframe-min-height"
81
+ ),
82
+ 10
83
+ );
84
+ const maxHeight = parseInt(
85
+ getComputedStyle(this.template.host).getPropertyValue(
86
+ "--playground-iframe-max-height"
87
+ ),
88
+ 10
89
+ );
90
+
91
+ const newHeight = Math.min(Math.max(height, minHeight), maxHeight);
92
+ iframe.style.height = `${newHeight}px`;
93
+ iframe.style.minHeight = `${newHeight}px`;
94
+ this.isLoading = false;
95
+ }
96
+ };
97
+ }