@zappie/foundations 0.1.9 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/account/index.js.map +0 -1
  3. package/dist/branch-map/colors.js.map +0 -1
  4. package/dist/branch-map/delivery-map.js.map +0 -1
  5. package/dist/branch-map/delivery-runtime.js.map +0 -1
  6. package/dist/branch-map/fit.js.map +0 -1
  7. package/dist/branch-map/geometry.js.map +0 -1
  8. package/dist/branch-map/index.js.map +0 -1
  9. package/dist/branch-map/loader.js.map +0 -1
  10. package/dist/branch-map/markers.js.map +0 -1
  11. package/dist/branch-map/runtime.js.map +0 -1
  12. package/dist/branch-map/use-map-runtime.js.map +0 -1
  13. package/dist/cart/index.js.map +0 -1
  14. package/dist/{core-DdojsUzR.d.ts → core-Cd-_zlQx.d.ts} +2 -1
  15. package/dist/deferred/index.js.map +0 -1
  16. package/dist/{engine-Bfbv9M0D.d.ts → engine-D2iRshfW.d.ts} +6 -4
  17. package/dist/fulfilment/address-picker.js.map +0 -1
  18. package/dist/fulfilment/branch-picker.js.map +0 -1
  19. package/dist/fulfilment/schedule.js.map +0 -1
  20. package/dist/fulfilment/switch.js.map +0 -1
  21. package/dist/{history-Bqu12Vkz.d.ts → history-BlytQinP.d.ts} +2 -1
  22. package/dist/index.d.ts +38 -36
  23. package/dist/index.js.map +0 -1
  24. package/dist/internal/combobox.js.map +0 -1
  25. package/dist/internal/data-attrs.js.map +0 -1
  26. package/dist/internal/ids.js.map +0 -1
  27. package/dist/internal/slot.js.map +1 -2
  28. package/dist/internal/use-controllable-state.js.map +0 -1
  29. package/dist/item-form/index.js.map +0 -1
  30. package/dist/item-form/option-group.js.map +0 -1
  31. package/dist/item-form/options.js.map +0 -1
  32. package/dist/item-form/quantity.js.map +0 -1
  33. package/dist/item-form/root.js.map +0 -1
  34. package/dist/media/image.js.map +0 -1
  35. package/dist/money.js.map +0 -1
  36. package/dist/next.d.ts +1 -1
  37. package/dist/next.js.map +0 -1
  38. package/dist/otp/input.js.map +0 -1
  39. package/dist/overlay/engine.d.ts +1 -1
  40. package/dist/overlay/engine.js.map +0 -1
  41. package/dist/overlay/history.js.map +0 -1
  42. package/dist/overlay/react.js.map +0 -1
  43. package/dist/payment/methods.js.map +0 -1
  44. package/dist/rich-text/banners.js.map +0 -1
  45. package/dist/rich-text/browser.d.ts +1 -1
  46. package/dist/rich-text/browser.js.map +0 -1
  47. package/dist/rich-text/core.js.map +0 -1
  48. package/dist/rich-text/index.d.ts +1 -1
  49. package/dist/rich-text/index.js.map +0 -1
  50. package/dist/search/engine.js.map +0 -1
  51. package/dist/search/index.js.map +0 -1
  52. package/dist/status/hours.js.map +0 -1
  53. package/dist/status/open-status.js.map +0 -1
  54. package/dist/version.js +1 -1
  55. package/dist/version.js.map +1 -2
  56. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @zappie/foundations
2
2
 
3
+ ## 0.1.12
4
+
5
+ ### Patch Changes
6
+
7
+ - 648b211: Source maps name each file and line without copying the source text, which keeps the archive within its size cap.
8
+
9
+ ## 0.1.11
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [8ea70dc]
14
+ - Updated dependencies [ea37bcf]
15
+ - @zappie/sdk@0.2.0
16
+
17
+ ## 0.1.10
18
+
19
+ ### Patch Changes
20
+
21
+ - b31a689: Declarations built with tsup 9.
22
+ - Updated dependencies [1cbfebc]
23
+ - Updated dependencies [b31a689]
24
+ - @zappie/sdk@0.1.24
25
+
3
26
  ## 0.1.9
4
27
 
5
28
  ### Patch Changes
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/account/index.tsx"],
4
- "sourcesContent": ["import { forwardRef } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\n/** Account stubs that do not read credentials or host authentication. */\nexport type Customer = { id: string; givenName?: string; phoneMasked: string };\n/**\n * Sign-in options reserved by the account contract.\n * The current Account sign-in stub rejects with AccountNotConfiguredError.\n */\nexport type SignInOptions = {\n returnTo?: string;\n prompt?: \"none\" | \"login\";\n display?: \"popup\" | \"redirect\" | \"auto\";\n scopes?: (\n | \"openid\"\n | \"phone\"\n | \"profile\"\n | \"addresses\"\n | \"orders:read\"\n | \"offline_access\"\n | \"cart\"\n )[];\n};\n/**\n * An error from Account sign-in and sign-out when authentication is not configured.\n */\nexport class AccountNotConfiguredError extends Error {\n readonly code = \"not_configured\";\n constructor() {\n super(\"Account authentication requires SDK auth (Open Storefronts PRD 13)\");\n this.name = \"AccountNotConfiguredError\";\n }\n}\nasync function signIn(_options?: SignInOptions): Promise<void> {\n throw new AccountNotConfiguredError();\n}\nasync function signOut(_options?: { everywhere?: boolean }): Promise<void> {\n throw new AccountNotConfiguredError();\n}\nconst SignIn = forwardRef<\n HTMLElement,\n PartProps<{ signIn: typeof signIn; status: \"not_configured\" }>\n>(function SignIn(props, ref) {\n return part(\n \"div\",\n props,\n { signIn, status: \"not_configured\" },\n { \"data-state\": \"invalid\" },\n ref,\n );\n});\nconst SignOut = forwardRef<\n HTMLElement,\n PartProps<{ signOut: typeof signOut; status: \"not_configured\" }>\n>(function SignOut(props, ref) {\n return part(\n \"div\",\n props,\n { signOut, status: \"not_configured\" },\n { \"data-state\": \"invalid\" },\n ref,\n );\n});\nconst CustomerPart = forwardRef<\n HTMLElement,\n PartProps<{ customer: Customer | null; status: \"not_configured\" }>\n>(function Customer(props, ref) {\n return part(\n \"div\",\n props,\n { customer: null, status: \"not_configured\" },\n { \"data-state\": \"invalid\" },\n ref,\n );\n});\n/**\n * Account UI stubs that expose a not_configured status and a null customer.\n * SignIn and SignOut reject with AccountNotConfiguredError. They do not authenticate.\n */\nexport const Account = { SignIn, SignOut, Customer: CustomerPart };\n"],
5
4
  "mappings": ";AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAA4B;AAwB9B,IAAM,4BAAN,cAAwC,MAAM;AAAA,EAEnD,cAAc;AACZ,UAAM,oEAAoE;AAF5E,SAAS,OAAO;AAGd,SAAK,OAAO;AAAA,EACd;AACF;AACA,eAAe,OAAO,UAAyC;AAC7D,QAAM,IAAI,0BAA0B;AACtC;AACA,eAAe,QAAQ,UAAoD;AACzE,QAAM,IAAI,0BAA0B;AACtC;AACA,IAAM,SAAS,WAGb,SAASA,QAAO,OAAO,KAAK;AAC5B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,QAAQ,QAAQ,iBAAiB;AAAA,IACnC,EAAE,cAAc,UAAU;AAAA,IAC1B;AAAA,EACF;AACF,CAAC;AACD,IAAM,UAAU,WAGd,SAASC,SAAQ,OAAO,KAAK;AAC7B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,SAAS,QAAQ,iBAAiB;AAAA,IACpC,EAAE,cAAc,UAAU;AAAA,IAC1B;AAAA,EACF;AACF,CAAC;AACD,IAAM,eAAe,WAGnB,SAAS,SAAS,OAAO,KAAK;AAC9B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,UAAU,MAAM,QAAQ,iBAAiB;AAAA,IAC3C,EAAE,cAAc,UAAU;AAAA,IAC1B;AAAA,EACF;AACF,CAAC;AAKM,IAAM,UAAU,EAAE,QAAQ,SAAS,UAAU,aAAa;",
6
5
  "names": ["SignIn", "SignOut"]
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/branch-map/colors.ts"],
4
- "sourcesContent": ["/** Colours resolved from CSS, for map parts that cannot read CSS variables. */\nexport type MapColors = {\n accent: string;\n accentForeground: string;\n /** The delivery map's line. */\n route?: string | undefined;\n};\n\nexport const DEFAULT_COLORS: MapColors = {\n accent: \"#111111\",\n accentForeground: \"#ffffff\",\n};\n\n/** Inline style for the hidden element whose computed colours the map reads. */\nexport const COLOR_PROBE_STYLE = {\n color: \"var(--map-accent, currentColor)\",\n backgroundColor: \"var(--map-accent-foreground, #ffffff)\",\n borderColor: \"var(--delivery-map-route, #163300)\",\n} as const;\n\n// Cluster icons and lines are drawn by Maps, which cannot read CSS variables.\nexport function readColors(probe: HTMLElement | null): MapColors {\n if (!probe || typeof getComputedStyle !== \"function\") {\n return DEFAULT_COLORS;\n }\n const style = getComputedStyle(probe);\n return {\n accent: style.color || DEFAULT_COLORS.accent,\n accentForeground: style.backgroundColor || DEFAULT_COLORS.accentForeground,\n route: style.borderTopColor || undefined,\n };\n}\n"],
5
4
  "mappings": ";AAQO,IAAM,iBAA4B;AAAA,EACvC,QAAQ;AAAA,EACR,kBAAkB;AACpB;AAGO,IAAM,oBAAoB;AAAA,EAC/B,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,aAAa;AACf;AAGO,SAAS,WAAW,OAAsC;AAC/D,MAAI,CAAC,SAAS,OAAO,qBAAqB,YAAY;AACpD,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,iBAAiB,KAAK;AACpC,SAAO;AAAA,IACL,QAAQ,MAAM,SAAS,eAAe;AAAA,IACtC,kBAAkB,MAAM,mBAAmB,eAAe;AAAA,IAC1D,OAAO,MAAM,kBAAkB;AAAA,EACjC;AACF;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/branch-map/delivery-map.tsx"],
