@osimart/create-storefront 0.0.0-stage → 1.0.1
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/README.md +80 -2
- package/THEME_DEV_API.md +80 -0
- package/app/app.vue +154 -0
- package/app/assets/css/osi-controls.css +824 -0
- package/app/components/SectionRenderer.vue +46 -0
- package/app/components/builder/BuilderOverlay.vue +582 -0
- package/app/components/checkout/ExtraFees.vue +329 -0
- package/app/components/checkout/PaymentHandoff.vue +388 -0
- package/app/components/common/Banner.vue +35 -0
- package/app/components/common/ConfirmDeleteModal.vue +117 -0
- package/app/components/common/DropdownSelect.vue +432 -0
- package/app/components/common/Loader.vue +32 -0
- package/app/components/common/OsiField.vue +218 -0
- package/app/components/common/PhoneNumInput.vue +434 -0
- package/app/composables/UseBodyScrollLock.js +122 -0
- package/app/composables/UseThemeGroup.js +575 -0
- package/app/composables/useAddToCart.js +87 -0
- package/app/composables/useAnalytics.js +15 -0
- package/app/composables/useAnnouncements.js +29 -0
- package/app/composables/useApiErrorToast.js +104 -0
- package/app/composables/useAuth.js +99 -0
- package/app/composables/useBanners.js +25 -0
- package/app/composables/useBuilderPreview.js +320 -0
- package/app/composables/useButtonStyle.js +107 -0
- package/app/composables/useCarousel.js +160 -0
- package/app/composables/useCart.js +109 -0
- package/app/composables/useCheckout.js +76 -0
- package/app/composables/useClassificationItems.js +269 -0
- package/app/composables/useClassificationPage.js +545 -0
- package/app/composables/useClassificationUrl.js +63 -0
- package/app/composables/useColorScheme.js +44 -0
- package/app/composables/useContactForm.js +53 -0
- package/app/composables/useContentTree.js +770 -0
- package/app/composables/useDragScroll.js +73 -0
- package/app/composables/useFooterSocials.js +107 -0
- package/app/composables/useInputStyle.js +45 -0
- package/app/composables/useLayoutShell.js +114 -0
- package/app/composables/useLazyVideo.js +36 -0
- package/app/composables/useMediaTypes.js +66 -0
- package/app/composables/useMediaUrl.js +71 -0
- package/app/composables/useMenu.js +100 -0
- package/app/composables/useNavStyle.js +219 -0
- package/app/composables/usePlaceholders.js +294 -0
- package/app/composables/usePolicies.js +26 -0
- package/app/composables/useProductCardMedia.js +135 -0
- package/app/composables/useProductCardStyle.js +132 -0
- package/app/composables/useProductDetail.js +982 -0
- package/app/composables/useProductGallery.js +166 -0
- package/app/composables/useProductItems.js +270 -0
- package/app/composables/useProductUrl.js +60 -0
- package/app/composables/useProductVariants.js +210 -0
- package/app/composables/useQuickShop.js +115 -0
- package/app/composables/useRelatedProducts.js +186 -0
- package/app/composables/useRenderContext.js +45 -0
- package/app/composables/useScopedCss.js +106 -0
- package/app/composables/useSearch.js +35 -0
- package/app/composables/useSectionData.js +462 -0
- package/app/composables/useSectionShell.js +113 -0
- package/app/composables/useSidebar.js +69 -0
- package/app/composables/useStoreData.js +57 -0
- package/app/composables/useStoreInit.js +52 -0
- package/app/composables/useStorefront.js +239 -0
- package/app/composables/useT.js +21 -0
- package/app/composables/useTextStyles.js +84 -0
- package/app/composables/useThemeControls.js +34 -0
- package/app/composables/useThemeSeo.js +91 -0
- package/app/composables/useThemeTokens.js +171 -0
- package/app/composables/useWishlist.js +66 -0
- package/app/logic/components/AnnouncementBar.js +118 -0
- package/app/logic/components/CardGrid.js +125 -0
- package/app/logic/components/CardList.js +98 -0
- package/app/logic/components/CartDrawer.js +273 -0
- package/app/logic/components/CartItem.js +31 -0
- package/app/logic/components/ClassificationBento.js +6 -0
- package/app/logic/components/ClassificationCard.js +6 -0
- package/app/logic/components/ClassificationCarousel.js +115 -0
- package/app/logic/components/ClassificationEditorial.js +41 -0
- package/app/logic/components/ClassificationFeatured.js +57 -0
- package/app/logic/components/ClassificationGrid.js +6 -0
- package/app/logic/components/ClassificationNav.js +61 -0
- package/app/logic/components/ClassificationPart.js +72 -0
- package/app/logic/components/FooterBlock.js +91 -0
- package/app/logic/components/FooterSocials.js +60 -0
- package/app/logic/components/HeaderDropdownList.js +29 -0
- package/app/logic/components/HeaderIcon.js +68 -0
- package/app/logic/components/HeaderLogo.js +39 -0
- package/app/logic/components/HeaderNav.js +268 -0
- package/app/logic/components/MobileMenu.js +280 -0
- package/app/logic/components/ProductBuyButtons.js +66 -0
- package/app/logic/components/ProductCard.js +160 -0
- package/app/logic/components/ProductFilters.js +37 -0
- package/app/logic/components/ProductGallery.js +19 -0
- package/app/logic/components/ProductInfoBlock.js +411 -0
- package/app/logic/components/ProductLightbox.js +12 -0
- package/app/logic/components/ProductQuantity.js +31 -0
- package/app/logic/components/ProductVariantPicker.js +125 -0
- package/app/logic/components/QuickBuy.js +194 -0
- package/app/logic/components/SearchOverlay.js +41 -0
- package/app/logic/components/SidebarMenuItem.js +39 -0
- package/app/logic/components/SiteFooter.js +476 -0
- package/app/logic/components/SiteHeader.js +266 -0
- package/app/logic/components/WhatsAppButton.js +75 -0
- package/app/logic/layouts/default.js +66 -0
- package/app/logic/sections/AnnouncementBarSection.js +6 -0
- package/app/logic/sections/BlockCarousel.js +103 -0
- package/app/logic/sections/ClassificationListSection.js +40 -0
- package/app/logic/sections/ContactFormSection.js +294 -0
- package/app/logic/sections/ContactInfoSection.js +419 -0
- package/app/logic/sections/ContentNode.js +189 -0
- package/app/logic/sections/CountdownSection.js +254 -0
- package/app/logic/sections/CustomHtmlSection.js +51 -0
- package/app/logic/sections/DividerSection.js +31 -0
- package/app/logic/sections/FaqSection.js +53 -0
- package/app/logic/sections/FeatureListSection.js +6 -0
- package/app/logic/sections/FeaturedProductSection.js +40 -0
- package/app/logic/sections/FooterSection.js +16 -0
- package/app/logic/sections/FooterUtilitiesSection.js +24 -0
- package/app/logic/sections/GallerySection.js +232 -0
- package/app/logic/sections/HeaderSection.js +24 -0
- package/app/logic/sections/HeroSection.js +24 -0
- package/app/logic/sections/LogoListSection.js +6 -0
- package/app/logic/sections/MainClassificationSection.js +473 -0
- package/app/logic/sections/MainProductSection.js +211 -0
- package/app/logic/sections/MainSearchSection.js +6 -0
- package/app/logic/sections/MapSection.js +92 -0
- package/app/logic/sections/MediaContentSection.js +38 -0
- package/app/logic/sections/MediaWithTextSection.js +238 -0
- package/app/logic/sections/NewsletterSection.js +126 -0
- package/app/logic/sections/ProductHighlightSection.js +201 -0
- package/app/logic/sections/ProductListSection.js +45 -0
- package/app/logic/sections/ProductRecommendationsSection.js +113 -0
- package/app/logic/sections/RichTextSection.js +6 -0
- package/app/logic/sections/ScrollingTextSection.js +69 -0
- package/app/logic/sections/SearchHeaderSection.js +22 -0
- package/app/logic/sections/SlideshowSection.js +118 -0
- package/app/logic/sections/SpacerSection.js +37 -0
- package/app/logic/sections/SplitShowcaseSection.js +6 -0
- package/app/logic/sections/TestimonialListSection.js +6 -0
- package/app/logic/sections/VideoSection.js +235 -0
- package/app/pages/[category]/[product].vue +196 -0
- package/app/pages/__builder-preview.vue +38 -0
- package/app/pages/categories/[slug].vue +208 -0
- package/app/pages/checkout.vue +1972 -0
- package/app/pages/forgot-password.vue +115 -0
- package/app/pages/index.vue +64 -0
- package/app/pages/login.vue +585 -0
- package/app/pages/order-detail.vue +442 -0
- package/app/pages/pages/[handle].vue +58 -0
- package/app/pages/privacy-policy.vue +46 -0
- package/app/pages/resetpassword.vue +138 -0
- package/app/pages/resetverificationcode.vue +163 -0
- package/app/pages/result.vue +203 -0
- package/app/pages/return-policy.vue +50 -0
- package/app/pages/search.vue +100 -0
- package/app/pages/shipping-policy.vue +46 -0
- package/app/pages/user-profile/components/AddAddressModal.vue +452 -0
- package/app/pages/user-profile/components/Whishlist.vue +137 -0
- package/app/pages/user-profile/components/left-bar.vue +233 -0
- package/app/pages/user-profile/components/orders.vue +162 -0
- package/app/pages/user-profile/components/profile-details.vue +566 -0
- package/app/pages/user-profile/components/settings.vue +194 -0
- package/app/pages/user-profile/index.vue +252 -0
- package/app/pages/verificationcode.vue +188 -0
- package/app/plugins/analytics.client.js +99 -0
- package/app/plugins/geo.js +24 -0
- package/app/plugins/reveal.client.js +53 -0
- package/app/plugins/toast.js +10 -0
- package/app/plugins/vue-query.js +5 -0
- package/app/services/ApiServices.js +74 -0
- package/app/services/AuthBus.js +162 -0
- package/app/services/Authentication/AuthenticationService.js +133 -0
- package/app/services/Authentication/TokenService.js +62 -0
- package/app/services/CartServices.js +91 -0
- package/app/services/CartToken.js +26 -0
- package/app/services/CategoryServices.js +54 -0
- package/app/services/CheckoutServices.js +113 -0
- package/app/services/HomeServices.js +133 -0
- package/app/services/NewsletterServices.js +29 -0
- package/app/services/PaymentServices.js +71 -0
- package/app/services/PoliciesServices.js +16 -0
- package/app/services/ProductServices.js +79 -0
- package/app/services/SearchServices.js +18 -0
- package/app/services/UserServices.js +105 -0
- package/app/services/UtilsServices.js +46 -0
- package/app/services/WishlistServices.js +51 -0
- package/app/services/storefront.service.js +57 -0
- package/app/stores/userStore.js +13 -0
- package/app/theme/categoryTree.js +68 -0
- package/app/theme/controls.js +88 -0
- package/app/theme/registry.js +56 -0
- package/app/utils/analytics.js +864 -0
- package/app/utils/device.js +50 -0
- package/app/utils/eventBus.js +24 -0
- package/app/utils/icon-presets.js +24 -0
- package/app/utils/productOffers.js +118 -0
- package/app/utils/requestCache.js +36 -0
- package/app/utils/scrollToError.js +59 -0
- package/app/utils/toPlainError.js +30 -0
- package/bin/osimart-theme.mjs +282 -0
- package/nuxt/osi-sections.ts +56 -0
- package/nuxt.config.ts +60 -0
- package/package.json +44 -4
- package/public/css/input.css +29 -0
- package/public/css/output.css +2716 -0
- package/public/favicon-32x32.png +0 -0
- package/public/robots.txt +2 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { NuxtLink } from "#components";
|
|
2
|
+
import { Autoplay, Pagination } from "swiper/modules";
|
|
3
|
+
|
|
4
|
+
export default function useLogic({ props }) {
|
|
5
|
+
const { s, t, shellStyle, innerStyle, align, customClass, heading } = useSectionShell(() => props.section);
|
|
6
|
+
const { mediaUrl } = useMediaUrl();
|
|
7
|
+
const placeholders = usePlaceholders();
|
|
8
|
+
const NuxtLinkComponent = NuxtLink;
|
|
9
|
+
|
|
10
|
+
const has = (v) => v !== undefined && v !== null && v !== "";
|
|
11
|
+
const num = (v, d) => (has(v) && Number.isFinite(Number(v)) ? Number(v) : d);
|
|
12
|
+
const px = (v) => (has(v) && Number.isFinite(Number(v)) ? `${Number(v)}px` : undefined);
|
|
13
|
+
|
|
14
|
+
/* ------------------------------------------------------------------ items */
|
|
15
|
+
|
|
16
|
+
const TILE_SIZES = ["NORMAL", "TALL", "WIDE", "LARGE"];
|
|
17
|
+
|
|
18
|
+
const items = computed(() => {
|
|
19
|
+
const rows = (props.section.blocks ?? [])
|
|
20
|
+
.filter((block) => !block.disabled)
|
|
21
|
+
.map((block) => {
|
|
22
|
+
const bs = block.settings ?? {};
|
|
23
|
+
const media = bs.MEDIA ?? block.media ?? block.relations?.media ?? bs.IMAGE ?? null;
|
|
24
|
+
return {
|
|
25
|
+
key: block.id ?? block.key,
|
|
26
|
+
src: mediaUrl(media),
|
|
27
|
+
caption: t(bs.CAPTION) || "",
|
|
28
|
+
url: bs.URL || bs.LINK_URL || null,
|
|
29
|
+
size: TILE_SIZES.includes(String(bs.SIZE).toUpperCase()) ? String(bs.SIZE).toUpperCase() : "NORMAL",
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Editor with no images yet: sample tiles so the layout is designable.
|
|
34
|
+
if (!rows.length && placeholders.allowed?.value) {
|
|
35
|
+
return Array.from({ length: 6 }, (_, i) => ({
|
|
36
|
+
key: `placeholder-${i}`, src: null, caption: "", url: null, placeholder: true,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
return rows;
|
|
40
|
+
});
|
|
41
|
+
/* ----------------------------------------------------------------- layout */
|
|
42
|
+
|
|
43
|
+
const layout = computed(() => {
|
|
44
|
+
const value = String(s.value.LAYOUT || "GRID").toUpperCase();
|
|
45
|
+
return ["GRID", "MASONRY", "CAROUSEL", "MARQUEE"].includes(value) ? value : "GRID";
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/* --------------------------------------------------------------- marquee */
|
|
49
|
+
const marqueeTrack = computed(() => {
|
|
50
|
+
const base = items.value;
|
|
51
|
+
if (!base.length) return [];
|
|
52
|
+
const repeat = Math.max(1, Math.ceil(10 / base.length));
|
|
53
|
+
// Copies get new render keys, but keep `baseKey` — the lightbox looks items
|
|
54
|
+
// up by their original key, and a suffixed key never matched.
|
|
55
|
+
const one = Array.from({ length: repeat }, (_, r) =>
|
|
56
|
+
base.map((i) => ({ ...i, baseKey: i.key, key: `${i.key}-r${r}` }))
|
|
57
|
+
).flat();
|
|
58
|
+
return [
|
|
59
|
+
...one.map((i) => ({ ...i, key: `${i.key}-a`, clone: false })),
|
|
60
|
+
...one.map((i) => ({ ...i, key: `${i.key}-b`, clone: true })),
|
|
61
|
+
];
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const marqueeVars = computed(() => {
|
|
65
|
+
const count = Math.max(1, marqueeTrack.value.length / 2);
|
|
66
|
+
return {
|
|
67
|
+
"--mq-duration": `${count * num(s.value.MARQUEE_SPEED, 4)}s`,
|
|
68
|
+
"--mq-h": px(s.value.MARQUEE_HEIGHT) ?? "280px",
|
|
69
|
+
"--mq-h-mobile": px(s.value.MARQUEE_HEIGHT_MOBILE) ?? "180px",
|
|
70
|
+
...marqueeWidths.value,
|
|
71
|
+
"--mq-align": MQ_ALIGN[String(s.value.MARQUEE_ALIGN || "CENTER").toUpperCase()] ?? "center",
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
const MQ_ALIGN = { TOP: "flex-start", CENTER: "center", BOTTOM: "flex-end" };
|
|
75
|
+
const marqueeMode = computed(() => {
|
|
76
|
+
const value = String(s.value.MARQUEE_SIZE_BY || "HEIGHT").toUpperCase();
|
|
77
|
+
return ["HEIGHT", "WIDTH", "SCREEN"].includes(value) ? value : "HEIGHT";
|
|
78
|
+
});
|
|
79
|
+
// SCREEN is WIDTH with a fluid value, so it shares the WIDTH class and CSS.
|
|
80
|
+
const marqueeSizeBy = computed(() => (marqueeMode.value === "HEIGHT" ? "height" : "width"));
|
|
81
|
+
const marqueeWidths = computed(() => {
|
|
82
|
+
const v = s.value;
|
|
83
|
+
if (marqueeMode.value === "SCREEN") {
|
|
84
|
+
const floor = px(v.MARQUEE_MIN_WIDTH) ?? "200px";
|
|
85
|
+
return {
|
|
86
|
+
"--mq-w": `max(${floor}, ${num(v.MARQUEE_SCREEN_WIDTH, 29)}vw)`,
|
|
87
|
+
"--mq-w-mobile": `max(${floor}, ${num(v.MARQUEE_SCREEN_WIDTH_MOBILE, 70)}vw)`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
"--mq-w": px(v.MARQUEE_WIDTH) ?? "240px",
|
|
92
|
+
"--mq-w-mobile": px(v.MARQUEE_WIDTH_MOBILE) ?? "160px",
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
const marqueeReverse = computed(() => String(s.value.MARQUEE_DIRECTION || "LEFT").toUpperCase() === "RIGHT");
|
|
96
|
+
const marqueePause = computed(() => s.value.MARQUEE_PAUSE_ON_HOVER !== false);
|
|
97
|
+
|
|
98
|
+
// Keep in sync with the RATIO options in builder/schemas.py.
|
|
99
|
+
const RATIOS = {
|
|
100
|
+
SQUARE: "1 / 1",
|
|
101
|
+
PORTRAIT: "3 / 4",
|
|
102
|
+
PORTRAIT_4_5: "4 / 5",
|
|
103
|
+
TALL: "2 / 3",
|
|
104
|
+
LANDSCAPE: "4 / 3",
|
|
105
|
+
WIDE: "16 / 9",
|
|
106
|
+
NATURAL: "auto",
|
|
107
|
+
};
|
|
108
|
+
// Same shapes as [width, height], for the bento tile maths.
|
|
109
|
+
const RATIO_PARTS = { SQUARE: [1, 1], PORTRAIT: [3, 4], PORTRAIT_4_5: [4, 5], TALL: [2, 3], LANDSCAPE: [4, 3], WIDE: [16, 9] };
|
|
110
|
+
// [columns, rows] each tile size spans.
|
|
111
|
+
const TILE_SPANS = { TALL: [1, 2], WIDE: [2, 1], LARGE: [2, 2] };
|
|
112
|
+
|
|
113
|
+
const isNatural = computed(() => s.value.RATIO === "NATURAL");
|
|
114
|
+
const bento = computed(
|
|
115
|
+
() => layout.value === "GRID" && !isNatural.value && items.value.some((i) => i.size !== "NORMAL")
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const subheading = computed(() => t(s.value.SUBHEADING));
|
|
119
|
+
const showCaptions = computed(() => !!s.value.SHOW_CAPTIONS);
|
|
120
|
+
const captionOverlay = computed(() => String(s.value.CAPTION_POSITION || "BELOW").toUpperCase() === "OVERLAY");
|
|
121
|
+
|
|
122
|
+
const vars = computed(() => {
|
|
123
|
+
const v = s.value;
|
|
124
|
+
const out = {
|
|
125
|
+
"--g-cols": num(v.COLUMNS, 3),
|
|
126
|
+
"--g-cols-tablet": num(v.COLUMNS_TABLET, Math.min(num(v.COLUMNS, 3), 3)),
|
|
127
|
+
"--g-cols-mobile": num(v.COLUMNS_MOBILE, 2),
|
|
128
|
+
"--g-gap": px(v.GAP) ?? "var(--osi-grid-gap, 16px)",
|
|
129
|
+
"--g-gap-mobile": px(v.GAP_MOBILE) ?? px(v.GAP) ?? "8px",
|
|
130
|
+
"--g-ratio": RATIOS[v.RATIO] ?? RATIOS.SQUARE,
|
|
131
|
+
"--g-ratio-mobile": RATIOS[v.RATIO_MOBILE] ?? RATIOS[v.RATIO] ?? RATIOS.SQUARE,
|
|
132
|
+
"--g-fit": String(v.IMAGE_FIT || "COVER").toLowerCase(),
|
|
133
|
+
"--g-radius": px(v.IMAGE_RADIUS) ?? "0px",
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
if (bento.value) {
|
|
137
|
+
// Per breakpoint: a column span that never exceeds the column count, and a
|
|
138
|
+
// frame ratio for each tile size so a spanned tile lines up with the grid
|
|
139
|
+
// (flex-grow absorbs the extra gap it covers).
|
|
140
|
+
const cols = { d: out["--g-cols"], t: out["--g-cols-tablet"], m: out["--g-cols-mobile"] };
|
|
141
|
+
const desk = RATIO_PARTS[v.RATIO] ?? RATIO_PARTS.SQUARE;
|
|
142
|
+
const mob = RATIO_PARTS[v.RATIO_MOBILE] ?? desk;
|
|
143
|
+
for (const bp of ["d", "t", "m"]) {
|
|
144
|
+
const maxSpan = Math.max(1, Math.min(2, cols[bp]));
|
|
145
|
+
const [w, h] = bp === "m" ? mob : desk;
|
|
146
|
+
out[`--g-span-${bp}`] = maxSpan;
|
|
147
|
+
for (const [size, [c, r]] of Object.entries(TILE_SPANS)) {
|
|
148
|
+
out[`--g-r-${size.toLowerCase()}-${bp}`] = `${w * Math.min(c, maxSpan)} / ${h * r}`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const rootClasses = computed(() => [
|
|
156
|
+
`osi-gallery--${layout.value.toLowerCase()}`,
|
|
157
|
+
`osi-gallery--hover-${String(s.value.HOVER_EFFECT || "ZOOM").toLowerCase()}`,
|
|
158
|
+
`osi-gallery--mq-by-${marqueeSizeBy.value}`,
|
|
159
|
+
{ "osi-gallery--natural": isNatural.value, "osi-gallery--bento": bento.value },
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
/* --------------------------------------------------------------- carousel */
|
|
163
|
+
|
|
164
|
+
const perView = computed(() => num(s.value.SLIDES_PER_VIEW, 4));
|
|
165
|
+
const overflows = computed(() => items.value.length > perView.value);
|
|
166
|
+
const loop = computed(() => !!s.value.CAROUSEL_LOOP && overflows.value);
|
|
167
|
+
|
|
168
|
+
const { slides, onSwiper, onSlideChange, goPrev, goNext, isBeginning, isEnd } = useCarousel({
|
|
169
|
+
items: () => items.value,
|
|
170
|
+
loop,
|
|
171
|
+
minLoopSlides: () => perView.value * 3,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const breakpoints = computed(() => ({
|
|
175
|
+
0: {
|
|
176
|
+
slidesPerView: num(s.value.SLIDES_PER_VIEW_MOBILE, 1.3),
|
|
177
|
+
spaceBetween: num(s.value.GAP_MOBILE, Math.min(num(s.value.GAP, 16), 12)),
|
|
178
|
+
},
|
|
179
|
+
640: { slidesPerView: num(s.value.SLIDES_PER_VIEW_TABLET, 2.5), spaceBetween: num(s.value.GAP, 16) },
|
|
180
|
+
1024: { slidesPerView: perView.value, spaceBetween: num(s.value.GAP, 16) },
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
const autoplay = computed(() =>
|
|
184
|
+
s.value.CAROUSEL_AUTOPLAY && overflows.value
|
|
185
|
+
? { delay: num(s.value.CAROUSEL_SPEED, 5) * 1000, disableOnInteraction: false, pauseOnMouseEnter: true }
|
|
186
|
+
: false
|
|
187
|
+
);
|
|
188
|
+
const showArrows = computed(() => s.value.SHOW_ARROWS !== false && overflows.value);
|
|
189
|
+
const showDots = computed(() => !!s.value.SHOW_DOTS && overflows.value);
|
|
190
|
+
|
|
191
|
+
/* --------------------------------------------------------------- lightbox */
|
|
192
|
+
|
|
193
|
+
// A linked image goes to its link; only unlinked ones open the lightbox, so a
|
|
194
|
+
// merchant's destination is never swallowed by a zoom overlay.
|
|
195
|
+
const lightboxEnabled = computed(() => s.value.ENABLE_LIGHTBOX !== false);
|
|
196
|
+
const realItems = computed(() => items.value.filter((i) => i.src));
|
|
197
|
+
const openIndex = ref(null);
|
|
198
|
+
const isOpen = computed(() => openIndex.value !== null);
|
|
199
|
+
|
|
200
|
+
useBodyScrollLock(isOpen);
|
|
201
|
+
|
|
202
|
+
function tagFor(item) {
|
|
203
|
+
if (item.url) return NuxtLinkComponent;
|
|
204
|
+
return lightboxEnabled.value && item.src ? "button" : "div";
|
|
205
|
+
}
|
|
206
|
+
function open(item) {
|
|
207
|
+
if (!lightboxEnabled.value || item.url || !item.src) return;
|
|
208
|
+
const key = item.baseKey ?? item.key;
|
|
209
|
+
const i = realItems.value.findIndex((x) => x.key === key);
|
|
210
|
+
openIndex.value = i >= 0 ? i : null;
|
|
211
|
+
}
|
|
212
|
+
function close() {
|
|
213
|
+
openIndex.value = null;
|
|
214
|
+
}
|
|
215
|
+
function step(delta) {
|
|
216
|
+
if (openIndex.value === null) return;
|
|
217
|
+
const count = realItems.value.length;
|
|
218
|
+
openIndex.value = (openIndex.value + delta + count) % count;
|
|
219
|
+
}
|
|
220
|
+
function onKeydown(event) {
|
|
221
|
+
if (!isOpen.value) return;
|
|
222
|
+
if (event.key === "Escape") close();
|
|
223
|
+
if (event.key === "ArrowRight") step(1);
|
|
224
|
+
if (event.key === "ArrowLeft") step(-1);
|
|
225
|
+
}
|
|
226
|
+
onMounted(() => window.addEventListener("keydown", onKeydown));
|
|
227
|
+
onBeforeUnmount(() => window.removeEventListener("keydown", onKeydown));
|
|
228
|
+
|
|
229
|
+
const current = computed(() => (openIndex.value === null ? null : realItems.value[openIndex.value]));
|
|
230
|
+
|
|
231
|
+
return { s, t, shellStyle, innerStyle, align, customClass, heading, mediaUrl, placeholders, NuxtLinkComponent, has, num, px, TILE_SIZES, items, layout, marqueeTrack, marqueeVars, MQ_ALIGN, marqueeMode, marqueeSizeBy, marqueeWidths, marqueeReverse, marqueePause, RATIOS, RATIO_PARTS, TILE_SPANS, isNatural, bento, subheading, showCaptions, captionOverlay, vars, rootClasses, perView, overflows, loop, slides, onSwiper, onSlideChange, goPrev, goNext, isBeginning, isEnd, breakpoints, autoplay, showArrows, showDots, lightboxEnabled, realItems, openIndex, isOpen, tagFor, open, close, step, onKeydown, current, Autoplay, Pagination };
|
|
232
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
export default function useLogic({ props }) {
|
|
3
|
+
// The Logo / Menu blocks' keys, so SiteHeader can tag those parts for the
|
|
4
|
+
// editor (hover, click to select). Their settings reach SiteHeader through
|
|
5
|
+
// the theme (useStorefront).
|
|
6
|
+
const blockKey = (type) => (props.section.blocks ?? []).find((b) => b.type === type)?.key ?? null;
|
|
7
|
+
provide(
|
|
8
|
+
"osiHeaderBlocks",
|
|
9
|
+
computed(() => ({
|
|
10
|
+
logo: blockKey("HEADER_LOGO"),
|
|
11
|
+
menu: blockKey("HEADER_MENU"),
|
|
12
|
+
// Per-menu mega menus (HEADER_MEGA): { key, settings } for HeaderNav.
|
|
13
|
+
megas: (props.section.blocks ?? [])
|
|
14
|
+
.filter((b) => b.type === "HEADER_MEGA" && !b.disabled)
|
|
15
|
+
.map((b) => ({ key: b.key, settings: b.settings ?? {} })),
|
|
16
|
+
}))
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// Checkout ("minimal" chrome) keeps the logo but drops the menu.
|
|
20
|
+
const route = useRoute();
|
|
21
|
+
const showNav = computed(() => (route.meta.chrome ?? "full") === "full");
|
|
22
|
+
|
|
23
|
+
return { blockKey, route, showNav };
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
export default function useLogic({ props }) {
|
|
3
|
+
const ALIGN_TO_POSITION = { LEFT: "CENTER_LEFT", CENTER: "CENTER", RIGHT: "CENTER_RIGHT" };
|
|
4
|
+
|
|
5
|
+
const asBanner = computed(() => {
|
|
6
|
+
const s = props.section.settings ?? {};
|
|
7
|
+
const align = String(s.HEADING_ALIGNMENT || "CENTER").toUpperCase();
|
|
8
|
+
return {
|
|
9
|
+
...props.section,
|
|
10
|
+
settings: {
|
|
11
|
+
...s,
|
|
12
|
+
LAYOUT: "OVERLAY",
|
|
13
|
+
HEADING_TAG: "H1",
|
|
14
|
+
FULL_BLEED: s.FULL_BLEED ?? true,
|
|
15
|
+
// AUTO: sized by the image shape (MEDIA_RATIO) instead of a screen share.
|
|
16
|
+
HEIGHT: s.HEIGHT === "AUTO" ? undefined : s.HEIGHT ?? (s.MEDIA_HEIGHT ? undefined : "LARGE"),
|
|
17
|
+
OVERLAY_POSITION: s.OVERLAY_POSITION || ALIGN_TO_POSITION[align] || "CENTER",
|
|
18
|
+
TEXT_ALIGNMENT: s.TEXT_ALIGNMENT || align,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return { ALIGN_TO_POSITION, asBanner };
|
|
24
|
+
}
|