@brytdesigns/shopify-product-info-components 1.1.10 → 3.0.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/main/components/index.js +5 -12
- package/dist/main/components/product-info.js +55 -36
- package/dist/main/components/product-info.js.map +1 -1
- package/dist/main/components/product-option-group.js +53 -66
- package/dist/main/components/product-option-group.js.map +1 -1
- package/dist/main/components/product-options.js +10 -9
- package/dist/main/components/product-options.js.map +1 -1
- package/dist/main/components/product-variant-input.js +49 -46
- package/dist/main/components/product-variant-input.js.map +1 -1
- package/dist/main/components/product-variant-render.js +28 -34
- package/dist/main/components/product-variant-render.js.map +1 -1
- package/dist/main/hooks/index.js +3 -4
- package/dist/main/hooks/useProduct.js +40 -61
- package/dist/main/hooks/useProduct.js.map +1 -1
- package/dist/main/hooks/useProductOptions.js +43 -64
- package/dist/main/hooks/useProductOptions.js.map +1 -1
- package/dist/main/index.d.ts +47 -27
- package/dist/main/index.js +30 -41
- package/dist/main/index.js.map +1 -1
- package/package.json +8 -8
- package/dist/esm/index.js +0 -385
- package/dist/esm/index.js.map +0 -1
- package/dist/main/components/index.js.map +0 -1
- package/dist/main/hooks/index.js.map +0 -1
- package/dist/main/types.js +0 -3
- package/dist/main/types.js.map +0 -1
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
export { productOptions_js as ProductOptions };
|
|
7
|
-
import * as productOptionGroup_js from './product-option-group.js';
|
|
8
|
-
export { productOptionGroup_js as ProductOptionGroup };
|
|
9
|
-
import * as productVariantInput_js from './product-variant-input.js';
|
|
10
|
-
export { productVariantInput_js as ProductVariantInput };
|
|
11
|
-
//# sourceMappingURL=index.js.map
|
|
12
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import "./product-info.js";
|
|
2
|
+
import "./product-option-group.js";
|
|
3
|
+
import "./product-options.js";
|
|
4
|
+
import "./product-variant-input.js";
|
|
5
|
+
import "./product-variant-render.js";
|
|
@@ -1,39 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
1
|
+
import { provideProductContext } from "../hooks/useProduct.js";
|
|
2
|
+
import "../hooks/index.js";
|
|
3
|
+
import { announce, createGuardedEffect } from "@brytdesigns/web-component-core/utils";
|
|
4
|
+
import { createMemo, createRoot, untrack } from "solid-js";
|
|
5
|
+
//#region src/components/product-info.ts
|
|
6
|
+
var Name = `product-info`;
|
|
7
|
+
var Component = (props, { element }) => {
|
|
8
|
+
const product = untrack(() => props.product);
|
|
9
|
+
if (!product) return console.warn(`${Name}: product attribute is required`);
|
|
10
|
+
if (!product.variants) return console.warn(`${Name}: product attribute needs variants array`);
|
|
11
|
+
if (!product.selected_or_first_available_variant) return console.warn(`${Name}: product attribute needs selected or first available variant object`);
|
|
12
|
+
provideProductContext(props, element);
|
|
13
|
+
const url = createMemo(() => {
|
|
14
|
+
const url = new URL(window.location.href);
|
|
15
|
+
const variant = props?.product?.selected_or_first_available_variant;
|
|
16
|
+
if (props.product && variant && variant?.id) url.searchParams.set("variant", variant.id.toString());
|
|
17
|
+
else url.searchParams.delete("variant");
|
|
18
|
+
return url;
|
|
19
|
+
});
|
|
20
|
+
function subscribe(cb) {
|
|
21
|
+
return createRoot((dispose) => {
|
|
22
|
+
createGuardedEffect(() => props.product, (product) => cb(product));
|
|
23
|
+
return dispose;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
Object.assign(element, { subscribe });
|
|
27
|
+
/**
|
|
28
|
+
* Choosing a variant rewrites price, availability and whatever else the
|
|
29
|
+
* theme renders from it — silently, as far as assistive tech is concerned.
|
|
30
|
+
*
|
|
31
|
+
* The radio or select announces its own new value, because it is a native
|
|
32
|
+
* control doing its job. Nothing announces what that selection *did*, which
|
|
33
|
+
* is the part a shopper actually needs: an option that turned out to be
|
|
34
|
+
* sold out reads identically to one that did not.
|
|
35
|
+
*/
|
|
36
|
+
createGuardedEffect(() => props.product?.selected_or_first_available_variant, (variant, previous) => {
|
|
37
|
+
if (previous === void 0 || !variant) return;
|
|
38
|
+
if (previous && previous.id === variant.id) return;
|
|
39
|
+
const described = variant.title ?? variant.options.join(", ");
|
|
40
|
+
const { availableLabel, unavailableLabel } = untrack(() => ({
|
|
41
|
+
availableLabel: props.availableLabel,
|
|
42
|
+
unavailableLabel: props.unavailableLabel
|
|
43
|
+
}));
|
|
44
|
+
const availability = typeof variant.available === "boolean" ? variant.available ? availableLabel : unavailableLabel : "";
|
|
45
|
+
announce([described, availability].filter(Boolean).join(". "));
|
|
46
|
+
});
|
|
47
|
+
createGuardedEffect(() => ({
|
|
48
|
+
isProductPage: props.isProductPage,
|
|
49
|
+
url: url()
|
|
50
|
+
}), ({ isProductPage, url }) => {
|
|
51
|
+
if (!isProductPage) return;
|
|
52
|
+
window.history.replaceState(null, "", url);
|
|
53
|
+
});
|
|
35
54
|
};
|
|
36
|
-
|
|
55
|
+
//#endregion
|
|
37
56
|
export { Component, Name };
|
|
38
|
-
|
|
57
|
+
|
|
39
58
|
//# sourceMappingURL=product-info.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"product-info.js","names":[],"sources":["../../../src/components/product-info.ts"],"sourcesContent":["import {\n\tannounce,\n\ttype CorrectComponentType,\n\tcreateGuardedEffect,\n} from \"@brytdesigns/web-component-core/utils\";\n\nimport { createMemo, createRoot, untrack } from \"solid-js\";\n\nimport { provideProductContext } from \"../hooks/index.js\";\n\nimport type { Product } from \"../types.js\";\n\nexport type Props = {\n\tproduct?: Product;\n\tisProductPage?: boolean;\n\t/** Appended to the announcement when the chosen variant is in stock. */\n\tavailableLabel: string;\n\t/** Appended when it is not. */\n\tunavailableLabel: string;\n};\n\nexport const Name = `product-info`;\n\nexport const Component: CorrectComponentType<Props> = (props, { element }) => {\n\t// One-time validation of the mounting attributes. Read into a local so the\n\t// snapshot is explicit and TypeScript can narrow it — an `untrack` closure\n\t// around each check would defeat narrowing.\n\tconst product = untrack(() => props.product);\n\tif (!product) return console.warn(`${Name}: product attribute is required`);\n\tif (!product.variants)\n\t\treturn console.warn(`${Name}: product attribute needs variants array`);\n\tif (!product.selected_or_first_available_variant)\n\t\treturn console.warn(\n\t\t\t`${Name}: product attribute needs selected or first available variant object`,\n\t\t);\n\tprovideProductContext(props as any, element);\n\n\tconst url = createMemo(() => {\n\t\tconst url = new URL(window.location.href);\n\t\tconst variant = props?.product?.selected_or_first_available_variant;\n\t\tif (props.product && variant && variant?.id) {\n\t\t\turl.searchParams.set(\"variant\", variant.id.toString());\n\t\t} else {\n\t\t\turl.searchParams.delete(\"variant\");\n\t\t}\n\t\treturn url;\n\t});\n\n\tfunction subscribe(cb: (product: Product | undefined) => void) {\n\t\t// `observable` is gone in v2; pushing a signal out to an external\n\t\t// subscriber is an effect in its own root, with the disposer as the\n\t\t// unsubscribe. The public signature is unchanged.\n\t\treturn createRoot((dispose) => {\n\t\t\tcreateGuardedEffect(\n\t\t\t\t() => props.product,\n\t\t\t\t(product) => cb(product),\n\t\t\t);\n\t\t\treturn dispose;\n\t\t});\n\t}\n\n\tObject.assign(element, {\n\t\tsubscribe,\n\t});\n\n\t/**\n\t * Choosing a variant rewrites price, availability and whatever else the\n\t * theme renders from it — silently, as far as assistive tech is concerned.\n\t *\n\t * The radio or select announces its own new value, because it is a native\n\t * control doing its job. Nothing announces what that selection *did*, which\n\t * is the part a shopper actually needs: an option that turned out to be\n\t * sold out reads identically to one that did not.\n\t */\n\tcreateGuardedEffect(\n\t\t() => props.product?.selected_or_first_available_variant,\n\t\t(variant, previous) => {\n\t\t\t// Silent on the first run. The page has only just loaded and the\n\t\t\t// reader is about to encounter all of this in order anyway.\n\t\t\tif (previous === undefined || !variant) return;\n\t\t\tif (previous && previous.id === variant.id) return;\n\n\t\t\tconst described = variant.title ?? variant.options.join(\", \");\n\t\t\t// Read when the announcement is made, deliberately: the labels are\n\t\t\t// copy, and the current wording is the right wording. `untrack`\n\t\t\t// states that — the apply phase is not a tracking scope, so a bare\n\t\t\t// read here is one Solid reports and nobody has reviewed.\n\t\t\tconst { availableLabel, unavailableLabel } = untrack(() => ({\n\t\t\t\tavailableLabel: props.availableLabel,\n\t\t\t\tunavailableLabel: props.unavailableLabel,\n\t\t\t}));\n\t\t\tconst availability =\n\t\t\t\ttypeof variant.available === \"boolean\"\n\t\t\t\t\t? variant.available\n\t\t\t\t\t\t? availableLabel\n\t\t\t\t\t\t: unavailableLabel\n\t\t\t\t\t: \"\";\n\t\t\tannounce([described, availability].filter(Boolean).join(\". \"));\n\t\t},\n\t);\n\n\tcreateGuardedEffect(\n\t\t() => ({ isProductPage: props.isProductPage, url: url() }),\n\t\t({ isProductPage, url }) => {\n\t\t\tif (!isProductPage) return;\n\t\t\twindow.history.replaceState(null, \"\", url);\n\t\t},\n\t);\n};\n"],"mappings":";;;;;AAqBA,IAAa,OAAO;AAEpB,IAAa,aAA0C,OAAO,EAAE,cAAc;CAI7E,MAAM,UAAU,cAAc,MAAM,OAAO;CAC3C,IAAI,CAAC,SAAS,OAAO,QAAQ,KAAK,GAAG,KAAK,gCAAgC;CAC1E,IAAI,CAAC,QAAQ,UACZ,OAAO,QAAQ,KAAK,GAAG,KAAK,yCAAyC;CACtE,IAAI,CAAC,QAAQ,qCACZ,OAAO,QAAQ,KACd,GAAG,KAAK,qEACT;CACD,sBAAsB,OAAc,OAAO;CAE3C,MAAM,MAAM,iBAAiB;EAC5B,MAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;EACxC,MAAM,UAAU,OAAO,SAAS;EAChC,IAAI,MAAM,WAAW,WAAW,SAAS,IACxC,IAAI,aAAa,IAAI,WAAW,QAAQ,GAAG,SAAS,CAAC;OAErD,IAAI,aAAa,OAAO,SAAS;EAElC,OAAO;CACR,CAAC;CAED,SAAS,UAAU,IAA4C;EAI9D,OAAO,YAAY,YAAY;GAC9B,0BACO,MAAM,UACX,YAAY,GAAG,OAAO,CACxB;GACA,OAAO;EACR,CAAC;CACF;CAEA,OAAO,OAAO,SAAS,EACtB,UACD,CAAC;;;;;;;;;;CAWD,0BACO,MAAM,SAAS,sCACpB,SAAS,aAAa;EAGtB,IAAI,aAAa,KAAA,KAAa,CAAC,SAAS;EACxC,IAAI,YAAY,SAAS,OAAO,QAAQ,IAAI;EAE5C,MAAM,YAAY,QAAQ,SAAS,QAAQ,QAAQ,KAAK,IAAI;EAK5D,MAAM,EAAE,gBAAgB,qBAAqB,eAAe;GAC3D,gBAAgB,MAAM;GACtB,kBAAkB,MAAM;EACzB,EAAE;EACF,MAAM,eACL,OAAO,QAAQ,cAAc,YAC1B,QAAQ,YACP,iBACA,mBACD;EACJ,SAAS,CAAC,WAAW,YAAY,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC;CAC9D,CACD;CAEA,2BACQ;EAAE,eAAe,MAAM;EAAe,KAAK,IAAI;CAAE,KACvD,EAAE,eAAe,UAAU;EAC3B,IAAI,CAAC,eAAe;EACpB,OAAO,QAAQ,aAAa,MAAM,IAAI,GAAG;CAC1C,CACD;AACD"}
|
|
@@ -1,69 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
position,
|
|
53
|
-
selectedOption
|
|
54
|
-
},
|
|
55
|
-
bubbles: true
|
|
56
|
-
})
|
|
57
|
-
);
|
|
58
|
-
methods.options.updateOption(selectedOption, position - 1);
|
|
59
|
-
}
|
|
60
|
-
element.addEventListener("change", handleChange);
|
|
61
|
-
return onCleanup(() => {
|
|
62
|
-
element.removeEventListener("change", handleChange);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
1
|
+
import { useProductOptions } from "../hooks/useProductOptions.js";
|
|
2
|
+
import "../hooks/index.js";
|
|
3
|
+
import { createGuardedEffect } from "@brytdesigns/web-component-core/utils";
|
|
4
|
+
//#region src/components/product-option-group.ts
|
|
5
|
+
var Name = `product-option-group`;
|
|
6
|
+
var Component = (props, { element }) => {
|
|
7
|
+
const [context, methods] = useProductOptions(element);
|
|
8
|
+
createGuardedEffect(() => ({
|
|
9
|
+
selectedOption: context.selectedOptions[props.position - 1],
|
|
10
|
+
position: props.position,
|
|
11
|
+
name: props.name,
|
|
12
|
+
inputType: props.inputType
|
|
13
|
+
}), ({ selectedOption, position, name, inputType }) => {
|
|
14
|
+
if (inputType !== "radio" && inputType !== "select") return;
|
|
15
|
+
const optionName = `${name}-${position}`;
|
|
16
|
+
if (inputType === "radio") {
|
|
17
|
+
element.querySelectorAll(`input[name="${optionName}"]`).forEach((input) => {
|
|
18
|
+
const prevValue = input.checked;
|
|
19
|
+
if (input.value === selectedOption) input.checked = true;
|
|
20
|
+
else input.checked = false;
|
|
21
|
+
if (prevValue !== input.checked && input.checked) input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
22
|
+
});
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const select = element.querySelector(`select[name="${optionName}"]`);
|
|
26
|
+
if (!select || !selectedOption) return;
|
|
27
|
+
select.value = selectedOption;
|
|
28
|
+
select.dispatchEvent(new Event("change", { bubbles: true }));
|
|
29
|
+
});
|
|
30
|
+
createGuardedEffect(() => props.position, (position) => {
|
|
31
|
+
function handleChange(event) {
|
|
32
|
+
const target = event.target;
|
|
33
|
+
if (!target) return;
|
|
34
|
+
if (target.type === "radio" && !target.checked) return;
|
|
35
|
+
const selectedOption = target.value;
|
|
36
|
+
if (context.selectedOptions[position - 1] === selectedOption) return;
|
|
37
|
+
element.dispatchEvent(new CustomEvent("option-updated", {
|
|
38
|
+
detail: {
|
|
39
|
+
group: props.name,
|
|
40
|
+
position,
|
|
41
|
+
selectedOption
|
|
42
|
+
},
|
|
43
|
+
bubbles: true
|
|
44
|
+
}));
|
|
45
|
+
methods.options.updateOption(selectedOption, position - 1);
|
|
46
|
+
}
|
|
47
|
+
element.addEventListener("change", handleChange);
|
|
48
|
+
return () => {
|
|
49
|
+
element.removeEventListener("change", handleChange);
|
|
50
|
+
};
|
|
51
|
+
});
|
|
65
52
|
};
|
|
66
|
-
|
|
53
|
+
//#endregion
|
|
67
54
|
export { Component, Name };
|
|
68
|
-
|
|
55
|
+
|
|
69
56
|
//# sourceMappingURL=product-option-group.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"product-option-group.js","names":[],"sources":["../../../src/components/product-option-group.ts"],"sourcesContent":["import type { CorrectComponentType } from \"@brytdesigns/web-component-core/utils\";\nimport { createGuardedEffect } from \"@brytdesigns/web-component-core/utils\";\n\nimport { useProductOptions } from \"../hooks/index.js\";\n\nexport type Props = {\n\tposition: number;\n\tname: string;\n\tinputType: \"radio\" | \"select\";\n};\n\nexport const Name = `product-option-group`;\n\nexport const Component: CorrectComponentType<Props> = (props, { element }) => {\n\tconst [context, methods] = useProductOptions(element);\n\n\tcreateGuardedEffect(\n\t\t() => ({\n\t\t\tselectedOption: context.selectedOptions[props.position - 1],\n\t\t\tposition: props.position,\n\t\t\tname: props.name,\n\t\t\tinputType: props.inputType,\n\t\t}),\n\t\t({ selectedOption, position, name, inputType }) => {\n\t\t\tif (inputType !== \"radio\" && inputType !== \"select\") return;\n\n\t\t\tconst optionName = `${name}-${position}`;\n\n\t\t\tif (inputType === \"radio\") {\n\t\t\t\tconst inputs = element.querySelectorAll<HTMLInputElement>(\n\t\t\t\t\t`input[name=\"${optionName}\"]`,\n\t\t\t\t);\n\t\t\t\tinputs.forEach((input) => {\n\t\t\t\t\tconst prevValue = input.checked;\n\t\t\t\t\tif (input.value === selectedOption) input.checked = true;\n\t\t\t\t\telse input.checked = false;\n\t\t\t\t\t// Only the input that became CHECKED announces itself.\n\t\t\t\t\t//\n\t\t\t\t\t// This used to fire on any input whose state flipped, which\n\t\t\t\t\t// meant the radio being *de*selected dispatched `change` too.\n\t\t\t\t\t// The browser never does that - clicking B fires `change` on B\n\t\t\t\t\t// alone - and `handleChange` below reads `target.value` off\n\t\t\t\t\t// whatever fired, so the deselected option's value was fed\n\t\t\t\t\t// straight back into `updateOption` and the group could\n\t\t\t\t\t// oscillate between two options.\n\t\t\t\t\tif (prevValue !== input.checked && input.checked) {\n\t\t\t\t\t\tinput.dispatchEvent(new Event(\"change\", { bubbles: true }));\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst select = element.querySelector<HTMLSelectElement>(\n\t\t\t\t`select[name=\"${optionName}\"]`,\n\t\t\t);\n\t\t\tif (!select || !selectedOption) return;\n\t\t\tselect.value = selectedOption;\n\t\t\tselect.dispatchEvent(new Event(\"change\", { bubbles: true }));\n\t\t},\n\t);\n\n\tcreateGuardedEffect(\n\t\t() => props.position,\n\t\t(position) => {\n\t\t\tfunction handleChange(event: Event) {\n\t\t\t\tconst target = event.target as HTMLInputElement;\n\t\t\t\tif (!target) return;\n\t\t\t\t// A radio that is not checked is not a selection. Reading `value`\n\t\t\t\t// off one treats the option the shopper just moved *away* from as\n\t\t\t\t// the new choice - the other half of the oscillation guarded\n\t\t\t\t// against above, and the half that also covers a `change` this\n\t\t\t\t// component did not dispatch.\n\t\t\t\tif (target.type === \"radio\" && !target.checked) return;\n\t\t\t\tconst selectedOption = target.value;\n\t\t\t\tif (context.selectedOptions[position - 1] === selectedOption) return;\n\n\t\t\t\telement.dispatchEvent(\n\t\t\t\t\tnew CustomEvent(\"option-updated\", {\n\t\t\t\t\t\tdetail: {\n\t\t\t\t\t\t\tgroup: props.name,\n\t\t\t\t\t\t\tposition: position,\n\t\t\t\t\t\t\tselectedOption,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbubbles: true,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\tmethods.options.updateOption(selectedOption, position - 1);\n\t\t\t}\n\n\t\t\telement.addEventListener(\"change\", handleChange);\n\n\t\t\treturn () => {\n\t\t\t\telement.removeEventListener(\"change\", handleChange);\n\t\t\t};\n\t\t},\n\t);\n};\n"],"mappings":";;;;AAWA,IAAa,OAAO;AAEpB,IAAa,aAA0C,OAAO,EAAE,cAAc;CAC7E,MAAM,CAAC,SAAS,WAAW,kBAAkB,OAAO;CAEpD,2BACQ;EACN,gBAAgB,QAAQ,gBAAgB,MAAM,WAAW;EACzD,UAAU,MAAM;EAChB,MAAM,MAAM;EACZ,WAAW,MAAM;CAClB,KACC,EAAE,gBAAgB,UAAU,MAAM,gBAAgB;EAClD,IAAI,cAAc,WAAW,cAAc,UAAU;EAErD,MAAM,aAAa,GAAG,KAAK,GAAG;EAE9B,IAAI,cAAc,SAAS;GAI1B,QAHuB,iBACtB,eAAe,WAAW,GAE3B,CAAA,CAAO,SAAS,UAAU;IACzB,MAAM,YAAY,MAAM;IACxB,IAAI,MAAM,UAAU,gBAAgB,MAAM,UAAU;SAC/C,MAAM,UAAU;IAUrB,IAAI,cAAc,MAAM,WAAW,MAAM,SACxC,MAAM,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;GAE5D,CAAC;GACD;EACD;EAEA,MAAM,SAAS,QAAQ,cACtB,gBAAgB,WAAW,GAC5B;EACA,IAAI,CAAC,UAAU,CAAC,gBAAgB;EAChC,OAAO,QAAQ;EACf,OAAO,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;CAC5D,CACD;CAEA,0BACO,MAAM,WACX,aAAa;EACb,SAAS,aAAa,OAAc;GACnC,MAAM,SAAS,MAAM;GACrB,IAAI,CAAC,QAAQ;GAMb,IAAI,OAAO,SAAS,WAAW,CAAC,OAAO,SAAS;GAChD,MAAM,iBAAiB,OAAO;GAC9B,IAAI,QAAQ,gBAAgB,WAAW,OAAO,gBAAgB;GAE9D,QAAQ,cACP,IAAI,YAAY,kBAAkB;IACjC,QAAQ;KACP,OAAO,MAAM;KACH;KACV;IACD;IACA,SAAS;GACV,CAAC,CACF;GAEA,QAAQ,QAAQ,aAAa,gBAAgB,WAAW,CAAC;EAC1D;EAEA,QAAQ,iBAAiB,UAAU,YAAY;EAE/C,aAAa;GACZ,QAAQ,oBAAoB,UAAU,YAAY;EACnD;CACD,CACD;AACD"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { provideProductOptionsContext } from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { provideProductOptionsContext } from "../hooks/useProductOptions.js";
|
|
2
|
+
import "../hooks/index.js";
|
|
3
|
+
import { untrack } from "solid-js";
|
|
4
|
+
//#region src/components/product-options.ts
|
|
5
|
+
var Name = `product-options`;
|
|
6
|
+
var Component = (props, { element }) => {
|
|
7
|
+
if (untrack(() => !props.selectedOptions)) return console.warn(`${Name}: selected-options attribute is required`);
|
|
8
|
+
provideProductOptionsContext(props, element);
|
|
8
9
|
};
|
|
9
|
-
|
|
10
|
+
//#endregion
|
|
10
11
|
export { Component, Name };
|
|
11
|
-
|
|
12
|
+
|
|
12
13
|
//# sourceMappingURL=product-options.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"product-options.js","names":[],"sources":["../../../src/components/product-options.ts"],"sourcesContent":["import type { CorrectComponentType } from \"@brytdesigns/web-component-core/utils\";\nimport { untrack } from \"solid-js\";\nimport { provideProductOptionsContext } from \"../hooks/index.js\";\n\nexport type Props = {\n\tselectedOptions: string[];\n};\n\nexport const Name = `product-options`;\n\nexport const Component: CorrectComponentType<Props> = (props, { element }) => {\n\tif (untrack(() => !props.selectedOptions))\n\t\treturn console.warn(`${Name}: selected-options attribute is required`);\n\tprovideProductOptionsContext(props, element);\n};\n"],"mappings":";;;;AAQA,IAAa,OAAO;AAEpB,IAAa,aAA0C,OAAO,EAAE,cAAc;CAC7E,IAAI,cAAc,CAAC,MAAM,eAAe,GACvC,OAAO,QAAQ,KAAK,GAAG,KAAK,yCAAyC;CACtE,6BAA6B,OAAO,OAAO;AAC5C"}
|
|
@@ -1,49 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1
|
+
import { useProduct } from "../hooks/useProduct.js";
|
|
2
|
+
import { useProductOptions } from "../hooks/useProductOptions.js";
|
|
3
|
+
import "../hooks/index.js";
|
|
4
|
+
import { createGuardedEffect } from "@brytdesigns/web-component-core/utils";
|
|
5
|
+
import { untrack } from "solid-js";
|
|
6
|
+
//#region src/components/product-variant-input.ts
|
|
7
|
+
var Name = `product-variant-input`;
|
|
8
|
+
var Component = (props, { element }) => {
|
|
9
|
+
const [productContext, productContextMethods] = useProduct(element);
|
|
10
|
+
const [_, optionsMethods] = useProductOptions(element);
|
|
11
|
+
createGuardedEffect(() => ({
|
|
12
|
+
target: props.target,
|
|
13
|
+
preventDefault: props.preventDefault
|
|
14
|
+
}), ({ target: selector, preventDefault }) => {
|
|
15
|
+
let target = null;
|
|
16
|
+
if (selector) target = element.querySelector(selector);
|
|
17
|
+
if (!target) target = element;
|
|
18
|
+
function handleChange(event) {
|
|
19
|
+
if (preventDefault) event.preventDefault();
|
|
20
|
+
const target = event.target;
|
|
21
|
+
if (!target) return;
|
|
22
|
+
const value = target.value;
|
|
23
|
+
const product = untrack(() => productContext?.product);
|
|
24
|
+
if (value === String(product?.selected_or_first_available_variant?.id ?? "")) return;
|
|
25
|
+
const variant = product?.variants.find((variant) => variant.id.toString() === value.toString());
|
|
26
|
+
if (!variant) return;
|
|
27
|
+
if (optionsMethods) optionsMethods.options.update(variant.options);
|
|
28
|
+
else productContextMethods.variant.update(variant.id);
|
|
29
|
+
}
|
|
30
|
+
target.addEventListener("change", handleChange);
|
|
31
|
+
return () => {
|
|
32
|
+
target.removeEventListener("change", handleChange);
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
createGuardedEffect(() => ({
|
|
36
|
+
target: props.target,
|
|
37
|
+
variantId: productContext?.product?.selected_or_first_available_variant?.id
|
|
38
|
+
}), ({ target: selector, variantId }) => {
|
|
39
|
+
let target = null;
|
|
40
|
+
if (selector) target = element.querySelector(selector);
|
|
41
|
+
if (!target) target = element.querySelector("input[name='id']");
|
|
42
|
+
if (!target) return console.warn(`${Name}: target input not found`);
|
|
43
|
+
if (!variantId) return;
|
|
44
|
+
if (target.value === variantId.toString()) return;
|
|
45
|
+
target.value = variantId.toString();
|
|
46
|
+
target.dispatchEvent(new Event("change", { bubbles: true }));
|
|
47
|
+
});
|
|
45
48
|
};
|
|
46
|
-
|
|
49
|
+
//#endregion
|
|
47
50
|
export { Component, Name };
|
|
48
|
-
|
|
51
|
+
|
|
49
52
|
//# sourceMappingURL=product-variant-input.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"product-variant-input.js","names":[],"sources":["../../../src/components/product-variant-input.ts"],"sourcesContent":["import type { CorrectComponentType } from \"@brytdesigns/web-component-core/utils\";\nimport { createGuardedEffect } from \"@brytdesigns/web-component-core/utils\";\nimport { untrack } from \"solid-js\";\n\nimport { useProduct, useProductOptions } from \"../hooks/index.js\";\n\nexport type Props = {\n\ttarget: string | null;\n\tpreventDefault?: boolean;\n};\n\nexport const Name = `product-variant-input`;\n\nexport const Component: CorrectComponentType<Props> = (props, { element }) => {\n\tconst [productContext, productContextMethods] = useProduct(element);\n\tconst [_, optionsMethods] = useProductOptions(element);\n\n\tcreateGuardedEffect(\n\t\t() => ({\n\t\t\ttarget: props.target,\n\t\t\tpreventDefault: props.preventDefault,\n\t\t}),\n\t\t({ target: selector, preventDefault }) => {\n\t\t\tlet target: Element | null = null;\n\t\t\tif (selector) target = element.querySelector(selector);\n\t\t\tif (!target) target = element;\n\n\t\t\tfunction handleChange(event: Event) {\n\t\t\t\tif (preventDefault) event.preventDefault();\n\t\t\t\tconst target = event.target as HTMLInputElement;\n\t\t\t\tif (!target) return;\n\t\t\t\tconst value = target.value;\n\t\t\t\t// An event handler is not a tracking scope, and the product has\n\t\t\t\t// to be read as it stands when the shopper picks — not as it\n\t\t\t\t// stood when the listener was attached. Declared with `untrack`\n\t\t\t\t// so the read is reviewed rather than reported.\n\t\t\t\tconst product = untrack(() => productContext?.product);\n\t\t\t\t// Compared as strings. `value` is always a string and the id is a\n\t\t\t\t// number, so `===` was never true and this early return never\n\t\t\t\t// fired - every change ran the lookup and the update below, even\n\t\t\t\t// when nothing had actually changed.\n\t\t\t\tif (\n\t\t\t\t\tvalue ===\n\t\t\t\t\tString(product?.selected_or_first_available_variant?.id ?? \"\")\n\t\t\t\t)\n\t\t\t\t\treturn;\n\t\t\t\tconst variant = product?.variants.find(\n\t\t\t\t\t(variant) => variant.id.toString() === value.toString(),\n\t\t\t\t);\n\t\t\t\tif (!variant) return;\n\t\t\t\tif (optionsMethods) optionsMethods.options.update(variant.options);\n\t\t\t\telse productContextMethods.variant.update(variant.id);\n\t\t\t}\n\n\t\t\ttarget.addEventListener(\"change\", handleChange);\n\n\t\t\treturn () => {\n\t\t\t\ttarget.removeEventListener(\"change\", handleChange);\n\t\t\t};\n\t\t},\n\t);\n\n\tcreateGuardedEffect(\n\t\t// `variantId` is read HERE, in the compute phase, because only the\n\t\t// compute phase tracks. It used to be read in the apply phase below,\n\t\t// which meant this effect depended on `props.target` alone: the hidden\n\t\t// `input[name=\"id\"]` was written once at mount and never again, so\n\t\t// choosing a swatch left the form posting the *first* variant and the\n\t\t// shopper received the wrong one. A single-function Solid 1 effect\n\t\t// tracked its whole body; a split Solid 2 effect does not.\n\t\t() => ({\n\t\t\ttarget: props.target,\n\t\t\tvariantId:\n\t\t\t\tproductContext?.product?.selected_or_first_available_variant?.id,\n\t\t}),\n\t\t({ target: selector, variantId }) => {\n\t\t\tlet target: HTMLInputElement | null = null;\n\t\t\tif (selector) target = element.querySelector<HTMLInputElement>(selector);\n\t\t\tif (!target)\n\t\t\t\ttarget = element.querySelector<HTMLInputElement>(\"input[name='id']\");\n\t\t\tif (!target) return console.warn(`${Name}: target input not found`);\n\n\t\t\tif (!variantId) return;\n\t\t\tif (target.value === variantId.toString()) return;\n\t\t\ttarget.value = variantId.toString();\n\t\t\ttarget.dispatchEvent(new Event(\"change\", { bubbles: true }));\n\t\t},\n\t);\n};\n"],"mappings":";;;;;;AAWA,IAAa,OAAO;AAEpB,IAAa,aAA0C,OAAO,EAAE,cAAc;CAC7E,MAAM,CAAC,gBAAgB,yBAAyB,WAAW,OAAO;CAClE,MAAM,CAAC,GAAG,kBAAkB,kBAAkB,OAAO;CAErD,2BACQ;EACN,QAAQ,MAAM;EACd,gBAAgB,MAAM;CACvB,KACC,EAAE,QAAQ,UAAU,qBAAqB;EACzC,IAAI,SAAyB;EAC7B,IAAI,UAAU,SAAS,QAAQ,cAAc,QAAQ;EACrD,IAAI,CAAC,QAAQ,SAAS;EAEtB,SAAS,aAAa,OAAc;GACnC,IAAI,gBAAgB,MAAM,eAAe;GACzC,MAAM,SAAS,MAAM;GACrB,IAAI,CAAC,QAAQ;GACb,MAAM,QAAQ,OAAO;GAKrB,MAAM,UAAU,cAAc,gBAAgB,OAAO;GAKrD,IACC,UACA,OAAO,SAAS,qCAAqC,MAAM,EAAE,GAE7D;GACD,MAAM,UAAU,SAAS,SAAS,MAChC,YAAY,QAAQ,GAAG,SAAS,MAAM,MAAM,SAAS,CACvD;GACA,IAAI,CAAC,SAAS;GACd,IAAI,gBAAgB,eAAe,QAAQ,OAAO,QAAQ,OAAO;QAC5D,sBAAsB,QAAQ,OAAO,QAAQ,EAAE;EACrD;EAEA,OAAO,iBAAiB,UAAU,YAAY;EAE9C,aAAa;GACZ,OAAO,oBAAoB,UAAU,YAAY;EAClD;CACD,CACD;CAEA,2BAQQ;EACN,QAAQ,MAAM;EACd,WACC,gBAAgB,SAAS,qCAAqC;CAChE,KACC,EAAE,QAAQ,UAAU,gBAAgB;EACpC,IAAI,SAAkC;EACtC,IAAI,UAAU,SAAS,QAAQ,cAAgC,QAAQ;EACvE,IAAI,CAAC,QACJ,SAAS,QAAQ,cAAgC,kBAAkB;EACpE,IAAI,CAAC,QAAQ,OAAO,QAAQ,KAAK,GAAG,KAAK,yBAAyB;EAElE,IAAI,CAAC,WAAW;EAChB,IAAI,OAAO,UAAU,UAAU,SAAS,GAAG;EAC3C,OAAO,QAAQ,UAAU,SAAS;EAClC,OAAO,cAAc,IAAI,MAAM,UAAU,EAAE,SAAS,KAAK,CAAC,CAAC;CAC5D,CACD;AACD"}
|
|
@@ -1,37 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
>
|
|
28
|
-
${() => Array.from(
|
|
29
|
-
activeVariantTemplate()?.content.cloneNode(true).childNodes || []
|
|
30
|
-
)}
|
|
31
|
-
<//>
|
|
32
|
-
`;
|
|
1
|
+
import { useProduct } from "../hooks/useProduct.js";
|
|
2
|
+
import "../hooks/index.js";
|
|
3
|
+
import { createMemo } from "solid-js";
|
|
4
|
+
import { Show, createComponent } from "@solidjs/web";
|
|
5
|
+
//#region src/components/product-variant-render.tsx
|
|
6
|
+
var Name = `product-variant-render`;
|
|
7
|
+
var Component = (_, { element }) => {
|
|
8
|
+
const templates = Array.from(element.querySelectorAll("template[variant-id]"));
|
|
9
|
+
if (!templates.length) return console.warn(`${Name}: No variant templates found`);
|
|
10
|
+
const [state] = useProduct(element);
|
|
11
|
+
const fallbackChildren = Array.from(element.children);
|
|
12
|
+
const activeVariantTemplate = createMemo(() => {
|
|
13
|
+
const variantId = state.product?.selected_or_first_available_variant?.id;
|
|
14
|
+
if (!variantId) return null;
|
|
15
|
+
return templates.find((t) => t.getAttribute("variant-id") === `${variantId}`) || null;
|
|
16
|
+
});
|
|
17
|
+
element.replaceChildren(...[]);
|
|
18
|
+
return createComponent(Show, {
|
|
19
|
+
get when() {
|
|
20
|
+
return !!activeVariantTemplate();
|
|
21
|
+
},
|
|
22
|
+
fallback: fallbackChildren,
|
|
23
|
+
get children() {
|
|
24
|
+
return Array.from(activeVariantTemplate()?.content.cloneNode(true).childNodes || []);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
33
27
|
};
|
|
34
|
-
|
|
28
|
+
//#endregion
|
|
35
29
|
export { Component, Name };
|
|
36
|
-
|
|
30
|
+
|
|
37
31
|
//# sourceMappingURL=product-variant-render.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"product-variant-render.js","names":["<Show when={!!activeVariantTemplate()} fallback={fallbackChildren}>\n\t\t\t{Array.from(\n\t\t\t\tactiveVariantTemplate()?.content.cloneNode(true).childNodes || [],\n\t\t\t)}\n\t\t</Show>","fallback={fallbackChildren}"],"sources":["../../../src/components/product-variant-render.tsx"],"sourcesContent":["import type { CorrectComponentType } from \"@brytdesigns/web-component-core/utils\";\n\nimport { Show } from \"@solidjs/web\";\nimport { createMemo } from \"solid-js\";\n\nimport { useProduct } from \"../hooks/index.js\";\n\nexport type Props = {};\n\nexport const Name = `product-variant-render`;\n\nexport const Component: CorrectComponentType<Props> = (_, { element }) => {\n\tconst templates = Array.from(\n\t\telement.querySelectorAll<HTMLTemplateElement>(\"template[variant-id]\"),\n\t);\n\tif (!templates.length)\n\t\treturn console.warn(`${Name}: No variant templates found`);\n\tconst [state] = useProduct(element);\n\n\tconst fallbackChildren = Array.from(element.children);\n\n\tconst activeVariantTemplate = createMemo(() => {\n\t\tconst variantId = state.product?.selected_or_first_available_variant?.id;\n\t\tif (!variantId) return null;\n\t\tconst template = templates.find(\n\t\t\t(t) => t.getAttribute(\"variant-id\") === `${variantId}`,\n\t\t);\n\t\treturn template || null;\n\t});\n\n\telement.replaceChildren(...[]);\n\n\treturn (\n\t\t<Show when={!!activeVariantTemplate()} fallback={fallbackChildren}>\n\t\t\t{Array.from(\n\t\t\t\tactiveVariantTemplate()?.content.cloneNode(true).childNodes || [],\n\t\t\t)}\n\t\t</Show>\n\t);\n};\n"],"mappings":";;;;;AASA,IAAa,OAAO;AAEpB,IAAa,aAA0C,GAAG,EAAE,cAAc;CACzE,MAAM,YAAY,MAAM,KACvB,QAAQ,iBAAsC,sBAAsB,CACrE;CACA,IAAI,CAAC,UAAU,QACd,OAAO,QAAQ,KAAK,GAAG,KAAK,6BAA6B;CAC1D,MAAM,CAAC,SAAS,WAAW,OAAO;CAElC,MAAM,mBAAmB,MAAM,KAAK,QAAQ,QAAQ;CAEpD,MAAM,wBAAwB,iBAAiB;EAC9C,MAAM,YAAY,MAAM,SAAS,qCAAqC;EACtE,IAAI,CAAC,WAAW,OAAO;EAIvB,OAHiB,UAAU,MACzB,MAAM,EAAE,aAAa,YAAY,MAAM,GAAG,WAErC,KAAY;CACpB,CAAC;CAED,QAAQ,gBAAgB,GAAG,CAAC,CAAC;CAE7B,OACCA,gBAAAA,MAAAA;EAAM,IAAA,OAAA;GAAM,OAAA,CAAC,CAAC,sBAAsB;EAAC;EAAEC,UAAU;EAAjD,IAAA,WAAA;GACE,OAAA,MAAM,KACN,sBAAsB,CAAC,EAAE,QAAQ,UAAU,IAAI,CAAC,CAAC,cAAc,CAAC,CACjE;EACK;;AAER"}
|
package/dist/main/hooks/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import { getProductContext, provideProductContext, useProduct, useProductAt, useProductContext } from "./useProduct.js";
|
|
2
|
+
import { getProductOptionsContext, provideProductOptionsContext, useProductOptions, useProductOptionsAt, useProductOptionsContext } from "./useProductOptions.js";
|
|
3
|
+
export { getProductContext, getProductOptionsContext, provideProductContext, provideProductOptionsContext, useProduct, useProductAt, useProductContext, useProductOptions, useProductOptionsAt, useProductOptionsContext };
|