4
- "sourcesContent": ["\"use client\";\nimport type { Branch } from \"@zappie/sdk\";\nimport { useEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { COLOR_PROBE_STYLE, readColors } from \"./colors\";\nimport type { createDeliveryMap } from \"./delivery-runtime\";\nimport { validCoordinates, type Coordinates } from \"./geometry\";\nimport { useMapRuntime, type MapLoadErrorState } from \"./use-map-runtime\";\n\n/** The branch, the shopper's address and an optional route for DeliveryMap. */\nexport type DeliveryMapProps = HTMLAttributes<HTMLDivElement> & {\n /** The branch the order leaves from. Only its coordinates are read. */\n branch: Pick<Branch, \"coordinates\">;\n /** Coordinates of the delivery address. */\n shopper?: Coordinates | undefined;\n /**\n * The path to draw, such as a decoded driving route, from the branch to the\n * shopper. Without it the map draws a straight line.\n */\n route?: Coordinates[];\n browserKey?: string;\n /** Image shown inside the branch marker, such as the business logo. */\n pinImageUrl?: string;\n /** Called each time the map fails to load. */\n onLoadError?: (error: unknown) => void;\n /**\n * Render your own message when the map fails to load, such as a retry\n * button that calls `retry`. Without it the map shows \"Map location unavailable\".\n */\n renderLoadError?: (state: MapLoadErrorState) => ReactNode;\n};\n\nconst loadDeliveryMap = (\n target: HTMLElement,\n key: string,\n signal: AbortSignal,\n) =>\n import(\"./delivery-runtime\").then(({ createDeliveryMap }) =>\n createDeliveryMap(target, key, signal),\n );\n\n/**\n * A static map of a delivery: the branch marker, the shopper's house marker\n * and the line between them, fitted to both. It has no controls and ignores\n * gestures. Maps loads after mount when you supply a browser key. Set the\n * container height with CSS. Colour the house marker with --map-accent and\n * the line with --delivery-map-route.\n */\nexport function DeliveryMap({\n branch,\n shopper,\n route,\n browserKey,\n pinImageUrl,\n onLoadError,\n renderLoadError,\n ...props\n}: DeliveryMapProps) {\n const probe = useRef<HTMLSpanElement>(null);\n const origin = validCoordinates(branch.coordinates)\n ? branch.coordinates\n : undefined;\n const destination = validCoordinates(shopper) ? shopper : undefined;\n const mappable = !!origin && !!destination;\n const { element, runtime, failure, retry } = useMapRuntime<\n Awaited<ReturnType<typeof createDeliveryMap>>\n >(browserKey, mappable, loadDeliveryMap, onLoadError);\n const failed = failure !== undefined;\n const coordinates = branch.coordinates;\n useEffect(() => {\n if (runtime && validCoordinates(coordinates) && validCoordinates(shopper)) {\n runtime.update({\n branch: coordinates,\n shopper,\n route,\n pinImageUrl,\n colors: readColors(probe.current),\n });\n }\n }, [runtime, coordinates, shopper, route, pinImageUrl]);\n if (!browserKey) {\n return null;\n }\n return (\n <div {...props} data-delivery-map=\"\">\n <span ref={probe} hidden style={COLOR_PROBE_STYLE} />\n <div\n hidden={failed || !mappable}\n ref={element}\n data-delivery-map-canvas=\"\"\n style={{ height: \"100%\" }}\n />\n {failure && renderLoadError ? (\n renderLoadError({ error: failure.error, retry })\n ) : failed || !mappable ? (\n <p role=\"status\">Map location unavailable</p>\n ) : null}\n </div>\n );\n}\n"],
5
4
  "mappings": ";;;AAEA,SAAS,WAAW,cAAmD;AACvE,SAAS,mBAAmB,kBAAkB;AAE9C,SAAS,wBAA0C;AACnD,SAAS,qBAA6C;AA6ElD,SACE,KADF;AApDJ,IAAM,kBAAkB,CACtB,QACA,KACA,WAEA,OAAO,uBAAoB,EAAE;AAAA,EAAK,CAAC,EAAE,kBAAkB,MACrD,kBAAkB,QAAQ,KAAK,MAAM;AACvC;AASK,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,QAAM,QAAQ,OAAwB,IAAI;AAC1C,QAAM,SAAS,iBAAiB,OAAO,WAAW,IAC9C,OAAO,cACP;AACJ,QAAM,cAAc,iBAAiB,OAAO,IAAI,UAAU;AAC1D,QAAM,WAAW,CAAC,CAAC,UAAU,CAAC,CAAC;AAC/B,QAAM,EAAE,SAAS,SAAS,SAAS,MAAM,IAAI,cAE3C,YAAY,UAAU,iBAAiB,WAAW;AACpD,QAAM,SAAS,YAAY;AAC3B,QAAM,cAAc,OAAO;AAC3B,YAAU,MAAM;AACd,QAAI,WAAW,iBAAiB,WAAW,KAAK,iBAAiB,OAAO,GAAG;AACzE,cAAQ,OAAO;AAAA,QACb,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ,WAAW,MAAM,OAAO;AAAA,MAClC,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,SAAS,aAAa,SAAS,OAAO,WAAW,CAAC;AACtD,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AACA,SACE,qBAAC,SAAK,GAAG,OAAO,qBAAkB,IAChC;AAAA,wBAAC,UAAK,KAAK,OAAO,QAAM,MAAC,OAAO,mBAAmB;AAAA,IACnD;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,UAAU,CAAC;AAAA,QACnB,KAAK;AAAA,QACL,4BAAyB;AAAA,QACzB,OAAO,EAAE,QAAQ,OAAO;AAAA;AAAA,IAC1B;AAAA,IACC,WAAW,kBACV,gBAAgB,EAAE,OAAO,QAAQ,OAAO,MAAM,CAAC,IAC7C,UAAU,CAAC,WACb,oBAAC,OAAE,MAAK,UAAS,sCAAwB,IACvC;AAAA,KACN;AAEJ;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/branch-map/delivery-runtime.ts"],
4
- "sourcesContent": ["import { pointBounds } from \"./fit\";\nimport type { Coordinates } from \"./geometry\";\nimport { loadMaps } from \"./loader\";\nimport {\n DEFAULT_COLORS,\n markerOverlayClass,\n paintMarker,\n type MapColors,\n type MarkerOverlay,\n} from \"./markers\";\n\nexport type DeliveryMapUpdate = {\n branch: Coordinates;\n shopper: Coordinates;\n route?: Coordinates[] | undefined;\n pinImageUrl?: string | undefined;\n colors?: MapColors | undefined;\n};\n\n/** The line between the branch and the shopper. */\nexport const DELIVERY_ROUTE = {\n color: \"#163300\",\n opacity: 0.6,\n weight: 5,\n} as const;\n/** Diameter of the branch logo marker. */\nexport const DELIVERY_LOGO_SIZE = 36;\n\n/** The points the delivery map frames and the path it draws. */\nexport function deliveryPath(update: DeliveryMapUpdate) {\n const via = (update.route ?? []).filter(\n (point) => Number.isFinite(point.lat) && Number.isFinite(point.lng),\n );\n return via.length >= 2 ? via : [update.branch, update.shopper];\n}\n\nexport function deliveryBounds(update: DeliveryMapUpdate) {\n return pointBounds([update.branch, update.shopper, ...deliveryPath(update)])!;\n}\n\nexport async function createDeliveryMap(\n element: HTMLElement,\n key: string,\n signal: AbortSignal,\n) {\n const maps = await loadMaps(key);\n signal.throwIfAborted();\n // A static preview: no panning, zooming, dragging or controls.\n const map = new maps.Map(element, {\n center: { lat: 0, lng: 0 },\n zoom: 12,\n gestureHandling: \"none\",\n disableDefaultUI: true,\n keyboardShortcuts: false,\n clickableIcons: false,\n });\n const Marker = markerOverlayClass(maps);\n let branchPin: MarkerOverlay | undefined;\n let shopperPin: MarkerOverlay | undefined;\n let line: google.maps.Polyline | undefined;\n let framed: string | undefined;\n let last: DeliveryMapUpdate | undefined;\n const pin = (key: string, position: Coordinates) => {\n const node = document.createElement(\"div\");\n node.setAttribute(\"aria-hidden\", \"true\");\n node.dataset.deliveryMapPin = key;\n const marker = new Marker(key, position, node);\n marker.setMap(map);\n return marker;\n };\n const frame = (update: DeliveryMapUpdate) => {\n map.fitBounds(deliveryBounds(update));\n };\n const resize =\n typeof ResizeObserver === \"function\"\n ? new ResizeObserver(() => {\n if (last) {\n frame(last);\n }\n })\n : undefined;\n resize?.observe(element);\n return {\n update(update: DeliveryMapUpdate) {\n last = update;\n const colors = update.colors ?? DEFAULT_COLORS;\n branchPin ??= pin(\"branch\", update.branch);\n branchPin.setPosition(update.branch);\n if (update.pinImageUrl) {\n // The logo is the marker itself, so its centre sits half its size above the location.\n paintMarker(\n branchPin.element,\n {\n kind: \"logo\",\n size: DELIVERY_LOGO_SIZE,\n imageUrl: update.pinImageUrl,\n shadow: \"sm\",\n lift: DELIVERY_LOGO_SIZE / 2,\n },\n colors,\n );\n } else {\n paintMarker(branchPin.element, { kind: \"dot\", selected: true }, colors);\n }\n branchPin.element.style.cursor = \"default\";\n shopperPin ??= pin(\"shopper\", update.shopper);\n shopperPin.setPosition(update.shopper);\n paintMarker(\n shopperPin.element,\n { kind: \"house\", color: \"#ffffff\", lift: 16 },\n colors,\n );\n line ??= new maps.Polyline({ map });\n line.setOptions({\n path: deliveryPath(update),\n strokeColor: update.colors?.route ?? DELIVERY_ROUTE.color,\n strokeOpacity: DELIVERY_ROUTE.opacity,\n strokeWeight: DELIVERY_ROUTE.weight,\n clickable: false,\n });\n const key = JSON.stringify(deliveryBounds(update));\n if (key !== framed) {\n framed = key;\n frame(update);\n }\n },\n destroy() {\n resize?.disconnect();\n branchPin?.setMap(null);\n shopperPin?.setMap(null);\n line?.setMap(null);\n maps.event.clearInstanceListeners(map);\n element.replaceChildren();\n },\n };\n}\n"],
5
4
  "mappings": "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",
6
5
  "names": ["key"]
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/branch-map/fit.ts"],
4
- "sourcesContent": ["import type { Branch } from \"@zappie/sdk\";\nimport {\n validCoordinates,\n visibleBranches,\n type Bounds,\n type Coordinates,\n} from \"./geometry\";\n\nexport type Padding =\n number | { top: number; right: number; bottom: number; left: number };\n\n/** Padding around every fitted point when no route is drawn. */\nexport const OVERVIEW_PADDING = 64;\n/** Padding around the shopper-to-branch route. */\nexport const ROUTE_PADDING: Padding = {\n top: 88,\n right: 88,\n bottom: 88,\n left: 88,\n};\n/** Route padding in a compact map, with room for both pin labels. */\nexport const COMPACT_ROUTE_PADDING: Padding = {\n top: 72,\n right: 52,\n bottom: 36,\n left: 52,\n};\n/** Zoom for a single point, in a full and a compact map. */\nexport const SINGLE_POINT_ZOOM = 14;\nexport const COMPACT_SINGLE_POINT_ZOOM = 13;\n\n/** Great-circle distance in kilometres. */\nexport function distanceKm(from: Coordinates, to: Coordinates) {\n const radians = (degrees: number) => (degrees * Math.PI) / 180;\n const dLat = radians(to.lat - from.lat);\n const dLng = radians(to.lng - from.lng);\n const a =\n Math.sin(dLat / 2) ** 2 +\n Math.cos(radians(from.lat)) *\n Math.cos(radians(to.lat)) *\n Math.sin(dLng / 2) ** 2;\n return 6371 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n}\n\n/**\n * The branch the map highlights: the selected one, or, once the shopper's\n * location is known, the nearest branch.\n */\nexport function focusedBranch(\n branches: Branch[],\n selectedId: string | undefined,\n shopper: Coordinates | undefined,\n) {\n const visible = visibleBranches(branches);\n const selected = visible.find((branch) => branch.id === selectedId);\n if (selected || !validCoordinates(shopper)) {\n return selected;\n }\n let nearest: (typeof visible)[number] | undefined;\n let best = Infinity;\n for (const branch of visible) {\n const distance = distanceKm(shopper, branch.coordinates);\n if (distance < best) {\n best = distance;\n nearest = branch;\n }\n }\n return nearest;\n}\n\n/** Diameter of a cluster icon for the number of branches it holds. */\nexport function clusterSize(count: number) {\n return count < 10 ? 40 : count < 50 ? 48 : 56;\n}\n\nexport function pointBounds(points: Coordinates[]): Bounds | undefined {\n if (!points.length) {\n return undefined;\n }\n // The largest longitude gap lies outside the bounds, including across the date line.\n // Sort by the wrapped longitude but report the original values, so they stay exact.\n const wrapped = (lng: number) => (lng + 360) % 360;\n const longitudes = points\n .map((point) => point.lng)\n .sort((a, b) => wrapped(a) - wrapped(b));\n let gap = -1;\n let start = 0;\n for (let index = 0; index < longitudes.length; index++) {\n const next =\n wrapped(longitudes[(index + 1) % longitudes.length]!) +\n (index === longitudes.length - 1 ? 360 : 0);\n if (next - wrapped(longitudes[index]!) > gap) {\n gap = next - wrapped(longitudes[index]!);\n start = (index + 1) % longitudes.length;\n }\n }\n return {\n north: Math.max(...points.map((point) => point.lat)),\n south: Math.min(...points.map((point) => point.lat)),\n west: longitudes[start]!,\n east: longitudes[(start + longitudes.length - 1) % longitudes.length]!,\n };\n}\n\nexport function branchBounds(branches: Branch[], location?: Coordinates) {\n const points = visibleBranches(branches).map((branch) => branch.coordinates);\n if (validCoordinates(location)) {\n points.push(location);\n }\n return pointBounds(points);\n}\n\n/** How the map frames its points. */\nexport type FitPlan =\n | { kind: \"center\"; center: Coordinates; zoom: number }\n | { kind: \"bounds\"; bounds: Bounds; padding: Padding; maxZoom: number };\n\n/**\n * With a route, frame the shopper and the focused branch. Otherwise frame the\n * shopper and every branch. A single point is centred at a fixed zoom.\n */\nexport function fitPlan({\n branches,\n shopper,\n focused,\n maxZoom,\n compact = false,\n}: {\n branches: Branch[];\n shopper?: Coordinates | undefined;\n focused?: (Branch & { coordinates: Coordinates }) | undefined;\n maxZoom: number;\n compact?: boolean;\n}): FitPlan | undefined {\n const location = validCoordinates(shopper) ? shopper : undefined;\n const points =\n location && focused\n ? [location, focused.coordinates]\n : [\n ...(location ? [location] : []),\n ...visibleBranches(branches).map((branch) => branch.coordinates),\n ];\n if (!points.length) {\n return undefined;\n }\n const route = !!(location && focused);\n if (points.length === 1) {\n return {\n kind: \"center\",\n center: points[0]!,\n zoom: Math.min(\n maxZoom,\n compact ? COMPACT_SINGLE_POINT_ZOOM : SINGLE_POINT_ZOOM,\n ),\n };\n }\n return {\n kind: \"bounds\",\n bounds: pointBounds(points)!,\n padding: route\n ? compact\n ? COMPACT_ROUTE_PADDING\n : ROUTE_PADDING\n : OVERVIEW_PADDING,\n maxZoom,\n };\n}\n"],
5
4
  "mappings": "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",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/branch-map/geometry.ts"],
