@syntrologie/adapt-product 2.41.3 → 2.43.0
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.js +1 -1
- package/dist/{chunk-A6OGE4OG.js → chunk-6DWXPP5X.js} +212 -24
- package/dist/chunk-6DWXPP5X.js.map +7 -0
- package/dist/decision/card.d.ts +24 -2
- package/dist/decision/card.d.ts.map +1 -1
- package/dist/runtime.js +1 -1
- package/dist/widgets/ProductCardLit.d.ts.map +1 -1
- package/dist/widgets/VariantPanelLit.d.ts +13 -0
- package/dist/widgets/VariantPanelLit.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-A6OGE4OG.js.map +0 -7
package/dist/cdn.js
CHANGED
|
@@ -21,6 +21,21 @@ function n(n2) {
|
|
|
21
21
|
// ../../sdk-contracts/dist/canvas-context.js
|
|
22
22
|
var canvasRuntimeContext = n("syntrologie:canvas-runtime");
|
|
23
23
|
|
|
24
|
+
// ../../sdk-contracts/dist/detector-events.js
|
|
25
|
+
var DETECTOR_EVENT_NAMES = [
|
|
26
|
+
"ui.hover",
|
|
27
|
+
"ui.idle",
|
|
28
|
+
"ui.hesitate",
|
|
29
|
+
"ui.rage_click",
|
|
30
|
+
"ui.scroll_thrash",
|
|
31
|
+
"ui.focus_bounce"
|
|
32
|
+
];
|
|
33
|
+
var CANONICAL_BUS_EVENT_NAMES = [
|
|
34
|
+
...DETECTOR_EVENT_NAMES,
|
|
35
|
+
"nav.section_viewed",
|
|
36
|
+
"nav.scroll_depth"
|
|
37
|
+
];
|
|
38
|
+
|
|
24
39
|
// ../../sdk-contracts/dist/dive-deeper.js
|
|
25
40
|
var DIVE_DEEPER_EVENT = "syntro:chat:dive-deeper";
|
|
26
41
|
function dispatchDiveDeeper(detail) {
|
|
@@ -43,7 +58,103 @@ function stripMountPlumbing(config) {
|
|
|
43
58
|
}
|
|
44
59
|
|
|
45
60
|
// ../../sdk-contracts/dist/routes.js
|
|
61
|
+
var RESERVED_BYTES = /* @__PURE__ */ new Set([
|
|
62
|
+
33,
|
|
63
|
+
// !
|
|
64
|
+
35,
|
|
65
|
+
// #
|
|
66
|
+
36,
|
|
67
|
+
// $
|
|
68
|
+
38,
|
|
69
|
+
// &
|
|
70
|
+
39,
|
|
71
|
+
// '
|
|
72
|
+
40,
|
|
73
|
+
// (
|
|
74
|
+
41,
|
|
75
|
+
// )
|
|
76
|
+
42,
|
|
77
|
+
// *
|
|
78
|
+
43,
|
|
79
|
+
// +
|
|
80
|
+
44,
|
|
81
|
+
// ,
|
|
82
|
+
47,
|
|
83
|
+
// /
|
|
84
|
+
58,
|
|
85
|
+
// :
|
|
86
|
+
59,
|
|
87
|
+
// ;
|
|
88
|
+
61,
|
|
89
|
+
// =
|
|
90
|
+
63,
|
|
91
|
+
// ?
|
|
92
|
+
64,
|
|
93
|
+
// @
|
|
94
|
+
91,
|
|
95
|
+
// [
|
|
96
|
+
93
|
|
97
|
+
// ]
|
|
98
|
+
]);
|
|
46
99
|
var utf8Decoder = new TextDecoder("utf-8", { fatal: false });
|
|
100
|
+
function decodeUnreservedOnly(input) {
|
|
101
|
+
let out = "";
|
|
102
|
+
let pending2 = [];
|
|
103
|
+
const flushPending = () => {
|
|
104
|
+
if (pending2.length === 0)
|
|
105
|
+
return;
|
|
106
|
+
const bytes = new Uint8Array(pending2);
|
|
107
|
+
out += utf8Decoder.decode(bytes);
|
|
108
|
+
pending2 = [];
|
|
109
|
+
};
|
|
110
|
+
let i2 = 0;
|
|
111
|
+
while (i2 < input.length) {
|
|
112
|
+
const ch = input[i2];
|
|
113
|
+
if (ch === "%" && i2 + 2 < input.length && isHex(input[i2 + 1]) && isHex(input[i2 + 2])) {
|
|
114
|
+
const byte = parseInt(input.slice(i2 + 1, i2 + 3), 16);
|
|
115
|
+
if (RESERVED_BYTES.has(byte)) {
|
|
116
|
+
flushPending();
|
|
117
|
+
out += `%${input.slice(i2 + 1, i2 + 3).toUpperCase()}`;
|
|
118
|
+
i2 += 3;
|
|
119
|
+
} else {
|
|
120
|
+
pending2.push(byte);
|
|
121
|
+
i2 += 3;
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
flushPending();
|
|
125
|
+
out += ch;
|
|
126
|
+
i2 += 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
flushPending();
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function isHex(c2) {
|
|
133
|
+
return c2 >= "0" && c2 <= "9" || c2 >= "a" && c2 <= "f" || c2 >= "A" && c2 <= "F";
|
|
134
|
+
}
|
|
135
|
+
function stripQueryAndHash(s4) {
|
|
136
|
+
const q = s4.indexOf("?");
|
|
137
|
+
if (q !== -1)
|
|
138
|
+
s4 = s4.slice(0, q);
|
|
139
|
+
const h = s4.indexOf("#");
|
|
140
|
+
if (h !== -1)
|
|
141
|
+
s4 = s4.slice(0, h);
|
|
142
|
+
return s4;
|
|
143
|
+
}
|
|
144
|
+
function normalizeRoute(path) {
|
|
145
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
146
|
+
throw new TypeError("normalizeRoute: input must be a non-empty string");
|
|
147
|
+
}
|
|
148
|
+
if (!path.startsWith("/")) {
|
|
149
|
+
throw new TypeError(`normalizeRoute: input must be absolute (start with '/'); got ${JSON.stringify(path)}`);
|
|
150
|
+
}
|
|
151
|
+
let s4 = stripQueryAndHash(path);
|
|
152
|
+
s4 = decodeUnreservedOnly(s4);
|
|
153
|
+
s4 = s4.replace(/\/+/g, "/");
|
|
154
|
+
if (s4.length > 1 && s4.endsWith("/"))
|
|
155
|
+
s4 = s4.slice(0, -1);
|
|
156
|
+
return s4;
|
|
157
|
+
}
|
|
47
158
|
|
|
48
159
|
// ../../sdk-contracts/dist/schemas.js
|
|
49
160
|
import { z } from "zod";
|
|
@@ -52,7 +163,26 @@ var AnchorIdZ = z.object({
|
|
|
52
163
|
selector: z.string().regex(NO_CSS_BREAKOUT_PATTERN, {
|
|
53
164
|
message: 'selector must not contain "{" or "}" \u2014 not valid CSS selector syntax, and content:hideBySelector injects this value directly into a <style> element where these characters would break out of the generated rule.'
|
|
54
165
|
}).describe("CSS selector for the target element"),
|
|
55
|
-
route: z.union([z.string(), z.array(z.string())]).
|
|
166
|
+
route: z.union([z.string(), z.array(z.string())]).superRefine((value, ctx) => {
|
|
167
|
+
for (const route of Array.isArray(value) ? value : [value]) {
|
|
168
|
+
let canonical;
|
|
169
|
+
try {
|
|
170
|
+
canonical = normalizeRoute(route);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
ctx.addIssue({
|
|
173
|
+
code: z.ZodIssueCode.custom,
|
|
174
|
+
message: `route must be an absolute path starting with "/" (got ${JSON.stringify(route)}): ${err instanceof Error ? err.message : String(err)}`
|
|
175
|
+
});
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (canonical !== route) {
|
|
179
|
+
ctx.addIssue({
|
|
180
|
+
code: z.ZodIssueCode.custom,
|
|
181
|
+
message: `route ${JSON.stringify(route)} is not canonical \u2014 use ${JSON.stringify(canonical)} (this must match what the backend's RouteCanonicalityCheck accepts)`
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}).describe("URL path(s) where this element exists")
|
|
56
186
|
}).strict().describe("DOM element target. selector = CSS selector, route = URL path(s) where the element exists.");
|
|
57
187
|
var AuthoringFieldsZ = {
|
|
58
188
|
id: z.string().optional().describe('Stable action identifier (e.g. "act_3db6a14d2ab0").'),
|
|
@@ -4248,6 +4378,14 @@ resolvePrice_fn = async function(productId, bind) {
|
|
|
4248
4378
|
};
|
|
4249
4379
|
|
|
4250
4380
|
// src/decision/card.ts
|
|
4381
|
+
var VARIANT_ERROR_COPY = {
|
|
4382
|
+
retryable: "Couldn't load options right now.",
|
|
4383
|
+
terminal: "Options aren't available for this item right now."
|
|
4384
|
+
};
|
|
4385
|
+
var NON_RETRYABLE_VARIANT_ERROR_NAME = "ProductNotFoundError";
|
|
4386
|
+
function isRetryableVariantError(errorName) {
|
|
4387
|
+
return errorName !== NON_RETRYABLE_VARIANT_ERROR_NAME;
|
|
4388
|
+
}
|
|
4251
4389
|
function createInitialCardState(generation = 0) {
|
|
4252
4390
|
return {
|
|
4253
4391
|
generation,
|
|
@@ -4270,18 +4408,23 @@ function selectCardView(state) {
|
|
|
4270
4408
|
mode: "add",
|
|
4271
4409
|
disabled: state.connection !== "connected" || state.commerce !== "available" || state.product.status !== "valid"
|
|
4272
4410
|
};
|
|
4273
|
-
let panel = { status: "hidden", payload: null, error: null };
|
|
4411
|
+
let panel = { status: "hidden", payload: null, error: null, retryable: false };
|
|
4274
4412
|
if (state.face === "back") {
|
|
4275
4413
|
switch (state.variants.status) {
|
|
4276
4414
|
case "ready":
|
|
4277
|
-
panel = { status: "ready", payload: state.variants.payload, error: null };
|
|
4415
|
+
panel = { status: "ready", payload: state.variants.payload, error: null, retryable: false };
|
|
4278
4416
|
break;
|
|
4279
4417
|
case "error":
|
|
4280
|
-
panel = {
|
|
4418
|
+
panel = {
|
|
4419
|
+
status: "error",
|
|
4420
|
+
payload: null,
|
|
4421
|
+
error: state.variants.retryable ? VARIANT_ERROR_COPY.retryable : VARIANT_ERROR_COPY.terminal,
|
|
4422
|
+
retryable: state.variants.retryable
|
|
4423
|
+
};
|
|
4281
4424
|
break;
|
|
4282
4425
|
case "idle":
|
|
4283
4426
|
case "loading":
|
|
4284
|
-
panel = { status: "loading", payload: null, error: null };
|
|
4427
|
+
panel = { status: "loading", payload: null, error: null, retryable: false };
|
|
4285
4428
|
break;
|
|
4286
4429
|
}
|
|
4287
4430
|
}
|
|
@@ -4300,7 +4443,7 @@ function replaceProps(state, event) {
|
|
|
4300
4443
|
const sameProduct = event.valid && event.productId !== null && state.product.status === "valid" && state.product.id === event.productId;
|
|
4301
4444
|
if (sameProduct && event.productId !== null) {
|
|
4302
4445
|
const effects2 = [{ type: "bind-product", productId: event.productId }];
|
|
4303
|
-
if (state.variants.status === "idle") {
|
|
4446
|
+
if (event.commerceCapable && state.variants.status === "idle") {
|
|
4304
4447
|
effects2.push({
|
|
4305
4448
|
type: "lookup-variants",
|
|
4306
4449
|
generation: state.generation,
|
|
@@ -4322,15 +4465,15 @@ function replaceProps(state, event) {
|
|
|
4322
4465
|
effects
|
|
4323
4466
|
};
|
|
4324
4467
|
}
|
|
4325
|
-
effects.push(
|
|
4326
|
-
|
|
4327
|
-
{ type: "lookup-variants", generation, productId: event.productId }
|
|
4328
|
-
|
|
4329
|
-
);
|
|
4468
|
+
effects.push({ type: "bind-product", productId: event.productId });
|
|
4469
|
+
if (event.commerceCapable) {
|
|
4470
|
+
effects.push({ type: "lookup-variants", generation, productId: event.productId });
|
|
4471
|
+
}
|
|
4472
|
+
effects.push({ type: "report-render-health" });
|
|
4330
4473
|
return {
|
|
4331
4474
|
state: {
|
|
4332
4475
|
...next,
|
|
4333
|
-
product: { status: "valid", id: event.productId }
|
|
4476
|
+
product: { status: "valid", id: event.productId, commerceCapable: event.commerceCapable }
|
|
4334
4477
|
},
|
|
4335
4478
|
effects
|
|
4336
4479
|
};
|
|
@@ -4339,7 +4482,12 @@ function publish(name, props) {
|
|
|
4339
4482
|
return { type: "publish-event", name, props };
|
|
4340
4483
|
}
|
|
4341
4484
|
function activatePrimary(state, event) {
|
|
4342
|
-
if (state.connection !== "connected" || state.commerce !== "available" || state.product.status !== "valid" ||
|
|
4485
|
+
if (state.connection !== "connected" || state.commerce !== "available" || state.product.status !== "valid" || // Defense in depth: the widget only wires this event to an
|
|
4486
|
+
// actionId==='add_to_cart' CTA click (a non-commerce/booking product's
|
|
4487
|
+
// CTA is a plain navigating <a>, never dispatched here) — but the state
|
|
4488
|
+
// machine itself must also refuse to start the Woo/Shopify variant-
|
|
4489
|
+
// lookup flow for a product that has none.
|
|
4490
|
+
!state.product.commerceCapable || state.face !== "front") {
|
|
4343
4491
|
return unchanged2(state);
|
|
4344
4492
|
}
|
|
4345
4493
|
const productId = state.product.id;
|
|
@@ -4490,7 +4638,11 @@ function transitionCard(state, event) {
|
|
|
4490
4638
|
});
|
|
4491
4639
|
const next = {
|
|
4492
4640
|
...state,
|
|
4493
|
-
variants: {
|
|
4641
|
+
variants: {
|
|
4642
|
+
status: "error",
|
|
4643
|
+
message: event.message,
|
|
4644
|
+
retryable: isRetryableVariantError(event.errorName)
|
|
4645
|
+
}
|
|
4494
4646
|
};
|
|
4495
4647
|
if (state.activation.status !== "pending") {
|
|
4496
4648
|
return { state: next, effects: [failureEffect] };
|
|
@@ -4524,7 +4676,10 @@ function transitionCard(state, event) {
|
|
|
4524
4676
|
case "add-failed":
|
|
4525
4677
|
return applyAddResult(state, event);
|
|
4526
4678
|
case "retry-activated":
|
|
4527
|
-
if (state.product.status !== "valid" || state.commerce !== "available" || state.variants.status !== "error"
|
|
4679
|
+
if (state.product.status !== "valid" || state.commerce !== "available" || state.variants.status !== "error" || // The panel never OFFERS a retry affordance when the failure isn't
|
|
4680
|
+
// retryable (see selectCardView), but a stale/duplicate dispatch of
|
|
4681
|
+
// this event must still be a no-op — it can never succeed.
|
|
4682
|
+
!state.variants.retryable) {
|
|
4528
4683
|
return unchanged2(state);
|
|
4529
4684
|
}
|
|
4530
4685
|
return {
|
|
@@ -4571,7 +4726,12 @@ function transitionCard(state, event) {
|
|
|
4571
4726
|
state: { ...state, generation, connection: "connected" },
|
|
4572
4727
|
effects: state.product.status === "valid" && state.commerce === "available" ? [
|
|
4573
4728
|
{ type: "bind-product", productId: state.product.id },
|
|
4574
|
-
|
|
4729
|
+
// Same commerce gate as replaceProps — a booking-fulfilment
|
|
4730
|
+
// product never re-fires the Woo/Shopify variant lookup,
|
|
4731
|
+
// including on the reconnect prefetch.
|
|
4732
|
+
...state.product.commerceCapable ? [
|
|
4733
|
+
{ type: "lookup-variants", generation, productId: state.product.id }
|
|
4734
|
+
] : [],
|
|
4575
4735
|
{ type: "report-render-health" }
|
|
4576
4736
|
] : []
|
|
4577
4737
|
};
|
|
@@ -5241,7 +5401,17 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
|
|
|
5241
5401
|
* used as the fallback when the selected variant carries no featured_image. */
|
|
5242
5402
|
this.productImage = null;
|
|
5243
5403
|
this.state = "idle";
|
|
5404
|
+
/** Already sanitized, visitor-safe copy by the time it reaches this
|
|
5405
|
+
* component — the decision core (`card.ts`'s `selectCardView`) NEVER
|
|
5406
|
+
* forwards the raw platform error text (which may name an internal
|
|
5407
|
+
* slug/handle, e.g. `WooCommerce product not found for slug "botox"`).
|
|
5408
|
+
* This component's only job is to render whatever generic string it's
|
|
5409
|
+
* given; it must never be handed a raw message to begin with. */
|
|
5244
5410
|
this.error = null;
|
|
5411
|
+
/** Whether "Try again" is offered — false for a structurally dead-end
|
|
5412
|
+
* failure (retrying the identical query can never succeed) so the panel
|
|
5413
|
+
* never invites a retry that's already known to be pointless. */
|
|
5414
|
+
this.retryable = true;
|
|
5245
5415
|
/** @internal */
|
|
5246
5416
|
this._selection = {};
|
|
5247
5417
|
/** @internal */
|
|
@@ -5335,8 +5505,10 @@ var _VariantPanelLit = class _VariantPanelLit extends LitElement7 {
|
|
|
5335
5505
|
if (this.state === "error") {
|
|
5336
5506
|
return html7`
|
|
5337
5507
|
<div class="svp-error-block" data-error-block>
|
|
5338
|
-
<div
|
|
5339
|
-
|
|
5508
|
+
<div>${this.error ?? "Couldn't load options."}</div>
|
|
5509
|
+
${this.retryable ? html7`<button class="svp-retry-btn" data-retry-button @click=${this._onRetry}>
|
|
5510
|
+
Try again
|
|
5511
|
+
</button>` : nothing3}
|
|
5340
5512
|
</div>
|
|
5341
5513
|
`;
|
|
5342
5514
|
}
|
|
@@ -5583,6 +5755,7 @@ _VariantPanelLit.properties = {
|
|
|
5583
5755
|
productImage: { attribute: false },
|
|
5584
5756
|
state: { type: String },
|
|
5585
5757
|
error: { type: String },
|
|
5758
|
+
retryable: { type: Boolean },
|
|
5586
5759
|
_selection: { state: true },
|
|
5587
5760
|
_touched: { state: true },
|
|
5588
5761
|
_expandedAxes: { state: true },
|
|
@@ -5821,6 +5994,7 @@ var ProductCardLit = class extends LitElement8 {
|
|
|
5821
5994
|
.productImage=${product.image ? { src: product.image.src, alt: product.image.alt } : null}
|
|
5822
5995
|
.state=${view.panel.status === "error" ? "error" : view.panel.status === "ready" ? "idle" : "loading"}
|
|
5823
5996
|
.error=${view.panel.error}
|
|
5997
|
+
.retryable=${view.panel.retryable}
|
|
5824
5998
|
@variant-panel-back=${this._onVariantBack}
|
|
5825
5999
|
@variant-panel-add=${this._onVariantAdd}
|
|
5826
6000
|
@variant-panel-retry=${this._onVariantRetry}
|
|
@@ -5961,7 +6135,14 @@ readProductObservation_fn = function() {
|
|
|
5961
6135
|
const rawProductId = this.props?.product?.id;
|
|
5962
6136
|
return {
|
|
5963
6137
|
productId: parsed?.success ? parsed.data.product.id : typeof rawProductId === "string" ? rawProductId : null,
|
|
5964
|
-
valid: parsed?.success === true
|
|
6138
|
+
valid: parsed?.success === true,
|
|
6139
|
+
// Does this product have a REAL Add-to-cart action? A booking-
|
|
6140
|
+
// fulfilment product (server-hydrated with a single "Book a consult"
|
|
6141
|
+
// href CTA — see product_hydration.build_product_from_hit) has none,
|
|
6142
|
+
// and its platform variant-lookup endpoint has nothing to find — the
|
|
6143
|
+
// decision core (card.ts) uses this to skip the Woo/Shopify variant
|
|
6144
|
+
// prefetch entirely for it, never just to disable a button.
|
|
6145
|
+
commerceCapable: parsed?.success === true && parsed.data.product.ctas.some((cta) => cta.actionId === "add_to_cart")
|
|
5965
6146
|
};
|
|
5966
6147
|
};
|
|
5967
6148
|
syncDecisionInputsBeforeIntent_fn = function() {
|
|
@@ -5971,7 +6152,7 @@ syncDecisionInputsBeforeIntent_fn = function() {
|
|
|
5971
6152
|
});
|
|
5972
6153
|
const observation = __privateMethod(this, _ProductCardLit_instances, readProductObservation_fn).call(this);
|
|
5973
6154
|
const product = this._decisionState.product;
|
|
5974
|
-
const isCurrent = observation.valid === (product.status === "valid") && observation.productId === product.id;
|
|
6155
|
+
const isCurrent = observation.valid === (product.status === "valid") && observation.productId === product.id && (product.status !== "valid" || observation.commerceCapable === product.commerceCapable);
|
|
5975
6156
|
if (!isCurrent) __privateMethod(this, _ProductCardLit_instances, dispatchCard_fn).call(this, { type: "props-replaced", ...observation });
|
|
5976
6157
|
};
|
|
5977
6158
|
ProductCardLit.properties = {
|
|
@@ -6398,9 +6579,16 @@ function renderProductCardInner(product, density, resolved, visibleFacts, ctas,
|
|
|
6398
6579
|
style=${styleMap(ctaStyles)}
|
|
6399
6580
|
@click=${disabled2 ? (e2) => {
|
|
6400
6581
|
e2.preventDefault();
|
|
6401
|
-
} :
|
|
6402
|
-
|
|
6403
|
-
|
|
6582
|
+
} : (
|
|
6583
|
+
// The commerce (Add-to-cart / open-picker) intent wires ONLY to
|
|
6584
|
+
// an actual add_to_cart CTA. A plain navigating CTA — e.g. a
|
|
6585
|
+
// booking-fulfilment product's "Book a consult" href, which
|
|
6586
|
+
// sits at i===0 too — must fall through to its own <a href>
|
|
6587
|
+
// and never enter the Woo/Shopify variant-lookup flow.
|
|
6588
|
+
i2 === 0 && c2.actionId === "add_to_cart" && onPrimaryCtaClick ? onPrimaryCtaClick : c2.actionId === "add_to_cart" ? (e2) => {
|
|
6589
|
+
e2.preventDefault();
|
|
6590
|
+
} : void 0
|
|
6591
|
+
)}
|
|
6404
6592
|
>${// Secondary "Product Page" link gets a trailing chevron (panel's
|
|
6405
6593
|
// quiet-secondary affordance) unless the author already added one.
|
|
6406
6594
|
i2 !== 0 && !/[›>→]\s*$/.test(ctaLabel) ? `${ctaLabel} \u203A` : ctaLabel}</a
|
|
@@ -11739,4 +11927,4 @@ export {
|
|
|
11739
11927
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
11740
11928
|
*)
|
|
11741
11929
|
*/
|
|
11742
|
-
//# sourceMappingURL=chunk-
|
|
11930
|
+
//# sourceMappingURL=chunk-6DWXPP5X.js.map
|