@zappie/sdk 0.1.18 → 0.1.19
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 +6 -0
- package/dist/debug.js +1 -1
- package/dist/index.js +1 -1
- package/dist/studio/index.d.ts +39 -0
- package/dist/studio/index.js +314 -0
- package/dist/template/react.js +19 -0
- package/dist/template/react.js.map +2 -2
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @zappie/sdk
|
|
2
2
|
|
|
3
|
+
## 0.1.19
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 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`.
|
|
8
|
+
|
|
3
9
|
## 0.1.18
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/dist/debug.js
CHANGED
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.
|
|
13
|
+
var VERSION = true ? "0.1.19" : "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
|
+
};
|
package/dist/template/react.js
CHANGED
|
@@ -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;
|
|
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": "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",
|
|
6
6
|
"names": ["message"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zappie/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
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,10 @@
|
|
|
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
|
+
},
|
|
92
96
|
"./persist/catalogue": {
|
|
93
97
|
"types": "./dist/persist/catalogue.d.ts",
|
|
94
98
|
"import": "./dist/persist/catalogue.js"
|
|
@@ -129,7 +133,7 @@
|
|
|
129
133
|
"url": "https://github.com/zappie/storefront-packages/issues"
|
|
130
134
|
},
|
|
131
135
|
"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",
|
|
136
|
+
"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",
|
|
133
137
|
"test": "vitest run --root ../.. packages/sdk/test",
|
|
134
138
|
"size": "node ../../scripts/release-packages/size-sdk.mjs && size-limit"
|
|
135
139
|
}
|