4
- "sourcesContent": ["import type { Branch } from \"@zappie/sdk\";\n\nexport type Coordinates = { lat: number; lng: number };\nexport type Bounds = {\n north: number;\n south: number;\n east: number;\n west: number;\n};\n\nexport function validCoordinates(value?: Coordinates): value is Coordinates {\n return (\n !!value &&\n Number.isFinite(value.lat) &&\n Number.isFinite(value.lng) &&\n Math.abs(value.lat) <= 90 &&\n Math.abs(value.lng) <= 180\n );\n}\n\n/** Branches with a real location. (0, 0) is the placeholder for \"no location on file\". */\nexport function visibleBranches(branches: Branch[]) {\n return branches.filter(\n (branch): branch is Branch & { coordinates: Coordinates } =>\n validCoordinates(branch.coordinates) &&\n (branch.coordinates.lat !== 0 || branch.coordinates.lng !== 0),\n );\n}\n"],
5
4
  "mappings": ";AAUO,SAAS,iBAAiB,OAA2C;AAC1E,SACE,CAAC,CAAC,SACF,OAAO,SAAS,MAAM,GAAG,KACzB,OAAO,SAAS,MAAM,GAAG,KACzB,KAAK,IAAI,MAAM,GAAG,KAAK,MACvB,KAAK,IAAI,MAAM,GAAG,KAAK;AAE3B;AAGO,SAAS,gBAAgB,UAAoB;AAClD,SAAO,SAAS;AAAA,IACd,CAAC,WACC,iBAAiB,OAAO,WAAW,MAClC,OAAO,YAAY,QAAQ,KAAK,OAAO,YAAY,QAAQ;AAAA,EAChE;AACF;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/branch-map/index.tsx"],
4
- "sourcesContent": ["\"use client\";\nimport type { Branch } from \"@zappie/sdk\";\nimport { useEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { COLOR_PROBE_STYLE, readColors } from \"./colors\";\nimport {\n validCoordinates,\n visibleBranches,\n type Coordinates,\n} from \"./geometry\";\nimport type { createMap } from \"./runtime\";\nimport { useMapRuntime, type MapLoadErrorState } from \"./use-map-runtime\";\n\nexport { DeliveryMap } from \"./delivery-map\";\nexport type { DeliveryMapProps } from \"./delivery-map\";\nexport type { MapLoadErrorState } from \"./use-map-runtime\";\n\n/** What a template gets to render when the map fails to load. */\nexport type BranchMapLoadErrorState = MapLoadErrorState;\n\n/** Branches, selection and optional shopper coordinates for BranchMap. */\nexport type BranchMapProps = Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onSelect\"\n> & {\n branches: Branch[];\n selectedBranchId?: string;\n onSelect: (branch: Branch) => void;\n /**\n * The shopper's coordinates. The map shows them with a house marker and\n * draws a line to the selected branch, or to the nearest one.\n */\n shopperLocation?: Coordinates;\n browserKey?: string;\n /**\n * Highest zoom applied after fitting. Defaults to 15 when a shopper location\n * is shown and 14 otherwise.\n */\n maxZoom?: number;\n /** Image shown inside each branch pin, such as the business logo. */\n pinImageUrl?: string;\n /** Show the map and satellite switch. Defaults to false. */\n mapTypeControl?: boolean;\n /**\n * Show the branch name above the selected pin, or above every pin. Off by\n * default. With a shopper location and no selection, \"selected\" labels the\n * nearest branch.\n */\n pinLabel?: \"selected\" | \"all\";\n /** Frame a small map, with tighter padding and a wider single-branch view. */\n compact?: boolean;\n /** Called each time the map fails to load. */\n onLoadError?: (error: unknown) => void;\n /**\n * Render your own message when the map fails to load, such as a retry\n * button that calls `retry`. Without it the map shows \"Map location unavailable\".\n */\n renderLoadError?: (state: BranchMapLoadErrorState) => ReactNode;\n};\n\nconst loadBranchMap =\n (mapTypeControl: boolean) =>\n (target: HTMLElement, key: string, signal: AbortSignal) =>\n import(\"./runtime\").then(({ createMap }) =>\n createMap(target, key, signal, { mapTypeControl }),\n );\n\n/**\n * Show branch locations. Maps loads after mount when you supply a browser key.\n * Nearby branches group into numbered clusters. Set the container height with\n * CSS. Colour clusters, the shopper marker, labels and the route with\n * --map-accent and --map-accent-foreground. Style dot pins with\n * --branch-map-pin, --branch-map-pin-selected, --branch-map-pin-border and\n * --branch-map-pin-size.\n */\nexport function BranchMap({\n branches,\n selectedBranchId,\n onSelect,\n shopperLocation,\n browserKey,\n maxZoom,\n pinImageUrl,\n mapTypeControl = false,\n pinLabel,\n compact = false,\n onLoadError,\n renderLoadError,\n ...props\n}: BranchMapProps) {\n const probe = useRef<HTMLSpanElement>(null);\n const hasCoordinates = visibleBranches(branches).length > 0;\n const { element, runtime, failure, retry } = useMapRuntime<\n Awaited<ReturnType<typeof createMap>>\n >(browserKey, hasCoordinates, loadBranchMap(mapTypeControl), onLoadError);\n const failed = failure !== undefined;\n const effectiveMaxZoom =\n maxZoom ?? (validCoordinates(shopperLocation) ? 15 : 14);\n useEffect(() => {\n runtime?.update(branches, selectedBranchId, onSelect, shopperLocation, {\n maxZoom: effectiveMaxZoom,\n pinImageUrl,\n mapTypeControl,\n pinLabel,\n compact,\n colors: readColors(probe.current),\n });\n }, [\n runtime,\n branches,\n selectedBranchId,\n onSelect,\n shopperLocation,\n effectiveMaxZoom,\n pinImageUrl,\n mapTypeControl,\n pinLabel,\n compact,\n ]);\n if (!browserKey) {\n return null;\n }\n const unavailable = branches.filter(\n (branch) => !visibleBranches([branch]).length,\n );\n return (\n <div {...props} data-branch-map=\"\">\n <span ref={probe} hidden style={COLOR_PROBE_STYLE} />\n <div\n hidden={failed || !hasCoordinates}\n ref={element}\n data-branch-map-canvas=\"\"\n style={{ height: \"100%\" }}\n />\n {failure && renderLoadError ? (\n renderLoadError({ error: failure.error, retry })\n ) : failed || !hasCoordinates ? (\n <p role=\"status\">Map location unavailable</p>\n ) : null}\n {unavailable.map((branch) => (\n <button\n key={branch.id}\n type=\"button\"\n aria-pressed={branch.id === selectedBranchId}\n onClick={() => onSelect(branch)}\n >\n {branch.name}: Map location unavailable\n </button>\n ))}\n {runtime && validCoordinates(shopperLocation) ? (\n <button type=\"button\" onClick={() => runtime.locate(shopperLocation)}>\n Use my current location\n </button>\n ) : null}\n </div>\n );\n}\n"],
5
4
  "mappings": ";;;AAEA,SAAS,WAAW,cAAmD;AACvE,SAAS,mBAAmB,kBAAkB;AAC9C;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAEP,SAAS,qBAA6C;AAEtD,SAAS,mBAAmB;AAkHtB,cAaE,YAbF;AAnEN,IAAM,gBACJ,CAAC,mBACD,CAAC,QAAqB,KAAa,WACjC,OAAO,cAAW,EAAE;AAAA,EAAK,CAAC,EAAE,UAAU,MACpC,UAAU,QAAQ,KAAK,QAAQ,EAAE,eAAe,CAAC;AACnD;AAUG,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAmB;AACjB,QAAM,QAAQ,OAAwB,IAAI;AAC1C,QAAM,iBAAiB,gBAAgB,QAAQ,EAAE,SAAS;AAC1D,QAAM,EAAE,SAAS,SAAS,SAAS,MAAM,IAAI,cAE3C,YAAY,gBAAgB,cAAc,cAAc,GAAG,WAAW;AACxE,QAAM,SAAS,YAAY;AAC3B,QAAM,mBACJ,YAAY,iBAAiB,eAAe,IAAI,KAAK;AACvD,YAAU,MAAM;AACd,aAAS,OAAO,UAAU,kBAAkB,UAAU,iBAAiB;AAAA,MACrE,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,WAAW,MAAM,OAAO;AAAA,IAClC,CAAC;AAAA,EACH,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AACA,QAAM,cAAc,SAAS;AAAA,IAC3B,CAAC,WAAW,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE;AAAA,EACzC;AACA,SACE,qBAAC,SAAK,GAAG,OAAO,mBAAgB,IAC9B;AAAA,wBAAC,UAAK,KAAK,OAAO,QAAM,MAAC,OAAO,mBAAmB;AAAA,IACnD;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,UAAU,CAAC;AAAA,QACnB,KAAK;AAAA,QACL,0BAAuB;AAAA,QACvB,OAAO,EAAE,QAAQ,OAAO;AAAA;AAAA,IAC1B;AAAA,IACC,WAAW,kBACV,gBAAgB,EAAE,OAAO,QAAQ,OAAO,MAAM,CAAC,IAC7C,UAAU,CAAC,iBACb,oBAAC,OAAE,MAAK,UAAS,sCAAwB,IACvC;AAAA,IACH,YAAY,IAAI,CAAC,WAChB;AAAA,MAAC;AAAA;AAAA,QAEC,MAAK;AAAA,QACL,gBAAc,OAAO,OAAO;AAAA,QAC5B,SAAS,MAAM,SAAS,MAAM;AAAA,QAE7B;AAAA,iBAAO;AAAA,UAAK;AAAA;AAAA;AAAA,MALR,OAAO;AAAA,IAMd,CACD;AAAA,IACA,WAAW,iBAAiB,eAAe,IAC1C,oBAAC,YAAO,MAAK,UAAS,SAAS,MAAM,QAAQ,OAAO,eAAe,GAAG,qCAEtE,IACE;AAAA,KACN;AAEJ;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/branch-map/loader.ts"],
4
- "sourcesContent": ["/// <reference types=\"google.maps\" />\nlet pending: Promise<typeof google.maps> | undefined;\n\nexport function loadMaps(key: string): Promise<typeof google.maps> {\n if (globalThis.google?.maps?.Map) {\n return Promise.resolve(google.maps);\n }\n if (pending) {\n return pending;\n }\n pending = new Promise((resolve, reject) => {\n const script = document.createElement(\"script\");\n const callback = `__zappieMaps${Date.now()}`;\n const scope = globalThis as unknown as Record<string, unknown>;\n const cleanup = () => {\n clearTimeout(timeout);\n delete scope[callback];\n };\n const fail = () => {\n cleanup();\n script.remove();\n pending = undefined;\n reject(new Error(\"Map location unavailable\"));\n };\n const timeout = setTimeout(fail, 15000);\n scope[callback] = () => {\n cleanup();\n resolve(google.maps);\n };\n script.async = true;\n script.src = `https://maps.googleapis.com/maps/api/js?${new URLSearchParams({ key, loading: \"async\", callback, v: \"weekly\" })}`;\n script.onerror = fail;\n document.head.appendChild(script);\n });\n return pending;\n}\n"],
5
4
  "mappings": ";AACA,IAAI;AAEG,SAAS,SAAS,KAA0C;AACjE,MAAI,WAAW,QAAQ,MAAM,KAAK;AAChC,WAAO,QAAQ,QAAQ,OAAO,IAAI;AAAA,EACpC;AACA,MAAI,SAAS;AACX,WAAO;AAAA,EACT;AACA,YAAU,IAAI,QAAQ,CAAC,SAAS,WAAW;AACzC,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,UAAM,WAAW,eAAe,KAAK,IAAI,CAAC;AAC1C,UAAM,QAAQ;AACd,UAAM,UAAU,MAAM;AACpB,mBAAa,OAAO;AACpB,aAAO,MAAM,QAAQ;AAAA,IACvB;AACA,UAAM,OAAO,MAAM;AACjB,cAAQ;AACR,aAAO,OAAO;AACd,gBAAU;AACV,aAAO,IAAI,MAAM,0BAA0B,CAAC;AAAA,IAC9C;AACA,UAAM,UAAU,WAAW,MAAM,IAAK;AACtC,UAAM,QAAQ,IAAI,MAAM;AACtB,cAAQ;AACR,cAAQ,OAAO,IAAI;AAAA,IACrB;AACA,WAAO,QAAQ;AACf,WAAO,MAAM,2CAA2C,IAAI,gBAAgB,EAAE,KAAK,SAAS,SAAS,UAAU,GAAG,SAAS,CAAC,CAAC;AAC7H,WAAO,UAAU;AACjB,aAAS,KAAK,YAAY,MAAM;AAAA,EAClC,CAAC;AACD,SAAO;AACT;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/branch-map/markers.ts"],
