@syntrologie/adapt-reviews 2.8.0-canary.414
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/cdn.d.ts +40 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +33 -0
- package/dist/cdn.js.map +7 -0
- package/dist/chunk-QU2QGSUW.js +380 -0
- package/dist/chunk-QU2QGSUW.js.map +7 -0
- package/dist/reviewDisplay.d.ts +124 -0
- package/dist/reviewDisplay.d.ts.map +1 -0
- package/dist/runtime.d.ts +62 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +17 -0
- package/dist/runtime.js.map +7 -0
- package/dist/schema.d.ts +62 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +53 -0
- package/dist/schema.js.map +7 -0
- package/package.json +58 -0
package/dist/cdn.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adaptive-reviews — CDN entrypoint
|
|
3
|
+
*
|
|
4
|
+
* Loaded by the runtime SDK's AppLoader when a tile references
|
|
5
|
+
* adaptive-reviews:reviews and the package isn't bundled (it isn't — see
|
|
6
|
+
* `syntrologie.bundlingMode: cdn-only` in package.json).
|
|
7
|
+
*
|
|
8
|
+
* Importing ./runtime registers the <syntro-reviews> custom element and
|
|
9
|
+
* exposes the widget mountable on the manifest.
|
|
10
|
+
*/
|
|
11
|
+
declare const manifest: {
|
|
12
|
+
id: string;
|
|
13
|
+
version: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
runtime: {
|
|
17
|
+
actions: {
|
|
18
|
+
kind: string;
|
|
19
|
+
executor: unknown;
|
|
20
|
+
}[];
|
|
21
|
+
widgets: {
|
|
22
|
+
id: string;
|
|
23
|
+
component: {
|
|
24
|
+
mount(container: HTMLElement, config?: Record<string, unknown> | null): () => void;
|
|
25
|
+
update(container: HTMLElement, config?: Record<string, unknown> | null): void;
|
|
26
|
+
};
|
|
27
|
+
metadata: {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
icon: string;
|
|
31
|
+
};
|
|
32
|
+
}[];
|
|
33
|
+
};
|
|
34
|
+
metadata: {
|
|
35
|
+
isBuiltIn: boolean;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export { manifest as runtime };
|
|
39
|
+
export default manifest;
|
|
40
|
+
//# sourceMappingURL=cdn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,QAAA,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;CAeb,CAAC;AAWF,OAAO,EAAE,QAAQ,IAAI,OAAO,EAAE,CAAC;AAC/B,eAAe,QAAQ,CAAC"}
|
package/dist/cdn.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runtime
|
|
3
|
+
} from "./chunk-QU2QGSUW.js";
|
|
4
|
+
|
|
5
|
+
// src/cdn.ts
|
|
6
|
+
var manifest = {
|
|
7
|
+
id: runtime.id,
|
|
8
|
+
version: runtime.version,
|
|
9
|
+
name: runtime.name,
|
|
10
|
+
description: runtime.description,
|
|
11
|
+
runtime: {
|
|
12
|
+
actions: (runtime.executors || []).map(
|
|
13
|
+
({ kind, executor }) => ({
|
|
14
|
+
kind,
|
|
15
|
+
executor
|
|
16
|
+
})
|
|
17
|
+
),
|
|
18
|
+
widgets: runtime.widgets
|
|
19
|
+
},
|
|
20
|
+
metadata: { isBuiltIn: false }
|
|
21
|
+
};
|
|
22
|
+
if (typeof window !== "undefined") {
|
|
23
|
+
const synOS = window.SynOS;
|
|
24
|
+
if (synOS?.appRegistry?.register) {
|
|
25
|
+
synOS.appRegistry.register(manifest);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
var cdn_default = manifest;
|
|
29
|
+
export {
|
|
30
|
+
cdn_default as default,
|
|
31
|
+
manifest as runtime
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=cdn.js.map
|
package/dist/cdn.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/cdn.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * adaptive-reviews \u2014 CDN entrypoint\n *\n * Loaded by the runtime SDK's AppLoader when a tile references\n * adaptive-reviews:reviews and the package isn't bundled (it isn't \u2014 see\n * `syntrologie.bundlingMode: cdn-only` in package.json).\n *\n * Importing ./runtime registers the <syntro-reviews> custom element and\n * exposes the widget mountable on the manifest.\n */\n\nimport { runtime as flatRuntime } from './runtime';\n\n// AppRegistry.activate reads widgets/actions from manifest.runtime.*, not the\n// flat shape exposed by ./runtime.ts. CDN bundles wrap it themselves (the\n// bundled-adaptive path did this in runtime-sdk's builtinRuntimeModules.ts).\nconst manifest = {\n id: flatRuntime.id,\n version: flatRuntime.version,\n name: flatRuntime.name,\n description: flatRuntime.description,\n runtime: {\n actions: (flatRuntime.executors || []).map(\n ({ kind, executor }: { kind: string; executor: unknown }) => ({\n kind,\n executor,\n })\n ),\n widgets: flatRuntime.widgets,\n },\n metadata: { isBuiltIn: false },\n};\n\nif (typeof window !== 'undefined') {\n const synOS = (\n window as unknown as { SynOS?: { appRegistry?: { register: (m: unknown) => void } } }\n ).SynOS;\n if (synOS?.appRegistry?.register) {\n synOS.appRegistry.register(manifest);\n }\n}\n\nexport { manifest as runtime };\nexport default manifest;\n"],
|
|
5
|
+
"mappings": ";;;;;AAgBA,IAAM,WAAW;AAAA,EACf,IAAI,QAAY;AAAA,EAChB,SAAS,QAAY;AAAA,EACrB,MAAM,QAAY;AAAA,EAClB,aAAa,QAAY;AAAA,EACzB,SAAS;AAAA,IACP,UAAU,QAAY,aAAa,CAAC,GAAG;AAAA,MACrC,CAAC,EAAE,MAAM,SAAS,OAA4C;AAAA,QAC5D;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS,QAAY;AAAA,EACvB;AAAA,EACA,UAAU,EAAE,WAAW,MAAM;AAC/B;AAEA,IAAI,OAAO,WAAW,aAAa;AACjC,QAAM,QACJ,OACA;AACF,MAAI,OAAO,aAAa,UAAU;AAChC,UAAM,YAAY,SAAS,QAAQ;AAAA,EACrC;AACF;AAGA,IAAO,cAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
// src/reviewDisplay.ts
|
|
2
|
+
import { html, LitElement, nothing } from "lit";
|
|
3
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
4
|
+
var PRODUCT_ID_ATTRIBUTE = "data-syntro-product-id";
|
|
5
|
+
async function fetchReviewDisplay(authedFetch, options = {}) {
|
|
6
|
+
const limit = normalizeLimit(options.limit);
|
|
7
|
+
const productId = options.productId?.trim();
|
|
8
|
+
const productParam = productId ? `&product_id=${encodeURIComponent(productId)}` : "";
|
|
9
|
+
const response = await authedFetch(`/api/adaptive/reviews?limit=${limit}${productParam}`, {
|
|
10
|
+
method: "GET",
|
|
11
|
+
headers: { Accept: "application/json" }
|
|
12
|
+
});
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new Error("Reviews are unavailable");
|
|
15
|
+
}
|
|
16
|
+
return await response.json();
|
|
17
|
+
}
|
|
18
|
+
function normalizeLimit(limit) {
|
|
19
|
+
if (!Number.isFinite(limit)) return 20;
|
|
20
|
+
return Math.min(50, Math.max(1, Math.trunc(limit)));
|
|
21
|
+
}
|
|
22
|
+
function defaultLoadReviews(limit, productId) {
|
|
23
|
+
const authedFetch = window.SynOS?.authedFetch;
|
|
24
|
+
if (!authedFetch) {
|
|
25
|
+
return Promise.reject(new Error("Reviews are unavailable"));
|
|
26
|
+
}
|
|
27
|
+
return fetchReviewDisplay(authedFetch, { limit, productId });
|
|
28
|
+
}
|
|
29
|
+
function formatAverage(value) {
|
|
30
|
+
return typeof value === "number" ? value.toFixed(1) : "0.0";
|
|
31
|
+
}
|
|
32
|
+
function reviewCountLabel(count) {
|
|
33
|
+
return count === 1 ? "1 review" : `${count} reviews`;
|
|
34
|
+
}
|
|
35
|
+
function createdAtLabel(value) {
|
|
36
|
+
if (!value) return null;
|
|
37
|
+
const date = new Date(value);
|
|
38
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
39
|
+
return date.toLocaleDateString(void 0, {
|
|
40
|
+
month: "short",
|
|
41
|
+
day: "numeric",
|
|
42
|
+
year: "numeric"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
var SyntroReviews = class extends LitElement {
|
|
46
|
+
constructor() {
|
|
47
|
+
super(...arguments);
|
|
48
|
+
this.heading = "Customer reviews";
|
|
49
|
+
this.limit = 20;
|
|
50
|
+
this._isLoading = true;
|
|
51
|
+
this._error = null;
|
|
52
|
+
this._data = null;
|
|
53
|
+
this._requestId = 0;
|
|
54
|
+
this._lastProductId = null;
|
|
55
|
+
this._observedAnchor = null;
|
|
56
|
+
}
|
|
57
|
+
createRenderRoot() {
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
connectedCallback() {
|
|
61
|
+
super.connectedCallback();
|
|
62
|
+
this._syncAnchorObserver();
|
|
63
|
+
void this._load();
|
|
64
|
+
}
|
|
65
|
+
disconnectedCallback() {
|
|
66
|
+
super.disconnectedCallback();
|
|
67
|
+
this._anchorObserver?.disconnect();
|
|
68
|
+
this._anchorObserver = void 0;
|
|
69
|
+
this._observedAnchor = null;
|
|
70
|
+
}
|
|
71
|
+
updated(changed) {
|
|
72
|
+
if (changed.has("productIdSelector")) this._syncAnchorObserver();
|
|
73
|
+
const reloadKeys = ["limit", "productId", "productIdSelector"];
|
|
74
|
+
if (reloadKeys.some((key) => changed.has(key)) && !changed.has("_isLoading")) {
|
|
75
|
+
void this._load();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the host-page element the `productIdSelector` points at. Guarded:
|
|
80
|
+
* an invalid selector must not throw and brick the widget — treat it as
|
|
81
|
+
* unresolved.
|
|
82
|
+
*/
|
|
83
|
+
_resolveAnchorEl() {
|
|
84
|
+
if (!this.productIdSelector || typeof document === "undefined") return null;
|
|
85
|
+
try {
|
|
86
|
+
return document.querySelector(this.productIdSelector);
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve the product to scope reviews to. A `productIdSelector` element
|
|
93
|
+
* carrying `data-syntro-product-id` wins; otherwise fall back to the
|
|
94
|
+
* explicit `productId`; otherwise null.
|
|
95
|
+
*/
|
|
96
|
+
_resolveProductId() {
|
|
97
|
+
const fromDom = this._resolveAnchorEl()?.getAttribute(PRODUCT_ID_ATTRIBUTE)?.trim();
|
|
98
|
+
if (fromDom) return fromDom;
|
|
99
|
+
const explicit = this.productId?.trim();
|
|
100
|
+
return explicit ? explicit : null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Watch the anchor's `data-syntro-product-id` so the widget re-scopes on SPA
|
|
104
|
+
* navigation. On a route like `/product/:slug`, the host reconciles the SAME
|
|
105
|
+
* anchor node and just mutates the attribute — nothing re-triggers a Lit
|
|
106
|
+
* prop change, so without this observer the widget would keep showing the
|
|
107
|
+
* previous product's reviews.
|
|
108
|
+
*/
|
|
109
|
+
_syncAnchorObserver() {
|
|
110
|
+
const anchor = this._resolveAnchorEl();
|
|
111
|
+
if (anchor === this._observedAnchor) return;
|
|
112
|
+
this._anchorObserver?.disconnect();
|
|
113
|
+
this._observedAnchor = anchor;
|
|
114
|
+
if (!anchor || typeof MutationObserver === "undefined") return;
|
|
115
|
+
this._anchorObserver = new MutationObserver(() => {
|
|
116
|
+
if (this._resolveProductId() !== this._lastProductId) void this._load();
|
|
117
|
+
});
|
|
118
|
+
this._anchorObserver.observe(anchor, {
|
|
119
|
+
attributes: true,
|
|
120
|
+
attributeFilter: [PRODUCT_ID_ATTRIBUTE]
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async _load() {
|
|
124
|
+
const requestId = ++this._requestId;
|
|
125
|
+
const productId = this._resolveProductId();
|
|
126
|
+
if (this.productIdSelector && !productId) {
|
|
127
|
+
this._lastProductId = null;
|
|
128
|
+
this._isLoading = false;
|
|
129
|
+
this._error = null;
|
|
130
|
+
this._data = { reviews: [], review_count: 0, average_rating: null };
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this._lastProductId = productId;
|
|
134
|
+
this._isLoading = true;
|
|
135
|
+
this._error = null;
|
|
136
|
+
try {
|
|
137
|
+
const loader = this.loadReviews ?? defaultLoadReviews;
|
|
138
|
+
const data = await loader(normalizeLimit(this.limit), productId);
|
|
139
|
+
if (requestId !== this._requestId) return;
|
|
140
|
+
this._data = data;
|
|
141
|
+
} catch {
|
|
142
|
+
if (requestId !== this._requestId) return;
|
|
143
|
+
this._data = null;
|
|
144
|
+
this._error = "Reviews are unavailable";
|
|
145
|
+
} finally {
|
|
146
|
+
if (requestId === this._requestId) {
|
|
147
|
+
this._isLoading = false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
render() {
|
|
152
|
+
const rootStyles = {
|
|
153
|
+
display: "block",
|
|
154
|
+
width: "100%",
|
|
155
|
+
color: "var(--sc-tile-text, var(--sc-overlay-text-color, #111827))",
|
|
156
|
+
fontFamily: "var(--sc-font-family, inherit)"
|
|
157
|
+
};
|
|
158
|
+
return html`
|
|
159
|
+
<section data-syntro-reviews style=${styleMap(rootStyles)} aria-label=${this.heading}>
|
|
160
|
+
${this._isLoading ? this._renderFrame(html`<div style=${styleMap(this._stateStyles())}>Loading...</div>`) : this._error ? this._renderFrame(
|
|
161
|
+
html`<div style=${styleMap(this._stateStyles())}>${this._error}</div>`
|
|
162
|
+
) : this._data && this._data.reviews.length > 0 ? this._renderReviews(this._data) : this._renderFrame(
|
|
163
|
+
html`<div style=${styleMap(this._stateStyles())}>No reviews yet</div>`
|
|
164
|
+
)}
|
|
165
|
+
</section>
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
_renderFrame(content) {
|
|
169
|
+
return html`
|
|
170
|
+
<div style=${styleMap(this._panelStyles())}>
|
|
171
|
+
<div style=${styleMap(this._headingStyles())}>${this.heading}</div>
|
|
172
|
+
${content}
|
|
173
|
+
</div>
|
|
174
|
+
`;
|
|
175
|
+
}
|
|
176
|
+
_renderReviews(data) {
|
|
177
|
+
return html`
|
|
178
|
+
<div style=${styleMap(this._panelStyles())}>
|
|
179
|
+
<div style=${styleMap(this._summaryStyles())}>
|
|
180
|
+
<div>
|
|
181
|
+
<div style=${styleMap(this._headingStyles())}>${this.heading}</div>
|
|
182
|
+
<div style=${styleMap(this._metaStyles())}>${reviewCountLabel(data.review_count)}</div>
|
|
183
|
+
</div>
|
|
184
|
+
<div style=${styleMap(this._ratingBadgeStyles())} aria-label=${`${formatAverage(data.average_rating)} out of 5`}>
|
|
185
|
+
<span style=${styleMap({ fontSize: "20px", fontWeight: "700" })}>${formatAverage(data.average_rating)}</span>
|
|
186
|
+
<span aria-hidden="true">★</span>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
<div style=${styleMap({ display: "grid", gap: "10px" })}>
|
|
190
|
+
${data.reviews.map((review) => this._renderReview(review))}
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
_renderReview(review) {
|
|
196
|
+
const date = createdAtLabel(review.created_at);
|
|
197
|
+
return html`
|
|
198
|
+
<article style=${styleMap(this._reviewStyles())}>
|
|
199
|
+
<div style=${styleMap(this._reviewHeaderStyles())}>
|
|
200
|
+
<div style=${styleMap({ fontWeight: "700" })}>${review.review_title || "Review"}</div>
|
|
201
|
+
<div aria-label=${`${review.rating} out of 5`} style=${styleMap(this._starsStyles())}>
|
|
202
|
+
${"\u2605".repeat(Math.max(0, Math.min(5, Math.round(review.rating))))}
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
<p style=${styleMap(this._bodyStyles())}>${review.review_body}</p>
|
|
206
|
+
<div style=${styleMap(this._metaStyles())}>
|
|
207
|
+
${review.reviewer_display_name || "Customer"}
|
|
208
|
+
${review.verified_purchase ? html`<span> · Verified purchase</span>` : nothing}
|
|
209
|
+
${date ? html`<span> · ${date}</span>` : nothing}
|
|
210
|
+
</div>
|
|
211
|
+
</article>
|
|
212
|
+
`;
|
|
213
|
+
}
|
|
214
|
+
_panelStyles() {
|
|
215
|
+
return {
|
|
216
|
+
boxSizing: "border-box",
|
|
217
|
+
border: "1px solid var(--sc-tile-border-color, rgba(148, 163, 184, 0.24))",
|
|
218
|
+
borderRadius: "8px",
|
|
219
|
+
background: "var(--sc-tile-background, #ffffff)",
|
|
220
|
+
padding: "14px",
|
|
221
|
+
boxShadow: "var(--sc-tile-shadow, 0 8px 24px rgba(15, 23, 42, 0.08))"
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
_headingStyles() {
|
|
225
|
+
return {
|
|
226
|
+
fontSize: "15px",
|
|
227
|
+
lineHeight: "20px",
|
|
228
|
+
fontWeight: "700",
|
|
229
|
+
margin: "0"
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
_summaryStyles() {
|
|
233
|
+
return {
|
|
234
|
+
display: "flex",
|
|
235
|
+
alignItems: "center",
|
|
236
|
+
justifyContent: "space-between",
|
|
237
|
+
gap: "12px",
|
|
238
|
+
marginBottom: "12px"
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
_ratingBadgeStyles() {
|
|
242
|
+
return {
|
|
243
|
+
display: "inline-flex",
|
|
244
|
+
alignItems: "center",
|
|
245
|
+
gap: "4px",
|
|
246
|
+
color: "var(--sc-accent, #2563eb)",
|
|
247
|
+
whiteSpace: "nowrap"
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
_reviewStyles() {
|
|
251
|
+
return {
|
|
252
|
+
borderTop: "1px solid var(--sc-tile-border-color, rgba(148, 163, 184, 0.2))",
|
|
253
|
+
paddingTop: "10px"
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
_reviewHeaderStyles() {
|
|
257
|
+
return {
|
|
258
|
+
display: "flex",
|
|
259
|
+
alignItems: "baseline",
|
|
260
|
+
justifyContent: "space-between",
|
|
261
|
+
gap: "10px",
|
|
262
|
+
fontSize: "14px",
|
|
263
|
+
lineHeight: "20px"
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
_starsStyles() {
|
|
267
|
+
return {
|
|
268
|
+
color: "var(--sc-accent, #2563eb)",
|
|
269
|
+
fontSize: "13px",
|
|
270
|
+
lineHeight: "18px",
|
|
271
|
+
whiteSpace: "nowrap"
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
_bodyStyles() {
|
|
275
|
+
return {
|
|
276
|
+
margin: "6px 0",
|
|
277
|
+
fontSize: "14px",
|
|
278
|
+
lineHeight: "20px"
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
_metaStyles() {
|
|
282
|
+
return {
|
|
283
|
+
color: "var(--sc-muted-text, #64748b)",
|
|
284
|
+
fontSize: "12px",
|
|
285
|
+
lineHeight: "18px"
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
_stateStyles() {
|
|
289
|
+
return {
|
|
290
|
+
marginTop: "8px",
|
|
291
|
+
color: "var(--sc-muted-text, #64748b)",
|
|
292
|
+
fontSize: "14px",
|
|
293
|
+
lineHeight: "20px"
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
SyntroReviews.properties = {
|
|
298
|
+
heading: { type: String },
|
|
299
|
+
limit: { type: Number },
|
|
300
|
+
productId: { type: String },
|
|
301
|
+
productIdSelector: { type: String },
|
|
302
|
+
loadReviews: { attribute: false },
|
|
303
|
+
_isLoading: { state: true },
|
|
304
|
+
_error: { state: true },
|
|
305
|
+
_data: { state: true }
|
|
306
|
+
};
|
|
307
|
+
if (!customElements.get("syntro-reviews")) {
|
|
308
|
+
customElements.define("syntro-reviews", SyntroReviews);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/runtime.ts
|
|
312
|
+
var REVIEWS_ADAPTIVE_APP_ID = "adaptive-reviews";
|
|
313
|
+
var REVIEWS_ADAPTIVE_WIDGET_ID = "adaptive-reviews:reviews";
|
|
314
|
+
function asPositiveLimit(value) {
|
|
315
|
+
const numeric = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
316
|
+
if (!Number.isFinite(numeric)) return void 0;
|
|
317
|
+
return Math.max(1, Math.floor(numeric));
|
|
318
|
+
}
|
|
319
|
+
function applyReviewsConfig(el, config) {
|
|
320
|
+
if (!config) return;
|
|
321
|
+
if (typeof config.heading === "string") {
|
|
322
|
+
el.heading = config.heading;
|
|
323
|
+
}
|
|
324
|
+
const limit = asPositiveLimit(config.limit);
|
|
325
|
+
if (limit !== void 0) {
|
|
326
|
+
el.limit = limit;
|
|
327
|
+
}
|
|
328
|
+
if (typeof config.productId === "string") {
|
|
329
|
+
el.productId = config.productId;
|
|
330
|
+
}
|
|
331
|
+
if (typeof config.productIdSelector === "string") {
|
|
332
|
+
el.productIdSelector = config.productIdSelector;
|
|
333
|
+
}
|
|
334
|
+
if (typeof config.loadReviews === "function") {
|
|
335
|
+
el.loadReviews = config.loadReviews;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
var reviewsWidget = {
|
|
339
|
+
mount(container, config) {
|
|
340
|
+
const el = document.createElement("syntro-reviews");
|
|
341
|
+
applyReviewsConfig(el, config);
|
|
342
|
+
container.appendChild(el);
|
|
343
|
+
return () => el.remove();
|
|
344
|
+
},
|
|
345
|
+
update(container, config) {
|
|
346
|
+
const el = container.querySelector("syntro-reviews");
|
|
347
|
+
if (el) applyReviewsConfig(el, config);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
var runtime = {
|
|
351
|
+
id: REVIEWS_ADAPTIVE_APP_ID,
|
|
352
|
+
version: "0.1.0",
|
|
353
|
+
name: "Reviews",
|
|
354
|
+
description: "Display imported and submitted customer reviews through the runtime SDK.",
|
|
355
|
+
/** No DOM-mutation executors — this widget renders only. */
|
|
356
|
+
executors: [],
|
|
357
|
+
widgets: [
|
|
358
|
+
{
|
|
359
|
+
id: REVIEWS_ADAPTIVE_WIDGET_ID,
|
|
360
|
+
component: reviewsWidget,
|
|
361
|
+
metadata: {
|
|
362
|
+
name: "Reviews",
|
|
363
|
+
description: "Display customer reviews from the adaptive review endpoint.",
|
|
364
|
+
icon: "\u{1F4AC}"
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
]
|
|
368
|
+
};
|
|
369
|
+
var reviewsAdaptiveRuntime = runtime;
|
|
370
|
+
var runtime_default = runtime;
|
|
371
|
+
|
|
372
|
+
export {
|
|
373
|
+
REVIEWS_ADAPTIVE_APP_ID,
|
|
374
|
+
REVIEWS_ADAPTIVE_WIDGET_ID,
|
|
375
|
+
reviewsWidget,
|
|
376
|
+
runtime,
|
|
377
|
+
reviewsAdaptiveRuntime,
|
|
378
|
+
runtime_default
|
|
379
|
+
};
|
|
380
|
+
//# sourceMappingURL=chunk-QU2QGSUW.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/reviewDisplay.ts", "../src/runtime.ts"],
|
|
4
|
+
"sourcesContent": ["import { html, LitElement, nothing } from 'lit';\nimport { styleMap } from 'lit/directives/style-map.js';\n\nexport type ReviewSource = 'imported' | 'submitted' | string;\n\nexport interface DisplayReview {\n product_id?: string | null;\n product_url?: string | null;\n rating: number;\n review_title?: string | null;\n review_body: string;\n reviewer_display_name?: string | null;\n verified_purchase?: boolean | null;\n created_at?: string | null;\n source: ReviewSource;\n}\n\nexport interface ReviewDisplayResponse {\n reviews: DisplayReview[];\n review_count: number;\n average_rating: number | null;\n}\n\nexport type SyntroAuthedFetch = (path: string, init?: RequestInit) => Promise<Response>;\nexport type LoadReviews = (\n limit: number,\n productId?: string | null\n) => Promise<ReviewDisplayResponse>;\n\n/**\n * Host-page attribute a product page sets to declare which product the\n * reviews widget should scope to. Paired with the widget's\n * `productIdSelector` prop: the widget reads this attribute off the element\n * that selector matches. Keeping it a shared constant avoids drift between\n * the host page and the widget.\n */\nexport const PRODUCT_ID_ATTRIBUTE = 'data-syntro-product-id';\n\nexport async function fetchReviewDisplay(\n authedFetch: SyntroAuthedFetch,\n options: { limit?: number; productId?: string | null } = {}\n): Promise<ReviewDisplayResponse> {\n const limit = normalizeLimit(options.limit);\n const productId = options.productId?.trim();\n const productParam = productId ? `&product_id=${encodeURIComponent(productId)}` : '';\n const response = await authedFetch(`/api/adaptive/reviews?limit=${limit}${productParam}`, {\n method: 'GET',\n headers: { Accept: 'application/json' },\n });\n if (!response.ok) {\n throw new Error('Reviews are unavailable');\n }\n return (await response.json()) as ReviewDisplayResponse;\n}\n\nfunction normalizeLimit(limit: number | undefined): number {\n if (!Number.isFinite(limit)) return 20;\n return Math.min(50, Math.max(1, Math.trunc(limit as number)));\n}\n\nfunction defaultLoadReviews(\n limit: number,\n productId?: string | null\n): Promise<ReviewDisplayResponse> {\n const authedFetch = (window as unknown as { SynOS?: { authedFetch?: SyntroAuthedFetch } }).SynOS\n ?.authedFetch;\n if (!authedFetch) {\n return Promise.reject(new Error('Reviews are unavailable'));\n }\n return fetchReviewDisplay(authedFetch, { limit, productId });\n}\n\nfunction formatAverage(value: number | null): string {\n return typeof value === 'number' ? value.toFixed(1) : '0.0';\n}\n\nfunction reviewCountLabel(count: number): string {\n return count === 1 ? '1 review' : `${count} reviews`;\n}\n\nfunction createdAtLabel(value?: string | null): string | null {\n if (!value) return null;\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return null;\n return date.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n}\n\nexport class SyntroReviews extends LitElement {\n static override properties = {\n heading: { type: String },\n limit: { type: Number },\n productId: { type: String },\n productIdSelector: { type: String },\n loadReviews: { attribute: false },\n _isLoading: { state: true },\n _error: { state: true },\n _data: { state: true },\n };\n\n heading = 'Customer reviews';\n limit = 20;\n /** Explicit product to scope reviews to. Overridden by `productIdSelector` when that resolves. */\n productId?: string;\n /**\n * CSS selector for a host-page element carrying `data-syntro-product-id`.\n * When set and resolvable, the widget scopes reviews to that product \u2014 this\n * is how one config drives per-product reviews across many product pages.\n * When unset (or unresolved), reviews are workspace-wide.\n */\n productIdSelector?: string;\n loadReviews?: LoadReviews;\n\n private _isLoading = true;\n private _error: string | null = null;\n private _data: ReviewDisplayResponse | null = null;\n private _requestId = 0;\n private _lastProductId: string | null = null;\n private _anchorObserver?: MutationObserver;\n private _observedAnchor: Element | null = null;\n\n override createRenderRoot() {\n return this;\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n this._syncAnchorObserver();\n void this._load();\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this._anchorObserver?.disconnect();\n this._anchorObserver = undefined;\n this._observedAnchor = null;\n }\n\n override updated(changed: Map<string, unknown>): void {\n if (changed.has('productIdSelector')) this._syncAnchorObserver();\n const reloadKeys = ['limit', 'productId', 'productIdSelector'];\n if (reloadKeys.some((key) => changed.has(key)) && !changed.has('_isLoading')) {\n void this._load();\n }\n }\n\n /**\n * Resolve the host-page element the `productIdSelector` points at. Guarded:\n * an invalid selector must not throw and brick the widget \u2014 treat it as\n * unresolved.\n */\n private _resolveAnchorEl(): Element | null {\n if (!this.productIdSelector || typeof document === 'undefined') return null;\n try {\n return document.querySelector(this.productIdSelector);\n } catch {\n return null;\n }\n }\n\n /**\n * Resolve the product to scope reviews to. A `productIdSelector` element\n * carrying `data-syntro-product-id` wins; otherwise fall back to the\n * explicit `productId`; otherwise null.\n */\n private _resolveProductId(): string | null {\n const fromDom = this._resolveAnchorEl()?.getAttribute(PRODUCT_ID_ATTRIBUTE)?.trim();\n if (fromDom) return fromDom;\n const explicit = this.productId?.trim();\n return explicit ? explicit : null;\n }\n\n /**\n * Watch the anchor's `data-syntro-product-id` so the widget re-scopes on SPA\n * navigation. On a route like `/product/:slug`, the host reconciles the SAME\n * anchor node and just mutates the attribute \u2014 nothing re-triggers a Lit\n * prop change, so without this observer the widget would keep showing the\n * previous product's reviews.\n */\n private _syncAnchorObserver(): void {\n const anchor = this._resolveAnchorEl();\n if (anchor === this._observedAnchor) return;\n this._anchorObserver?.disconnect();\n this._observedAnchor = anchor;\n if (!anchor || typeof MutationObserver === 'undefined') return;\n this._anchorObserver = new MutationObserver(() => {\n if (this._resolveProductId() !== this._lastProductId) void this._load();\n });\n this._anchorObserver.observe(anchor, {\n attributes: true,\n attributeFilter: [PRODUCT_ID_ATTRIBUTE],\n });\n }\n\n private async _load(): Promise<void> {\n const requestId = ++this._requestId;\n const productId = this._resolveProductId();\n // Fail closed: when a productIdSelector is configured but nothing resolves,\n // do NOT fall back to a workspace-wide fetch \u2014 that would bleed other\n // products' reviews onto this page. Show an empty state; the anchor\n // observer re-fires _load once `data-syntro-product-id` appears.\n if (this.productIdSelector && !productId) {\n this._lastProductId = null;\n this._isLoading = false;\n this._error = null;\n this._data = { reviews: [], review_count: 0, average_rating: null };\n return;\n }\n this._lastProductId = productId;\n this._isLoading = true;\n this._error = null;\n try {\n const loader = this.loadReviews ?? defaultLoadReviews;\n const data = await loader(normalizeLimit(this.limit), productId);\n if (requestId !== this._requestId) return;\n this._data = data;\n } catch {\n if (requestId !== this._requestId) return;\n this._data = null;\n this._error = 'Reviews are unavailable';\n } finally {\n if (requestId === this._requestId) {\n this._isLoading = false;\n }\n }\n }\n\n override render() {\n const rootStyles: Record<string, string> = {\n display: 'block',\n width: '100%',\n color: 'var(--sc-tile-text, var(--sc-overlay-text-color, #111827))',\n fontFamily: 'var(--sc-font-family, inherit)',\n };\n\n return html`\n <section data-syntro-reviews style=${styleMap(rootStyles)} aria-label=${this.heading}>\n ${\n this._isLoading\n ? this._renderFrame(html`<div style=${styleMap(this._stateStyles())}>Loading...</div>`)\n : this._error\n ? this._renderFrame(\n html`<div style=${styleMap(this._stateStyles())}>${this._error}</div>`\n )\n : this._data && this._data.reviews.length > 0\n ? this._renderReviews(this._data)\n : this._renderFrame(\n html`<div style=${styleMap(this._stateStyles())}>No reviews yet</div>`\n )\n }\n </section>\n `;\n }\n\n private _renderFrame(content: unknown) {\n return html`\n <div style=${styleMap(this._panelStyles())}>\n <div style=${styleMap(this._headingStyles())}>${this.heading}</div>\n ${content}\n </div>\n `;\n }\n\n private _renderReviews(data: ReviewDisplayResponse) {\n return html`\n <div style=${styleMap(this._panelStyles())}>\n <div style=${styleMap(this._summaryStyles())}>\n <div>\n <div style=${styleMap(this._headingStyles())}>${this.heading}</div>\n <div style=${styleMap(this._metaStyles())}>${reviewCountLabel(data.review_count)}</div>\n </div>\n <div style=${styleMap(this._ratingBadgeStyles())} aria-label=${`${formatAverage(data.average_rating)} out of 5`}>\n <span style=${styleMap({ fontSize: '20px', fontWeight: '700' })}>${formatAverage(data.average_rating)}</span>\n <span aria-hidden=\"true\">\u2605</span>\n </div>\n </div>\n <div style=${styleMap({ display: 'grid', gap: '10px' })}>\n ${data.reviews.map((review) => this._renderReview(review))}\n </div>\n </div>\n `;\n }\n\n private _renderReview(review: DisplayReview) {\n const date = createdAtLabel(review.created_at);\n return html`\n <article style=${styleMap(this._reviewStyles())}>\n <div style=${styleMap(this._reviewHeaderStyles())}>\n <div style=${styleMap({ fontWeight: '700' })}>${review.review_title || 'Review'}</div>\n <div aria-label=${`${review.rating} out of 5`} style=${styleMap(this._starsStyles())}>\n ${'\u2605'.repeat(Math.max(0, Math.min(5, Math.round(review.rating))))}\n </div>\n </div>\n <p style=${styleMap(this._bodyStyles())}>${review.review_body}</p>\n <div style=${styleMap(this._metaStyles())}>\n ${review.reviewer_display_name || 'Customer'}\n ${review.verified_purchase ? html`<span> \u00B7 Verified purchase</span>` : nothing}\n ${date ? html`<span> \u00B7 ${date}</span>` : nothing}\n </div>\n </article>\n `;\n }\n\n private _panelStyles(): Record<string, string> {\n return {\n boxSizing: 'border-box',\n border: '1px solid var(--sc-tile-border-color, rgba(148, 163, 184, 0.24))',\n borderRadius: '8px',\n background: 'var(--sc-tile-background, #ffffff)',\n padding: '14px',\n boxShadow: 'var(--sc-tile-shadow, 0 8px 24px rgba(15, 23, 42, 0.08))',\n };\n }\n\n private _headingStyles(): Record<string, string> {\n return {\n fontSize: '15px',\n lineHeight: '20px',\n fontWeight: '700',\n margin: '0',\n };\n }\n\n private _summaryStyles(): Record<string, string> {\n return {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: '12px',\n marginBottom: '12px',\n };\n }\n\n private _ratingBadgeStyles(): Record<string, string> {\n return {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '4px',\n color: 'var(--sc-accent, #2563eb)',\n whiteSpace: 'nowrap',\n };\n }\n\n private _reviewStyles(): Record<string, string> {\n return {\n borderTop: '1px solid var(--sc-tile-border-color, rgba(148, 163, 184, 0.2))',\n paddingTop: '10px',\n };\n }\n\n private _reviewHeaderStyles(): Record<string, string> {\n return {\n display: 'flex',\n alignItems: 'baseline',\n justifyContent: 'space-between',\n gap: '10px',\n fontSize: '14px',\n lineHeight: '20px',\n };\n }\n\n private _starsStyles(): Record<string, string> {\n return {\n color: 'var(--sc-accent, #2563eb)',\n fontSize: '13px',\n lineHeight: '18px',\n whiteSpace: 'nowrap',\n };\n }\n\n private _bodyStyles(): Record<string, string> {\n return {\n margin: '6px 0',\n fontSize: '14px',\n lineHeight: '20px',\n };\n }\n\n private _metaStyles(): Record<string, string> {\n return {\n color: 'var(--sc-muted-text, #64748b)',\n fontSize: '12px',\n lineHeight: '18px',\n };\n }\n\n private _stateStyles(): Record<string, string> {\n return {\n marginTop: '8px',\n color: 'var(--sc-muted-text, #64748b)',\n fontSize: '14px',\n lineHeight: '20px',\n };\n }\n}\n\nif (!customElements.get('syntro-reviews')) {\n customElements.define('syntro-reviews', SyntroReviews);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'syntro-reviews': SyntroReviews;\n }\n}\n", "/**\n * adaptive-reviews \u2014 Runtime manifest (flat shape)\n *\n * Exports the runtime descriptor consumed by the SDK's AppLoader. Importing\n * this module registers the <syntro-reviews> custom element as a side effect.\n * This is a lazy CDN adaptive (see `syntrologie.bundlingMode: cdn-only`), so\n * it must NOT import runtime-sdk internals \u2014 the flat shape here is wrapped\n * into a manifest by `cdn.ts` before self-registering.\n */\n\nimport './reviewDisplay.js';\nimport type { ReviewDisplayResponse, SyntroReviews } from './reviewDisplay.js';\n\nexport const REVIEWS_ADAPTIVE_APP_ID = 'adaptive-reviews';\nexport const REVIEWS_ADAPTIVE_WIDGET_ID = 'adaptive-reviews:reviews';\n\ntype ReviewLoader = (limit: number, productId?: string | null) => Promise<ReviewDisplayResponse>;\n\nfunction asPositiveLimit(value: unknown): number | undefined {\n const numeric =\n typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN;\n if (!Number.isFinite(numeric)) return undefined;\n return Math.max(1, Math.floor(numeric));\n}\n\nfunction applyReviewsConfig(el: SyntroReviews, config?: Record<string, unknown> | null): void {\n if (!config) return;\n\n if (typeof config.heading === 'string') {\n el.heading = config.heading;\n }\n\n const limit = asPositiveLimit(config.limit);\n if (limit !== undefined) {\n el.limit = limit;\n }\n\n if (typeof config.productId === 'string') {\n el.productId = config.productId;\n }\n\n if (typeof config.productIdSelector === 'string') {\n el.productIdSelector = config.productIdSelector;\n }\n\n if (typeof config.loadReviews === 'function') {\n el.loadReviews = config.loadReviews as ReviewLoader;\n }\n}\n\n/**\n * Mountable widget: receives the tile container + validated props, returns an\n * unmount fn. Same contract as the other adaptives' mountables.\n */\nexport const reviewsWidget = {\n mount(container: HTMLElement, config?: Record<string, unknown> | null) {\n const el = document.createElement('syntro-reviews') as SyntroReviews;\n applyReviewsConfig(el, config);\n container.appendChild(el);\n return () => el.remove();\n },\n\n update(container: HTMLElement, config?: Record<string, unknown> | null) {\n const el = container.querySelector('syntro-reviews') as SyntroReviews | null;\n if (el) applyReviewsConfig(el, config);\n },\n};\n\nexport const runtime = {\n id: REVIEWS_ADAPTIVE_APP_ID,\n version: '0.1.0',\n name: 'Reviews',\n description: 'Display imported and submitted customer reviews through the runtime SDK.',\n\n /** No DOM-mutation executors \u2014 this widget renders only. */\n executors: [],\n\n widgets: [\n {\n id: REVIEWS_ADAPTIVE_WIDGET_ID,\n component: reviewsWidget,\n metadata: {\n name: 'Reviews',\n description: 'Display customer reviews from the adaptive review endpoint.',\n icon: '\\u{1F4AC}',\n },\n },\n ],\n};\n\n// Back-compat alias for existing importers/tests.\nexport const reviewsAdaptiveRuntime = runtime;\n\nexport default runtime;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
export type ReviewSource = 'imported' | 'submitted' | string;
|
|
3
|
+
export interface DisplayReview {
|
|
4
|
+
product_id?: string | null;
|
|
5
|
+
product_url?: string | null;
|
|
6
|
+
rating: number;
|
|
7
|
+
review_title?: string | null;
|
|
8
|
+
review_body: string;
|
|
9
|
+
reviewer_display_name?: string | null;
|
|
10
|
+
verified_purchase?: boolean | null;
|
|
11
|
+
created_at?: string | null;
|
|
12
|
+
source: ReviewSource;
|
|
13
|
+
}
|
|
14
|
+
export interface ReviewDisplayResponse {
|
|
15
|
+
reviews: DisplayReview[];
|
|
16
|
+
review_count: number;
|
|
17
|
+
average_rating: number | null;
|
|
18
|
+
}
|
|
19
|
+
export type SyntroAuthedFetch = (path: string, init?: RequestInit) => Promise<Response>;
|
|
20
|
+
export type LoadReviews = (limit: number, productId?: string | null) => Promise<ReviewDisplayResponse>;
|
|
21
|
+
/**
|
|
22
|
+
* Host-page attribute a product page sets to declare which product the
|
|
23
|
+
* reviews widget should scope to. Paired with the widget's
|
|
24
|
+
* `productIdSelector` prop: the widget reads this attribute off the element
|
|
25
|
+
* that selector matches. Keeping it a shared constant avoids drift between
|
|
26
|
+
* the host page and the widget.
|
|
27
|
+
*/
|
|
28
|
+
export declare const PRODUCT_ID_ATTRIBUTE = "data-syntro-product-id";
|
|
29
|
+
export declare function fetchReviewDisplay(authedFetch: SyntroAuthedFetch, options?: {
|
|
30
|
+
limit?: number;
|
|
31
|
+
productId?: string | null;
|
|
32
|
+
}): Promise<ReviewDisplayResponse>;
|
|
33
|
+
export declare class SyntroReviews extends LitElement {
|
|
34
|
+
static properties: {
|
|
35
|
+
heading: {
|
|
36
|
+
type: StringConstructor;
|
|
37
|
+
};
|
|
38
|
+
limit: {
|
|
39
|
+
type: NumberConstructor;
|
|
40
|
+
};
|
|
41
|
+
productId: {
|
|
42
|
+
type: StringConstructor;
|
|
43
|
+
};
|
|
44
|
+
productIdSelector: {
|
|
45
|
+
type: StringConstructor;
|
|
46
|
+
};
|
|
47
|
+
loadReviews: {
|
|
48
|
+
attribute: boolean;
|
|
49
|
+
};
|
|
50
|
+
_isLoading: {
|
|
51
|
+
state: boolean;
|
|
52
|
+
};
|
|
53
|
+
_error: {
|
|
54
|
+
state: boolean;
|
|
55
|
+
};
|
|
56
|
+
_data: {
|
|
57
|
+
state: boolean;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
heading: string;
|
|
61
|
+
limit: number;
|
|
62
|
+
/** Explicit product to scope reviews to. Overridden by `productIdSelector` when that resolves. */
|
|
63
|
+
productId?: string;
|
|
64
|
+
/**
|
|
65
|
+
* CSS selector for a host-page element carrying `data-syntro-product-id`.
|
|
66
|
+
* When set and resolvable, the widget scopes reviews to that product — this
|
|
67
|
+
* is how one config drives per-product reviews across many product pages.
|
|
68
|
+
* When unset (or unresolved), reviews are workspace-wide.
|
|
69
|
+
*/
|
|
70
|
+
productIdSelector?: string;
|
|
71
|
+
loadReviews?: LoadReviews;
|
|
72
|
+
private _isLoading;
|
|
73
|
+
private _error;
|
|
74
|
+
private _data;
|
|
75
|
+
private _requestId;
|
|
76
|
+
private _lastProductId;
|
|
77
|
+
private _anchorObserver?;
|
|
78
|
+
private _observedAnchor;
|
|
79
|
+
createRenderRoot(): this;
|
|
80
|
+
connectedCallback(): void;
|
|
81
|
+
disconnectedCallback(): void;
|
|
82
|
+
updated(changed: Map<string, unknown>): void;
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the host-page element the `productIdSelector` points at. Guarded:
|
|
85
|
+
* an invalid selector must not throw and brick the widget — treat it as
|
|
86
|
+
* unresolved.
|
|
87
|
+
*/
|
|
88
|
+
private _resolveAnchorEl;
|
|
89
|
+
/**
|
|
90
|
+
* Resolve the product to scope reviews to. A `productIdSelector` element
|
|
91
|
+
* carrying `data-syntro-product-id` wins; otherwise fall back to the
|
|
92
|
+
* explicit `productId`; otherwise null.
|
|
93
|
+
*/
|
|
94
|
+
private _resolveProductId;
|
|
95
|
+
/**
|
|
96
|
+
* Watch the anchor's `data-syntro-product-id` so the widget re-scopes on SPA
|
|
97
|
+
* navigation. On a route like `/product/:slug`, the host reconciles the SAME
|
|
98
|
+
* anchor node and just mutates the attribute — nothing re-triggers a Lit
|
|
99
|
+
* prop change, so without this observer the widget would keep showing the
|
|
100
|
+
* previous product's reviews.
|
|
101
|
+
*/
|
|
102
|
+
private _syncAnchorObserver;
|
|
103
|
+
private _load;
|
|
104
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
105
|
+
private _renderFrame;
|
|
106
|
+
private _renderReviews;
|
|
107
|
+
private _renderReview;
|
|
108
|
+
private _panelStyles;
|
|
109
|
+
private _headingStyles;
|
|
110
|
+
private _summaryStyles;
|
|
111
|
+
private _ratingBadgeStyles;
|
|
112
|
+
private _reviewStyles;
|
|
113
|
+
private _reviewHeaderStyles;
|
|
114
|
+
private _starsStyles;
|
|
115
|
+
private _bodyStyles;
|
|
116
|
+
private _metaStyles;
|
|
117
|
+
private _stateStyles;
|
|
118
|
+
}
|
|
119
|
+
declare global {
|
|
120
|
+
interface HTMLElementTagNameMap {
|
|
121
|
+
'syntro-reviews': SyntroReviews;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=reviewDisplay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviewDisplay.d.ts","sourceRoot":"","sources":["../src/reviewDisplay.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adaptive-reviews — Runtime manifest (flat shape)
|
|
3
|
+
*
|
|
4
|
+
* Exports the runtime descriptor consumed by the SDK's AppLoader. Importing
|
|
5
|
+
* this module registers the <syntro-reviews> custom element as a side effect.
|
|
6
|
+
* This is a lazy CDN adaptive (see `syntrologie.bundlingMode: cdn-only`), so
|
|
7
|
+
* it must NOT import runtime-sdk internals — the flat shape here is wrapped
|
|
8
|
+
* into a manifest by `cdn.ts` before self-registering.
|
|
9
|
+
*/
|
|
10
|
+
import './reviewDisplay.js';
|
|
11
|
+
export declare const REVIEWS_ADAPTIVE_APP_ID = "adaptive-reviews";
|
|
12
|
+
export declare const REVIEWS_ADAPTIVE_WIDGET_ID = "adaptive-reviews:reviews";
|
|
13
|
+
/**
|
|
14
|
+
* Mountable widget: receives the tile container + validated props, returns an
|
|
15
|
+
* unmount fn. Same contract as the other adaptives' mountables.
|
|
16
|
+
*/
|
|
17
|
+
export declare const reviewsWidget: {
|
|
18
|
+
mount(container: HTMLElement, config?: Record<string, unknown> | null): () => void;
|
|
19
|
+
update(container: HTMLElement, config?: Record<string, unknown> | null): void;
|
|
20
|
+
};
|
|
21
|
+
export declare const runtime: {
|
|
22
|
+
id: string;
|
|
23
|
+
version: string;
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
/** No DOM-mutation executors — this widget renders only. */
|
|
27
|
+
executors: never[];
|
|
28
|
+
widgets: {
|
|
29
|
+
id: string;
|
|
30
|
+
component: {
|
|
31
|
+
mount(container: HTMLElement, config?: Record<string, unknown> | null): () => void;
|
|
32
|
+
update(container: HTMLElement, config?: Record<string, unknown> | null): void;
|
|
33
|
+
};
|
|
34
|
+
metadata: {
|
|
35
|
+
name: string;
|
|
36
|
+
description: string;
|
|
37
|
+
icon: string;
|
|
38
|
+
};
|
|
39
|
+
}[];
|
|
40
|
+
};
|
|
41
|
+
export declare const reviewsAdaptiveRuntime: {
|
|
42
|
+
id: string;
|
|
43
|
+
version: string;
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
/** No DOM-mutation executors — this widget renders only. */
|
|
47
|
+
executors: never[];
|
|
48
|
+
widgets: {
|
|
49
|
+
id: string;
|
|
50
|
+
component: {
|
|
51
|
+
mount(container: HTMLElement, config?: Record<string, unknown> | null): () => void;
|
|
52
|
+
update(container: HTMLElement, config?: Record<string, unknown> | null): void;
|
|
53
|
+
};
|
|
54
|
+
metadata: {
|
|
55
|
+
name: string;
|
|
56
|
+
description: string;
|
|
57
|
+
icon: string;
|
|
58
|
+
};
|
|
59
|
+
}[];
|
|
60
|
+
};
|
|
61
|
+
export default runtime;
|
|
62
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,oBAAoB,CAAC;AAG5B,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAC1D,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AAoCrE;;;GAGG;AACH,eAAO,MAAM,aAAa;qBACP,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;sBAOnD,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAIvE,CAAC;AAEF,eAAO,MAAM,OAAO;;;;;IAMlB,4DAA4D;;;;;6BAnB3C,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;8BAOnD,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;;;;;;;;CA0BvE,CAAC;AAGF,eAAO,MAAM,sBAAsB;;;;;IAjBjC,4DAA4D;;;;;6BAnB3C,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;8BAOnD,WAAW,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;;;;;;;;CA6B3B,CAAC;AAE9C,eAAe,OAAO,CAAC"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
REVIEWS_ADAPTIVE_APP_ID,
|
|
3
|
+
REVIEWS_ADAPTIVE_WIDGET_ID,
|
|
4
|
+
reviewsAdaptiveRuntime,
|
|
5
|
+
reviewsWidget,
|
|
6
|
+
runtime,
|
|
7
|
+
runtime_default
|
|
8
|
+
} from "./chunk-QU2QGSUW.js";
|
|
9
|
+
export {
|
|
10
|
+
REVIEWS_ADAPTIVE_APP_ID,
|
|
11
|
+
REVIEWS_ADAPTIVE_WIDGET_ID,
|
|
12
|
+
runtime_default as default,
|
|
13
|
+
reviewsAdaptiveRuntime,
|
|
14
|
+
reviewsWidget,
|
|
15
|
+
runtime
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=runtime.js.map
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adaptive-reviews — Zod schema + capabilities (source of truth for tile
|
|
3
|
+
* config validation and LLM authoring guidance).
|
|
4
|
+
*
|
|
5
|
+
* `generate-unified-schema.mjs::discoverAdaptives()` reads `tileWidgets` and
|
|
6
|
+
* `CAPABILITIES_DOCUMENTATION` from this package's `dist/schema.js` and folds
|
|
7
|
+
* them into the unified canvas-config schema — the same path every other
|
|
8
|
+
* adaptive uses. (These used to live hand-coded in the generator's
|
|
9
|
+
* BUILTIN_* blocks while reviews was a runtime-sdk built-in.)
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
export declare const reviewsPropsSchema: z.ZodObject<{
|
|
13
|
+
heading: z.ZodOptional<z.ZodString>;
|
|
14
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
productId: z.ZodOptional<z.ZodString>;
|
|
16
|
+
productIdSelector: z.ZodOptional<z.ZodString>;
|
|
17
|
+
}, "strict", z.ZodTypeAny, {
|
|
18
|
+
limit?: number | undefined;
|
|
19
|
+
productId?: string | undefined;
|
|
20
|
+
heading?: string | undefined;
|
|
21
|
+
productIdSelector?: string | undefined;
|
|
22
|
+
}, {
|
|
23
|
+
limit?: number | undefined;
|
|
24
|
+
productId?: string | undefined;
|
|
25
|
+
heading?: string | undefined;
|
|
26
|
+
productIdSelector?: string | undefined;
|
|
27
|
+
}>;
|
|
28
|
+
export declare const tileWidgets: {
|
|
29
|
+
widget: string;
|
|
30
|
+
defName: string;
|
|
31
|
+
propsSchema: z.ZodObject<{
|
|
32
|
+
heading: z.ZodOptional<z.ZodString>;
|
|
33
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
productId: z.ZodOptional<z.ZodString>;
|
|
35
|
+
productIdSelector: z.ZodOptional<z.ZodString>;
|
|
36
|
+
}, "strict", z.ZodTypeAny, {
|
|
37
|
+
limit?: number | undefined;
|
|
38
|
+
productId?: string | undefined;
|
|
39
|
+
heading?: string | undefined;
|
|
40
|
+
productIdSelector?: string | undefined;
|
|
41
|
+
}, {
|
|
42
|
+
limit?: number | undefined;
|
|
43
|
+
productId?: string | undefined;
|
|
44
|
+
heading?: string | undefined;
|
|
45
|
+
productIdSelector?: string | undefined;
|
|
46
|
+
}>;
|
|
47
|
+
}[];
|
|
48
|
+
export declare const CAPABILITIES_DOCUMENTATION: {
|
|
49
|
+
packageId: string;
|
|
50
|
+
plannerSummary: string;
|
|
51
|
+
description: string;
|
|
52
|
+
whenToUse: {
|
|
53
|
+
goal: string;
|
|
54
|
+
action: string;
|
|
55
|
+
}[];
|
|
56
|
+
conventions: {
|
|
57
|
+
name: string;
|
|
58
|
+
description: string;
|
|
59
|
+
}[];
|
|
60
|
+
events: never[];
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;EAqBpB,CAAC;AAEZ,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;GAMvB,CAAC;AAMF,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;CAmCtC,CAAC"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/schema.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var reviewsPropsSchema = z.object({
|
|
4
|
+
heading: z.string().optional().describe("Section title shown above the review cards."),
|
|
5
|
+
limit: z.number().int().min(1).max(50).optional().describe("Maximum number of reviews to display."),
|
|
6
|
+
productId: z.string().optional().describe("Explicit product id to scope reviews to. Overridden by productIdSelector."),
|
|
7
|
+
productIdSelector: z.string().optional().describe(
|
|
8
|
+
"CSS selector for a host-page element carrying data-syntro-product-id. When it resolves, reviews are scoped to that product \u2014 lets one config drive per-product reviews across many product pages. Omit for workspace-wide reviews."
|
|
9
|
+
)
|
|
10
|
+
}).strict();
|
|
11
|
+
var tileWidgets = [
|
|
12
|
+
{
|
|
13
|
+
widget: "adaptive-reviews:reviews",
|
|
14
|
+
defName: "reviewsAdaptiveProps",
|
|
15
|
+
propsSchema: reviewsPropsSchema
|
|
16
|
+
}
|
|
17
|
+
];
|
|
18
|
+
var CAPABILITIES_DOCUMENTATION = {
|
|
19
|
+
packageId: "adaptive-reviews",
|
|
20
|
+
plannerSummary: "Customer review display for imported and submitted reviews. Reach for this when the strategy needs social proof or review content from the reviews system.",
|
|
21
|
+
description: "Displays imported and submitted customer reviews from the reviews system.",
|
|
22
|
+
whenToUse: [
|
|
23
|
+
{
|
|
24
|
+
goal: "Show review content on a customer site",
|
|
25
|
+
action: 'Add a tile with widget: "adaptive-reviews:reviews" and configure heading and limit props.'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
goal: "Surface social proof from imported review rows",
|
|
29
|
+
action: "Use the existing reviews import flow, then include the reviews adaptive tile in the published SDK config."
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
conventions: [
|
|
33
|
+
{
|
|
34
|
+
name: "Review data source",
|
|
35
|
+
description: "Review rows are managed by the reviews system and read from GET /api/adaptive/reviews; do not place review row data in SDK config."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "Display props",
|
|
39
|
+
description: "Use heading for the section title and limit for the maximum number of reviews to display."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "Per-product scoping",
|
|
43
|
+
description: "Set productIdSelector to a host-page selector whose element carries data-syntro-product-id to scope reviews to the current product; omit for workspace-wide reviews."
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
events: []
|
|
47
|
+
};
|
|
48
|
+
export {
|
|
49
|
+
CAPABILITIES_DOCUMENTATION,
|
|
50
|
+
reviewsPropsSchema,
|
|
51
|
+
tileWidgets
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/schema.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * adaptive-reviews \u2014 Zod schema + capabilities (source of truth for tile\n * config validation and LLM authoring guidance).\n *\n * `generate-unified-schema.mjs::discoverAdaptives()` reads `tileWidgets` and\n * `CAPABILITIES_DOCUMENTATION` from this package's `dist/schema.js` and folds\n * them into the unified canvas-config schema \u2014 the same path every other\n * adaptive uses. (These used to live hand-coded in the generator's\n * BUILTIN_* blocks while reviews was a runtime-sdk built-in.)\n */\n\nimport { z } from 'zod';\n\nexport const reviewsPropsSchema = z\n .object({\n heading: z.string().optional().describe('Section title shown above the review cards.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe('Maximum number of reviews to display.'),\n productId: z\n .string()\n .optional()\n .describe('Explicit product id to scope reviews to. Overridden by productIdSelector.'),\n productIdSelector: z\n .string()\n .optional()\n .describe(\n 'CSS selector for a host-page element carrying data-syntro-product-id. When it resolves, reviews are scoped to that product \u2014 lets one config drive per-product reviews across many product pages. Omit for workspace-wide reviews.'\n ),\n })\n .strict();\n\nexport const tileWidgets = [\n {\n widget: 'adaptive-reviews:reviews',\n defName: 'reviewsAdaptiveProps',\n propsSchema: reviewsPropsSchema,\n },\n];\n\n// FLAT shape (like every other adaptive's schema.ts) \u2014 discoverAdaptives keys\n// this under the package dir name itself (`docs[dir] = capsDocs`), so exporting\n// it keyed by 'adaptive-reviews' here would double-nest it and blank the\n// planner's reviews guidance.\nexport const CAPABILITIES_DOCUMENTATION = {\n packageId: 'adaptive-reviews',\n plannerSummary:\n 'Customer review display for imported and submitted reviews. Reach for this when the strategy needs social proof or review content from the reviews system.',\n description: 'Displays imported and submitted customer reviews from the reviews system.',\n whenToUse: [\n {\n goal: 'Show review content on a customer site',\n action:\n 'Add a tile with widget: \"adaptive-reviews:reviews\" and configure heading and limit props.',\n },\n {\n goal: 'Surface social proof from imported review rows',\n action:\n 'Use the existing reviews import flow, then include the reviews adaptive tile in the published SDK config.',\n },\n ],\n conventions: [\n {\n name: 'Review data source',\n description:\n 'Review rows are managed by the reviews system and read from GET /api/adaptive/reviews; do not place review row data in SDK config.',\n },\n {\n name: 'Display props',\n description:\n 'Use heading for the section title and limit for the maximum number of reviews to display.',\n },\n {\n name: 'Per-product scoping',\n description:\n 'Set productIdSelector to a host-page selector whose element carries data-syntro-product-id to scope reviews to the current product; omit for workspace-wide reviews.',\n },\n ],\n events: [],\n};\n"],
|
|
5
|
+
"mappings": ";AAWA,SAAS,SAAS;AAEX,IAAM,qBAAqB,EAC/B,OAAO;AAAA,EACN,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,6CAA6C;AAAA,EACrF,OAAO,EACJ,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,IAAI,EAAE,EACN,SAAS,EACT,SAAS,uCAAuC;AAAA,EACnD,WAAW,EACR,OAAO,EACP,SAAS,EACT,SAAS,2EAA2E;AAAA,EACvF,mBAAmB,EAChB,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC,EACA,OAAO;AAEH,IAAM,cAAc;AAAA,EACzB;AAAA,IACE,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AACF;AAMO,IAAM,6BAA6B;AAAA,EACxC,WAAW;AAAA,EACX,gBACE;AAAA,EACF,aAAa;AAAA,EACb,WAAW;AAAA,IACT;AAAA,MACE,MAAM;AAAA,MACN,QACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,QACE;AAAA,IACJ;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX;AAAA,MACE,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,EACF;AAAA,EACA,QAAQ,CAAC;AACX;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@syntrologie/adapt-reviews",
|
|
3
|
+
"version": "2.8.0-canary.414",
|
|
4
|
+
"description": "Adaptive Reviews — display tile for imported and submitted customer reviews, scoped per product.",
|
|
5
|
+
"license": "Proprietary",
|
|
6
|
+
"private": false,
|
|
7
|
+
"author": "Syntrologie <eng@syntrologie.com>",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/SyntropyForge/amazing-demos.git",
|
|
11
|
+
"directory": "packages/adaptives/adaptive-reviews"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"syntrologie": {
|
|
17
|
+
"bundlingMode": "cdn-only"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"exports": {
|
|
21
|
+
"./runtime": {
|
|
22
|
+
"types": "./dist/runtime.d.ts",
|
|
23
|
+
"import": "./dist/runtime.js"
|
|
24
|
+
},
|
|
25
|
+
"./schema": {
|
|
26
|
+
"types": "./dist/schema.d.ts",
|
|
27
|
+
"import": "./dist/schema.js"
|
|
28
|
+
},
|
|
29
|
+
"./cdn": {
|
|
30
|
+
"types": "./dist/cdn.d.ts",
|
|
31
|
+
"import": "./dist/cdn.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc --emitDeclarationOnly && node ../scripts/build-lib.mjs",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"clean": "rm -rf dist",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"lint": "biome check ."
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"lit": "^3.0.0",
|
|
47
|
+
"zod": "^3.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@open-wc/testing-helpers": "3.0.1",
|
|
51
|
+
"@syntrologie/sdk-contracts": "*",
|
|
52
|
+
"jsdom": "26.1.0",
|
|
53
|
+
"lit": "3.3.2",
|
|
54
|
+
"typescript": "5.9.3",
|
|
55
|
+
"vitest": "4.0.18",
|
|
56
|
+
"zod": "3.25.76"
|
|
57
|
+
}
|
|
58
|
+
}
|