@zappie/sdk 0.1.18 → 0.1.20

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # @zappie/sdk
2
2
 
3
+ ## 0.1.20
4
+
5
+ ### Patch Changes
6
+
7
+ - 050600e: `@zappie/sdk/testing` ships without a source map. It is test-only code, and its 22.7 KB map filled the package's size budget.
8
+ - 050600e: Add `@zappie/sdk/vite` with `zappieSource()`, the Vite plugin that marks each element under `app/` with `data-zappie-source` (file and line) and `data-zappie-component` for Studio's region selector. It runs only on `vite dev` with `previewHost`, the `ZAPPIE_PREVIEW_HOST` Studio sets, so no build carries the attributes. It needs Vite 8, an optional peer, and adds no dependency.
9
+
10
+ ## 0.1.19
11
+
12
+ ### Patch Changes
13
+
14
+ - 628ac42: Add `@zappie/sdk/studio`, the Studio region selector. On the Studio dev server, `TemplateSettingsProvider` loads it in draft mode and posts `selectable` after `ready`. It outlines the region under the pointer with its merchant-facing name, steps out and back in with the arrow keys, moves through regions with Tab, and selects with a click, Enter or Space without following links or pressing buttons. It posts the selection as `region` and Escape as `select-cancel`, and announces names through a live region. It takes `select` only from the framing Studio dashboard in `STUDIO_ORIGINS`. Kit parts are named by `KIT_REGION_NAMES`. A production build drops the loader, so stand-in previews and live sites load none of it and post `selectable` with `on: false`.
15
+
3
16
  ## 0.1.18
4
17
 
5
18
  ### Patch Changes