4
- "sourcesContent": ["/// <reference types=\"google.maps\" />\nimport type { MapColors } from \"./colors\";\nimport type { Coordinates } from \"./geometry\";\n\nexport { DEFAULT_COLORS, type MapColors } from \"./colors\";\n\nconst SHADOW_SM =\n \"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)\";\nconst SHADOW_MD =\n \"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)\";\n/** Every marker sits in a 40px box whose bottom centre is the location. */\nconst MARKER_BOX = 40;\nconst LABEL_GAP = 4;\n\n/** What a marker shows. */\nexport type MarkerSpec =\n | {\n kind: \"logo\";\n /** Outer diameter, including the white border. */\n size: number;\n imageUrl: string;\n shadow: \"sm\" | \"md\";\n /** Offset from the location to the logo's centre. Defaults to the 40px box. */\n lift?: number;\n }\n | { kind: \"dot\"; selected: boolean }\n | {\n kind: \"house\";\n /** Icon colour. The fill is the accent. */\n color: string;\n /** Offset from the location to the icon's centre. Defaults to the 40px box. */\n lift?: number;\n };\n\n/** Choose the branch marker: the logo when there is an image, otherwise the themed dot. */\nexport function branchMarker(\n imageUrl: string | undefined,\n focused: boolean,\n): MarkerSpec {\n return imageUrl\n ? {\n kind: \"logo\",\n size: focused ? 40 : 32,\n imageUrl,\n shadow: focused ? \"md\" : \"sm\",\n }\n : { kind: \"dot\", selected: focused };\n}\n\nconst SVG_NAMESPACE = \"http://www.w3.org/2000/svg\";\n\nfunction houseIcon(): SVGSVGElement {\n const svg = document.createElementNS(SVG_NAMESPACE, \"svg\");\n for (const [name, value] of Object.entries({\n width: \"20\",\n height: \"20\",\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n \"stroke-linecap\": \"round\",\n \"stroke-linejoin\": \"round\",\n \"aria-hidden\": \"true\",\n })) {\n svg.setAttribute(name, value);\n }\n svg.style.display = \"block\";\n for (const data of [\n [\n \"path\",\n \"d\",\n \"M15 22a1 1 0 0 1-1-1v-4a1 1 0 0 1 .445-.832l3-2a1 1 0 0 1 1.11 0l3 2A1 1 0 0 1 22 17v4a1 1 0 0 1-1 1z\",\n ],\n [\n \"path\",\n \"d\",\n \"M18 10a8 8 0 0 0-16 0c0 4.993 5.539 10.193 7.399 11.799a1 1 0 0 0 .601.2\",\n ],\n [\"path\", \"d\", \"M18 22v-3\"],\n [\"circle\", \"cx\", \"10\"],\n ] as const) {\n const child = document.createElementNS(SVG_NAMESPACE, data[0]);\n child.setAttribute(data[1], data[2]);\n if (data[0] === \"circle\") {\n child.setAttribute(\"cy\", \"10\");\n child.setAttribute(\"r\", \"3\");\n }\n svg.appendChild(child);\n }\n return svg;\n}\n\n/** Style a marker element in place, so selection changes keep the same node. */\nexport function paintMarker(\n element: HTMLElement,\n spec: MarkerSpec,\n colors: MapColors,\n) {\n const { left, top } = element.style;\n try {\n paint(element, spec, colors);\n } finally {\n element.style.left = left;\n element.style.top = top;\n }\n}\n\nfunction paint(element: HTMLElement, spec: MarkerSpec, colors: MapColors) {\n element.dataset.marker = spec.kind;\n if (spec.kind === \"logo\") {\n const lift = spec.lift ?? MARKER_BOX / 2;\n element.style.cssText = `position:absolute;box-sizing:border-box;transform:translate(-50%,calc(-50% - ${lift}px));width:${spec.size}px;height:${spec.size}px;padding:0;border-radius:9999px;overflow:hidden;border:2px solid white;background:white;box-shadow:${spec.shadow === \"md\" ? SHADOW_MD : SHADOW_SM};cursor:pointer;transition:width 150ms cubic-bezier(0.4,0,0.2,1),height 150ms cubic-bezier(0.4,0,0.2,1)`;\n let image = element.querySelector(\"img\");\n if (!image) {\n element.replaceChildren();\n image = document.createElement(\"img\");\n image.alt = \"\";\n image.decoding = \"async\";\n image.draggable = false;\n image.onerror = () => {\n image!.style.visibility = \"hidden\";\n };\n element.appendChild(image);\n }\n image.style.cssText =\n \"display:block;width:100%;height:100%;border-radius:9999px;object-fit:cover\";\n if (image.getAttribute(\"src\") !== spec.imageUrl) {\n image.src = spec.imageUrl;\n }\n return;\n }\n if (spec.kind === \"dot\") {\n element.replaceChildren();\n element.style.cssText = `position:absolute;transform:translate(-50%,-100%);width:var(--branch-map-pin-size,24px);height:var(--branch-map-pin-size,24px);border-radius:50%;border:2px solid var(--branch-map-pin-border,currentColor);background:var(${spec.selected ? \"--branch-map-pin-selected\" : \"--branch-map-pin\"},currentColor);cursor:pointer`;\n return;\n }\n element.style.cssText = `position:absolute;box-sizing:border-box;transform:translate(-50%,calc(-50% - ${spec.lift ?? MARKER_BOX / 2}px));width:32px;height:32px;padding:4px;border-radius:4px;border:2px solid white;background:${colors.accent};color:${spec.color};box-shadow:${SHADOW_MD}`;\n if (!element.firstChild) {\n element.appendChild(houseIcon());\n }\n}\n\n/** Style a label above a marker. Branch labels are capitalised like the default storefront's. */\nexport function paintLabel(\n element: HTMLElement,\n options: { offset: string; capitalize: boolean; colors: MapColors },\n) {\n const { left, top } = element.style;\n element.style.cssText = `position:absolute;box-sizing:border-box;transform:translate(-50%,calc(-100% - ${options.offset}));white-space:nowrap;pointer-events:none;padding:4px 8px;border-radius:8px;font-size:12px;line-height:16px;font-weight:500;${options.capitalize ? \"text-transform:capitalize;\" : \"\"}background:var(--branch-map-label-background,white);color:var(--branch-map-label-color,#111);border:2px solid var(--branch-map-label-border,${options.colors.accent});box-shadow:${SHADOW_MD}`;\n element.style.left = left;\n element.style.top = top;\n}\n\n/** Offset from the location to the bottom of a label above a marker. */\nexport function labelOffset(spec: MarkerSpec) {\n return spec.kind === \"dot\"\n ? `var(--branch-map-pin-size,24px) + ${LABEL_GAP}px`\n : `${MARKER_BOX + LABEL_GAP}px`;\n}\n\nexport type MarkerOverlay = google.maps.OverlayView & {\n readonly element: HTMLElement;\n readonly label: HTMLElement;\n /** The clusterer reads markers through these two methods. */\n getPosition(): google.maps.LatLng;\n getVisible(): boolean;\n setLabel(text: string | undefined): void;\n setPosition(position: Coordinates): void;\n};\n\n/**\n * An overlay that places one element (a button or a div) and an optional\n * label at a location. The DOM nodes persist across restyles.\n */\nexport function markerOverlayClass(\n maps: typeof google.maps,\n): new (\n key: string,\n position: Coordinates,\n element: HTMLElement,\n) => MarkerOverlay {\n return class Marker extends maps.OverlayView implements MarkerOverlay {\n // Declared first so two markers compare unequal at once (the clusterer deep-compares them).\n readonly key: string;\n readonly element: HTMLElement;\n readonly label: HTMLElement;\n #position: Coordinates;\n #pane: Element | undefined;\n constructor(key: string, position: Coordinates, element: HTMLElement) {\n super();\n this.key = key;\n this.element = element;\n this.#position = position;\n this.label = document.createElement(\"span\");\n this.label.setAttribute(\"aria-hidden\", \"true\");\n }\n getPosition() {\n return new maps.LatLng(this.#position);\n }\n getVisible() {\n return true;\n }\n setPosition(position: Coordinates) {\n this.#position = position;\n if (this.getMap()) {\n this.draw();\n }\n }\n // An unlabelled marker has no label node in the map.\n setLabel(text: string | undefined) {\n this.label.textContent = text ?? \"\";\n if (!text) {\n this.label.remove();\n } else if (this.#pane && !this.label.isConnected) {\n this.#pane.appendChild(this.label);\n }\n }\n onAdd() {\n this.#pane = this.getPanes()?.overlayMouseTarget;\n this.#pane?.appendChild(this.element);\n if (this.label.textContent) {\n this.#pane?.appendChild(this.label);\n }\n maps.OverlayView.preventMapHitsAndGesturesFrom(this.element);\n }\n draw() {\n const point = this.getProjection()?.fromLatLngToDivPixel(\n new maps.LatLng(this.#position),\n );\n if (point) {\n for (const node of [this.element, this.label]) {\n node.style.left = `${point.x}px`;\n node.style.top = `${point.y}px`;\n }\n }\n }\n onRemove() {\n this.#pane = undefined;\n this.element.remove();\n this.label.remove();\n }\n };\n}\n"],
5
4
  "mappings": "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",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/branch-map/runtime.ts"],
