import React from 'react'; import { CompatibleModule } from '../utils/configurator-utils.js'; import { CarouselDisplayMode, CarouselLayout, VariantDisplayMode, VariantDisplayStyleOverlay } from '../types/config-enums.js'; import type { BedPartSizeFilterFlags, OnChangePayload, UnifiedPricePayload } from '../types/inject-config.js'; import type { StringReplacementsConfig } from '../types/string-replacements.js'; import { type ConfiguratorIframeScreenRect } from '../utils/configurator-dom-queries.js'; import { type StringInterpolationVars } from '../lib/strings/resolve-string-replacement.js'; export type DrawerSizes = 'closed' | 'small' | 'large'; export type AnimationState = 'unavailable' | 'open' | 'close' | 'loop' | 'stop'; export type TransitionProxyMode = 'opening' | 'closing' | null; export interface ProductFilters { [optionName: string]: { [filterKey: string]: Array<{ value: string; checked: boolean; }>; }; } export interface Product { id: string; name: string; price: number; discount: number; lowestPrice: number; metadata: any; } export interface Selection { id: string; name: string; sku?: string; thumbnail?: string; miniThumbnails?: { small: string; medium: string; large: string; }; price: number; blurHash: string; groupId?: string; filter?: { [key: string]: string[]; }; swatch?: Swatch; isVisible?: boolean; metadata?: { bedSize?: string; }; } export interface Group { id: string; name: string; selections: Selection[]; isVisible?: boolean; } export interface Option { id: string; name: string; groups: Group[]; hasNonOption?: boolean; isVisible?: boolean; } export interface ConfiguratorState { options: Option[]; selectedSelections: Array<{ optionId: string; groupId: string; selectionId: string; }>; configuratorSettings: { swatchSettings: SwatchRulesData; availableProductFilters: { [key: string]: string[]; }; }; snap2Objects: any[]; } export interface SizeOption { id: 'size'; name: 'size'; groups: [ { id: 'size-group'; selections: Array<{ id: string; name: string; price: number; thumbnail?: string; }>; } ]; hasNonOption?: boolean; } export interface Discount { percentage: number; amount: number; formattedAmount: string; } export interface Swatch { name: string; option: string; manufacturerId: string; description: string; sku: string; thumbnail: { blurHash: string; thumbnail: string; miniThumbnails: { large: string; medium: string; small: string; }; }; } export type SwatchRulesData = { freeSwatchLimit: number; canExeedFreeLimit: boolean; pricePerSwatch: number; minSwatches: number; maxSwatches: number; enabled: boolean; }; interface OV25UIContextType { shadowDOMs?: { mobileDrawer?: ShadowRoot; snap2CheckoutSheet?: ShadowRoot; configuratorViewControls?: ShadowRoot; popoverPortal?: ShadowRoot; modalPortal?: ShadowRoot; swatchbookPortal?: ShadowRoot; }; cssString?: string; products: Product[]; currentProductId?: string; configuratorState?: ConfiguratorState; selectedSelections: Array<{ optionId: string; groupId?: string; selectionId: string; }>; activeOptionId: string | null; quantity: number; price: number; subtotal: number; formattedPrice: string; formattedSubtotal: string; discount: Discount; galleryIndex: number; currentSku: any; range: any; drawerSize: DrawerSizes; isVariantsOpen: boolean; isDrawerOrDialogOpen: boolean; galleryCarouselFullscreenImage: string | null; setGalleryCarouselFullscreenImage: React.Dispatch>; /** Last frame from the iframe shown in the gallery slot while the iframe moves to sheet/modal. */ configuratorTransitionProxyBitmap: ImageBitmap | null; configuratorTransitionProxyMode: TransitionProxyMode; /** Captured sheet/modal iframe bounds; closing proxy is portaled here and slides/fades with the shell. */ configuratorClosingProxyRect: ConfiguratorIframeScreenRect | null; useInstantIframeCloseRestore: boolean; setConfiguratorTransitionProxyBitmap: (bitmap: ImageBitmap | null) => void; setConfiguratorTransitionProxyMode: (mode: TransitionProxyMode) => void; setConfiguratorClosingProxyRect: (rect: ConfiguratorIframeScreenRect | null) => void; setUseInstantIframeCloseRestore: React.Dispatch>; releaseConfiguratorTransitionProxy: () => void; /** Stacked gallery: next close sync should skip the legacy 500ms wait (instant iframe restore). Set in useIframePositioning; consumed in ProductGallery. */ stackedGalleryCloseSyncImmediateRef: React.MutableRefObject; arPreviewLink: string | null; error: Error | null; canAnimate: boolean; animationState: AnimationState; iframeRef: React.RefObject; isMobile: boolean; deferThreeD: boolean; /** True when gallery mounts in the hidden preload container (no page slot): modal open should not wait on iframe ImageBitmap. */ configuratorGalleryIsDeferred: boolean; showOptional: boolean; hidePricing: boolean; disableAddToCart: boolean; hideAr: boolean; productLink: string | null; apiKey: string; configurationUuid: string | null; stringReplacements?: StringReplacementsConfig; /** OV25 bed iframe: `bedAllowNone` query segment (omit when all parts may use None). */ bedAllowNoneQueryValue?: string; /** Dining iframe display option. Omitted unless explicitly false. */ diningShowAttachmentPoints?: boolean; /** Bed iframe: latest size from `CURRENT_BED_SIZE` postMessage (`null` until first message or non-bed). */ currentBedSize: string | null; /** When true for a part, selections in that option hide if `metadata.bedSize` ≠ {@link currentBedSize}. */ bedFilterSelectionsByCurrentSize: BedPartSizeFilterFlags; /** Display symbol for prices; see inject `flags.currencySymbol`. */ currencySymbol: string; buyNowFunction: (payload?: OnChangePayload) => void; addToBasketFunction: (payload?: OnChangePayload) => void; buySwatches: () => void; images?: string[]; logoURL?: string; hideLogo: boolean; isProductGalleryStacked: boolean; carouselLayout: CarouselLayout; carouselLayoutMobile: CarouselLayout; carouselMaxImagesDesktop?: number; carouselMaxImagesMobile?: number; showCarousel: boolean; mobileLogoURL?: string; uniqueId?: string; initialiseMenuUsesExternalSelector?: boolean; currentProduct?: Product; sizeOption: SizeOption; activeOption?: Option; availableProductFilters?: ProductFilters; optionHasVisibleFilters: (option: { id: string; name: string; }) => boolean; showFilters?: boolean; allOptions: (Option | SizeOption)[]; allOptionsWithoutModules: (Option | SizeOption)[]; variantPanelOptions: (Option | SizeOption)[]; galleryIndexToUse: number; filteredActiveOption?: Option | null; searchQueries: { [optionId: string]: string; }; selectedSwatches: Swatch[]; swatchRulesData: SwatchRulesData; isSwatchBookOpen: boolean; swatchBookFlash: 'destructive' | 'cta' | null; setSwatchBookFlash: React.Dispatch>; hasSelectionsWithSwatches: boolean; availableCameras: Array<{ id: string; displayName: string; }>; availableLights: Array<{ id: string; displayName: string; }>; isSnap2Mode: boolean; /** Latest normalized price payload from CURRENT_PRICE; drives Snap2 checkout sheet line items. */ commercePriceSnapshot: UnifiedPricePayload | null; isSnap2CheckoutSheetOpen: boolean; setIsSnap2CheckoutSheetOpen: React.Dispatch>; snap2SaveResponse: { success: boolean; shareUrl?: string; error?: string; } | null; isModalOpen: boolean; controlsHidden: boolean; hasConfigureButton: boolean; useInlineVariantControls: boolean; configuratorDisplayMode: 'inline' | 'sheet' | 'drawer' | 'variants-only-sheet' | 'modal' | 'inline-sheet'; configuratorDisplayModeMobile: 'inline' | 'drawer' | 'modal' | 'variants-only-sheet'; useSimpleVariantsSelector: boolean; /** Drawer trigger: single Configure button or per-option buttons (ProductOptionsGroup). */ configuratorTriggerStyle: 'single-button' | 'split-buttons'; variantDisplayStyleMobile: VariantDisplayMode; variantDisplayStyleInline: VariantDisplayStyleOverlay; variantDisplayStyleInlineMobile: VariantDisplayStyleOverlay; variantDisplayStyleOverlay: VariantDisplayStyleOverlay; variantDisplayStyleOverlayMobile: VariantDisplayStyleOverlay; snap2VariantSheetSide: 'left' | 'right'; snap2ModuleSheetPosition: 'left' | 'right' | 'bottom'; snap2ModulesEmbedInVariantSheet: boolean; shareDialogTrigger: 'none' | 'save-button' | 'modal-close'; skipNextDrawerCloseRef: React.MutableRefObject; skipNextShareClickRef: React.MutableRefObject; expandToOptionIdOnOpen: string | null; preloading: boolean; setPreloading: (preloading: boolean) => void; resetIframe: () => void; iframeResetKey: number; configureHandlerRef: React.MutableRefObject<(() => void) | null>; compatibleModules: CompatibleModule[] | null; isModuleSelectionLoading: boolean; selectedModuleType: 'all' | 'middle' | 'corner' | 'end'; isModulePanelOpen: boolean; setProducts: React.Dispatch>; setCurrentProductId: React.Dispatch>; setConfiguratorState: React.Dispatch>; setSelectedSelections: React.Dispatch>>; setActiveOptionId: React.Dispatch>; setQuantity: React.Dispatch>; setPrice: React.Dispatch>; setSubtotal: React.Dispatch>; setDiscount: React.Dispatch>; setGalleryIndex: React.Dispatch>; setCurrentSku: React.Dispatch>; setRange: React.Dispatch>; setDrawerSize: React.Dispatch>; setIsVariantsOpen: React.Dispatch>; setIsDrawerOrDialogOpen: React.Dispatch>; setArPreviewLink: React.Dispatch>; setError: React.Dispatch>; setCanAnimate: React.Dispatch>; setAnimationState: React.Dispatch>; setAvailableProductFilters: React.Dispatch>; setSearchQuery: (optionId: string, query: string) => void; setSelectedSwatches: React.Dispatch>; setSwatchRulesData: React.Dispatch>; toggleSwatch: (swatch: Swatch) => void; isSwatchSelected: (swatch: Swatch) => boolean; setIsSwatchBookOpen: React.Dispatch>; setAvailableCameras: React.Dispatch>>; selectCamera: (cameraId: string) => void; setAvailableLights: React.Dispatch>>; selectLightGroup: (subGroupId: string) => void; setSnap2SaveResponse: React.Dispatch>; setIsModalOpen: React.Dispatch>; setControlsHidden: React.Dispatch>; setHasConfigureButton: React.Dispatch>; setShareDialogTrigger: React.Dispatch>; toggleHideAll: () => void; setCompatibleModules: React.Dispatch>; setIsModuleSelectionLoading: React.Dispatch>; setSelectedModuleType: React.Dispatch>; setIsModulePanelOpen: React.Dispatch>; handleSelectionSelect: (selection: Selection, optionId?: string) => void; handleOptionClick: (optionId: string) => void; handleNextOption: () => void; handlePreviousOption: () => void; getSelectedValue: (option: Option | SizeOption) => string; toggleAR: () => void; cleanupConfigurator: () => void; applySearchAndFilters: (option: Option | SizeOption, optionId: string) => Option | SizeOption; /** Open the variant configurator (closes swatch book, opens variants drawer). Optionally pass an option name (case insensitive) to open on that option. */ openConfigurator: (optionName?: string) => void; /** Open configurator or Snap2 modal based on product type. For useSimpleVariantsSelector single button. */ openConfiguratorOrSnap2: () => void; /** Close the variant configurator drawer. For use by custom buttons. */ closeConfigurator: () => void; /** Open the swatch book. Exposed for custom buttons. */ openSwatchBook: () => void; /** Close the swatch book. Exposed for custom buttons. */ closeSwatchBook: () => void; /** Resolve a configured replacement by key. */ getString: (key: string, vars?: StringInterpolationVars, fallback?: string) => string; } export declare const createUniqueContext: (uniqueId: string) => React.Context; export declare const OV25UIProvider: React.FC<{ children: React.ReactNode; productLink: string | null; apiKey: string; configurationUuid: string; stringReplacements?: StringReplacementsConfig; bedAllowNoneQueryValue?: string; diningShowAttachmentPoints?: boolean; bedFilterSelectionsByCurrentSize?: BedPartSizeFilterFlags; buyNowFunction: (payload?: OnChangePayload) => void; addToBasketFunction: (payload?: OnChangePayload) => void; buySwatchesFunction: (swatches: Swatch[], swatchRulesData: SwatchRulesData) => void; onChange?: (payload: OnChangePayload) => void; images?: string[]; deferThreeD?: boolean; showOptional?: boolean; hidePricing?: boolean; disableAddToCart?: boolean; hideAr?: boolean; forceMobile?: boolean; logoURL?: string; hideLogo?: boolean; isProductGalleryStacked: boolean; carouselDisplayMode?: CarouselDisplayMode; carouselDisplayModeMobile?: CarouselDisplayMode; carouselMaxImagesDesktop?: number; carouselMaxImagesMobile?: number; /** @deprecated Use carouselDisplayMode */ carouselLayout?: CarouselDisplayMode; showCarousel?: boolean; hasConfigureButton: boolean; mobileLogoURL?: string; uniqueId?: string; useInlineVariantControls?: boolean; useInlineVariantControlsMobile?: boolean; configuratorDisplayMode?: 'inline' | 'sheet' | 'drawer' | 'modal' | 'variants-only-sheet' | 'inline-sheet'; configuratorDisplayModeMobile?: 'inline' | 'drawer' | 'modal' | 'variants-only-sheet'; useSimpleVariantsSelector?: boolean; configuratorTriggerStyle?: 'single-button' | 'split-buttons'; configuratorTriggerStyleMobile?: 'single-button' | 'split-buttons'; variantDisplayStyle?: VariantDisplayMode; variantDisplayStyleMobile?: VariantDisplayMode; variantDisplayStyleInline?: VariantDisplayStyleOverlay; variantDisplayStyleInlineMobile?: VariantDisplayStyleOverlay; variantDisplayStyleOverlay?: VariantDisplayStyleOverlay; variantDisplayStyleOverlayMobile?: VariantDisplayStyleOverlay; /** Normalized option keys (id or name, lowercase) to hide from variant UI; defaults stay from iframe. */ hideVariantOptions?: string[]; shadowDOMs?: { mobileDrawer?: ShadowRoot; snap2CheckoutSheet?: ShadowRoot; configuratorViewControls?: ShadowRoot; popoverPortal?: ShadowRoot; modalPortal?: ShadowRoot; swatchbookPortal?: ShadowRoot; }; cssString?: string; configuratorGalleryIsDeferred?: boolean; /** Display symbol for iframe price strings; default £. */ currencySymbol?: string; snap2VariantSheetSideDesktop?: 'left' | 'right'; snap2VariantSheetSideMobile?: 'left' | 'right'; snap2ModulePanelPositionDesktop?: 'left' | 'right' | 'bottom'; snap2ModulePanelPositionMobile?: 'left' | 'right' | 'bottom'; initialiseMenuUsesExternalSelector?: boolean; }>; export declare const useOV25UI: () => OV25UIContextType; export {};