@osimart/create-storefront 1.0.21 → 1.0.22
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/app/composables/UseThemeGroup.js +4 -0
- package/app/composables/useContentTree.js +28 -2
- package/app/composables/useProductItems.js +5 -3
- package/app/composables/useProductVariants.js +32 -1
- package/app/logic/components/CartDrawer.js +4 -1
- package/app/logic/components/ProductVariantPicker.js +7 -2
- package/app/logic/components/QuickBuy.js +14 -6
- package/app/logic/components/SiteHeader.js +2 -0
- package/app/logic/sections/MainClassificationSection.js +2 -0
- package/package.json +1 -1
|
@@ -115,6 +115,8 @@ const DEFAULTS = {
|
|
|
115
115
|
|
|
116
116
|
// cart
|
|
117
117
|
HEADER_CART_COUNT: "BADGE",
|
|
118
|
+
HEADER_CART_BADGE_BG: null,
|
|
119
|
+
HEADER_CART_BADGE_TEXT: null,
|
|
118
120
|
|
|
119
121
|
// chrome
|
|
120
122
|
HEADER_SHOW_ACCOUNT: true,
|
|
@@ -453,6 +455,8 @@ const DEFAULTS = {
|
|
|
453
455
|
CART_DRAWER_EMPTY_BTN_STYLE: "THEME",
|
|
454
456
|
CART_DRAWER_RECS_ADD_STYLE: "THEME",
|
|
455
457
|
CART_DRAWER_QTY_SIZE: 30,
|
|
458
|
+
CART_DRAWER_SHOW_REMOVE: true,
|
|
459
|
+
CART_DRAWER_REMOVE_COLOR: null,
|
|
456
460
|
CART_DRAWER_RECS_CARD_BACKGROUND: null,
|
|
457
461
|
CART_DRAWER_RECS_PRICE_POSITION: "BELOW",
|
|
458
462
|
CART_DRAWER_RECS_NAME_LINES: 2,
|
|
@@ -438,9 +438,21 @@ export function useContentTree(section, options = {}) {
|
|
|
438
438
|
if (bleed) {
|
|
439
439
|
for (const [k, val] of Object.entries(paddingOf(v))) box[k.replace("--cb-p", "--card-p")] = val;
|
|
440
440
|
}
|
|
441
|
+
// Quick buy's colour swatches follow the card's Swatches block.
|
|
442
|
+
const findSw = (list) => {
|
|
443
|
+
for (const c of list ?? []) {
|
|
444
|
+
if (c.type === "CARD_SWATCHES") return c;
|
|
445
|
+
const hit = findSw(c.children);
|
|
446
|
+
if (hit) return hit;
|
|
447
|
+
}
|
|
448
|
+
return null;
|
|
449
|
+
};
|
|
450
|
+
const swatchRadius = findSw(children)?.swRadius;
|
|
451
|
+
if (swatchRadius) kids = kids.map((c) => (c.type === "CARD_IMAGE" ? { ...c, swatchRadius } : c));
|
|
441
452
|
return {
|
|
442
453
|
...base,
|
|
443
454
|
...flexOf(v),
|
|
455
|
+
swatchRadius,
|
|
444
456
|
children: kids,
|
|
445
457
|
sizeClasses: ["w-fill"],
|
|
446
458
|
sizeVars: {},
|
|
@@ -579,9 +591,15 @@ export function useContentTree(section, options = {}) {
|
|
|
579
591
|
box: boxOf(v, { background: false }),
|
|
580
592
|
max: num(v.MAX, 5),
|
|
581
593
|
square: upper(v.SHAPE, "CIRCLE") === "SQUARE",
|
|
594
|
+
// Own radius (0 = sharp) over the shape's; the card's quick buy uses it too.
|
|
595
|
+
swRadius: has(v.RADIUS) ? `${num(v.RADIUS, 0)}px` : upper(v.SHAPE, "CIRCLE") === "SQUARE" ? "2px" : undefined,
|
|
582
596
|
// COLOR: the colour option only; ALL: every option (sizes as text).
|
|
583
597
|
show: upper(v.SHOW, "ALL"),
|
|
584
|
-
swatchVars: {
|
|
598
|
+
swatchVars: {
|
|
599
|
+
"--cb-sw": `${num(v.SIZE, 16)}px`,
|
|
600
|
+
"--cb-sw-gap": `${num(v.GAP, 6)}px`,
|
|
601
|
+
...(has(v.RADIUS) ? { "--cb-sw-r": `${num(v.RADIUS, 0)}px` } : {}),
|
|
602
|
+
},
|
|
585
603
|
};
|
|
586
604
|
|
|
587
605
|
case "CARD_BUTTON":
|
|
@@ -753,8 +771,16 @@ export function useContentTree(section, options = {}) {
|
|
|
753
771
|
return {
|
|
754
772
|
...base,
|
|
755
773
|
...sizeOf(v, "WIDTH", "WIDTH_MOBILE", "FILL"),
|
|
756
|
-
|
|
774
|
+
// Background too: their Text-look settings carry BG_COLOR (Title,
|
|
775
|
+
// Description, Breadcrumbs…); blocks without it just leave it unset.
|
|
776
|
+
box: boxOf(v),
|
|
757
777
|
...(textDefault ? textLook(v, textDefault) : {}),
|
|
778
|
+
// Text parts always fill their row (no Width setting), so the text box
|
|
779
|
+
// — capped by MAX_WIDTH — would sit at the start even with the section
|
|
780
|
+
// centred. ContentNode lines it up instead: the block's own Alignment,
|
|
781
|
+
// else its group's (the inherited --cb-col-align).
|
|
782
|
+
textPart: !!textDefault,
|
|
783
|
+
selfVars: textDefault ? compact({ "--cb-self-align": COL_ALIGN[upper(v.ALIGNMENT, "")] }) : {},
|
|
758
784
|
component: markRaw(component),
|
|
759
785
|
};
|
|
760
786
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isColorType } from "#osi/composables/useProductVariants";
|
|
1
2
|
/**
|
|
2
3
|
* PRODUCT_LIST's items — products in the one shape the card template reads.
|
|
3
4
|
*
|
|
@@ -147,7 +148,8 @@ export function useProductItems(section, products, options = {}) {
|
|
|
147
148
|
return Number(product.remaining_stock) <= 0;
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
|
|
151
|
+
// By the option's display_type (the name only for older rows).
|
|
152
|
+
const isColorOption = (type) => isColorType(type);
|
|
151
153
|
|
|
152
154
|
/**
|
|
153
155
|
* Every option (Color, Size, …) with the values that have a variant behind
|
|
@@ -163,10 +165,10 @@ export function useProductItems(section, products, options = {}) {
|
|
|
163
165
|
const value = (variant.values ?? []).find((v) => v.type === type.id);
|
|
164
166
|
if (!value || seen.has(value.id)) continue;
|
|
165
167
|
seen.add(value.id);
|
|
166
|
-
const color =
|
|
168
|
+
const color = isColorOption(type) ? value.hex_code || "" : "";
|
|
167
169
|
values.push({ key: value.id, name: value.name || value.value || "", color });
|
|
168
170
|
}
|
|
169
|
-
return { key: type.id, name: type.name, isColor:
|
|
171
|
+
return { key: type.id, name: type.name, isColor: isColorOption(type), values };
|
|
170
172
|
})
|
|
171
173
|
.filter((option) => option.values.length);
|
|
172
174
|
}
|
|
@@ -17,6 +17,37 @@
|
|
|
17
17
|
* @param {object|Ref|(() => object)} product pass a getter — the card is
|
|
18
18
|
* rendered in a v-for and the product prop changes identity on re-fetch.
|
|
19
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* Is this option (a vary_by type / a filter) drawn as colour swatches?
|
|
22
|
+
* Its display_type decides ("COLOR" — case doesn't matter). Only a row from an
|
|
23
|
+
* older API with no display_type at all falls back to the name (Color /
|
|
24
|
+
* Colour), so a type named "Color" set to buttons shows buttons.
|
|
25
|
+
*/
|
|
26
|
+
export function isColorType(type) {
|
|
27
|
+
const display = type?.display_type;
|
|
28
|
+
if (display !== undefined && display !== null && display !== "") {
|
|
29
|
+
return String(display).toLowerCase() === "color";
|
|
30
|
+
}
|
|
31
|
+
return ["color", "colour"].includes(String(type?.name ?? "").toLowerCase());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sold out for a picked variant — the product page's rule: the variant's own
|
|
36
|
+
* stock decides once one is picked (one that doesn't track inventory is always
|
|
37
|
+
* available); before that, the product's status, then its stock.
|
|
38
|
+
*/
|
|
39
|
+
export function isVariantSoldOut(product, variant = null) {
|
|
40
|
+
const out = (n) => n !== null && n !== undefined && Number(n) <= 0;
|
|
41
|
+
if (variant?.id) {
|
|
42
|
+
if (!variant.track_inventory) return false;
|
|
43
|
+
return out(variant.remaining_stock);
|
|
44
|
+
}
|
|
45
|
+
const status = product?.status;
|
|
46
|
+
if (status === "In Stock" || status === "On Demand") return false;
|
|
47
|
+
if (status === "Out of Stock") return true;
|
|
48
|
+
return out(product?.remaining_stock);
|
|
49
|
+
}
|
|
50
|
+
|
|
20
51
|
export function useProductVariants(product) {
|
|
21
52
|
const p = computed(() => toValue(product) ?? {});
|
|
22
53
|
|
|
@@ -32,7 +63,7 @@ export function useProductVariants(product) {
|
|
|
32
63
|
);
|
|
33
64
|
|
|
34
65
|
const colorType = computed(
|
|
35
|
-
() => variantTypes.value.find((type) => type
|
|
66
|
+
() => variantTypes.value.find((type) => isColorType(type)) ?? null
|
|
36
67
|
);
|
|
37
68
|
|
|
38
69
|
/** Distinct colour values that actually have a variant behind them. */
|
|
@@ -114,6 +114,7 @@ export default function useLogic() {
|
|
|
114
114
|
if (s.CART_DRAWER_RECS_CARD_BACKGROUND) style["--recs-card-bg"] = s.CART_DRAWER_RECS_CARD_BACKGROUND;
|
|
115
115
|
if (s.CART_DRAWER_RECS_PRICE_COLOR) style["--recs-price-color"] = s.CART_DRAWER_RECS_PRICE_COLOR;
|
|
116
116
|
if (s.CART_DRAWER_RECS_LINE_COLOR) style["--recs-line"] = s.CART_DRAWER_RECS_LINE_COLOR;
|
|
117
|
+
if (s.CART_DRAWER_REMOVE_COLOR) style["--cart-remove-color"] = s.CART_DRAWER_REMOVE_COLOR;
|
|
117
118
|
// Quantity buttons (inside each cart line): picked up by .osi-qty-btn below.
|
|
118
119
|
const q = buttonVars("CART_DRAWER_QTY");
|
|
119
120
|
for (const [k, v] of Object.entries(q)) style[k.replace("--btn-", "--qty-")] = v;
|
|
@@ -137,6 +138,8 @@ export default function useLogic() {
|
|
|
137
138
|
const increase = (item) => edit(increaseItem, item);
|
|
138
139
|
const decrease = (item) => edit(decreaseItem, item);
|
|
139
140
|
const remove = (item) => edit(removeItem, item);
|
|
141
|
+
// Unset (older themes) counts as shown.
|
|
142
|
+
const showRemove = computed(() => settings.value.CART_DRAWER_SHOW_REMOVE !== false);
|
|
140
143
|
|
|
141
144
|
function close() {
|
|
142
145
|
toggleCartSidebar(false);
|
|
@@ -261,5 +264,5 @@ export default function useLogic() {
|
|
|
261
264
|
// rely on a page's onMounted to load the cart.
|
|
262
265
|
onMounted(fetchCart);
|
|
263
266
|
|
|
264
|
-
return { settings, t, TEXT_DEFAULTS, txt, scheme, isCartSidebarVisible, toggleCartSidebar, cartItems, topLevelItems, childrenOf, cartTotal, count, fetchCart, showApiError, mediaUrl, productUrl, handleAddToCart, store, currency, money, px, has, VARIANTS, buttonVars, button, checkoutBtn, emptyBtn, addBtn, cardStyle, RATIOS, rootStyle, isUpdating, edit, increase, decrease, remove, close, go, threshold, shipping, recs, recsLoading, recsKey, pickedIds, limit, loadRecs, inCart, shownRecs, recsLayout, pricePos, adding, addRec, priceOf, analytics };
|
|
267
|
+
return { settings, t, TEXT_DEFAULTS, txt, scheme, isCartSidebarVisible, toggleCartSidebar, cartItems, topLevelItems, childrenOf, cartTotal, count, fetchCart, showApiError, mediaUrl, productUrl, handleAddToCart, store, currency, money, px, has, VARIANTS, buttonVars, button, checkoutBtn, emptyBtn, addBtn, cardStyle, RATIOS, rootStyle, isUpdating, edit, increase, decrease, remove, showRemove, close, go, threshold, shipping, recs, recsLoading, recsKey, pickedIds, limit, loadRecs, inCart, shownRecs, recsLayout, pricePos, adding, addRec, priceOf, analytics };
|
|
265
268
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isColorType } from "#osi/composables/useProductVariants";
|
|
1
2
|
import { useProductDetail } from "#osi/composables/useProductDetail";
|
|
2
3
|
|
|
3
4
|
export default function useLogic({ props }) {
|
|
@@ -30,7 +31,10 @@ export default function useLogic({ props }) {
|
|
|
30
31
|
const pickerStyle = computed(() =>
|
|
31
32
|
compact({
|
|
32
33
|
"--sw-size": px(s.value.PICKER_SWATCH_SIZE) ?? "32px",
|
|
33
|
-
|
|
34
|
+
// Own radius first (0 = sharp), else the shape's.
|
|
35
|
+
"--sw-radius":
|
|
36
|
+
px(s.value.PICKER_SWATCH_RADIUS) ??
|
|
37
|
+
({ SQUARE: "2px", ROUNDED: "8px" }[s.value.PICKER_SWATCH_SHAPE] ?? "999px"),
|
|
34
38
|
"--sw-ring": s.value.PICKER_SWATCH_RING_COLOR,
|
|
35
39
|
"--pk-gap": px(s.value.PICKER_GAP) ?? "8px",
|
|
36
40
|
"--pk-option-gap": px(s.value.PICKER_OPTION_GAP) ?? "16px",
|
|
@@ -53,7 +57,8 @@ export default function useLogic({ props }) {
|
|
|
53
57
|
})
|
|
54
58
|
);
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
// Swatches by the option's display_type (the name only for older rows).
|
|
61
|
+
const isColor = (type) => isColorType(type);
|
|
57
62
|
|
|
58
63
|
/** How one option type is drawn. */
|
|
59
64
|
function modeFor(type) {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Every click inside stops at the panel: the card around it is a link.
|
|
18
18
|
*/
|
|
19
|
+
import { isColorType, isVariantSoldOut } from "#osi/composables/useProductVariants";
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
export default function useLogic({ props, emit }) {
|
|
@@ -86,9 +87,13 @@ export default function useLogic({ props, emit }) {
|
|
|
86
87
|
|
|
87
88
|
/* ------------------------------------------------------------------- details */
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
// Swatches by the option's display_type, like the product page.
|
|
91
|
+
const isColor = (type) => isColorType(type);
|
|
90
92
|
const selected = computed(() => variants.selectedVariant.value);
|
|
91
|
-
|
|
93
|
+
// The product page's rule: a picked combination that's out of stock is sold
|
|
94
|
+
// out even when the product as a whole is "In Stock".
|
|
95
|
+
const soldOut = computed(() => isVariantSoldOut(props.product, selected.value));
|
|
96
|
+
const comingSoon = computed(() => !!props.product?.coming_soon);
|
|
92
97
|
// The picked option has no price: shown as unavailable, can't be added.
|
|
93
98
|
const noPrice = computed(() => !!selected.value && !(Number(selected.value.price) > 0));
|
|
94
99
|
const currency = computed(
|
|
@@ -178,6 +183,7 @@ export default function useLogic({ props, emit }) {
|
|
|
178
183
|
quickShop.flashError("That combination isn't available.");
|
|
179
184
|
return;
|
|
180
185
|
}
|
|
186
|
+
if (soldOut.value || comingSoon.value) return;
|
|
181
187
|
await quickShop.addVariant(selected.value, qty.value);
|
|
182
188
|
}
|
|
183
189
|
|
|
@@ -185,10 +191,12 @@ export default function useLogic({ props, emit }) {
|
|
|
185
191
|
quickShop.errorMessage.value ||
|
|
186
192
|
(quickShop.adding.value
|
|
187
193
|
? "Adding…"
|
|
188
|
-
:
|
|
189
|
-
? "
|
|
190
|
-
:
|
|
194
|
+
: comingSoon.value
|
|
195
|
+
? "Coming soon"
|
|
196
|
+
: soldOut.value
|
|
197
|
+
? "Out of stock"
|
|
198
|
+
: "Add to cart") // no price: same label, the button is disabled
|
|
191
199
|
);
|
|
192
200
|
|
|
193
|
-
return { isMobile, cartType, store, toggleCartSidebar, mediaUrl, router, variants, quickShop, shown, locked, afterClose, inline, popup, close, onAfterLeave, onKey, closeBtn, onAfterEnter, isColor, selected, soldOut, noPrice, currency, money, price, pickedName, images, track, slide, onScroll, goTo, description, descEl, descLong, descOpen, measureDesc, step, qty, dec, inc, add, ctaLabel };
|
|
201
|
+
return { isMobile, cartType, store, toggleCartSidebar, mediaUrl, router, variants, quickShop, shown, locked, afterClose, inline, popup, close, onAfterLeave, onKey, closeBtn, onAfterEnter, isColor, selected, soldOut, comingSoon, noPrice, currency, money, price, pickedName, images, track, slide, onScroll, goTo, description, descEl, descLong, descOpen, measureDesc, step, qty, dec, inc, add, ctaLabel };
|
|
194
202
|
}
|
|
@@ -144,6 +144,8 @@ export default function useLogic() {
|
|
|
144
144
|
style["--osi-header-icon"] = s.HEADER_ICON_COLOR || "var(--osi-text)";
|
|
145
145
|
if (s.HEADER_ICON_BG) style["--osi-header-icon-bg"] = s.HEADER_ICON_BG;
|
|
146
146
|
if (s.HEADER_ICON_BORDER) style["--osi-header-icon-border"] = s.HEADER_ICON_BORDER;
|
|
147
|
+
if (s.HEADER_CART_BADGE_BG) style["--osi-header-badge-bg"] = s.HEADER_CART_BADGE_BG;
|
|
148
|
+
if (s.HEADER_CART_BADGE_TEXT) style["--osi-header-badge-text"] = s.HEADER_CART_BADGE_TEXT;
|
|
147
149
|
style["--osi-header-pad-y"] = `${s.HEADER_PADDING_Y}px`;
|
|
148
150
|
// Side padding lives on the header itself, not on the inner box — see the
|
|
149
151
|
// note on .osi-header-inner below. 16 is COMMON_STYLE_SETTINGS' PADDING_X
|
|
@@ -332,6 +332,8 @@ export default function useLogic({ props }) {
|
|
|
332
332
|
|
|
333
333
|
// filters
|
|
334
334
|
"--pf-swatch": px(num(s.value.SWATCH_SIZE, 28)),
|
|
335
|
+
// Empty: the shape's radius (circle / square).
|
|
336
|
+
"--pf-swatch-r": px(s.value.SWATCH_RADIUS),
|
|
335
337
|
|
|
336
338
|
// grid
|
|
337
339
|
"--plp-cols": effectiveColumns.value,
|
package/package.json
CHANGED