4
- "sourcesContent": ["import { MarkerClusterer, type Cluster } from \"@googlemaps/markerclusterer\";\nimport type { Branch } from \"@zappie/sdk\";\nimport { clusterSize, fitPlan, focusedBranch, type FitPlan } from \"./fit\";\nimport {\n validCoordinates,\n visibleBranches,\n type Coordinates,\n} from \"./geometry\";\nimport { loadMaps } from \"./loader\";\nimport {\n branchMarker,\n DEFAULT_COLORS,\n labelOffset,\n markerOverlayClass,\n paintLabel,\n paintMarker,\n type MapColors,\n type MarkerOverlay,\n} from \"./markers\";\n\nexport type MapUpdateOptions = {\n maxZoom: number;\n pinImageUrl: string | undefined;\n mapTypeControl: boolean;\n pinLabel?: \"selected\" | \"all\" | undefined;\n compact?: boolean | undefined;\n colors?: MapColors | undefined;\n};\n\n// The clusterer reads only getPosition, getVisible and setMap, which overlays provide.\nconst clusterable = (marker: MarkerOverlay) =>\n marker as unknown as google.maps.Marker;\n\nconst ROUTE_OPACITY = 0.7;\nconst ROUTE_WEIGHT = 4;\nconst CLUSTER_EXPAND_PADDING = 56;\nconst CLUSTER_EXPAND_MAX_ZOOM = 21;\nconst CLUSTER_EXPAND_MAX_ATTEMPTS = 6;\n\n/** The cluster icon: an accent circle with a white ring and the branch count. */\nexport function clusterIcon(count: number, colors: MapColors) {\n const size = clusterSize(count);\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 ${size} ${size}\"><circle cx=\"${size / 2}\" cy=\"${size / 2}\" r=\"${size / 2 - 3}\" fill=\"${colors.accent}\" stroke=\"#ffffff\" stroke-width=\"3\"/></svg>`;\n return {\n size,\n url: `data:image/svg+xml;base64,${btoa(svg)}`,\n label: {\n text: String(count),\n color: colors.accentForeground,\n fontSize: \"13px\",\n fontWeight: \"600\",\n },\n };\n}\n\nexport async function createMap(\n element: HTMLElement,\n key: string,\n signal: AbortSignal,\n options: { mapTypeControl: boolean },\n) {\n const maps = await loadMaps(key);\n signal.throwIfAborted();\n const map = new maps.Map(element, {\n center: { lat: 0, lng: 0 },\n zoom: 12,\n mapTypeControl: options.mapTypeControl,\n streetViewControl: false,\n });\n const Marker = markerOverlayClass(maps);\n const pins = new Map<string, { marker: MarkerOverlay; branch: Branch }>();\n let shopperPin: MarkerOverlay | undefined;\n let route: google.maps.Polyline | undefined;\n let clusterer: MarkerClusterer | undefined;\n let clusterColors: string | undefined;\n let clustered: string | undefined;\n let fitListener: google.maps.MapsEventListener | undefined;\n let stopExpanding: (() => void) | undefined;\n let fitted: string | undefined;\n let lastSelected: string | undefined;\n let select: (branch: Branch) => void = () => {};\n\n const fit = (plan: FitPlan) => {\n fitListener?.remove();\n fitListener = undefined;\n if (plan.kind === \"center\") {\n map.panTo(plan.center);\n map.setZoom(plan.zoom);\n return;\n }\n map.fitBounds(plan.bounds, plan.padding);\n fitListener = maps.event.addListenerOnce(map, \"idle\", () => {\n fitListener = undefined;\n const zoom = map.getZoom();\n if (typeof zoom === \"number\" && zoom > plan.maxZoom) {\n map.setZoom(plan.maxZoom);\n }\n });\n };\n\n const ensureClusterer = (colors: MapColors) => {\n const key = `${colors.accent}|${colors.accentForeground}`;\n if (clusterer && clusterColors === key) {\n return clusterer;\n }\n clusterer?.clearMarkers(true);\n clusterer?.setMap(null);\n clusterColors = key;\n clustered = undefined;\n // The default click handler fits the map to the cluster, which expands it.\n clusterer = new MarkerClusterer({\n map,\n renderer: {\n render: ({ count, position }) => {\n const icon = clusterIcon(count, colors);\n return new maps.Marker({\n position,\n icon: {\n url: icon.url,\n scaledSize: new maps.Size(icon.size, icon.size),\n },\n label: icon.label,\n zIndex: 999,\n });\n },\n },\n });\n return clusterer;\n };\n\n // A selected branch hidden inside a cluster: zoom in until its own pin shows.\n const expandTo = (marker: MarkerOverlay) => {\n stopExpanding?.();\n const active = clusterer;\n if (!active) {\n return;\n }\n let attempts = 0;\n let cancelled = false;\n let idle: google.maps.MapsEventListener | undefined;\n const expand = () => {\n if (cancelled) {\n return;\n }\n active.render();\n const cluster = (\n active as MarkerClusterer & { clusters?: Cluster[] }\n ).clusters?.find((candidate) =>\n candidate.markers?.includes(clusterable(marker)),\n );\n if (!cluster || cluster.count <= 1) {\n return;\n }\n if (attempts === 0 && cluster.bounds) {\n map.fitBounds(cluster.bounds, CLUSTER_EXPAND_PADDING);\n } else {\n const zoom = map.getZoom();\n if (typeof zoom !== \"number\" || zoom >= CLUSTER_EXPAND_MAX_ZOOM) {\n return;\n }\n map.setZoom(Math.min(zoom + 2, CLUSTER_EXPAND_MAX_ZOOM));\n }\n attempts += 1;\n if (attempts < CLUSTER_EXPAND_MAX_ATTEMPTS) {\n idle = maps.event.addListenerOnce(map, \"idle\", expand);\n }\n };\n expand();\n stopExpanding = () => {\n cancelled = true;\n idle?.remove();\n stopExpanding = undefined;\n };\n };\n\n return {\n update(\n branches: Branch[],\n selected: string | undefined,\n onSelect: (branch: Branch) => void,\n location?: Coordinates,\n options?: MapUpdateOptions,\n ) {\n select = onSelect;\n const colors = options?.colors ?? DEFAULT_COLORS;\n map.setOptions({ mapTypeControl: options?.mapTypeControl ?? false });\n const visible = visibleBranches(branches);\n const shopper = validCoordinates(location) ? location : undefined;\n const focused = focusedBranch(visible, selected, shopper);\n\n for (const [id, pin] of pins) {\n if (!visible.some((branch) => branch.id === id)) {\n pin.marker.setMap(null);\n pins.delete(id);\n }\n }\n for (const branch of visible) {\n let pin = pins.get(branch.id);\n if (!pin) {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.dataset.branchMapPin = \"\";\n const id = branch.id;\n button.onclick = () => {\n const current = pins.get(id);\n if (current) {\n select(current.branch);\n }\n };\n pin = {\n marker: new Marker(branch.id, branch.coordinates, button),\n branch,\n };\n pin.marker.label.dataset.branchMapPinLabel = \"\";\n pins.set(branch.id, pin);\n }\n pin.branch = branch;\n pin.marker.setPosition(branch.coordinates);\n const isFocused = branch.id === focused?.id;\n const spec = branchMarker(options?.pinImageUrl, isFocused);\n const button = pin.marker.element;\n button.setAttribute(\"aria-label\", branch.name);\n button.setAttribute(\"aria-pressed\", String(branch.id === selected));\n button.dataset.selected = String(branch.id === selected);\n button.dataset.focused = String(isFocused);\n paintMarker(button, spec, colors);\n const showLabel =\n options?.pinLabel === \"all\" ||\n (options?.pinLabel === \"selected\" && isFocused);\n pin.marker.setLabel(showLabel ? branch.name : undefined);\n pin.marker.label.dataset.selected = String(branch.id === selected);\n paintLabel(pin.marker.label, {\n offset: labelOffset(spec),\n capitalize: true,\n colors,\n });\n }\n\n const active = ensureClusterer(colors);\n const markerSet = [...pins.keys()].join(\"\\n\");\n if (markerSet !== clustered) {\n clustered = markerSet;\n active.clearMarkers(true);\n active.addMarkers(\n [...pins.values()].map(({ marker }) => clusterable(marker)),\n );\n }\n\n if (shopper) {\n if (!shopperPin) {\n const house = document.createElement(\"div\");\n house.setAttribute(\"role\", \"img\");\n house.setAttribute(\"aria-label\", \"Your location\");\n house.dataset.branchMapShopper = \"\";\n shopperPin = new Marker(\"shopper\", shopper, house);\n shopperPin.setLabel(\"You\");\n shopperPin.setMap(map);\n }\n shopperPin.setPosition(shopper);\n paintMarker(\n shopperPin.element,\n { kind: \"house\", color: colors.accentForeground },\n colors,\n );\n paintLabel(shopperPin.label, {\n offset: labelOffset({ kind: \"house\", color: \"\" }),\n capitalize: false,\n colors,\n });\n } else {\n shopperPin?.setMap(null);\n shopperPin = undefined;\n }\n\n if (shopper && focused) {\n route ??= new maps.Polyline({ map });\n route.setOptions({\n path: [shopper, focused.coordinates],\n strokeColor: colors.accent,\n strokeOpacity: ROUTE_OPACITY,\n strokeWeight: ROUTE_WEIGHT,\n clickable: false,\n });\n } else {\n route?.setMap(null);\n route = undefined;\n }\n\n const plan = fitPlan({\n branches: visible,\n shopper,\n focused,\n maxZoom: options?.maxZoom ?? 14,\n compact: options?.compact ?? false,\n });\n // Refit only when what the map frames changes. A new selection pans instead.\n const frame = JSON.stringify(plan ?? null);\n const previous = lastSelected;\n lastSelected = selected;\n if (frame !== fitted) {\n fitted = frame;\n if (plan) {\n fit(plan);\n }\n } else {\n const target = visible.find((branch) => branch.id === selected);\n if (target && selected !== previous && !shopper && !options?.compact) {\n map.panTo(target.coordinates);\n }\n }\n const marker = selected ? pins.get(selected)?.marker : undefined;\n if (marker && selected !== previous) {\n expandTo(marker);\n }\n },\n locate(location: Coordinates) {\n map.panTo(location);\n },\n destroy() {\n fitListener?.remove();\n stopExpanding?.();\n clusterer?.clearMarkers(true);\n clusterer?.setMap(null);\n for (const { marker } of pins.values()) {\n marker.setMap(null);\n marker.element.onclick = null;\n }\n pins.clear();\n shopperPin?.setMap(null);\n route?.setMap(null);\n maps.event.clearInstanceListeners(map);\n element.replaceChildren();\n },\n };\n}\n"],
5
4
  "mappings": "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",
6
5
  "names": ["key", "options", "button", "marker"]
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/branch-map/use-map-runtime.ts"],
4
- "sourcesContent": ["import { useEffect, useRef, useState } from \"react\";\n\n/** What a template gets to render when a map fails to load. */\nexport type MapLoadErrorState = {\n /** Why the map failed to load. */\n error: unknown;\n /** Load the map again. */\n retry: () => void;\n};\n\n/** Load a map runtime after mount, retrying on request and cleaning up on unmount. */\nexport function useMapRuntime<T extends { destroy(): void }>(\n browserKey: string | undefined,\n enabled: boolean,\n load: (target: HTMLElement, key: string, signal: AbortSignal) => Promise<T>,\n onLoadError: ((error: unknown) => void) | undefined,\n) {\n const element = useRef<HTMLDivElement>(null);\n const [runtime, setRuntime] = useState<T>();\n const [failure, setFailure] = useState<{ error: unknown }>();\n const [attempt, setAttempt] = useState(0);\n const loader = useRef(load);\n const reportError = useRef(onLoadError);\n useEffect(() => {\n loader.current = load;\n reportError.current = onLoadError;\n }, [load, onLoadError]);\n useEffect(() => {\n if (!browserKey || !element.current || !enabled) {\n return;\n }\n let disposed = false;\n const controller = new AbortController();\n let instance: T | undefined;\n const target = element.current;\n setFailure(undefined);\n void loader\n .current(target, browserKey, controller.signal)\n .then((map) => {\n instance = map;\n if (disposed) {\n map.destroy();\n } else {\n setRuntime(map);\n }\n })\n .catch((error: unknown) => {\n if (!disposed) {\n setFailure({ error });\n reportError.current?.(error);\n }\n });\n return () => {\n disposed = true;\n controller.abort();\n instance?.destroy();\n setRuntime(undefined);\n };\n }, [browserKey, enabled, attempt]);\n return {\n element,\n runtime,\n failure,\n retry: () => setAttempt((count) => count + 1),\n };\n}\n"],
5
4
  "mappings": ";AAAA,SAAS,WAAW,QAAQ,gBAAgB;AAWrC,SAAS,cACd,YACA,SACA,MACA,aACA;AACA,QAAM,UAAU,OAAuB,IAAI;AAC3C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAY;AAC1C,QAAM,CAAC,SAAS,UAAU,IAAI,SAA6B;AAC3D,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,CAAC;AACxC,QAAM,SAAS,OAAO,IAAI;AAC1B,QAAM,cAAc,OAAO,WAAW;AACtC,YAAU,MAAM;AACd,WAAO,UAAU;AACjB,gBAAY,UAAU;AAAA,EACxB,GAAG,CAAC,MAAM,WAAW,CAAC;AACtB,YAAU,MAAM;AACd,QAAI,CAAC,cAAc,CAAC,QAAQ,WAAW,CAAC,SAAS;AAC/C;AAAA,IACF;AACA,QAAI,WAAW;AACf,UAAM,aAAa,IAAI,gBAAgB;AACvC,QAAI;AACJ,UAAM,SAAS,QAAQ;AACvB,eAAW,MAAS;AACpB,SAAK,OACF,QAAQ,QAAQ,YAAY,WAAW,MAAM,EAC7C,KAAK,CAAC,QAAQ;AACb,iBAAW;AACX,UAAI,UAAU;AACZ,YAAI,QAAQ;AAAA,MACd,OAAO;AACL,mBAAW,GAAG;AAAA,MAChB;AAAA,IACF,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAI,CAAC,UAAU;AACb,mBAAW,EAAE,MAAM,CAAC;AACpB,oBAAY,UAAU,KAAK;AAAA,MAC7B;AAAA,IACF,CAAC;AACH,WAAO,MAAM;AACX,iBAAW;AACX,iBAAW,MAAM;AACjB,gBAAU,QAAQ;AAClB,iBAAW,MAAS;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,YAAY,SAAS,OAAO,CAAC;AACjC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,MAAM,WAAW,CAAC,UAAU,QAAQ,CAAC;AAAA,EAC9C;AACF;",
