@zappie/foundations 0.1.4 → 0.1.6

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.
@@ -1,7 +1,7 @@
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\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. It\n * 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\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 becomes a rejection.\n const attempt = new Promise<T>((resolve) => resolve(load())).then(\n (value) => {\n loaded = component(value);\n return loaded;\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({ fallback = null, ...props }: DeferredProps<P>) {\n const { open } = props;\n const [ready, setReady] = useState<AnyComponent | undefined>(() => loaded);\n const [opened, setOpened] = useState<boolean>(open);\n if (open && !opened) {\n setOpened(true);\n }\n const Loaded = ready ?? loaded;\n useEffect(() => {\n if (!open || Loaded) {\n return;\n }\n let active = true;\n preload().then((value) => {\n if (active) {\n setReady(() => value);\n }\n }, ignore);\n return () => {\n active = false;\n };\n }, [open, Loaded]);\n if (!(open || opened)) {\n return null;\n }\n if (!Loaded) {\n return open ? <Fragment>{fallback}</Fragment> : null;\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
- "mappings": ";;;AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AA6Ka;AAzGpB,SAAS,UAAU,OAAqC;AACtD,MACE,OAAO,UAAU,YACjB,UAAU,QACV,aAAa,SACb,EAAE,cAAc,QAChB;AACA,WAAO,MAAM;AAAA,EACf;AACA,SAAO;AACT;AAEA,IAAM,SAAS,MAAM;AAAC;AAcf,SAAS,SACd,MACkC;AAElC,MAAI;AACJ,MAAI;AAEJ,QAAM,UAAU,MAA6B;AAC3C,QAAI,QAAQ;AACV,aAAO,QAAQ,QAAQ,MAAM;AAAA,IAC/B;AACA,QAAI,CAAC,SAAS;AAEZ,YAAM,UAAU,IAAI,QAAW,CAAC,YAAY,QAAQ,KAAK,CAAC,CAAC,EAAE;AAAA,QAC3D,CAAC,UAAU;AACT,mBAAS,UAAU,KAAK;AACxB,iBAAO;AAAA,QACT;AAAA,QACA,CAAC,UAAmB;AAElB,cAAI,YAAY,SAAS;AACvB,sBAAU;AAAA,UACZ;AACA,gBAAM;AAAA,QACR;AAAA,MACF;AAEA,cAAQ,MAAM,MAAM;AACpB,gBAAU;AAAA,IACZ;AACA,WAAO;AAAA,EACT;AAEA,QAAM,kBAAkB,CAAC,UAA+B,CAAC,MAAM;AAC7D,QAAI,OAAO,WAAW,eAAe,QAAQ;AAC3C,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,MAAM;AAClB,cAAQ,EAAE,MAAM,MAAM;AAAA,IACxB;AACA,QAAI,OAAO,OAAO,wBAAwB,YAAY;AACpD,YAAMA,UAAS,OAAO;AAAA,QACpB;AAAA,QACA,QAAQ,YAAY,SAChB,SACA,EAAE,SAAS,QAAQ,QAAQ;AAAA,MACjC;AACA,aAAO,MAAM,OAAO,mBAAmBA,OAAM;AAAA,IAC/C;AACA,UAAM,SAAS,OAAO,WAAW,OAAO,QAAQ,WAAW,CAAC;AAC5D,WAAO,MAAM,OAAO,aAAa,MAAM;AAAA,EACzC;AAEA,WAAS,kBAAkB,EAAE,WAAW,MAAM,GAAG,MAAM,GAAqB;AAC1E,UAAM,EAAE,KAAK,IAAI;AACjB,UAAM,CAAC,OAAO,QAAQ,IAAI,SAAmC,MAAM,MAAM;AACzE,UAAM,CAAC,QAAQ,SAAS,IAAI,SAAkB,IAAI;AAClD,QAAI,QAAQ,CAAC,QAAQ;AACnB,gBAAU,IAAI;AAAA,IAChB;AACA,UAAM,SAAS,SAAS;AACxB,cAAU,MAAM;AACd,UAAI,CAAC,QAAQ,QAAQ;AACnB;AAAA,MACF;AACA,UAAI,SAAS;AACb,cAAQ,EAAE,KAAK,CAAC,UAAU;AACxB,YAAI,QAAQ;AACV,mBAAS,MAAM,KAAK;AAAA,QACtB;AAAA,MACF,GAAG,MAAM;AACT,aAAO,MAAM;AACX,iBAAS;AAAA,MACX;AAAA,IACF,GAAG,CAAC,MAAM,MAAM,CAAC;AACjB,QAAI,EAAE,QAAQ,SAAS;AACrB,aAAO;AAAA,IACT;AACA,QAAI,CAAC,QAAQ;AACX,aAAO,OAAO,oBAAC,YAAU,oBAAS,IAAc;AAAA,IAClD;AACA,WAAO,oBAAC,UAAQ,GAAI,OAAmC;AAAA,EACzD;AACA,oBAAkB,cAAc;AAEhC,SAAO;AAAA;AAAA,IAEL;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EACb;AACF;AAWO,SAAS,iBAAiB,QAEP;AACxB,SAAO,QAAQ,MAAM;AACnB,UAAM,QAAQ,MAAM;AAClB,aAAO,QAAQ,EAAE,MAAM,MAAM;AAAA,IAC/B;AACA,WAAO,EAAE,gBAAgB,OAAO,eAAe,OAAO,SAAS,MAAM;AAAA,EACvE,GAAG,CAAC,MAAM,CAAC;AACb;",
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
+ "mappings": "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",
6
6
  "names": ["handle"]
7
7
  }
@@ -1,6 +1,5 @@
1
1
  import * as react from 'react';
2
2
  import { AllHTMLAttributes, ReactNode, Ref } from 'react';
3
- import * as react_jsx_runtime from 'react/jsx-runtime';
4
3
  import { Dialog } from '@base-ui/react/dialog';
5
4
 
6
5
  /**
@@ -41,7 +40,7 @@ type Parts = {
41
40
  declare function useEngineParts(): Parts;
42
41
  declare function Surface(props: SurfaceProps & {
43
42
  children?: ReactNode;
44
- }): react_jsx_runtime.JSX.Element;
43
+ }): react.JSX.Element;
45
44
  declare const EngineTitle: react.ForwardRefExoticComponent<Omit<react.AllHTMLAttributes<HTMLElement>, "children" | "defaultValue" | "value"> & {
46
45
  asChild?: boolean;
47
46
  children?: ReactNode | ((state: unknown) => ReactNode);
package/dist/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  import * as react from 'react';
2
2
  import { HTMLAttributes, ReactNode, ComponentType, ReactElement, KeyboardEvent as KeyboardEvent$1, ImgHTMLAttributes, ChangeEvent, ClipboardEvent } from 'react';
3
- import * as react_jsx_runtime from 'react/jsx-runtime';
4
3
  import * as _zappie_sdk from '@zappie/sdk';
5
4
  import { Branch as Branch$1, CartLine, CartLineInput, CartIssue, CartFix, Quote, DeliveryAddress, MenuSchedule, CartFulfilment, OptionGroup as OptionGroup$1, Item, Money as Money$1, Choice as Choice$1, MoneyFormatOptions, PaymentMethodKind, Category, Menu, OpeningTime } from '@zappie/sdk';
6
5
  import { useCart } from '@zappie/sdk/react';
7
- import { P as PartProps, a as Presentation, S as SwipeDirection } from './engine-XTsgCwwe.js';
6
+ import { P as PartProps, a as Presentation, S as SwipeDirection } from './engine-Bfbv9M0D.js';
8
7
  import { C as CloseOptions, O as OverlayQuery } from './history-Bqu12Vkz.js';
9
8
  import '@base-ui/react/dialog';
10
9
 
@@ -68,19 +67,56 @@ type Coordinates = {
68
67
  lng: number;
69
68
  };
70
69
 
71
- /** What a template gets to render when the map fails to load. */
72
- type BranchMapLoadErrorState = {
70
+ /** What a template gets to render when a map fails to load. */
71
+ type MapLoadErrorState = {
73
72
  /** Why the map failed to load. */
74
73
  error: unknown;
75
74
  /** Load the map again. */
76
75
  retry: () => void;
77
76
  };
77
+
78
+ /** The branch, the shopper's address and an optional route for DeliveryMap. */
79
+ type DeliveryMapProps = HTMLAttributes<HTMLDivElement> & {
80
+ /** The branch the order leaves from. Only its coordinates are read. */
81
+ branch: Pick<Branch$1, "coordinates">;
82
+ /** Coordinates of the delivery address. */
83
+ shopper?: Coordinates | undefined;
84
+ /**
85
+ * The path to draw, such as a decoded driving route, from the branch to the
86
+ * shopper. Without it the map draws a straight line.
87
+ */
88
+ route?: Coordinates[];
89
+ browserKey?: string;
90
+ /** Image shown inside the branch marker, such as the business logo. */
91
+ pinImageUrl?: string;
92
+ /** Called each time the map fails to load. */
93
+ onLoadError?: (error: unknown) => void;
94
+ /**
95
+ * Render your own message when the map fails to load, such as a retry
96
+ * button that calls `retry`. Without it the map shows "Map location unavailable".
97
+ */
98
+ renderLoadError?: (state: MapLoadErrorState) => ReactNode;
99
+ };
100
+ /**
101
+ * A static map of a delivery: the branch marker, the shopper's house marker
102
+ * and the line between them, fitted to both. It has no controls and ignores
103
+ * gestures. Maps loads after mount when you supply a browser key. Set the
104
+ * container height with CSS. Colour the house marker with --map-accent and
105
+ * the line with --delivery-map-route.
106
+ */
107
+ declare function DeliveryMap({ branch, shopper, route, browserKey, pinImageUrl, onLoadError, renderLoadError, ...props }: DeliveryMapProps): react.JSX.Element | null;
108
+
109
+ /** What a template gets to render when the map fails to load. */
110
+ type BranchMapLoadErrorState = MapLoadErrorState;
78
111
  /** Branches, selection and optional shopper coordinates for BranchMap. */
79
112
  type BranchMapProps = Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> & {
80
113
  branches: Branch$1[];
81
114
  selectedBranchId?: string;
82
115
  onSelect: (branch: Branch$1) => void;
83
- /** The shopper's coordinates. The map fits them with the branches. */
116
+ /**
117
+ * The shopper's coordinates. The map shows them with a house marker and
118
+ * draws a line to the selected branch, or to the nearest one.
119
+ */
84
120
  shopperLocation?: Coordinates;
85
121
  browserKey?: string;
86
122
  /**
@@ -88,12 +124,18 @@ type BranchMapProps = Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> & {
88
124
  * is shown and 14 otherwise.
89
125
  */
90
126
  maxZoom?: number;
91
- /** Image shown inside each branch pin. */
127
+ /** Image shown inside each branch pin, such as the business logo. */
92
128
  pinImageUrl?: string;
93
129
  /** Show the map and satellite switch. Defaults to false. */
94
130
  mapTypeControl?: boolean;
95
- /** Show the branch name above the selected pin, or above every pin. Off by default. */
131
+ /**
132
+ * Show the branch name above the selected pin, or above every pin. Off by
133
+ * default. With a shopper location and no selection, "selected" labels the
134
+ * nearest branch.
135
+ */
96
136
  pinLabel?: "selected" | "all";
137
+ /** Frame a small map, with tighter padding and a wider single-branch view. */
138
+ compact?: boolean;
97
139
  /** Called each time the map fails to load. */
98
140
  onLoadError?: (error: unknown) => void;
99
141
  /**
@@ -104,10 +146,13 @@ type BranchMapProps = Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> & {
104
146
  };
105
147
  /**
106
148
  * Show branch locations. Maps loads after mount when you supply a browser key.
107
- * Set the container height with CSS. Style pins with --branch-map-pin,
108
- * --branch-map-pin-selected, --branch-map-pin-border and --branch-map-pin-size.
149
+ * Nearby branches group into numbered clusters. Set the container height with
150
+ * CSS. Colour clusters, the shopper marker, labels and the route with
151
+ * --map-accent and --map-accent-foreground. Style dot pins with
152
+ * --branch-map-pin, --branch-map-pin-selected, --branch-map-pin-border and
153
+ * --branch-map-pin-size.
109
154
  */
110
- declare function BranchMap({ branches, selectedBranchId, onSelect, shopperLocation, browserKey, maxZoom, pinImageUrl, mapTypeControl, pinLabel, onLoadError, renderLoadError, ...props }: BranchMapProps): react_jsx_runtime.JSX.Element | null;
155
+ declare function BranchMap({ branches, selectedBranchId, onSelect, shopperLocation, browserKey, maxZoom, pinImageUrl, mapTypeControl, pinLabel, compact, onLoadError, renderLoadError, ...props }: BranchMapProps): react.JSX.Element | null;
111
156
 
112
157
  /**
113
158
  * The current quantity, bounds, step, disabled state and value setter exposed by a quantity control.
@@ -132,7 +177,7 @@ type QuantityProps = PartProps<QuantityState> & {
132
177
  step?: number;
133
178
  disabled?: boolean;
134
179
  };
135
- declare function Root$5({ value, defaultValue, onValueChange, min, max, step, disabled, ...props }: QuantityProps): react_jsx_runtime.JSX.Element;
180
+ declare function Root$5({ value, defaultValue, onValueChange, min, max, step, disabled, ...props }: QuantityProps): react.JSX.Element;
136
181
  /**
137
182
  * Composable quantity controls with bounded step changes and keyboard support.
138
183
  * Wrap Value, Increment and Decrement in Quantity.Root.
@@ -163,7 +208,7 @@ declare const Quantity: {
163
208
  };
164
209
 
165
210
  type CartContext = ReturnType<typeof useCart>;
166
- declare function Root$4(props: PartProps<CartContext>): react_jsx_runtime.JSX.Element;
211
+ declare function Root$4(props: PartProps<CartContext>): react.JSX.Element;
167
212
  type LineContext = {
168
213
  line: CartLine;
169
214
  item: _zappie_sdk.Item | undefined;
@@ -172,7 +217,7 @@ type LineContext = {
172
217
  };
173
218
  declare function CartQuantity({ lineId, ...props }: Omit<QuantityProps, "value" | "defaultValue" | "onValueChange"> & {
174
219
  lineId: string;
175
- }): react_jsx_runtime.JSX.Element | null;
220
+ }): react.JSX.Element | null;
176
221
  /**
177
222
  * Composable cart parts for lines, quantity changes, removal, empty state and validation issues.
178
223
  * Wrap the parts in Cart.Root inside your ZappieProvider.
@@ -230,6 +275,11 @@ type DeferredProps<P> = Omit<P, "open"> & {
230
275
  open: boolean;
231
276
  /** Shown while `open` is true and the code is still loading, for inline parts that should hold their place. */
232
277
  fallback?: ReactNode;
278
+ /**
279
+ * Shown while `open` is true and the code failed to load. Call `retry` to load it again.
280
+ * A later `preload()`, such as the next tap on the trigger, also retries and renders on success.
281
+ */
282
+ failed?: (retry: () => void) => ReactNode;
233
283
  };
234
284
  /** Options for `preloadWhenIdle`. */
235
285
  type DeferredIdleOptions = {
@@ -255,8 +305,9 @@ type Deferred<P> = {
255
305
  preloadWhenIdle(options?: DeferredIdleOptions): () => void;
256
306
  /**
257
307
  * Renders nothing until `open` is first true and the code is in. When
258
- * `open` turns true it loads the code and renders as soon as it arrives. It
259
- * never suspends. After the first open the component stays mounted and
308
+ * `open` turns true it loads the code and renders as soon as it arrives. If
309
+ * the load fails it shows `failed` and renders once a retry or a later
310
+ * `preload()` succeeds. It never suspends. After the first open the component stays mounted and
260
311
  * every prop passes through, `open` included, so it can run its own exit.
261
312
  */
262
313
  Component: (props: DeferredProps<P>) => ReactElement | null;
@@ -368,7 +419,7 @@ declare function Picker({ getSuggestions, onSelect, query, defaultQuery, onQuery
368
419
  defaultQuery?: string;
369
420
  onQueryChange?: (query: string) => void;
370
421
  debounceMs?: number;
371
- }): react_jsx_runtime.JSX.Element;
422
+ }): react.JSX.Element;
372
423
  /**
373
424
  * Composable address picker parts with debounced suggestions and keyboard navigation.
374
425
  * Supply getSuggestions and onSelect to Address.Picker. It cancels outdated requests.
@@ -538,7 +589,7 @@ type ItemFormInvalidGroup = {
538
589
  * Item form configuration with controlled or default selections and cart commit callbacks.
539
590
  * Supply onCommit to update an existing line instead of adding a new one.
540
591
  */
541
- type ItemFormProps = PartProps<ItemFormContext> & {
592
+ type ItemFormProps = Omit<PartProps<ItemFormContext>, "onInvalid"> & {
542
593
  item: Item;
543
594
  value?: CartLineInput;
544
595
  defaultValue?: Partial<CartLineInput>;
@@ -555,7 +606,7 @@ type ItemFormProps = PartProps<ItemFormContext> & {
555
606
  */
556
607
  onInvalid?: (groups: ItemFormInvalidGroup[]) => void;
557
608
  };
558
- declare function Root$3({ item, value, defaultValue, onValueChange, defaultQuantity, onAdded, onCommit, onInvalid, ...props }: ItemFormProps): react_jsx_runtime.JSX.Element;
609
+ declare function Root$3({ item, value, defaultValue, onValueChange, defaultQuantity, onAdded, onCommit, onInvalid, ...props }: ItemFormProps): react.JSX.Element;
559
610
 
560
611
  type GroupState = {
561
612
  group: OptionGroup$1;
@@ -631,7 +682,7 @@ type ImageProps = Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "srcSet"> &
631
682
  mode?: "resize" | "direct";
632
683
  };
633
684
  /** Render an image through same-origin resizing, or directly when unavailable. */
634
- declare function Image({ src, sizes, priority, quality, mode, alt, ...props }: ImageProps): react_jsx_runtime.JSX.Element;
685
+ declare function Image({ src, sizes, priority, quality, mode, alt, ...props }: ImageProps): react.JSX.Element;
635
686
 
636
687
  /**
637
688
  * Render formatted SDK money in a data element, with the minor-unit amount as its value.
@@ -724,7 +775,7 @@ declare function Root$2({ param, id, presentation, swipeDirection, fallback, omi
724
775
  presentation?: ResponsivePresentation;
725
776
  /** Swipe direction to dismiss. Defaults to `down` for drawers and `right` for sheets. */
726
777
  swipeDirection?: SwipeDirection;
727
- } & CloseOptions): react_jsx_runtime.JSX.Element;
778
+ } & CloseOptions): react.JSX.Element;
728
779
  /**
729
780
  * Composable dialog, drawer and sheet parts with URL-backed open state and a lazy modal engine.
730
781
  * Use Root, Trigger, Content, Title and Close to connect history, focus handling and dismissal.
@@ -792,7 +843,7 @@ declare function Root$1({ methods, value, defaultValue, onValueChange, ...props
792
843
  value?: PaymentMethodKind | null;
793
844
  defaultValue?: PaymentMethodKind | null;
794
845
  onValueChange?: (value: PaymentMethodKind | null) => void;
795
- }): react_jsx_runtime.JSX.Element;
846
+ }): react.JSX.Element;
796
847
  /**
797
848
  * Composable payment-method radio controls with keyboard navigation.
798
849
  * Root manages the selection. Item renders each method and prevents selection when disabled.
@@ -860,7 +911,7 @@ type RootProps = Omit<PartProps<SearchState>, "onSelect"> & {
860
911
  onSelect?: (result: SearchResult) => void;
861
912
  source?: SearchSource;
862
913
  };
863
- declare function Root(props: RootProps): react_jsx_runtime.JSX.Element;
914
+ declare function Root(props: RootProps): react.JSX.Element;
864
915
  /**
865
916
  * Composable menu search parts with combobox navigation and result announcements.
866
917
  * Use Root with your own source or let it index the active branch menu through your ZappieProvider.
@@ -950,4 +1001,4 @@ declare const OpenStatus: react.ForwardRefExoticComponent<Omit<react.AllHTMLAttr
950
1001
  /** The installed @zappie/foundations version. */
951
1002
  declare const FOUNDATIONS_VERSION: string;
952
1003
 
953
- export { Account, AccountNotConfiguredError, type ActiveGroup, Address, type AddressState, type AddressSuggestion, Branch, BranchMap, type BranchMapLoadErrorState, type BranchMapProps, Cart, Choice, type Customer, type Deferred, type DeferredIdleOptions, type DeferredProps, FOUNDATIONS_VERSION, Fulfilment, Image, type ImageProps, type IntentPreloadHandlers, ItemForm, type ItemFormContext, type ItemFormInvalidGroup, type ItemFormProps, Money, OpenStatus, OptionGroup, Otp, Overlay, PartProps, type PaymentMethod, PaymentMethods, Quantity, type QuantityProps, type QuantityState, Schedule, type ScheduleSlot, type ScheduleState, Search, type SearchResult, type SearchSource, type SearchState, type SignInOptions, Totals, type TotalsState, createSearchIndex, deferred, formatOpeningHours, formatWallClock, getOpenStatus, parseWallClock, scheduleSlots, useControllableState, useIntentPreload, useOverlay };
1004
+ export { Account, AccountNotConfiguredError, type ActiveGroup, Address, type AddressState, type AddressSuggestion, Branch, BranchMap, type BranchMapLoadErrorState, type BranchMapProps, Cart, Choice, type Customer, type Deferred, type DeferredIdleOptions, type DeferredProps, DeliveryMap, type DeliveryMapProps, FOUNDATIONS_VERSION, Fulfilment, Image, type ImageProps, type IntentPreloadHandlers, ItemForm, type ItemFormContext, type ItemFormInvalidGroup, type ItemFormProps, type MapLoadErrorState, Money, OpenStatus, OptionGroup, Otp, Overlay, PartProps, type PaymentMethod, PaymentMethods, Quantity, type QuantityProps, type QuantityState, Schedule, type ScheduleSlot, type ScheduleState, Search, type SearchResult, type SearchSource, type SearchState, type SignInOptions, Totals, type TotalsState, createSearchIndex, deferred, formatOpeningHours, formatWallClock, getOpenStatus, parseWallClock, scheduleSlots, useControllableState, useIntentPreload, useOverlay };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.ts
4
4
  import { PACKAGE_VERSION } from "./version.js";
5
5
  import { Account, AccountNotConfiguredError } from "./account/index.js";
6
- import { BranchMap } from "./branch-map/index.js";
6
+ import { BranchMap, DeliveryMap } from "./branch-map/index.js";
7
7
  import { Cart, Totals } from "./cart/index.js";
8
8
  import { deferred, useIntentPreload } from "./deferred/index.js";
9
9
  import { Address } from "./fulfilment/address-picker.js";
@@ -33,6 +33,7 @@ export {
33
33
  Branch,
34
34
  BranchMap,
35
35
  Cart,
36
+ DeliveryMap,
36
37
  FOUNDATIONS_VERSION,
37
38
  Fulfilment,
38
39
  Image,
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
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 } from \"./branch-map\";\nexport type { BranchMapLoadErrorState, BranchMapProps } 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
- "mappings": ";;;AACA,SAAS,uBAAuB;AAIhC,SAAS,SAAS,iCAAiC;AAEnD,SAAS,iBAAiB;AAE1B,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;AAzCpB,IAAM,sBAA8B;",
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
+ "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
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
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 = PartProps<ItemFormContext> & {\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"],
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
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,3 @@
1
1
  import 'react';
2
- import 'react/jsx-runtime';
3
2
  import '@base-ui/react/dialog';
4
- export { E as EngineClose, b as EngineTitle, a as Presentation, c as Surface, d as SurfaceProps, S as SwipeDirection, u as useEngineParts } from '../engine-XTsgCwwe.js';
3
+ export { E as EngineClose, b as EngineTitle, a as Presentation, c as Surface, d as SurfaceProps, S as SwipeDirection, u as useEngineParts } from '../engine-Bfbv9M0D.js';