@amplifyup/sdk 0.1.17 → 0.1.19
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/dist/AmplifyRenderer.d.mts +1 -1
- package/dist/AmplifyRenderer.d.ts +1 -1
- package/dist/AmplifyRenderer.js +278 -18
- package/dist/AmplifyRenderer.js.map +1 -1
- package/dist/AmplifyRenderer.mjs +278 -18
- package/dist/AmplifyRenderer.mjs.map +1 -1
- package/dist/index.d.mts +25 -4
- package/dist/index.d.ts +25 -4
- package/dist/index.js +228 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +228 -19
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +278 -18
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +278 -18
- package/dist/react.mjs.map +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/{types-B2Swk8BW.d.mts → types-Cy5aWQ5c.d.mts} +4 -0
- package/dist/{types-B2Swk8BW.d.ts → types-Cy5aWQ5c.d.ts} +4 -0
- package/package.json +1 -1
package/dist/AmplifyRenderer.mjs
CHANGED
|
@@ -366,6 +366,9 @@ var AmplifyUpSDK = class {
|
|
|
366
366
|
this.attachedEventListeners = [];
|
|
367
367
|
this.trackingIntervals = [];
|
|
368
368
|
this.trackingCleanup = null;
|
|
369
|
+
this.componentViewObservers = [];
|
|
370
|
+
this.componentViewSeen = /* @__PURE__ */ new Set();
|
|
371
|
+
this.pageEngagementActive = false;
|
|
369
372
|
this.adapter = adapter || new AWSAdapter();
|
|
370
373
|
if (typeof window !== "undefined") {
|
|
371
374
|
this.isVisualEditorMode = this.detectVisualEditorMode();
|
|
@@ -538,6 +541,7 @@ var AmplifyUpSDK = class {
|
|
|
538
541
|
this.initialPageSent = true;
|
|
539
542
|
const url = window.location.pathname;
|
|
540
543
|
this.page(url);
|
|
544
|
+
this.setupPageEngagement();
|
|
541
545
|
}
|
|
542
546
|
}
|
|
543
547
|
if (this.config?.debug && this.initialized) {
|
|
@@ -570,6 +574,168 @@ var AmplifyUpSDK = class {
|
|
|
570
574
|
}
|
|
571
575
|
this.trackingIntervals.forEach((id) => clearInterval(id));
|
|
572
576
|
this.trackingIntervals = [];
|
|
577
|
+
this.cleanupComponentViewTracking();
|
|
578
|
+
this.pageEngagementActive = false;
|
|
579
|
+
}
|
|
580
|
+
/** Current page path for engagement attribution. */
|
|
581
|
+
currentPath() {
|
|
582
|
+
if (typeof window === "undefined") return "/";
|
|
583
|
+
return window.location.pathname || "/";
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Automatic page-level Scroll Depth + Time on Page (dwell heartbeat).
|
|
587
|
+
*/
|
|
588
|
+
setupPageEngagement() {
|
|
589
|
+
if (typeof window === "undefined" || typeof document === "undefined") return;
|
|
590
|
+
if (this.isPreviewMode() || this.pageEngagementActive) return;
|
|
591
|
+
this.pageEngagementActive = true;
|
|
592
|
+
const milestones = [25, 50, 75, 100];
|
|
593
|
+
const reached = /* @__PURE__ */ new Set();
|
|
594
|
+
let visibleMs = 0;
|
|
595
|
+
let visibleSince = document.visibilityState === "visible" ? Date.now() : null;
|
|
596
|
+
let lastDwellSecondsSent = 0;
|
|
597
|
+
let scrollRaf = null;
|
|
598
|
+
const path = () => this.currentPath();
|
|
599
|
+
const measureScrollPercent = () => {
|
|
600
|
+
const doc = document.documentElement;
|
|
601
|
+
const body = document.body;
|
|
602
|
+
const scrollTop = window.scrollY || doc.scrollTop || body?.scrollTop || 0;
|
|
603
|
+
const scrollHeight = Math.max(
|
|
604
|
+
doc.scrollHeight,
|
|
605
|
+
body?.scrollHeight || 0,
|
|
606
|
+
doc.offsetHeight,
|
|
607
|
+
body?.offsetHeight || 0
|
|
608
|
+
);
|
|
609
|
+
const clientHeight = window.innerHeight || doc.clientHeight || 0;
|
|
610
|
+
const traversable = Math.max(scrollHeight - clientHeight, 0);
|
|
611
|
+
if (traversable <= 0) return 100;
|
|
612
|
+
return Math.min(100, Math.round(scrollTop / traversable * 100));
|
|
613
|
+
};
|
|
614
|
+
const checkScroll = () => {
|
|
615
|
+
if (this.isPreviewMode()) return;
|
|
616
|
+
const pct = measureScrollPercent();
|
|
617
|
+
for (const milestone of milestones) {
|
|
618
|
+
if (pct >= milestone && !reached.has(milestone)) {
|
|
619
|
+
reached.add(milestone);
|
|
620
|
+
this.track("Scroll Depth", {
|
|
621
|
+
percentage: milestone,
|
|
622
|
+
path: path()
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
const onScroll = () => {
|
|
628
|
+
if (scrollRaf != null) return;
|
|
629
|
+
scrollRaf = window.requestAnimationFrame(() => {
|
|
630
|
+
scrollRaf = null;
|
|
631
|
+
checkScroll();
|
|
632
|
+
});
|
|
633
|
+
};
|
|
634
|
+
const accumulateVisible = () => {
|
|
635
|
+
if (visibleSince == null) return;
|
|
636
|
+
visibleMs += Date.now() - visibleSince;
|
|
637
|
+
visibleSince = Date.now();
|
|
638
|
+
};
|
|
639
|
+
const emitDwell = (force = false) => {
|
|
640
|
+
if (this.isPreviewMode()) return;
|
|
641
|
+
accumulateVisible();
|
|
642
|
+
const seconds = Math.floor(visibleMs / 1e3);
|
|
643
|
+
if (seconds <= 0) return;
|
|
644
|
+
if (!force && seconds <= lastDwellSecondsSent) return;
|
|
645
|
+
lastDwellSecondsSent = seconds;
|
|
646
|
+
this.track("Time on Page", {
|
|
647
|
+
seconds,
|
|
648
|
+
path: path()
|
|
649
|
+
});
|
|
650
|
+
};
|
|
651
|
+
const onVisibility = () => {
|
|
652
|
+
if (document.visibilityState === "visible") {
|
|
653
|
+
visibleSince = Date.now();
|
|
654
|
+
} else {
|
|
655
|
+
emitDwell(true);
|
|
656
|
+
visibleSince = null;
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
const onPageHide = () => {
|
|
660
|
+
emitDwell(true);
|
|
661
|
+
};
|
|
662
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
663
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
664
|
+
window.addEventListener("pagehide", onPageHide);
|
|
665
|
+
checkScroll();
|
|
666
|
+
const heartbeat = setInterval(() => {
|
|
667
|
+
if (document.visibilityState === "visible") {
|
|
668
|
+
emitDwell(false);
|
|
669
|
+
}
|
|
670
|
+
}, 15e3);
|
|
671
|
+
this.trackingIntervals.push(heartbeat);
|
|
672
|
+
const previousCleanup = this.trackingCleanup;
|
|
673
|
+
this.trackingCleanup = () => {
|
|
674
|
+
window.removeEventListener("scroll", onScroll);
|
|
675
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
676
|
+
window.removeEventListener("pagehide", onPageHide);
|
|
677
|
+
if (scrollRaf != null) {
|
|
678
|
+
window.cancelAnimationFrame(scrollRaf);
|
|
679
|
+
scrollRaf = null;
|
|
680
|
+
}
|
|
681
|
+
previousCleanup?.();
|
|
682
|
+
this.pageEngagementActive = false;
|
|
683
|
+
};
|
|
684
|
+
if (this.config?.debug) {
|
|
685
|
+
console.log("[AmplifyUp SDK] Page engagement tracking enabled");
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Automatic Component View when component hosts enter the viewport.
|
|
690
|
+
*/
|
|
691
|
+
setupComponentViewTracking() {
|
|
692
|
+
if (typeof document === "undefined" || typeof IntersectionObserver === "undefined") {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (this.isPreviewMode()) return;
|
|
696
|
+
this.cleanupComponentViewTracking();
|
|
697
|
+
const hosts = document.querySelectorAll("[data-amplifyup-component]");
|
|
698
|
+
if (hosts.length === 0) {
|
|
699
|
+
if (this.config?.debug) {
|
|
700
|
+
console.log("[AmplifyUp SDK] No component hosts found for Component View");
|
|
701
|
+
}
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const observer = new IntersectionObserver(
|
|
705
|
+
(entries) => {
|
|
706
|
+
entries.forEach((entry) => {
|
|
707
|
+
if (!entry.isIntersecting) return;
|
|
708
|
+
const el = entry.target;
|
|
709
|
+
const componentId = el.getAttribute("data-amplifyup-component") || el.getAttribute("data-component-id");
|
|
710
|
+
if (!componentId || this.componentViewSeen.has(componentId)) {
|
|
711
|
+
observer.unobserve(el);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
this.componentViewSeen.add(componentId);
|
|
715
|
+
const componentName = el.getAttribute("data-component-name") || void 0;
|
|
716
|
+
this.track("Component View", {
|
|
717
|
+
componentId,
|
|
718
|
+
componentName,
|
|
719
|
+
path: this.currentPath()
|
|
720
|
+
});
|
|
721
|
+
observer.unobserve(el);
|
|
722
|
+
});
|
|
723
|
+
},
|
|
724
|
+
{ threshold: 0.5 }
|
|
725
|
+
);
|
|
726
|
+
hosts.forEach((host) => observer.observe(host));
|
|
727
|
+
this.componentViewObservers.push(observer);
|
|
728
|
+
if (this.config?.debug) {
|
|
729
|
+
console.log(
|
|
730
|
+
"[AmplifyUp SDK] Component View tracking enabled for",
|
|
731
|
+
hosts.length,
|
|
732
|
+
"hosts"
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
cleanupComponentViewTracking() {
|
|
737
|
+
this.componentViewObservers.forEach((obs) => obs.disconnect());
|
|
738
|
+
this.componentViewObservers = [];
|
|
573
739
|
}
|
|
574
740
|
/**
|
|
575
741
|
* Track a page view
|
|
@@ -609,9 +775,13 @@ var AmplifyUpSDK = class {
|
|
|
609
775
|
instanceId,
|
|
610
776
|
trackingId: this.config?.trackingId,
|
|
611
777
|
url: typeof window !== "undefined" ? window.location.href : void 0,
|
|
778
|
+
path: properties?.path || (typeof window !== "undefined" ? window.location.pathname : void 0),
|
|
612
779
|
// Timestamp is included in event payload
|
|
613
780
|
...properties
|
|
614
781
|
};
|
|
782
|
+
if (!trackProperties.path && typeof window !== "undefined") {
|
|
783
|
+
trackProperties.path = window.location.pathname;
|
|
784
|
+
}
|
|
615
785
|
this.adapter.track(eventName, trackProperties);
|
|
616
786
|
}
|
|
617
787
|
/**
|
|
@@ -900,6 +1070,13 @@ Available Console Commands:
|
|
|
900
1070
|
if (pageConfig?.eventTracking) {
|
|
901
1071
|
this.setupEventTracking(pageConfig.eventTracking);
|
|
902
1072
|
}
|
|
1073
|
+
if (typeof window !== "undefined") {
|
|
1074
|
+
window.setTimeout(() => this.setupComponentViewTracking(), 150);
|
|
1075
|
+
window.setTimeout(() => {
|
|
1076
|
+
this.setupComponentViewTracking();
|
|
1077
|
+
this.refreshEventTracking();
|
|
1078
|
+
}, 800);
|
|
1079
|
+
}
|
|
903
1080
|
return pageConfig;
|
|
904
1081
|
}
|
|
905
1082
|
/**
|
|
@@ -907,10 +1084,10 @@ Available Console Commands:
|
|
|
907
1084
|
*/
|
|
908
1085
|
setupEventTracking(eventConfig) {
|
|
909
1086
|
if (typeof document === "undefined") return;
|
|
910
|
-
this.
|
|
911
|
-
this.eventTrackingConfig = eventConfig;
|
|
1087
|
+
this.detachEventListeners();
|
|
1088
|
+
this.eventTrackingConfig = Array.isArray(eventConfig) ? [...eventConfig] : [];
|
|
912
1089
|
if (this.config?.debug) {
|
|
913
|
-
console.log("[AmplifyUp SDK] Setting up event tracking:",
|
|
1090
|
+
console.log("[AmplifyUp SDK] Setting up event tracking:", this.eventTrackingConfig.length, "events");
|
|
914
1091
|
}
|
|
915
1092
|
if (document.readyState === "loading") {
|
|
916
1093
|
document.addEventListener("DOMContentLoaded", () => this.attachEventListeners());
|
|
@@ -925,19 +1102,35 @@ Available Console Commands:
|
|
|
925
1102
|
if (typeof document === "undefined") return;
|
|
926
1103
|
this.eventTrackingConfig.forEach((eventConfig) => {
|
|
927
1104
|
if (eventConfig.enabled === false) return;
|
|
1105
|
+
if (!eventConfig.selector) {
|
|
1106
|
+
if (this.config?.debug) {
|
|
1107
|
+
console.warn(`[AmplifyUp SDK] Event ${eventConfig.eventName} has no selector, skipping`);
|
|
1108
|
+
}
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
928
1111
|
let targetElements = [];
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1112
|
+
try {
|
|
1113
|
+
const escapeAttr = (value) => typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(value) : value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1114
|
+
const scopeRoot = eventConfig.componentId ? document.querySelector(
|
|
1115
|
+
`[data-amplifyup-component="${escapeAttr(eventConfig.componentId)}"], [data-component-id="${escapeAttr(eventConfig.componentId)}"]`
|
|
1116
|
+
) : document;
|
|
1117
|
+
if (!scopeRoot) {
|
|
933
1118
|
if (this.config?.debug) {
|
|
934
|
-
console.
|
|
1119
|
+
console.log(
|
|
1120
|
+
`[AmplifyUp SDK] Component host not found for ${eventConfig.componentId}`
|
|
1121
|
+
);
|
|
935
1122
|
}
|
|
936
1123
|
return;
|
|
937
1124
|
}
|
|
938
|
-
|
|
1125
|
+
targetElements = Array.from(
|
|
1126
|
+
scopeRoot.querySelectorAll(eventConfig.selector)
|
|
1127
|
+
);
|
|
1128
|
+
if (scopeRoot instanceof Element && scopeRoot !== document.documentElement && scopeRoot.matches?.(eventConfig.selector)) {
|
|
1129
|
+
targetElements.unshift(scopeRoot);
|
|
1130
|
+
}
|
|
1131
|
+
} catch (e) {
|
|
939
1132
|
if (this.config?.debug) {
|
|
940
|
-
console.warn(`[AmplifyUp SDK]
|
|
1133
|
+
console.warn(`[AmplifyUp SDK] Invalid selector: ${eventConfig.selector}`, e);
|
|
941
1134
|
}
|
|
942
1135
|
return;
|
|
943
1136
|
}
|
|
@@ -993,8 +1186,15 @@ Available Console Commands:
|
|
|
993
1186
|
handleTrackedEvent(event, config, element) {
|
|
994
1187
|
const properties = {
|
|
995
1188
|
eventType: config.eventType,
|
|
996
|
-
selector: config.selector
|
|
1189
|
+
selector: config.selector,
|
|
1190
|
+
path: this.currentPath()
|
|
997
1191
|
};
|
|
1192
|
+
if (config.componentId) {
|
|
1193
|
+
properties.componentId = config.componentId;
|
|
1194
|
+
}
|
|
1195
|
+
if (config.componentName) {
|
|
1196
|
+
properties.componentName = config.componentName;
|
|
1197
|
+
}
|
|
998
1198
|
if (config.properties && config.properties.length > 0) {
|
|
999
1199
|
config.properties.forEach((prop) => {
|
|
1000
1200
|
const dataAttrValue = element.getAttribute(`data-${prop}`);
|
|
@@ -1065,9 +1265,9 @@ Available Console Commands:
|
|
|
1065
1265
|
element._amplifyupViewObserver = observer;
|
|
1066
1266
|
}
|
|
1067
1267
|
/**
|
|
1068
|
-
*
|
|
1268
|
+
* Detach listeners/observers without clearing the saved event config.
|
|
1069
1269
|
*/
|
|
1070
|
-
|
|
1270
|
+
detachEventListeners() {
|
|
1071
1271
|
this.attachedEventListeners.forEach(({ element, eventType, handler }) => {
|
|
1072
1272
|
element.removeEventListener(eventType, handler);
|
|
1073
1273
|
});
|
|
@@ -1081,6 +1281,12 @@ Available Console Commands:
|
|
|
1081
1281
|
}
|
|
1082
1282
|
});
|
|
1083
1283
|
}
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Clean up all attached event listeners and clear config
|
|
1287
|
+
*/
|
|
1288
|
+
cleanupEventTracking() {
|
|
1289
|
+
this.detachEventListeners();
|
|
1084
1290
|
this.eventTrackingConfig = [];
|
|
1085
1291
|
if (this.config?.debug) {
|
|
1086
1292
|
console.log("[AmplifyUp SDK] Cleaned up event tracking");
|
|
@@ -1090,11 +1296,13 @@ Available Console Commands:
|
|
|
1090
1296
|
* Manually trigger event tracking setup (useful after dynamic content loads)
|
|
1091
1297
|
*/
|
|
1092
1298
|
refreshEventTracking() {
|
|
1093
|
-
if (this.eventTrackingConfig.length
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1299
|
+
if (this.eventTrackingConfig.length === 0) return;
|
|
1300
|
+
const config = [...this.eventTrackingConfig];
|
|
1301
|
+
this.detachEventListeners();
|
|
1302
|
+
this.eventTrackingConfig = config;
|
|
1303
|
+
this.attachEventListeners();
|
|
1304
|
+
if (this.config?.debug) {
|
|
1305
|
+
console.log("[AmplifyUp SDK] Refreshed event tracking:", config.length, "events");
|
|
1098
1306
|
}
|
|
1099
1307
|
}
|
|
1100
1308
|
/** Origins allowed to send Composer postMessage commands to the SDK. */
|
|
@@ -2087,6 +2295,7 @@ function getSDK(adapter) {
|
|
|
2087
2295
|
return sdkInstance;
|
|
2088
2296
|
}
|
|
2089
2297
|
var trackContentView = (contentId, contentType, properties) => getSDK().trackContentView(contentId, contentType, properties);
|
|
2298
|
+
var refreshEventTracking = () => getSDK().refreshEventTracking();
|
|
2090
2299
|
getSDK();
|
|
2091
2300
|
createContext(null);
|
|
2092
2301
|
var usePathname = null;
|
|
@@ -2146,6 +2355,32 @@ function AmplifyRenderer({
|
|
|
2146
2355
|
});
|
|
2147
2356
|
}
|
|
2148
2357
|
}, [pageConfig]);
|
|
2358
|
+
useEffect(() => {
|
|
2359
|
+
if (!pageConfig?.eventTracking?.length) return;
|
|
2360
|
+
const sdk = getSDK();
|
|
2361
|
+
if (!sdk.isInitialized()) return;
|
|
2362
|
+
sdk.setupEventTracking(pageConfig.eventTracking);
|
|
2363
|
+
const t1 = window.setTimeout(() => {
|
|
2364
|
+
sdk.setupComponentViewTracking();
|
|
2365
|
+
refreshEventTracking();
|
|
2366
|
+
}, 200);
|
|
2367
|
+
const t2 = window.setTimeout(() => {
|
|
2368
|
+
sdk.setupComponentViewTracking();
|
|
2369
|
+
refreshEventTracking();
|
|
2370
|
+
}, 1e3);
|
|
2371
|
+
return () => {
|
|
2372
|
+
window.clearTimeout(t1);
|
|
2373
|
+
window.clearTimeout(t2);
|
|
2374
|
+
};
|
|
2375
|
+
}, [pageConfig?.id]);
|
|
2376
|
+
useEffect(() => {
|
|
2377
|
+
if (!pageConfig) return;
|
|
2378
|
+
const t = window.setTimeout(() => {
|
|
2379
|
+
getSDK().setupComponentViewTracking();
|
|
2380
|
+
refreshEventTracking();
|
|
2381
|
+
}, 100);
|
|
2382
|
+
return () => window.clearTimeout(t);
|
|
2383
|
+
}, [variantCache, pageConfig?.id]);
|
|
2149
2384
|
useEffect(() => {
|
|
2150
2385
|
const isVisualEditorMode = () => {
|
|
2151
2386
|
if (typeof window === "undefined") return false;
|
|
@@ -2351,6 +2586,31 @@ function AmplifyRenderer({
|
|
|
2351
2586
|
pageConfig.layout?.header !== false;
|
|
2352
2587
|
pageConfig.layout?.footer !== false;
|
|
2353
2588
|
const renderNode = (node) => {
|
|
2589
|
+
const zoneIdFromProps = node.componentId === "AmplifyZone" ? String(node.props?.zoneId || "") : "";
|
|
2590
|
+
if (node.componentId === "AmplifyZone") {
|
|
2591
|
+
const contentSlot = node.slots?.find((s) => s.slotId === "content") || node.slots?.[0];
|
|
2592
|
+
const children = Array.isArray(contentSlot?.children) ? contentSlot.children : [];
|
|
2593
|
+
return /* @__PURE__ */ jsx(
|
|
2594
|
+
"div",
|
|
2595
|
+
{
|
|
2596
|
+
"data-node-id": node.id,
|
|
2597
|
+
"data-amplifyup-node": "",
|
|
2598
|
+
"data-amplifyup-component": "AmplifyZone",
|
|
2599
|
+
"data-amplifyup-zone": zoneIdFromProps || void 0,
|
|
2600
|
+
"data-zone-id": zoneIdFromProps || void 0,
|
|
2601
|
+
children: /* @__PURE__ */ jsx(
|
|
2602
|
+
"div",
|
|
2603
|
+
{
|
|
2604
|
+
"data-slot-id": "content",
|
|
2605
|
+
"data-amplifyup-slot": "content",
|
|
2606
|
+
"data-parent-node-id": node.id,
|
|
2607
|
+
"data-parent-component-id": "AmplifyZone",
|
|
2608
|
+
children: children.filter(Boolean).map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child) }, child.id))
|
|
2609
|
+
}
|
|
2610
|
+
)
|
|
2611
|
+
}
|
|
2612
|
+
);
|
|
2613
|
+
}
|
|
2354
2614
|
let slotChildren = {};
|
|
2355
2615
|
console.log(`[AmplifyRenderer] Rendering node ${node.id} (${node.componentId})`, {
|
|
2356
2616
|
hasSlots: !!(node.slots && node.slots.length > 0),
|