6
5
  "names": []
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/cart/index.tsx"],
4
- "sourcesContent": ["\"use client\";\nimport type {\n CartFix,\n CartIssue,\n CartLine,\n CartLineInput,\n Quote,\n} from \"@zappie/sdk\";\nimport { useCart, useCartCatalogue, useZappie } from \"@zappie/sdk/react\";\nimport {\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useState,\n} from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { Quantity, type QuantityProps } from \"../item-form/quantity\";\ntype CartContext = ReturnType<typeof useCart>;\nconst Context = createContext<CartContext | null>(null);\nfunction useContextValue() {\n const value = useContext(Context);\n if (!value) {\n throw new Error(\"Cart parts require Cart.Root\");\n }\n return value;\n}\nfunction Root(props: PartProps<CartContext>) {\n const state = useCart();\n return (\n <Context.Provider value={state}>\n {part(\"div\", props, state, {\n \"data-state\": !state.isHydrated\n ? \"loading\"\n : state.issues.some((issue) => issue.severity === \"blocking\")\n ? \"invalid\"\n : \"valid\",\n })}\n </Context.Provider>\n );\n}\ntype LineContext = {\n line: CartLine;\n item: import(\"@zappie/sdk\").Item | undefined;\n edit: (patch: Partial<Omit<CartLineInput, \"itemId\">>) => CartLine;\n remove: () => void;\n};\nconst Line = forwardRef<\n HTMLElement,\n PartProps<LineContext> & { line: CartLine }\n>(function Line({ line, ...props }, ref) {\n const cart = useContextValue();\n const item = useCartCatalogue()?.items[line.itemId];\n const current = cart.lines.find((candidate) => candidate.id === line.id);\n if (!current) {\n return null;\n }\n const context = {\n line: current,\n item,\n edit: (patch: Partial<Omit<CartLineInput, \"itemId\">>) =>\n cart.actions.update(line.id, patch),\n remove: () => cart.actions.remove(line.id),\n };\n return part(\n \"div\",\n props,\n context,\n {\n \"data-zappie-cart-line\": line.id,\n \"data-invalid\": cart.issues.some(\n (issue) => issue.lineId === line.id && issue.severity === \"blocking\",\n )\n ? \"\"\n : undefined,\n },\n ref,\n );\n});\nfunction CartQuantity({\n lineId,\n ...props\n}: Omit<QuantityProps, \"value\" | \"defaultValue\" | \"onValueChange\"> & {\n lineId: string;\n}) {\n const cart = useContextValue();\n const line = cart.lines.find((candidate) => candidate.id === lineId);\n const item = useCartCatalogue()?.items[line?.itemId ?? \"\"];\n if (!line) {\n return null;\n }\n const step = item?.quantity.step ?? 1;\n const min = Math.ceil((item?.quantity.min ?? 1) / step) * step;\n return (\n <Quantity.Root\n {...props}\n value={line.quantity}\n min={min}\n max={Math.min(item?.quantity.max ?? 99, 99)}\n step={step}\n onValueChange={(quantity) => cart.actions.update(lineId, { quantity })}\n />\n );\n}\nconst Remove = forwardRef<HTMLElement, PartProps & { lineId: string }>(\n function Remove({ lineId, ...props }, ref) {\n const cart = useContextValue();\n return part(\n \"button\",\n props,\n {},\n { type: \"button\", onClick: () => cart.actions.remove(lineId) },\n ref,\n );\n },\n);\nconst Empty = forwardRef<HTMLElement, PartProps>(function Empty(props, ref) {\n const cart = useContextValue();\n return cart.isHydrated && !cart.lines.length\n ? part(\"div\", props, {}, {}, ref)\n : null;\n});\nconst Issues = forwardRef<\n HTMLElement,\n PartProps<{\n issues: readonly CartIssue[];\n applyFix: (issue: CartIssue, fix?: CartFix) => void;\n }>\n>(function Issues(props, ref) {\n const cart = useContextValue();\n return part(\n \"div\",\n props,\n { issues: cart.issues, applyFix: cart.actions.applyFix },\n {\n \"aria-live\": \"polite\",\n \"data-state\": cart.issues.length ? \"invalid\" : \"valid\",\n },\n ref,\n );\n});\n/**\n * Composable cart parts for lines, quantity changes, removal, empty state and validation issues.\n * Wrap the parts in Cart.Root inside your ZappieProvider.\n */\nexport const Cart = {\n Root,\n Line,\n Quantity: CartQuantity,\n Remove,\n Empty,\n Issues,\n};\n/**\n * The quote and its display status: loading, stale, ready or offline.\n */\nexport type TotalsState = {\n quote?: Quote | undefined;\n status: \"loading\" | \"stale\" | \"ready\" | \"offline\";\n};\n/**\n * Expose the cart quote and its freshness or network status to your render function.\n * Subscribes to the cart and quote failures through your ZappieProvider.\n */\nexport const Totals = forwardRef<HTMLElement, PartProps<TotalsState>>(\n function Totals(props, ref) {\n const cart = useCart();\n const client = useZappie();\n const [failure, setFailure] = useState<\"offline\" | \"stale\" | null>(null);\n const [expired, setExpired] = useState(false);\n const quote = \"quote\" in cart.state ? cart.state.quote : undefined;\n useEffect(\n () =>\n client.on(\"quote_failed\", ({ code }) =>\n setFailure(\n code === \"network_error\" || code === \"timeout\"\n ? \"offline\"\n : \"stale\",\n ),\n ),\n [client],\n );\n useEffect(() => {\n if (cart.state.status === \"quoting\" || quote) {\n setFailure(null);\n }\n setExpired(Boolean(quote && Date.parse(quote.expiresAt) <= Date.now()));\n if (!quote) {\n return;\n }\n const timeout = setTimeout(\n () => setExpired(true),\n Math.max(\n 0,\n Math.min(2147483647, Date.parse(quote.expiresAt) - Date.now()),\n ),\n );\n return () => clearTimeout(timeout);\n }, [quote, cart.state.status]);\n // The SDK re-quotes shortly after every change, so a cart with lines and\n // no quote yet is waiting for one rather than stale.\n const pending =\n cart.state.status === \"ready\" && !quote && cart.lines.length > 0;\n const status: TotalsState[\"status\"] =\n failure === \"offline\"\n ? \"offline\"\n : cart.state.status === \"quoting\" || !cart.isHydrated\n ? \"loading\"\n : failure === \"stale\" || expired\n ? \"stale\"\n : pending\n ? \"loading\"\n : !quote\n ? \"stale\"\n : \"ready\";\n return part(\n \"div\",\n props,\n { quote, status },\n {\n \"aria-live\": \"polite\",\n \"aria-busy\": status === \"loading\",\n \"data-state\":\n status === \"ready\"\n ? \"valid\"\n : status === \"offline\"\n ? \"stale\"\n : status,\n },\n ref,\n );\n },\n);\n"],
5
4
  "mappings": "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",
6
5
  "names": ["Line", "Remove", "Empty", "Issues", "Totals"]
7
6
  }
@@ -20,4 +20,5 @@ type ParseRichText = (html: string) => ParsedRichTextNode[];
20
20
  */
21
21
  declare const createSanitizeRichText: (parseRichText: ParseRichText) => (html?: string | null) => string;
22
22
 
23
- export { type ParsedRichTextNode as P, type ParseRichText as a, createSanitizeRichText as c };
23
+ export { createSanitizeRichText as c };
24
+ export type { ParsedRichTextNode as P, ParseRichText as a };
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/deferred/index.tsx"],
4
- "sourcesContent": ["\"use client\";\nimport {\n Fragment,\n useEffect,\n useMemo,\n useState,\n type ComponentType,\n type ReactElement,\n type ReactNode,\n} from \"react\";\n\n/** Props for a deferred component: its own props plus `open`, which the helper reads and passes on. */\nexport type DeferredProps<P> = Omit<P, \"open\"> & {\n /**\n * Load the code if needed and render the component. Before the first open\n * it renders nothing; after it, the component stays mounted and receives\n * `open`, so it can animate out and return focus when it closes.\n */\n open: boolean;\n /** Shown while `open` is true and the code is still loading, for inline parts that should hold their place. */\n fallback?: ReactNode;\n /**\n * Shown while `open` is true and the code failed to load. Call `retry` to load it again.\n * A later `preload()`, such as the next tap on the trigger, also retries and renders on success.\n */\n failed?: (retry: () => void) => ReactNode;\n};\n\n/** Options for `preloadWhenIdle`. */\nexport type DeferredIdleOptions = {\n /** Start the load after this many milliseconds even if the browser never goes idle. */\n timeout?: number;\n};\n\n/**\n * A component whose code loads on demand and renders without suspending.\n * Create it once at module scope with `deferred()`.\n */\nexport type Deferred<P> = {\n /**\n * Start loading the code. Repeated calls share one load. A failed load is\n * forgotten, so the next call tries again.\n * @returns The loaded component, or the load error.\n */\n preload(): Promise<ComponentType<P>>;\n /**\n * Load the code once the browser is idle. Use it only for small chunks.\n * Does nothing on the server.\n * @returns A function that cancels the scheduled load.\n */\n preloadWhenIdle(options?: DeferredIdleOptions): () => void;\n /**\n * Renders nothing until `open` is first true and the code is in. When\n * `open` turns true it loads the code and renders as soon as it arrives. If\n * the load fails it shows `failed` and renders once a retry or a later\n * `preload()` succeeds. It never suspends. After the first open the component stays mounted and\n * every prop passes through, `open` included, so it can run its own exit.\n */\n Component: (props: DeferredProps<P>) => ReactElement | null;\n};\n\n/** Event handlers that start loading a deferred component when a shopper reaches for its trigger. */\nexport type IntentPreloadHandlers = {\n onPointerEnter: () => void;\n onPointerDown: () => void;\n onFocus: () => void;\n};\n\n/** What a deferred load resolves to: a component, or a module whose default export is one. */\nexport type DeferredModule =\n ComponentType<never> | { default: ComponentType<never> };\n\n/** The props of the component a deferred load resolves to. */\nexport type DeferredModuleProps<T> = T extends {\n default: ComponentType<infer P>;\n}\n ? P\n : T extends ComponentType<infer P>\n ? P\n : never;\n\ntype AnyComponent = ComponentType<Record<string, unknown>>;\n\nfunction component(value: DeferredModule): AnyComponent {\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"default\" in value &&\n !(\"$$typeof\" in value)\n ) {\n return value.default as AnyComponent;\n }\n return value as AnyComponent;\n}\n\nconst ignore = () => {};\n\n/**\n * Load a component's code on demand without `React.lazy` or `<Suspense>`.\n * React holds a suspended boundary's first reveal for about 300 ms, so a\n * lazy overlay opens late. A deferred one renders on the frame its code\n * arrives. Pass a module whose default export is the component, or resolve\n * to the component itself.\n *\n * @example\n * const Bag = deferred(() => import(\"./bag-sheet\"));\n * const Finder = deferred(() => import(\"./finder\").then((m) => m.Finder));\n * <Bag.Component open={open} onClose={close} />\n */\nexport function deferred<T extends DeferredModule>(\n load: () => Promise<T>,\n): Deferred<DeferredModuleProps<T>> {\n type P = DeferredModuleProps<T>;\n let loaded: AnyComponent | undefined;\n let pending: Promise<AnyComponent> | undefined;\n // Mounted instances that are waiting for the code, told when any load succeeds.\n const waiting = new Set<(value: AnyComponent) => void>();\n\n const preload = (): Promise<AnyComponent> => {\n if (loaded) {\n return Promise.resolve(loaded);\n }\n if (!pending) {\n // Starts the import now. A load that throws, or a module whose component\n // cannot be read, becomes a rejection the next call retries.\n const attempt = new Promise<T>((resolve) => resolve(load()))\n .then(component)\n .then(\n (result) => {\n loaded = result;\n for (const notify of waiting) {\n notify(result);\n }\n return result;\n },\n (error: unknown) => {\n // Forget the failure so the next open or preload tries again.\n if (pending === attempt) {\n pending = undefined;\n }\n throw error;\n },\n );\n // Callers may ignore the promise; the component reports nothing either.\n attempt.catch(ignore);\n pending = attempt;\n }\n return pending;\n };\n\n const preloadWhenIdle = (options: DeferredIdleOptions = {}) => {\n if (typeof window === \"undefined\" || loaded) {\n return ignore;\n }\n const start = () => {\n preload().catch(ignore);\n };\n if (typeof window.requestIdleCallback === \"function\") {\n const handle = window.requestIdleCallback(\n start,\n options.timeout === undefined\n ? undefined\n : { timeout: options.timeout },\n );\n return () => window.cancelIdleCallback(handle);\n }\n const handle = window.setTimeout(start, options.timeout ?? 1);\n return () => window.clearTimeout(handle);\n };\n\n function DeferredComponent({\n fallback = null,\n failed,\n ...props\n }: DeferredProps<P>) {\n const { open } = props;\n const [ready, setReady] = useState<AnyComponent | undefined>(() => loaded);\n const [opened, setOpened] = useState<boolean>(open);\n // A failed load shows `failed` until a retry or a new open starts another.\n const [failure, setFailure] = useState(false);\n const [retries, setRetries] = useState(0);\n if (open && !opened) {\n setOpened(true);\n }\n const Loaded = ready ?? loaded;\n useEffect(() => {\n if (Loaded) {\n return;\n }\n const notify = (value: AnyComponent) => setReady(() => value);\n waiting.add(notify);\n return () => {\n waiting.delete(notify);\n };\n }, [Loaded]);\n useEffect(() => {\n if (!open || Loaded) {\n return;\n }\n let active = true;\n setFailure(false);\n preload().then(\n (value) => {\n if (active) {\n setReady(() => value);\n }\n },\n () => {\n if (active) {\n setFailure(true);\n }\n },\n );\n return () => {\n active = false;\n };\n }, [open, Loaded, retries]);\n if (!(open || opened)) {\n return null;\n }\n if (!Loaded) {\n if (!open) {\n return null;\n }\n return (\n <Fragment>\n {failure && failed\n ? failed(() => setRetries((count) => count + 1))\n : fallback}\n </Fragment>\n );\n }\n return <Loaded {...(props as Record<string, unknown>)} />;\n }\n DeferredComponent.displayName = \"Deferred\";\n\n return {\n // The load resolved to the component typed by T.\n preload: preload as () => Promise<ComponentType<P>>,\n preloadWhenIdle,\n Component: DeferredComponent,\n };\n}\n\n/**\n * Start loading a deferred component when the shopper points at, presses or\n * focuses its trigger, so the code is usually in by the click. Spread the\n * result on the trigger.\n *\n * @example\n * const intent = useIntentPreload(Bag);\n * <button {...intent} onClick={openBag}>Bag</button>\n */\nexport function useIntentPreload(target: {\n preload(): Promise<unknown>;\n}): IntentPreloadHandlers {\n return useMemo(() => {\n const start = () => {\n target.preload().catch(ignore);\n };\n return { onPointerEnter: start, onPointerDown: start, onFocus: start };\n }, [target]);\n}\n"],
5
4
  "mappings": "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",
