@zappie/foundations 0.1.10 → 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.
- package/CHANGELOG.md +14 -0
- package/dist/account/index.js.map +0 -1
- package/dist/branch-map/colors.js.map +0 -1
- package/dist/branch-map/delivery-map.js.map +0 -1
- package/dist/branch-map/delivery-runtime.js.map +0 -1
- package/dist/branch-map/fit.js.map +0 -1
- package/dist/branch-map/geometry.js.map +0 -1
- package/dist/branch-map/index.js.map +0 -1
- package/dist/branch-map/loader.js.map +0 -1
- package/dist/branch-map/markers.js.map +0 -1
- package/dist/branch-map/runtime.js.map +0 -1
- package/dist/branch-map/use-map-runtime.js.map +0 -1
- package/dist/cart/index.js.map +0 -1
- package/dist/deferred/index.js.map +0 -1
- package/dist/fulfilment/address-picker.js.map +0 -1
- package/dist/fulfilment/branch-picker.js.map +0 -1
- package/dist/fulfilment/schedule.js.map +0 -1
- package/dist/fulfilment/switch.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal/combobox.js.map +0 -1
- package/dist/internal/data-attrs.js.map +0 -1
- package/dist/internal/ids.js.map +0 -1
- package/dist/internal/slot.js.map +0 -1
- package/dist/internal/use-controllable-state.js.map +0 -1
- package/dist/item-form/index.js.map +0 -1
- package/dist/item-form/option-group.js.map +0 -1
- package/dist/item-form/options.js.map +0 -1
- package/dist/item-form/quantity.js.map +0 -1
- package/dist/item-form/root.js.map +0 -1
- package/dist/media/image.js.map +0 -1
- package/dist/money.js.map +0 -1
- package/dist/next.js.map +0 -1
- package/dist/otp/input.js.map +0 -1
- package/dist/overlay/engine.js.map +0 -1
- package/dist/overlay/history.js.map +0 -1
- package/dist/overlay/react.js.map +0 -1
- package/dist/payment/methods.js.map +0 -1
- package/dist/rich-text/banners.js.map +0 -1
- package/dist/rich-text/browser.js.map +0 -1
- package/dist/rich-text/core.js.map +0 -1
- package/dist/rich-text/index.js.map +0 -1
- package/dist/search/engine.js.map +0 -1
- package/dist/search/index.js.map +0 -1
- package/dist/status/hours.js.map +0 -1
- package/dist/status/open-status.js.map +0 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +0 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
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
|
+
|
|
3
17
|
## 0.1.10
|
|
4
18
|
|
|
5
19
|
### 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
|
}
|
package/dist/cart/index.js.map
CHANGED
|
@@ -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
|
}
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport { PACKAGE_VERSION } from \"./version\";\n\n/** The installed @zappie/foundations version. */\nexport const FOUNDATIONS_VERSION: string = PACKAGE_VERSION;\nexport { Account, AccountNotConfiguredError } from \"./account\";\nexport type { Customer, SignInOptions } from \"./account\";\nexport { BranchMap, DeliveryMap } from \"./branch-map\";\nexport type {\n BranchMapLoadErrorState,\n BranchMapProps,\n DeliveryMapProps,\n MapLoadErrorState,\n} from \"./branch-map\";\nexport { Cart, Totals } from \"./cart\";\nexport type { TotalsState } from \"./cart\";\nexport { deferred, useIntentPreload } from \"./deferred\";\nexport type {\n Deferred,\n DeferredIdleOptions,\n DeferredProps,\n IntentPreloadHandlers,\n} from \"./deferred\";\nexport { Address } from \"./fulfilment/address-picker\";\nexport type {\n AddressState,\n AddressSuggestion,\n} from \"./fulfilment/address-picker\";\nexport { Branch } from \"./fulfilment/branch-picker\";\nexport { Schedule, scheduleSlots } from \"./fulfilment/schedule\";\nexport type { ScheduleSlot, ScheduleState } from \"./fulfilment/schedule\";\nexport { Fulfilment } from \"./fulfilment/switch\";\nexport type { PartProps } from \"./internal/slot\";\nexport { useControllableState } from \"./internal/use-controllable-state\";\nexport * from \"./item-form\";\nexport { Image } from \"./media/image\";\nexport type { ImageProps } from \"./media/image\";\nexport { Money } from \"./money\";\nexport { Otp } from \"./otp/input\";\nexport { Overlay, useOverlay } from \"./overlay/react\";\nexport { PaymentMethods } from \"./payment/methods\";\nexport type { PaymentMethod } from \"./payment/methods\";\nexport { createSearchIndex, Search } from \"./search\";\nexport type { SearchResult, SearchSource, SearchState } from \"./search\";\nexport {\n formatOpeningHours,\n formatWallClock,\n getOpenStatus,\n parseWallClock,\n} from \"./status/hours\";\nexport { OpenStatus } from \"./status/open-status\";\n"],
|
|
5
4
|
"mappings": ";;;AACA,SAAS,uBAAuB;AAIhC,SAAS,SAAS,iCAAiC;AAEnD,SAAS,WAAW,mBAAmB;AAOvC,SAAS,MAAM,cAAc;AAE7B,SAAS,UAAU,wBAAwB;AAO3C,SAAS,eAAe;AAKxB,SAAS,cAAc;AACvB,SAAS,UAAU,qBAAqB;AAExC,SAAS,kBAAkB;AAE3B,SAAS,4BAA4B;AACrC,cAAc;AACd,SAAS,aAAa;AAEtB,SAAS,aAAa;AACtB,SAAS,WAAW;AACpB,SAAS,SAAS,kBAAkB;AACpC,SAAS,sBAAsB;AAE/B,SAAS,mBAAmB,cAAc;AAE1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AA9CpB,IAAM,sBAA8B;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/internal/combobox.tsx"],
|
|
4
|
-
"sourcesContent": ["import {\n useEffect,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport { usePartId } from \"./ids\";\nexport function useCombobox<T>(\n results: readonly T[],\n onSelect: (item: T) => void,\n) {\n const id = usePartId();\n const [activeIndex, setActiveIndex] = useState(-1);\n const [isOpen, setOpen] = useState(false);\n const [listId, setListId] = useState(`${id}-list`);\n const [optionIds, setOptionIds] = useState<Record<number, string>>({});\n // Reset the highlight when the result set changes size; callers reset on a new query.\n // Keying on the array itself would reset on every re-render with an unmemoised array.\n useEffect(() => setActiveIndex(-1), [results.length]);\n const registerOption = (index: number, id: string) =>\n setOptionIds((previous) =>\n previous[index] === id ? previous : { ...previous, [index]: id },\n );\n const active = activeIndex < results.length ? activeIndex : -1;\n const optionId = (index: number) =>\n optionIds[index] ?? `${id}-option-${index}`;\n const activeId = isOpen && active >= 0 ? optionId(active) : undefined;\n // aria-activedescendant doesn't move focus, so keep the highlighted option visible.\n useEffect(() => {\n if (activeId) {\n document.getElementById(activeId)?.scrollIntoView?.({ block: \"nearest\" });\n }\n }, [activeId]);\n const select = (index: number) => {\n const item = results[index];\n if (item !== undefined) {\n onSelect(item);\n setOpen(false);\n setActiveIndex(-1);\n }\n };\n return {\n setListId,\n registerOption,\n activeIndex: active,\n setActiveIndex,\n isOpen,\n setOpen,\n select,\n inputProps: {\n role: \"combobox\",\n \"aria-autocomplete\": \"list\" as const,\n \"aria-expanded\": isOpen,\n \"aria-controls\": listId,\n \"aria-activedescendant\": activeId,\n onKeyDown: (event: KeyboardEvent) => {\n // Keys pressed while an input method composes text belong to the IME, not the list.\n if (event.nativeEvent.isComposing || event.keyCode === 229) {\n return;\n }\n if (event.key === \"Escape\") {\n event.preventDefault();\n setOpen(false);\n setActiveIndex(-1);\n } else if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n setOpen(true);\n setActiveIndex(\n results.length\n ? active < 0\n ? event.key === \"ArrowDown\"\n ? 0\n : results.length - 1\n : (active +\n (event.key === \"ArrowDown\" ? 1 : -1) +\n results.length) %\n results.length\n : -1,\n );\n } else if (event.key === \"Enter\" && isOpen && active >= 0) {\n event.preventDefault();\n select(active);\n }\n },\n onFocus: () => setOpen(true),\n },\n listProps: { id: listId, role: \"listbox\", hidden: !isOpen },\n getOptionProps: (index: number) => ({\n id: optionId(index),\n role: \"option\",\n \"aria-selected\": active === index,\n \"data-selected\": active === index ? \"\" : undefined,\n onMouseDown: (event: { preventDefault: () => void }) =>\n event.preventDefault(),\n // Inside a drawer or sheet, the overlay engine starts a swipe on any\n // press that isn't a button, link or field, then captures the pointer,\n // so the option's click never fires on touch. Options aren't swipe handles.\n onPointerDown: (event: { stopPropagation: () => void }) =>\n event.stopPropagation(),\n onTouchStart: (event: { stopPropagation: () => void }) =>\n event.stopPropagation(),\n onClick: () => select(index),\n }),\n };\n}\n/** Visually hidden but announced; an accessibility mechanism, not a visual default. */\nconst visuallyHidden: CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n margin: -1,\n padding: 0,\n border: 0,\n overflow: \"hidden\",\n clip: \"rect(0 0 0 0)\",\n whiteSpace: \"nowrap\",\n};\n/** Default English announcement of how many options a combobox is showing. */\nexport function countAnnouncement(count: number) {\n return count === 0\n ? \"No results\"\n : `${count} result${count === 1 ? \"\" : \"s\"} available`;\n}\n/** A polite status region so screen readers hear how many options are listed. */\nexport function LiveRegion({ message }: { message: string }) {\n return (\n <span\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n data-zappie-live=\"\"\n style={visuallyHidden}\n >\n {message}\n </span>\n );\n}\n/** Append the live region to a root's children; with `asChild` the caller owns the single child. */\nexport function withLiveRegion<S>(\n props: { asChild?: boolean | undefined; children?: unknown },\n message: string,\n): typeof props {\n if (props.asChild) {\n return props;\n }\n const children = props.children;\n return {\n ...props,\n children: (state: S) => (\n <>\n {typeof children === \"function\"\n ? (children as (state: S) => ReactNode)(state)\n : (children as ReactNode)}\n <LiveRegion message={message} />\n </>\n ),\n };\n}\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": ["id"]
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/internal/data-attrs.ts"],
|
|
4
|
-
"sourcesContent": ["export function flag(value: unknown): \"\" | undefined {\n return value ? \"\" : undefined;\n}\n"],
|
|
5
4
|
"mappings": ";AAAO,SAAS,KAAK,OAAgC;AACnD,SAAO,QAAQ,KAAK;AACtB;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
package/dist/internal/ids.js.map
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/internal/ids.ts"],
|
|
4
|
-
"sourcesContent": ["import { useId } from \"react\";\nexport function usePartId(provided?: string) {\n const id = useId();\n return provided ?? `zappie-${id.replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n}\n"],
|
|
5
4
|
"mappings": ";AAAA,SAAS,aAAa;AACf,SAAS,UAAU,UAAmB;AAC3C,QAAM,KAAK,MAAM;AACjB,SAAO,YAAY,UAAU,GAAG,QAAQ,mBAAmB,EAAE,CAAC;AAChE;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/internal/slot.tsx"],
|
|
4
|
-
"sourcesContent": ["import {\n Children,\n cloneElement,\n createElement,\n isValidElement,\n type AllHTMLAttributes,\n type ElementType,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from \"react\";\nexport type PartOwnedAttributes = \"children\" | \"defaultValue\" | \"value\";\n/**\n * HTML attributes shared by foundation parts, with optional render-function children.\n * Set asChild to merge the part props and ref into a single child element.\n */\nexport type PartProps<S = Record<string, never>> = Omit<\n AllHTMLAttributes<HTMLElement>,\n PartOwnedAttributes\n> & {\n asChild?: boolean;\n children?: ReactNode | ((state: S) => ReactNode);\n};\nexport function composeRefs<T>(...refs: (Ref<T> | undefined)[]): Ref<T> {\n let cleanups: (() => void)[] = [];\n return (value) => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n cleanups = [];\n if (value === null) {\n return;\n }\n for (const ref of refs) {\n if (typeof ref === \"function\") {\n const cleanup = ref(value);\n cleanups.push(\n typeof cleanup === \"function\" ? cleanup : () => ref(null),\n );\n } else if (ref) {\n ref.current = value;\n cleanups.push(() => {\n ref.current = null;\n });\n }\n }\n };\n}\n/** Consumer handlers run first and may cancel the primitive's action. */\nexport function mergeProps(\n base: Record<string, unknown>,\n consumer: Record<string, unknown>,\n) {\n const result = { ...base, ...consumer };\n if (\n typeof base.className === \"string\" &&\n typeof consumer.className === \"string\"\n ) {\n result.className = `${base.className} ${consumer.className}`;\n }\n if (\n base.style &&\n consumer.style &&\n typeof base.style === \"object\" &&\n typeof consumer.style === \"object\"\n ) {\n result.style = { ...base.style, ...consumer.style };\n }\n for (const key of Object.keys(base)) {\n if (\n /^on[A-Z]/.test(key) &&\n typeof base[key] === \"function\" &&\n typeof consumer[key] === \"function\"\n ) {\n result[key] = (event: { defaultPrevented?: boolean }) => {\n (consumer[key] as (event: unknown) => void)(event);\n if (!event.defaultPrevented) {\n (base[key] as (event: unknown) => void)(event);\n }\n };\n }\n }\n return result;\n}\nexport function part<S>(\n tag: ElementType,\n props: PartProps<S>,\n state: S,\n defaults: Record<string, unknown> = {},\n ref?: Ref<HTMLElement>,\n) {\n const { asChild, children, ...dom } = props;\n const content = typeof children === \"function\" ? children(state) : children;\n const merged = mergeProps(defaults, dom);\n if (asChild || (tag === \"input\" && typeof children === \"function\")) {\n const child = Children.only(content);\n if (!isValidElement(child)) {\n throw new Error(\"asChild requires one React element\");\n }\n const element = child as ReactElement<Record<string, unknown>>;\n // React 18 stores ref on the element; React 19 stores it in props.\n const descriptor = Object.getOwnPropertyDescriptor(element, \"ref\");\n const childRef =\n element.props.ref ??\n (descriptor && \"value\" in descriptor ? descriptor.value : undefined);\n return cloneElement(element, {\n ...mergeProps(merged, element.props),\n ref: composeRefs(ref, childRef as Ref<HTMLElement>),\n });\n }\n return createElement(tag, { ...merged, ref }, content);\n}\n/** A part's caller-supplied id, whether passed as a prop or set on its `asChild` element. */\nexport function partId(props: {\n id?: string | undefined;\n asChild?: boolean | undefined;\n children?: unknown;\n}): string | undefined {\n if (props.id) {\n return props.id;\n }\n if (props.asChild && isValidElement(props.children)) {\n const id = (props.children.props as { id?: unknown }).id;\n return typeof id === \"string\" ? id : undefined;\n }\n return undefined;\n}\n"],
|
|
5
4
|
"mappings": ";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AAaA,SAAS,eAAkB,MAAsC;AACtE,MAAI,WAA2B,CAAC;AAChC,SAAO,CAAC,UAAU;AAChB,eAAW,WAAW,UAAU;AAC9B,cAAQ;AAAA,IACV;AACA,eAAW,CAAC;AACZ,QAAI,UAAU,MAAM;AAClB;AAAA,IACF;AACA,eAAW,OAAO,MAAM;AACtB,UAAI,OAAO,QAAQ,YAAY;AAC7B,cAAM,UAAU,IAAI,KAAK;AACzB,iBAAS;AAAA,UACP,OAAO,YAAY,aAAa,UAAU,MAAM,IAAI,IAAI;AAAA,QAC1D;AAAA,MACF,WAAW,KAAK;AACd,YAAI,UAAU;AACd,iBAAS,KAAK,MAAM;AAClB,cAAI,UAAU;AAAA,QAChB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,WACd,MACA,UACA;AACA,QAAM,SAAS,EAAE,GAAG,MAAM,GAAG,SAAS;AACtC,MACE,OAAO,KAAK,cAAc,YAC1B,OAAO,SAAS,cAAc,UAC9B;AACA,WAAO,YAAY,GAAG,KAAK,SAAS,IAAI,SAAS,SAAS;AAAA,EAC5D;AACA,MACE,KAAK,SACL,SAAS,SACT,OAAO,KAAK,UAAU,YACtB,OAAO,SAAS,UAAU,UAC1B;AACA,WAAO,QAAQ,EAAE,GAAG,KAAK,OAAO,GAAG,SAAS,MAAM;AAAA,EACpD;AACA,aAAW,OAAO,OAAO,KAAK,IAAI,GAAG;AACnC,QACE,WAAW,KAAK,GAAG,KACnB,OAAO,KAAK,GAAG,MAAM,cACrB,OAAO,SAAS,GAAG,MAAM,YACzB;AACA,aAAO,GAAG,IAAI,CAAC,UAA0C;AACvD,QAAC,SAAS,GAAG,EAA+B,KAAK;AACjD,YAAI,CAAC,MAAM,kBAAkB;AAC3B,UAAC,KAAK,GAAG,EAA+B,KAAK;AAAA,QAC/C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AACO,SAAS,KACd,KACA,OACA,OACA,WAAoC,CAAC,GACrC,KACA;AACA,QAAM,EAAE,SAAS,UAAU,GAAG,IAAI,IAAI;AACtC,QAAM,UAAU,OAAO,aAAa,aAAa,SAAS,KAAK,IAAI;AACnE,QAAM,SAAS,WAAW,UAAU,GAAG;AACvC,MAAI,WAAY,QAAQ,WAAW,OAAO,aAAa,YAAa;AAClE,UAAM,QAAQ,SAAS,KAAK,OAAO;AACnC,QAAI,CAAC,eAAe,KAAK,GAAG;AAC1B,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AACA,UAAM,UAAU;AAEhB,UAAM,aAAa,OAAO,yBAAyB,SAAS,KAAK;AACjE,UAAM,WACJ,QAAQ,MAAM,QACb,cAAc,WAAW,aAAa,WAAW,QAAQ;AAC5D,WAAO,aAAa,SAAS;AAAA,MAC3B,GAAG,WAAW,QAAQ,QAAQ,KAAK;AAAA,MACnC,KAAK,YAAY,KAAK,QAA4B;AAAA,IACpD,CAAC;AAAA,EACH;AACA,SAAO,cAAc,KAAK,EAAE,GAAG,QAAQ,IAAI,GAAG,OAAO;AACvD;AAEO,SAAS,OAAO,OAIA;AACrB,MAAI,MAAM,IAAI;AACZ,WAAO,MAAM;AAAA,EACf;AACA,MAAI,MAAM,WAAW,eAAe,MAAM,QAAQ,GAAG;AACnD,UAAM,KAAM,MAAM,SAAS,MAA2B;AACtD,WAAO,OAAO,OAAO,WAAW,KAAK;AAAA,EACvC;AACA,SAAO;AACT;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/internal/use-controllable-state.ts"],
|
|
4
|
-
"sourcesContent": ["import { useCallback, useRef, useState } from \"react\";\n/**\n * Manage either a supplied value or internal state initialized from defaultValue.\n * The setter accepts a value or updater and calls onChange only when Object.is detects a change.\n *\n * @returns The current value and a stable setter. A defined value keeps state controlled by you.\n */\nexport function useControllableState<T>({\n value,\n defaultValue,\n onChange,\n}: {\n value?: T | undefined;\n defaultValue: T;\n onChange?: ((value: T) => void) | undefined;\n}) {\n const [internal, setInternal] = useState(defaultValue);\n const current = value === undefined ? internal : value;\n const latest = useRef({ current, onChange, controlled: value !== undefined });\n latest.current = { current, onChange, controlled: value !== undefined };\n const set = useCallback((next: T | ((previous: T) => T)) => {\n const snapshot = latest.current;\n const resolved =\n typeof next === \"function\"\n ? (next as (previous: T) => T)(snapshot.current)\n : next;\n if (Object.is(resolved, snapshot.current)) {\n return;\n }\n if (!snapshot.controlled) {\n latest.current.current = resolved;\n setInternal(resolved);\n }\n snapshot.onChange?.(resolved);\n }, []);\n return [current, set] as const;\n}\n"],
|
|
5
4
|
"mappings": ";AAAA,SAAS,aAAa,QAAQ,gBAAgB;AAOvC,SAAS,qBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,YAAY;AACrD,QAAM,UAAU,UAAU,SAAY,WAAW;AACjD,QAAM,SAAS,OAAO,EAAE,SAAS,UAAU,YAAY,UAAU,OAAU,CAAC;AAC5E,SAAO,UAAU,EAAE,SAAS,UAAU,YAAY,UAAU,OAAU;AACtE,QAAM,MAAM,YAAY,CAAC,SAAmC;AAC1D,UAAM,WAAW,OAAO;AACxB,UAAM,WACJ,OAAO,SAAS,aACX,KAA4B,SAAS,OAAO,IAC7C;AACN,QAAI,OAAO,GAAG,UAAU,SAAS,OAAO,GAAG;AACzC;AAAA,IACF;AACA,QAAI,CAAC,SAAS,YAAY;AACxB,aAAO,QAAQ,UAAU;AACzB,kBAAY,QAAQ;AAAA,IACtB;AACA,aAAS,WAAW,QAAQ;AAAA,EAC9B,GAAG,CAAC,CAAC;AACL,SAAO,CAAC,SAAS,GAAG;AACtB;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/item-form/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { Add, Options } from \"./options\";\nimport { Root } from \"./root\";\n/**\n * Composable item configuration parts for selecting options, validating a line and adding it to the cart.\n * Use ItemForm.Root within your ZappieProvider and place Options and Add inside it.\n */\nexport const ItemForm = { Root, Options, Add };\nexport { Choice, OptionGroup } from \"./option-group\";\nexport { Quantity } from \"./quantity\";\nexport type { QuantityProps, QuantityState } from \"./quantity\";\nexport type {\n ActiveGroup,\n ItemFormContext,\n ItemFormInvalidGroup,\n ItemFormProps,\n} from \"./root\";\n"],
|
|
5
4
|
"mappings": ";AAAA,SAAS,KAAK,eAAe;AAC7B,SAAS,YAAY;AAMrB,SAAS,QAAQ,mBAAmB;AACpC,SAAS,gBAAgB;AAFlB,IAAM,WAAW,EAAE,MAAM,SAAS,IAAI;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/item-form/option-group.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport type {\n Choice as ChoiceModel,\n OptionGroup as GroupModel,\n} from \"@zappie/sdk\";\nimport {\n cloneElement,\n createContext,\n forwardRef,\n isValidElement,\n memo,\n useContext,\n useMemo,\n useRef,\n type ChangeEvent,\n type KeyboardEvent,\n} from \"react\";\nimport { flag } from \"../internal/data-attrs\";\nimport { usePartId } from \"../internal/ids\";\nimport { composeRefs, part, type PartProps } from \"../internal/slot\";\nimport { GroupRegistry, useItemForm } from \"./root\";\ntype GroupState = {\n group: GroupModel;\n selected: string[];\n invalid: boolean;\n input: string;\n setInput: (value: string) => void;\n toggle: (id: string) => void;\n quantities: Readonly<Record<string, number>>;\n /** Set a choice's quantity within its bounds. Zero deselects it. A positive value selects it. */\n setQuantity: (id: string, quantity: number) => void;\n};\nconst Context = createContext<GroupState | null>(null);\nfunction useGroup() {\n const value = useContext(Context);\n if (!value) {\n throw new Error(\"Choice and OptionGroup.Input require OptionGroup\");\n }\n return value;\n}\nconst Group = forwardRef<\n HTMLElement,\n PartProps<GroupState> & {\n group: GroupModel;\n /**\n * The hidden text that describes a required multi-select group as required, since\n * role=\"group\" can't take aria-required. Defaults to \"Required\". Pass false when the\n * group's own heading already says so to assistive technology.\n */\n requiredNote?: string | false;\n }\n>(function OptionGroup(\n {\n group: supplied,\n requiredNote = \"Required\",\n \"aria-describedby\": describedBy,\n ...props\n },\n ref,\n) {\n const ctx = useItemForm();\n const registry = useContext(GroupRegistry)!;\n const group =\n ctx.groups.find((candidate) => candidate.id === supplied.id) ?? supplied;\n const mine =\n ctx.value.choices?.filter((choice) => choice.groupId === group.id) ?? [];\n const selected = mine.map((choice) => choice.choiceId);\n const quantities = Object.fromEntries(\n mine.map((choice) => [choice.choiceId, choice.quantity ?? 1]),\n );\n const invalid = ctx.issues.some((issue) => issue.groupId === group.id);\n const input = ctx.value.inputs?.[group.id] ?? \"\";\n const latest = useRef({ ctx, group });\n latest.current = { ctx, group };\n const actions = useMemo(\n () => ({\n toggle: (id: string) => {\n const { ctx, group } = latest.current;\n const choice = group.choices.find((candidate) => candidate.id === id);\n if (!choice?.isAvailable) {\n return;\n }\n const previous = ctx.value.choices ?? [];\n const chosen = previous.some(\n (candidate) =>\n candidate.groupId === group.id && candidate.choiceId === id,\n );\n let choices = previous.filter(\n (candidate) =>\n candidate.groupId !== group.id ||\n (group.selection.max !== 1 && candidate.choiceId !== id),\n );\n if (!chosen || (group.selection.max === 1 && group.required)) {\n choices = [\n ...choices,\n {\n groupId: group.id,\n choiceId: id,\n quantity: choice.quantity?.min ?? 1,\n },\n ];\n }\n const inputs = { ...ctx.value.inputs };\n delete inputs[group.id];\n ctx.setValue({ ...ctx.value, choices, inputs });\n },\n setQuantity: (id: string, quantity: number) => {\n const { ctx, group } = latest.current;\n const choice = group.choices.find((candidate) => candidate.id === id);\n if (!choice?.isAvailable || !Number.isInteger(quantity)) {\n return;\n }\n const others = (ctx.value.choices ?? []).filter(\n (candidate) =>\n candidate.groupId !== group.id ||\n (group.selection.max !== 1 && candidate.choiceId !== id),\n );\n if (quantity <= 0) {\n ctx.setValue({\n ...ctx.value,\n choices: (ctx.value.choices ?? []).filter(\n (candidate) =>\n candidate.groupId !== group.id || candidate.choiceId !== id,\n ),\n });\n return;\n }\n const min = choice.quantity?.min ?? 1;\n const max = choice.quantity?.max ?? 99;\n ctx.setValue({\n ...ctx.value,\n choices: [\n ...others,\n {\n groupId: group.id,\n choiceId: id,\n quantity: Math.min(max, Math.max(min, quantity)),\n },\n ],\n });\n },\n setInput: (value: string) => {\n const { ctx, group } = latest.current;\n ctx.setValue({\n ...ctx.value,\n choices: (ctx.value.choices ?? []).filter(\n (choice) => choice.groupId !== group.id,\n ),\n inputs: {\n ...ctx.value.inputs,\n [group.id]: value.slice(0, group.input?.maxLength ?? 500),\n },\n });\n },\n }),\n [],\n );\n const selectionKey = JSON.stringify(quantities);\n const state = useMemo(\n () => ({ group, selected, quantities, invalid, input, ...actions }),\n [group, selectionKey, invalid, input, actions],\n );\n const single = group.kind === \"select\" && group.selection.max === 1;\n const noteId = usePartId();\n const note =\n group.required && group.kind === \"select\" && !single && requiredNote;\n // An asChild element's own description joins the note, as partId reads its id.\n const child =\n props.asChild &&\n isValidElement<{ \"aria-describedby\"?: string | undefined }>(props.children)\n ? props.children\n : undefined;\n const own = describedBy ?? child?.props[\"aria-describedby\"];\n const description = note ? `${noteId}${own ? ` ${own}` : \"\"}` : own;\n return (\n <Context.Provider value={state}>\n {note && (\n <span id={noteId} hidden>\n {note}\n </span>\n )}\n {part(\n \"div\",\n child\n ? {\n ...props,\n children: cloneElement(child, {\n \"aria-describedby\": description,\n }),\n }\n : props,\n state,\n {\n role: single ? \"radiogroup\" : \"group\",\n tabIndex: -1,\n \"aria-label\": group.name,\n \"aria-required\": single ? group.required : undefined,\n \"aria-describedby\": description,\n \"data-zappie-required\": group.required ? \"true\" : undefined,\n \"aria-invalid\": invalid || undefined,\n \"data-invalid\": flag(invalid),\n \"data-state\": invalid ? \"invalid\" : \"valid\",\n \"data-zappie-option-group\": group.id,\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => {\n if (\n !single ||\n ![\n \"ArrowDown\",\n \"ArrowRight\",\n \"ArrowUp\",\n \"ArrowLeft\",\n \"Home\",\n \"End\",\n ].includes(event.key)\n ) {\n return;\n }\n const choices = Array.from(\n event.currentTarget.querySelectorAll<HTMLElement>(\n '[role=\"radio\"]:not([aria-disabled=\"true\"])',\n ),\n );\n if (!choices.length) {\n return;\n }\n const current = choices.indexOf(\n document.activeElement as HTMLElement,\n );\n const index =\n event.key === \"Home\"\n ? 0\n : event.key === \"End\"\n ? choices.length - 1\n : (current +\n (event.key === \"ArrowUp\" || event.key === \"ArrowLeft\"\n ? -1\n : 1) +\n choices.length) %\n choices.length;\n event.preventDefault();\n choices[index]?.focus();\n choices[index]?.click();\n },\n },\n composeRefs(ref, (node) => {\n if (node) {\n registry.set(group.id, node);\n } else {\n registry.delete(group.id);\n }\n }),\n )}\n </Context.Provider>\n );\n});\ntype ChoiceState = {\n choice: ChoiceModel;\n selected: boolean;\n disabled: boolean;\n toggle: () => void;\n /** Selected quantity (0 when unselected) and the choice's bounds. */\n quantity: number;\n min: number;\n max: number;\n setQuantity: (quantity: number) => void;\n};\nconst ChoiceView = memo(\n forwardRef<\n HTMLElement,\n PartProps<ChoiceState> & {\n choice: ChoiceModel;\n selected: boolean;\n single: boolean;\n tabIndex: number;\n quantity: number;\n toggleChoice: (id: string) => void;\n setChoiceQuantity: (id: string, quantity: number) => void;\n }\n >(function ChoiceView(\n {\n choice,\n selected,\n single,\n tabIndex,\n quantity,\n toggleChoice,\n setChoiceQuantity,\n ...props\n },\n ref,\n ) {\n const toggle = () => toggleChoice(choice.id);\n const state = {\n choice,\n selected,\n disabled: !choice.isAvailable,\n toggle,\n quantity,\n min: choice.quantity?.min ?? 1,\n max: choice.quantity?.max ?? 99,\n setQuantity: (next: number) => setChoiceQuantity(choice.id, next),\n };\n return part(\n \"button\",\n props,\n state,\n {\n type: \"button\",\n role: single ? \"radio\" : \"checkbox\",\n tabIndex,\n \"aria-checked\": selected,\n \"aria-disabled\": !choice.isAvailable,\n disabled: !choice.isAvailable,\n \"data-selected\": flag(selected),\n \"data-sold-out\": flag(!choice.isAvailable),\n \"data-disabled\": flag(!choice.isAvailable),\n \"data-state\": selected ? \"checked\" : \"unchecked\",\n \"data-zappie-option\": choice.id,\n onClick: toggle,\n },\n ref,\n );\n }),\n);\n/**\n * Render a selectable option within an OptionGroup, with radio or checkbox semantics.\n * Exposes selection and quantity controls and disables unavailable choices.\n */\nexport const Choice = forwardRef<\n HTMLElement,\n PartProps<ChoiceState> & { choice: ChoiceModel }\n>(function Choice(props, ref) {\n const ctx = useGroup();\n const single = ctx.group.selection.max === 1;\n const selected = ctx.selected.includes(props.choice.id);\n const first = ctx.group.choices.find((choice) => choice.isAvailable);\n const tabIndex =\n !single ||\n selected ||\n (!ctx.selected.length && first?.id === props.choice.id)\n ? 0\n : -1;\n return (\n <ChoiceView\n {...props}\n ref={ref}\n selected={selected}\n single={single}\n tabIndex={tabIndex}\n quantity={ctx.quantities[props.choice.id] ?? 0}\n toggleChoice={ctx.toggle}\n setChoiceQuantity={ctx.setQuantity}\n />\n );\n});\nconst Input = forwardRef<HTMLElement, PartProps<GroupState>>(\n function Input(props, ref) {\n const ctx = useGroup();\n const id = usePartId(props.id);\n const toggle = ctx.group.kind === \"toggle\";\n return part(\n \"input\",\n props,\n ctx,\n {\n id,\n type: toggle\n ? \"checkbox\"\n : ctx.group.kind === \"select\"\n ? \"text\"\n : ctx.group.kind,\n value: toggle ? undefined : ctx.input,\n checked: toggle ? ctx.input === \"true\" : undefined,\n required: ctx.group.required,\n \"aria-label\": ctx.group.name,\n \"aria-invalid\": ctx.invalid || undefined,\n \"data-invalid\": flag(ctx.invalid),\n maxLength: ctx.group.input?.maxLength ?? 500,\n placeholder: ctx.group.input?.placeholder,\n onChange: (event: ChangeEvent<HTMLInputElement>) =>\n ctx.setInput(\n toggle ? String(event.target.checked) : event.target.value,\n ),\n },\n ref,\n );\n },\n);\n/**\n * Group item options with selection rules, validation state and an Input part for typed responses.\n * Use within ItemForm.Root. Use Choice for options and OptionGroup.Input for typed responses.\n */\nexport const OptionGroup = Object.assign(Group, { Input });\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": ["ctx", "group", "ChoiceView", "Choice", "Input", "OptionGroup"]
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/item-form/options.tsx"],
|
|
4
|
-
"sourcesContent": ["import { forwardRef } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { useItemForm, type ItemFormContext } from \"./root\";\nexport const Options = forwardRef<HTMLElement, PartProps<ItemFormContext>>(\n function Options(props, ref) {\n return part(\"div\", props, useItemForm(), {}, ref);\n },\n);\nexport const Add = forwardRef<HTMLElement, PartProps<ItemFormContext>>(\n function Add(props, ref) {\n const context = useItemForm();\n // Keep invalid configurations actionable so add() can explain and focus errors.\n return part(\n \"button\",\n props,\n context,\n {\n type: \"button\",\n onClick: context.add,\n \"data-zappie-action\": \"add-to-cart\",\n },\n ref,\n );\n },\n);\n"],
|
|
5
4
|
"mappings": ";AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAA4B;AACrC,SAAS,mBAAyC;AAC3C,IAAM,UAAU;AAAA,EACrB,SAASA,SAAQ,OAAO,KAAK;AAC3B,WAAO,KAAK,OAAO,OAAO,YAAY,GAAG,CAAC,GAAG,GAAG;AAAA,EAClD;AACF;AACO,IAAM,MAAM;AAAA,EACjB,SAASC,KAAI,OAAO,KAAK;AACvB,UAAM,UAAU,YAAY;AAE5B,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,SAAS,QAAQ;AAAA,QACjB,sBAAsB;AAAA,MACxB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;",
|
|
6
5
|
"names": ["Options", "Add"]
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/item-form/quantity.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useMemo,\n type ChangeEvent,\n type KeyboardEvent,\n} from \"react\";\nimport { flag } from \"../internal/data-attrs\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { useControllableState } from \"../internal/use-controllable-state\";\n/**\n * The current quantity, bounds, step, disabled state and value setter exposed by a quantity control.\n */\nexport type QuantityState = {\n value: number;\n setValue: (value: number) => void;\n min: number;\n max: number;\n step: number;\n disabled: boolean;\n};\nconst Context = createContext<QuantityState | null>(null);\nfunction useQuantity() {\n const context = useContext(Context);\n if (!context) {\n throw new Error(\"Quantity parts require Quantity.Root\");\n }\n return context;\n}\n/**\n * Controlled or default quantity, bounds and change callback for Quantity.Root.\n */\nexport type QuantityProps = PartProps<QuantityState> & {\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n min?: number;\n max?: number;\n step?: number;\n disabled?: boolean;\n};\nfunction Root({\n value,\n defaultValue,\n onValueChange,\n min = 1,\n max = 99,\n step = 1,\n disabled = false,\n ...props\n}: QuantityProps) {\n if (\n !Number.isFinite(min) ||\n !Number.isFinite(max) ||\n !Number.isFinite(step) ||\n step <= 0 ||\n min > max\n ) {\n throw new RangeError(\n \"Quantity requires finite ordered bounds and a positive step\",\n );\n }\n const [current, set] = useControllableState({\n value,\n defaultValue: defaultValue ?? min,\n onChange: onValueChange,\n });\n const setValue = useCallback(\n (next: number) => {\n if (!disabled && Number.isFinite(next)) {\n const last = min + Math.floor((max - min) / step) * step;\n set(\n Math.max(\n min,\n Math.min(last, min + Math.round((next - min) / step) * step),\n ),\n );\n }\n },\n [min, max, step, disabled, set],\n );\n const state = useMemo(\n () => ({ value: current, setValue, min, max, step, disabled }),\n [current, setValue, min, max, step, disabled],\n );\n return (\n <Context.Provider value={state}>\n {part(\"div\", props, state, { \"data-disabled\": flag(disabled) })}\n </Context.Provider>\n );\n}\nfunction button(direction: -1 | 1) {\n return forwardRef<HTMLElement, PartProps<QuantityState>>(\n function QuantityButton(props, ref) {\n const ctx = useQuantity();\n const disabled =\n ctx.disabled ||\n (direction < 0 ? ctx.value <= ctx.min : ctx.value + ctx.step > ctx.max);\n return part(\n \"button\",\n props,\n ctx,\n {\n type: \"button\",\n disabled,\n \"aria-disabled\": disabled,\n \"data-disabled\": flag(disabled),\n onClick: () => ctx.setValue(ctx.value + direction * ctx.step),\n },\n ref,\n );\n },\n );\n}\nconst Value = forwardRef<HTMLElement, PartProps<QuantityState>>(\n function Value(props, ref) {\n const ctx = useQuantity();\n return part(\n \"input\",\n props,\n ctx,\n {\n type: \"text\",\n inputMode: \"numeric\",\n role: \"spinbutton\",\n value: String(ctx.value),\n disabled: ctx.disabled,\n \"aria-valuenow\": ctx.value,\n \"aria-valuemin\": ctx.min,\n \"aria-valuemax\": ctx.max,\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n if (event.target.value.trim()) {\n ctx.setValue(Number(event.target.value));\n }\n },\n onKeyDown: (event: KeyboardEvent) => {\n const next = {\n ArrowUp: ctx.value + ctx.step,\n ArrowDown: ctx.value - ctx.step,\n Home: ctx.min,\n End: ctx.max,\n }[event.key];\n if (next !== undefined) {\n event.preventDefault();\n ctx.setValue(next);\n }\n },\n },\n ref,\n );\n },\n);\n/**\n * Composable quantity controls with bounded step changes and keyboard support.\n * Wrap Value, Increment and Decrement in Quantity.Root.\n *\n * @example\n * ```tsx\n * <Quantity.Root min={1} max={10} defaultValue={1}>\n * <Quantity.Decrement>Less</Quantity.Decrement>\n * <Quantity.Value aria-label=\"Quantity\" />\n * <Quantity.Increment>More</Quantity.Increment>\n * </Quantity.Root>\n * ```\n */\nexport const Quantity = {\n Root,\n Value,\n Increment: button(1),\n Decrement: button(-1),\n};\n"],
|
|
5
4
|
"mappings": ";;;AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,YAAY;AACrB,SAAS,YAA4B;AACrC,SAAS,4BAA4B;AA6EjC;AAjEJ,IAAM,UAAU,cAAoC,IAAI;AACxD,SAAS,cAAc;AACrB,QAAM,UAAU,WAAW,OAAO;AAClC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AACA,SAAO;AACT;AAaA,SAAS,KAAK;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,WAAW;AAAA,EACX,GAAG;AACL,GAAkB;AAChB,MACE,CAAC,OAAO,SAAS,GAAG,KACpB,CAAC,OAAO,SAAS,GAAG,KACpB,CAAC,OAAO,SAAS,IAAI,KACrB,QAAQ,KACR,MAAM,KACN;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,CAAC,SAAS,GAAG,IAAI,qBAAqB;AAAA,IAC1C;AAAA,IACA,cAAc,gBAAgB;AAAA,IAC9B,UAAU;AAAA,EACZ,CAAC;AACD,QAAM,WAAW;AAAA,IACf,CAAC,SAAiB;AAChB,UAAI,CAAC,YAAY,OAAO,SAAS,IAAI,GAAG;AACtC,cAAM,OAAO,MAAM,KAAK,OAAO,MAAM,OAAO,IAAI,IAAI;AACpD;AAAA,UACE,KAAK;AAAA,YACH;AAAA,YACA,KAAK,IAAI,MAAM,MAAM,KAAK,OAAO,OAAO,OAAO,IAAI,IAAI,IAAI;AAAA,UAC7D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,KAAK,KAAK,MAAM,UAAU,GAAG;AAAA,EAChC;AACA,QAAM,QAAQ;AAAA,IACZ,OAAO,EAAE,OAAO,SAAS,UAAU,KAAK,KAAK,MAAM,SAAS;AAAA,IAC5D,CAAC,SAAS,UAAU,KAAK,KAAK,MAAM,QAAQ;AAAA,EAC9C;AACA,SACE,oBAAC,QAAQ,UAAR,EAAiB,OAAO,OACtB,eAAK,OAAO,OAAO,OAAO,EAAE,iBAAiB,KAAK,QAAQ,EAAE,CAAC,GAChE;AAEJ;AACA,SAAS,OAAO,WAAmB;AACjC,SAAO;AAAA,IACL,SAAS,eAAe,OAAO,KAAK;AAClC,YAAM,MAAM,YAAY;AACxB,YAAM,WACJ,IAAI,aACH,YAAY,IAAI,IAAI,SAAS,IAAI,MAAM,IAAI,QAAQ,IAAI,OAAO,IAAI;AACrE,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN;AAAA,UACA,iBAAiB;AAAA,UACjB,iBAAiB,KAAK,QAAQ;AAAA,UAC9B,SAAS,MAAM,IAAI,SAAS,IAAI,QAAQ,YAAY,IAAI,IAAI;AAAA,QAC9D;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AACA,IAAM,QAAQ;AAAA,EACZ,SAASA,OAAM,OAAO,KAAK;AACzB,UAAM,MAAM,YAAY;AACxB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,WAAW;AAAA,QACX,MAAM;AAAA,QACN,OAAO,OAAO,IAAI,KAAK;AAAA,QACvB,UAAU,IAAI;AAAA,QACd,iBAAiB,IAAI;AAAA,QACrB,iBAAiB,IAAI;AAAA,QACrB,iBAAiB,IAAI;AAAA,QACrB,UAAU,CAAC,UAAyC;AAClD,cAAI,MAAM,OAAO,MAAM,KAAK,GAAG;AAC7B,gBAAI,SAAS,OAAO,MAAM,OAAO,KAAK,CAAC;AAAA,UACzC;AAAA,QACF;AAAA,QACA,WAAW,CAAC,UAAyB;AACnC,gBAAM,OAAO;AAAA,YACX,SAAS,IAAI,QAAQ,IAAI;AAAA,YACzB,WAAW,IAAI,QAAQ,IAAI;AAAA,YAC3B,MAAM,IAAI;AAAA,YACV,KAAK,IAAI;AAAA,UACX,EAAE,MAAM,GAAG;AACX,cAAI,SAAS,QAAW;AACtB,kBAAM,eAAe;AACrB,gBAAI,SAAS,IAAI;AAAA,UACnB;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAcO,IAAM,WAAW;AAAA,EACtB;AAAA,EACA;AAAA,EACA,WAAW,OAAO,CAAC;AAAA,EACnB,WAAW,OAAO,EAAE;AACtB;",
|
|
6
5
|
"names": ["Value"]
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/item-form/root.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport {\n previewItem,\n ZappieCartError,\n type CartIssue,\n type CartLine,\n type CartLineInput,\n type Item,\n type Money,\n type OptionGroup,\n} from \"@zappie/sdk\";\nimport { useCartCatalogue, useZappie } from \"@zappie/sdk/react\";\nimport {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { useControllableState } from \"../internal/use-controllable-state\";\n/**\n * An SDK option group with the effective rules exposed by the item preview.\n */\nexport type ActiveGroup = OptionGroup;\n/**\n * The current item selection, preview prices, validation issues and actions supplied to item form parts.\n */\nexport type ItemFormContext = {\n item: Item;\n groups: ActiveGroup[];\n quantity: number;\n setQuantity: (quantity: number) => void;\n unitPrice: Money;\n total: Money;\n issues: CartIssue[];\n canAdd: boolean;\n add: () => CartLine | null;\n status: \"incomplete\" | \"valid\" | \"added\";\n value: CartLineInput;\n setValue: (value: CartLineInput) => void;\n};\nexport const ItemFormStateContext = createContext<ItemFormContext | null>(null);\nexport function useItemForm() {\n const value = useContext(ItemFormStateContext);\n if (!value) {\n throw new Error(\"ItemForm parts require ItemForm.Root\");\n }\n return value;\n}\n/**\n * An option group that failed validation when the shopper tried to add, with its element and issues.\n */\nexport type ItemFormInvalidGroup = {\n groupId: string;\n /** The item's option group, or undefined for a group the template rendered from its own model. */\n group: ActiveGroup | undefined;\n /** The group's rendered element, or undefined when the group is not on screen. */\n element: HTMLElement | undefined;\n issues: CartIssue[];\n};\nexport const GroupRegistry = createContext<Map<string, HTMLElement> | null>(\n null,\n);\n/**\n * Item form configuration with controlled or default selections and cart commit callbacks.\n * Supply onCommit to update an existing line instead of adding a new one.\n */\nexport type ItemFormProps = Omit<PartProps<ItemFormContext>, \"onInvalid\"> & {\n item: Item;\n value?: CartLineInput;\n defaultValue?: Partial<CartLineInput>;\n onValueChange?: (value: CartLineInput) => void;\n defaultQuantity?: number;\n onAdded?: (line: CartLine) => void;\n /** Update an existing line through the same validation and focus workflow. */\n onCommit?: (input: CartLineInput) => CartLine;\n /**\n * Called when an add fails validation, after the first invalid group gets\n * focus without scrolling. Groups are in display order. Scroll to\n * `groups[0].element` yourself, for example smoothly. The list is empty\n * when no issue belongs to a group.\n */\n onInvalid?: (groups: ItemFormInvalidGroup[]) => void;\n};\nexport function Root({\n item,\n value,\n defaultValue,\n onValueChange,\n defaultQuantity,\n onAdded,\n onCommit,\n onInvalid,\n ...props\n}: ItemFormProps) {\n const client = useZappie();\n const menu = useCartCatalogue();\n const [input, setInput] = useControllableState({\n value,\n defaultValue: {\n ...defaultValue,\n itemId: item.id,\n quantity: defaultQuantity ?? defaultValue?.quantity ?? item.quantity.min,\n },\n onChange: onValueChange,\n });\n const registry = useRef(new Map<string, HTMLElement>());\n const [added, setAdded] = useState(false);\n // Errors show only after the shopper tries to add (PRD 05 5.3), then track live validation.\n const [attempted, setAttempted] = useState(false);\n const [failure, setFailure] = useState<CartIssue[] | null>(null);\n const normalized = useMemo(\n () => ({ ...input, itemId: item.id }),\n [input, item.id],\n );\n const preview = useMemo(\n () => previewItem(item, normalized),\n [item, normalized],\n );\n const validation = useMemo(\n () => client.cart.validate(normalized),\n [client, normalized, menu],\n );\n const issues =\n failure ?? (attempted && !validation.ok ? validation.issues : []);\n const setValue = useCallback(\n (next: CartLineInput) => {\n setAdded(false);\n setFailure(null);\n setInput({ ...next, itemId: item.id });\n },\n [setInput, item.id],\n );\n const setQuantity = useCallback(\n (quantity: number) => setValue({ ...normalized, quantity }),\n [setValue, normalized],\n );\n const focusIssue = useCallback(\n (errors: CartIssue[]) => {\n const issuesOf = (groupId: string) =>\n errors.filter((issue) => issue.groupId === groupId);\n const group = (groupId: string) =>\n preview.groups.find((candidate) => candidate.id === groupId);\n // Rendered groups first, in the order they mounted, then any invalid group not on screen.\n const rendered = [...registry.current]\n .filter(([groupId]) => issuesOf(groupId).length > 0)\n .map(([groupId, element]) => ({\n groupId,\n group: group(groupId),\n element: element as HTMLElement | undefined,\n issues: issuesOf(groupId),\n }));\n const offscreen = preview.groups\n .filter(\n (candidate) =>\n !registry.current.has(candidate.id) &&\n issuesOf(candidate.id).length > 0,\n )\n .map((candidate) => ({\n groupId: candidate.id,\n group: candidate,\n element: undefined,\n issues: issuesOf(candidate.id),\n }));\n const invalid = [...rendered, ...offscreen];\n // Focus moves for keyboard and screen reader users; the template decides how to scroll.\n invalid[0]?.element?.focus({ preventScroll: true });\n onInvalid?.(invalid);\n },\n [preview.groups, onInvalid],\n );\n const add = useCallback(() => {\n const result = client.cart.validate(normalized);\n if (!result.ok) {\n setAttempted(true);\n setFailure(result.issues);\n focusIssue(result.issues);\n return null;\n }\n try {\n const line = onCommit\n ? onCommit(normalized)\n : client.cart.add(item, normalized);\n setAttempted(false);\n setAdded(true);\n onAdded?.(line);\n return line;\n } catch (error) {\n if (error instanceof ZappieCartError) {\n setAttempted(true);\n setFailure([...error.issues]);\n focusIssue([...error.issues]);\n return null;\n }\n throw error;\n }\n }, [client, normalized, item, onAdded, onCommit, focusIssue]);\n const context: ItemFormContext = useMemo(\n () => ({\n item,\n ...preview,\n quantity: normalized.quantity ?? item.quantity.min,\n setQuantity,\n issues,\n canAdd: validation.ok && !failure,\n add,\n status: added ? \"added\" : validation.ok ? \"valid\" : \"incomplete\",\n value: normalized,\n setValue,\n }),\n [\n item,\n preview,\n normalized,\n setQuantity,\n issues,\n validation.ok,\n failure,\n add,\n added,\n setValue,\n ],\n );\n return (\n <ItemFormStateContext.Provider value={context}>\n <GroupRegistry.Provider value={registry.current}>\n {part(\"div\", props, context, {\n \"data-zappie-item\": item.id,\n \"data-state\": issues.length ? \"invalid\" : \"valid\",\n \"data-sold-out\": item.isAvailable ? undefined : \"\",\n })}\n </GroupRegistry.Provider>\n </ItemFormStateContext.Provider>\n );\n}\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
package/dist/media/image.js.map
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/media/image.tsx"],
|
|
4
|
-
"sourcesContent": ["import { canResize, resized, srcSet } from \"@zappie/sdk/template\";\nimport { useCallback, useState, type ImgHTMLAttributes } from \"react\";\n\n/** Props for a responsive storefront image. */\nexport type ImageProps = Omit<\n ImgHTMLAttributes<HTMLImageElement>,\n \"src\" | \"srcSet\"\n> & {\n src: string;\n alt: string;\n sizes?: string;\n priority?: boolean;\n quality?: number;\n mode?: \"resize\" | \"direct\";\n /** Lowercase hosts your zone resizes. Defaults to `RESIZE_HOSTS` from `@zappie/sdk/template`. */\n resizeHosts?: readonly string[];\n};\n\n/** Render an image through same-origin resizing, falling back once to `src` if that fails. */\nexport function Image({\n src,\n sizes = \"100vw\",\n priority = false,\n quality,\n mode = \"resize\",\n resizeHosts,\n alt,\n onError,\n ...props\n}: ImageProps) {\n const [failed, setFailed] = useState<string | null>(null);\n const responsive =\n mode === \"resize\" && failed !== src && canResize(src, resizeHosts);\n // Catches an error that fired before hydration.\n const checkLoaded = useCallback(\n (image: HTMLImageElement | null) => {\n if (responsive && image?.complete && image.naturalWidth === 0) {\n setFailed(src);\n }\n },\n [responsive, src],\n );\n return (\n <img\n {...props}\n ref={checkLoaded}\n alt={alt}\n src={responsive ? resized(src, 960, quality, resizeHosts) : src}\n {...(responsive\n ? { srcSet: srcSet(src, quality, resizeHosts), sizes }\n : {})}\n loading={priority ? \"eager\" : \"lazy\"}\n decoding={priority ? \"sync\" : \"async\"}\n {...(priority ? { fetchPriority: \"high\" as const } : {})}\n onError={(event) => {\n if (responsive) {\n setFailed(src);\n }\n onError?.(event);\n }}\n />\n );\n}\n"],
|
|
5
4
|
"mappings": ";AAAA,SAAS,WAAW,SAAS,cAAc;AAC3C,SAAS,aAAa,gBAAwC;AA0C1D;AAxBG,SAAS,MAAM;AAAA,EACpB;AAAA,EACA,QAAQ;AAAA,EACR,WAAW;AAAA,EACX;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAe;AACb,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAwB,IAAI;AACxD,QAAM,aACJ,SAAS,YAAY,WAAW,OAAO,UAAU,KAAK,WAAW;AAEnE,QAAM,cAAc;AAAA,IAClB,CAAC,UAAmC;AAClC,UAAI,cAAc,OAAO,YAAY,MAAM,iBAAiB,GAAG;AAC7D,kBAAU,GAAG;AAAA,MACf;AAAA,IACF;AAAA,IACA,CAAC,YAAY,GAAG;AAAA,EAClB;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,KAAK;AAAA,MACL;AAAA,MACA,KAAK,aAAa,QAAQ,KAAK,KAAK,SAAS,WAAW,IAAI;AAAA,MAC3D,GAAI,aACD,EAAE,QAAQ,OAAO,KAAK,SAAS,WAAW,GAAG,MAAM,IACnD,CAAC;AAAA,MACL,SAAS,WAAW,UAAU;AAAA,MAC9B,UAAU,WAAW,SAAS;AAAA,MAC7B,GAAI,WAAW,EAAE,eAAe,OAAgB,IAAI,CAAC;AAAA,MACtD,SAAS,CAAC,UAAU;AAClB,YAAI,YAAY;AACd,oBAAU,GAAG;AAAA,QACf;AACA,kBAAU,KAAK;AAAA,MACjB;AAAA;AAAA,EACF;AAEJ;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
package/dist/money.js.map
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/money.tsx"],
|
|
4
|
-
"sourcesContent": ["import {\n format,\n type MoneyFormatOptions,\n type Money as MoneyValue,\n} from \"@zappie/sdk\";\nimport { forwardRef } from \"react\";\nimport { part, type PartProps } from \"./internal/slot\";\n/**\n * Render formatted SDK money in a data element, with the minor-unit amount as its value.\n * You can override the content with children or a render function receiving the formatted string.\n */\nexport const Money = forwardRef<\n HTMLElement,\n PartProps<{ formatted: string }> & {\n value: MoneyValue;\n formatOptions?: MoneyFormatOptions;\n }\n>(function Money({ value, formatOptions, ...props }, ref) {\n const formatted = format.money(value, formatOptions);\n return part(\n \"data\",\n { ...props, children: props.children ?? formatted },\n { formatted },\n { value: String(value.amountMinor) },\n ref,\n );\n});\n"],
|
|
5
4
|
"mappings": ";AAAA;AAAA,EACE;AAAA,OAGK;AACP,SAAS,kBAAkB;AAC3B,SAAS,YAA4B;AAK9B,IAAM,QAAQ,WAMnB,SAASA,OAAM,EAAE,OAAO,eAAe,GAAG,MAAM,GAAG,KAAK;AACxD,QAAM,YAAY,OAAO,MAAM,OAAO,aAAa;AACnD,SAAO;AAAA,IACL;AAAA,IACA,EAAE,GAAG,OAAO,UAAU,MAAM,YAAY,UAAU;AAAA,IAClD,EAAE,UAAU;AAAA,IACZ,EAAE,OAAO,OAAO,MAAM,WAAW,EAAE;AAAA,IACnC;AAAA,EACF;AACF,CAAC;",
|
|
6
5
|
"names": ["Money"]
|
|
7
6
|
}
|
package/dist/next.js.map
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/overlay/next.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport { useEffect } from \"react\";\nimport { LOCATION_EVENT } from \"./history\";\n/**\n * The event overlays re-read the URL on. A host that intercepts `popstate`\n * for its own shallow history (and stops it) must dispatch this too, or open\n * overlays never see Back.\n */\nexport { LOCATION_EVENT as OVERLAY_LOCATION_EVENT };\nlet subscribers = 0;\nlet uninstall: (() => void) | undefined;\n/** Capture same-page shallow moves before Next attempts a server navigation. */\nexport function installShallowHistory() {\n if (typeof window === \"undefined\") {\n return () => {};\n }\n if (subscribers++ === 0) {\n let previous = { base: \"\", pathname: \"\", shallow: false };\n const record = () => {\n previous = {\n base: window.history.state?.__zappieBase ?? window.location.pathname,\n pathname: window.location.pathname,\n shallow: Boolean(window.history.state?.__zappieBase),\n };\n };\n record();\n const onPop = (event: PopStateEvent) => {\n const base = event.state?.__zappieBase ?? window.location.pathname;\n if (\n (Boolean(event.state?.__zappieBase) || previous.shallow) &&\n base === previous.base &&\n window.location.pathname === previous.pathname\n ) {\n event.stopImmediatePropagation();\n record();\n window.dispatchEvent(new Event(LOCATION_EVENT));\n return;\n }\n record();\n };\n window.addEventListener(\"popstate\", onPop, true);\n window.addEventListener(LOCATION_EVENT, record);\n uninstall = () => {\n window.removeEventListener(\"popstate\", onPop, true);\n window.removeEventListener(LOCATION_EVENT, record);\n };\n }\n let released = false;\n return () => {\n if (!released) {\n released = true;\n if (--subscribers === 0) {\n uninstall?.();\n }\n }\n };\n}\n/**\n * Install the shared shallow-history bridge on mount and release it on unmount.\n * Keeps same-page overlay history changes from triggering Next server navigation.\n */\nexport function useNextOverlayBridge() {\n useEffect(installShallowHistory, []);\n}\n"],
|
|
5
4
|
"mappings": ";;;AACA,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB;AAO/B,IAAI,cAAc;AAClB,IAAI;AAEG,SAAS,wBAAwB;AACtC,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AACA,MAAI,kBAAkB,GAAG;AACvB,QAAI,WAAW,EAAE,MAAM,IAAI,UAAU,IAAI,SAAS,MAAM;AACxD,UAAM,SAAS,MAAM;AACnB,iBAAW;AAAA,QACT,MAAM,OAAO,QAAQ,OAAO,gBAAgB,OAAO,SAAS;AAAA,QAC5D,UAAU,OAAO,SAAS;AAAA,QAC1B,SAAS,QAAQ,OAAO,QAAQ,OAAO,YAAY;AAAA,MACrD;AAAA,IACF;AACA,WAAO;AACP,UAAM,QAAQ,CAAC,UAAyB;AACtC,YAAM,OAAO,MAAM,OAAO,gBAAgB,OAAO,SAAS;AAC1D,WACG,QAAQ,MAAM,OAAO,YAAY,KAAK,SAAS,YAChD,SAAS,SAAS,QAClB,OAAO,SAAS,aAAa,SAAS,UACtC;AACA,cAAM,yBAAyB;AAC/B,eAAO;AACP,eAAO,cAAc,IAAI,MAAM,cAAc,CAAC;AAC9C;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,WAAO,iBAAiB,YAAY,OAAO,IAAI;AAC/C,WAAO,iBAAiB,gBAAgB,MAAM;AAC9C,gBAAY,MAAM;AAChB,aAAO,oBAAoB,YAAY,OAAO,IAAI;AAClD,aAAO,oBAAoB,gBAAgB,MAAM;AAAA,IACnD;AAAA,EACF;AACA,MAAI,WAAW;AACf,SAAO,MAAM;AACX,QAAI,CAAC,UAAU;AACb,iBAAW;AACX,UAAI,EAAE,gBAAgB,GAAG;AACvB,oBAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;AAKO,SAAS,uBAAuB;AACrC,YAAU,uBAAuB,CAAC,CAAC;AACrC;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
package/dist/otp/input.js.map
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/otp/input.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport {\n forwardRef,\n useEffect,\n useRef,\n type ChangeEvent,\n type ClipboardEvent,\n type KeyboardEvent,\n} from \"react\";\nimport { composeRefs, part, type PartProps } from \"../internal/slot\";\nimport { useControllableState } from \"../internal/use-controllable-state\";\nexport type OtpState = {\n value: string;\n slots: string[];\n getSlotProps: (index: number) => ReturnType<typeof slotAttributes>;\n};\nfunction slotAttributes(\n index: number,\n length: number,\n value: string,\n disabled: boolean,\n update: (index: number, text: string) => void,\n focus: (index: number) => void,\n) {\n return {\n type: \"text\",\n inputMode: \"numeric\" as const,\n autoComplete: index === 0 ? \"one-time-code\" : \"off\",\n \"aria-label\": `Digit ${index + 1} of ${length}`,\n \"data-otp-slot\": index,\n value: value[index] ?? \"\",\n disabled,\n onChange: (event: ChangeEvent<HTMLInputElement>) =>\n update(index, event.target.value),\n onPaste: (event: ClipboardEvent<HTMLInputElement>) => {\n event.preventDefault();\n update(index, event.clipboardData.getData(\"text\"));\n },\n onKeyDown: (event: KeyboardEvent<HTMLInputElement>) => {\n if (event.key === \"Backspace\") {\n event.preventDefault();\n const target = value[index] ? index : Math.max(0, index - 1);\n update(target, \"\");\n focus(target);\n } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") {\n event.preventDefault();\n focus(index + (event.key === \"ArrowLeft\" ? -1 : 1));\n }\n },\n onFocus: (event: { currentTarget: HTMLInputElement }) =>\n event.currentTarget.select(),\n };\n}\nconst Input = forwardRef<\n HTMLElement,\n PartProps<OtpState> & {\n length?: number;\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n onComplete?: (value: string) => void;\n disabled?: boolean;\n }\n>(function Input(\n {\n length = 6,\n value,\n defaultValue = \"\",\n onValueChange,\n onComplete,\n disabled = false,\n ...props\n },\n ref,\n) {\n if (!Number.isInteger(length) || length < 1 || length > 12) {\n throw new RangeError(\"OTP length must be 1\u201312\");\n }\n const [current, setValue] = useControllableState({\n value,\n defaultValue,\n onChange: onValueChange,\n });\n const element = useRef<HTMLElement>(null);\n const previous = useRef(\"\");\n const callback = useRef(onComplete);\n callback.current = onComplete;\n useEffect(() => {\n if (\n current.length === length &&\n /^\\d+$/.test(current) &&\n previous.current !== current\n ) {\n callback.current?.(current);\n }\n previous.current = current;\n }, [current, length]);\n const focus = (index: number) =>\n element.current\n ?.querySelector<HTMLInputElement>(\n `[data-otp-slot=\"${Math.max(0, Math.min(length - 1, index))}\"]`,\n )\n ?.focus();\n const update = (index: number, text: string) => {\n if (disabled) {\n return;\n }\n const digits = text.replace(/\\D/g, \"\");\n const start = digits.length >= length ? 0 : index;\n const next = (\n current.slice(0, start) +\n digits +\n current.slice(start + Math.max(1, digits.length))\n ).slice(0, length);\n setValue(next);\n if (digits) {\n focus(start + digits.length);\n }\n };\n // An SMS code can be autofilled (or typed) before hydration: it sits in the\n // DOM with no change event. Adopt it once the component mounts.\n const adopt = useRef(update);\n adopt.current = update;\n useEffect(() => {\n const typed = Array.from(\n element.current?.querySelectorAll<HTMLInputElement>(\"[data-otp-slot]\") ??\n [],\n (input) => input.value,\n ).join(\"\");\n if (typed && typed !== current) {\n adopt.current(0, typed);\n }\n // Mount only: later edits arrive as change events.\n }, []);\n const getSlotProps = (index: number) =>\n slotAttributes(index, length, current, disabled, update, focus);\n const slots = Array.from({ length }, (_, index) => current[index] ?? \"\");\n return part(\n \"div\",\n {\n ...props,\n children:\n props.children ??\n slots.map((_, index) => <input key={index} {...getSlotProps(index)} />),\n },\n { value: current, slots, getSlotProps },\n { role: \"group\", \"data-disabled\": disabled ? \"\" : undefined },\n composeRefs(ref, element),\n );\n});\n/**\n * A numeric one-time-code input with paste, autofill and keyboard focus handling.\n * Otp.Input defaults to six digits and calls onComplete when a new complete code is entered.\n * You supply code verification.\n */\nexport const Otp = { Input };\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": ["Input"]
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/overlay/engine.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\n// The overlay engine: the only module that imports Base UI. It loads on first open (or an\n// idle prefetch) so dialog and drawer mechanics stay out of the initial bundle.\nimport { Dialog } from \"@base-ui/react/dialog\";\nimport { Drawer } from \"@base-ui/react/drawer\";\nimport {\n createContext,\n forwardRef,\n useContext,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport { composeRefs, part, partId, type PartProps } from \"../internal/slot\";\n\nexport type Presentation = \"dialog\" | \"drawer\" | \"sheet\";\nexport type SwipeDirection = \"up\" | \"down\" | \"left\" | \"right\";\ntype ChangeDetails = {\n reason: string;\n event: Event;\n cancel: () => void;\n};\nexport type SurfaceProps = {\n open: boolean;\n presentation: Presentation;\n swipeDirection: SwipeDirection;\n /** Ids the trigger points at; caller-supplied ids win. */\n contentId: string;\n titleId?: string | undefined;\n trigger: { current: HTMLElement | null };\n onOpen: () => void;\n onClose: () => void;\n onEscape?: ((event: KeyboardEvent) => void) | undefined;\n onInteractOutside?: ((event: Event) => void) | undefined;\n forceMount?: true | undefined;\n container?: HTMLElement | null | undefined;\n state: unknown;\n contentProps: PartProps<never>;\n contentRef: Ref<HTMLElement>;\n};\ntype Parts = { Title: typeof Dialog.Title; Close: typeof Dialog.Close };\nconst PartsContext = createContext<Parts | null>(null);\nexport function useEngineParts() {\n const parts = useContext(PartsContext);\n if (!parts) {\n throw new Error(\n \"Overlay.Title and Overlay.Close must be inside Overlay.Content\",\n );\n }\n return parts;\n}\n/** Route Base UI's open changes through the URL, letting callbacks cancel a dismissal. */\nfunction changeHandler(props: SurfaceProps) {\n return (open: boolean, details: ChangeDetails) => {\n if (open) {\n props.onOpen();\n return;\n }\n const callback =\n details.reason === \"escape-key\"\n ? (props.onEscape as ((event: Event) => void) | undefined)\n : details.reason === \"outside-press\" || details.reason === \"focus-out\"\n ? props.onInteractOutside\n : undefined;\n callback?.(details.event);\n if (details.event?.defaultPrevented && callback) {\n details.cancel();\n return;\n }\n props.onClose();\n };\n}\nfunction Popup({\n props,\n Component,\n children,\n}: {\n props: SurfaceProps;\n Component: typeof Dialog.Popup;\n children?: ReactNode;\n}) {\n const { contentProps, contentRef, presentation, state } = props;\n const { id: _ownId, ...rest } = contentProps as PartProps<never> & {\n id?: string;\n };\n const id = partId(contentProps as Parameters<typeof partId>[0]);\n return (\n <Component\n id={id ?? props.contentId}\n // Prefer an explicit [data-autofocus] target, else Base UI's first tabbable.\n initialFocus={() =>\n document\n .getElementById(id ?? props.contentId)\n ?.querySelector<HTMLElement>(\"[data-autofocus]\") ?? true\n }\n finalFocus={() => props.trigger.current ?? true}\n render={(baseProps) => {\n const { ref: baseRef, ...base } = baseProps as unknown as Record<\n string,\n unknown\n > & {\n ref?: Ref<HTMLElement>;\n };\n return part(\n \"div\",\n (children === undefined\n ? rest\n : { ...rest, children }) as PartProps<unknown>,\n state,\n {\n ...base,\n \"aria-modal\": true,\n \"data-presentation\": presentation,\n \"data-state\": props.open ? \"open\" : \"closed\",\n },\n composeRefs(baseRef, contentRef),\n );\n }}\n />\n );\n}\nexport function Surface(props: SurfaceProps & { children?: ReactNode }) {\n const { children, forceMount, container, presentation } = props;\n const portal = {\n ...(forceMount ? { keepMounted: true } : {}),\n ...(container !== undefined ? { container } : {}),\n };\n if (presentation === \"dialog\") {\n return (\n <Dialog.Root\n open={props.open}\n onOpenChange={changeHandler(props) as never}\n >\n <PartsContext.Provider\n value={{ Title: Dialog.Title, Close: Dialog.Close }}\n >\n <Dialog.Portal {...portal}>\n {/* Unstyled; templates style [data-zappie-backdrop] with data-state/data-presentation. */}\n <Dialog.Backdrop\n data-zappie-backdrop=\"\"\n data-presentation={presentation}\n data-state={props.open ? \"open\" : \"closed\"}\n />\n <Popup props={props} Component={Dialog.Popup}>\n {children}\n </Popup>\n </Dialog.Portal>\n </PartsContext.Provider>\n </Dialog.Root>\n );\n }\n return (\n <Drawer.Root\n open={props.open}\n onOpenChange={changeHandler(props) as never}\n swipeDirection={props.swipeDirection}\n >\n <PartsContext.Provider\n value={{\n Title: Drawer.Title as typeof Dialog.Title,\n Close: Drawer.Close as typeof Dialog.Close,\n }}\n >\n <Drawer.Portal {...portal}>\n <Drawer.Backdrop\n data-zappie-backdrop=\"\"\n data-presentation={presentation}\n data-state={props.open ? \"open\" : \"closed\"}\n />\n {/* Keeps focused fields visible above the on-screen keyboard in bottom sheets. */}\n <Drawer.VirtualKeyboardProvider>\n <Drawer.Viewport>\n <Popup\n props={props}\n Component={Drawer.Popup as typeof Dialog.Popup}\n >\n {children}\n </Popup>\n </Drawer.Viewport>\n </Drawer.VirtualKeyboardProvider>\n </Drawer.Portal>\n </PartsContext.Provider>\n </Drawer.Root>\n );\n}\n/** Title and Close rendered with whichever engine (dialog or drawer) owns the content. */\ntype EnginePartProps = PartProps<unknown> & {\n id?: string | undefined;\n state: unknown;\n};\nexport const EngineTitle = forwardRef<HTMLElement, EnginePartProps>(\n function EngineTitle({ state, ...props }, ref) {\n const { Title } = useEngineParts();\n const id = partId(props as Parameters<typeof partId>[0]);\n return (\n <Title\n {...(id ? { id } : {})}\n render={(base) => {\n const { ref: baseRef, ...rest } = base as unknown as Record<\n string,\n unknown\n > & {\n ref?: Ref<HTMLElement>;\n };\n return part(\"h2\", props, state, rest, composeRefs(baseRef, ref));\n }}\n />\n );\n },\n);\nexport const EngineClose = forwardRef<HTMLElement, EnginePartProps>(\n function EngineClose({ state, ...props }, ref) {\n const { Close } = useEngineParts();\n return (\n <Close\n render={(base) => {\n const { ref: baseRef, ...rest } = base as unknown as Record<\n string,\n unknown\n > & {\n ref?: Ref<HTMLElement>;\n };\n return part(\n \"button\",\n props,\n state,\n { type: \"button\", ...rest },\n composeRefs(baseRef, ref),\n );\n }}\n />\n );\n },\n);\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": ["EngineTitle", "EngineClose"]
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/overlay/history.ts"],
|
|
4
|
-
"sourcesContent": ["export type OverlayEntry = { param: string; id: string; stack: string[] };\nexport type OverlayQuery = Record<string, string | null | undefined>;\nexport type CloseOptions = {\n fallback?: OverlayQuery | undefined;\n omitParams?: string[] | undefined;\n};\nexport const LOCATION_EVENT = \"zappie:location\";\nexport function getCurrentOverlayEntry(): OverlayEntry | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n const entry = window.history.state?.__zappieOverlay;\n if (\n !entry ||\n typeof entry.param !== \"string\" ||\n typeof entry.id !== \"string\"\n ) {\n return null;\n }\n return {\n param: entry.param,\n id: entry.id,\n stack: Array.isArray(entry.stack)\n ? entry.stack.filter((key: unknown) => typeof key === \"string\")\n : [],\n };\n}\nexport function subscribeLocation(listener: () => void) {\n window.addEventListener(\"popstate\", listener);\n window.addEventListener(LOCATION_EVENT, listener);\n return () => {\n window.removeEventListener(\"popstate\", listener);\n window.removeEventListener(LOCATION_EVENT, listener);\n };\n}\nexport function locationSnapshot() {\n return window.location.search;\n}\nfunction applyQuery(url: URL, query: OverlayQuery) {\n for (const [key, value] of Object.entries(query)) {\n if (value == null) {\n url.searchParams.delete(key);\n } else {\n url.searchParams.set(key, value);\n }\n }\n}\nfunction write(\n type: \"pushState\" | \"replaceState\",\n url: URL,\n overlay: OverlayEntry | null,\n) {\n const state = { ...window.history.state };\n delete state.__zappieOverlay;\n if (overlay) {\n state.__zappieOverlay = overlay;\n }\n state.__zappieBase ??= window.location.pathname;\n if (url.pathname !== window.location.pathname) {\n delete state.__NA;\n delete state.__PRIVATE_NEXTJS_INTERNALS_TREE;\n }\n // Bypass Next's wrapper: shallow overlay writes must not remount the page.\n const prototype = Object.getPrototypeOf(window.history) as History;\n prototype[type].call(window.history, state, \"\", url.href);\n window.dispatchEvent(new Event(LOCATION_EVENT));\n}\nexport function openOverlay(\n param: string,\n id: string,\n query: OverlayQuery = {},\n) {\n const url = new URL(window.location.href);\n applyQuery(url, query);\n url.searchParams.set(param, id);\n if (url.href === window.location.href) {\n return;\n }\n const previous = getCurrentOverlayEntry();\n write(\"pushState\", url, {\n param,\n id,\n stack: [...(previous?.stack ?? []), `${param}:${id}`],\n });\n}\nexport function closeOverlay(\n param: string,\n id?: string,\n options: CloseOptions = {},\n) {\n const url = new URL(window.location.href);\n if (!url.searchParams.has(param)) {\n return;\n }\n const entry = getCurrentOverlayEntry();\n const stack = entry?.stack ?? [];\n const index = id\n ? stack.lastIndexOf(`${param}:${id}`)\n : stack.reduce(\n (found, key, index) => (key.startsWith(`${param}:`) ? index : found),\n -1,\n );\n if (index >= 0) {\n window.history.go(-(stack.length - index));\n return;\n }\n applyQuery(url, options.fallback ?? {});\n if (!options.fallback || !(param in options.fallback)) {\n url.searchParams.delete(param);\n }\n for (const omitted of options.omitParams ?? []) {\n url.searchParams.delete(omitted);\n }\n write(\n \"replaceState\",\n url,\n entry && url.searchParams.get(entry.param) === entry.id ? entry : null,\n );\n}\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": ["index"]
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/overlay/react.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type Ref,\n} from \"react\";\nimport { createPortal, flushSync } from \"react-dom\";\nimport { composeRefs, part, partId, type PartProps } from \"../internal/slot\";\nimport type { Presentation, SwipeDirection } from \"./engine\";\nimport {\n closeOverlay,\n locationSnapshot,\n openOverlay,\n subscribeLocation,\n type CloseOptions,\n type OverlayQuery,\n} from \"./history\";\n\ntype EngineModule = typeof import(\"./engine\");\nlet engineModule: EngineModule | undefined;\nlet enginePromise: Promise<EngineModule> | undefined;\n/**\n * Load the overlay engine (Base UI dialog and drawer). It is a separate chunk, prefetched once\n * the page has loaded and the browser is idle, and on trigger hover/focus, so it is ready by\n * the first open.\n */\nexport function loadOverlayEngine(): Promise<EngineModule> {\n enginePromise ??= import(\"./engine\").then(\n (module) => (engineModule = module),\n (error: unknown) => {\n // A failed chunk load (e.g. offline) must not poison later opens.\n enginePromise = undefined;\n throw error;\n },\n );\n return enginePromise;\n}\n\n/**\n * Control an overlay whose open state is stored in a URL query parameter.\n *\n * @param param - The query parameter to read and update.\n * @param id - The value that identifies this overlay as open.\n * @param options - Fallback query values and query parameters to omit when closing.\n * @returns Reactive open state and history-backed open and close actions.\n */\nexport function useOverlay(param: string, id: string, options?: CloseOptions) {\n const search = useSyncExternalStore(\n subscribeLocation,\n locationSnapshot,\n () => \"\",\n );\n const optionsRef = useRef(options);\n optionsRef.current = options;\n const open = useCallback(\n (query?: OverlayQuery) => openOverlay(param, id, query),\n [param, id],\n );\n const close = useCallback(\n () => closeOverlay(param, id, optionsRef.current),\n [param, id],\n );\n return useMemo(\n () => ({\n isOpen: new URLSearchParams(search).get(param) === id,\n open,\n close,\n }),\n [search, param, id, open, close],\n );\n}\n\n/** Tailwind's default min-width breakpoints. */\nconst BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280 } as const;\nexport type ResponsivePresentation =\n | Presentation\n | ({ base: Presentation } & Partial<\n Record<keyof typeof BREAKPOINTS, Presentation>\n >);\n/** Choose a presentation using media queries. The server renders `base`. */\nfunction usePresentation(value: ResponsivePresentation): Presentation {\n // Serialise so inline objects don't resubscribe on every render.\n const key = typeof value === \"string\" ? value : JSON.stringify(value);\n const resolve = useCallback((): Presentation => {\n const parsed = (\n key.startsWith(\"{\") ? JSON.parse(key) : key\n ) as ResponsivePresentation;\n if (typeof parsed === \"string\") {\n return parsed;\n }\n let current = parsed.base;\n for (const [name, width] of Object.entries(BREAKPOINTS)) {\n const at = parsed[name as keyof typeof BREAKPOINTS];\n if (at && window.matchMedia(`(min-width: ${width}px)`).matches) {\n current = at;\n }\n }\n return current;\n }, [key]);\n const subscribe = useCallback(\n (onChange: () => void) => {\n if (!key.startsWith(\"{\")) {\n return () => {};\n }\n const queries = Object.values(BREAKPOINTS).map((width) =>\n window.matchMedia(`(min-width: ${width}px)`),\n );\n queries.forEach((query) => query.addEventListener(\"change\", onChange));\n return () =>\n queries.forEach((query) =>\n query.removeEventListener(\"change\", onChange),\n );\n },\n [key],\n );\n return useSyncExternalStore(subscribe, resolve, () =>\n typeof value === \"string\" ? value : value.base,\n );\n}\n\ntype OverlayState = ReturnType<typeof useOverlay>;\ntype OverlayContext = OverlayState & {\n presentation: Presentation;\n swipeDirection: SwipeDirection;\n contentId: string;\n setContentId: (id: string) => void;\n trigger: { current: HTMLElement | null };\n};\nconst Context = createContext<OverlayContext | null>(null);\nfunction useContextValue() {\n const value = useContext(Context);\n if (!value) {\n throw new Error(\"Overlay parts require Overlay.Root\");\n }\n return value;\n}\nconst prefetch = () => void loadOverlayEngine().catch(() => {});\n\n// The prefetch is scheduled when this module loads, not when an Overlay.Root mounts: a template\n// that loads its overlay with deferred() mounts the Root only on the tap, which would leave the\n// engine's download inside the open. Waiting for the load event keeps it out of the page load.\nfunction prefetchWhenIdle() {\n if (typeof window === \"undefined\") {\n return;\n }\n const schedule = () => {\n if (engineModule) {\n return;\n }\n if (typeof window.requestIdleCallback === \"function\") {\n window.requestIdleCallback(prefetch);\n } else {\n window.setTimeout(prefetch, 1500);\n }\n };\n if (document.readyState === \"complete\") {\n schedule();\n } else {\n window.addEventListener(\"load\", schedule, { once: true });\n }\n}\nprefetchWhenIdle();\n\n/**\n * The URL is the only open state. The engine (Base UI) supplies the modal mechanics: portal,\n * focus scope, scroll lock, outside and Escape dismissal, exit presence and, for drawers and\n * sheets, swipe-to-dismiss. Every open or close it requests goes through the history core.\n */\nfunction Root({\n param,\n id,\n presentation = \"dialog\",\n swipeDirection,\n fallback,\n omitParams,\n navigation,\n ...props\n}: PartProps<OverlayState> & {\n param: string;\n id: string;\n /** Your URL adapter for overlays whose open state lives in the path. */\n navigation?: OverlayState;\n /** A `dialog`, swipeable `drawer` or `sheet`. Use `{ base, sm, md, lg, xl }` for responsive layouts. */\n presentation?: ResponsivePresentation;\n /** Swipe direction to dismiss. Defaults to `down` for drawers and `right` for sheets. */\n swipeDirection?: SwipeDirection;\n} & CloseOptions) {\n const queryOverlay = useOverlay(param, id, { fallback, omitParams });\n const overlay = navigation ?? queryOverlay;\n const resolved = usePresentation(presentation);\n const generated = useId();\n const [contentId, setContentId] = useState(`zappie-overlay-${generated}`);\n const trigger = useRef<HTMLElement | null>(null);\n const context = useMemo(\n () => ({\n ...overlay,\n presentation: resolved,\n swipeDirection:\n swipeDirection ?? (resolved === \"sheet\" ? \"right\" : \"down\"),\n contentId,\n setContentId,\n trigger,\n }),\n [overlay, resolved, swipeDirection, contentId],\n );\n return (\n <Context.Provider value={context}>\n {part(\"div\", props, overlay, {\n \"data-state\": overlay.isOpen ? \"open\" : \"closed\",\n \"data-presentation\": resolved,\n })}\n </Context.Provider>\n );\n}\nconst Trigger = forwardRef<HTMLElement, PartProps<OverlayState>>(\n function Trigger(props, ref) {\n const ctx = useContextValue();\n return part(\n \"button\",\n props,\n ctx,\n {\n type: \"button\",\n \"aria-haspopup\": \"dialog\",\n \"aria-expanded\": ctx.isOpen,\n \"aria-controls\": ctx.contentId,\n \"data-state\": ctx.isOpen ? \"open\" : \"closed\",\n onPointerEnter: prefetch,\n onFocus: prefetch,\n onClick: () => ctx.open(),\n },\n composeRefs(ref, (node: HTMLElement | null) => {\n ctx.trigger.current = node;\n }),\n );\n },\n);\nexport type OverlayContentProps = PartProps<OverlayState> & {\n /** Runs on Escape. Call `event.preventDefault()` to keep the overlay open. */\n onEscape?: (event: KeyboardEvent) => void;\n /** Runs on an outside pointer press or focus. Call `event.preventDefault()` to keep the overlay open. */\n onInteractOutside?: (event: Event) => void;\n /** Keep the content mounted while closed, for exit animations driven by `data-state`. */\n forceMount?: true;\n /** Where to mount the content. Defaults to `document.body`. */\n container?: HTMLElement | null;\n};\n\ntype EngineKind = \"dialog\" | \"drawer\";\nconst engineKind = (presentation: Presentation): EngineKind =>\n presentation === \"dialog\" ? \"dialog\" : \"drawer\";\n/** The engine paths (Base UI's dialog, or its drawer) that have opened on this page. */\nconst warmed = new Set<EngineKind>();\n/** Whether the engine owns the content around a nested Root yet. */\nconst ContentContext = createContext<{ attached: boolean } | null>(null);\n// Everything a shopper can do to the page while the frame waits for the engine.\nconst HELD_EVENTS = [\n \"pointerdown\",\n \"mousedown\",\n \"touchstart\",\n \"click\",\n \"keydown\",\n \"wheel\",\n \"touchmove\",\n] as const;\ntype StartTimes = Map<string, CSSNumberish | null>;\nconst animationsOf = (node: Element | null | undefined) =>\n typeof node?.getAnimations === \"function\" ? node.getAnimations() : [];\n/** The start time of each CSS animation running on a node, by animation name. */\nfunction startTimes(node: Element | null | undefined): StartTimes {\n const times: StartTimes = new Map();\n for (const animation of animationsOf(node)) {\n if (\"animationName\" in animation) {\n times.set((animation as CSSAnimation).animationName, animation.startTime);\n }\n }\n return times;\n}\n/** Carry the frame's running animations over to the engine's nodes, so they don't restart. */\nfunction resume(node: Element | null | undefined, times: StartTimes) {\n for (const animation of animationsOf(node)) {\n const start =\n \"animationName\" in animation\n ? times.get((animation as CSSAnimation).animationName)\n : undefined;\n if (start !== undefined && start !== null) {\n animation.startTime = start;\n }\n }\n}\n\ntype FrameProps = {\n ctx: OverlayContext;\n contentProps: PartProps<never>;\n contentRef: Ref<HTMLElement>;\n container: HTMLElement | null | undefined;\n onEscape: ((event: KeyboardEvent) => void) | undefined;\n onInteractOutside: ((event: Event) => void) | undefined;\n onAttach: (times: [StartTimes, StartTimes]) => void;\n};\n/**\n * The overlay's first frame on a page: its backdrop and its panel with the content, in the\n * engine's markup, painted without the engine. The first open on a page runs Base UI's and\n * React DOM's modal code for the first time, and V8 compiles it then, which takes several\n * frames on a slow phone. The frame paints on the tap and the engine replaces it at the start\n * of the next frame, so that work lands after the overlay is on screen.\n *\n * Until the engine is in, the frame is hidden from assistive technology and takes no input:\n * every key, press and scroll is held, so focus stays on the trigger and nothing on the page\n * can be reached. Escape and an outside press still close it, through the same callbacks.\n */\nfunction Frame({\n ctx,\n contentProps,\n contentRef,\n container,\n onEscape,\n onInteractOutside,\n onAttach,\n}: FrameProps) {\n const backdrop = useRef<HTMLDivElement>(null);\n const popup = useRef<HTMLElement | null>(null);\n const latest = useRef({ onEscape, onInteractOutside, onAttach, ctx });\n latest.current = { onEscape, onInteractOutside, onAttach, ctx };\n useLayoutEffect(() => {\n const dismiss = (\n callback: ((event: never) => void) | undefined,\n event: Event,\n ) => {\n callback?.(event as never);\n if (!(callback && event.defaultPrevented)) {\n latest.current.ctx.close();\n }\n };\n const hold = (event: Event) => {\n const inside =\n event.target instanceof Node && popup.current?.contains(event.target);\n if (\n event.type === \"keydown\" &&\n (event as KeyboardEvent).key === \"Escape\"\n ) {\n dismiss(latest.current.onEscape, event);\n } else if (event.type === \"pointerdown\" && !inside) {\n dismiss(latest.current.onInteractOutside, event);\n }\n if (event.cancelable) {\n event.preventDefault();\n }\n event.stopImmediatePropagation();\n };\n for (const type of HELD_EVENTS) {\n window.addEventListener(type, hold, { capture: true, passive: false });\n }\n // The first callback runs before this frame paints; the second starts the next frame.\n let live = true;\n let frame = requestAnimationFrame(() => {\n frame = requestAnimationFrame(() => {\n if (live) {\n latest.current.onAttach([\n startTimes(backdrop.current),\n startTimes(popup.current),\n ]);\n }\n });\n });\n return () => {\n live = false;\n cancelAnimationFrame(frame);\n for (const type of HELD_EVENTS) {\n window.removeEventListener(type, hold, { capture: true });\n }\n };\n }, []);\n const { presentation, swipeDirection } = ctx;\n const drawer = presentation !== \"dialog\";\n const { id: _ownId, ...rest } = contentProps as PartProps<never> & {\n id?: string;\n };\n const id = partId(contentProps as Parameters<typeof partId>[0]);\n const panel = part(\n \"div\",\n rest as PartProps<unknown>,\n ctx,\n {\n \"data-open\": \"\",\n ...(drawer ? { \"data-swipe-direction\": swipeDirection } : {}),\n id: id ?? ctx.contentId,\n role: \"dialog\",\n tabIndex: -1,\n style: drawer\n ? {\n \"--drawer-swipe-movement-x\": \"0px\",\n \"--drawer-swipe-movement-y\": \"0px\",\n \"--drawer-swipe-progress\": 0,\n \"--nested-drawers\": 0,\n \"--drawer-snap-point-offset\": \"0px\",\n \"--drawer-swipe-strength\": 1,\n }\n : { \"--nested-dialogs\": 0 },\n \"aria-modal\": true,\n \"data-presentation\": presentation,\n \"data-state\": \"open\",\n // The engine's panel replaces this one on the next frame and is the one announced.\n \"aria-hidden\": true,\n },\n composeRefs(contentRef, (node: HTMLElement | null) => {\n popup.current = node;\n }),\n );\n return createPortal(\n <div data-zappie-overlay-frame=\"\">\n <div\n role=\"presentation\"\n aria-hidden=\"true\"\n style={{ position: \"fixed\", inset: 0, userSelect: \"none\" }}\n />\n <div\n ref={backdrop}\n data-open=\"\"\n role=\"presentation\"\n style={\n {\n userSelect: \"none\",\n ...(drawer\n ? { \"--drawer-swipe-progress\": 0, \"--drawer-swipe-strength\": 1 }\n : {}),\n } as CSSProperties\n }\n data-zappie-backdrop=\"\"\n data-presentation={presentation}\n data-state=\"open\"\n aria-hidden=\"true\"\n />\n {drawer ? (\n <div\n data-open=\"\"\n role=\"presentation\"\n style={{ \"--drawer-keyboard-inset\": \"0px\" } as CSSProperties}\n >\n {panel}\n </div>\n ) : (\n panel\n )}\n </div>,\n container ?? document.body,\n );\n}\n\ntype Phase = \"closed\" | \"frame\" | \"engine\";\nconst Content = forwardRef<HTMLElement, OverlayContentProps>(function Content(\n { onEscape, onInteractOutside, forceMount, container, ...props },\n ref,\n) {\n const ctx = useContextValue();\n const parent = useContext(ContentContext);\n const [loaded, setEngine] = useState(engineModule);\n // Content that mounted before the idle prefetch finished still sees the engine once it is in.\n const engine = loaded ?? engineModule;\n // A page's first open of each engine path paints a frame first; see Frame. Nested overlays\n // open inside an engine that is already running, and a forced mount has no tap to answer.\n const firstPhase = (): Phase =>\n !forceMount &&\n ctx.isOpen &&\n !parent &&\n !warmed.has(engineKind(ctx.presentation))\n ? \"frame\"\n : \"engine\";\n // Once the engine is in, the surface stays mounted so it can play exit transitions.\n const [phase, setPhase] = useState<Phase>(() =>\n engine && (ctx.isOpen || forceMount) ? firstPhase() : \"closed\",\n );\n if (phase === \"closed\" && engine && (ctx.isOpen || forceMount)) {\n setPhase(firstPhase());\n }\n if (phase === \"frame\" && !ctx.isOpen) {\n setPhase(\"closed\");\n }\n const customId = partId(props as Parameters<typeof partId>[0]);\n const { setContentId } = ctx;\n useEffect(() => {\n if (customId) {\n setContentId(customId);\n }\n }, [customId, setContentId]);\n useEffect(() => {\n if (engine || !(ctx.isOpen || forceMount)) {\n return undefined;\n }\n let live = true;\n void loadOverlayEngine().then(\n (module) => {\n if (live) {\n // The engine arrived after the open, so it paints the overlay itself.\n setEngine(module);\n setPhase((current) => (current === \"closed\" ? \"engine\" : current));\n }\n },\n () => {},\n );\n return () => {\n live = false;\n };\n }, [engine, ctx.isOpen, forceMount]);\n const kind = engineKind(ctx.presentation);\n useEffect(() => {\n if (phase === \"engine\" && ctx.isOpen) {\n warmed.add(kind);\n }\n }, [phase, ctx.isOpen, kind]);\n const panel = useRef<HTMLElement | null>(null);\n const engineRef = useMemo(\n () =>\n composeRefs(ref, (node: HTMLElement | null) => {\n panel.current = node;\n }),\n [ref],\n );\n const attach = useCallback(([backdrop, popup]: [StartTimes, StartTimes]) => {\n flushSync(() => setPhase(\"engine\"));\n const node = panel.current;\n resume(node, popup);\n resume(\n node\n ?.closest(\"[data-base-ui-portal]\")\n ?.querySelector(\"[data-zappie-backdrop]\"),\n backdrop,\n );\n }, []);\n const attached = useMemo(() => ({ attached: phase === \"engine\" }), [phase]);\n if (!engine || phase === \"closed\" || (parent && !parent.attached)) {\n return null;\n }\n if (phase === \"frame\") {\n return (\n <ContentContext.Provider value={attached}>\n <PartsContext.Provider value={frameParts}>\n <Frame\n ctx={ctx}\n contentProps={props as PartProps<never>}\n contentRef={ref}\n container={container}\n onEscape={onEscape}\n onInteractOutside={onInteractOutside}\n onAttach={attach}\n />\n </PartsContext.Provider>\n </ContentContext.Provider>\n );\n }\n return (\n <ContentContext.Provider value={attached}>\n <PartsContext.Provider value={engine}>\n <engine.Surface\n open={ctx.isOpen}\n presentation={ctx.presentation}\n swipeDirection={ctx.swipeDirection}\n contentId={ctx.contentId}\n trigger={ctx.trigger}\n onOpen={() => ctx.open()}\n onClose={ctx.close}\n onEscape={onEscape}\n onInteractOutside={onInteractOutside}\n forceMount={forceMount}\n container={container}\n state={ctx}\n contentProps={props as PartProps<never>}\n contentRef={engineRef}\n />\n </PartsContext.Provider>\n </ContentContext.Provider>\n );\n});\n\ntype PartComponent = EngineModule[\"EngineTitle\"];\ntype Parts = { EngineTitle: PartComponent; EngineClose: PartComponent };\ntype FramePartProps = PartProps<unknown> & { state: unknown };\n/** Title and Close for the content: the engine's, or the frame's inert stand-ins. */\nconst PartsContext = createContext<Parts | null>(null);\nconst frameParts: Parts = {\n EngineTitle: forwardRef<HTMLElement, FramePartProps>(function FrameTitle(\n { state, ...props },\n ref,\n ) {\n return part(\"h2\", props, state, {}, ref);\n }),\n EngineClose: forwardRef<HTMLElement, FramePartProps>(function FrameClose(\n { state, ...props },\n ref,\n ) {\n return part(\"button\", props, state, { type: \"button\" }, ref);\n }),\n};\nfunction useEngine() {\n const parts = useContext(PartsContext);\n if (!parts) {\n throw new Error(\n \"Overlay.Title and Overlay.Close must be inside Overlay.Content\",\n );\n }\n return parts;\n}\nconst Title = forwardRef<HTMLElement, PartProps<OverlayState>>(\n function Title(props, ref) {\n const ctx = useContextValue();\n const { EngineTitle } = useEngine();\n return (\n <EngineTitle {...(props as PartProps<unknown>)} state={ctx} ref={ref} />\n );\n },\n);\nconst Close = forwardRef<HTMLElement, PartProps<OverlayState>>(\n function Close(props, ref) {\n const ctx = useContextValue();\n const { EngineClose } = useEngine();\n return (\n <EngineClose {...(props as PartProps<unknown>)} state={ctx} ref={ref} />\n );\n },\n);\n/**\n * Composable dialog, drawer and sheet parts with URL-backed open state and a lazy modal engine.\n * Use Root, Trigger, Content, Title and Close to connect history, focus handling and dismissal.\n */\nexport const Overlay = { Root, Trigger, Content, Title, Close };\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": ["Trigger", "Content", "Title", "Close"]
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/payment/methods.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport type { PaymentMethodKind } from \"@zappie/sdk\";\nimport {\n createContext,\n forwardRef,\n useContext,\n type KeyboardEvent,\n} from \"react\";\nimport { usePartId } from \"../internal/ids\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { useControllableState } from \"../internal/use-controllable-state\";\n/**\n * A payment-method choice with optional disabled state and an explanatory reason.\n */\nexport type PaymentMethod = {\n value: PaymentMethodKind;\n disabled?: boolean;\n reason?: string;\n};\ntype PaymentState = {\n methods: PaymentMethod[];\n value: PaymentMethodKind | null;\n setValue: (value: PaymentMethodKind) => void;\n};\nconst Context = createContext<PaymentState | null>(null);\nfunction Root({\n methods,\n value,\n defaultValue = null,\n onValueChange,\n ...props\n}: PartProps<PaymentState> & {\n methods: (PaymentMethod | PaymentMethodKind)[];\n value?: PaymentMethodKind | null;\n defaultValue?: PaymentMethodKind | null;\n onValueChange?: (value: PaymentMethodKind | null) => void;\n}) {\n const [selected, set] = useControllableState({\n value,\n defaultValue,\n onChange: onValueChange,\n });\n const normalized = methods.map((method) =>\n typeof method === \"string\" ? { value: method } : method,\n );\n const setValue = (next: PaymentMethodKind) => {\n if (\n normalized.some((method) => method.value === next && !method.disabled)\n ) {\n set(next);\n }\n };\n const state = { methods: normalized, value: selected, setValue };\n return (\n <Context.Provider value={state}>\n {part(\"div\", props, state, {\n role: \"radiogroup\",\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => {\n if (\n ![\n \"ArrowRight\",\n \"ArrowDown\",\n \"ArrowLeft\",\n \"ArrowUp\",\n \"Home\",\n \"End\",\n ].includes(event.key)\n ) {\n return;\n }\n const buttons = Array.from(\n event.currentTarget.querySelectorAll<HTMLElement>(\n '[role=\"radio\"]:not([aria-disabled=\"true\"])',\n ),\n );\n if (!buttons.length) {\n return;\n }\n event.preventDefault();\n const current = buttons.indexOf(\n document.activeElement as HTMLElement,\n );\n const index =\n event.key === \"Home\"\n ? 0\n : event.key === \"End\"\n ? buttons.length - 1\n : (current +\n ([\"ArrowLeft\", \"ArrowUp\"].includes(event.key) ? -1 : 1) +\n buttons.length) %\n buttons.length;\n buttons[index]?.focus();\n buttons[index]?.click();\n },\n })}\n </Context.Provider>\n );\n}\nconst Item = forwardRef<\n HTMLElement,\n PartProps<PaymentMethod & { selected: boolean }> & {\n value: PaymentMethodKind;\n }\n>(function Item({ value, ...props }, ref) {\n const ctx = useContext(Context);\n const id = usePartId();\n if (!ctx) {\n throw new Error(\"PaymentMethods.Item requires PaymentMethods.Root\");\n }\n const method = ctx.methods.find((method) => method.value === value);\n if (!method) {\n return null;\n }\n const selected = ctx.value === value;\n const first = ctx.methods.find((method) => !method.disabled)?.value;\n return part(\n \"button\",\n props,\n { ...method, selected },\n {\n id,\n type: \"button\",\n role: \"radio\",\n \"aria-checked\": selected,\n \"aria-disabled\": method.disabled || undefined,\n \"aria-description\": method.reason,\n disabled: method.disabled,\n tabIndex: selected || (!ctx.value && first === value) ? 0 : -1,\n \"data-disabled\": method.disabled ? \"\" : undefined,\n \"data-selected\": selected ? \"\" : undefined,\n \"data-state\": selected ? \"checked\" : \"unchecked\",\n onClick: () => ctx.setValue(value),\n },\n ref,\n );\n});\n/**\n * Composable payment-method radio controls with keyboard navigation.\n * Root manages the selection. Item renders each method and prevents selection when disabled.\n */\nexport const PaymentMethods = { Root, Item };\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": ["Item", "method"]
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/rich-text/banners.ts"],
|
|
4
|
-
"sourcesContent": ["// Multiple menu info banners are stored as one string joined by this delimiter\n// (the dashboard editor joins on it; here we split it back out for display).\n/**\n * The separator used to store multiple menu info banners in a single string.\n */\nexport const BANNER_DELIMITER = \"<::>\";\n\n// The delimiter looks like an HTML tag, so once it lands inside the dashboard's\n// rich-text editor as content it gets serialised as escaped text \u2014 `<::>`,\n// usually on its own `<p>` line. Splitting on the literal `<::>` then finds\n// nothing and every banner collapses into one. Convert the escaped form (and its\n// `<p>` wrapper) back to the real delimiter before splitting.\nconst normalizeBannerDelimiters = (html: string): string =>\n html\n .replace(/<p>\\s*(?:<::>|<::>)\\s*<\\/p>/gi, BANNER_DELIMITER)\n .replaceAll(\"<::>\", BANNER_DELIMITER);\n\n/** Split a stored info-banner value into individual banner HTML fragments. */\nexport const splitInfoBanner = (infoBanner?: string | null): string[] =>\n normalizeBannerDelimiters(infoBanner ?? \"\").split(BANNER_DELIMITER);\n"],
|
|
5
4
|
"mappings": ";AAKO,IAAM,mBAAmB;AAOhC,IAAM,4BAA4B,CAAC,SACjC,KACG,QAAQ,uCAAuC,gBAAgB,EAC/D,WAAW,cAAc,gBAAgB;AAGvC,IAAM,kBAAkB,CAAC,eAC9B,0BAA0B,cAAc,EAAE,EAAE,MAAM,gBAAgB;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/rich-text/browser.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ParsedRichTextNode } from \"./core\";\n\nconst TEXT_NODE = 3;\nconst COMMENT_NODE = 8;\n\nconst convertNode = (node: Node): ParsedRichTextNode => {\n if (node.nodeType === TEXT_NODE) {\n return { type: \"text\", data: node.nodeValue || \"\" };\n }\n\n if (node.nodeType === COMMENT_NODE) {\n return { type: \"comment\" };\n }\n\n return {\n type: \"tag\",\n name: node.nodeName.toLowerCase(),\n children: Array.from(node.childNodes).map(convertNode),\n };\n};\n\n/**\n * Parse HTML with the browser DOMParser and convert body children to the shared rich-text node format.\n * Requires a DOM environment. Pair with createSanitizeRichText to remove unsupported content.\n */\nexport const parseRichText = (html: string): ParsedRichTextNode[] => {\n const document = new DOMParser().parseFromString(html, \"text/html\");\n return Array.from(document.body.childNodes).map(convertNode);\n};\n"],
|
|
5
4
|
"mappings": ";AAEA,IAAM,YAAY;AAClB,IAAM,eAAe;AAErB,IAAM,cAAc,CAAC,SAAmC;AACtD,MAAI,KAAK,aAAa,WAAW;AAC/B,WAAO,EAAE,MAAM,QAAQ,MAAM,KAAK,aAAa,GAAG;AAAA,EACpD;AAEA,MAAI,KAAK,aAAa,cAAc;AAClC,WAAO,EAAE,MAAM,UAAU;AAAA,EAC3B;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,KAAK,SAAS,YAAY;AAAA,IAChC,UAAU,MAAM,KAAK,KAAK,UAAU,EAAE,IAAI,WAAW;AAAA,EACvD;AACF;AAMO,IAAM,gBAAgB,CAAC,SAAuC;AACnE,QAAM,WAAW,IAAI,UAAU,EAAE,gBAAgB,MAAM,WAAW;AAClE,SAAO,MAAM,KAAK,SAAS,KAAK,UAAU,EAAE,IAAI,WAAW;AAC7D;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/rich-text/core.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * A parser-neutral rich-text node containing its type, optional tag name, text and children.\n */\nexport type ParsedRichTextNode = {\n type: string;\n name?: string;\n data?: string;\n children?: ParsedRichTextNode[];\n};\n\n/**\n * A parser adapter that converts HTML into the node tree consumed by the rich-text sanitizer.\n */\nexport type ParseRichText = (html: string) => ParsedRichTextNode[];\n\nconst ALLOWED_TAGS = new Set([\n \"b\",\n \"blockquote\",\n \"br\",\n \"code\",\n \"del\",\n \"em\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"hr\",\n \"i\",\n \"li\",\n \"ol\",\n \"p\",\n \"pre\",\n \"s\",\n \"span\",\n \"strike\",\n \"strong\",\n \"sub\",\n \"sup\",\n \"u\",\n \"ul\",\n]);\n\nconst DROP_CONTENT_TAGS = new Set([\n \"audio\",\n \"canvas\",\n \"embed\",\n \"iframe\",\n \"math\",\n \"noscript\",\n \"object\",\n \"script\",\n \"style\",\n \"svg\",\n \"template\",\n \"video\",\n]);\n\nconst VOID_TAGS = new Set([\"br\", \"hr\"]);\n\nconst escapeText = (value: string): string =>\n value\n .replaceAll(\"\\r\\n\", \"\\n\")\n .replaceAll(\"\\r\", \"\\n\")\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"\\u00a0\", \" \")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\n\nconst serializeNode = (node: ParsedRichTextNode): string => {\n if (node.type === \"text\") {\n return escapeText(node.data || \"\");\n }\n\n if (node.type === \"comment\" || node.type === \"directive\") {\n return \"\";\n }\n\n const tagName = node.name?.toLowerCase();\n if (tagName && DROP_CONTENT_TAGS.has(tagName)) {\n return \"\";\n }\n\n const children = (node.children || []).map(serializeNode).join(\"\");\n if (!tagName || !ALLOWED_TAGS.has(tagName)) {\n return children;\n }\n\n if (VOID_TAGS.has(tagName)) {\n return `<${tagName}>`;\n }\n\n return `<${tagName}>${children}</${tagName}>`;\n};\n\n/**\n * Create a sanitizer using your HTML parser adapter.\n * Keeps supported formatting tags without attributes, escapes text and removes unsafe element content.\n *\n * @param parseRichText - A parser that returns the shared rich-text node format.\n * @returns A sanitizer that accepts HTML and returns sanitized HTML, or an empty string for empty input.\n */\nexport const createSanitizeRichText =\n (parseRichText: ParseRichText) =>\n (html?: string | null): string => {\n if (!html) {\n return \"\";\n }\n\n return parseRichText(html).map(serializeNode).join(\"\");\n };\n"],
|
|
5
4
|
"mappings": ";AAeA,IAAM,eAAe,oBAAI,IAAI;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,oBAAoB,oBAAI,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,YAAY,oBAAI,IAAI,CAAC,MAAM,IAAI,CAAC;AAEtC,IAAM,aAAa,CAAC,UAClB,MACG,WAAW,QAAQ,IAAI,EACvB,WAAW,MAAM,IAAI,EACrB,WAAW,KAAK,OAAO,EACvB,WAAW,QAAU,QAAQ,EAC7B,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM;AAE3B,IAAM,gBAAgB,CAAC,SAAqC;AAC1D,MAAI,KAAK,SAAS,QAAQ;AACxB,WAAO,WAAW,KAAK,QAAQ,EAAE;AAAA,EACnC;AAEA,MAAI,KAAK,SAAS,aAAa,KAAK,SAAS,aAAa;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,KAAK,MAAM,YAAY;AACvC,MAAI,WAAW,kBAAkB,IAAI,OAAO,GAAG;AAC7C,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,KAAK,YAAY,CAAC,GAAG,IAAI,aAAa,EAAE,KAAK,EAAE;AACjE,MAAI,CAAC,WAAW,CAAC,aAAa,IAAI,OAAO,GAAG;AAC1C,WAAO;AAAA,EACT;AAEA,MAAI,UAAU,IAAI,OAAO,GAAG;AAC1B,WAAO,IAAI,OAAO;AAAA,EACpB;AAEA,SAAO,IAAI,OAAO,IAAI,QAAQ,KAAK,OAAO;AAC5C;AASO,IAAM,yBACX,CAAC,kBACD,CAAC,SAAiC;AAChC,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AAEA,SAAO,cAAc,IAAI,EAAE,IAAI,aAAa,EAAE,KAAK,EAAE;AACvD;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/search/engine.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Category, Item, Menu } from \"@zappie/sdk\";\n/**\n * A scored item or category returned by menu search, ordered by descending score.\n */\nexport type SearchResult =\n | { type: \"item\"; item: Item; score: number }\n | { type: \"category\"; category: Category; score: number };\nexport function normalizeSearch(text: string) {\n return text\n .normalize(\"NFD\")\n .replace(/\\p{M}/gu, \"\")\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean);\n}\nfunction near(a: string, b: string) {\n if (Math.abs(a.length - b.length) > 1) {\n return false;\n }\n let i = 0;\n let j = 0;\n let edits = 0;\n while (i < a.length && j < b.length) {\n if (a[i] === b[j]) {\n i++;\n j++;\n continue;\n }\n if (++edits > 1) {\n return false;\n }\n if (a.length === b.length) {\n if (a[i] === b[j + 1] && a[i + 1] === b[j]) {\n i += 2;\n j += 2;\n } else {\n i++;\n j++;\n }\n } else if (a.length > b.length) {\n i++;\n } else {\n j++;\n }\n }\n return edits + (a.length - i) + (b.length - j) <= 1;\n}\n/**\n * Index item names, tags and descriptions plus category names and descriptions for local search.\n * Matches case- and accent-insensitive prefixes and allows a single edit for query tokens of at least four characters.\n *\n * @param menu - The menu snapshot you want to search.\n * @returns A search function that requires every query token to match and sorts results by descending score.\n */\nexport function createSearchIndex(menu: Menu) {\n const entries = [\n ...Object.values(menu.items).map((item) => ({\n result: { type: \"item\" as const, item },\n fields: [\n normalizeSearch(item.name),\n normalizeSearch(item.tags.join(\" \")),\n normalizeSearch(item.description ?? \"\"),\n ],\n })),\n ...menu.categories.map((category) => ({\n result: { type: \"category\" as const, category },\n fields: [\n normalizeSearch(category.name),\n [],\n normalizeSearch(category.description ?? \"\"),\n ],\n })),\n ];\n return (query: string): SearchResult[] => {\n const tokens = normalizeSearch(query);\n if (!tokens.length) {\n return [];\n }\n const results: SearchResult[] = [];\n for (const entry of entries) {\n let score = 0;\n for (const token of tokens) {\n let best = 0;\n for (let field = 0; field < entry.fields.length; field++) {\n const weight = [30, 20, 10][field]!;\n for (const word of entry.fields[field]!) {\n if (word.startsWith(token)) {\n best = Math.max(best, weight + (word === token ? 3 : 2));\n } else if (token.length >= 4 && near(token, word)) {\n best = Math.max(best, weight);\n }\n }\n }\n if (!best) {\n score = 0;\n break;\n }\n score += best;\n }\n if (score) {\n results.push({ ...entry.result, score });\n }\n }\n return results.sort((a, b) => b.score - a.score);\n };\n}\n"],
|
|
5
4
|
"mappings": ";AAOO,SAAS,gBAAgB,MAAc;AAC5C,SAAO,KACJ,UAAU,KAAK,EACf,QAAQ,WAAW,EAAE,EACrB,YAAY,EACZ,MAAM,iBAAiB,EACvB,OAAO,OAAO;AACnB;AACA,SAAS,KAAK,GAAW,GAAW;AAClC,MAAI,KAAK,IAAI,EAAE,SAAS,EAAE,MAAM,IAAI,GAAG;AACrC,WAAO;AAAA,EACT;AACA,MAAI,IAAI;AACR,MAAI,IAAI;AACR,MAAI,QAAQ;AACZ,SAAO,IAAI,EAAE,UAAU,IAAI,EAAE,QAAQ;AACnC,QAAI,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG;AACjB;AACA;AACA;AAAA,IACF;AACA,QAAI,EAAE,QAAQ,GAAG;AACf,aAAO;AAAA,IACT;AACA,QAAI,EAAE,WAAW,EAAE,QAAQ;AACzB,UAAI,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG;AAC1C,aAAK;AACL,aAAK;AAAA,MACP,OAAO;AACL;AACA;AAAA,MACF;AAAA,IACF,WAAW,EAAE,SAAS,EAAE,QAAQ;AAC9B;AAAA,IACF,OAAO;AACL;AAAA,IACF;AAAA,EACF;AACA,SAAO,SAAS,EAAE,SAAS,MAAM,EAAE,SAAS,MAAM;AACpD;AAQO,SAAS,kBAAkB,MAAY;AAC5C,QAAM,UAAU;AAAA,IACd,GAAG,OAAO,OAAO,KAAK,KAAK,EAAE,IAAI,CAAC,UAAU;AAAA,MAC1C,QAAQ,EAAE,MAAM,QAAiB,KAAK;AAAA,MACtC,QAAQ;AAAA,QACN,gBAAgB,KAAK,IAAI;AAAA,QACzB,gBAAgB,KAAK,KAAK,KAAK,GAAG,CAAC;AAAA,QACnC,gBAAgB,KAAK,eAAe,EAAE;AAAA,MACxC;AAAA,IACF,EAAE;AAAA,IACF,GAAG,KAAK,WAAW,IAAI,CAAC,cAAc;AAAA,MACpC,QAAQ,EAAE,MAAM,YAAqB,SAAS;AAAA,MAC9C,QAAQ;AAAA,QACN,gBAAgB,SAAS,IAAI;AAAA,QAC7B,CAAC;AAAA,QACD,gBAAgB,SAAS,eAAe,EAAE;AAAA,MAC5C;AAAA,IACF,EAAE;AAAA,EACJ;AACA,SAAO,CAAC,UAAkC;AACxC,UAAM,SAAS,gBAAgB,KAAK;AACpC,QAAI,CAAC,OAAO,QAAQ;AAClB,aAAO,CAAC;AAAA,IACV;AACA,UAAM,UAA0B,CAAC;AACjC,eAAW,SAAS,SAAS;AAC3B,UAAI,QAAQ;AACZ,iBAAW,SAAS,QAAQ;AAC1B,YAAI,OAAO;AACX,iBAAS,QAAQ,GAAG,QAAQ,MAAM,OAAO,QAAQ,SAAS;AACxD,gBAAM,SAAS,CAAC,IAAI,IAAI,EAAE,EAAE,KAAK;AACjC,qBAAW,QAAQ,MAAM,OAAO,KAAK,GAAI;AACvC,gBAAI,KAAK,WAAW,KAAK,GAAG;AAC1B,qBAAO,KAAK,IAAI,MAAM,UAAU,SAAS,QAAQ,IAAI,EAAE;AAAA,YACzD,WAAW,MAAM,UAAU,KAAK,KAAK,OAAO,IAAI,GAAG;AACjD,qBAAO,KAAK,IAAI,MAAM,MAAM;AAAA,YAC9B;AAAA,UACF;AAAA,QACF;AACA,YAAI,CAAC,MAAM;AACT,kBAAQ;AACR;AAAA,QACF;AACA,iBAAS;AAAA,MACX;AACA,UAAI,OAAO;AACT,gBAAQ,KAAK,EAAE,GAAG,MAAM,QAAQ,MAAM,CAAC;AAAA,MACzC;AAAA,IACF;AACA,WAAO,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAAA,EACjD;AACF;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
package/dist/search/index.js.map
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/search/index.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport { useCart, useMenu, useZappie } from \"@zappie/sdk/react\";\nimport {\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useMemo,\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\";\nimport { createSearchIndex, type SearchResult } from \"./engine\";\n/**\n * Search query, results and combobox navigation exposed to search parts and render functions.\n */\nexport type SearchState = ReturnType<typeof useCombobox<SearchResult>> & {\n query: string;\n setQuery: (query: string) => void;\n results: SearchResult[];\n setComposing: (value: boolean) => void;\n error?: string;\n retry?: () => void;\n};\nconst Context = createContext<SearchState | null>(null);\nfunction useSearch() {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\"Search parts require Search.Root\");\n }\n return ctx;\n}\n/**\n * Externally supplied search results and optional loading, error, retry and composition controls.\n */\nexport type SearchSource = {\n results: SearchResult[];\n isLoading?: boolean;\n isStale?: boolean;\n error?: string;\n retry?: () => void;\n onCompositionChange?: (composing: boolean) => void;\n};\ntype RootProps = Omit<PartProps<SearchState>, \"onSelect\"> & {\n announcement?: (count: number) => string;\n query?: string;\n defaultQuery?: string;\n onQueryChange?: (query: string) => void;\n onSelect?: (result: SearchResult) => void;\n source?: SearchSource;\n};\nfunction Root(props: RootProps) {\n if (props.source) {\n return <ControlledRoot {...props} source={props.source} />;\n }\n return <UncontrolledRoot {...props} />;\n}\nfunction UncontrolledRoot(props: RootProps) {\n const cart = useCart();\n const menu = useMenu(cart.branchId ?? undefined);\n const [query, setQuery] = useControllableState({\n value: props.query,\n defaultValue: props.defaultQuery ?? \"\",\n onChange: props.onQueryChange,\n });\n const index = useMemo(\n () => (menu.data ? createSearchIndex(menu.data) : () => []),\n [menu.data],\n );\n const results = useMemo(() => index(query), [index, query]);\n return (\n <ControlledRoot\n {...props}\n query={query}\n onQueryChange={setQuery}\n source={{ results, isLoading: menu.isLoading, isStale: menu.isStale }}\n />\n );\n}\nfunction ControlledRoot({\n query,\n defaultQuery = \"\",\n onQueryChange,\n onSelect = () => {},\n announcement = countAnnouncement,\n source,\n ...props\n}: RootProps & { source: SearchSource }) {\n const client = useZappie();\n const [value, setValue] = useControllableState({\n value: query,\n defaultValue: defaultQuery,\n onChange: onQueryChange,\n });\n const results = source.results;\n const combo = useCombobox(results, onSelect);\n const setQuery = (next: string) => {\n setValue(next);\n combo.setActiveIndex(-1);\n combo.setOpen(true);\n };\n useEffect(() => {\n if (!value.trim()) {\n return;\n }\n const timer = setTimeout(\n () =>\n client._internal.emit(\"search\", {\n query: value,\n resultCount: results.length,\n }),\n 600,\n );\n return () => clearTimeout(timer);\n }, [client, value, results.length]);\n const state = {\n ...combo,\n query: value,\n setQuery,\n results,\n setComposing: (value: boolean) => source.onCompositionChange?.(value),\n ...(source.error ? { error: source.error } : {}),\n ...(source.retry ? { retry: source.retry } : {}),\n };\n return (\n <Context.Provider value={state}>\n {part(\n \"div\",\n withLiveRegion(\n props,\n combo.isOpen && value.trim() ? announcement(results.length) : \"\",\n ) as typeof props,\n state,\n {\n \"data-state\": source.error\n ? \"error\"\n : source.isLoading\n ? \"loading\"\n : source.isStale\n ? \"stale\"\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<SearchState>>(\n function Input(props, ref) {\n const state = useSearch();\n return part(\n \"input\",\n props,\n state,\n {\n ...state.inputProps,\n type: \"search\",\n onCompositionStart: () => state.setComposing(true),\n onCompositionEnd: () => state.setComposing(false),\n value: state.query,\n onChange: (event: ChangeEvent<HTMLInputElement>) =>\n state.setQuery(event.target.value),\n },\n ref,\n );\n },\n);\nconst Results = forwardRef<HTMLElement, PartProps<SearchState>>(\n function Results(props, ref) {\n const state = useSearch();\n return part(\n \"div\",\n props,\n state,\n state.listProps,\n composeRefs(ref, (node) => {\n if (node?.id && node.id !== state.listProps.id) {\n state.setListId(node.id);\n }\n }),\n );\n },\n);\nconst Result = forwardRef<\n HTMLElement,\n PartProps<SearchState> & { index: number }\n>(function Result({ index, ...props }, ref) {\n const state = useSearch();\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 menu search parts with combobox navigation and result announcements.\n * Use Root with your own source or let it index the active branch menu through your ZappieProvider.\n */\nexport const Search = { Root, Input, Results, Result };\nexport { createSearchIndex } from \"./engine\";\nexport type { SearchResult } from \"./engine\";\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": ["createSearchIndex", "value", "Input", "Results", "Result"]
|
|
7
6
|
}
|
package/dist/status/hours.js.map
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/status/hours.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Branch, OpeningTime } from \"@zappie/sdk\";\nconst DAY = 86400000;\nconst formatters = new Map<string, Intl.DateTimeFormat>();\nfunction formatter(timezone: string) {\n let value = formatters.get(timezone);\n if (!value) {\n value = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hourCycle: \"h23\",\n });\n formatters.set(timezone, value);\n }\n return value;\n}\nexport function localParts(date: Date, timezone: string) {\n const parts = Object.fromEntries(\n formatter(timezone)\n .formatToParts(date)\n .map(({ type, value }) => [type, value]),\n );\n return {\n date: `${parts.year}-${parts.month}-${parts.day}`,\n hour: Number(parts.hour),\n minute: Number(parts.minute),\n second: Number(parts.second),\n };\n}\n/**\n * Parse a zero-padded HH:mm or HH:mm:ss wall-clock value without applying a time zone.\n *\n * @returns Clock parts with 12-hour and 24-hour values, or undefined for an invalid time.\n * @example\n * ```ts\n * parseWallClock(\"14:30\"); // { hour24: 14, hour12: 2, minute: 30, second: 0, period: \"PM\" }\n * ```\n */\nexport function parseWallClock(value: string) {\n const match = /^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/.exec(value);\n if (\n !match ||\n Number(match[1]) > 23 ||\n Number(match[2]) > 59 ||\n Number(match[3] ?? 0) > 59\n ) {\n return undefined;\n }\n const hour24 = Number(match[1]);\n return {\n hour24,\n hour12: hour24 % 12 || 12,\n minute: Number(match[2]),\n second: Number(match[3] ?? 0),\n period: hour24 >= 12 ? \"PM\" : \"AM\",\n };\n}\n/**\n * Format a valid wall-clock time as a 12-hour label with AM or PM.\n * Omits zero minutes and all seconds. Returns invalid input unchanged.\n *\n * @example\n * ```ts\n * formatWallClock(\"14:00\"); // \"2 PM\"\n * formatWallClock(\"09:30:15\"); // \"9:30 AM\"\n * ```\n */\nexport function formatWallClock(value: string) {\n const time = parseWallClock(value);\n return time\n ? `${time.hour12}${time.minute ? `:${String(time.minute).padStart(2, \"0\")}` : \"\"} ${time.period}`\n : value;\n}\n/**\n * Group consecutive days with identical opening intervals into display labels.\n * Skips disabled intervals and days with no enabled hours, and orders the groups from Sunday to Saturday.\n */\nexport function formatOpeningHours(times: OpeningTime[]) {\n const names = [\n \"Sunday\",\n \"Monday\",\n \"Tuesday\",\n \"Wednesday\",\n \"Thursday\",\n \"Friday\",\n \"Saturday\",\n ];\n const groups: { days: number[]; dayLabel: string; hoursLabel: string }[] = [];\n for (let day = 0; day < 7; day++) {\n const intervals = times\n .filter((time) => !time.disabled && time.day === day)\n .sort((a, b) => a.opens.localeCompare(b.opens));\n if (!intervals.length) {\n continue;\n }\n const hoursLabel = intervals\n .map(\n (time) =>\n `${formatWallClock(time.opens)} \u2013 ${formatWallClock(time.closes)}`,\n )\n .join(\", \");\n const last = groups[groups.length - 1];\n if (\n last &&\n last.days[last.days.length - 1] === day - 1 &&\n last.hoursLabel === hoursLabel\n ) {\n last.days.push(day);\n last.dayLabel = `${names[last.days[0]!]} \u2013 ${names[day]}`;\n } else {\n groups.push({ days: [day], dayLabel: names[day]!, hoursLabel });\n }\n }\n return groups;\n}\nexport function dateOffset(date: string, days: number) {\n return new Date(Date.parse(`${date}T00:00:00Z`) + days * DAY)\n .toISOString()\n .slice(0, 10);\n}\n/** Return all matching instants (two at a DST fold, none in a DST gap). */\nexport function wallInstants(date: string, clock: string, timezone: string) {\n const time = parseWallClock(clock);\n if (!time) {\n return [];\n }\n const wall =\n Date.parse(`${date}T00:00:00Z`) +\n ((time.hour24 * 60 + time.minute) * 60 + time.second) * 1000;\n const offsets = new Set<number>();\n for (const shift of [-DAY, 0, DAY]) {\n const sample = wall + shift;\n const local = localParts(new Date(sample), timezone);\n const asUTC =\n Date.parse(`${local.date}T00:00:00Z`) +\n ((local.hour * 60 + local.minute) * 60 + local.second) * 1000;\n offsets.add(asUTC - sample);\n }\n const matches = [];\n for (const offset of offsets) {\n const instant = wall - offset;\n const local = localParts(new Date(instant), timezone);\n if (\n local.date === date &&\n local.hour === time.hour24 &&\n local.minute === time.minute &&\n local.second === time.second\n ) {\n matches.push(instant);\n }\n }\n return matches.sort((a, b) => a - b);\n}\nexport type OpeningInterval = { start: number; end: number };\nexport function openingIntervals(\n branch: Branch,\n timezone: string,\n now: Date,\n days = 8,\n): OpeningInterval[] {\n const today = localParts(now, timezone).date;\n const intervals: OpeningInterval[] = [];\n for (let offset = -1; offset < days; offset++) {\n const date = dateOffset(today, offset);\n const day = new Date(`${date}T00:00:00Z`).getUTCDay();\n const special = branch.specialHours?.find((hours) => hours.date === date);\n const times = special\n ? special.closed || !special.opens || !special.closes\n ? []\n : [{ opens: special.opens, closes: special.closes }]\n : branch.openingTimes.filter(\n (hours) => hours.day === day && !hours.disabled,\n );\n for (const hours of times) {\n const starts = wallInstants(date, hours.opens, timezone);\n const overnight = hours.closes <= hours.opens;\n const endDate = overnight ? dateOffset(date, 1) : date;\n const ends = wallInstants(endDate, hours.closes, timezone);\n const start = starts[0];\n let end = ends[ends.length - 1];\n if (\n overnight &&\n branch.specialHours?.some(\n (hours) => hours.date === endDate && hours.closed,\n )\n ) {\n end = wallInstants(endDate, \"00:00\", timezone)[0];\n }\n if (start !== undefined && end !== undefined && start < end) {\n intervals.push({ start, end });\n }\n }\n }\n const closedUntil = branch.closedUntil\n ? Date.parse(branch.closedUntil)\n : -Infinity;\n const sorted = intervals\n .map(({ start, end }) => ({ start: Math.max(start, closedUntil), end }))\n .filter(({ start, end }) => start < end)\n .sort((a, b) => a.start - b.start);\n const merged: OpeningInterval[] = [];\n for (const interval of sorted) {\n const last = merged[merged.length - 1];\n if (last && interval.start <= last.end) {\n last.end = Math.max(last.end, interval.end);\n } else {\n merged.push({ ...interval });\n }\n }\n return merged;\n}\nexport type OpenStatusState = {\n isOpen: boolean;\n label: string;\n nextChange?: Date;\n};\n/**\n * Calculate whether a branch is open and provide its next opening or closing label.\n * Accounts for weekly hours, special hours, overnight intervals and closedUntil.\n *\n * @param branch - The branch whose opening rules apply.\n * @param timezone - The time zone used to interpret and display its hours.\n * @param now - The instant to check. Defaults to the current time.\n * @returns Open state, a display label and the next boundary found in the opening-hours window.\n */\nexport function getOpenStatus(\n branch: Branch,\n timezone: string,\n now = new Date(),\n): OpenStatusState {\n const intervals = openingIntervals(branch, timezone, now);\n const current = intervals.find(\n ({ start, end }) => start <= now.getTime() && end > now.getTime(),\n );\n const next =\n current?.end ?? intervals.find(({ start }) => start > now.getTime())?.start;\n if (next === undefined) {\n return { isOpen: false, label: \"Closed\" };\n }\n const nextChange = new Date(next);\n const local = localParts(nextChange, timezone);\n const clock = formatWallClock(\n `${String(local.hour).padStart(2, \"0\")}:${String(local.minute).padStart(2, \"0\")}`,\n )\n .toLowerCase()\n .replace(\" \", \"\");\n const today = localParts(now, timezone).date;\n const suffix =\n local.date === today\n ? \"\"\n : local.date === dateOffset(today, 1)\n ? \" tomorrow\"\n : ` ${new Intl.DateTimeFormat(\"en\", { timeZone: timezone, weekday: \"long\" }).format(nextChange)}`;\n return {\n isOpen: Boolean(current),\n label: current ? `Open until ${clock}` : `Opens ${clock}${suffix}`,\n nextChange,\n };\n}\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": ["hours"]
|
|
7
6
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/status/open-status.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\";\nimport type { Branch } from \"@zappie/sdk\";\nimport { useStore } from \"@zappie/sdk/react\";\nimport { forwardRef, useEffect, useState } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { getOpenStatus, type OpenStatusState } from \"./hours\";\nexport function useOpenStatus(branch: Branch, timezone: string) {\n const [now, setNow] = useState(() => new Date());\n const state = getOpenStatus(branch, timezone, now);\n const boundary = state.nextChange?.getTime();\n useEffect(() => {\n // With no weekly boundary, retry at the next day for future calendar exceptions.\n const delay = boundary !== undefined ? boundary - Date.now() : 86400000;\n const timeout = setTimeout(\n () => setNow(new Date()),\n Math.max(1, Math.min(2147483647, delay)),\n );\n const refresh = () => setNow(new Date());\n window.addEventListener(\"focus\", refresh);\n return () => {\n clearTimeout(timeout);\n window.removeEventListener(\"focus\", refresh);\n };\n }, [branch, timezone, boundary]);\n return state;\n}\n/**\n * Expose a branch opening label and open/closed state in a span or your render function.\n * Uses your time zone override, then the branch, store or UTC, and refreshes at boundaries and window focus.\n */\nexport const OpenStatus = forwardRef<\n HTMLElement,\n PartProps<OpenStatusState> & { branch: Branch; timezone?: string }\n>(function OpenStatus({ branch, timezone, ...props }, ref) {\n const store = useStore();\n const state = useOpenStatus(\n branch,\n timezone ?? branch.timezone ?? store.data?.timezone ?? \"UTC\",\n );\n return part(\n \"span\",\n props,\n state,\n { \"data-state\": state.isOpen ? \"open\" : \"closed\" },\n ref,\n );\n});\n"],
|
|
5
4
|
"mappings": ";;;AAEA,SAAS,gBAAgB;AACzB,SAAS,YAAY,WAAW,gBAAgB;AAChD,SAAS,YAA4B;AACrC,SAAS,qBAA2C;AAC7C,SAAS,cAAc,QAAgB,UAAkB;AAC9D,QAAM,CAAC,KAAK,MAAM,IAAI,SAAS,MAAM,oBAAI,KAAK,CAAC;AAC/C,QAAM,QAAQ,cAAc,QAAQ,UAAU,GAAG;AACjD,QAAM,WAAW,MAAM,YAAY,QAAQ;AAC3C,YAAU,MAAM;AAEd,UAAM,QAAQ,aAAa,SAAY,WAAW,KAAK,IAAI,IAAI;AAC/D,UAAM,UAAU;AAAA,MACd,MAAM,OAAO,oBAAI,KAAK,CAAC;AAAA,MACvB,KAAK,IAAI,GAAG,KAAK,IAAI,YAAY,KAAK,CAAC;AAAA,IACzC;AACA,UAAM,UAAU,MAAM,OAAO,oBAAI,KAAK,CAAC;AACvC,WAAO,iBAAiB,SAAS,OAAO;AACxC,WAAO,MAAM;AACX,mBAAa,OAAO;AACpB,aAAO,oBAAoB,SAAS,OAAO;AAAA,IAC7C;AAAA,EACF,GAAG,CAAC,QAAQ,UAAU,QAAQ,CAAC;AAC/B,SAAO;AACT;AAKO,IAAM,aAAa,WAGxB,SAASA,YAAW,EAAE,QAAQ,UAAU,GAAG,MAAM,GAAG,KAAK;AACzD,QAAM,QAAQ,SAAS;AACvB,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,YAAY,OAAO,YAAY,MAAM,MAAM,YAAY;AAAA,EACzD;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,cAAc,MAAM,SAAS,SAAS,SAAS;AAAA,IACjD;AAAA,EACF;AACF,CAAC;",
|
|
6
5
|
"names": ["OpenStatus"]
|
|
7
6
|
}
|
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Builds replace __ZAPPIE_FOUNDATIONS_VERSION__ with the version in package.json. Code run straight\n// from source, such as a plain Node script, has no build step and gets the\n// development label instead.\ndeclare const __ZAPPIE_FOUNDATIONS_VERSION__: string | undefined;\n\n/** The installed @zappie/foundations version. */\nexport const PACKAGE_VERSION: string =\n typeof __ZAPPIE_FOUNDATIONS_VERSION__ === \"string\"\n ? __ZAPPIE_FOUNDATIONS_VERSION__\n : \"0.0.0-development\";\n"],
|
|
5
4
|
"mappings": ";AAMO,IAAM,kBACX,OACI,WACA;",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zappie/foundations",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "UI-less React primitives for Zappie storefronts: behaviour, state and accessibility without styles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@base-ui/react": "^1.9.0",
|
|
35
35
|
"@googlemaps/markerclusterer": "^2.6.2",
|
|
36
|
-
"@zappie/sdk": "^0.
|
|
36
|
+
"@zappie/sdk": "^0.2.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"react": "^18.2.0 || ^19.0.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"url": "https://github.com/zappie/storefront-packages/issues"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
|
-
"build": "tsup && node ../../scripts/release-packages/build-modules.mjs",
|
|
57
|
+
"build": "tsup && node ../../scripts/release-packages/build-modules.mjs --no-sources-content",
|
|
58
58
|
"size": "node scripts/check-size.mjs"
|
|
59
59
|
}
|
|
60
60
|
}
|