@osimart/create-storefront 0.0.0-stage → 1.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.
Files changed (206) hide show
  1. package/README.md +80 -2
  2. package/THEME_DEV_API.md +80 -0
  3. package/app/app.vue +154 -0
  4. package/app/assets/css/osi-controls.css +824 -0
  5. package/app/components/SectionRenderer.vue +46 -0
  6. package/app/components/builder/BuilderOverlay.vue +582 -0
  7. package/app/components/checkout/ExtraFees.vue +329 -0
  8. package/app/components/checkout/PaymentHandoff.vue +388 -0
  9. package/app/components/common/Banner.vue +35 -0
  10. package/app/components/common/ConfirmDeleteModal.vue +117 -0
  11. package/app/components/common/DropdownSelect.vue +432 -0
  12. package/app/components/common/Loader.vue +32 -0
  13. package/app/components/common/OsiField.vue +218 -0
  14. package/app/components/common/PhoneNumInput.vue +434 -0
  15. package/app/composables/UseBodyScrollLock.js +122 -0
  16. package/app/composables/UseThemeGroup.js +575 -0
  17. package/app/composables/useAddToCart.js +87 -0
  18. package/app/composables/useAnalytics.js +15 -0
  19. package/app/composables/useAnnouncements.js +29 -0
  20. package/app/composables/useApiErrorToast.js +104 -0
  21. package/app/composables/useAuth.js +99 -0
  22. package/app/composables/useBanners.js +25 -0
  23. package/app/composables/useBuilderPreview.js +320 -0
  24. package/app/composables/useButtonStyle.js +107 -0
  25. package/app/composables/useCarousel.js +160 -0
  26. package/app/composables/useCart.js +109 -0
  27. package/app/composables/useCheckout.js +76 -0
  28. package/app/composables/useClassificationItems.js +269 -0
  29. package/app/composables/useClassificationPage.js +545 -0
  30. package/app/composables/useClassificationUrl.js +63 -0
  31. package/app/composables/useColorScheme.js +44 -0
  32. package/app/composables/useContactForm.js +53 -0
  33. package/app/composables/useContentTree.js +770 -0
  34. package/app/composables/useDragScroll.js +73 -0
  35. package/app/composables/useFooterSocials.js +107 -0
  36. package/app/composables/useInputStyle.js +45 -0
  37. package/app/composables/useLayoutShell.js +114 -0
  38. package/app/composables/useLazyVideo.js +36 -0
  39. package/app/composables/useMediaTypes.js +66 -0
  40. package/app/composables/useMediaUrl.js +71 -0
  41. package/app/composables/useMenu.js +100 -0
  42. package/app/composables/useNavStyle.js +219 -0
  43. package/app/composables/usePlaceholders.js +294 -0
  44. package/app/composables/usePolicies.js +26 -0
  45. package/app/composables/useProductCardMedia.js +135 -0
  46. package/app/composables/useProductCardStyle.js +132 -0
  47. package/app/composables/useProductDetail.js +982 -0
  48. package/app/composables/useProductGallery.js +166 -0
  49. package/app/composables/useProductItems.js +270 -0
  50. package/app/composables/useProductUrl.js +60 -0
  51. package/app/composables/useProductVariants.js +210 -0
  52. package/app/composables/useQuickShop.js +115 -0
  53. package/app/composables/useRelatedProducts.js +186 -0
  54. package/app/composables/useRenderContext.js +45 -0
  55. package/app/composables/useScopedCss.js +106 -0
  56. package/app/composables/useSearch.js +35 -0
  57. package/app/composables/useSectionData.js +462 -0
  58. package/app/composables/useSectionShell.js +113 -0
  59. package/app/composables/useSidebar.js +69 -0
  60. package/app/composables/useStoreData.js +57 -0
  61. package/app/composables/useStoreInit.js +52 -0
  62. package/app/composables/useStorefront.js +239 -0
  63. package/app/composables/useT.js +21 -0
  64. package/app/composables/useTextStyles.js +84 -0
  65. package/app/composables/useThemeControls.js +34 -0
  66. package/app/composables/useThemeSeo.js +91 -0
  67. package/app/composables/useThemeTokens.js +171 -0
  68. package/app/composables/useWishlist.js +66 -0
  69. package/app/logic/components/AnnouncementBar.js +118 -0
  70. package/app/logic/components/CardGrid.js +125 -0
  71. package/app/logic/components/CardList.js +98 -0
  72. package/app/logic/components/CartDrawer.js +273 -0
  73. package/app/logic/components/CartItem.js +31 -0
  74. package/app/logic/components/ClassificationBento.js +6 -0
  75. package/app/logic/components/ClassificationCard.js +6 -0
  76. package/app/logic/components/ClassificationCarousel.js +115 -0
  77. package/app/logic/components/ClassificationEditorial.js +41 -0
  78. package/app/logic/components/ClassificationFeatured.js +57 -0
  79. package/app/logic/components/ClassificationGrid.js +6 -0
  80. package/app/logic/components/ClassificationNav.js +61 -0
  81. package/app/logic/components/ClassificationPart.js +72 -0
  82. package/app/logic/components/FooterBlock.js +91 -0
  83. package/app/logic/components/FooterSocials.js +60 -0
  84. package/app/logic/components/HeaderDropdownList.js +29 -0
  85. package/app/logic/components/HeaderIcon.js +68 -0
  86. package/app/logic/components/HeaderLogo.js +39 -0
  87. package/app/logic/components/HeaderNav.js +268 -0
  88. package/app/logic/components/MobileMenu.js +280 -0
  89. package/app/logic/components/ProductBuyButtons.js +66 -0
  90. package/app/logic/components/ProductCard.js +160 -0
  91. package/app/logic/components/ProductFilters.js +37 -0
  92. package/app/logic/components/ProductGallery.js +19 -0
  93. package/app/logic/components/ProductInfoBlock.js +411 -0
  94. package/app/logic/components/ProductLightbox.js +12 -0
  95. package/app/logic/components/ProductQuantity.js +31 -0
  96. package/app/logic/components/ProductVariantPicker.js +125 -0
  97. package/app/logic/components/QuickBuy.js +194 -0
  98. package/app/logic/components/SearchOverlay.js +41 -0
  99. package/app/logic/components/SidebarMenuItem.js +39 -0
  100. package/app/logic/components/SiteFooter.js +476 -0
  101. package/app/logic/components/SiteHeader.js +266 -0
  102. package/app/logic/components/WhatsAppButton.js +75 -0
  103. package/app/logic/layouts/default.js +66 -0
  104. package/app/logic/sections/AnnouncementBarSection.js +6 -0
  105. package/app/logic/sections/BlockCarousel.js +103 -0
  106. package/app/logic/sections/ClassificationListSection.js +40 -0
  107. package/app/logic/sections/ContactFormSection.js +294 -0
  108. package/app/logic/sections/ContactInfoSection.js +419 -0
  109. package/app/logic/sections/ContentNode.js +189 -0
  110. package/app/logic/sections/CountdownSection.js +254 -0
  111. package/app/logic/sections/CustomHtmlSection.js +51 -0
  112. package/app/logic/sections/DividerSection.js +31 -0
  113. package/app/logic/sections/FaqSection.js +53 -0
  114. package/app/logic/sections/FeatureListSection.js +6 -0
  115. package/app/logic/sections/FeaturedProductSection.js +40 -0
  116. package/app/logic/sections/FooterSection.js +16 -0
  117. package/app/logic/sections/FooterUtilitiesSection.js +24 -0
  118. package/app/logic/sections/GallerySection.js +232 -0
  119. package/app/logic/sections/HeaderSection.js +24 -0
  120. package/app/logic/sections/HeroSection.js +24 -0
  121. package/app/logic/sections/LogoListSection.js +6 -0
  122. package/app/logic/sections/MainClassificationSection.js +473 -0
  123. package/app/logic/sections/MainProductSection.js +211 -0
  124. package/app/logic/sections/MainSearchSection.js +6 -0
  125. package/app/logic/sections/MapSection.js +92 -0
  126. package/app/logic/sections/MediaContentSection.js +38 -0
  127. package/app/logic/sections/MediaWithTextSection.js +238 -0
  128. package/app/logic/sections/NewsletterSection.js +126 -0
  129. package/app/logic/sections/ProductHighlightSection.js +201 -0
  130. package/app/logic/sections/ProductListSection.js +45 -0
  131. package/app/logic/sections/ProductRecommendationsSection.js +113 -0
  132. package/app/logic/sections/RichTextSection.js +6 -0
  133. package/app/logic/sections/ScrollingTextSection.js +69 -0
  134. package/app/logic/sections/SearchHeaderSection.js +22 -0
  135. package/app/logic/sections/SlideshowSection.js +118 -0
  136. package/app/logic/sections/SpacerSection.js +37 -0
  137. package/app/logic/sections/SplitShowcaseSection.js +6 -0
  138. package/app/logic/sections/TestimonialListSection.js +6 -0
  139. package/app/logic/sections/VideoSection.js +235 -0
  140. package/app/pages/[category]/[product].vue +196 -0
  141. package/app/pages/__builder-preview.vue +38 -0
  142. package/app/pages/categories/[slug].vue +208 -0
  143. package/app/pages/checkout.vue +1972 -0
  144. package/app/pages/forgot-password.vue +115 -0
  145. package/app/pages/index.vue +64 -0
  146. package/app/pages/login.vue +585 -0
  147. package/app/pages/order-detail.vue +442 -0
  148. package/app/pages/pages/[handle].vue +58 -0
  149. package/app/pages/privacy-policy.vue +46 -0
  150. package/app/pages/resetpassword.vue +138 -0
  151. package/app/pages/resetverificationcode.vue +163 -0
  152. package/app/pages/result.vue +203 -0
  153. package/app/pages/return-policy.vue +50 -0
  154. package/app/pages/search.vue +100 -0
  155. package/app/pages/shipping-policy.vue +46 -0
  156. package/app/pages/user-profile/components/AddAddressModal.vue +452 -0
  157. package/app/pages/user-profile/components/Whishlist.vue +137 -0
  158. package/app/pages/user-profile/components/left-bar.vue +233 -0
  159. package/app/pages/user-profile/components/orders.vue +162 -0
  160. package/app/pages/user-profile/components/profile-details.vue +566 -0
  161. package/app/pages/user-profile/components/settings.vue +194 -0
  162. package/app/pages/user-profile/index.vue +252 -0
  163. package/app/pages/verificationcode.vue +188 -0
  164. package/app/plugins/analytics.client.js +99 -0
  165. package/app/plugins/geo.ts +24 -0
  166. package/app/plugins/reveal.client.js +53 -0
  167. package/app/plugins/toast.js +10 -0
  168. package/app/plugins/vue-query.ts +5 -0
  169. package/app/services/ApiServices.js +74 -0
  170. package/app/services/AuthBus.js +162 -0
  171. package/app/services/Authentication/AuthenticationService.js +133 -0
  172. package/app/services/Authentication/TokenService.js +62 -0
  173. package/app/services/CartServices.js +91 -0
  174. package/app/services/CartToken.js +26 -0
  175. package/app/services/CategoryServices.js +54 -0
  176. package/app/services/CheckoutServices.js +113 -0
  177. package/app/services/HomeServices.js +133 -0
  178. package/app/services/NewsletterServices.js +29 -0
  179. package/app/services/PaymentServices.js +71 -0
  180. package/app/services/PoliciesServices.js +16 -0
  181. package/app/services/ProductServices.js +79 -0
  182. package/app/services/SearchServices.js +18 -0
  183. package/app/services/UserServices.js +105 -0
  184. package/app/services/UtilsServices.js +46 -0
  185. package/app/services/WishlistServices.js +51 -0
  186. package/app/services/storefront.service.js +57 -0
  187. package/app/stores/userStore.js +13 -0
  188. package/app/theme/categoryTree.js +68 -0
  189. package/app/theme/controls.js +88 -0
  190. package/app/theme/registry.js +56 -0
  191. package/app/utils/analytics.js +864 -0
  192. package/app/utils/device.js +50 -0
  193. package/app/utils/eventBus.js +24 -0
  194. package/app/utils/icon-presets.js +24 -0
  195. package/app/utils/productOffers.js +118 -0
  196. package/app/utils/requestCache.js +36 -0
  197. package/app/utils/scrollToError.js +59 -0
  198. package/app/utils/toPlainError.js +30 -0
  199. package/bin/osimart-theme.mjs +259 -0
  200. package/nuxt/osi-sections.ts +56 -0
  201. package/nuxt.config.ts +60 -0
  202. package/package.json +43 -3
  203. package/public/css/input.css +29 -0
  204. package/public/css/output.css +2716 -0
  205. package/public/favicon-32x32.png +0 -0
  206. 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
+ }
@@ -0,0 +1,6 @@
1
+
2
+ export default function useLogic() {
3
+ const ROOT_DEFAULTS = { DIRECTION: "VERTICAL", ALIGNMENT: "CENTER", POSITION: "TOP", GAP: 24 };
4
+
5
+ return { ROOT_DEFAULTS };
6
+ }