bitsnap-react 1.18.0 → 1.19.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/AGENTS.md +106 -0
- package/README.md +98 -0
- package/dist/BitsnapCart-E72W5RRZ.mjs +2 -0
- package/dist/BitsnapCart-LHJ6KOTV.css +2 -0
- package/dist/BitsnapCart-LHJ6KOTV.css.map +1 -0
- package/dist/BitsnapCart-Q2HER6GU.js +2 -0
- package/dist/BitsnapCart-Q2HER6GU.js.map +1 -0
- package/dist/CartProvider-677CE2FI.js +2 -0
- package/dist/CartProvider-677CE2FI.js.map +1 -0
- package/dist/CartProvider-YPDOFSYC.mjs +2 -0
- package/dist/{PanelModelFiles-JZFSF7BF.mjs → PanelModelFiles-4B2HYGIE.mjs} +2 -2
- package/dist/{PanelModelFiles-7HPXWONN.js → PanelModelFiles-EKQRLF46.js} +2 -2
- package/dist/PanelModelFiles-EKQRLF46.js.map +1 -0
- package/dist/analytics.js +1 -1
- package/dist/analytics.js.map +1 -1
- package/dist/analytics.mjs +1 -1
- package/dist/catalog.js +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/catalog.mjs +1 -1
- package/dist/chat.css +2 -0
- package/dist/chat.css.map +1 -0
- package/dist/chat.d.mts +172 -0
- package/dist/chat.d.ts +172 -0
- package/dist/chat.js +2 -0
- package/dist/chat.js.map +1 -0
- package/dist/chat.mjs +2 -0
- package/dist/chat.mjs.map +1 -0
- package/dist/chunk-2FD2QP4Y.js +2 -0
- package/dist/chunk-2FD2QP4Y.js.map +1 -0
- package/dist/chunk-5JRUPLXY.mjs +2 -0
- package/dist/chunk-5JRUPLXY.mjs.map +1 -0
- package/dist/chunk-5M6M7FAR.mjs +2 -0
- package/dist/chunk-5M6M7FAR.mjs.map +1 -0
- package/dist/chunk-6Q3VGPSF.js.map +1 -1
- package/dist/chunk-AAC576BC.mjs +5 -0
- package/dist/chunk-AAC576BC.mjs.map +1 -0
- package/dist/{chunk-4OVOKGGS.mjs → chunk-AB37DWOK.mjs} +2 -2
- package/dist/{chunk-ZATPBYOA.js → chunk-BUYTK7KM.js} +2 -2
- package/dist/chunk-BUYTK7KM.js.map +1 -0
- package/dist/{chunk-DP2NOHCI.js → chunk-D2ASWUXM.js} +2 -2
- package/dist/chunk-D2ASWUXM.js.map +1 -0
- package/dist/chunk-DDIEFKNP.mjs +2 -0
- package/dist/{chunk-ZHTMK5TA.mjs.map → chunk-DDIEFKNP.mjs.map} +1 -1
- package/dist/{chunk-4HGZZJEL.js → chunk-DOJICJCQ.js} +2 -2
- package/dist/chunk-DOJICJCQ.js.map +1 -0
- package/dist/chunk-EMR4YXNY.mjs +2 -0
- package/dist/chunk-EMR4YXNY.mjs.map +1 -0
- package/dist/{chunk-JA62KNAQ.js → chunk-GJOGATP6.js} +2 -2
- package/dist/chunk-GJOGATP6.js.map +1 -0
- package/dist/chunk-GQ5OKU74.js +2 -0
- package/dist/chunk-GQ5OKU74.js.map +1 -0
- package/dist/chunk-JSDGZ4NY.js.map +1 -1
- package/dist/chunk-KHQEDQYF.js +5 -0
- package/dist/chunk-KHQEDQYF.js.map +1 -0
- package/dist/chunk-KOYBZORL.js.map +1 -1
- package/dist/chunk-LGPNJ5XE.js +2 -0
- package/dist/chunk-LGPNJ5XE.js.map +1 -0
- package/dist/chunk-MDAFPGPA.js +2 -0
- package/dist/chunk-MDAFPGPA.js.map +1 -0
- package/dist/{chunk-JHJ3ZZF4.mjs → chunk-MMLBQHBM.mjs} +2 -2
- package/dist/chunk-MWWEPEU2.js +2 -0
- package/dist/chunk-MWWEPEU2.js.map +1 -0
- package/dist/{chunk-GEBRXFT3.mjs → chunk-N523LFAM.mjs} +2 -2
- package/dist/chunk-NWXZ45TV.js +2 -0
- package/dist/chunk-NWXZ45TV.js.map +1 -0
- package/dist/chunk-OFPRIENJ.mjs +2 -0
- package/dist/chunk-OFPRIENJ.mjs.map +1 -0
- package/dist/chunk-OR566YJI.mjs +2 -0
- package/dist/chunk-OR566YJI.mjs.map +1 -0
- package/dist/chunk-Q337IQNA.js +2 -0
- package/dist/chunk-Q337IQNA.js.map +1 -0
- package/dist/chunk-SCEWYZUZ.mjs +2 -0
- package/dist/chunk-SCEWYZUZ.mjs.map +1 -0
- package/dist/{chunk-CF45RDMD.mjs → chunk-SSSF64M7.mjs} +2 -2
- package/dist/{chunk-CF45RDMD.mjs.map → chunk-SSSF64M7.mjs.map} +1 -1
- package/dist/chunk-W3RGQYPD.js.map +1 -1
- package/dist/chunk-WG54EHST.mjs +2 -0
- package/dist/chunk-WG54EHST.mjs.map +1 -0
- package/dist/chunk-XEXPQFKN.mjs +2 -0
- package/dist/chunk-XEXPQFKN.mjs.map +1 -0
- package/dist/chunk-YV2C276H.js +2 -0
- package/dist/chunk-YV2C276H.js.map +1 -0
- package/dist/consent.js.map +1 -1
- package/dist/course.d.mts +2 -14
- package/dist/course.d.ts +2 -14
- package/dist/course.js +1 -1
- package/dist/course.js.map +1 -1
- package/dist/course.mjs +1 -1
- package/dist/course.mjs.map +1 -1
- package/dist/embed/analytics.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +288 -4
- package/dist/index.d.ts +288 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/methods-64D5DD6K.js +2 -0
- package/dist/methods-64D5DD6K.js.map +1 -0
- package/dist/methods-NQA3DFLE.mjs +2 -0
- package/dist/methods-NQA3DFLE.mjs.map +1 -0
- package/dist/model-files-client-P4PORZJ6.js.map +1 -1
- package/dist/models.js.map +1 -1
- package/dist/returns.js +1 -1
- package/dist/returns.js.map +1 -1
- package/dist/returns.mjs +1 -1
- package/dist/texts-9kfgClsA.d.mts +14 -0
- package/dist/texts-9kfgClsA.d.ts +14 -0
- package/dist/{types-D6LsoycW.d.mts → types-B-sjyZQs.d.mts} +7 -1
- package/dist/{types-ClFCVzk_.d.ts → types-iRe6ix2y.d.ts} +7 -1
- package/dist/viewer3d.js.map +1 -1
- package/dist/viewer3d.mjs +1 -1
- package/dist/viewer3d.mjs.map +1 -1
- package/dist/widgets-BT2LN2Nq.d.mts +547 -0
- package/dist/widgets-BVOSj9fU.d.ts +547 -0
- package/package.json +18 -3
- package/dist/CartProvider-OEI526IN.js +0 -2
- package/dist/CartProvider-OEI526IN.js.map +0 -1
- package/dist/CartProvider-RYAA3JDA.mjs +0 -2
- package/dist/PanelModelFiles-7HPXWONN.js.map +0 -1
- package/dist/chunk-4HGZZJEL.js.map +0 -1
- package/dist/chunk-5X7TP3TQ.mjs +0 -2
- package/dist/chunk-5X7TP3TQ.mjs.map +0 -1
- package/dist/chunk-7RXW6XFY.js +0 -2
- package/dist/chunk-7RXW6XFY.js.map +0 -1
- package/dist/chunk-DP2NOHCI.js.map +0 -1
- package/dist/chunk-EVIDNS22.mjs +0 -2
- package/dist/chunk-EVIDNS22.mjs.map +0 -1
- package/dist/chunk-G4V2KWTV.js +0 -2
- package/dist/chunk-G4V2KWTV.js.map +0 -1
- package/dist/chunk-GWF3MRUP.js +0 -2
- package/dist/chunk-GWF3MRUP.js.map +0 -1
- package/dist/chunk-JA62KNAQ.js.map +0 -1
- package/dist/chunk-UPAOIY4R.mjs +0 -2
- package/dist/chunk-UPAOIY4R.mjs.map +0 -1
- package/dist/chunk-V5ECGBFW.js +0 -5
- package/dist/chunk-V5ECGBFW.js.map +0 -1
- package/dist/chunk-VNV5BD4X.js +0 -2
- package/dist/chunk-VNV5BD4X.js.map +0 -1
- package/dist/chunk-VNYHJT6V.mjs +0 -2
- package/dist/chunk-VNYHJT6V.mjs.map +0 -1
- package/dist/chunk-WJO5GSY3.mjs +0 -5
- package/dist/chunk-WJO5GSY3.mjs.map +0 -1
- package/dist/chunk-ZATPBYOA.js.map +0 -1
- package/dist/chunk-ZHTMK5TA.mjs +0 -2
- package/dist/methods-AULQDKVS.mjs +0 -2
- package/dist/methods-PMH5VQ25.js +0 -2
- package/dist/methods-PMH5VQ25.js.map +0 -1
- /package/dist/{CartProvider-RYAA3JDA.mjs.map → BitsnapCart-E72W5RRZ.mjs.map} +0 -0
- /package/dist/{methods-AULQDKVS.mjs.map → CartProvider-YPDOFSYC.mjs.map} +0 -0
- /package/dist/{PanelModelFiles-JZFSF7BF.mjs.map → PanelModelFiles-4B2HYGIE.mjs.map} +0 -0
- /package/dist/{chunk-4OVOKGGS.mjs.map → chunk-AB37DWOK.mjs.map} +0 -0
- /package/dist/{chunk-JHJ3ZZF4.mjs.map → chunk-MMLBQHBM.mjs.map} +0 -0
- /package/dist/{chunk-GEBRXFT3.mjs.map → chunk-N523LFAM.mjs.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/emems/.t3/worktrees/bitsnap-react/emems-chat-v1/dist/chunk-YV2C276H.js","../src/components/checkout/BitsnapCart.tsx","../src/components/checkout/CartComponent.tsx","../src/components/checkout/appearance.ts","../src/components/checkout/appearance.context.tsx","../src/components/checkout/CartComponentContent.tsx","../src/ui/select.tsx","../src/components/checkout/CountrySelector.tsx"],"names":["CHECKOUT_SLOT_NAMES","CHECKOUT_TOKEN_VARIABLES","CHECKOUT_THEME_PRESETS","CHECKOUT_SHADCN_VARIABLES","resolveDropdownStyle","tokens","resolveCheckoutAppearance","appearance","resolved","resolveAppearanceCore","getCheckoutSlotAppearance","slot","getSlotAppearance","CheckoutAppearanceContext","createContext","CheckoutAppearanceProvider","children","useMemo","jsx","useCheckoutAppearance","useContext","useCheckoutSlot","Select","props","SelectValue","selectTriggerThemeClasses","SelectTrigger","className","size","theme","jsxs","cn","ChevronDownIcon","SelectContent","position","align","SelectScrollUpButton","SelectScrollDownButton","SelectItem","CheckIcon","ChevronUpIcon","CountrySelector","id","open","disabled","onToggle","onChange","selectedValue","countries","query","setQuery","useState","isCustomized","boundaryClassName","boundaryStyle","dropdownStyle","countryTrigger","countryDropdown","countrySearch","countryOption","searchCountryText","useCheckoutText","noCountriesFoundText","useEffect","filteredCountries","country","selectedCountry","el","value","isOpen","e"],"mappings":"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","file":"/home/emems/.t3/worktrees/bitsnap-react/emems-chat-v1/dist/chunk-YV2C276H.js","sourcesContent":[null,"import { useEffect, useId, useRef } from \"react\";\nimport zod from \"zod\";\nimport {\n InstrumentProvider,\n useCartInstrument,\n useInstrument,\n type AnalyticsOption,\n} from \"@/src/analytics/instrument\";\nimport type { CheckoutAppearance } from \"./appearance\";\nimport type { CheckoutTexts } from \"./texts\";\nimport CartComponent from \"./CartComponent\";\nimport { getCheckoutMethods, getProjectID, setProjectID } from \"./CartProvider\";\nimport { sendAnalyticEvent } from \"./frontent.analytics\";\nimport { isErr } from \"./lib/err\";\nimport { useCheckoutStore } from \"./state\";\nimport { getLocale, setLocale } from \"./locale\";\nimport { getCheckoutText, type CheckoutTextKey } from \"./texts\";\n\nenum CartEvent {\n ADD_TO_CART = \"ADD_TO_CART\",\n}\n\nconst cartAddToCartSchema = zod.object({\n id: zod.string(),\n isSubscription: zod.boolean().default(false),\n quantity: zod.number(),\n metadata: zod.record(zod.string(), zod.string().optional()).optional(),\n url: zod.string().optional(),\n});\n\ntype CartAddToCartEvent = zod.infer<typeof cartAddToCartSchema>;\n\nexport type BitsnapCartProps = {\n projectID: string;\n locale?: string;\n children?: React.ReactNode;\n onVisibleChange?: (isVisible: boolean) => void;\n className?: string;\n numberOfProductsInCartOptions?: {\n style?: React.CSSProperties;\n isVisible?: boolean;\n };\n /**\n * Optional checkout appearance. Omitting it (or passing `{}`) keeps the\n * current presentation; every token and element slot is partial.\n */\n appearance?: CheckoutAppearance;\n /**\n * Optional text overrides. Any key given here replaces the built-in string\n * whatever the locale; omitted keys keep the `pl`/`en` defaults.\n */\n texts?: Partial<CheckoutTexts>;\n /**\n * Usage statistics for your Bitsnap dashboard (cart opened, checkout\n * started, add to cart). On by default; `false` turns it off. An object\n * lets you forward every event: `{ onEvent }`. Nothing is stored on the\n * visitor's device.\n */\n analytics?: AnalyticsOption;\n};\n\nconst visuallyHidden: React.CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n margin: -1,\n padding: 0,\n overflow: \"hidden\",\n clip: \"rect(0, 0, 0, 0)\",\n whiteSpace: \"nowrap\",\n border: 0,\n};\n\nfunction BitsnapCart({\n projectID,\n locale,\n children,\n onVisibleChange,\n className,\n numberOfProductsInCartOptions,\n appearance,\n texts,\n analytics,\n}: BitsnapCartProps) {\n const instrument = useInstrument(projectID, analytics, \"cart\");\n // Cart changes made outside of components (`Bitsnap.addProductToCart`) are reported here.\n useCartInstrument(instrument);\n const { isCartVisible, showCart, hideCart, numberOfProductsInCart } =\n useCheckoutStore();\n const panelId = `bitsnap-cart-${useId()}`;\n const text = (key: CheckoutTextKey) =>\n getCheckoutText(key, locale ?? getLocale(), texts);\n // The `window.cart` handlers are created once and must read the latest props.\n const textRef = useRef(text);\n textRef.current = text;\n\n /** Shows a message through the error area of the cart drawer. */\n function showCartNotice(key: CheckoutTextKey) {\n useCheckoutStore.getState().setNotice(textRef.current(key));\n showCart();\n }\n\n function sentPostMessageToIframe(msg: unknown) {\n const iframes = document.querySelectorAll(\"iframe\");\n\n iframes.forEach((iframe) => {\n iframe.contentWindow?.postMessage(msg, \"*\");\n });\n }\n\n useEffect(() => {\n onVisibleChange?.(isCartVisible);\n if (isCartVisible) {\n sendAnalyticEvent({\n event: \"viewCart\",\n });\n }\n }, [isCartVisible]);\n\n useEffect(() => {\n setProjectID(projectID);\n }, [projectID]);\n\n useEffect(() => {\n setLocale(locale);\n }, [locale]);\n\n useEffect(() => {\n if (!(\"cart\" in window)) {\n const projectID = getProjectID();\n if (projectID == null) {\n console.warn(\"There is no project ID configured.\");\n return;\n }\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n window[\"cart\"] = {\n buyProduct: async (args: unknown) => {\n try {\n const payload = zod\n .object({\n productID: zod.string(),\n email: zod.string().optional(),\n name: zod.string().optional(),\n country: zod.string().optional(),\n marketingAgreement: zod.boolean().optional(),\n locale: zod.string().optional(),\n })\n .parse(args);\n const methods = getCheckoutMethods(projectID);\n try {\n const products = await methods.getProducts();\n if (isErr(products) == false) {\n sendAnalyticEvent({\n event: \"initiateCheckout\",\n items: products.map((el) => ({\n id: el.productID,\n name: el.details?.name ?? \"\",\n price: el.details?.price ?? 0,\n quantity: el.quantity,\n currency: el.details?.currency ?? \"PLN\",\n })),\n });\n }\n } catch (e) {}\n\n const result = await methods.justRedirectToPayment(payload);\n\n if (isErr(result) == false) {\n window.location.href = result.url;\n } else {\n showCartNotice(\"redirectFailed\");\n }\n } catch (e) {\n console.warn(e);\n }\n },\n };\n }\n }, []);\n\n async function handleAddingToCart(id: string, payload?: unknown) {\n const projectID = getProjectID();\n if (projectID == null) {\n console.warn(\"There is no project ID configured.\");\n return;\n }\n\n try {\n const data = cartAddToCartSchema.parse(payload);\n\n if (data.isSubscription) {\n await handleAddingSubscriptionToCart(projectID, data);\n return;\n }\n\n const methods = getCheckoutMethods(projectID);\n\n await methods.addProduct({\n productID: data.id,\n quantity: data.quantity,\n metadata: data.metadata,\n url: data.url,\n });\n\n sentPostMessageToIframe({\n id: id,\n type: CartEvent.ADD_TO_CART,\n success: true,\n });\n showCart();\n } catch (e) {\n console.warn(\"cannot add item to cart\", e);\n }\n }\n\n async function handleAddingSubscriptionToCart(\n projectID: string,\n event: CartAddToCartEvent,\n ) {\n console.warn(\n \"subscriptions cannot be added to the cart yet\",\n projectID,\n event.id,\n );\n showCartNotice(\"subscriptionUnsupported\");\n }\n\n function setupEventListener() {\n if (\"__cart_is_listening\" in window) {\n return;\n }\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error\n window[\"__cart_is_listening\"] = true;\n window.addEventListener(\"message\", (event) => {\n if (\n typeof event.data[\"id\"] != \"undefined\" &&\n typeof event.data[\"type\"] != \"undefined\"\n ) {\n const { id, type } = event.data;\n switch (type) {\n case CartEvent.ADD_TO_CART.toString():\n handleAddingToCart(id, event.data[\"payload\"]).then().catch();\n break;\n default:\n break;\n }\n }\n });\n }\n\n useEffect(() => {\n setupEventListener();\n\n if (typeof window !== \"undefined\") {\n try {\n const parsedParams = new URLSearchParams(window.location.search);\n const refLink =\n parsedParams.get(\"ref\") ?? parsedParams.get(\"utm-source\");\n if (\n typeof localStorage != \"undefined\" &&\n refLink &&\n refLink.length > 0\n ) {\n localStorage.setItem(\"bitsnap-ref\", refLink);\n }\n } catch (e) {\n return;\n }\n }\n }, []);\n\n return (\n <>\n <button\n type=\"button\"\n onClick={() => (isCartVisible ? hideCart() : showCart())}\n aria-label={\n children\n ? undefined\n : numberOfProductsInCart > 0\n ? `${text(\"openCart\")}. ${text(\"cartItemsCount\")}: ${numberOfProductsInCart}`\n : text(\"openCart\")\n }\n aria-expanded={isCartVisible}\n aria-haspopup=\"dialog\"\n aria-controls={isCartVisible ? panelId : undefined}\n style={{ position: \"relative\" }}\n className={[\n \"bitsnap-react\",\n className ?? \"rounded-full hover:bg-neutral-300 transition p-1\",\n ].join(\" \")}\n >\n {children ? (\n <>{children}</>\n ) : (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"lucide lucide-shopping-cart\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <circle cx=\"8\" cy=\"21\" r=\"1\" />\n <circle cx=\"19\" cy=\"21\" r=\"1\" />\n <path d=\"M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12\" />\n </svg>\n )}\n {numberOfProductsInCart > 0 && (\n <span style={visuallyHidden}>\n {text(\"cartItemsCount\")}: {numberOfProductsInCart}\n </span>\n )}\n {numberOfProductsInCartOptions?.isVisible != false &&\n numberOfProductsInCart > 0 && (\n <div\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n bottom: -5,\n right: -5,\n background: \"black\",\n color: \"white\",\n borderRadius: \"50%\",\n width: \"22px\",\n height: \"22px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontSize: \"13px\",\n fontWeight: 600,\n boxShadow: \"0 2px 8px rgba(0, 0, 0, 0.25)\",\n ...numberOfProductsInCartOptions?.style,\n }}\n >\n {numberOfProductsInCart}\n </div>\n )}\n </button>\n <InstrumentProvider value={instrument}>\n <CartComponent\n isVisible={isCartVisible}\n locale={locale}\n appearance={appearance}\n texts={texts}\n panelId={panelId}\n shouldHide={() => {\n hideCart();\n }}\n />\n </InstrumentProvider>\n </>\n );\n}\n\nexport default BitsnapCart;\n","import { useAutoAnimate } from \"@formkit/auto-animate/react\";\nimport * as Dialog from \"@radix-ui/react-dialog\";\nimport { useCallback, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"@/src/lib/utils\";\nimport {\n CheckoutAppearanceProvider,\n useCheckoutAppearance,\n useCheckoutSlot,\n} from \"./appearance.context\";\nimport type { CheckoutAppearance } from \"./appearance\";\nimport CartComponentContent from \"./CartComponentContent\";\nimport CartProvider from \"./CartProvider\";\nimport { CheckoutTextsProvider, useCheckoutText } from \"./texts.context\";\nimport type { CheckoutTexts } from \"./texts\";\n\ninterface Props {\n isVisible: boolean;\n shouldHide: () => void;\n locale?: string;\n appearance?: CheckoutAppearance;\n texts?: Partial<CheckoutTexts>;\n /** DOM id of the panel, for `aria-controls` of the button that opens it. */\n panelId?: string;\n}\n\nfunction CartComponent({ isVisible, shouldHide, panelId, locale }: Props) {\n const [parent] = useAutoAnimate(/* optional config */);\n // The drawer renders inside the checkout root: the stylesheet is scoped to `.bitsnap-react`.\n const [rootElement, setRootElement] = useState<HTMLDivElement | null>(null);\n const rootRef = useCallback(\n (element: HTMLDivElement | null) => {\n parent(element);\n setRootElement(element);\n },\n [parent],\n );\n // The trigger lives outside of the dialog, so Radix does not know which element to give the focus back to.\n const returnFocusTo = useRef<HTMLElement | null>(null);\n const { boundaryClassName, boundaryStyle } = useCheckoutAppearance();\n const root = useCheckoutSlot(\"root\");\n const overlay = useCheckoutSlot(\"overlay\");\n const panel = useCheckoutSlot(\"panel\");\n const header = useCheckoutSlot(\"header\");\n const title = useCheckoutSlot(\"title\");\n const closeButton = useCheckoutSlot(\"closeButton\");\n const cartText = useCheckoutText(\"cart\");\n const closeText = useCheckoutText(\"close\");\n\n return (\n <div\n ref={rootRef}\n data-bitsnap-checkout=\"root\"\n data-bitsnap-checkout-slot=\"root\"\n className={cn(\"bitsnap-react\", boundaryClassName, root.className)}\n style={{ zIndex: 999999, ...boundaryStyle, ...root.style }}\n >\n {rootElement != null && (\n <Dialog.Root\n open={isVisible}\n onOpenChange={(open) => {\n if (!open) {\n shouldHide();\n }\n }}\n >\n <Dialog.Portal container={rootElement}>\n <Dialog.Overlay\n data-bitsnap-checkout-slot=\"overlay\"\n aria-hidden=\"true\"\n className={cn(\n \"fixed top-0 right-0 left-0 bottom-0 cursor-pointer z-10\",\n overlay.className,\n )}\n style={overlay.style}\n onClick={shouldHide}\n />\n <Dialog.Content\n id={panelId}\n data-bitsnap-checkout-slot=\"panel\"\n aria-modal=\"true\"\n aria-describedby={undefined}\n onOpenAutoFocus={() => {\n const active = document.activeElement;\n returnFocusTo.current = active instanceof HTMLElement ? active : null;\n }}\n onCloseAutoFocus={(event) => {\n event.preventDefault();\n returnFocusTo.current?.focus();\n returnFocusTo.current = null;\n }}\n className={cn(\n // Scrolls when the summary and the payment choice do not fit (small phones).\n \"fixed z-20 top-0 right-0 bottom-0 w-full md:w-[350px] xl:w-[420px] flex flex-col overflow-y-auto\",\n panel.className,\n )}\n style={panel.style}\n // The overlay closes the drawer on click; a close on pointer down would let the click through.\n onPointerDownOutside={(event) => event.preventDefault()}\n >\n <div\n data-bitsnap-checkout-slot=\"header\"\n className={cn(\n \"mx-3 mt-7 flex justify-between items-center\",\n header.className,\n )}\n style={header.style}\n >\n <Dialog.Title asChild>\n <h1\n data-bitsnap-checkout-slot=\"title\"\n className={cn(\"text-2xl font-medium\", title.className)}\n style={title.style}\n >\n {cartText}\n </h1>\n </Dialog.Title>\n <button\n type=\"button\"\n data-bitsnap-checkout-slot=\"closeButton\"\n className={cn(\n \"rounded-xl p-2 transition\",\n closeButton.className,\n )}\n style={closeButton.style}\n aria-label={closeText}\n onClick={shouldHide}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"lucide lucide-x\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n </svg>\n </button>\n </div>\n\n <CartComponentContent className={\"grow\"} locale={locale} />\n </Dialog.Content>\n </Dialog.Portal>\n </Dialog.Root>\n )}\n </div>\n );\n}\n\nconst WrapperCartComponent = (props: Props) => {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n return createPortal(\n <CheckoutAppearanceProvider appearance={props.appearance}>\n <CheckoutTextsProvider texts={props.texts} locale={props.locale}>\n <CartProvider locale={props.locale}>\n <CartComponent {...props} />\n </CartProvider>\n </CheckoutTextsProvider>\n </CheckoutAppearanceProvider>,\n document.body,\n );\n};\n\nexport default WrapperCartComponent;\n","import type { CSSProperties } from \"react\";\nimport {\n getSlotAppearance,\n resolveAppearanceCore,\n type AppearanceElements,\n type ShadcnVariableMapping,\n type SlotAppearance,\n} from \"@/src/lib/appearance-core\";\n\n/**\n * Checkout appearance API.\n *\n * `CheckoutAppearance` is resolved once per render into plain data: a theme\n * name, the tokens that are actually in effect, the per-slot overrides and the\n * inline style that both theme boundaries (the drawer root and the portalled\n * country dropdown) have to receive. Everything here is pure so it can be\n * tested without a DOM.\n *\n * Backward compatibility: the resolver never invents values. A token is only\n * present in the resolved output when the caller provided it or when the\n * caller explicitly selected a theme preset, so every unset token keeps falling\n * back to the value the checkout renders today (see `checkout.css`).\n */\n\nexport type CheckoutTheme = \"dark\" | \"light\";\n\nexport type CheckoutAppearanceTokens = {\n /** Checkout wide font family. The library never loads fonts. */\n fontFamily?: string;\n /** Base font size of the checkout. */\n fontSize?: string;\n /** Panel background. */\n background?: string;\n /** Raised surfaces: country dropdown, search bar, loading skeletons. */\n surface?: string;\n /** Main text color. */\n foreground?: string;\n /** Secondary text: empty cart, delivery info, country label, search input. */\n mutedForeground?: string;\n /** Borders: country trigger, dropdown, product separators. */\n border?: string;\n /** Checkout button background. */\n buttonBackground?: string;\n /** Checkout button text. */\n buttonForeground?: string;\n /** Checkout button background on hover. */\n buttonHoverBackground?: string;\n /** Hover and selected surfaces: close button, country options. */\n hoverSurface?: string;\n /** Focus ring color. Also applied to shared UI primitives through `--ring`. */\n focusRing?: string;\n /** Error and unavailable state color. */\n error?: string;\n /** Drawer backdrop color. */\n overlay?: string;\n /** Corner radius, applied through `--radius`. */\n radius?: string;\n};\n\n/**\n * Stable slot names. They are exposed as `data-bitsnap-checkout-slot`\n * attributes so they can also be targeted from plain CSS.\n */\nexport const CHECKOUT_SLOT_NAMES = [\n \"root\",\n \"overlay\",\n \"panel\",\n \"header\",\n \"title\",\n \"closeButton\",\n \"productList\",\n \"product\",\n \"productImage\",\n \"productName\",\n \"productPrice\",\n \"quantityControl\",\n \"quantityInput\",\n \"quantityButton\",\n \"removeButton\",\n \"summary\",\n \"totalLabel\",\n \"totalValue\",\n \"deliveryText\",\n \"summaryNote\",\n \"countryLabel\",\n \"countryTrigger\",\n \"countryDropdown\",\n \"countrySearch\",\n \"countryOption\",\n \"paymentButtons\",\n \"paymentMethods\",\n \"paymentMethodOption\",\n \"checkoutButton\",\n \"emptyState\",\n \"error\",\n \"skeleton\",\n] as const;\n\nexport type CheckoutSlotName = (typeof CHECKOUT_SLOT_NAMES)[number];\n\nexport type CheckoutSlotAppearance = SlotAppearance;\n\nexport type CheckoutAppearanceElements = AppearanceElements<CheckoutSlotName>;\n\nexport type CheckoutAppearance = {\n /**\n * `\"dark\"` keeps the current presentation, `\"light\"` applies the light preset.\n * Omitted means `\"dark\"`.\n */\n theme?: CheckoutTheme;\n /** Partial overrides. Anything omitted keeps today's value. */\n tokens?: CheckoutAppearanceTokens;\n /** Per element `className`/`style` overrides. */\n elements?: CheckoutAppearanceElements;\n};\n\nexport type ResolvedCheckoutAppearance = {\n theme: CheckoutTheme;\n /**\n * `true` as soon as the caller passed anything through `appearance`. The\n * country dropdown is portalled outside of the checkout root, so it only\n * receives the checkout boundary when the caller opted in - an omitted or\n * empty `appearance` keeps the previous (unthemed) dropdown untouched.\n */\n isCustomized: boolean;\n /** Tokens that are in effect. Empty entries were dropped. */\n tokens: CheckoutAppearanceTokens;\n elements: CheckoutAppearanceElements;\n /** Theme marker class for the theme boundaries. */\n boundaryClassName: string;\n /** Inline custom properties applied to the drawer root. */\n boundaryStyle: CSSProperties;\n /**\n * Extra properties for the portalled country dropdown. The shared select\n * content paints itself with `bg-popover`/`text-popover-foreground`, so those\n * variables have to carry the checkout colors over to the portal. They are\n * resolved eagerly because the dropdown is the only place where the built-in\n * defaults differ from the drawer.\n */\n dropdownStyle: CSSProperties;\n};\n\n/** CSS custom property used for each token. */\nexport const CHECKOUT_TOKEN_VARIABLES = {\n fontFamily: \"--bitsnap-checkout-font-family\",\n fontSize: \"--bitsnap-checkout-font-size\",\n background: \"--bitsnap-checkout-background\",\n surface: \"--bitsnap-checkout-surface\",\n foreground: \"--bitsnap-checkout-foreground\",\n mutedForeground: \"--bitsnap-checkout-muted-foreground\",\n border: \"--bitsnap-checkout-border\",\n buttonBackground: \"--bitsnap-checkout-button-background\",\n buttonForeground: \"--bitsnap-checkout-button-foreground\",\n buttonHoverBackground: \"--bitsnap-checkout-button-hover-background\",\n hoverSurface: \"--bitsnap-checkout-hover-surface\",\n focusRing: \"--bitsnap-checkout-focus-ring\",\n error: \"--bitsnap-checkout-error\",\n overlay: \"--bitsnap-checkout-overlay\",\n radius: \"--bitsnap-checkout-radius\",\n} as const;\n\nexport type CheckoutTokenName = keyof typeof CHECKOUT_TOKEN_VARIABLES;\n\n/**\n * Theme presets are intentionally partial. `\"dark\"` is empty because every\n * default already renders the current dark presentation; `\"light\"` only carries\n * the colors that have to change for a light checkout, so typography is never\n * reset implicitly.\n */\nexport const CHECKOUT_THEME_PRESETS: Record<\n CheckoutTheme,\n CheckoutAppearanceTokens\n> = {\n dark: {},\n light: {\n background: \"#ffffff\",\n surface: \"#f9fafb\",\n foreground: \"#18181b\",\n mutedForeground: \"#71717a\",\n border: \"#e4e4e7\",\n buttonBackground: \"#18181b\",\n buttonForeground: \"#fafafa\",\n buttonHoverBackground: \"#000000\",\n hoverSurface: \"#e4e4e7\",\n focusRing: \"#71717a\",\n error: \"#dc2626\",\n overlay: \"rgb(0 0 0 / 0.35)\",\n },\n};\n\n/**\n * Some shared UI primitives (`Button`, `SelectTrigger`, ...) read the shadcn\n * theme variables instead of checkout tokens. Those are remapped so a themed\n * dropdown and themed primitives stay consistent. They are only written when\n * the matching token is in effect, so untouched tokens change nothing.\n */\nconst CHECKOUT_SHADCN_VARIABLES: ShadcnVariableMapping<CheckoutTokenName> = [\n { token: \"background\", variable: \"--background\" },\n { token: \"foreground\", variable: \"--foreground\" },\n { token: \"surface\", variable: \"--card\" },\n { token: \"surface\", variable: \"--popover\" },\n { token: \"foreground\", variable: \"--popover-foreground\" },\n { token: \"mutedForeground\", variable: \"--muted-foreground\" },\n { token: \"border\", variable: \"--border\" },\n { token: \"border\", variable: \"--input\" },\n { token: \"focusRing\", variable: \"--ring\" },\n { token: \"error\", variable: \"--destructive\" },\n { token: \"radius\", variable: \"--radius\" },\n];\n\n/**\n * The portalled dropdown renders `bg-popover`/`text-popover-foreground` from the\n * shared primitives, so those two variables are resolved eagerly: without a\n * token they fall back to the dark values the checkout has always used.\n */\nfunction resolveDropdownStyle(tokens: CheckoutAppearanceTokens): CSSProperties {\n return {\n \"--popover\": tokens.surface ?? \"var(--color-neutral-800)\",\n \"--popover-foreground\": tokens.foreground ?? \"var(--color-neutral-200)\",\n } as CSSProperties;\n}\n\nexport function resolveCheckoutAppearance(\n appearance?: CheckoutAppearance,\n): ResolvedCheckoutAppearance {\n const resolved = resolveAppearanceCore<\n CheckoutTokenName,\n CheckoutTheme,\n CheckoutSlotName\n >(appearance, {\n defaultTheme: \"dark\",\n presets: CHECKOUT_THEME_PRESETS,\n variables: CHECKOUT_TOKEN_VARIABLES,\n shadcnVariables: CHECKOUT_SHADCN_VARIABLES,\n });\n\n return {\n theme: resolved.theme,\n isCustomized: resolved.isCustomized,\n tokens: resolved.tokens,\n elements: resolved.elements,\n boundaryClassName: resolved.theme,\n boundaryStyle: resolved.boundaryStyle,\n dropdownStyle: resolveDropdownStyle(resolved.tokens),\n };\n}\n\n/** Caller overrides for a single slot, if any were provided. */\nexport function getCheckoutSlotAppearance(\n resolved: ResolvedCheckoutAppearance,\n slot: CheckoutSlotName,\n): CheckoutSlotAppearance | undefined {\n return getSlotAppearance(resolved.elements, slot);\n}\n","import { createContext, useContext, useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport {\n getCheckoutSlotAppearance,\n resolveCheckoutAppearance,\n type CheckoutAppearance,\n type CheckoutSlotAppearance,\n type CheckoutSlotName,\n type ResolvedCheckoutAppearance,\n} from \"./appearance\";\n\nconst CheckoutAppearanceContext = createContext<ResolvedCheckoutAppearance>(\n resolveCheckoutAppearance(),\n);\n\n/**\n * Checkout scoped appearance provider. It only wraps the checkout drawer, so\n * the configuration never leaks into the panel, the notifications or any\n * global state.\n */\nexport function CheckoutAppearanceProvider({\n appearance,\n children,\n}: {\n appearance?: CheckoutAppearance;\n children: ReactNode;\n}) {\n const resolved = useMemo(\n () => resolveCheckoutAppearance(appearance),\n [appearance],\n );\n\n return (\n <CheckoutAppearanceContext.Provider value={resolved}>\n {children}\n </CheckoutAppearanceContext.Provider>\n );\n}\n\nexport function useCheckoutAppearance(): ResolvedCheckoutAppearance {\n return useContext(CheckoutAppearanceContext);\n}\n\n/**\n * Returns the caller overrides for a slot. The values are meant to be merged\n * with the built-in presentation:\n *\n * ```tsx\n * const slot = useCheckoutSlot(\"title\");\n *\n * return <h1 className={cn(\"text-2xl font-medium\", slot.className)} style={slot.style} />;\n * ```\n */\nexport function useCheckoutSlot(\n slot: CheckoutSlotName,\n): CheckoutSlotAppearance {\n const resolved = useCheckoutAppearance();\n\n return getCheckoutSlotAppearance(resolved, slot) ?? {};\n}\n","import { useAutoAnimate } from \"@formkit/auto-animate/react\";\nimport React from \"react\";\nimport { useInstrumentContext } from \"@/src/analytics/instrument\";\nimport { getCartLines, getProjectID, useCartProvider } from \"./CartProvider\";\nimport CountrySelector from \"./CountrySelector\";\nimport { isErr } from \"./lib/err\";\nimport { formatCurrency } from \"./lib/round.number\";\nimport LoadingIndicator from \"./LoadingIndicator\";\nimport SingleProduct from \"./SingleProduct\";\nimport { Skeleton } from \"./Skeleton\";\nimport { ApplePayButton, GooglePayButton } from \"..\";\nimport { Spinner } from \"@/src/ui/spinner\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { cn } from \"@/src/lib/utils\";\nimport { sendAnalyticEvent } from \"./frontent.analytics\";\nimport { getLocale } from \"./locale\";\nimport { buyErrorText } from \"./buy-error\";\nimport { useCheckoutText, useCheckoutTextResolver } from \"./texts.context\";\nimport { useCheckoutSlot } from \"./appearance.context\";\nimport { rememberPendingOrder } from \"./pending-order\";\nimport { useCheckoutStore } from \"./state\";\nimport { PaymentMethodChoice } from \"./PaymentMethodChoice\";\nimport {\n effectivePaymentMethod,\n withoutCod,\n type PaymentMethod,\n type PaymentMethodChoice as Choice,\n} from \"./payment-method\";\nimport { InPostPayButtonInner } from \"../express/InPostPayButton\";\nimport { RevolutPayButtonInner } from \"../express/RevolutPayButton\";\n\nconst CartComponentContent = ({\n className,\n locale,\n}: {\n className: string;\n locale?: string;\n}) => {\n const provider = useCartProvider();\n const instrument = useInstrumentContext();\n const currentLocale = locale ?? getLocale();\n\n const productList = useCheckoutSlot(\"productList\");\n const summary = useCheckoutSlot(\"summary\");\n const totalLabel = useCheckoutSlot(\"totalLabel\");\n const totalValue = useCheckoutSlot(\"totalValue\");\n const deliveryText = useCheckoutSlot(\"deliveryText\");\n const summaryNote = useCheckoutSlot(\"summaryNote\");\n const countryLabel = useCheckoutSlot(\"countryLabel\");\n const paymentButtons = useCheckoutSlot(\"paymentButtons\");\n const checkoutButton = useCheckoutSlot(\"checkoutButton\");\n const emptyState = useCheckoutSlot(\"emptyState\");\n const error = useCheckoutSlot(\"error\");\n const skeleton = useCheckoutSlot(\"skeleton\");\n\n const unavailableProductsText = useCheckoutText(\"unavailableProducts\");\n const resolveCheckoutText = useCheckoutTextResolver();\n const emptyCartText = useCheckoutText(\"emptyCart\");\n const totalText = useCheckoutText(\"total\");\n const deliveryCopy = useCheckoutText(\"delivery\");\n const chooseCountryText = useCheckoutText(\"chooseCountry\");\n const nextStepText = useCheckoutText(\"nextStep\");\n const loadFailedText = useCheckoutText(\"loadFailed\");\n const retryText = useCheckoutText(\"retry\");\n const countryRequiredText = useCheckoutText(\"countryRequired\");\n const extrasCalculatedNextText = useCheckoutText(\"extrasCalculatedNext\");\n const countrySelectorId = React.useId();\n const countryHintId = React.useId();\n\n const { mutateAsync: removeProduct } = useMutation({\n mutationFn: provider.removeProductFromCart,\n });\n const { mutateAsync: updateQuantity } = useMutation({\n mutationFn: provider.updateQuantity,\n });\n const { mutateAsync: setCountryAsync } = useMutation({\n mutationFn: provider.setCountry,\n });\n const { mutateAsync: clearCart } = useMutation({\n mutationFn: provider.clearCart,\n });\n\n const [errMsg, setErrMsg] = React.useState(\"\");\n // Raised outside of the drawer (for example a failed redirect of `window.cart.buyProduct`).\n const notice = useCheckoutStore((state) => state.notice);\n React.useEffect(\n () => () => useCheckoutStore.getState().setNotice(undefined),\n [],\n );\n const shownError = errMsg.length > 0 ? errMsg : (notice ?? \"\");\n const [missingItemIds, setMissingItemIds] = React.useState<string[]>([]);\n\n const [isCountryOpen, setIsCountryOpen] = React.useState(false);\n\n const {\n mutateAsync: continueToCheckoutAsync,\n isPending: isContinueToCheckoutLoading,\n } = useMutation({\n mutationFn: provider.redirectToNextStep,\n });\n\n const { data: availableCountries } = useQuery({\n queryKey: [\"cart-available-countries\"],\n queryFn: provider.getAvailableCountries,\n });\n const { data: isApplePayAvailable } = useQuery({\n queryKey: [\"cart-one-click-payment\"],\n queryFn: provider.checkIfApplePayIsAvailable,\n });\n const { data, isLoading, isError, refetch } = useQuery({\n queryKey: [\"cart\", currentLocale],\n queryFn: provider.getProducts,\n });\n const { data: countryData, refetch: refetchCountry } = useQuery({\n queryKey: [\"cart-country\"],\n queryFn: provider.getCountry,\n });\n\n const [productsParent] = useAutoAnimate(/* optional config */);\n\n const products = isErr(data) ? undefined : data;\n\n const selectedCountry =\n countryData != null && !isErr(countryData) && countryData != \"\"\n ? countryData\n : undefined;\n\n // Whether the shop lets the buyer pay by hand (transfer / BLIK to a phone)\n // or on delivery for this cart. Asked once per cart content and country\n // (cash on delivery is for Poland only); any failure means \"no choice\".\n // Without a country \"Next step\" is disabled, so there is nothing to ask yet.\n // Until a new country or cart is answered, the last choice stays on screen\n // without cash on delivery, which the new answer may not offer.\n const queryClient = useQueryClient();\n const cartSignature =\n products == null || products.length === 0 || selectedCountry == null\n ? undefined\n : products\n .map((el) => `${el.productID}:${el.quantity}`)\n .sort()\n .join(\",\");\n const paymentMethodsQuery = {\n queryKey: [\n \"cart-payment-methods\",\n getProjectID(),\n currentLocale,\n selectedCountry,\n cartSignature,\n ],\n queryFn: async (): Promise<Choice | null> =>\n provider.getPaymentMethodChoice().catch(() => null),\n staleTime: 60_000,\n retry: false,\n } as const;\n const {\n data: paymentMethodChoiceData,\n isPlaceholderData: isPaymentMethodChoicePlaceholder,\n } = useQuery({\n ...paymentMethodsQuery,\n enabled: cartSignature != null,\n placeholderData: (previous: Choice | null | undefined) =>\n previous === undefined ? undefined : withoutCod(previous),\n });\n const paymentMethodChoice =\n cartSignature != null ? (paymentMethodChoiceData ?? null) : null;\n const [selectedPaymentMethod, setSelectedPaymentMethod] = React.useState<\n PaymentMethod | undefined\n >(undefined);\n const [isWaitingForChoice, setIsWaitingForChoice] = React.useState(false);\n const paymentMethodsRef = React.useRef<HTMLDivElement>(null);\n const hasLoadFailed = !isLoading && (isError || isErr(data));\n\n const countries = isErr(availableCountries) ? [] : availableCountries;\n\n const sumOfProducts =\n products?.reduce((prev, curr) => {\n if (curr.details == null) {\n return prev;\n }\n return prev + (curr.details.pricing?.effectivePrice ?? curr.details.price) * curr.quantity;\n }, 0) ?? 0;\n const currency = products?.[0]?.details?.currency ?? \"PLN\";\n\n const isSomeProductDeliverable =\n products?.some((product) => product.details?.isDeliverable === true) ??\n false;\n\n // The hint waits for the answer: the country is read asynchronously.\n const isCountryMissing = countryData !== undefined && selectedCountry == null;\n\n // A stored country the shop does not ship to (or none at all) would leave the checkout button disabled.\n const autoSelectedCountry = React.useRef(false);\n React.useEffect(() => {\n if (\n autoSelectedCountry.current ||\n countries == null ||\n countries.length === 0 ||\n countryData == null ||\n (selectedCountry != null &&\n countries.some((el) => el.code === selectedCountry))\n ) {\n return;\n }\n autoSelectedCountry.current = true;\n setCountryAsync(countries[0].code)\n .then(() => refetchCountry())\n .catch(() => {});\n }, [countries, countryData, selectedCountry]);\n\n // One `cart_opened` per opening of the drawer, once the cart is loaded.\n const opened = React.useRef(false);\n React.useEffect(() => {\n if (opened.current || isLoading) {\n return;\n }\n opened.current = true;\n instrument.track(\"cart_opened\", {\n item_count: products?.reduce((sum, el) => sum + el.quantity, 0) ?? 0,\n value_minor: sumOfProducts,\n currency,\n });\n }, [isLoading, instrument, products, sumOfProducts, currency]);\n\n const reportedQuantity = React.useRef(new Map<string, number>());\n React.useEffect(() => {\n reportedQuantity.current.clear();\n }, [products]);\n\n /** `add_to_cart` / `remove_from_cart` for a quantity change in the drawer. */\n function trackQuantityChange(id: string, newQuantity: number) {\n const line = products?.find((el) => el.id === id);\n if (line?.details == null) {\n return;\n }\n // Quick clicks arrive before the cart is reloaded: count from the last reported quantity.\n const delta = newQuantity - (reportedQuantity.current.get(id) ?? line.quantity);\n reportedQuantity.current.set(id, newQuantity);\n if (delta === 0) {\n return;\n }\n const price = line.details.pricing?.effectivePrice ?? line.details.price;\n const productID = line.catalogProductID ?? line.productID;\n instrument.track(\n delta > 0 ? \"add_to_cart\" : \"remove_from_cart\",\n {\n qty: Math.abs(delta),\n value_minor: Math.round(price * Math.abs(delta)),\n currency: line.details.currency,\n },\n {\n product_id: productID,\n variant_id: productID !== line.productID ? line.productID : null,\n },\n );\n }\n\n const startedCheckout = React.useRef(false);\n\n /** Rejects when the cart could not be changed; the cart is reloaded either way. */\n async function shouldUpdate(id: string, newQuantity?: number) {\n if (newQuantity == null) {\n return;\n }\n trackQuantityChange(id, Math.max(0, newQuantity));\n try {\n const result =\n newQuantity <= 0\n ? await removeProduct({ id: id })\n : await updateQuantity({ id: id, quantity: newQuantity });\n if (isErr(result)) {\n throw new Error(`cannot change the cart: ${result.error}`);\n }\n } finally {\n await refetch().catch(() => undefined);\n }\n }\n\n async function continueToCheckout() {\n try {\n setErrMsg(\"\");\n setMissingItemIds([]);\n useCheckoutStore.getState().setNotice(undefined);\n\n // A click before the choice arrived waits for it (at most the request\n // timeout); a choice the buyer has not seen yet is shown, not used. The\n // last choice shown in the meantime is used if the new one still offers\n // the selected method.\n let choice = paymentMethodChoice;\n let paymentMethod =\n choice != null\n ? effectivePaymentMethod(choice, selectedPaymentMethod)\n : undefined;\n if (\n cartSignature != null &&\n (paymentMethodChoiceData === undefined || isPaymentMethodChoicePlaceholder)\n ) {\n setIsWaitingForChoice(true);\n try {\n choice = await queryClient\n .fetchQuery(paymentMethodsQuery)\n .catch(() => null);\n } finally {\n setIsWaitingForChoice(false);\n }\n if (choice == null) {\n paymentMethod = undefined;\n } else if (paymentMethod == null || !choice.methods.includes(paymentMethod)) {\n // Let the buyer see the options first; focus moves to the selected one.\n window.setTimeout(() => {\n paymentMethodsRef.current\n ?.querySelector<HTMLInputElement>(\"input:checked\")\n ?.focus();\n }, 0);\n return;\n }\n }\n\n if (products != null) {\n if (!startedCheckout.current && products.length > 0) {\n startedCheckout.current = true;\n instrument.track(\"checkout_started\", {\n item_count: products.reduce((sum, el) => sum + el.quantity, 0),\n checkout_type: \"cart\",\n value_minor: sumOfProducts,\n currency,\n });\n }\n try {\n sendAnalyticEvent({\n event: \"initiateCheckout\",\n items: products?.map((el) => ({\n id: el.productID,\n name: el.details?.name ?? \"\",\n price: el.details?.price ?? 0,\n quantity: el.quantity,\n currency: el.details?.currency ?? \"PLN\",\n })),\n });\n } catch (e) {}\n }\n\n // What this order is made of; only these lines go once it is paid.\n const cartLinesBeforeBuy = getCartLines();\n const response = await continueToCheckoutAsync(\n paymentMethod != null ? { paymentMethod } : undefined,\n );\n\n if (isErr(response)) {\n // The seller may have changed the payment methods in the meantime.\n void queryClient.invalidateQueries({ queryKey: [\"cart-payment-methods\"] });\n if (response.error == \"item-ids-not-found\") {\n setErrMsg(unavailableProductsText);\n if (\"data\" in response && Array.isArray(response.data)) {\n setMissingItemIds(response.data as string[]);\n }\n } else if (\n paymentMethod === \"cod\" &&\n response.error == \"gateway-not-configured\"\n ) {\n // The shop turned cash on delivery off, or this cart does not qualify.\n setErrMsg(resolveCheckoutText(\"paymentMethodCodUnavailable\"));\n } else {\n setErrMsg(buyErrorText(response, resolveCheckoutText));\n }\n return;\n }\n\n // The cart stays until the order is paid (or placed, for cash on\n // delivery); without an order to ask about it is cleared now.\n if (\n !rememberPendingOrder(response, {\n cartProjectId: getProjectID(),\n lines: cartLinesBeforeBuy,\n })\n ) {\n await clearCart();\n }\n window.location.href = response.url;\n } catch (e: unknown) {\n console.warn(\"checkout failed\", e);\n setErrMsg(resolveCheckoutText(\"checkoutFailed\"));\n }\n }\n\n return (\n <div className={`${className} flex flex-col`} ref={productsParent}>\n {isLoading && (\n <div className={\"relative flex w-full justify-center flex-col gap-4\"}>\n <Skeleton\n data-bitsnap-checkout-slot=\"skeleton\"\n className={cn(\"w-full h-32\", skeleton.className)}\n style={skeleton.style}\n />\n <Skeleton\n data-bitsnap-checkout-slot=\"skeleton\"\n className={cn(\"w-full h-32\", skeleton.className)}\n style={skeleton.style}\n />\n </div>\n )}\n\n {hasLoadFailed && (\n <div className={\"flex flex-col items-start gap-3 p-4\"}>\n <p\n role=\"alert\"\n data-bitsnap-checkout-slot=\"error\"\n className={cn(error.className)}\n style={error.style}\n >\n {loadFailedText}\n </p>\n {notice != null && notice.length > 0 && (\n <p\n role=\"alert\"\n data-bitsnap-checkout-slot=\"error\"\n className={cn(\"text-sm\", error.className)}\n style={error.style}\n >\n {notice}\n </p>\n )}\n <button\n type=\"button\"\n data-bitsnap-checkout-slot=\"checkoutButton\"\n onClick={() => {\n refetch().then().catch();\n }}\n className={cn(\n \"px-3 py-2 rounded-md transition font-bold\",\n checkoutButton.className,\n )}\n style={checkoutButton.style}\n >\n {retryText}\n </button>\n </div>\n )}\n\n {!isLoading && !hasLoadFailed && (products == null || products.length == 0) && (\n <div className={\"flex flex-col gap-4 p-4\"}>\n <p\n data-bitsnap-checkout-slot=\"emptyState\"\n className={cn(emptyState.className)}\n style={emptyState.style}\n >\n {emptyCartText}\n </p>\n {notice != null && notice.length > 0 && (\n <p\n role=\"alert\"\n data-bitsnap-checkout-slot=\"error\"\n className={cn(\"text-sm\", error.className)}\n style={error.style}\n >\n {notice}\n </p>\n )}\n </div>\n )}\n\n <div\n data-bitsnap-checkout-slot=\"productList\"\n className={cn(\n \"max-h-[70vh] overflow-clip overflow-y-scroll\",\n productList.className,\n )}\n style={productList.style}\n >\n {products != null && products.length > 0 && (\n <ul className={\"mt-5\"}>\n {products.map((product) => (\n <React.Fragment key={product.id}>\n {product.details != null && (\n <li className={\"mb-3\"}>\n <SingleProduct\n isNotAvailable={missingItemIds.includes(\n product.productID,\n )}\n quantity={product.quantity}\n details={product.details}\n url={product.url}\n shouldUpdate={(newQuantity) =>\n shouldUpdate(product.id, newQuantity)\n }\n />\n <hr className=\"h-1\" />\n </li>\n )}\n </React.Fragment>\n ))}\n </ul>\n )}\n </div>\n\n <div className=\"grow\"></div>\n\n {sumOfProducts > 0 && currency != null && (\n <>\n <div\n data-bitsnap-checkout-slot=\"summary\"\n className={cn(\"mx-3 flex flex-col\", summary.className)}\n style={summary.style}\n >\n <div className={\"flex flex-row justify-between text-lg\"}>\n <p\n data-bitsnap-checkout-slot=\"totalLabel\"\n className={cn(\"text-xl\", totalLabel.className)}\n style={totalLabel.style}\n >\n {totalText}\n </p>\n <div className={\"flex flex-col items-end\"}>\n <p\n data-bitsnap-checkout-slot=\"totalValue\"\n className={cn(\"font-medium\", totalValue.className)}\n style={totalValue.style}\n >\n {formatCurrency(sumOfProducts, currency)}\n </p>\n {isSomeProductDeliverable && (\n <p\n data-bitsnap-checkout-slot=\"deliveryText\"\n className={cn(\n \"opacity-70 text-right text-base\",\n deliveryText.className,\n )}\n style={deliveryText.style}\n >\n {deliveryCopy}\n </p>\n )}\n </div>\n </div>\n <p\n data-bitsnap-checkout-slot=\"summaryNote\"\n className={cn(\"opacity-70 text-sm\", summaryNote.className)}\n style={summaryNote.style}\n >\n {extrasCalculatedNextText}\n </p>\n </div>\n\n {countries && countries?.length > 1 && (\n <div>\n <h4\n data-bitsnap-checkout-slot=\"countryLabel\"\n className={cn(\"ml-3 text-sm\", countryLabel.className)}\n style={countryLabel.style}\n >\n {chooseCountryText}\n </h4>\n <CountrySelector\n id={countrySelectorId}\n open={isCountryOpen}\n onToggle={() => {\n setIsCountryOpen(!isCountryOpen);\n }}\n onChange={(newValue) => {\n setCountryAsync(newValue).then(() => {\n refetchCountry().then().catch();\n });\n }}\n selectedValue={selectedCountry ?? \"\"}\n countries={countries}\n />\n </div>\n )}\n\n <div className={\"mb-3 flex flex-col\"}>\n {isApplePayAvailable && products != null && products.length > 0 && (\n <div\n data-bitsnap-checkout-slot=\"paymentButtons\"\n className={cn(\"w-full px-2\", paymentButtons.className)}\n style={paymentButtons.style}\n >\n <ApplePayButton\n colorType=\"white\"\n style={{ width: \"100%\" }}\n items={products?.map((el) => ({\n name: el.details?.name ?? \"\",\n id: el.productID,\n price: el.details?.pricing?.effectivePrice ?? el.details?.price ?? 0,\n currency: el.details?.currency,\n quantity: el.quantity,\n isDeliverable: el.details?.isDeliverable ?? false,\n metadata: el.metadata,\n }))}\n />\n </div>\n )}\n {products != null && products.length > 0 && (\n <div\n data-bitsnap-checkout-slot=\"paymentButtons\"\n className={cn(\"w-full px-2\", paymentButtons.className)}\n style={paymentButtons.style}\n >\n <GooglePayButton\n buttonSizeMode=\"fill\"\n buttonColor=\"white\"\n style={{ width: \"100%\" }}\n items={products?.map((el) => ({\n name: el.details?.name ?? \"\",\n id: el.productID,\n price: el.details?.pricing?.effectivePrice ?? el.details?.price ?? 0,\n quantity: el.quantity,\n isDeliverable: el.details?.isDeliverable ?? false,\n metadata: el.metadata,\n }))}\n />\n </div>\n )}\n {products != null && products.length > 0 && (\n <div\n data-bitsnap-checkout-slot=\"paymentButtons\"\n className={cn(\"w-full px-2\", paymentButtons.className)}\n style={paymentButtons.style}\n >\n <InPostPayButtonInner\n projectID={getProjectID() ?? \"\"}\n locale={currentLocale}\n />\n <RevolutPayButtonInner\n projectID={getProjectID() ?? \"\"}\n locale={currentLocale}\n />\n </div>\n )}\n {paymentMethodChoice != null && (\n <div ref={paymentMethodsRef} className=\"contents\">\n <PaymentMethodChoice\n choice={paymentMethodChoice}\n selected={effectivePaymentMethod(\n paymentMethodChoice,\n selectedPaymentMethod,\n )}\n onChange={setSelectedPaymentMethod}\n disabled={isContinueToCheckoutLoading}\n />\n </div>\n )}\n <button\n data-bitsnap-checkout-slot=\"checkoutButton\"\n onClick={continueToCheckout}\n disabled={\n isLoading ||\n isContinueToCheckoutLoading ||\n isWaitingForChoice ||\n selectedCountry == null\n }\n aria-describedby={isCountryMissing ? countryHintId : undefined}\n className={cn(\n \"px-3 py-2 my-2 mx-2 rounded-md disabled:opacity-40 disabled:cursor-not-allowed transition font-bold\",\n checkoutButton.className,\n )}\n style={checkoutButton.style}\n >\n {isContinueToCheckoutLoading || isWaitingForChoice ? (\n <Spinner />\n ) : (\n nextStepText\n )}\n </button>\n {isCountryMissing && (\n <p\n id={countryHintId}\n className={\"text-sm text-center opacity-70\"}\n >\n {countryRequiredText}\n </p>\n )}\n {shownError.length > 0 && (\n <p\n role=\"alert\"\n data-bitsnap-checkout-slot=\"error\"\n className={cn(\"text-sm text-center\", error.className)}\n style={error.style}\n >\n {shownError}\n </p>\n )}\n </div>\n </>\n )}\n </div>\n );\n};\n\nexport default CartComponentContent;\n","import * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon } from \"lucide-react\"\n\nimport { cn } from \"@/src/lib/utils\"\n\nfunction Select({\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Root>) {\n return <SelectPrimitive.Root data-slot=\"select\" {...props} />\n}\n\nfunction SelectGroup({\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Group>) {\n return <SelectPrimitive.Group data-slot=\"select-group\" {...props} />\n}\n\nfunction SelectValue({\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Value>) {\n return <SelectPrimitive.Value data-slot=\"select-value\" {...props} />\n}\n\n/**\n * Colors owned by the shared shadcn theme. They live in Tailwind's\n * `utilities` layer, so plain CSS can never win against them. Components that\n * theme themselves through their own stylesheet (the checkout and its\n * `appearance` tokens) opt out with `theme=\"bare\"`, which keeps the colors the\n * checkout owns (`background-color`, `border-color`) and leaves the rest alone.\n * The default is unchanged.\n */\nconst selectTriggerThemeClasses =\n \"border-input dark:bg-input/30 dark:hover:bg-input/50 bg-transparent\"\n\nfunction SelectTrigger({\n className,\n size = \"default\",\n theme = \"default\",\n children,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {\n size?: \"sm\" | \"default\"\n theme?: \"default\" | \"bare\"\n}) {\n return (\n <SelectPrimitive.Trigger\n data-slot=\"select-trigger\"\n data-size={size}\n className={cn(\n \"data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex w-fit items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n theme === \"bare\" ? \"\" : selectTriggerThemeClasses,\n className\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <ChevronDownIcon className=\"size-4 opacity-50\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n )\n}\n\nfunction SelectContent({\n className,\n children,\n position = \"popper\",\n align = \"center\",\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Content>) {\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n data-slot=\"select-content\"\n className={cn(\n \"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md\",\n position === \"popper\" &&\n \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n className\n )}\n position={position}\n align={align}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1\"\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n )\n}\n\nfunction SelectLabel({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Label>) {\n return (\n <SelectPrimitive.Label\n data-slot=\"select-label\"\n className={cn(\"text-muted-foreground px-2 py-1.5 text-xs\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectItem({\n className,\n children,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Item>) {\n return (\n <SelectPrimitive.Item\n data-slot=\"select-item\"\n className={cn(\n \"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2\",\n className\n )}\n {...props}\n >\n <span className=\"absolute right-2 flex size-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <CheckIcon className=\"size-4\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n )\n}\n\nfunction SelectSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.Separator>) {\n return (\n <SelectPrimitive.Separator\n data-slot=\"select-separator\"\n className={cn(\"bg-border pointer-events-none -mx-1 my-1 h-px\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectScrollUpButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {\n return (\n <SelectPrimitive.ScrollUpButton\n data-slot=\"select-scroll-up-button\"\n className={cn(\n \"flex cursor-default items-center justify-center py-1\",\n className\n )}\n {...props}\n >\n <ChevronUpIcon className=\"size-4\" />\n </SelectPrimitive.ScrollUpButton>\n )\n}\n\nfunction SelectScrollDownButton({\n className,\n ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {\n return (\n <SelectPrimitive.ScrollDownButton\n data-slot=\"select-scroll-down-button\"\n className={cn(\n \"flex cursor-default items-center justify-center py-1\",\n className\n )}\n {...props}\n >\n <ChevronDownIcon className=\"size-4\" />\n </SelectPrimitive.ScrollDownButton>\n )\n}\n\nexport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n}\n","import { useEffect, useState } from \"react\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/src/ui/select\";\nimport { cn } from \"@/src/lib/utils\";\nimport { useCheckoutAppearance, useCheckoutSlot } from \"./appearance.context\";\nimport { useCheckoutText } from \"./texts.context\";\n\nexport interface CountrySelectorProps {\n id: string;\n open: boolean;\n disabled?: boolean;\n onToggle: () => void;\n onChange: (value: string) => void;\n selectedValue: string;\n countries: { name: string; code: string }[];\n}\n\nfunction CountrySelector({\n id,\n open,\n disabled = false,\n onToggle,\n onChange,\n selectedValue,\n countries,\n}: CountrySelectorProps) {\n const [query, setQuery] = useState(\"\");\n\n const {\n isCustomized,\n boundaryClassName,\n boundaryStyle,\n dropdownStyle,\n } = useCheckoutAppearance();\n const countryTrigger = useCheckoutSlot(\"countryTrigger\");\n const countryDropdown = useCheckoutSlot(\"countryDropdown\");\n const countrySearch = useCheckoutSlot(\"countrySearch\");\n const countryOption = useCheckoutSlot(\"countryOption\");\n const searchCountryText = useCheckoutText(\"searchCountry\");\n const noCountriesFoundText = useCheckoutText(\"noCountriesFound\");\n\n useEffect(() => {\n if (selectedValue == null && countries.length > 0) {\n onChange(countries[0].code);\n }\n }, []);\n\n const filteredCountries = countries.filter((country) =>\n country.name.toLowerCase().startsWith(query.toLowerCase()),\n );\n\n const selectedCountry = countries.find((el) => el.code === selectedValue);\n\n return (\n <Select\n value={selectedValue}\n onValueChange={(value) => {\n onChange(value);\n setQuery(\"\");\n }}\n open={open}\n onOpenChange={(isOpen) => {\n if (isOpen !== open) {\n onToggle();\n }\n if (!isOpen) {\n setQuery(\"\");\n }\n }}\n disabled={disabled}\n >\n <SelectTrigger\n data-bitsnap-checkout-slot=\"countryTrigger\"\n theme=\"bare\"\n className={cn(\n disabled\n ? \"bg-neutral-100 dark:bg-neutral-800 dark:border-neutral-700\"\n : \"\",\n \"w-full rounded-md\",\n countryTrigger.className,\n )}\n style={countryTrigger.style}\n >\n <SelectValue>\n {selectedCountry && (\n <span className=\"truncate flex items-center\">\n <img\n alt={selectedValue}\n src={`https://purecatamphetamine.github.io/country-flag-icons/3x2/${selectedValue}.svg`}\n className=\"inline mr-2 h-4 rounded-sm\"\n />\n {selectedCountry.name}\n </span>\n )}\n </SelectValue>\n </SelectTrigger>\n <SelectContent\n data-bitsnap-checkout={isCustomized ? \"dropdown\" : undefined}\n data-bitsnap-checkout-slot={isCustomized ? \"countryDropdown\" : undefined}\n className={cn(\n // The dropdown is portalled outside of the checkout root, so it only\n // joins the checkout theme boundary once the caller opts into an\n // appearance. Without it the content renders exactly as before.\n isCustomized ? \"bitsnap-react\" : \"\",\n boundaryClassName,\n \"w-full max-h-80 max-w-[350px] z-[999999]\",\n countryDropdown.className,\n )}\n style={{\n ...(isCustomized ? { ...boundaryStyle, ...dropdownStyle } : undefined),\n ...countryDropdown.style,\n }}\n position=\"popper\"\n >\n <div\n data-bitsnap-checkout-slot=\"countrySearch\"\n className={cn(\"sticky top-0 z-10 p-1 border-b\", countrySearch.className)}\n style={countrySearch.style}\n >\n <input\n type=\"search\"\n name=\"search\"\n autoComplete=\"off\"\n className=\"block w-full outline-none sm:text-body-regular bg-transparent rounded-md px-2 py-1.5\"\n placeholder={searchCountryText}\n aria-label={searchCountryText}\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n />\n </div>\n <div className=\"max-h-64 overflow-y-auto\">\n {filteredCountries.length === 0 ? (\n <div className=\"cursor-default select-none relative py-2 pl-3 pr-9 text-sm\">\n {noCountriesFoundText}\n </div>\n ) : (\n filteredCountries.map((country) => (\n <SelectItem\n key={`${id}-${country.code}`}\n data-bitsnap-checkout-slot=\"countryOption\"\n value={country.code}\n className={cn(\n \"cursor-default select-none relative py-2 pl-3 pr-9 flex items-center transition\",\n countryOption.className,\n )}\n style={countryOption.style}\n >\n <img\n alt={country.code}\n src={`https://purecatamphetamine.github.io/country-flag-icons/3x2/${country.code}.svg`}\n className=\"inline mr-2 h-4 rounded-sm\"\n />\n <span className=\"font-normal truncate\">{country.name}</span>\n </SelectItem>\n ))\n )}\n </div>\n </SelectContent>\n </Select>\n );\n}\n\nexport default CountrySelector;\n"]}
|
package/dist/consent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/consent.js","../src/consent/react.tsx","../src/consent/core/types.ts","../src/consent/core/activate.ts","../src/consent/core/config.ts","../src/consent/core/styles.ts","../src/consent/core/ui.ts"],"names":["CONSENT_CATEGORIES","SCRIPT_SELECTOR","FRAME_SELECTOR","allowed","c","category","settled","script","resolve","run","doc","placeholder","name","value","external","loaded","activateScripts","activated","gate","loading","inline","frame","src","DEFAULT_API_HOST","DEFAULT_COLLECT_HOST","defaultConfig","THEMES","POSITIONS","HEX_COLOR","safeColor","safeUrl","url","cleanServices","services","item","cookie","mergeConfig","layers","result","layer","accent","privacy","fetchRemoteConfig","apiHost","key","timeoutMs","controller","timer","response"],"mappings":"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","file":"/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/consent.js","sourcesContent":[null,"import { useCallback, useRef, useSyncExternalStore } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"../lib/isomorphic-layout-effect\";\nimport { createConsent } from \"./core/controller\";\nimport type {\n BitsnapConsentApi,\n ConsentChoices,\n ConsentConfig,\n ConsentOptions,\n ConsentState,\n} from \"./core/types\";\n\ndeclare global {\n interface Window {\n BitsnapConsent?: BitsnapConsentApi;\n }\n}\n\nexport type BitsnapConsentProps = {\n /** Public project key: loads the project's banner config and logs choices. */\n projectKey?: string;\n theme?: ConsentOptions[\"theme\"];\n locale?: ConsentOptions[\"locale\"];\n position?: ConsentOptions[\"position\"];\n privacyPolicyUrl?: string;\n /** `project` keeps the consent cookie per project key. */\n scope?: ConsentOptions[\"scope\"];\n apiHost?: string;\n collectHost?: string;\n /** Local config, merged over the project config. */\n config?: Partial<ConsentConfig>;\n reloadOnWithdraw?: boolean;\n /** Google Consent Mode v2: `auto` (default) is on when `gtag` or `dataLayer` already exist. */\n gcm?: ConsentOptions[\"gcm\"];\n /** Categories offered when no remote config is loaded. */\n categories?: ConsentOptions[\"categories\"];\n /** Dashboard preview: shows the banner from the local options only; touches no cookie. */\n preview?: boolean;\n /** Replaces the banner title. */\n title?: string;\n /** Replaces the banner description. */\n description?: string;\n};\n\n/** Mounts Bitsnap Consent on the page. Renders nothing itself (the banner lives in a Shadow DOM). */\nexport function BitsnapConsent(props: BitsnapConsentProps): null {\n const {\n projectKey, theme, locale, position, privacyPolicyUrl, scope, apiHost, collectHost, reloadOnWithdraw,\n gcm, preview, title, description,\n } = props;\n const configKey = JSON.stringify(props.config ?? null);\n const configRef = useRef(props.config);\n configRef.current = props.config;\n const categoriesKey = JSON.stringify(props.categories ?? null);\n const categoriesRef = useRef(props.categories);\n categoriesRef.current = props.categories;\n\n // A layout effect: the page's passive effects (first events, tracker) must find the API already there.\n useIsomorphicLayoutEffect(() => {\n // A banner from the script tag or another instance already runs on this page.\n if (window.BitsnapConsent) return;\n const api = createConsent({\n projectKey,\n theme,\n locale,\n position,\n privacyPolicyUrl,\n scope,\n apiHost,\n collectHost,\n reloadOnWithdraw,\n gcm,\n preview,\n title,\n description,\n categories: categoriesRef.current,\n config: configRef.current,\n });\n window.BitsnapConsent = api;\n return () => {\n api.destroy();\n if (window.BitsnapConsent === api) delete window.BitsnapConsent;\n };\n }, [\n projectKey, theme, locale, position, privacyPolicyUrl, scope, apiHost, collectHost, reloadOnWithdraw,\n gcm, preview, title, description, categoriesKey, configKey,\n ]);\n\n return null;\n}\n\nlet lastKey = \"null\";\nlet lastState: ConsentState | null = null;\n\nfunction getSnapshot(): ConsentState | null {\n const current = typeof window === \"undefined\" ? null : (window.BitsnapConsent?.get() ?? null);\n const key = JSON.stringify(current);\n if (key !== lastKey) {\n lastKey = key;\n lastState = current;\n }\n return lastState;\n}\n\nfunction subscribe(onChange: () => void): () => void {\n window.addEventListener(\"bitsnap:consent\", onChange);\n window.addEventListener(\"bitsnap:consent:ready\", onChange);\n return () => {\n window.removeEventListener(\"bitsnap:consent\", onChange);\n window.removeEventListener(\"bitsnap:consent:ready\", onChange);\n };\n}\n\nexport type UseConsent = {\n /** The stored choice, or `null` while there is none (or on the server). */\n consent: ConsentState | null;\n /** `true` once the visitor has made a choice that is still valid. */\n decided: boolean;\n open: () => void;\n accept: (categories?: Partial<ConsentChoices>) => void;\n acceptAll: () => void;\n rejectAll: () => void;\n reset: () => void;\n};\n\n/** Current consent state of the page, kept up to date as the visitor chooses. */\nexport function useConsent(): UseConsent {\n const consent = useSyncExternalStore(subscribe, getSnapshot, () => null);\n const open = useCallback(() => window.BitsnapConsent?.open(), []);\n const accept = useCallback((categories?: Partial<ConsentChoices>) => window.BitsnapConsent?.accept(categories), []);\n const acceptAll = useCallback(() => window.BitsnapConsent?.acceptAll(), []);\n const rejectAll = useCallback(() => window.BitsnapConsent?.rejectAll(), []);\n const reset = useCallback(() => window.BitsnapConsent?.reset(), []);\n return { consent, decided: consent !== null, open, accept, acceptAll, rejectAll, reset };\n}\n","export type ConsentCategory = \"analytics\" | \"marketing\" | \"preferences\";\n\nexport type ConsentChoices = Record<ConsentCategory, boolean>;\n\nexport type ConsentTheme = \"auto\" | \"light\" | \"dark\";\n\nexport type ConsentPosition = \"bottom\" | \"bottom-left\" | \"bottom-right\";\n\nexport type ConsentLocale = \"pl\" | \"en\";\n\nexport const CONSENT_CATEGORIES: readonly ConsentCategory[] = [\n \"analytics\",\n \"marketing\",\n \"preferences\",\n];\n\nexport const CONSENT_TEXT_KEYS = [\n \"title\",\n \"description\",\n \"acceptAll\",\n \"rejectAll\",\n \"settings\",\n \"save\",\n \"close\",\n \"necessaryTitle\",\n \"necessaryDescription\",\n \"analyticsTitle\",\n \"analyticsDescription\",\n \"marketingTitle\",\n \"marketingDescription\",\n \"preferencesTitle\",\n \"preferencesDescription\",\n \"poweredBy\",\n] as const;\n\nexport type ConsentTexts = Record<(typeof CONSENT_TEXT_KEYS)[number], string>;\n\nexport type ConsentService = {\n name: string;\n category: ConsentCategory;\n /** Cookies to delete (best effort) when the category is withdrawn. A trailing `*` matches a prefix. */\n cookies?: string[];\n};\n\nexport type ConsentConfig = {\n /** Banner version. A stored choice with a lower `bv` makes the banner ask again. */\n version: number;\n theme: ConsentTheme;\n accentColor?: string;\n position: ConsentPosition;\n privacyPolicyUrl?: string;\n categories: ConsentChoices;\n services: ConsentService[];\n texts?: Partial<Record<ConsentLocale, Partial<ConsentTexts>>>;\n poweredBy: true;\n};\n\n/** What the consent cookie holds, and what `get()` returns. */\nexport type ConsentState = {\n id: string;\n /** Unix seconds. */\n ts: number;\n /** Version of the banner the choice was made on. */\n bv: number;\n c: ConsentChoices;\n};\n\nexport type ConsentAction =\n | \"accept_all\"\n | \"reject_all\"\n | \"custom\"\n | \"withdraw\";\n\n/** `detail` of `bitsnap:consent` and the argument of `on('change')`. */\nexport type ConsentChangeDetail = ConsentState & {\n /** `reset` when the visitor's stored choice was cleared (`get()` is `null` then). */\n action: ConsentAction | \"reset\";\n};\n\nexport type ConsentOptions = {\n /** Public project key. Enables the remote config and the consent log. */\n projectKey?: string;\n theme?: ConsentTheme;\n locale?: \"auto\" | ConsentLocale;\n position?: ConsentPosition;\n privacyPolicyUrl?: string;\n /** `project` keeps one cookie per project key (`bitsnap_consent_<key>`). */\n scope?: \"global\" | \"project\";\n apiHost?: string;\n collectHost?: string;\n /** Google Consent Mode v2. `auto` is on when `gtag` or `dataLayer` already exist. */\n gcm?: \"auto\" | \"on\" | \"off\";\n /** Local overrides, merged over the defaults and the remote config. */\n config?: Partial<ConsentConfig>;\n /** Categories used when no remote config is loaded. */\n categories?: Partial<ConsentChoices>;\n reloadOnWithdraw?: boolean;\n /**\n * Dashboard preview: shows the banner at once from the local options, never\n * reads or writes the cookie, loads no remote config, sends no consent log,\n * and the buttons only close the banner.\n */\n preview?: boolean;\n /** Replaces the banner title (current locale, non-empty values only). */\n title?: string;\n /** Replaces the banner description (current locale, non-empty values only). */\n description?: string;\n};\n\nexport type BitsnapConsentApi = {\n /**\n * Whose choice this banner holds: `project` keeps one cookie per project key\n * (`bitsnap_consent_<key>`), `global` the site-wide `bitsnap_consent`.\n */\n readonly scope: \"global\" | \"project\";\n /** The bare public project key the banner was started with, if any (`pk_` stripped). */\n readonly projectKey: string | null;\n get(): ConsentState | null;\n /**\n * `true` when the visitor refused `category`, also in a choice made on an older banner\n * version (which `get()` no longer returns) that they have not answered again yet.\n */\n denied(category: ConsentCategory): boolean;\n on(event: \"change\", fn: (detail: ConsentChangeDetail) => void): () => void;\n open(): void;\n close(): void;\n accept(categories?: Partial<ConsentChoices>): void;\n acceptAll(): void;\n rejectAll(): void;\n reset(): void;\n /** Resolves with the stored choice (or `null`) once config and stored choice are applied. */\n ready: Promise<ConsentState | null>;\n destroy(): void;\n};\n","import { CONSENT_CATEGORIES, type ConsentChoices } from \"./types\";\n\nconst SCRIPT_SELECTOR = 'script[type=\"text/plain\"][data-bitsnap-consent]:not([data-bitsnap-queued])';\nconst FRAME_SELECTOR = \"iframe[data-bitsnap-consent][data-src]\";\n\nconst allowed = (c: ConsentChoices, category: string | null): boolean =>\n (CONSENT_CATEGORIES as readonly string[]).includes(category ?? \"\") &&\n c[category as keyof ConsentChoices];\n\n/** Settles when the script has loaded or failed: a broken script must not block the ones after it. */\nfunction settled(script: HTMLScriptElement): Promise<void> {\n return new Promise((resolve) => {\n script.addEventListener(\"load\", () => resolve(), { once: true });\n script.addEventListener(\"error\", () => resolve(), { once: true });\n });\n}\n\n/** Swaps the placeholder for a live script; resolves once an external one has loaded. */\nfunction run(doc: Document, placeholder: HTMLScriptElement): Promise<void> | undefined {\n const script = doc.createElement(\"script\");\n for (const { name, value } of Array.from(placeholder.attributes)) {\n if (name === \"type\" || name === \"data-bitsnap-queued\") continue;\n if (name === \"data-type\") {\n // The embed writes the original `type=\"module\"` here, as `type` itself is `text/plain`.\n if (value === \"module\") script.setAttribute(\"type\", \"module\");\n } else if (name === \"data-src\") script.setAttribute(\"src\", value);\n else script.setAttribute(name, value);\n }\n if (placeholder.nonce) script.nonce = placeholder.nonce;\n const external = script.hasAttribute(\"src\");\n if (external) {\n // Dynamic scripts are async by default; keep the order of the page.\n if (!placeholder.hasAttribute(\"async\")) script.async = false;\n } else {\n script.text = placeholder.text;\n }\n const loaded = external ? settled(script) : undefined;\n placeholder.parentNode?.replaceChild(script, placeholder);\n return loaded;\n}\n\n/**\n * Turns the consented placeholders into live elements:\n * `<script type=\"text/plain\" data-bitsnap-consent=\"marketing\" [data-src] [data-type=\"module\"]>` becomes\n * a real script (attributes copied, `type` dropped or restored from `data-type`, `data-src` -> `src`),\n * and `<iframe data-src>` gets its `src`.\n * Placeholders run in document order: an inline script waits for the external scripts before it\n * (`<script src=lib.js>` then `<script>Lib.init()</script>`), external ones only follow each other.\n */\nexport function activateScripts(doc: Document, c: ConsentChoices): number {\n let activated = 0;\n try {\n /** Set once a placeholder had to wait: everything after it queues behind it. */\n let gate: Promise<void> | null = null;\n /** External scripts inserted since the last inline one. */\n let loading: Promise<void>[] = [];\n doc.querySelectorAll<HTMLScriptElement>(SCRIPT_SELECTOR).forEach((placeholder) => {\n if (!allowed(c, placeholder.getAttribute(\"data-bitsnap-consent\"))) return;\n activated++;\n const inline = !placeholder.hasAttribute(\"data-src\") && !placeholder.hasAttribute(\"src\");\n if (gate || (inline && loading.length > 0)) {\n // Marked so that another call (a later choice) does not queue it again.\n placeholder.setAttribute(\"data-bitsnap-queued\", \"\");\n gate = (gate ?? Promise.all(loading).then(() => {})).then(() => {\n try {\n return run(doc, placeholder);\n } catch {\n // never throw into the host page\n }\n });\n loading = [];\n return;\n }\n const loaded = run(doc, placeholder);\n if (loaded) loading.push(loaded);\n });\n doc.querySelectorAll<HTMLIFrameElement>(FRAME_SELECTOR).forEach((frame) => {\n if (!allowed(c, frame.getAttribute(\"data-bitsnap-consent\"))) return;\n const src = frame.getAttribute(\"data-src\");\n if (src && frame.getAttribute(\"src\") !== src) {\n frame.setAttribute(\"src\", src);\n frame.removeAttribute(\"data-src\");\n activated++;\n }\n });\n } catch {\n // never throw into the host page\n }\n return activated;\n}\n","import {\n CONSENT_CATEGORIES,\n type ConsentChoices,\n type ConsentConfig,\n type ConsentService,\n} from \"./types\";\n\nexport const DEFAULT_API_HOST = \"https://bitsnap.pl\";\nexport const DEFAULT_COLLECT_HOST = \"https://collect.bitsnap.pl\";\n\nexport function defaultConfig(): ConsentConfig {\n return {\n version: 1,\n theme: \"auto\",\n position: \"bottom\",\n categories: { analytics: true, marketing: true, preferences: false },\n services: [],\n poweredBy: true,\n };\n}\n\nconst THEMES = [\"auto\", \"light\", \"dark\"];\nconst POSITIONS = [\"bottom\", \"bottom-left\", \"bottom-right\"];\n\nconst HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;\n\nexport function safeColor(value: unknown): string | undefined {\n return typeof value === \"string\" && HEX_COLOR.test(value.trim())\n ? value.trim()\n : undefined;\n}\n\n/** Only http(s) and relative URLs may end up in the banner. */\nexport function safeUrl(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const url = value.trim();\n return /^(?:https?:\\/\\/|\\/(?!\\/)|#|\\?)/i.test(url) ? url : undefined;\n}\n\nfunction cleanServices(value: unknown): ConsentService[] {\n if (!Array.isArray(value)) return [];\n const services: ConsentService[] = [];\n for (const item of value) {\n if (\n item &&\n typeof item.name === \"string\" &&\n (CONSENT_CATEGORIES as readonly string[]).includes(item.category)\n ) {\n services.push({\n name: item.name,\n category: item.category,\n cookies: Array.isArray(item.cookies)\n ? item.cookies.filter((cookie: unknown) => typeof cookie === \"string\")\n : undefined,\n });\n }\n }\n return services;\n}\n\n/** Layers partial configs (later wins) over the defaults, dropping invalid values. */\nexport function mergeConfig(\n ...layers: (Partial<ConsentConfig> | null | undefined)[]\n): ConsentConfig {\n const result = defaultConfig();\n for (const layer of layers) {\n if (!layer || typeof layer !== \"object\") continue;\n if (typeof layer.version === \"number\" && layer.version >= 0) {\n result.version = Math.floor(layer.version);\n }\n if (THEMES.includes(layer.theme as string)) result.theme = layer.theme!;\n if (POSITIONS.includes(layer.position as string)) result.position = layer.position!;\n const accent = safeColor(layer.accentColor);\n if (accent) result.accentColor = accent;\n const privacy = safeUrl(layer.privacyPolicyUrl);\n if (privacy) result.privacyPolicyUrl = privacy;\n if (layer.categories && typeof layer.categories === \"object\") {\n for (const category of CONSENT_CATEGORIES) {\n if (typeof layer.categories[category] === \"boolean\") {\n result.categories[category] = layer.categories[category];\n }\n }\n }\n if (layer.services !== undefined) result.services = cleanServices(layer.services);\n if (layer.texts && typeof layer.texts === \"object\") result.texts = layer.texts;\n }\n return result;\n}\n\nexport function parseCategoryList(value: string | null | undefined): ConsentChoices {\n const wanted = (value ?? \"\").split(\",\").map((item) => item.trim());\n return {\n analytics: wanted.includes(\"analytics\"),\n marketing: wanted.includes(\"marketing\"),\n preferences: wanted.includes(\"preferences\"),\n };\n}\n\n/** What `GET /api/consent/config` answers: whether the banner is on, and its config. */\nexport type RemoteConsentConfig = {\n /** `false` when the project turned the banner off: nothing is shown, stored choices still apply. */\n enabled: boolean;\n config: Partial<ConsentConfig> | null;\n};\n\n/** Loads the project's banner config. Resolves to `null` on any failure or after the timeout. */\nexport async function fetchRemoteConfig(\n apiHost: string,\n key: string,\n timeoutMs = 3000,\n): Promise<RemoteConsentConfig | null> {\n if (typeof fetch !== \"function\") return null;\n const controller = typeof AbortController === \"function\" ? new AbortController() : undefined;\n const timer = setTimeout(() => controller?.abort(), timeoutMs);\n try {\n const response = await fetch(\n `${apiHost.replace(/\\/+$/, \"\")}/api/consent/config?key=${encodeURIComponent(key)}`,\n { signal: controller?.signal, credentials: \"omit\" },\n );\n if (!response.ok) return null;\n const body = await response.json();\n if (!body || typeof body !== \"object\") return null;\n const config = body.config && typeof body.config === \"object\" ? body.config : null;\n const enabled = body.enabled !== false;\n return config || !enabled ? { enabled, config } : null;\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n","/** Colors of both themes. Every text/background pair is checked against WCAG AA in the tests. */\nexport const PALETTE = {\n light: {\n bg: \"#ffffff\",\n fg: \"#14171c\",\n muted: \"#495260\",\n border: \"#c9ced6\",\n off: \"#6b7280\",\n thumb: \"#ffffff\",\n accent: \"#1d4ed8\",\n onAccent: \"#ffffff\",\n link: \"#1d4ed8\",\n },\n dark: {\n bg: \"#171a20\",\n fg: \"#f1f3f6\",\n muted: \"#b7beca\",\n border: \"#3a404b\",\n off: \"#8b93a1\",\n thumb: \"#171a20\",\n accent: \"#8ab4ff\",\n onAccent: \"#0b1220\",\n link: \"#9dc0ff\",\n },\n} as const;\n\nconst vars = (p: (typeof PALETTE)[\"light\"]) =>\n `--bg:${p.bg};--fg:${p.fg};--muted:${p.muted};--border:${p.border};--off:${p.off};--thumb:${p.thumb};--accent:${p.accent};--on-accent:${p.onAccent};--link:${p.link};--focus:${p.accent}`;\n\nexport const STYLES = `\n:host{all:initial}\n*{box-sizing:border-box}\n[hidden]{display:none!important}\n.root{${vars(PALETTE.light)};position:fixed;inset:0;pointer-events:none;z-index:2147483000;font:14px/1.5 system-ui,-apple-system,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,sans-serif;color:var(--fg);-webkit-text-size-adjust:100%}\n.root[data-theme=dark]{${vars(PALETTE.dark)};color-scheme:dark}\nbutton,input{font:inherit;color:inherit}\n.banner,.overlay{pointer-events:auto}\n@supports (color:color-mix(in srgb,red 50%,blue)){::selection{background:color-mix(in srgb,var(--accent) 24%,transparent)}}\n.banner{position:absolute;background:var(--bg);color:var(--fg);max-height:92vh;overflow:auto;outline:none}\n.banner.enter{animation:in .22s ease-out}\n.banner.closing{animation:out .2s ease-in forwards;pointer-events:none}\n.bottom{left:0;right:0;bottom:0;border-top:1px solid var(--border);box-shadow:0 -8px 28px rgba(15,23,42,.08);padding:20px 24px calc(20px + env(safe-area-inset-bottom,0px))}\n.root[data-theme=dark] .bottom{box-shadow:0 -8px 28px rgba(0,0,0,.4)}\n.bottom .inner{display:grid;grid-template-columns:minmax(min(100%,42ch),1fr) auto;grid-template-areas:\"text actions\" \"foot actions\";gap:4px 40px;align-items:center;max-width:1120px;margin:0 auto}\n.bottom .text{max-width:68ch}\n.bottom .btn,.bottom .link-btn{flex:0 0 auto}\n.card{bottom:16px;width:min(400px,calc(100vw - 32px));border:1px solid var(--border);border-radius:14px;padding:20px;box-shadow:0 12px 32px rgba(15,23,42,.14)}\n.root[data-theme=dark] .card{box-shadow:0 12px 32px rgba(0,0,0,.45)}\n.bottom-left{left:16px}\n.bottom-right{right:16px}\n.card .inner{display:flex;flex-direction:column;gap:16px}\n.text{grid-area:text}\n.actions{grid-area:actions;display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:flex-end}\n.card .actions{justify-content:stretch}\n.foot{grid-area:foot}\n.title{overflow-wrap:anywhere;font-size:17px;font-weight:650;line-height:1.35;letter-spacing:-.01em;text-wrap:balance;margin:0 0 6px}\n.desc{overflow-wrap:anywhere;margin:0;color:var(--muted);line-height:1.55;text-wrap:pretty}\n.desc a{color:var(--link);text-decoration:underline;text-underline-offset:3px}\n.btn,.link-btn{min-height:38px;padding:0 16px;border-radius:8px;font-size:13.5px;font-weight:600;cursor:pointer;white-space:nowrap;transition:background-color .12s ease-out,filter .12s ease-out,border-color .12s ease-out}\n.btn{border:2px solid var(--accent);background:var(--accent);color:var(--on-accent);flex:1 1 150px}\n.btn:hover{filter:brightness(.93)}\n.btn:active{filter:brightness(.87)}\n.link-btn{border:1px solid var(--off);background:transparent;color:var(--fg);text-decoration:none}\n.link-btn:hover{border-color:var(--fg)}\n@supports (color:color-mix(in srgb,red 50%,blue)){.link-btn:hover{background:color-mix(in srgb,var(--fg) 9%,transparent)}.link-btn:active{background:color-mix(in srgb,var(--fg) 14%,transparent)}}\n.card .link-btn{flex:1 1 100%}\nbutton:focus-visible,a:focus-visible,.dialog:focus-visible{outline:2px solid var(--focus);outline-offset:2px}\n.foot{font-size:12px;color:var(--muted);padding-top:8px}\n.foot a{color:var(--muted);text-decoration:none;display:inline-flex;align-items:center;gap:5px;vertical-align:middle}\n.foot a:hover{text-decoration:underline}\n.foot b{color:var(--fg);font-weight:700;letter-spacing:-.01em}\n.foot svg{display:block}\n.overlay{position:absolute;inset:0;background:rgba(10,12,16,.58);display:flex;align-items:center;justify-content:center;padding:16px}\n.overlay.enter{animation:fade .2s ease-out}\n.overlay.closing{animation:fadeout .2s ease-in forwards;pointer-events:none}\n.dialog{background:var(--bg);color:var(--fg);width:100%;max-width:560px;max-height:min(92vh,760px);display:flex;flex-direction:column;border-radius:16px;box-shadow:0 16px 48px rgba(0,0,0,.35);outline:none}\n.dialog.enter{animation:in .22s ease-out}\n.d-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:16px 8px 8px 24px}\n.d-head .title{margin:0;font-size:18px}\n.x{width:44px;height:44px;border:0;border-radius:10px;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex:none}\n.x:hover{background:var(--border)}\n.d-body{overflow:auto;padding:0 24px 8px;flex:1}\n.cat{border-top:1px solid var(--border);padding:10px 0 14px}\n.row{display:flex;align-items:center;justify-content:space-between;gap:12px}\n.cat-title{font-weight:600;font-size:15px}\n.always{font-size:12px;color:var(--muted);margin-right:4px}\n.cat p{margin:0;color:var(--muted)}\n.cat .svc{margin-top:6px;font-size:13px}\n.cat .svc b{font-weight:600;color:var(--fg)}\n.switch{position:relative;width:52px;height:44px;display:flex;align-items:center;justify-content:flex-end;flex:none}\n.switch input{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}\n.switch input:disabled{cursor:default}\n.track{width:44px;height:24px;border-radius:12px;background:var(--off);position:relative;transition:background .15s}\n.track::after{content:\"\";position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:var(--thumb);transition:transform .15s}\ninput:checked+.track{background:var(--accent)}\ninput:checked+.track::after{transform:translateX(20px);background:var(--on-accent)}\ninput:disabled+.track{opacity:.6}\ninput:focus-visible+.track{outline:2px solid var(--focus);outline-offset:2px}\n.d-foot{display:flex;flex-direction:column;gap:8px;padding:12px 24px 16px;border-top:1px solid var(--border)}\n.d-foot .actions{justify-content:stretch}\n@keyframes in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}\n@keyframes fade{from{opacity:0}to{opacity:1}}\n@keyframes out{from{opacity:1;transform:none}to{opacity:0;transform:translateY(12px)}}\n@keyframes fadeout{from{opacity:1}to{opacity:0}}\n@media(max-width:960px){\n.bottom .inner{grid-template-columns:minmax(0,1fr);grid-template-areas:\"text\" \"actions\" \"foot\";gap:14px}\n.bottom .actions{justify-content:flex-start}\n}\n@media(max-width:640px){\n.bottom{padding:16px 16px calc(16px + env(safe-area-inset-bottom,0px))}\n.bottom .inner{gap:12px}\n.bottom .actions{justify-content:stretch}\n.bottom .btn{flex:1 1 130px}\n.bottom .link-btn{flex:1 1 100%}\n.card{left:8px;right:8px;bottom:8px;width:auto}\n.overlay{align-items:flex-end;padding:0}\n.dialog{max-width:none;border-radius:16px 16px 0 0;max-height:92vh}\n.d-head{padding-left:16px}\n.d-body{padding:0 16px 8px}\n.d-foot{padding:12px 16px calc(16px + env(safe-area-inset-bottom,0px))}\n.btn{flex-basis:130px}\n.btn,.link-btn{padding:0 12px;min-height:40px}\n}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}\n`.replace(/\\n/g, \"\");\n","import { contrastRatio, onColor } from \"./contrast\";\nimport { UI_LABELS } from \"./texts\";\nimport { PALETTE, STYLES } from \"./styles\";\nimport {\n CONSENT_CATEGORIES,\n type ConsentCategory,\n type ConsentChoices,\n type ConsentConfig,\n type ConsentLocale,\n type ConsentTexts,\n} from \"./types\";\n\nexport type UIOptions = {\n doc: Document;\n config: ConsentConfig;\n texts: ConsentTexts;\n locale: ConsentLocale;\n /** Current choice, used to preset the toggles. */\n getChoices(): ConsentChoices | null;\n /** Whether a valid choice exists, i.e. closing the settings must not bring back the banner. */\n hasChoice(): boolean;\n onAcceptAll(): void;\n onRejectAll(): void;\n onSave(choices: ConsentChoices): void;\n};\n\nexport type ConsentUI = {\n /** `focus` moves keyboard focus into the banner; leave it off when the banner opens on page load. */\n showBanner(focus?: boolean): void;\n showSettings(): void;\n hide(): void;\n destroy(): void;\n};\n\nconst WORDMARK =\n '<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" aria-hidden=\"true\"><rect width=\"16\" height=\"16\" rx=\"4\" fill=\"currentColor\" style=\"color:var(--fg)\"/><circle cx=\"8\" cy=\"8\" r=\"3\" fill=\"var(--bg)\"/></svg>';\n\nconst CLOSE_ICON =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"M3 3l10 10M13 3L3 13\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" fill=\"none\"/></svg>';\n\nconst FOCUSABLE = \"button:not([disabled]),input:not([disabled]),a[href]\";\n\n/**\n * How long the exit animation runs. The banner and the settings layer fade\n * out before they disappear, so the page does not blink.\n */\nexport const EXIT_ANIMATION_MS = 200;\n\n/** A running exit animation: the node, its listener and its fallback timer. */\ntype Exit = {\n node: HTMLElement;\n listener: (event: Event) => void;\n timer: number | undefined;\n};\n\nexport function createUI(options: UIOptions): ConsentUI {\n const { doc, config, texts, locale } = options;\n const labels = UI_LABELS[locale];\n let host: HTMLElement | null = null;\n let shadow: ShadowRoot | null = null;\n let root: HTMLElement;\n let banner: HTMLElement;\n let overlay: HTMLElement;\n let dialog: HTMLElement;\n let settingsButton: HTMLElement;\n let layer: \"none\" | \"banner\" | \"settings\" = \"none\";\n let opener: HTMLElement | null = null;\n let media: MediaQueryList | null = null;\n const toggles = new Map<ConsentCategory, HTMLInputElement>();\n /** The exit animations running right now; at most one per node. */\n const exits: Exit[] = [];\n\n function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n props: Record<string, string> = {},\n ...children: (Node | string)[]\n ): HTMLElementTagNameMap[K] {\n const node = doc.createElement(tag);\n for (const key of Object.keys(props)) {\n if (key === \"class\") node.className = props[key];\n else node.setAttribute(key, props[key]);\n }\n for (const child of children) node.append(child);\n return node;\n }\n\n function button(cls: string, text: string, onClick: () => void) {\n const node = el(\"button\", { type: \"button\", class: cls }, text);\n node.addEventListener(\"click\", onClick);\n return node;\n }\n\n function powered(): HTMLElement {\n const link = el(\"a\", {\n href: `https://bitsnap.pl/?utm_source=consent_banner&utm_medium=referral&utm_content=${encodeURIComponent(doc.location?.hostname ?? \"\")}`,\n target: \"_blank\",\n rel: \"noopener\",\n });\n link.append(texts.poweredBy + \" \");\n link.insertAdjacentHTML(\"beforeend\", WORDMARK);\n link.append(el(\"b\", {}, \"bitsnap\"));\n return el(\"div\", { class: \"foot\" }, link);\n }\n\n function description(): HTMLElement {\n const node = el(\"p\", { class: \"desc\", id: \"d\" }, texts.description);\n if (config.privacyPolicyUrl) {\n node.append(\n \" \",\n el(\n \"a\",\n { href: config.privacyPolicyUrl, target: \"_blank\", rel: \"noopener\" },\n labels.privacy,\n ),\n );\n }\n return node;\n }\n\n function buildBanner() {\n settingsButton = button(\"link-btn\", texts.settings, () => showSettings());\n banner = el(\n \"div\",\n {\n class: `banner ${config.position === \"bottom\" ? \"bottom\" : `card ${config.position}`}`,\n // Not modal: the page stays usable, so it is a live region, not a dialog.\n role: \"region\",\n \"aria-live\": \"polite\",\n \"aria-labelledby\": \"t\",\n \"aria-describedby\": \"d\",\n tabindex: \"-1\",\n },\n el(\n \"div\",\n { class: \"inner\" },\n el(\"div\", { class: \"text\" }, el(\"div\", { class: \"title\", id: \"t\" }, texts.title), description()),\n el(\n \"div\",\n { class: \"actions\" },\n button(\"btn\", texts.acceptAll, options.onAcceptAll),\n button(\"btn\", texts.rejectAll, options.onRejectAll),\n settingsButton,\n ),\n powered(),\n ),\n );\n }\n\n function category(id: ConsentCategory | \"necessary\"): HTMLElement {\n const services = config.services.filter((service) => service.category === id);\n const input = el(\"input\", { type: \"checkbox\", role: \"switch\", \"aria-labelledby\": `l-${id}`, \"aria-describedby\": `p-${id}` });\n if (id === \"necessary\") {\n input.checked = true;\n input.disabled = true;\n } else {\n toggles.set(id, input);\n }\n const node = el(\n \"div\",\n { class: \"cat\" },\n el(\n \"div\",\n { class: \"row\" },\n el(\"span\", { class: \"cat-title\", id: `l-${id}` }, texts[`${id}Title` as keyof ConsentTexts]),\n el(\n \"span\",\n { style: \"display:flex;align-items:center\" },\n ...(id === \"necessary\" ? [el(\"span\", { class: \"always\" }, labels.always)] : []),\n el(\"label\", { class: \"switch\" }, input, el(\"span\", { class: \"track\" })),\n ),\n ),\n el(\"p\", { id: `p-${id}` }, texts[`${id}Description` as keyof ConsentTexts]),\n );\n if (services.length > 0) {\n node.append(\n el(\"p\", { class: \"svc\" }, el(\"b\", {}, `${labels.services}: `), services.map((s) => s.name).join(\", \")),\n );\n }\n return node;\n }\n\n function buildSettings() {\n const close = el(\"button\", { type: \"button\", class: \"x\", \"aria-label\": texts.close });\n close.insertAdjacentHTML(\"beforeend\", CLOSE_ICON);\n close.addEventListener(\"click\", () => closeSettings());\n const body = el(\"div\", { class: \"d-body\" }, category(\"necessary\"));\n for (const id of CONSENT_CATEGORIES) {\n if (config.categories[id]) body.append(category(id));\n }\n dialog = el(\n \"div\",\n { class: \"dialog\", role: \"dialog\", \"aria-modal\": \"true\", \"aria-labelledby\": \"st\", tabindex: \"-1\" },\n el(\"div\", { class: \"d-head\" }, el(\"div\", { class: \"title\", id: \"st\" }, texts.settings), close),\n body,\n el(\n \"div\",\n { class: \"d-foot\" },\n el(\n \"div\",\n { class: \"actions\" },\n button(\"btn\", texts.save, () => options.onSave(readToggles())),\n button(\"btn\", texts.acceptAll, options.onAcceptAll),\n button(\"btn\", texts.rejectAll, options.onRejectAll),\n ),\n powered(),\n ),\n );\n overlay = el(\"div\", { class: \"overlay\" }, dialog);\n overlay.addEventListener(\"mousedown\", (event) => {\n if (event.target === overlay) closeSettings();\n });\n }\n\n function readToggles(): ConsentChoices {\n const choices = { analytics: false, marketing: false, preferences: false };\n toggles.forEach((input, key) => (choices[key] = input.checked));\n return choices;\n }\n\n function applyTheme() {\n const dark = config.theme === \"dark\" || (config.theme === \"auto\" && !!media?.matches);\n root.setAttribute(\"data-theme\", dark ? \"dark\" : \"light\");\n applyAccent(dark);\n }\n\n /** A custom accent that is too faint on the dark background gives way to the dark theme's own. */\n function applyAccent(dark: boolean) {\n const accent = config.accentColor;\n if (!accent) return;\n if (dark && contrastRatio(accent, PALETTE.dark.bg) < 3) {\n for (const name of [\"--accent\", \"--focus\", \"--on-accent\"]) root.style.removeProperty(name);\n return;\n }\n root.style.setProperty(\"--accent\", accent);\n root.style.setProperty(\"--focus\", accent);\n root.style.setProperty(\"--on-accent\", onColor(accent));\n }\n\n function ensure() {\n if (host) return;\n host = el(\"div\", { id: \"bitsnap-consent\", \"data-bitsnap-consent-host\": \"\" });\n shadow = host.attachShadow({ mode: \"open\" });\n shadow.append(el(\"style\", {}, STYLES));\n root = el(\"div\", { class: \"root\", lang: locale });\n buildBanner();\n buildSettings();\n banner.hidden = true;\n overlay.hidden = true;\n root.append(banner, overlay);\n shadow.append(root);\n if (typeof doc.defaultView?.matchMedia === \"function\") {\n media = doc.defaultView.matchMedia(\"(prefers-color-scheme: dark)\");\n media.addEventListener?.(\"change\", applyTheme);\n }\n applyTheme();\n doc.body.append(host);\n }\n\n function focusables(): HTMLElement[] {\n return Array.from(dialog.querySelectorAll<HTMLElement>(FOCUSABLE));\n }\n\n /** Whether the visitor asked for less motion: the layers then come and go at once. */\n function reducedMotion(): boolean {\n try {\n return doc.defaultView?.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches === true;\n } catch {\n return false;\n }\n }\n\n /** Stops a running exit animation; the node stays where it is. */\n function stopExit(node: HTMLElement) {\n const index = exits.findIndex((exit) => exit.node === node);\n if (index === -1) return;\n const [exit] = exits.splice(index, 1);\n exit.node.removeEventListener(\"animationend\", exit.listener);\n if (exit.timer !== undefined) doc.defaultView?.clearTimeout(exit.timer);\n exit.node.classList.remove(\"closing\");\n }\n\n /** Hides the node at once, without an animation. */\n function hideNow(node: HTMLElement) {\n stopExit(node);\n node.hidden = true;\n }\n\n /**\n * Fades the node out and hides it when the animation ends. The timer is the\n * fallback for a browser that sends no event (a hidden tab, a reduced\n * motion setting): the layer always disappears.\n */\n function exitTo(node: HTMLElement) {\n stopExit(node);\n if (node.hidden) return;\n if (reducedMotion()) {\n node.hidden = true;\n return;\n }\n node.classList.add(\"closing\");\n const listener = (event: Event) => {\n // Another animation of a child ends first; only our own counts.\n if (event.target !== node) return;\n finish();\n };\n const timer = doc.defaultView?.setTimeout(finish, EXIT_ANIMATION_MS + 120);\n function finish() {\n stopExit(node);\n node.hidden = true;\n }\n node.addEventListener(\"animationend\", listener);\n exits.push({ node, listener, timer });\n }\n\n /** Shows the node and plays the entrance animation again. */\n function enter(node: HTMLElement) {\n stopExit(node);\n node.hidden = false;\n // The class has to leave and come back, or the animation does not restart.\n node.classList.remove(\"enter\");\n void node.offsetWidth;\n node.classList.add(\"enter\");\n }\n\n function onKeydown(event: KeyboardEvent) {\n if (layer !== \"settings\") return;\n if (event.key === \"Escape\") {\n event.preventDefault();\n closeSettings();\n return;\n }\n if (event.key !== \"Tab\") return;\n const items = focusables();\n if (items.length === 0) return;\n const active = shadow?.activeElement as HTMLElement | null;\n const first = items[0];\n const last = items[items.length - 1];\n const outside = !active || !dialog.contains(active);\n if (event.shiftKey && (outside || active === first || active === dialog)) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (outside || active === last)) {\n event.preventDefault();\n first.focus();\n }\n }\n\n function showBanner(focus = false) {\n try {\n ensure();\n layer = \"banner\";\n doc.removeEventListener(\"keydown\", onKeydown, true);\n hideNow(overlay);\n enter(banner);\n if (focus) banner.focus({ preventScroll: true });\n } catch {\n // never throw into the host page\n }\n }\n\n function showSettings() {\n try {\n ensure();\n if (layer !== \"settings\") {\n const active = doc.activeElement as HTMLElement | null;\n opener = active && active !== host && active !== doc.body ? active : null;\n }\n const current = options.getChoices();\n toggles.forEach((input, key) => (input.checked = !!current?.[key]));\n layer = \"settings\";\n hideNow(banner);\n enter(overlay);\n enter(dialog);\n doc.addEventListener(\"keydown\", onKeydown, true);\n dialog.focus({ preventScroll: true });\n } catch {\n // never throw into the host page\n }\n }\n\n function closeSettings() {\n if (layer !== \"settings\") return;\n doc.removeEventListener(\"keydown\", onKeydown, true);\n exitTo(overlay);\n if (options.hasChoice()) {\n layer = \"none\";\n opener?.focus?.({ preventScroll: true });\n } else {\n layer = \"banner\";\n enter(banner);\n settingsButton.focus({ preventScroll: true });\n }\n }\n\n function hide() {\n if (!host) return;\n doc.removeEventListener(\"keydown\", onKeydown, true);\n const wasSettings = layer === \"settings\";\n layer = \"none\";\n exitTo(banner);\n exitTo(overlay);\n if (wasSettings) opener?.focus?.({ preventScroll: true });\n }\n\n function destroy() {\n doc.removeEventListener(\"keydown\", onKeydown, true);\n media?.removeEventListener?.(\"change\", applyTheme);\n while (exits.length > 0) stopExit(exits[0]!.node);\n host?.remove();\n host = null;\n shadow = null;\n layer = \"none\";\n }\n\n return { showBanner, showSettings, hide, destroy };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/emems/.t3/worktrees/bitsnap-react/emems-chat-v1/dist/consent.js","../src/consent/react.tsx","../src/consent/core/types.ts","../src/consent/core/activate.ts","../src/consent/core/config.ts","../src/consent/core/styles.ts","../src/consent/core/ui.ts"],"names":["CONSENT_CATEGORIES","SCRIPT_SELECTOR","FRAME_SELECTOR","allowed","c","category","settled","script","resolve","run","doc","placeholder","name","value","external","loaded","activateScripts","activated","gate","loading","inline","frame","src","DEFAULT_API_HOST","DEFAULT_COLLECT_HOST","defaultConfig","THEMES","POSITIONS","HEX_COLOR","safeColor","safeUrl","url","cleanServices","services","item","cookie","mergeConfig","layers","result","layer","accent","privacy","fetchRemoteConfig","apiHost","key","timeoutMs","controller","timer","response"],"mappings":"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","file":"/home/emems/.t3/worktrees/bitsnap-react/emems-chat-v1/dist/consent.js","sourcesContent":[null,"import { useCallback, useRef, useSyncExternalStore } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"../lib/isomorphic-layout-effect\";\nimport { createConsent } from \"./core/controller\";\nimport type {\n BitsnapConsentApi,\n ConsentChoices,\n ConsentConfig,\n ConsentOptions,\n ConsentState,\n} from \"./core/types\";\n\ndeclare global {\n interface Window {\n BitsnapConsent?: BitsnapConsentApi;\n }\n}\n\nexport type BitsnapConsentProps = {\n /** Public project key: loads the project's banner config and logs choices. */\n projectKey?: string;\n theme?: ConsentOptions[\"theme\"];\n locale?: ConsentOptions[\"locale\"];\n position?: ConsentOptions[\"position\"];\n privacyPolicyUrl?: string;\n /** `project` keeps the consent cookie per project key. */\n scope?: ConsentOptions[\"scope\"];\n apiHost?: string;\n collectHost?: string;\n /** Local config, merged over the project config. */\n config?: Partial<ConsentConfig>;\n reloadOnWithdraw?: boolean;\n /** Google Consent Mode v2: `auto` (default) is on when `gtag` or `dataLayer` already exist. */\n gcm?: ConsentOptions[\"gcm\"];\n /** Categories offered when no remote config is loaded. */\n categories?: ConsentOptions[\"categories\"];\n /** Dashboard preview: shows the banner from the local options only; touches no cookie. */\n preview?: boolean;\n /** Replaces the banner title. */\n title?: string;\n /** Replaces the banner description. */\n description?: string;\n};\n\n/** Mounts Bitsnap Consent on the page. Renders nothing itself (the banner lives in a Shadow DOM). */\nexport function BitsnapConsent(props: BitsnapConsentProps): null {\n const {\n projectKey, theme, locale, position, privacyPolicyUrl, scope, apiHost, collectHost, reloadOnWithdraw,\n gcm, preview, title, description,\n } = props;\n const configKey = JSON.stringify(props.config ?? null);\n const configRef = useRef(props.config);\n configRef.current = props.config;\n const categoriesKey = JSON.stringify(props.categories ?? null);\n const categoriesRef = useRef(props.categories);\n categoriesRef.current = props.categories;\n\n // A layout effect: the page's passive effects (first events, tracker) must find the API already there.\n useIsomorphicLayoutEffect(() => {\n // A banner from the script tag or another instance already runs on this page.\n if (window.BitsnapConsent) return;\n const api = createConsent({\n projectKey,\n theme,\n locale,\n position,\n privacyPolicyUrl,\n scope,\n apiHost,\n collectHost,\n reloadOnWithdraw,\n gcm,\n preview,\n title,\n description,\n categories: categoriesRef.current,\n config: configRef.current,\n });\n window.BitsnapConsent = api;\n return () => {\n api.destroy();\n if (window.BitsnapConsent === api) delete window.BitsnapConsent;\n };\n }, [\n projectKey, theme, locale, position, privacyPolicyUrl, scope, apiHost, collectHost, reloadOnWithdraw,\n gcm, preview, title, description, categoriesKey, configKey,\n ]);\n\n return null;\n}\n\nlet lastKey = \"null\";\nlet lastState: ConsentState | null = null;\n\nfunction getSnapshot(): ConsentState | null {\n const current = typeof window === \"undefined\" ? null : (window.BitsnapConsent?.get() ?? null);\n const key = JSON.stringify(current);\n if (key !== lastKey) {\n lastKey = key;\n lastState = current;\n }\n return lastState;\n}\n\nfunction subscribe(onChange: () => void): () => void {\n window.addEventListener(\"bitsnap:consent\", onChange);\n window.addEventListener(\"bitsnap:consent:ready\", onChange);\n return () => {\n window.removeEventListener(\"bitsnap:consent\", onChange);\n window.removeEventListener(\"bitsnap:consent:ready\", onChange);\n };\n}\n\nexport type UseConsent = {\n /** The stored choice, or `null` while there is none (or on the server). */\n consent: ConsentState | null;\n /** `true` once the visitor has made a choice that is still valid. */\n decided: boolean;\n open: () => void;\n accept: (categories?: Partial<ConsentChoices>) => void;\n acceptAll: () => void;\n rejectAll: () => void;\n reset: () => void;\n};\n\n/** Current consent state of the page, kept up to date as the visitor chooses. */\nexport function useConsent(): UseConsent {\n const consent = useSyncExternalStore(subscribe, getSnapshot, () => null);\n const open = useCallback(() => window.BitsnapConsent?.open(), []);\n const accept = useCallback((categories?: Partial<ConsentChoices>) => window.BitsnapConsent?.accept(categories), []);\n const acceptAll = useCallback(() => window.BitsnapConsent?.acceptAll(), []);\n const rejectAll = useCallback(() => window.BitsnapConsent?.rejectAll(), []);\n const reset = useCallback(() => window.BitsnapConsent?.reset(), []);\n return { consent, decided: consent !== null, open, accept, acceptAll, rejectAll, reset };\n}\n","export type ConsentCategory = \"analytics\" | \"marketing\" | \"preferences\";\n\nexport type ConsentChoices = Record<ConsentCategory, boolean>;\n\nexport type ConsentTheme = \"auto\" | \"light\" | \"dark\";\n\nexport type ConsentPosition = \"bottom\" | \"bottom-left\" | \"bottom-right\";\n\nexport type ConsentLocale = \"pl\" | \"en\";\n\nexport const CONSENT_CATEGORIES: readonly ConsentCategory[] = [\n \"analytics\",\n \"marketing\",\n \"preferences\",\n];\n\nexport const CONSENT_TEXT_KEYS = [\n \"title\",\n \"description\",\n \"acceptAll\",\n \"rejectAll\",\n \"settings\",\n \"save\",\n \"close\",\n \"necessaryTitle\",\n \"necessaryDescription\",\n \"analyticsTitle\",\n \"analyticsDescription\",\n \"marketingTitle\",\n \"marketingDescription\",\n \"preferencesTitle\",\n \"preferencesDescription\",\n \"poweredBy\",\n] as const;\n\nexport type ConsentTexts = Record<(typeof CONSENT_TEXT_KEYS)[number], string>;\n\nexport type ConsentService = {\n name: string;\n category: ConsentCategory;\n /** Cookies to delete (best effort) when the category is withdrawn. A trailing `*` matches a prefix. */\n cookies?: string[];\n};\n\nexport type ConsentConfig = {\n /** Banner version. A stored choice with a lower `bv` makes the banner ask again. */\n version: number;\n theme: ConsentTheme;\n accentColor?: string;\n position: ConsentPosition;\n privacyPolicyUrl?: string;\n categories: ConsentChoices;\n services: ConsentService[];\n texts?: Partial<Record<ConsentLocale, Partial<ConsentTexts>>>;\n poweredBy: true;\n};\n\n/** What the consent cookie holds, and what `get()` returns. */\nexport type ConsentState = {\n id: string;\n /** Unix seconds. */\n ts: number;\n /** Version of the banner the choice was made on. */\n bv: number;\n c: ConsentChoices;\n};\n\nexport type ConsentAction =\n | \"accept_all\"\n | \"reject_all\"\n | \"custom\"\n | \"withdraw\";\n\n/** `detail` of `bitsnap:consent` and the argument of `on('change')`. */\nexport type ConsentChangeDetail = ConsentState & {\n /** `reset` when the visitor's stored choice was cleared (`get()` is `null` then). */\n action: ConsentAction | \"reset\";\n};\n\nexport type ConsentOptions = {\n /** Public project key. Enables the remote config and the consent log. */\n projectKey?: string;\n theme?: ConsentTheme;\n locale?: \"auto\" | ConsentLocale;\n position?: ConsentPosition;\n privacyPolicyUrl?: string;\n /** `project` keeps one cookie per project key (`bitsnap_consent_<key>`). */\n scope?: \"global\" | \"project\";\n apiHost?: string;\n collectHost?: string;\n /** Google Consent Mode v2. `auto` is on when `gtag` or `dataLayer` already exist. */\n gcm?: \"auto\" | \"on\" | \"off\";\n /** Local overrides, merged over the defaults and the remote config. */\n config?: Partial<ConsentConfig>;\n /** Categories used when no remote config is loaded. */\n categories?: Partial<ConsentChoices>;\n reloadOnWithdraw?: boolean;\n /**\n * Dashboard preview: shows the banner at once from the local options, never\n * reads or writes the cookie, loads no remote config, sends no consent log,\n * and the buttons only close the banner.\n */\n preview?: boolean;\n /** Replaces the banner title (current locale, non-empty values only). */\n title?: string;\n /** Replaces the banner description (current locale, non-empty values only). */\n description?: string;\n};\n\nexport type BitsnapConsentApi = {\n /**\n * Whose choice this banner holds: `project` keeps one cookie per project key\n * (`bitsnap_consent_<key>`), `global` the site-wide `bitsnap_consent`.\n */\n readonly scope: \"global\" | \"project\";\n /** The bare public project key the banner was started with, if any (`pk_` stripped). */\n readonly projectKey: string | null;\n get(): ConsentState | null;\n /**\n * `true` when the visitor refused `category`, also in a choice made on an older banner\n * version (which `get()` no longer returns) that they have not answered again yet.\n */\n denied(category: ConsentCategory): boolean;\n on(event: \"change\", fn: (detail: ConsentChangeDetail) => void): () => void;\n open(): void;\n close(): void;\n accept(categories?: Partial<ConsentChoices>): void;\n acceptAll(): void;\n rejectAll(): void;\n reset(): void;\n /** Resolves with the stored choice (or `null`) once config and stored choice are applied. */\n ready: Promise<ConsentState | null>;\n destroy(): void;\n};\n","import { CONSENT_CATEGORIES, type ConsentChoices } from \"./types\";\n\nconst SCRIPT_SELECTOR = 'script[type=\"text/plain\"][data-bitsnap-consent]:not([data-bitsnap-queued])';\nconst FRAME_SELECTOR = \"iframe[data-bitsnap-consent][data-src]\";\n\nconst allowed = (c: ConsentChoices, category: string | null): boolean =>\n (CONSENT_CATEGORIES as readonly string[]).includes(category ?? \"\") &&\n c[category as keyof ConsentChoices];\n\n/** Settles when the script has loaded or failed: a broken script must not block the ones after it. */\nfunction settled(script: HTMLScriptElement): Promise<void> {\n return new Promise((resolve) => {\n script.addEventListener(\"load\", () => resolve(), { once: true });\n script.addEventListener(\"error\", () => resolve(), { once: true });\n });\n}\n\n/** Swaps the placeholder for a live script; resolves once an external one has loaded. */\nfunction run(doc: Document, placeholder: HTMLScriptElement): Promise<void> | undefined {\n const script = doc.createElement(\"script\");\n for (const { name, value } of Array.from(placeholder.attributes)) {\n if (name === \"type\" || name === \"data-bitsnap-queued\") continue;\n if (name === \"data-type\") {\n // The embed writes the original `type=\"module\"` here, as `type` itself is `text/plain`.\n if (value === \"module\") script.setAttribute(\"type\", \"module\");\n } else if (name === \"data-src\") script.setAttribute(\"src\", value);\n else script.setAttribute(name, value);\n }\n if (placeholder.nonce) script.nonce = placeholder.nonce;\n const external = script.hasAttribute(\"src\");\n if (external) {\n // Dynamic scripts are async by default; keep the order of the page.\n if (!placeholder.hasAttribute(\"async\")) script.async = false;\n } else {\n script.text = placeholder.text;\n }\n const loaded = external ? settled(script) : undefined;\n placeholder.parentNode?.replaceChild(script, placeholder);\n return loaded;\n}\n\n/**\n * Turns the consented placeholders into live elements:\n * `<script type=\"text/plain\" data-bitsnap-consent=\"marketing\" [data-src] [data-type=\"module\"]>` becomes\n * a real script (attributes copied, `type` dropped or restored from `data-type`, `data-src` -> `src`),\n * and `<iframe data-src>` gets its `src`.\n * Placeholders run in document order: an inline script waits for the external scripts before it\n * (`<script src=lib.js>` then `<script>Lib.init()</script>`), external ones only follow each other.\n */\nexport function activateScripts(doc: Document, c: ConsentChoices): number {\n let activated = 0;\n try {\n /** Set once a placeholder had to wait: everything after it queues behind it. */\n let gate: Promise<void> | null = null;\n /** External scripts inserted since the last inline one. */\n let loading: Promise<void>[] = [];\n doc.querySelectorAll<HTMLScriptElement>(SCRIPT_SELECTOR).forEach((placeholder) => {\n if (!allowed(c, placeholder.getAttribute(\"data-bitsnap-consent\"))) return;\n activated++;\n const inline = !placeholder.hasAttribute(\"data-src\") && !placeholder.hasAttribute(\"src\");\n if (gate || (inline && loading.length > 0)) {\n // Marked so that another call (a later choice) does not queue it again.\n placeholder.setAttribute(\"data-bitsnap-queued\", \"\");\n gate = (gate ?? Promise.all(loading).then(() => {})).then(() => {\n try {\n return run(doc, placeholder);\n } catch {\n // never throw into the host page\n }\n });\n loading = [];\n return;\n }\n const loaded = run(doc, placeholder);\n if (loaded) loading.push(loaded);\n });\n doc.querySelectorAll<HTMLIFrameElement>(FRAME_SELECTOR).forEach((frame) => {\n if (!allowed(c, frame.getAttribute(\"data-bitsnap-consent\"))) return;\n const src = frame.getAttribute(\"data-src\");\n if (src && frame.getAttribute(\"src\") !== src) {\n frame.setAttribute(\"src\", src);\n frame.removeAttribute(\"data-src\");\n activated++;\n }\n });\n } catch {\n // never throw into the host page\n }\n return activated;\n}\n","import {\n CONSENT_CATEGORIES,\n type ConsentChoices,\n type ConsentConfig,\n type ConsentService,\n} from \"./types\";\n\nexport const DEFAULT_API_HOST = \"https://bitsnap.pl\";\nexport const DEFAULT_COLLECT_HOST = \"https://collect.bitsnap.pl\";\n\nexport function defaultConfig(): ConsentConfig {\n return {\n version: 1,\n theme: \"auto\",\n position: \"bottom\",\n categories: { analytics: true, marketing: true, preferences: false },\n services: [],\n poweredBy: true,\n };\n}\n\nconst THEMES = [\"auto\", \"light\", \"dark\"];\nconst POSITIONS = [\"bottom\", \"bottom-left\", \"bottom-right\"];\n\nconst HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;\n\nexport function safeColor(value: unknown): string | undefined {\n return typeof value === \"string\" && HEX_COLOR.test(value.trim())\n ? value.trim()\n : undefined;\n}\n\n/** Only http(s) and relative URLs may end up in the banner. */\nexport function safeUrl(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const url = value.trim();\n return /^(?:https?:\\/\\/|\\/(?!\\/)|#|\\?)/i.test(url) ? url : undefined;\n}\n\nfunction cleanServices(value: unknown): ConsentService[] {\n if (!Array.isArray(value)) return [];\n const services: ConsentService[] = [];\n for (const item of value) {\n if (\n item &&\n typeof item.name === \"string\" &&\n (CONSENT_CATEGORIES as readonly string[]).includes(item.category)\n ) {\n services.push({\n name: item.name,\n category: item.category,\n cookies: Array.isArray(item.cookies)\n ? item.cookies.filter((cookie: unknown) => typeof cookie === \"string\")\n : undefined,\n });\n }\n }\n return services;\n}\n\n/** Layers partial configs (later wins) over the defaults, dropping invalid values. */\nexport function mergeConfig(\n ...layers: (Partial<ConsentConfig> | null | undefined)[]\n): ConsentConfig {\n const result = defaultConfig();\n for (const layer of layers) {\n if (!layer || typeof layer !== \"object\") continue;\n if (typeof layer.version === \"number\" && layer.version >= 0) {\n result.version = Math.floor(layer.version);\n }\n if (THEMES.includes(layer.theme as string)) result.theme = layer.theme!;\n if (POSITIONS.includes(layer.position as string)) result.position = layer.position!;\n const accent = safeColor(layer.accentColor);\n if (accent) result.accentColor = accent;\n const privacy = safeUrl(layer.privacyPolicyUrl);\n if (privacy) result.privacyPolicyUrl = privacy;\n if (layer.categories && typeof layer.categories === \"object\") {\n for (const category of CONSENT_CATEGORIES) {\n if (typeof layer.categories[category] === \"boolean\") {\n result.categories[category] = layer.categories[category];\n }\n }\n }\n if (layer.services !== undefined) result.services = cleanServices(layer.services);\n if (layer.texts && typeof layer.texts === \"object\") result.texts = layer.texts;\n }\n return result;\n}\n\nexport function parseCategoryList(value: string | null | undefined): ConsentChoices {\n const wanted = (value ?? \"\").split(\",\").map((item) => item.trim());\n return {\n analytics: wanted.includes(\"analytics\"),\n marketing: wanted.includes(\"marketing\"),\n preferences: wanted.includes(\"preferences\"),\n };\n}\n\n/** What `GET /api/consent/config` answers: whether the banner is on, and its config. */\nexport type RemoteConsentConfig = {\n /** `false` when the project turned the banner off: nothing is shown, stored choices still apply. */\n enabled: boolean;\n config: Partial<ConsentConfig> | null;\n};\n\n/** Loads the project's banner config. Resolves to `null` on any failure or after the timeout. */\nexport async function fetchRemoteConfig(\n apiHost: string,\n key: string,\n timeoutMs = 3000,\n): Promise<RemoteConsentConfig | null> {\n if (typeof fetch !== \"function\") return null;\n const controller = typeof AbortController === \"function\" ? new AbortController() : undefined;\n const timer = setTimeout(() => controller?.abort(), timeoutMs);\n try {\n const response = await fetch(\n `${apiHost.replace(/\\/+$/, \"\")}/api/consent/config?key=${encodeURIComponent(key)}`,\n { signal: controller?.signal, credentials: \"omit\" },\n );\n if (!response.ok) return null;\n const body = await response.json();\n if (!body || typeof body !== \"object\") return null;\n const config = body.config && typeof body.config === \"object\" ? body.config : null;\n const enabled = body.enabled !== false;\n return config || !enabled ? { enabled, config } : null;\n } catch {\n return null;\n } finally {\n clearTimeout(timer);\n }\n}\n","/** Colors of both themes. Every text/background pair is checked against WCAG AA in the tests. */\nexport const PALETTE = {\n light: {\n bg: \"#ffffff\",\n fg: \"#14171c\",\n muted: \"#495260\",\n border: \"#c9ced6\",\n off: \"#6b7280\",\n thumb: \"#ffffff\",\n accent: \"#1d4ed8\",\n onAccent: \"#ffffff\",\n link: \"#1d4ed8\",\n },\n dark: {\n bg: \"#171a20\",\n fg: \"#f1f3f6\",\n muted: \"#b7beca\",\n border: \"#3a404b\",\n off: \"#8b93a1\",\n thumb: \"#171a20\",\n accent: \"#8ab4ff\",\n onAccent: \"#0b1220\",\n link: \"#9dc0ff\",\n },\n} as const;\n\nconst vars = (p: (typeof PALETTE)[\"light\"]) =>\n `--bg:${p.bg};--fg:${p.fg};--muted:${p.muted};--border:${p.border};--off:${p.off};--thumb:${p.thumb};--accent:${p.accent};--on-accent:${p.onAccent};--link:${p.link};--focus:${p.accent}`;\n\nexport const STYLES = `\n:host{all:initial}\n*{box-sizing:border-box}\n[hidden]{display:none!important}\n.root{${vars(PALETTE.light)};position:fixed;inset:0;pointer-events:none;z-index:2147483000;font:14px/1.5 system-ui,-apple-system,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,sans-serif;color:var(--fg);-webkit-text-size-adjust:100%}\n.root[data-theme=dark]{${vars(PALETTE.dark)};color-scheme:dark}\nbutton,input{font:inherit;color:inherit}\n.banner,.overlay{pointer-events:auto}\n@supports (color:color-mix(in srgb,red 50%,blue)){::selection{background:color-mix(in srgb,var(--accent) 24%,transparent)}}\n.banner{position:absolute;background:var(--bg);color:var(--fg);max-height:92vh;overflow:auto;outline:none}\n.banner.enter{animation:in .22s ease-out}\n.banner.closing{animation:out .2s ease-in forwards;pointer-events:none}\n.bottom{left:0;right:0;bottom:0;border-top:1px solid var(--border);box-shadow:0 -8px 28px rgba(15,23,42,.08);padding:20px 24px calc(20px + env(safe-area-inset-bottom,0px))}\n.root[data-theme=dark] .bottom{box-shadow:0 -8px 28px rgba(0,0,0,.4)}\n.bottom .inner{display:grid;grid-template-columns:minmax(min(100%,42ch),1fr) auto;grid-template-areas:\"text actions\" \"foot actions\";gap:4px 40px;align-items:center;max-width:1120px;margin:0 auto}\n.bottom .text{max-width:68ch}\n.bottom .btn,.bottom .link-btn{flex:0 0 auto}\n.card{bottom:16px;width:min(400px,calc(100vw - 32px));border:1px solid var(--border);border-radius:14px;padding:20px;box-shadow:0 12px 32px rgba(15,23,42,.14)}\n.root[data-theme=dark] .card{box-shadow:0 12px 32px rgba(0,0,0,.45)}\n.bottom-left{left:16px}\n.bottom-right{right:16px}\n.card .inner{display:flex;flex-direction:column;gap:16px}\n.text{grid-area:text}\n.actions{grid-area:actions;display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:flex-end}\n.card .actions{justify-content:stretch}\n.foot{grid-area:foot}\n.title{overflow-wrap:anywhere;font-size:17px;font-weight:650;line-height:1.35;letter-spacing:-.01em;text-wrap:balance;margin:0 0 6px}\n.desc{overflow-wrap:anywhere;margin:0;color:var(--muted);line-height:1.55;text-wrap:pretty}\n.desc a{color:var(--link);text-decoration:underline;text-underline-offset:3px}\n.btn,.link-btn{min-height:38px;padding:0 16px;border-radius:8px;font-size:13.5px;font-weight:600;cursor:pointer;white-space:nowrap;transition:background-color .12s ease-out,filter .12s ease-out,border-color .12s ease-out}\n.btn{border:2px solid var(--accent);background:var(--accent);color:var(--on-accent);flex:1 1 150px}\n.btn:hover{filter:brightness(.93)}\n.btn:active{filter:brightness(.87)}\n.link-btn{border:1px solid var(--off);background:transparent;color:var(--fg);text-decoration:none}\n.link-btn:hover{border-color:var(--fg)}\n@supports (color:color-mix(in srgb,red 50%,blue)){.link-btn:hover{background:color-mix(in srgb,var(--fg) 9%,transparent)}.link-btn:active{background:color-mix(in srgb,var(--fg) 14%,transparent)}}\n.card .link-btn{flex:1 1 100%}\nbutton:focus-visible,a:focus-visible,.dialog:focus-visible{outline:2px solid var(--focus);outline-offset:2px}\n.foot{font-size:12px;color:var(--muted);padding-top:8px}\n.foot a{color:var(--muted);text-decoration:none;display:inline-flex;align-items:center;gap:5px;vertical-align:middle}\n.foot a:hover{text-decoration:underline}\n.foot b{color:var(--fg);font-weight:700;letter-spacing:-.01em}\n.foot svg{display:block}\n.overlay{position:absolute;inset:0;background:rgba(10,12,16,.58);display:flex;align-items:center;justify-content:center;padding:16px}\n.overlay.enter{animation:fade .2s ease-out}\n.overlay.closing{animation:fadeout .2s ease-in forwards;pointer-events:none}\n.dialog{background:var(--bg);color:var(--fg);width:100%;max-width:560px;max-height:min(92vh,760px);display:flex;flex-direction:column;border-radius:16px;box-shadow:0 16px 48px rgba(0,0,0,.35);outline:none}\n.dialog.enter{animation:in .22s ease-out}\n.d-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:16px 8px 8px 24px}\n.d-head .title{margin:0;font-size:18px}\n.x{width:44px;height:44px;border:0;border-radius:10px;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex:none}\n.x:hover{background:var(--border)}\n.d-body{overflow:auto;padding:0 24px 8px;flex:1}\n.cat{border-top:1px solid var(--border);padding:10px 0 14px}\n.row{display:flex;align-items:center;justify-content:space-between;gap:12px}\n.cat-title{font-weight:600;font-size:15px}\n.always{font-size:12px;color:var(--muted);margin-right:4px}\n.cat p{margin:0;color:var(--muted)}\n.cat .svc{margin-top:6px;font-size:13px}\n.cat .svc b{font-weight:600;color:var(--fg)}\n.switch{position:relative;width:52px;height:44px;display:flex;align-items:center;justify-content:flex-end;flex:none}\n.switch input{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}\n.switch input:disabled{cursor:default}\n.track{width:44px;height:24px;border-radius:12px;background:var(--off);position:relative;transition:background .15s}\n.track::after{content:\"\";position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:var(--thumb);transition:transform .15s}\ninput:checked+.track{background:var(--accent)}\ninput:checked+.track::after{transform:translateX(20px);background:var(--on-accent)}\ninput:disabled+.track{opacity:.6}\ninput:focus-visible+.track{outline:2px solid var(--focus);outline-offset:2px}\n.d-foot{display:flex;flex-direction:column;gap:8px;padding:12px 24px 16px;border-top:1px solid var(--border)}\n.d-foot .actions{justify-content:stretch}\n@keyframes in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}\n@keyframes fade{from{opacity:0}to{opacity:1}}\n@keyframes out{from{opacity:1;transform:none}to{opacity:0;transform:translateY(12px)}}\n@keyframes fadeout{from{opacity:1}to{opacity:0}}\n@media(max-width:960px){\n.bottom .inner{grid-template-columns:minmax(0,1fr);grid-template-areas:\"text\" \"actions\" \"foot\";gap:14px}\n.bottom .actions{justify-content:flex-start}\n}\n@media(max-width:640px){\n.bottom{padding:16px 16px calc(16px + env(safe-area-inset-bottom,0px))}\n.bottom .inner{gap:12px}\n.bottom .actions{justify-content:stretch}\n.bottom .btn{flex:1 1 130px}\n.bottom .link-btn{flex:1 1 100%}\n.card{left:8px;right:8px;bottom:8px;width:auto}\n.overlay{align-items:flex-end;padding:0}\n.dialog{max-width:none;border-radius:16px 16px 0 0;max-height:92vh}\n.d-head{padding-left:16px}\n.d-body{padding:0 16px 8px}\n.d-foot{padding:12px 16px calc(16px + env(safe-area-inset-bottom,0px))}\n.btn{flex-basis:130px}\n.btn,.link-btn{padding:0 12px;min-height:40px}\n}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}\n`.replace(/\\n/g, \"\");\n","import { contrastRatio, onColor } from \"./contrast\";\nimport { UI_LABELS } from \"./texts\";\nimport { PALETTE, STYLES } from \"./styles\";\nimport {\n CONSENT_CATEGORIES,\n type ConsentCategory,\n type ConsentChoices,\n type ConsentConfig,\n type ConsentLocale,\n type ConsentTexts,\n} from \"./types\";\n\nexport type UIOptions = {\n doc: Document;\n config: ConsentConfig;\n texts: ConsentTexts;\n locale: ConsentLocale;\n /** Current choice, used to preset the toggles. */\n getChoices(): ConsentChoices | null;\n /** Whether a valid choice exists, i.e. closing the settings must not bring back the banner. */\n hasChoice(): boolean;\n onAcceptAll(): void;\n onRejectAll(): void;\n onSave(choices: ConsentChoices): void;\n};\n\nexport type ConsentUI = {\n /** `focus` moves keyboard focus into the banner; leave it off when the banner opens on page load. */\n showBanner(focus?: boolean): void;\n showSettings(): void;\n hide(): void;\n destroy(): void;\n};\n\nconst WORDMARK =\n '<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" aria-hidden=\"true\"><rect width=\"16\" height=\"16\" rx=\"4\" fill=\"currentColor\" style=\"color:var(--fg)\"/><circle cx=\"8\" cy=\"8\" r=\"3\" fill=\"var(--bg)\"/></svg>';\n\nconst CLOSE_ICON =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"M3 3l10 10M13 3L3 13\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" fill=\"none\"/></svg>';\n\nconst FOCUSABLE = \"button:not([disabled]),input:not([disabled]),a[href]\";\n\n/**\n * How long the exit animation runs. The banner and the settings layer fade\n * out before they disappear, so the page does not blink.\n */\nexport const EXIT_ANIMATION_MS = 200;\n\n/** A running exit animation: the node, its listener and its fallback timer. */\ntype Exit = {\n node: HTMLElement;\n listener: (event: Event) => void;\n timer: number | undefined;\n};\n\nexport function createUI(options: UIOptions): ConsentUI {\n const { doc, config, texts, locale } = options;\n const labels = UI_LABELS[locale];\n let host: HTMLElement | null = null;\n let shadow: ShadowRoot | null = null;\n let root: HTMLElement;\n let banner: HTMLElement;\n let overlay: HTMLElement;\n let dialog: HTMLElement;\n let settingsButton: HTMLElement;\n let layer: \"none\" | \"banner\" | \"settings\" = \"none\";\n let opener: HTMLElement | null = null;\n let media: MediaQueryList | null = null;\n const toggles = new Map<ConsentCategory, HTMLInputElement>();\n /** The exit animations running right now; at most one per node. */\n const exits: Exit[] = [];\n\n function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n props: Record<string, string> = {},\n ...children: (Node | string)[]\n ): HTMLElementTagNameMap[K] {\n const node = doc.createElement(tag);\n for (const key of Object.keys(props)) {\n if (key === \"class\") node.className = props[key];\n else node.setAttribute(key, props[key]);\n }\n for (const child of children) node.append(child);\n return node;\n }\n\n function button(cls: string, text: string, onClick: () => void) {\n const node = el(\"button\", { type: \"button\", class: cls }, text);\n node.addEventListener(\"click\", onClick);\n return node;\n }\n\n function powered(): HTMLElement {\n const link = el(\"a\", {\n href: `https://bitsnap.pl/?utm_source=consent_banner&utm_medium=referral&utm_content=${encodeURIComponent(doc.location?.hostname ?? \"\")}`,\n target: \"_blank\",\n rel: \"noopener\",\n });\n link.append(texts.poweredBy + \" \");\n link.insertAdjacentHTML(\"beforeend\", WORDMARK);\n link.append(el(\"b\", {}, \"bitsnap\"));\n return el(\"div\", { class: \"foot\" }, link);\n }\n\n function description(): HTMLElement {\n const node = el(\"p\", { class: \"desc\", id: \"d\" }, texts.description);\n if (config.privacyPolicyUrl) {\n node.append(\n \" \",\n el(\n \"a\",\n { href: config.privacyPolicyUrl, target: \"_blank\", rel: \"noopener\" },\n labels.privacy,\n ),\n );\n }\n return node;\n }\n\n function buildBanner() {\n settingsButton = button(\"link-btn\", texts.settings, () => showSettings());\n banner = el(\n \"div\",\n {\n class: `banner ${config.position === \"bottom\" ? \"bottom\" : `card ${config.position}`}`,\n // Not modal: the page stays usable, so it is a live region, not a dialog.\n role: \"region\",\n \"aria-live\": \"polite\",\n \"aria-labelledby\": \"t\",\n \"aria-describedby\": \"d\",\n tabindex: \"-1\",\n },\n el(\n \"div\",\n { class: \"inner\" },\n el(\"div\", { class: \"text\" }, el(\"div\", { class: \"title\", id: \"t\" }, texts.title), description()),\n el(\n \"div\",\n { class: \"actions\" },\n button(\"btn\", texts.acceptAll, options.onAcceptAll),\n button(\"btn\", texts.rejectAll, options.onRejectAll),\n settingsButton,\n ),\n powered(),\n ),\n );\n }\n\n function category(id: ConsentCategory | \"necessary\"): HTMLElement {\n const services = config.services.filter((service) => service.category === id);\n const input = el(\"input\", { type: \"checkbox\", role: \"switch\", \"aria-labelledby\": `l-${id}`, \"aria-describedby\": `p-${id}` });\n if (id === \"necessary\") {\n input.checked = true;\n input.disabled = true;\n } else {\n toggles.set(id, input);\n }\n const node = el(\n \"div\",\n { class: \"cat\" },\n el(\n \"div\",\n { class: \"row\" },\n el(\"span\", { class: \"cat-title\", id: `l-${id}` }, texts[`${id}Title` as keyof ConsentTexts]),\n el(\n \"span\",\n { style: \"display:flex;align-items:center\" },\n ...(id === \"necessary\" ? [el(\"span\", { class: \"always\" }, labels.always)] : []),\n el(\"label\", { class: \"switch\" }, input, el(\"span\", { class: \"track\" })),\n ),\n ),\n el(\"p\", { id: `p-${id}` }, texts[`${id}Description` as keyof ConsentTexts]),\n );\n if (services.length > 0) {\n node.append(\n el(\"p\", { class: \"svc\" }, el(\"b\", {}, `${labels.services}: `), services.map((s) => s.name).join(\", \")),\n );\n }\n return node;\n }\n\n function buildSettings() {\n const close = el(\"button\", { type: \"button\", class: \"x\", \"aria-label\": texts.close });\n close.insertAdjacentHTML(\"beforeend\", CLOSE_ICON);\n close.addEventListener(\"click\", () => closeSettings());\n const body = el(\"div\", { class: \"d-body\" }, category(\"necessary\"));\n for (const id of CONSENT_CATEGORIES) {\n if (config.categories[id]) body.append(category(id));\n }\n dialog = el(\n \"div\",\n { class: \"dialog\", role: \"dialog\", \"aria-modal\": \"true\", \"aria-labelledby\": \"st\", tabindex: \"-1\" },\n el(\"div\", { class: \"d-head\" }, el(\"div\", { class: \"title\", id: \"st\" }, texts.settings), close),\n body,\n el(\n \"div\",\n { class: \"d-foot\" },\n el(\n \"div\",\n { class: \"actions\" },\n button(\"btn\", texts.save, () => options.onSave(readToggles())),\n button(\"btn\", texts.acceptAll, options.onAcceptAll),\n button(\"btn\", texts.rejectAll, options.onRejectAll),\n ),\n powered(),\n ),\n );\n overlay = el(\"div\", { class: \"overlay\" }, dialog);\n overlay.addEventListener(\"mousedown\", (event) => {\n if (event.target === overlay) closeSettings();\n });\n }\n\n function readToggles(): ConsentChoices {\n const choices = { analytics: false, marketing: false, preferences: false };\n toggles.forEach((input, key) => (choices[key] = input.checked));\n return choices;\n }\n\n function applyTheme() {\n const dark = config.theme === \"dark\" || (config.theme === \"auto\" && !!media?.matches);\n root.setAttribute(\"data-theme\", dark ? \"dark\" : \"light\");\n applyAccent(dark);\n }\n\n /** A custom accent that is too faint on the dark background gives way to the dark theme's own. */\n function applyAccent(dark: boolean) {\n const accent = config.accentColor;\n if (!accent) return;\n if (dark && contrastRatio(accent, PALETTE.dark.bg) < 3) {\n for (const name of [\"--accent\", \"--focus\", \"--on-accent\"]) root.style.removeProperty(name);\n return;\n }\n root.style.setProperty(\"--accent\", accent);\n root.style.setProperty(\"--focus\", accent);\n root.style.setProperty(\"--on-accent\", onColor(accent));\n }\n\n function ensure() {\n if (host) return;\n host = el(\"div\", { id: \"bitsnap-consent\", \"data-bitsnap-consent-host\": \"\" });\n shadow = host.attachShadow({ mode: \"open\" });\n shadow.append(el(\"style\", {}, STYLES));\n root = el(\"div\", { class: \"root\", lang: locale });\n buildBanner();\n buildSettings();\n banner.hidden = true;\n overlay.hidden = true;\n root.append(banner, overlay);\n shadow.append(root);\n if (typeof doc.defaultView?.matchMedia === \"function\") {\n media = doc.defaultView.matchMedia(\"(prefers-color-scheme: dark)\");\n media.addEventListener?.(\"change\", applyTheme);\n }\n applyTheme();\n doc.body.append(host);\n }\n\n function focusables(): HTMLElement[] {\n return Array.from(dialog.querySelectorAll<HTMLElement>(FOCUSABLE));\n }\n\n /** Whether the visitor asked for less motion: the layers then come and go at once. */\n function reducedMotion(): boolean {\n try {\n return doc.defaultView?.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches === true;\n } catch {\n return false;\n }\n }\n\n /** Stops a running exit animation; the node stays where it is. */\n function stopExit(node: HTMLElement) {\n const index = exits.findIndex((exit) => exit.node === node);\n if (index === -1) return;\n const [exit] = exits.splice(index, 1);\n exit.node.removeEventListener(\"animationend\", exit.listener);\n if (exit.timer !== undefined) doc.defaultView?.clearTimeout(exit.timer);\n exit.node.classList.remove(\"closing\");\n }\n\n /** Hides the node at once, without an animation. */\n function hideNow(node: HTMLElement) {\n stopExit(node);\n node.hidden = true;\n }\n\n /**\n * Fades the node out and hides it when the animation ends. The timer is the\n * fallback for a browser that sends no event (a hidden tab, a reduced\n * motion setting): the layer always disappears.\n */\n function exitTo(node: HTMLElement) {\n stopExit(node);\n if (node.hidden) return;\n if (reducedMotion()) {\n node.hidden = true;\n return;\n }\n node.classList.add(\"closing\");\n const listener = (event: Event) => {\n // Another animation of a child ends first; only our own counts.\n if (event.target !== node) return;\n finish();\n };\n const timer = doc.defaultView?.setTimeout(finish, EXIT_ANIMATION_MS + 120);\n function finish() {\n stopExit(node);\n node.hidden = true;\n }\n node.addEventListener(\"animationend\", listener);\n exits.push({ node, listener, timer });\n }\n\n /** Shows the node and plays the entrance animation again. */\n function enter(node: HTMLElement) {\n stopExit(node);\n node.hidden = false;\n // The class has to leave and come back, or the animation does not restart.\n node.classList.remove(\"enter\");\n void node.offsetWidth;\n node.classList.add(\"enter\");\n }\n\n function onKeydown(event: KeyboardEvent) {\n if (layer !== \"settings\") return;\n if (event.key === \"Escape\") {\n event.preventDefault();\n closeSettings();\n return;\n }\n if (event.key !== \"Tab\") return;\n const items = focusables();\n if (items.length === 0) return;\n const active = shadow?.activeElement as HTMLElement | null;\n const first = items[0];\n const last = items[items.length - 1];\n const outside = !active || !dialog.contains(active);\n if (event.shiftKey && (outside || active === first || active === dialog)) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (outside || active === last)) {\n event.preventDefault();\n first.focus();\n }\n }\n\n function showBanner(focus = false) {\n try {\n ensure();\n layer = \"banner\";\n doc.removeEventListener(\"keydown\", onKeydown, true);\n hideNow(overlay);\n enter(banner);\n if (focus) banner.focus({ preventScroll: true });\n } catch {\n // never throw into the host page\n }\n }\n\n function showSettings() {\n try {\n ensure();\n if (layer !== \"settings\") {\n const active = doc.activeElement as HTMLElement | null;\n opener = active && active !== host && active !== doc.body ? active : null;\n }\n const current = options.getChoices();\n toggles.forEach((input, key) => (input.checked = !!current?.[key]));\n layer = \"settings\";\n hideNow(banner);\n enter(overlay);\n enter(dialog);\n doc.addEventListener(\"keydown\", onKeydown, true);\n dialog.focus({ preventScroll: true });\n } catch {\n // never throw into the host page\n }\n }\n\n function closeSettings() {\n if (layer !== \"settings\") return;\n doc.removeEventListener(\"keydown\", onKeydown, true);\n exitTo(overlay);\n if (options.hasChoice()) {\n layer = \"none\";\n opener?.focus?.({ preventScroll: true });\n } else {\n layer = \"banner\";\n enter(banner);\n settingsButton.focus({ preventScroll: true });\n }\n }\n\n function hide() {\n if (!host) return;\n doc.removeEventListener(\"keydown\", onKeydown, true);\n const wasSettings = layer === \"settings\";\n layer = \"none\";\n exitTo(banner);\n exitTo(overlay);\n if (wasSettings) opener?.focus?.({ preventScroll: true });\n }\n\n function destroy() {\n doc.removeEventListener(\"keydown\", onKeydown, true);\n media?.removeEventListener?.(\"change\", applyTheme);\n while (exits.length > 0) stopExit(exits[0]!.node);\n host?.remove();\n host = null;\n shadow = null;\n layer = \"none\";\n }\n\n return { showBanner, showSettings, hide, destroy };\n}\n"]}
|
package/dist/course.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { k as AnalyticsOption } from './instrument-eXaC8hc2.mjs';
|
|
3
|
-
import { G as GetThemeOutput } from './types-
|
|
3
|
+
import { G as GetThemeOutput } from './types-B-sjyZQs.mjs';
|
|
4
|
+
import { L as LocaleTables } from './texts-9kfgClsA.mjs';
|
|
4
5
|
import { Client } from '@connectrpc/connect';
|
|
5
6
|
import { GenService, GenMessage } from '@bufbuild/protobuf/codegenv2';
|
|
6
7
|
import { Timestamp } from '@bufbuild/protobuf/wkt';
|
|
@@ -9,19 +10,6 @@ import 'react';
|
|
|
9
10
|
import './texts-LGhuQfA9.mjs';
|
|
10
11
|
import './appearance-core-Dt2grZdl.mjs';
|
|
11
12
|
|
|
12
|
-
/**
|
|
13
|
-
* Shared text machinery: typed string tables per locale, the locale fallback
|
|
14
|
-
* and a merge with caller overrides. Each module (checkout, later returns)
|
|
15
|
-
* supplies its own key type and default tables.
|
|
16
|
-
*/
|
|
17
|
-
/** Strings of one locale, keyed by text key. */
|
|
18
|
-
type TextTable<Key extends string> = Record<Key, string>;
|
|
19
|
-
/** Default tables for the supported locales. */
|
|
20
|
-
type LocaleTables<Key extends string> = {
|
|
21
|
-
en: TextTable<Key>;
|
|
22
|
-
pl: TextTable<Key>;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
13
|
/**
|
|
26
14
|
* Every string of the course view. Pass a partial object through the `texts`
|
|
27
15
|
* prop of `PanelCourse` to override any of them, whatever the locale.
|
package/dist/course.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { k as AnalyticsOption } from './instrument-eXaC8hc2.js';
|
|
3
|
-
import { G as GetThemeOutput } from './types-
|
|
3
|
+
import { G as GetThemeOutput } from './types-iRe6ix2y.js';
|
|
4
|
+
import { L as LocaleTables } from './texts-9kfgClsA.js';
|
|
4
5
|
import { Client } from '@connectrpc/connect';
|
|
5
6
|
import { GenService, GenMessage } from '@bufbuild/protobuf/codegenv2';
|
|
6
7
|
import { Timestamp } from '@bufbuild/protobuf/wkt';
|
|
@@ -9,19 +10,6 @@ import 'react';
|
|
|
9
10
|
import './texts-m6XQGZ6w.js';
|
|
10
11
|
import './appearance-core-Dt2grZdl.js';
|
|
11
12
|
|
|
12
|
-
/**
|
|
13
|
-
* Shared text machinery: typed string tables per locale, the locale fallback
|
|
14
|
-
* and a merge with caller overrides. Each module (checkout, later returns)
|
|
15
|
-
* supplies its own key type and default tables.
|
|
16
|
-
*/
|
|
17
|
-
/** Strings of one locale, keyed by text key. */
|
|
18
|
-
type TextTable<Key extends string> = Record<Key, string>;
|
|
19
|
-
/** Default tables for the supported locales. */
|
|
20
|
-
type LocaleTables<Key extends string> = {
|
|
21
|
-
en: TextTable<Key>;
|
|
22
|
-
pl: TextTable<Key>;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
13
|
/**
|
|
26
14
|
* Every string of the course view. Pass a partial object through the `texts`
|
|
27
15
|
* prop of `PanelCourse` to override any of them, whatever the locale.
|