package/dist/debug.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/debug.ts
2
- var VERSION = true ? "0.1.18" : "0.0.0-development";
2
+ var VERSION = true ? "0.1.20" : "0.0.0-development";
3
3
  function installDebug(client) {
4
4
  if (typeof window === "undefined" || client.mode !== "test") {
5
5
  return;
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { ZappieError, isZappieError } from "./errors.js";
10
10
  export * from "./models.js";
11
11
  import { format, money } from "./money.js";
12
12
  import { RestTransport } from "./transport/rest.js";
13
- var VERSION = true ? "0.1.18" : "0.0.0-development";
13
+ var VERSION = true ? "0.1.20" : "0.0.0-development";
14
14
  export {
15
15
  API_VERSION,
16
16
  RestTransport,
@@ -0,0 +1,39 @@
1
+ /** The editors in `EDITOR_ORIGINS` allowed to drive region selection: the merchant dashboard and its dev server. */
2
+ declare const STUDIO_ORIGINS: readonly string[];
3
+ /** Merchant-facing names for the Zappie kit's components, so kit files keep their hashes. */
4
+ declare const KIT_REGION_NAMES: Readonly<Record<string, string>>;
5
+ /** A region the merchant selected, as the preview posts it to Studio. */
6
+ type StudioRegion = {
7
+ zappie: "region";
8
+ /** Where the region is rendered, as `app/…:line`. */
9
+ source: string;
10
+ /** The function component that holds it, or an empty string. */
11
+ component: string;
12
+ /** The merchant-facing name. Never a path. */
13
+ name: string;
14
+ /** Up to five distinct `source` values of enclosing regions, nearest first. */
15
+ ancestry: string[];
16
+ /** The region's box in CSS pixels, relative to the viewport. */
17
+ rect: {
18
+ x: number;
19
+ y: number;
20
+ width: number;
21
+ height: number;
22
+ };
23
+ scrollY: number;
24
+ /** The page path and query. */
25
+ path: string;
26
+ };
27
+ /**
28
+ * The merchant-facing name of a region: its own or an enclosing `data-zappie-region` within the same component, then
29
+ * the kit's name for its component, then the component name made readable, then the element's role.
30
+ */
31
+ declare function regionName(element: Element): string;
32
+ /**
33
+ * Start the Studio region selector for an editor at `parent`, which must be in `STUDIO_ORIGINS`. It draws nothing until
34
+ * the editor sends `{ zappie: "select", on: true }`, and posts `region` and `select-cancel` through `post`. Returns a
35
+ * function that removes it, or null when `parent` isn't a Studio editor.
36
+ */
37
+ declare function startStudio(parent: string, post: (message: object) => void): (() => void) | null;
38
+
39
+ export { KIT_REGION_NAMES, STUDIO_ORIGINS, type StudioRegion, regionName, startStudio };
@@ -0,0 +1,314 @@
1
+ // src/studio/index.ts
2
+ import { EDITOR_ORIGINS } from "../template/index.js";
3
+ var STUDIO_ORIGINS = Object.freeze(
4
+ EDITOR_ORIGINS.filter(
5
+ (origin) => /^https:\/\/dashboard\.|\/\/localhost:5181$/.test(origin)
6
+ )
7
+ );
8
+ var SOURCE = "data-zappie-source";
9
+ var COMPONENT = "data-zappie-component";
10
+ var REGION = "data-zappie-region";
11
+ var KIT_REGION_NAMES = {
12
+ Account: "Account",
13
+ AccountPage: "Account",
14
+ AddressPicker: "Delivery address",
15
+ CartButton: "Cart button",
16
+ CartCheckoutLink: "Checkout button",
17
+ CartDrawer: "Cart",
18
+ CartIssueList: "Cart notices",
19
+ CartLine: "Cart item",
20
+ CartTotals: "Cart totals",
21
+ CategoryNav: "Categories",
22
+ CheckoutButton: "Checkout button",
23
+ CheckoutPage: "Checkout",
24
+ CheckoutReturn: "Payment result",
25
+ Consent: "Cookie notice",
26
+ FulfilmentSwitch: "Delivery or pickup",
27
+ ItemCard: "Menu item",
28
+ ItemChoice: "Item option",
29
+ ItemConfiguration: "Item options",
30
+ ItemOptions: "Item options",
31
+ ItemSheet: "Item details",
32
+ Logo: "Logo",
33
+ MenuPage: "Menu",
34
+ Money: "Price",
35
+ OpenStatus: "Opening hours",
36
+ OrderPage: "Order",
37
+ OrderStatus: "Order status",
38
+ Orders: "Orders",
39
+ OrdersPage: "Orders",
40
+ Quantity: "Quantity",
41
+ SchedulePicker: "Order time",
42
+ Search: "Search",
43
+ SearchRow: "Search result",
44
+ SignInButton: "Sign in button",
45
+ StoreFooter: "Shop footer",
46
+ StoreHeader: "Shop header",
47
+ ZappieProvider: "Page"
48
+ };
49
+ var cap = (text, length) => [...text].slice(0, length).join("");
50
+ var read = (element, name) => element.getAttribute(name) ?? "";
51
+ function regionOf(target) {
52
+ const found = target?.closest(`[${SOURCE}]`);
53
+ return found && found !== document.documentElement && found !== document.body ? found : null;
54
+ }
55
+ function regionName(element) {
56
+ const component = read(element, COMPONENT);
57
+ for (let at = element; at && (!at.hasAttribute(COMPONENT) || read(at, COMPONENT) === component); at = at.parentElement) {
58
+ const name2 = at.getAttribute(REGION)?.trim();
59
+ if (name2) {
60
+ return cap(name2, 60);
61
+ }
62
+ }
63
+ const words = component.replace(/\B(?=[A-Z])/g, " ").toLowerCase();
64
+ const name = KIT_REGION_NAMES[component] ?? (words && words[0].toUpperCase() + words.slice(1));
65
+ if (name) {
66
+ return cap(name, 60);
67
+ }
68
+ const tag = element.localName;
69
+ return /^(img|picture|svg|video)$/.test(tag) ? "Image" : tag === "button" || element.getAttribute("role") === "button" ? "Button" : /^h[1-6]$/.test(tag) ? "Heading" : tag === "a" ? "Link" : "Section";
70
+ }
71
+ function ancestry(element) {
72
+ const own = read(element, SOURCE);
73
+ const sources = [];
74
+ for (let at = regionOf(element.parentElement); at && sources.length < 5; at = regionOf(at.parentElement)) {
75
+ const source = read(at, SOURCE);
76
+ if (source !== own && !sources.includes(source)) {
77
+ sources.push(source);
78
+ }
79
+ }
80
+ return sources;
81
+ }
82
+ var shown = (element) => {
83
+ const box = element.getBoundingClientRect();
84
+ return box.width > 0 && box.height > 0;
85
+ };
86
+ function regionStops() {
87
+ const stops = /* @__PURE__ */ new Set();
88
+ for (const element of document.querySelectorAll(`[${SOURCE}]`)) {
89
+ const stop = element.closest(`[${REGION}]`) ?? element;
90
+ if (regionOf(stop) === stop && shown(stop)) {
91
+ stops.add(stop);
92
+ }
93
+ }
94
+ return [...stops];
95
+ }
96
+ var CSS = ".b{position:fixed;left:0;top:0;box-sizing:border-box;border:2px solid #101828;border-radius:2px;box-shadow:0 0 0 2px #fff,inset 0 0 0 2px #fff}.b[hidden]{display:none}.s{border-width:3px}.l{position:absolute;left:-2px;bottom:100%;margin-bottom:4px;padding:2px 6px;border-radius:4px;background:#101828;color:#fff;box-shadow:0 0 0 1px #fff;font:600 12px/1.5 system-ui,sans-serif;white-space:nowrap}.t .l{bottom:auto;top:100%;margin:4px 0 0}.v{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}";
97
+ var CAPTURED = [
98
+ "pointerdown",
99
+ "pointerup",
100
+ "mousedown",
101
+ "mouseup",
102
+ "click",
103
+ "auxclick",
104
+ "dblclick"
105
+ ];
106
+ function startStudio(parent, post) {
107
+ if (!STUDIO_ORIGINS.includes(parent)) {
108
+ return null;
109
+ }
110
+ const host = document.createElement("div");
111
+ host.style.cssText = "position:fixed;inset:0;pointer-events:none;z-index:2147483647";
112
+ const root = host.attachShadow({ mode: "open" });
113
+ const make = (tag, name) => {
114
+ const element = document.createElement(tag);
115
+ element.className = name;
116
+ return element;
117
+ };
118
+ const style = make("style", "");
119
+ style.textContent = CSS;
120
+ const box = make("div", "b");
121
+ box.hidden = true;
122
+ const label = box.appendChild(make("span", "l"));
123
+ const live = make("div", "v");
124
+ live.setAttribute("aria-live", "polite");
125
+ for (const part of [style, box, live]) {
126
+ root.appendChild(part);
127
+ }
128
+ document.body.appendChild(host);
129
+ let on = false;
130
+ let selected = false;
131
+ let current = null;
132
+ let hovered = null;
133
+ let down = [];
134
+ let pointer = null;
135
+ let frame = 0;
136
+ let drawn = "";
137
+ const highlight = (element, announce = false) => {
138
+ current = element;
139
+ drawn = "";
140
+ if (element) {
141
+ label.textContent = regionName(element);
142
+ if (announce) {
143
+ live.textContent = label.textContent;
144
+ }
145
+ }
146
+ };
147
+ const draw = () => {
148
+ frame = requestAnimationFrame(draw);
149
+ if (pointer && !selected) {
150
+ const element = regionOf(document.elementFromPoint(...pointer));
151
+ pointer = null;
152
+ if (element !== hovered) {
153
+ hovered = element;
154
+ down = [];
155
+ highlight(element);
156
+ }
157
+ }
158
+ if (current && !current.isConnected) {
159
+ current = null;
160
+ }
161
+ if (!current) {
162
+ box.hidden = true;
163
+ return;
164
+ }
165
+ const { x, y, width, height } = current.getBoundingClientRect();
166
+ const key2 = `${x} ${y} ${width} ${height}`;
167
+ if (key2 === drawn) {
168
+ return;
169
+ }
170
+ drawn = key2;
171
+ box.hidden = false;
172
+ box.classList.toggle("t", y < 24);
173
+ box.style.transition = matchMedia("(prefers-reduced-motion: reduce)").matches ? "none" : "transform 100ms";
174
+ box.style.transform = `translate(${x}px,${y}px)`;
175
+ box.style.width = `${width}px`;
176
+ box.style.height = `${height}px`;
177
+ };
178
+ const choose = (element) => {
179
+ if (!element) {
180
+ return;
181
+ }
182
+ selected = true;
183
+ highlight(element);
184
+ box.classList.add("s");
185
+ live.textContent = `Selected ${label.textContent}`;
186
+ const { x, y, width, height } = element.getBoundingClientRect();
187
+ post({
188
+ zappie: "region",
189
+ source: read(element, SOURCE),
190
+ component: cap(read(element, COMPONENT), 80),
191
+ name: regionName(element),
192
+ ancestry: ancestry(element),
193
+ rect: { x, y, width, height },
194
+ scrollY: window.scrollY,
195
+ path: location.pathname + location.search
196
+ });
197
+ };
198
+ const step = (up) => {
199
+ if (!current) {
200
+ return false;
201
+ }
202
+ let next;
203
+ if (up) {
204
+ const own = read(current, SOURCE);
205
+ next = regionOf(current.parentElement);
206
+ while (next && read(next, SOURCE) === own) {
207
+ next = regionOf(next.parentElement);
208
+ }
209
+ if (next) {
210
+ down.push(current);
211
+ }
212
+ } else {
213
+ next = down.pop() ?? [...current.querySelectorAll(`[${SOURCE}]`)].find(
214
+ (element) => read(element, SOURCE) !== read(current, SOURCE) && shown(element)
215
+ );
216
+ }
217
+ if (next) {
218
+ highlight(next, true);
219
+ }
220
+ return Boolean(next);
221
+ };
222
+ const capture = (event) => {
223
+ event.preventDefault();
224
+ event.stopImmediatePropagation();
225
+ if (event.type === "click" && !selected) {
226
+ const target = event.target;
227
+ choose(current?.contains(target) ? current : regionOf(target));
228
+ }
229
+ };
230
+ const move = (event) => {
231
+ pointer = [event.clientX, event.clientY];
232
+ };
233
+ const wheel = (event) => {
234
+ if (event.altKey && !selected && step(event.deltaY < 0)) {
235
+ event.preventDefault();
236
+ }
237
+ };
238
+ const key = (event) => {
239
+ let handled = true;
240
+ if (event.key === "Escape") {
241
+ stop();
242
+ post({ zappie: "select-cancel" });
243
+ } else if (selected) {
244
+ handled = false;
245
+ } else if (event.key === "Tab") {
246
+ const stops = regionStops();
247
+ const at = current ? stops.indexOf(current.closest(`[${REGION}]`) ?? current) : -1;
248
+ const next = stops[(at + (event.shiftKey ? at < 0 ? 0 : -1 : 1) + stops.length) % stops.length];
249
+ down = [];
250
+ highlight(next ?? null, true);
251
+ next?.scrollIntoView?.({ block: "nearest" });
252
+ } else if (event.key === "Enter" || event.key === " ") {
253
+ choose(current);
254
+ } else if (event.key === "ArrowUp" || event.key === "ArrowDown") {
255
+ handled = step(event.key === "ArrowUp");
256
+ } else {
257
+ handled = false;
258
+ }
259
+ if (handled) {
260
+ event.preventDefault();
261
+ event.stopImmediatePropagation();
262
+ }
263
+ };
264
+ const listen = (add) => {
265
+ const method = (add ? window.addEventListener : window.removeEventListener).bind(window);
266
+ for (const type of CAPTURED) {
267
+ method(type, capture, true);
268
+ }
269
+ method("pointermove", move, true);
270
+ method("keydown", key, true);
271
+ method("wheel", wheel, { capture: true, passive: false });
272
+ };
273
+ const stop = () => {
274
+ if (on) {
275
+ on = false;
276
+ listen(false);
277
+ cancelAnimationFrame(frame);
278
+ }
279
+ selected = false;
280
+ hovered = null;
281
+ down = [];
282
+ highlight(null);
283
+ box.hidden = true;
284
+ box.classList.remove("s");
285
+ };
286
+ const start = () => {
287
+ stop();
288
+ on = true;
289
+ listen(true);
290
+ draw();
291
+ };
292
+ const message = (event) => {
293
+ const data = event.data;
294
+ if (event.origin === parent && event.source === window.parent && data?.zappie === "select" && typeof data.on === "boolean") {
295
+ if (data.on) {
296
+ start();
297
+ } else {
298
+ stop();
299
+ }
300
+ }
301
+ };
302
+ window.addEventListener("message", message);
303
+ return () => {
304
+ stop();
305
+ window.removeEventListener("message", message);
306
+ host.remove();
307
+ };
308
+ }
309
+ export {
310
+ KIT_REGION_NAMES,
311
+ STUDIO_ORIGINS,
312
+ regionName,
313
+ startStudio
314
+ };
@@ -65,7 +65,26 @@ function TemplateSettingsProvider({
65
65
  send({ zappie: "ready" });
66
66
  route();
67
67
  scroll();
68
+ let live = true;
69
+ let studio = null;
70
+ if (mode === "draft") {
71
+ if (import.meta.env.DEV && document.querySelector("[data-zappie-source]")) {
72
+ import("@zappie/sdk/studio").then(
73
+ ({ startStudio }) => {
74
+ if (live) {
75
+ studio = startStudio(parent, send);
76
+ send({ zappie: "selectable", on: Boolean(studio) });
77
+ }
78
+ },
79
+ () => send({ zappie: "selectable", on: false })
80
+ );
81
+ } else {
82
+ send({ zappie: "selectable", on: false });
83
+ }
84
+ }
68
85
  return () => {
86
+ live = false;
87
+ studio?.();
69
88
  window.removeEventListener("message", message);
70
89
  window.removeEventListener("popstate", route);
71
90
  window.removeEventListener("scroll", scroll);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/template/react.tsx"],
4
- "sourcesContent": ["\"use client\";\nimport { useStore } from \"@zappie/sdk/react\";\nimport {\n editorOrigin,\n settingsFromMessage,\n templateSettings,\n type TemplateMode,\n type TemplateSettingValue,\n} from \"@zappie/sdk/template\";\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\n\nconst Context = createContext<{\n mode: TemplateMode;\n preview: Record<string, TemplateSettingValue> | null;\n}>({ mode: \"site\", preview: null });\n\n/** Supply the host-controlled template mode and editor bridge to a storefront. */\nexport function TemplateSettingsProvider({\n mode,\n children,\n}: {\n mode: TemplateMode;\n children: ReactNode;\n}) {\n const [preview, setPreview] = useState<Record<\n string,\n TemplateSettingValue\n > | null>(null);\n useEffect(() => {\n setPreview(null);\n if (mode === \"site\") {\n return;\n }\n if (window.parent === window) {\n return;\n }\n // Talk only to an allowed Zappie editor framing this page. Anything else gets silence.\n const parent = editorOrigin(location.ancestorOrigins, document.referrer);\n if (!parent) {\n return;\n }\n const send = (message: object) =>\n window.parent.postMessage(message, parent);\n const route = () =>\n send({ zappie: \"route\", path: location.pathname + location.search });\n const scroll = () =>\n send({\n zappie: \"scroll\",\n y: window.scrollY,\n height: document.documentElement.scrollHeight,\n });\n const message = (event: MessageEvent) => {\n if (event.origin !== parent || event.source !== window.parent) {\n return;\n }\n const values = settingsFromMessage(event.data);\n if (values) {\n setPreview(values);\n }\n };\n const pushState = history.pushState;\n const replaceState = history.replaceState;\n history.pushState = function (...args) {\n pushState.apply(this, args);\n route();\n };\n history.replaceState = function (...args) {\n replaceState.apply(this, args);\n route();\n };\n window.addEventListener(\"message\", message);\n window.addEventListener(\"popstate\", route);\n window.addEventListener(\"scroll\", scroll, { passive: true });\n send({ zappie: \"ready\" });\n route();\n scroll();\n return () => {\n window.removeEventListener(\"message\", message);\n window.removeEventListener(\"popstate\", route);\n window.removeEventListener(\"scroll\", scroll);\n history.pushState = pushState;\n history.replaceState = replaceState;\n };\n }, [mode]);\n const value = useMemo(() => ({ mode, preview }), [mode, preview]);\n return <Context.Provider value={value}>{children}</Context.Provider>;\n}\n\n/** Read effective merchant settings over the defaults declared by a template. */\nexport function useTemplateSettings<T extends Record<string, unknown>>(\n defaults: T,\n): T {\n const store = useStore();\n const { mode, preview } = useContext(Context);\n return templateSettings(\n defaults,\n store.data?.templateSettings,\n preview,\n mode,\n );\n}\n"],
5
- "mappings": ";;;AACA,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AA4EE;AA1ET,IAAM,UAAU,cAGb,EAAE,MAAM,QAAQ,SAAS,KAAK,CAAC;AAG3B,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AACF,GAGG;AACD,QAAM,CAAC,SAAS,UAAU,IAAI,SAGpB,IAAI;AACd,YAAU,MAAM;AACd,eAAW,IAAI;AACf,QAAI,SAAS,QAAQ;AACnB;AAAA,IACF;AACA,QAAI,OAAO,WAAW,QAAQ;AAC5B;AAAA,IACF;AAEA,UAAM,SAAS,aAAa,SAAS,iBAAiB,SAAS,QAAQ;AACvE,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AACA,UAAM,OAAO,CAACA,aACZ,OAAO,OAAO,YAAYA,UAAS,MAAM;AAC3C,UAAM,QAAQ,MACZ,KAAK,EAAE,QAAQ,SAAS,MAAM,SAAS,WAAW,SAAS,OAAO,CAAC;AACrE,UAAM,SAAS,MACb,KAAK;AAAA,MACH,QAAQ;AAAA,MACR,GAAG,OAAO;AAAA,MACV,QAAQ,SAAS,gBAAgB;AAAA,IACnC,CAAC;AACH,UAAM,UAAU,CAAC,UAAwB;AACvC,UAAI,MAAM,WAAW,UAAU,MAAM,WAAW,OAAO,QAAQ;AAC7D;AAAA,MACF;AACA,YAAM,SAAS,oBAAoB,MAAM,IAAI;AAC7C,UAAI,QAAQ;AACV,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AACA,UAAM,YAAY,QAAQ;AAC1B,UAAM,eAAe,QAAQ;AAC7B,YAAQ,YAAY,YAAa,MAAM;AACrC,gBAAU,MAAM,MAAM,IAAI;AAC1B,YAAM;AAAA,IACR;AACA,YAAQ,eAAe,YAAa,MAAM;AACxC,mBAAa,MAAM,MAAM,IAAI;AAC7B,YAAM;AAAA,IACR;AACA,WAAO,iBAAiB,WAAW,OAAO;AAC1C,WAAO,iBAAiB,YAAY,KAAK;AACzC,WAAO,iBAAiB,UAAU,QAAQ,EAAE,SAAS,KAAK,CAAC;AAC3D,SAAK,EAAE,QAAQ,QAAQ,CAAC;AACxB,UAAM;AACN,WAAO;AACP,WAAO,MAAM;AACX,aAAO,oBAAoB,WAAW,OAAO;AAC7C,aAAO,oBAAoB,YAAY,KAAK;AAC5C,aAAO,oBAAoB,UAAU,MAAM;AAC3C,cAAQ,YAAY;AACpB,cAAQ,eAAe;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AACT,QAAM,QAAQ,QAAQ,OAAO,EAAE,MAAM,QAAQ,IAAI,CAAC,MAAM,OAAO,CAAC;AAChE,SAAO,oBAAC,QAAQ,UAAR,EAAiB,OAAe,UAAS;AACnD;AAGO,SAAS,oBACd,UACG;AACH,QAAM,QAAQ,SAAS;AACvB,QAAM,EAAE,MAAM,QAAQ,IAAI,WAAW,OAAO;AAC5C,SAAO;AAAA,IACL;AAAA,IACA,MAAM,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["\"use client\";\nimport { useStore } from \"@zappie/sdk/react\";\nimport {\n editorOrigin,\n settingsFromMessage,\n templateSettings,\n type TemplateMode,\n type TemplateSettingValue,\n} from \"@zappie/sdk/template\";\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\n\nconst Context = createContext<{\n mode: TemplateMode;\n preview: Record<string, TemplateSettingValue> | null;\n}>({ mode: \"site\", preview: null });\n\n/** Supply the host-controlled template mode and editor bridge to a storefront. */\nexport function TemplateSettingsProvider({\n mode,\n children,\n}: {\n mode: TemplateMode;\n children: ReactNode;\n}) {\n const [preview, setPreview] = useState<Record<\n string,\n TemplateSettingValue\n > | null>(null);\n useEffect(() => {\n setPreview(null);\n if (mode === \"site\") {\n return;\n }\n if (window.parent === window) {\n return;\n }\n // Talk only to an allowed Zappie editor framing this page. Anything else gets silence.\n const parent = editorOrigin(location.ancestorOrigins, document.referrer);\n if (!parent) {\n return;\n }\n const send = (message: object) =>\n window.parent.postMessage(message, parent);\n const route = () =>\n send({ zappie: \"route\", path: location.pathname + location.search });\n const scroll = () =>\n send({\n zappie: \"scroll\",\n y: window.scrollY,\n height: document.documentElement.scrollHeight,\n });\n const message = (event: MessageEvent) => {\n if (event.origin !== parent || event.source !== window.parent) {\n return;\n }\n const values = settingsFromMessage(event.data);\n if (values) {\n setPreview(values);\n }\n };\n const pushState = history.pushState;\n const replaceState = history.replaceState;\n history.pushState = function (...args) {\n pushState.apply(this, args);\n route();\n };\n history.replaceState = function (...args) {\n replaceState.apply(this, args);\n route();\n };\n window.addEventListener(\"message\", message);\n window.addEventListener(\"popstate\", route);\n window.addEventListener(\"scroll\", scroll, { passive: true });\n send({ zappie: \"ready\" });\n route();\n scroll();\n let live = true;\n let studio: (() => void) | null = null;\n if (mode === \"draft\") {\n // Only the Studio dev server marks sources. A build drops this branch, so no published page loads the selector.\n if (\n (import.meta as unknown as { env: { DEV: boolean } }).env.DEV &&\n document.querySelector(\"[data-zappie-source]\")\n ) {\n import(\"@zappie/sdk/studio\").then(\n ({ startStudio }) => {\n if (live) {\n studio = startStudio(parent, send);\n send({ zappie: \"selectable\", on: Boolean(studio) });\n }\n },\n () => send({ zappie: \"selectable\", on: false }),\n );\n } else {\n send({ zappie: \"selectable\", on: false });\n }\n }\n return () => {\n live = false;\n studio?.();\n window.removeEventListener(\"message\", message);\n window.removeEventListener(\"popstate\", route);\n window.removeEventListener(\"scroll\", scroll);\n history.pushState = pushState;\n history.replaceState = replaceState;\n };\n }, [mode]);\n const value = useMemo(() => ({ mode, preview }), [mode, preview]);\n return <Context.Provider value={value}>{children}</Context.Provider>;\n}\n\n/** Read effective merchant settings over the defaults declared by a template. */\nexport function useTemplateSettings<T extends Record<string, unknown>>(\n defaults: T,\n): T {\n const store = useStore();\n const { mode, preview } = useContext(Context);\n return templateSettings(\n defaults,\n store.data?.templateSettings,\n preview,\n mode,\n );\n}\n"],
5
+ "mappings": ";;;AACA,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAmGE;AAjGT,IAAM,UAAU,cAGb,EAAE,MAAM,QAAQ,SAAS,KAAK,CAAC;AAG3B,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AACF,GAGG;AACD,QAAM,CAAC,SAAS,UAAU,IAAI,SAGpB,IAAI;AACd,YAAU,MAAM;AACd,eAAW,IAAI;AACf,QAAI,SAAS,QAAQ;AACnB;AAAA,IACF;AACA,QAAI,OAAO,WAAW,QAAQ;AAC5B;AAAA,IACF;AAEA,UAAM,SAAS,aAAa,SAAS,iBAAiB,SAAS,QAAQ;AACvE,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AACA,UAAM,OAAO,CAACA,aACZ,OAAO,OAAO,YAAYA,UAAS,MAAM;AAC3C,UAAM,QAAQ,MACZ,KAAK,EAAE,QAAQ,SAAS,MAAM,SAAS,WAAW,SAAS,OAAO,CAAC;AACrE,UAAM,SAAS,MACb,KAAK;AAAA,MACH,QAAQ;AAAA,MACR,GAAG,OAAO;AAAA,MACV,QAAQ,SAAS,gBAAgB;AAAA,IACnC,CAAC;AACH,UAAM,UAAU,CAAC,UAAwB;AACvC,UAAI,MAAM,WAAW,UAAU,MAAM,WAAW,OAAO,QAAQ;AAC7D;AAAA,MACF;AACA,YAAM,SAAS,oBAAoB,MAAM,IAAI;AAC7C,UAAI,QAAQ;AACV,mBAAW,MAAM;AAAA,MACnB;AAAA,IACF;AACA,UAAM,YAAY,QAAQ;AAC1B,UAAM,eAAe,QAAQ;AAC7B,YAAQ,YAAY,YAAa,MAAM;AACrC,gBAAU,MAAM,MAAM,IAAI;AAC1B,YAAM;AAAA,IACR;AACA,YAAQ,eAAe,YAAa,MAAM;AACxC,mBAAa,MAAM,MAAM,IAAI;AAC7B,YAAM;AAAA,IACR;AACA,WAAO,iBAAiB,WAAW,OAAO;AAC1C,WAAO,iBAAiB,YAAY,KAAK;AACzC,WAAO,iBAAiB,UAAU,QAAQ,EAAE,SAAS,KAAK,CAAC;AAC3D,SAAK,EAAE,QAAQ,QAAQ,CAAC;AACxB,UAAM;AACN,WAAO;AACP,QAAI,OAAO;AACX,QAAI,SAA8B;AAClC,QAAI,SAAS,SAAS;AAEpB,UACG,YAAqD,IAAI,OAC1D,SAAS,cAAc,sBAAsB,GAC7C;AACA,eAAO,oBAAoB,EAAE;AAAA,UAC3B,CAAC,EAAE,YAAY,MAAM;AACnB,gBAAI,MAAM;AACR,uBAAS,YAAY,QAAQ,IAAI;AACjC,mBAAK,EAAE,QAAQ,cAAc,IAAI,QAAQ,MAAM,EAAE,CAAC;AAAA,YACpD;AAAA,UACF;AAAA,UACA,MAAM,KAAK,EAAE,QAAQ,cAAc,IAAI,MAAM,CAAC;AAAA,QAChD;AAAA,MACF,OAAO;AACL,aAAK,EAAE,QAAQ,cAAc,IAAI,MAAM,CAAC;AAAA,MAC1C;AAAA,IACF;AACA,WAAO,MAAM;AACX,aAAO;AACP,eAAS;AACT,aAAO,oBAAoB,WAAW,OAAO;AAC7C,aAAO,oBAAoB,YAAY,KAAK;AAC5C,aAAO,oBAAoB,UAAU,MAAM;AAC3C,cAAQ,YAAY;AACpB,cAAQ,eAAe;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AACT,QAAM,QAAQ,QAAQ,OAAO,EAAE,MAAM,QAAQ,IAAI,CAAC,MAAM,OAAO,CAAC;AAChE,SAAO,oBAAC,QAAQ,UAAR,EAAiB,OAAe,UAAS;AACnD;AAGO,SAAS,oBACd,UACG;AACH,QAAM,QAAQ,SAAS;AACvB,QAAM,EAAE,MAAM,QAAQ,IAAI,WAAW,OAAO;AAC5C,SAAO;AAAA,IACL;AAAA,IACA,MAAM,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,EACF;AACF;",
6
6
  "names": ["message"]
7
7
  }
@@ -448,4 +448,3 @@ export {
448
448
  createMockZappie,
449
449
  fixtures
450
450
  };
451
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,41 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ /** The attribute that names the file and line rendering an element, in dev only. */
4
+ declare const SOURCE_ATTRIBUTE = "data-zappie-source";
5
+ /** The attribute that names the function component holding an element, in dev only. */
6
+ declare const COMPONENT_ATTRIBUTE = "data-zappie-component";
7
+ /** The options {@link zappieSource} takes. */
8
+ type ZappieSourceOptions = {
9
+ /** The draft host Studio serves the dev server behind, from `ZAPPIE_PREVIEW_HOST`. Without it the plugin is off. */
10
+ previewHost: string | undefined;
11
+ };
12
+ /** A file {@link markSource} marked: its new code and a source map back to the saved file. */
13
+ type MarkedSource = {
14
+ code: string;
15
+ map: {
16
+ version: 3;
17
+ sources: string[];
18
+ names: string[];
19
+ mappings: string;
20
+ };
21
+ };
22
+ /**
23
+ * Mark each intrinsic JSX element under `app/` with the file and line that render it, and the component that holds it,
24
+ * so the Studio preview can tell Zee where a region the merchant selected lives. It runs only on the Studio dev server:
25
+ * `vite dev` with `previewHost`, the `ZAPPIE_PREVIEW_HOST` that Studio sets, given. `vite build` never runs it, so no
26
+ * source path reaches a published site.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { zappieSource } from "@zappie/sdk/vite";
31
+ *
32
+ * export default defineConfig({
33
+ * plugins: [zappieSource({ previewHost: process.env.ZAPPIE_PREVIEW_HOST }), reactRouter()],
34
+ * });
35
+ * ```
36
+ */
37
+ declare function zappieSource({ previewHost }: ZappieSourceOptions): Plugin;
38
+ /** Add source and component attributes to the intrinsic elements of one saved `.tsx` or `.jsx` file. */
39
+ declare function markSource(code: string, file: string): MarkedSource | null;
40
+
41
+ export { COMPONENT_ATTRIBUTE, type MarkedSource, SOURCE_ATTRIBUTE, type ZappieSourceOptions, markSource, zappieSource };
@@ -0,0 +1,154 @@
1
+ // src/vite/index.ts
2
+ import { parseSync } from "vite";
3
+ var SOURCE_ATTRIBUTE = "data-zappie-source";
4
+ var COMPONENT_ATTRIBUTE = "data-zappie-component";
5
+ function zappieSource({ previewHost }) {
6
+ let root = "";
7
+ return {
8
+ name: "zappie-source",
9
+ enforce: "pre",
10
+ apply: (_config, { command }) => command === "serve" && Boolean(previewHost),
11
+ configResolved(config) {
12
+ root = config.root;
13
+ },
14
+ transform: {
15
+ filter: { id: /\.[jt]sx$/ },
16
+ handler(code, id) {
17
+ if (!id.startsWith(`${root}/`)) {
18
+ return null;
19
+ }
20
+ const file = id.slice(root.length + 1);
21
+ if (!file.startsWith("app/") || file.includes("node_modules/")) {
22
+ return null;
23
+ }
24
+ return markSource(code, file);
25
+ }
26
+ }
27
+ };
28
+ }
29
+ function markSource(code, file) {
30
+ if (!/^[\x21-\x7e]+$/.test(file)) {
31
+ return null;
32
+ }
33
+ const escaped = file.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;");
34
+ const parsed = parseSync(file, code);
35
+ if (parsed.errors.length > 0) {
36
+ return null;
37
+ }
38
+ const lineStarts = [0];
39
+ for (let index = code.indexOf("\n"); index !== -1; index = code.indexOf("\n", index + 1)) {
40
+ lineStarts.push(index + 1);
41
+ }
42
+ const lineOf = (offset) => {
43
+ let low = 0;
44
+ let high = lineStarts.length - 1;
45
+ while (low < high) {
46
+ const middle = low + high + 1 >> 1;
47
+ if (lineStarts[middle] <= offset) {
48
+ low = middle;
49
+ } else {
50
+ high = middle - 1;
51
+ }
52
+ }
53
+ return low;
54
+ };
55
+ const inserts = [];
56
+ const visit = (value, component) => {
57
+ if (!value || typeof value !== "object") {
58
+ return;
59
+ }
60
+ if (Array.isArray(value)) {
61
+ for (const entry of value) {
62
+ visit(entry, component);
63
+ }
64
+ return;
65
+ }
66
+ const node = value;
67
+ const named = componentName(node) ?? component;
68
+ if (node.type === "JSXOpeningElement") {
69
+ const name = node.name;
70
+ const attributes = node.attributes;
71
+ if (name.type === "JSXIdentifier" && typeof name.name === "string" && /^[a-z]/.test(name.name) && !attributes.some(
72
+ (attribute) => attribute.name?.name === SOURCE_ATTRIBUTE
73
+ )) {
74
+ const component2 = named ? ` ${COMPONENT_ATTRIBUTE}="${named}"` : "";
75
+ inserts.push([
76
+ name.end,
77
+ ` ${SOURCE_ATTRIBUTE}="${escaped}:${lineOf(node.start) + 1}"${component2}`
78
+ ]);
79
+ }
80
+ }
81
+ for (const key in node) {
82
+ if (key !== "type" && key !== "start" && key !== "end") {
83
+ visit(node[key], named);
84
+ }
85
+ }
86
+ };
87
+ visit(parsed.program, void 0);
88
+ if (inserts.length === 0) {
89
+ return null;
90
+ }
91
+ inserts.sort((a, b) => a[0] - b[0]);
92
+ let output = "";
93
+ let from = 0;
94
+ const lines = lineStarts.map(
95
+ () => [[0, 0]]
96
+ );
97
+ const added = lineStarts.map(() => 0);
98
+ for (const [offset, text] of inserts) {
99
+ output += code.slice(from, offset) + text;
100
+ from = offset;
101
+ const line = lineOf(offset);
102
+ const column = offset - lineStarts[line];
103
+ added[line] += text.length;
104
+ lines[line].push([column + added[line], column]);
105
+ }
106
+ output += code.slice(from);
107
+ let previousLine = 0;
108
+ let previousColumn = 0;
109
+ const mappings = lines.map((segments, line) => {
110
+ let previousGenerated = 0;
111
+ return segments.map(([generated, original]) => {
112
+ const encoded = vlq(generated - previousGenerated) + "A" + vlq(line - previousLine) + vlq(original - previousColumn);
113
+ previousGenerated = generated;
114
+ previousLine = line;
115
+ previousColumn = original;
116
+ return encoded;
117
+ }).join(",");
118
+ }).join(";");
119
+ return {
120
+ code: output,
121
+ map: { version: 3, sources: [file], names: [], mappings }
122
+ };
123
+ }
124
+ var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
125
+ function vlq(value) {
126
+ let rest = value < 0 ? -value << 1 | 1 : value << 1;
127
+ let encoded = "";
128
+ do {
129
+ let digit = rest & 31;
130
+ rest >>>= 5;
131
+ if (rest > 0) {
132
+ digit |= 32;
133
+ }
134
+ encoded += BASE64[digit];
135
+ } while (rest > 0);
136
+ return encoded;
137
+ }
138
+ function componentName(node) {
139
+ const id = node.id ?? void 0;
140
+ const capitalised = (name) => typeof name === "string" && /^[A-Z]/.test(name) ? name : void 0;
141
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression") {
142
+ return capitalised(id?.name);
143
+ }
144
+ if (node.type === "VariableDeclarator" && id?.type === "Identifier") {
145
+ return capitalised(id.name);
146
+ }
147
+ return void 0;
148
+ }
149
+ export {
150
+ COMPONENT_ATTRIBUTE,
151
+ SOURCE_ATTRIBUTE,
152
+ markSource,
153
+ zappieSource
154
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zappie/sdk",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Typed, framework-agnostic client for Zappie menus, carts, checkout, auth and events.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -89,6 +89,14 @@
89
89
  "types": "./dist/template/react.d.ts",
90
90
  "import": "./dist/template/react.js"
91
91
  },
92
+ "./studio": {
93
+ "types": "./dist/studio/index.d.ts",
94
+ "import": "./dist/studio/index.js"
95
+ },
96
+ "./vite": {
97
+ "types": "./dist/vite/index.d.ts",
98
+ "import": "./dist/vite/index.js"
99
+ },
92
100
  "./persist/catalogue": {
93
101
  "types": "./dist/persist/catalogue.d.ts",
94
102
  "import": "./dist/persist/catalogue.js"
@@ -111,11 +119,15 @@
111
119
  "CHANGELOG.md"
112
120
  ],
113
121
  "peerDependencies": {
114
- "react": "^18.2.0 || ^19.0.0"
122
+ "react": "^18.2.0 || ^19.0.0",
123
+ "vite": "^8.0.0"
115
124
  },
116
125
  "peerDependenciesMeta": {
117
126
  "react": {
118
127
  "optional": true
128
+ },
129
+ "vite": {
130
+ "optional": true
119
131
  }
120
132
  },
121
133
  "repository": {
@@ -128,8 +140,11 @@
128
140
  "bugs": {
129
141
  "url": "https://github.com/zappie/storefront-packages/issues"
130
142
  },
143
+ "devDependencies": {
144
+ "vite": "8.3.4"
145
+ },
131
146
  "scripts": {
132
- "build": "tsup && tsup --config tsup.checkout.config.ts && tsup --config tsup.checkout-defaults.config.ts && tsup --config tsup.customer.config.ts && tsup --config tsup.places.config.ts && tsup --config tsup.url.config.ts && tsup --config tsup.redirect.config.ts && tsup --config tsup.visits.config.ts && tsup --config tsup.pricing.config.ts && tsup --config tsup.site-config.config.ts && tsup --config tsup.template.config.ts && node ../../scripts/release-packages/build-modules.mjs",
147
+ "build": "tsup && tsup --config tsup.checkout.config.ts && tsup --config tsup.checkout-defaults.config.ts && tsup --config tsup.customer.config.ts && tsup --config tsup.places.config.ts && tsup --config tsup.url.config.ts && tsup --config tsup.redirect.config.ts && tsup --config tsup.visits.config.ts && tsup --config tsup.pricing.config.ts && tsup --config tsup.site-config.config.ts && tsup --config tsup.template.config.ts && node ../../scripts/release-packages/build-modules.mjs --unmapped=studio/index.js --unmapped=testing/index.js --unmapped=vite/index.js",
133
148
  "test": "vitest run --root ../.. packages/sdk/test",
134
149
  "size": "node ../../scripts/release-packages/size-sdk.mjs && size-limit"
135
150
  }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/testing/index.ts"],
4
- "sourcesContent": ["import { estimateUnitPrice } from \"../cart/price\";\nimport type { WireQuote } from \"../cart/types\";\nimport { validateLine } from \"../cart/validate\";\nimport { createZappie } from \"../client\";\nimport type { ZappieErrorCode } from \"../errors\";\nimport { ZappieError } from \"../errors\";\nimport { delay, throwIfAborted } from \"../internal/abort\";\nimport { multiplyMoney } from \"../internal/money-math\";\nimport type { Menu, Store } from \"../models\";\nimport { money } from \"../money\";\nimport type { RequestCtx, Transport } from \"../transport/types\";\nexport { VERSION } from \"../index\";\n/** Store and branch menus supplied to the deterministic mock transport. */\nexport type MockFixtures = { store: Store; menus: Record<string, Menu> };\n/** Mock timing and per-operation failures. */\nexport type MockOptions = {\n latencyMs?: number;\n failWith?: Partial<Record<keyof Transport, ZappieErrorCode>>;\n};\n/** Create an isolated in-memory SDK client without making network requests. */\nexport function createMockZappie(input: MockFixtures, opts: MockOptions = {}) {\n const fixtures = structuredClone(input);\n const before = async (method: keyof Transport, ctx: RequestCtx) => {\n throwIfAborted(ctx.signal);\n if (opts.latencyMs) {\n await delay(opts.latencyMs, ctx.signal);\n }\n const code = opts.failWith?.[method];\n if (code) {\n throw new ZappieError(code, `Mock ${method} failure`);\n }\n };\n const menu = (id: string) => {\n const value = fixtures.menus[id];\n if (!value) {\n throw new ZappieError(\"branch_not_found\", \"No fixture for this branch\");\n }\n return structuredClone(value);\n };\n const transport: Transport = {\n async quote(ctx, body): Promise<WireQuote> {\n await before(\"quote\", ctx);\n const value = menu(body.branch_id);\n const branch = fixtures.store.branches.find(\n (b) => b.id === body.branch_id,\n )!;\n const issues: WireQuote[\"issues\"] = [];\n const lines = body.lines.map((line) => {\n const item = value.items[line.item_id];\n const input = {\n itemId: line.item_id,\n ...(line.menu_id !== undefined ? { menuId: line.menu_id } : {}),\n quantity: line.quantity,\n choices: line.options.map((c) => ({\n groupId: c.group_id,\n choiceId: c.choice_id,\n quantity: c.quantity,\n })),\n inputs: line.inputs,\n ...(line.variant_id ? { variantId: line.variant_id } : {}),\n };\n const invalid = validateLine(item, input, { menu: value, branch });\n issues.push(\n ...invalid.map((i) => ({\n code: i.code,\n severity: i.severity,\n message: i.message,\n line_id: line.line_id,\n ...(i.groupId ? { group_id: i.groupId } : {}),\n fixes: [],\n })),\n );\n const price =\n item && !invalid.length\n ? estimateUnitPrice(item, input)\n : money(0, fixtures.store.currency);\n return {\n line_id: line.line_id,\n unit_price: {\n amount_minor: price.amountMinor,\n currency: price.currency,\n },\n line_total: {\n amount_minor: multiplyMoney(price, line.quantity).amountMinor,\n currency: price.currency,\n },\n };\n });\n const subtotal = {\n amount_minor: lines.reduce((n, l) => n + l.line_total.amount_minor, 0),\n currency: fixtures.store.currency,\n };\n return {\n branch_id: body.branch_id,\n lines,\n subtotal,\n total: subtotal,\n charges: [],\n discounts: [],\n issues,\n can_checkout: !issues.some((issue) => issue.severity === \"blocking\"),\n checkout: issues.some((issue) => issue.severity === \"blocking\")\n ? { ready: false, reason: \"issues\" }\n : { ready: true },\n menu_version: value.version,\n expires_at: new Date(Date.now() + 300_000).toISOString(),\n };\n },\n async getStore(ctx) {\n await before(\"getStore\", ctx);\n return structuredClone(fixtures.store);\n },\n async getMenu(ctx) {\n await before(\"getMenu\", ctx);\n const value = menu(ctx.branchId);\n return ctx.ifNoneMatch === value.version\n ? { notModified: true }\n : { menu: value, etag: value.version };\n },\n async getMenuVersion(ctx) {\n await before(\"getMenuVersion\", ctx);\n const value = menu(ctx.branchId);\n return { version: value.version, availabilityHash: value.version };\n },\n async getItem(ctx) {\n await before(\"getItem\", ctx);\n const value = menu(ctx.branchId).items[ctx.itemId];\n if (!value) {\n throw new ZappieError(\"item_not_found\", \"Item not found\");\n }\n return value;\n },\n };\n return createZappie({\n store: fixtures.store.slug,\n key: \"pk_test_fixture\",\n transport,\n });\n}\nconst fulfilment = {\n pickup: true,\n delivery: true,\n dineIn: false,\n scheduling: false,\n};\nconst store: Store = {\n id: \"biz_fixture\",\n slug: \"default-store\",\n name: \"Default storefront\",\n currency: \"NGN\",\n timezone: \"Africa/Lagos\",\n brand: {},\n fulfilment,\n paymentMethods: [\"card\"],\n branches: [\n {\n id: \"br_fixture\",\n slug: \"main\",\n name: \"Main\",\n openingTimes: [{ day: 1, opens: \"08:00\", closes: \"22:00\" }],\n isOpen: true,\n fulfilment,\n },\n ],\n};\nconst menu: Menu = {\n id: \"menu_fixture\",\n branchId: \"br_fixture\",\n name: \"Menu\",\n version: \"fixture-v1\",\n categories: [{ id: \"cat_fixture\", name: \"Food\", itemIds: [\"item_fixture\"] }],\n items: {\n item_fixture: {\n id: \"item_fixture\",\n name: \"Lunch\",\n images: [],\n price: money(650000, \"NGN\"),\n isAvailable: true,\n quantity: { min: 1, step: 1 },\n variants: [\n {\n id: \"variant_fixture\",\n attributeChoices: {},\n price: money(650000, \"NGN\"),\n isAvailable: true,\n },\n ],\n optionGroups: [],\n tags: [],\n },\n },\n};\nconst ngn = (amountMinor: number) => money(amountMinor, \"NGN\");\nconst templateStore: Store = {\n id: \"biz_template\",\n slug: \"default-template\",\n name: \"Default template\",\n description: \"A public storefront fixture for template authors.\",\n templateSettings: {\n accent: \"#175c3a\",\n headerPhoto: \"https://example.com/default-template-cover.png\",\n },\n currency: \"NGN\",\n timezone: \"Africa/Lagos\",\n brand: {\n logoUrl: \"https://example.com/default-template-logo.png\",\n coverImageUrl: \"https://example.com/default-template-cover.png\",\n },\n fulfilment: { pickup: true, delivery: true, dineIn: true, scheduling: true },\n paymentMethods: [\"card\", \"bank_transfer\", \"wallet\"],\n branches: [\n {\n id: \"br_template_main\",\n slug: \"main\",\n name: \"Main\",\n timezone: \"Africa/Lagos\",\n address: \"1 Example Street, Lagos\",\n phone: \"+234 800 000 0000\",\n pickupAddress: {\n placeId: \"place_template_main\",\n coordinates: { lat: 6.5244, lng: 3.3792 },\n },\n socialLinks: [\n { type: \"INSTAGRAM\", url: \"https://instagram.com/default-template\" },\n { type: \"WEBSITE\", url: \"https://example.com\" },\n ],\n openingTimes: [1, 2, 3, 4, 5].map((day) => ({\n day: day as 1 | 2 | 3 | 4 | 5,\n opens: \"08:00\",\n closes: \"22:00\",\n })),\n isOpen: true,\n isDefault: true,\n fulfilment: {\n pickup: true,\n delivery: true,\n dineIn: true,\n scheduling: true,\n },\n minimumOrder: ngn(200000),\n },\n {\n id: \"br_template_island\",\n slug: \"island\",\n name: \"Island\",\n timezone: \"Africa/Lagos\",\n openingTimes: [{ day: 6, opens: \"10:00\", closes: \"18:00\" }],\n specialHours: [{ date: \"2026-12-25\", closed: true }],\n isOpen: false,\n closedUntil: \"2026-12-26T10:00:00+01:00\",\n fulfilment: {\n pickup: false,\n delivery: true,\n dineIn: false,\n scheduling: false,\n },\n },\n ],\n};\nconst templateMenu: Menu = {\n id: \"menu_template\",\n branchId: \"br_template_main\",\n name: \"All day\",\n version: \"template-v1\",\n categories: [\n {\n id: \"cat_popular\",\n name: \"Popular\",\n itemIds: [\"item_jollof\", \"item_zobo\"],\n },\n { id: \"cat_mains\", name: \"Mains\", itemIds: [\"item_jollof\"] },\n {\n id: \"cat_drinks\",\n name: \"Drinks and desserts\",\n itemIds: [\"item_zobo\", \"item_cake\"],\n },\n ],\n items: {\n item_jollof: {\n id: \"item_jollof\",\n name: \"Jollof rice\",\n description: \"Smoky party jollof.\",\n images: [],\n price: ngn(450000),\n isAvailable: true,\n quantity: { min: 1, max: 10, step: 1 },\n variants: [\n {\n id: \"variant_jollof\",\n attributeChoices: {},\n price: ngn(450000),\n isAvailable: true,\n },\n ],\n optionGroups: [\n {\n id: \"grp_protein\",\n name: \"Protein\",\n kind: \"select\",\n required: true,\n selection: { min: 1, max: 1 },\n isAttribute: false,\n choices: [\n {\n id: \"opt_chicken\",\n name: \"Chicken\",\n price: ngn(0),\n isAvailable: true,\n },\n {\n id: \"opt_beef\",\n name: \"Beef\",\n price: ngn(50000),\n isAvailable: true,\n },\n {\n id: \"opt_fish\",\n name: \"Fish\",\n price: ngn(80000),\n isAvailable: false,\n },\n ],\n },\n {\n id: \"grp_sauce\",\n name: \"Sauce\",\n helpText: \"Required with chicken.\",\n kind: \"select\",\n required: false,\n selection: { min: 0, max: 1 },\n isAttribute: false,\n choices: [\n {\n id: \"opt_pepper\",\n name: \"Pepper sauce\",\n price: ngn(0),\n isAvailable: true,\n },\n {\n id: \"opt_stew\",\n name: \"Stew\",\n price: ngn(20000),\n isAvailable: true,\n },\n ],\n conditions: [\n {\n groupId: \"grp_protein\",\n choiceIds: [\"opt_chicken\"],\n match: \"any\",\n setRequired: true,\n setMin: 1,\n },\n ],\n },\n {\n id: \"grp_extras\",\n name: \"Extras\",\n kind: \"select\",\n required: false,\n selection: { min: 0, max: 3 },\n isAttribute: false,\n choices: [\n {\n id: \"opt_plantain\",\n name: \"Plantain\",\n price: ngn(30000),\n isAvailable: true,\n quantity: { min: 1, max: 3 },\n },\n {\n id: \"opt_egg\",\n name: \"Boiled egg\",\n price: ngn(15000),\n isAvailable: true,\n },\n ],\n },\n {\n id: \"grp_note\",\n name: \"Note for the kitchen\",\n kind: \"text\",\n required: false,\n selection: { min: 0 },\n isAttribute: false,\n choices: [],\n input: { maxLength: 140, placeholder: \"No onions, please\" },\n },\n ],\n tags: [\"popular\"],\n },\n item_zobo: {\n id: \"item_zobo\",\n name: \"Zobo\",\n images: [],\n price: ngn(120000),\n isAvailable: true,\n quantity: { min: 1, step: 1 },\n variants: [\n {\n id: \"variant_zobo_small\",\n attributeChoices: { grp_size: \"opt_small\" },\n price: ngn(120000),\n isAvailable: true,\n },\n {\n id: \"variant_zobo_large\",\n attributeChoices: { grp_size: \"opt_large\" },\n price: ngn(200000),\n isAvailable: false,\n },\n ],\n optionGroups: [\n {\n id: \"grp_size\",\n name: \"Size\",\n kind: \"select\",\n required: true,\n selection: { min: 1, max: 1 },\n isAttribute: true,\n choices: [\n {\n id: \"opt_small\",\n name: \"Small\",\n price: ngn(0),\n isAvailable: true,\n },\n {\n id: \"opt_large\",\n name: \"Large\",\n price: ngn(0),\n isAvailable: false,\n },\n ],\n },\n ],\n tags: [],\n },\n item_cake: {\n id: \"item_cake\",\n name: \"Chocolate cake\",\n images: [],\n price: ngn(350000),\n compareAtPrice: ngn(400000),\n isAvailable: false,\n quantity: { min: 1, step: 1 },\n variants: [\n {\n id: \"variant_cake\",\n attributeChoices: {},\n price: ngn(350000),\n isAvailable: false,\n },\n ],\n optionGroups: [],\n tags: [],\n },\n },\n};\n/**\n * Sample data with no merchant IDs or photos. `minimal` has one plain item.\n * `defaultTemplate` covers several branches, shared category membership, required and\n * conditional options, per-choice quantities, text input and attribute variants.\n */\nexport const fixtures = {\n minimal: { store, menu },\n defaultTemplate: { store: templateStore, menu: templateMenu },\n};\n"],
5
- "mappings": "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",
6
- "names": ["fixtures", "menu", "input"]
7
- }