6
5
  "names": ["handle"]
7
6
  }
@@ -2,11 +2,12 @@ import * as react from 'react';
2
2
  import { AllHTMLAttributes, ReactNode, Ref } from 'react';
3
3
  import { Dialog } from '@base-ui/react/dialog';
4
4
 
5
+ type PartOwnedAttributes = "children" | "defaultValue" | "value";
5
6
  /**
6
7
  * HTML attributes shared by foundation parts, with optional render-function children.
7
8
  * Set asChild to merge the part props and ref into a single child element.
8
9
  */
9
- type PartProps<S = Record<string, never>> = Omit<AllHTMLAttributes<HTMLElement>, "children" | "defaultValue" | "value"> & {
10
+ type PartProps<S = Record<string, never>> = Omit<AllHTMLAttributes<HTMLElement>, PartOwnedAttributes> & {
10
11
  asChild?: boolean;
11
12
  children?: ReactNode | ((state: S) => ReactNode);
12
13
  };
@@ -41,14 +42,14 @@ declare function useEngineParts(): Parts;
41
42
  declare function Surface(props: SurfaceProps & {
42
43
  children?: ReactNode;
43
44
  }): react.JSX.Element;
44
- declare const EngineTitle: react.ForwardRefExoticComponent<Omit<react.AllHTMLAttributes<HTMLElement>, "children" | "defaultValue" | "value"> & {
45
+ declare const EngineTitle: react.ForwardRefExoticComponent<Omit<react.AllHTMLAttributes<HTMLElement>, PartOwnedAttributes> & {
45
46
  asChild?: boolean;
46
47
  children?: ReactNode | ((state: unknown) => ReactNode);
47
48
  } & {
48
49
  id?: string | undefined;
49
50
  state: unknown;
50
51
  } & react.RefAttributes<HTMLElement>>;
51
- declare const EngineClose: react.ForwardRefExoticComponent<Omit<react.AllHTMLAttributes<HTMLElement>, "children" | "defaultValue" | "value"> & {
52
+ declare const EngineClose: react.ForwardRefExoticComponent<Omit<react.AllHTMLAttributes<HTMLElement>, PartOwnedAttributes> & {
52
53
  asChild?: boolean;
53
54
  children?: ReactNode | ((state: unknown) => ReactNode);
54
55
  } & {
@@ -56,4 +57,5 @@ declare const EngineClose: react.ForwardRefExoticComponent<Omit<react.AllHTMLAtt
56
57
  state: unknown;
57
58
  } & react.RefAttributes<HTMLElement>>;
58
59
 
59
- export { EngineClose as E, type PartProps as P, type SwipeDirection as S, type Presentation as a, EngineTitle as b, Surface as c, type SurfaceProps as d, useEngineParts as u };
60
+ export { EngineClose as E, EngineTitle as c, Surface as d, useEngineParts as u };
61
+ export type { PartOwnedAttributes as P, SwipeDirection as S, PartProps as a, Presentation as b, SurfaceProps as e };
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/fulfilment/address-picker.tsx"],
4
- "sourcesContent": ["\"use client\";\nimport type { DeliveryAddress } from \"@zappie/sdk\";\nimport {\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useState,\n type ChangeEvent,\n} from \"react\";\nimport {\n countAnnouncement,\n useCombobox,\n withLiveRegion,\n} from \"../internal/combobox\";\nimport { composeRefs, part, type PartProps } from \"../internal/slot\";\nimport { useControllableState } from \"../internal/use-controllable-state\";\n/**\n * A labelled address suggestion with an ID and the delivery address selected by the shopper.\n */\nexport type AddressSuggestion = {\n id: string;\n label: string;\n address: DeliveryAddress;\n};\n/**\n * Address picker state with query controls, combobox navigation, suggestions and request status.\n */\nexport type AddressState = ReturnType<typeof useCombobox<AddressSuggestion>> & {\n query: string;\n setQuery: (query: string) => void;\n suggestions: AddressSuggestion[];\n status: \"loading\" | \"ready\" | \"error\";\n error?: unknown;\n};\nconst Context = createContext<AddressState | null>(null);\nfunction useAddress() {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\"Address parts require Address.Picker\");\n }\n return ctx;\n}\nfunction Picker({\n getSuggestions,\n onSelect,\n query,\n defaultQuery = \"\",\n onQueryChange,\n debounceMs = 200,\n announcement = countAnnouncement,\n ...props\n}: PartProps<AddressState> & {\n /** Screen-reader text for the suggestion count. Override it for your language. */\n announcement?: (count: number) => string;\n getSuggestions: (\n query: string,\n signal?: AbortSignal,\n ) => Promise<AddressSuggestion[]>;\n onSelect: (suggestion: AddressSuggestion) => void;\n query?: string;\n defaultQuery?: string;\n onQueryChange?: (query: string) => void;\n debounceMs?: number;\n}) {\n const [value, setValue] = useControllableState({\n value: query,\n defaultValue: defaultQuery,\n onChange: onQueryChange,\n });\n const [response, setResponse] = useState<{\n query: string;\n suggestions: AddressSuggestion[];\n status: AddressState[\"status\"];\n error?: unknown;\n }>({ query: \"\", suggestions: [], status: \"ready\" });\n const suggestions = response.query === value ? response.suggestions : [];\n const combo = useCombobox(suggestions, (suggestion) => {\n setValue(suggestion.label);\n onSelect(suggestion);\n });\n useEffect(() => {\n if (!value.trim()) {\n setResponse({ query: value, suggestions: [], status: \"ready\" });\n return;\n }\n let live = true;\n const abort = new AbortController();\n setResponse({ query: value, suggestions: [], status: \"loading\" });\n const timer = setTimeout(() => {\n void Promise.resolve()\n .then(() => getSuggestions(value, abort.signal))\n .then(\n (items) => {\n if (live) {\n setResponse({\n query: value,\n suggestions: items,\n status: \"ready\",\n });\n }\n },\n (error) => {\n if (live) {\n setResponse({\n query: value,\n suggestions: [],\n status: \"error\",\n error,\n });\n }\n },\n );\n }, debounceMs);\n return () => {\n live = false;\n abort.abort();\n clearTimeout(timer);\n };\n }, [value, getSuggestions, debounceMs]);\n const setQuery = (next: string) => {\n setValue(next);\n combo.setOpen(true);\n combo.setActiveIndex(-1);\n };\n const state = {\n ...combo,\n query: value,\n setQuery,\n suggestions,\n status: response.status,\n error: response.error,\n };\n return (\n <Context.Provider value={state}>\n {part(\n \"div\",\n withLiveRegion(\n props,\n combo.isOpen && value.trim() && response.status === \"ready\"\n ? announcement(suggestions.length)\n : \"\",\n ) as typeof props,\n state,\n {\n \"data-state\":\n response.status === \"error\"\n ? \"invalid\"\n : response.status === \"loading\"\n ? \"loading\"\n : \"valid\",\n onBlur: (event: {\n currentTarget: HTMLElement;\n relatedTarget: Node | null;\n }) => {\n if (!event.currentTarget.contains(event.relatedTarget)) {\n combo.setOpen(false);\n }\n },\n },\n )}\n </Context.Provider>\n );\n}\nconst Input = forwardRef<HTMLElement, PartProps<AddressState>>(\n function Input(props, ref) {\n const state = useAddress();\n return part(\n \"input\",\n props,\n state,\n {\n ...state.inputProps,\n type: \"text\",\n autoComplete: \"street-address\",\n \"data-zappie-address-input\": \"\",\n value: state.query,\n onChange: (event: ChangeEvent<HTMLInputElement>) =>\n state.setQuery(event.target.value),\n },\n ref,\n );\n },\n);\nconst Suggestions = forwardRef<HTMLElement, PartProps<AddressState>>(\n function Suggestions(props, ref) {\n const state = useAddress();\n return part(\n \"div\",\n props,\n state,\n { ...state.listProps, \"aria-busy\": state.status === \"loading\" },\n composeRefs(ref, (node) => {\n if (node?.id && node.id !== state.listProps.id) {\n state.setListId(node.id);\n }\n }),\n );\n },\n);\nconst Suggestion = forwardRef<\n HTMLElement,\n PartProps<AddressState> & { index: number }\n>(function Suggestion({ index, ...props }, ref) {\n const state = useAddress();\n return part(\n \"div\",\n props,\n state,\n state.getOptionProps(index),\n composeRefs(ref, (node) => {\n if (node?.id && node.id !== state.getOptionProps(index).id) {\n state.registerOption(index, node.id);\n }\n }),\n );\n});\n/**\n * Composable address picker parts with debounced suggestions and keyboard navigation.\n * Supply getSuggestions and onSelect to Address.Picker. It cancels outdated requests.\n */\nexport const Address = { Picker, Input, Suggestions, Suggestion };\n"],
5
4
  "mappings": "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",
6
5
  "names": ["Input", "Suggestions", "Suggestion"]
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/fulfilment/branch-picker.tsx"],
4
- "sourcesContent": ["\"use client\";\nimport type { Branch as BranchModel } from \"@zappie/sdk\";\nimport { useCart, useStore } from \"@zappie/sdk/react\";\nimport { forwardRef, useState } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { getOpenStatus } from \"../status/hours\";\nexport type BranchPickerState = {\n branches: BranchModel[];\n selected?: BranchModel | undefined;\n isOpenNow: (branch: BranchModel) => boolean;\n select: (branch: BranchModel) => Promise<void>;\n isLoading: boolean;\n error?: unknown;\n};\nconst Picker = forwardRef<\n HTMLElement,\n PartProps<BranchPickerState> & {\n branches: BranchModel[];\n value?: string;\n onSelect?: (branch: BranchModel) => void;\n }\n>(function Picker({ branches, value, onSelect, ...props }, ref) {\n const cart = useCart();\n const store = useStore();\n const [isLoading, setLoading] = useState(false);\n const [error, setError] = useState<unknown>();\n const selected = branches.find(\n (branch) => branch.id === (value ?? cart.branchId),\n );\n const select = async (branch: BranchModel) => {\n if (\n isLoading ||\n branch.id === cart.branchId ||\n !branches.some((candidate) => candidate.id === branch.id)\n ) {\n return;\n }\n setLoading(true);\n setError(undefined);\n try {\n await cart.actions.moveTo(branch.id);\n onSelect?.(branch);\n } catch (failure) {\n setError(failure);\n } finally {\n setLoading(false);\n }\n };\n return part(\n \"div\",\n props,\n {\n branches,\n selected,\n select,\n isLoading,\n error,\n isOpenNow: (branch) =>\n getOpenStatus(branch, branch.timezone ?? store.data?.timezone ?? \"UTC\")\n .isOpen,\n },\n {\n role: \"group\",\n \"aria-busy\": isLoading,\n \"data-state\": isLoading ? \"loading\" : error ? \"invalid\" : \"valid\",\n },\n ref,\n );\n});\n/**\n * A branch picker that moves the cart to a selected branch and exposes loading and error state.\n * Use Branch.Picker within your ZappieProvider.\n */\nexport const Branch = { Picker };\n"],
5
4
  "mappings": ";;;AAEA,SAAS,SAAS,gBAAgB;AAClC,SAAS,YAAY,gBAAgB;AACrC,SAAS,YAA4B;AACrC,SAAS,qBAAqB;AAS9B,IAAM,SAAS,WAOb,SAASA,QAAO,EAAE,UAAU,OAAO,UAAU,GAAG,MAAM,GAAG,KAAK;AAC9D,QAAM,OAAO,QAAQ;AACrB,QAAM,QAAQ,SAAS;AACvB,QAAM,CAAC,WAAW,UAAU,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAkB;AAC5C,QAAM,WAAW,SAAS;AAAA,IACxB,CAAC,WAAW,OAAO,QAAQ,SAAS,KAAK;AAAA,EAC3C;AACA,QAAM,SAAS,OAAO,WAAwB;AAC5C,QACE,aACA,OAAO,OAAO,KAAK,YACnB,CAAC,SAAS,KAAK,CAAC,cAAc,UAAU,OAAO,OAAO,EAAE,GACxD;AACA;AAAA,IACF;AACA,eAAW,IAAI;AACf,aAAS,MAAS;AAClB,QAAI;AACF,YAAM,KAAK,QAAQ,OAAO,OAAO,EAAE;AACnC,iBAAW,MAAM;AAAA,IACnB,SAAS,SAAS;AAChB,eAAS,OAAO;AAAA,IAClB,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,CAAC,WACV,cAAc,QAAQ,OAAO,YAAY,MAAM,MAAM,YAAY,KAAK,EACnE;AAAA,IACP;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aAAa;AAAA,MACb,cAAc,YAAY,YAAY,QAAQ,YAAY;AAAA,IAC5D;AAAA,IACA;AAAA,EACF;AACF,CAAC;AAKM,IAAM,SAAS,EAAE,OAAO;",
6
5
  "names": ["Picker"]
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/fulfilment/schedule.tsx"],
4
- "sourcesContent": ["\"use client\";\nimport type { Branch, MenuSchedule } from \"@zappie/sdk\";\nimport { useMenu, useStore } from \"@zappie/sdk/react\";\nimport { forwardRef, useEffect, useMemo, useState } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { useControllableState } from \"../internal/use-controllable-state\";\nimport { getOpenStatus, localParts, openingIntervals } from \"../status/hours\";\n/**\n * A selectable time with an ISO instant, local date and display label including the UTC offset.\n */\nexport type ScheduleSlot = { value: string; label: string; date: string };\n/**\n * Available days and slots, the selected time and controls for choosing ASAP or a scheduled time.\n */\nexport type ScheduleState = {\n days: { date: string; slots: ScheduleSlot[] }[];\n slots: ScheduleSlot[];\n value: string | null;\n setValue: (value: string | null) => void;\n isAsap: boolean;\n canAsap: boolean;\n isValid: boolean;\n};\n/**\n * Generate scheduling slots within branch hours and any supplied menu hours and preorder limits.\n * Returns no slots when the branch does not support scheduling.\n *\n * @param branch - The branch whose hours and scheduling capability apply.\n * @param timezone - The time zone used to interpret opening hours and label slots.\n * @param now - The instant from which notice and scheduling limits are measured.\n * @returns Slots with ISO values, local dates and display labels.\n */\nexport function scheduleSlots(\n branch: Branch,\n timezone: string,\n now: Date,\n {\n intervalMinutes = 30,\n maxDays = 14,\n noticeMinutes = 0,\n menuSchedule,\n }: {\n intervalMinutes?: number | undefined;\n maxDays?: number | undefined;\n noticeMinutes?: number | undefined;\n menuSchedule?: MenuSchedule | undefined;\n } = {},\n) {\n if (\n !Number.isInteger(intervalMinutes) ||\n intervalMinutes < 1 ||\n !Number.isInteger(maxDays) ||\n maxDays < 1 ||\n maxDays > 90 ||\n !Number.isFinite(noticeMinutes) ||\n noticeMinutes < 0\n ) {\n throw new RangeError(\n \"Schedule requires a positive interval, 1\u201390 days and a non-negative notice\",\n );\n }\n if (!branch.fulfilment.scheduling) {\n return [];\n }\n // Pre-order limits apply to pre-order menus only. Other menus carry zeros\n // (the API's default), and a zero horizon would leave nothing to schedule.\n const preorder = menuSchedule?.preorder ? menuSchedule : undefined;\n const days =\n preorder?.preorderMaxDays && preorder.preorderMaxDays > 0\n ? Math.min(maxDays, preorder.preorderMaxDays)\n : maxDays;\n const earliest =\n now.getTime() +\n Math.max(noticeMinutes, (preorder?.preorderNoticeHours ?? 0) * 60) * 60000;\n const latest = now.getTime() + days * 86400000;\n const menuIntervals = menuSchedule?.openingTimes.length\n ? openingIntervals(\n {\n ...branch,\n openingTimes: menuSchedule.openingTimes,\n specialHours: [],\n },\n timezone,\n now,\n days + 1,\n )\n : undefined;\n const slots: ScheduleSlot[] = [];\n for (const { start, end } of openingIntervals(\n branch,\n timezone,\n now,\n days + 1,\n )) {\n const first =\n start +\n Math.max(0, Math.ceil((earliest - start) / (intervalMinutes * 60000))) *\n intervalMinutes *\n 60000;\n for (\n let instant = first;\n instant < end && instant <= latest;\n instant += intervalMinutes * 60000\n ) {\n if (\n menuIntervals &&\n !menuIntervals.some(\n (interval) => interval.start <= instant && interval.end > instant,\n )\n ) {\n continue;\n }\n const date = new Date(instant);\n slots.push({\n value: date.toISOString(),\n date: localParts(date, timezone).date,\n label: new Intl.DateTimeFormat(\"en\", {\n timeZone: timezone,\n hour: \"numeric\",\n minute: \"2-digit\",\n timeZoneName: \"shortOffset\",\n }).format(date),\n });\n }\n }\n return slots;\n}\n/**\n * Expose valid fulfilment slots and ASAP eligibility using branch hours and loaded menu metadata.\n * Use null for ASAP. Requires a ZappieProvider and refreshes the available times each minute.\n */\nexport const Schedule = forwardRef<\n HTMLElement,\n PartProps<ScheduleState> & {\n branch: Branch;\n timezone?: string;\n value?: string | null;\n defaultValue?: string | null;\n onValueChange?: (value: string | null) => void;\n intervalMinutes?: number | undefined;\n maxDays?: number | undefined;\n noticeMinutes?: number;\n }\n>(function Schedule(\n {\n branch,\n timezone: suppliedTimezone,\n value,\n defaultValue = null,\n onValueChange,\n intervalMinutes,\n maxDays,\n noticeMinutes,\n ...props\n },\n ref,\n) {\n const store = useStore();\n const menu = useMenu(branch.id, { metadataOnly: true });\n const timezone =\n suppliedTimezone ?? branch.timezone ?? store.data?.timezone ?? \"UTC\";\n const [now, setNow] = useState(() => new Date());\n useEffect(() => {\n const timer = setTimeout(\n () => setNow(new Date()),\n 60000 - (Date.now() % 60000),\n );\n return () => clearTimeout(timer);\n }, [now]);\n const [selection, setSelection] = useControllableState({\n value,\n defaultValue,\n onChange: onValueChange,\n });\n const slots = useMemo(\n () =>\n menu.isLoading || menu.error\n ? []\n : scheduleSlots(branch, timezone, now, {\n intervalMinutes,\n maxDays,\n noticeMinutes,\n menuSchedule: menu.data?.schedule,\n }),\n [\n branch,\n timezone,\n now,\n intervalMinutes,\n maxDays,\n noticeMinutes,\n menu.data?.schedule,\n menu.isLoading,\n menu.error,\n ],\n );\n const days = Array.from(new Set(slots.map((slot) => slot.date))).map(\n (date) => ({ date, slots: slots.filter((slot) => slot.date === date) }),\n );\n const canAsap =\n !menu.isLoading &&\n !menu.error &&\n !menu.data?.schedule?.preorder &&\n getOpenStatus(branch, timezone, now).isOpen;\n const isValid =\n selection === null\n ? canAsap\n : slots.some((slot) => slot.value === selection);\n const setValue = (next: string | null) => {\n if (next === null ? canAsap : slots.some((slot) => slot.value === next)) {\n setSelection(next);\n }\n };\n return part(\n \"div\",\n props,\n {\n days,\n slots,\n value: selection,\n setValue,\n isAsap: selection === null,\n canAsap,\n isValid,\n },\n {\n role: \"group\",\n \"data-state\": isValid ? \"valid\" : \"invalid\",\n \"data-invalid\": isValid ? undefined : \"\",\n },\n ref,\n );\n});\n"],
5
4
  "mappings": "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",
6
5
  "names": ["Schedule"]
7
6
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/fulfilment/switch.tsx"],
4
- "sourcesContent": ["\"use client\";\nimport { ZappieError, type CartFulfilment } from \"@zappie/sdk\";\nimport { useCart, useStore } from \"@zappie/sdk/react\";\nimport { forwardRef } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { useControllableState } from \"../internal/use-controllable-state\";\nexport type FulfilmentState = {\n options: CartFulfilment[\"type\"][];\n value: CartFulfilment | null;\n setValue: (value: CartFulfilment) => void;\n};\nconst Switch = forwardRef<\n HTMLElement,\n PartProps<FulfilmentState> & {\n value?: CartFulfilment | null;\n defaultValue?: CartFulfilment | null;\n onValueChange?: (value: CartFulfilment | null) => void;\n }\n>(function Switch({ value, defaultValue, onValueChange, ...props }, ref) {\n const cart = useCart();\n const store = useStore();\n const [selection, setSelection] = useControllableState({\n value,\n defaultValue: defaultValue ?? cart.fulfilment,\n onChange: onValueChange,\n });\n const current =\n value === undefined && defaultValue === undefined\n ? cart.fulfilment\n : selection;\n const branch =\n store.data?.branches.find(\n (branch) => branch.id === (cart.branchId ?? current?.branchId),\n ) ??\n store.data?.branches.find((branch) => branch.isDefault) ??\n store.data?.branches[0];\n const available = branch?.fulfilment;\n const options: CartFulfilment[\"type\"][] = [];\n if (available?.pickup) {\n options.push(\"pickup\");\n }\n if (available?.delivery) {\n options.push(\"delivery\");\n }\n if (available?.dineIn) {\n options.push(\"dine_in\");\n }\n const setValue = (next: CartFulfilment) => {\n if (!options.includes(next.type) || next.branchId !== branch?.id) {\n throw new ZappieError(\n \"invalid_request\",\n \"Select an offered fulfilment mode for this branch\",\n );\n }\n cart.actions.setFulfilment(next);\n setSelection(next);\n };\n return part(\n \"div\",\n props,\n { options, value: current, setValue },\n { role: \"group\" },\n ref,\n );\n});\n/**\n * A fulfilment switch that exposes the active branch modes and updates the SDK cart.\n * Selections must use an offered mode and the active branch ID.\n */\nexport const Fulfilment = { Switch };\n"],
5
4
  "mappings": ";;;AACA,SAAS,mBAAwC;AACjD,SAAS,SAAS,gBAAgB;AAClC,SAAS,kBAAkB;AAC3B,SAAS,YAA4B;AACrC,SAAS,4BAA4B;AAMrC,IAAM,SAAS,WAOb,SAASA,QAAO,EAAE,OAAO,cAAc,eAAe,GAAG,MAAM,GAAG,KAAK;AACvE,QAAM,OAAO,QAAQ;AACrB,QAAM,QAAQ,SAAS;AACvB,QAAM,CAAC,WAAW,YAAY,IAAI,qBAAqB;AAAA,IACrD;AAAA,IACA,cAAc,gBAAgB,KAAK;AAAA,IACnC,UAAU;AAAA,EACZ,CAAC;AACD,QAAM,UACJ,UAAU,UAAa,iBAAiB,SACpC,KAAK,aACL;AACN,QAAM,SACJ,MAAM,MAAM,SAAS;AAAA,IACnB,CAACC,YAAWA,QAAO,QAAQ,KAAK,YAAY,SAAS;AAAA,EACvD,KACA,MAAM,MAAM,SAAS,KAAK,CAACA,YAAWA,QAAO,SAAS,KACtD,MAAM,MAAM,SAAS,CAAC;AACxB,QAAM,YAAY,QAAQ;AAC1B,QAAM,UAAoC,CAAC;AAC3C,MAAI,WAAW,QAAQ;AACrB,YAAQ,KAAK,QAAQ;AAAA,EACvB;AACA,MAAI,WAAW,UAAU;AACvB,YAAQ,KAAK,UAAU;AAAA,EACzB;AACA,MAAI,WAAW,QAAQ;AACrB,YAAQ,KAAK,SAAS;AAAA,EACxB;AACA,QAAM,WAAW,CAAC,SAAyB;AACzC,QAAI,CAAC,QAAQ,SAAS,KAAK,IAAI,KAAK,KAAK,aAAa,QAAQ,IAAI;AAChE,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,SAAK,QAAQ,cAAc,IAAI;AAC/B,iBAAa,IAAI;AAAA,EACnB;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,SAAS,OAAO,SAAS,SAAS;AAAA,IACpC,EAAE,MAAM,QAAQ;AAAA,IAChB;AAAA,EACF;AACF,CAAC;AAKM,IAAM,aAAa,EAAE,OAAO;",
6
5
  "names": ["Switch", "branch"]
7
6
  }
@@ -5,4 +5,5 @@ type CloseOptions = {
5
5
  };
6
6
  declare const LOCATION_EVENT = "zappie:location";
7
7
 
8
- export { type CloseOptions as C, LOCATION_EVENT as L, type OverlayQuery as O };
8
+ export { LOCATION_EVENT as L };
9
+ export type { CloseOptions as C, OverlayQuery as O };