@zappie/foundations 0.1.1 → 0.1.2
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 +8 -0
- package/dist/account/index.js +49 -0
- package/dist/account/index.js.map +7 -0
- package/dist/cart/index.js +168 -0
- package/dist/cart/index.js.map +7 -0
- package/dist/fulfilment/address-picker.js +168 -0
- package/dist/fulfilment/address-picker.js.map +7 -0
- package/dist/fulfilment/branch-picker.js +54 -0
- package/dist/fulfilment/branch-picker.js.map +7 -0
- package/dist/fulfilment/schedule.js +146 -0
- package/dist/fulfilment/schedule.js.map +7 -0
- package/dist/fulfilment/switch.js +54 -0
- package/dist/fulfilment/switch.js.map +7 -0
- package/dist/index.js +48 -2114
- package/dist/index.js.map +7 -1
- package/dist/internal/combobox.js +131 -0
- package/dist/internal/combobox.js.map +7 -0
- package/dist/internal/data-attrs.js +8 -0
- package/dist/internal/data-attrs.js.map +7 -0
- package/dist/internal/ids.js +10 -0
- package/dist/internal/ids.js.map +7 -0
- package/dist/{chunk-D3J7S33L.js → internal/slot.js} +13 -6
- package/dist/internal/slot.js.map +7 -0
- package/dist/internal/use-controllable-state.js +29 -0
- package/dist/internal/use-controllable-state.js.map +7 -0
- package/dist/item-form/index.js +13 -0
- package/dist/item-form/index.js.map +7 -0
- package/dist/item-form/option-group.js +268 -0
- package/dist/item-form/option-group.js.map +7 -0
- package/dist/item-form/options.js +30 -0
- package/dist/item-form/options.js.map +7 -0
- package/dist/item-form/quantity.js +131 -0
- package/dist/item-form/quantity.js.map +7 -0
- package/dist/item-form/root.js +151 -0
- package/dist/item-form/root.js.map +7 -0
- package/dist/media/image.js +30 -0
- package/dist/media/image.js.map +7 -0
- package/dist/money.js +20 -0
- package/dist/money.js.map +7 -0
- package/dist/next.js +8 -6
- package/dist/next.js.map +7 -1
- package/dist/otp/input.js +110 -0
- package/dist/otp/input.js.map +7 -0
- package/dist/overlay/engine.js +16 -8
- package/dist/overlay/engine.js.map +7 -1
- package/dist/{chunk-BY2WQGLO.js → overlay/history.js} +9 -4
- package/dist/overlay/history.js.map +7 -0
- package/dist/overlay/react.js +261 -0
- package/dist/overlay/react.js.map +7 -0
- package/dist/payment/methods.js +103 -0
- package/dist/payment/methods.js.map +7 -0
- package/dist/rich-text/banners.js +9 -0
- package/dist/rich-text/banners.js.map +7 -0
- package/dist/rich-text/browser.js +3 -3
- package/dist/rich-text/browser.js.map +7 -1
- package/dist/rich-text/core.js +75 -0
- package/dist/rich-text/core.js.map +7 -0
- package/dist/rich-text/index.js +3 -79
- package/dist/rich-text/index.js.map +7 -1
- package/dist/search/engine.js +93 -0
- package/dist/search/engine.js.map +7 -0
- package/dist/search/index.js +175 -0
- package/dist/search/index.js.map +7 -0
- package/dist/status/hours.js +177 -0
- package/dist/status/hours.js.map +7 -0
- package/dist/status/open-status.js +45 -0
- package/dist/status/open-status.js.map +7 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +7 -0
- package/package.json +3 -3
- package/dist/chunk-BY2WQGLO.js.map +0 -1
- package/dist/chunk-D3J7S33L.js.map +0 -1
|
@@ -1 +1,7 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/rich-text/index.ts"],
|
|
4
|
+
"sourcesContent": ["export * from \"./banners\";\nexport * from \"./core\";\n"],
|
|
5
|
+
"mappings": ";AAAA,cAAc;AACd,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/search/engine.ts
|
|
2
|
+
function normalizeSearch(text) {
|
|
3
|
+
return text.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
4
|
+
}
|
|
5
|
+
function near(a, b) {
|
|
6
|
+
if (Math.abs(a.length - b.length) > 1) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
let i = 0;
|
|
10
|
+
let j = 0;
|
|
11
|
+
let edits = 0;
|
|
12
|
+
while (i < a.length && j < b.length) {
|
|
13
|
+
if (a[i] === b[j]) {
|
|
14
|
+
i++;
|
|
15
|
+
j++;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (++edits > 1) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (a.length === b.length) {
|
|
22
|
+
if (a[i] === b[j + 1] && a[i + 1] === b[j]) {
|
|
23
|
+
i += 2;
|
|
24
|
+
j += 2;
|
|
25
|
+
} else {
|
|
26
|
+
i++;
|
|
27
|
+
j++;
|
|
28
|
+
}
|
|
29
|
+
} else if (a.length > b.length) {
|
|
30
|
+
i++;
|
|
31
|
+
} else {
|
|
32
|
+
j++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return edits + (a.length - i) + (b.length - j) <= 1;
|
|
36
|
+
}
|
|
37
|
+
function createSearchIndex(menu) {
|
|
38
|
+
const entries = [
|
|
39
|
+
...Object.values(menu.items).map((item) => ({
|
|
40
|
+
result: { type: "item", item },
|
|
41
|
+
fields: [
|
|
42
|
+
normalizeSearch(item.name),
|
|
43
|
+
normalizeSearch(item.tags.join(" ")),
|
|
44
|
+
normalizeSearch(item.description ?? "")
|
|
45
|
+
]
|
|
46
|
+
})),
|
|
47
|
+
...menu.categories.map((category) => ({
|
|
48
|
+
result: { type: "category", category },
|
|
49
|
+
fields: [
|
|
50
|
+
normalizeSearch(category.name),
|
|
51
|
+
[],
|
|
52
|
+
normalizeSearch(category.description ?? "")
|
|
53
|
+
]
|
|
54
|
+
}))
|
|
55
|
+
];
|
|
56
|
+
return (query) => {
|
|
57
|
+
const tokens = normalizeSearch(query);
|
|
58
|
+
if (!tokens.length) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
const results = [];
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
let score = 0;
|
|
64
|
+
for (const token of tokens) {
|
|
65
|
+
let best = 0;
|
|
66
|
+
for (let field = 0; field < entry.fields.length; field++) {
|
|
67
|
+
const weight = [30, 20, 10][field];
|
|
68
|
+
for (const word of entry.fields[field]) {
|
|
69
|
+
if (word.startsWith(token)) {
|
|
70
|
+
best = Math.max(best, weight + (word === token ? 3 : 2));
|
|
71
|
+
} else if (token.length >= 4 && near(token, word)) {
|
|
72
|
+
best = Math.max(best, weight);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!best) {
|
|
77
|
+
score = 0;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
score += best;
|
|
81
|
+
}
|
|
82
|
+
if (score) {
|
|
83
|
+
results.push({ ...entry.result, score });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results.sort((a, b) => b.score - a.score);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
createSearchIndex,
|
|
91
|
+
normalizeSearch
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/search/engine.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Category, Item, Menu } from \"@zappie/sdk\";\n/**\n * A scored item or category returned by menu search, ordered by descending score.\n */\nexport type SearchResult =\n | { type: \"item\"; item: Item; score: number }\n | { type: \"category\"; category: Category; score: number };\nexport function normalizeSearch(text: string) {\n return text\n .normalize(\"NFD\")\n .replace(/\\p{M}/gu, \"\")\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean);\n}\nfunction near(a: string, b: string) {\n if (Math.abs(a.length - b.length) > 1) {\n return false;\n }\n let i = 0;\n let j = 0;\n let edits = 0;\n while (i < a.length && j < b.length) {\n if (a[i] === b[j]) {\n i++;\n j++;\n continue;\n }\n if (++edits > 1) {\n return false;\n }\n if (a.length === b.length) {\n if (a[i] === b[j + 1] && a[i + 1] === b[j]) {\n i += 2;\n j += 2;\n } else {\n i++;\n j++;\n }\n } else if (a.length > b.length) {\n i++;\n } else {\n j++;\n }\n }\n return edits + (a.length - i) + (b.length - j) <= 1;\n}\n/**\n * Index item names, tags and descriptions plus category names and descriptions for local search.\n * Matches case- and accent-insensitive prefixes and allows a single edit for query tokens of at least four characters.\n *\n * @param menu - The menu snapshot you want to search.\n * @returns A search function that requires every query token to match and sorts results by descending score.\n */\nexport function createSearchIndex(menu: Menu) {\n const entries = [\n ...Object.values(menu.items).map((item) => ({\n result: { type: \"item\" as const, item },\n fields: [\n normalizeSearch(item.name),\n normalizeSearch(item.tags.join(\" \")),\n normalizeSearch(item.description ?? \"\"),\n ],\n })),\n ...menu.categories.map((category) => ({\n result: { type: \"category\" as const, category },\n fields: [\n normalizeSearch(category.name),\n [],\n normalizeSearch(category.description ?? \"\"),\n ],\n })),\n ];\n return (query: string): SearchResult[] => {\n const tokens = normalizeSearch(query);\n if (!tokens.length) {\n return [];\n }\n const results: SearchResult[] = [];\n for (const entry of entries) {\n let score = 0;\n for (const token of tokens) {\n let best = 0;\n for (let field = 0; field < entry.fields.length; field++) {\n const weight = [30, 20, 10][field]!;\n for (const word of entry.fields[field]!) {\n if (word.startsWith(token)) {\n best = Math.max(best, weight + (word === token ? 3 : 2));\n } else if (token.length >= 4 && near(token, word)) {\n best = Math.max(best, weight);\n }\n }\n }\n if (!best) {\n score = 0;\n break;\n }\n score += best;\n }\n if (score) {\n results.push({ ...entry.result, score });\n }\n }\n return results.sort((a, b) => b.score - a.score);\n };\n}\n"],
|
|
5
|
+
"mappings": ";AAOO,SAAS,gBAAgB,MAAc;AAC5C,SAAO,KACJ,UAAU,KAAK,EACf,QAAQ,WAAW,EAAE,EACrB,YAAY,EACZ,MAAM,iBAAiB,EACvB,OAAO,OAAO;AACnB;AACA,SAAS,KAAK,GAAW,GAAW;AAClC,MAAI,KAAK,IAAI,EAAE,SAAS,EAAE,MAAM,IAAI,GAAG;AACrC,WAAO;AAAA,EACT;AACA,MAAI,IAAI;AACR,MAAI,IAAI;AACR,MAAI,QAAQ;AACZ,SAAO,IAAI,EAAE,UAAU,IAAI,EAAE,QAAQ;AACnC,QAAI,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG;AACjB;AACA;AACA;AAAA,IACF;AACA,QAAI,EAAE,QAAQ,GAAG;AACf,aAAO;AAAA,IACT;AACA,QAAI,EAAE,WAAW,EAAE,QAAQ;AACzB,UAAI,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG;AAC1C,aAAK;AACL,aAAK;AAAA,MACP,OAAO;AACL;AACA;AAAA,MACF;AAAA,IACF,WAAW,EAAE,SAAS,EAAE,QAAQ;AAC9B;AAAA,IACF,OAAO;AACL;AAAA,IACF;AAAA,EACF;AACA,SAAO,SAAS,EAAE,SAAS,MAAM,EAAE,SAAS,MAAM;AACpD;AAQO,SAAS,kBAAkB,MAAY;AAC5C,QAAM,UAAU;AAAA,IACd,GAAG,OAAO,OAAO,KAAK,KAAK,EAAE,IAAI,CAAC,UAAU;AAAA,MAC1C,QAAQ,EAAE,MAAM,QAAiB,KAAK;AAAA,MACtC,QAAQ;AAAA,QACN,gBAAgB,KAAK,IAAI;AAAA,QACzB,gBAAgB,KAAK,KAAK,KAAK,GAAG,CAAC;AAAA,QACnC,gBAAgB,KAAK,eAAe,EAAE;AAAA,MACxC;AAAA,IACF,EAAE;AAAA,IACF,GAAG,KAAK,WAAW,IAAI,CAAC,cAAc;AAAA,MACpC,QAAQ,EAAE,MAAM,YAAqB,SAAS;AAAA,MAC9C,QAAQ;AAAA,QACN,gBAAgB,SAAS,IAAI;AAAA,QAC7B,CAAC;AAAA,QACD,gBAAgB,SAAS,eAAe,EAAE;AAAA,MAC5C;AAAA,IACF,EAAE;AAAA,EACJ;AACA,SAAO,CAAC,UAAkC;AACxC,UAAM,SAAS,gBAAgB,KAAK;AACpC,QAAI,CAAC,OAAO,QAAQ;AAClB,aAAO,CAAC;AAAA,IACV;AACA,UAAM,UAA0B,CAAC;AACjC,eAAW,SAAS,SAAS;AAC3B,UAAI,QAAQ;AACZ,iBAAW,SAAS,QAAQ;AAC1B,YAAI,OAAO;AACX,iBAAS,QAAQ,GAAG,QAAQ,MAAM,OAAO,QAAQ,SAAS;AACxD,gBAAM,SAAS,CAAC,IAAI,IAAI,EAAE,EAAE,KAAK;AACjC,qBAAW,QAAQ,MAAM,OAAO,KAAK,GAAI;AACvC,gBAAI,KAAK,WAAW,KAAK,GAAG;AAC1B,qBAAO,KAAK,IAAI,MAAM,UAAU,SAAS,QAAQ,IAAI,EAAE;AAAA,YACzD,WAAW,MAAM,UAAU,KAAK,KAAK,OAAO,IAAI,GAAG;AACjD,qBAAO,KAAK,IAAI,MAAM,MAAM;AAAA,YAC9B;AAAA,UACF;AAAA,QACF;AACA,YAAI,CAAC,MAAM;AACT,kBAAQ;AACR;AAAA,QACF;AACA,iBAAS;AAAA,MACX;AACA,UAAI,OAAO;AACT,gBAAQ,KAAK,EAAE,GAAG,MAAM,QAAQ,MAAM,CAAC;AAAA,MACzC;AAAA,IACF;AACA,WAAO,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAAA,EACjD;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/search/index.tsx
|
|
4
|
+
import { useCart, useMenu, useZappie } from "@zappie/sdk/react";
|
|
5
|
+
import {
|
|
6
|
+
createContext,
|
|
7
|
+
forwardRef,
|
|
8
|
+
useContext,
|
|
9
|
+
useEffect,
|
|
10
|
+
useMemo
|
|
11
|
+
} from "react";
|
|
12
|
+
import {
|
|
13
|
+
countAnnouncement,
|
|
14
|
+
useCombobox,
|
|
15
|
+
withLiveRegion
|
|
16
|
+
} from "../internal/combobox.js";
|
|
17
|
+
import { composeRefs, part } from "../internal/slot.js";
|
|
18
|
+
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
19
|
+
import { createSearchIndex } from "./engine.js";
|
|
20
|
+
import { createSearchIndex as createSearchIndex2 } from "./engine.js";
|
|
21
|
+
import { jsx } from "react/jsx-runtime";
|
|
22
|
+
var Context = createContext(null);
|
|
23
|
+
function useSearch() {
|
|
24
|
+
const ctx = useContext(Context);
|
|
25
|
+
if (!ctx) {
|
|
26
|
+
throw new Error("Search parts require Search.Root");
|
|
27
|
+
}
|
|
28
|
+
return ctx;
|
|
29
|
+
}
|
|
30
|
+
function Root(props) {
|
|
31
|
+
if (props.source) {
|
|
32
|
+
return /* @__PURE__ */ jsx(ControlledRoot, { ...props, source: props.source });
|
|
33
|
+
}
|
|
34
|
+
return /* @__PURE__ */ jsx(UncontrolledRoot, { ...props });
|
|
35
|
+
}
|
|
36
|
+
function UncontrolledRoot(props) {
|
|
37
|
+
const cart = useCart();
|
|
38
|
+
const menu = useMenu(cart.branchId ?? void 0);
|
|
39
|
+
const [query, setQuery] = useControllableState({
|
|
40
|
+
value: props.query,
|
|
41
|
+
defaultValue: props.defaultQuery ?? "",
|
|
42
|
+
onChange: props.onQueryChange
|
|
43
|
+
});
|
|
44
|
+
const index = useMemo(
|
|
45
|
+
() => menu.data ? createSearchIndex(menu.data) : () => [],
|
|
46
|
+
[menu.data]
|
|
47
|
+
);
|
|
48
|
+
const results = useMemo(() => index(query), [index, query]);
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
ControlledRoot,
|
|
51
|
+
{
|
|
52
|
+
...props,
|
|
53
|
+
query,
|
|
54
|
+
onQueryChange: setQuery,
|
|
55
|
+
source: { results, isLoading: menu.isLoading, isStale: menu.isStale }
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
function ControlledRoot({
|
|
60
|
+
query,
|
|
61
|
+
defaultQuery = "",
|
|
62
|
+
onQueryChange,
|
|
63
|
+
onSelect = () => {
|
|
64
|
+
},
|
|
65
|
+
announcement = countAnnouncement,
|
|
66
|
+
source,
|
|
67
|
+
...props
|
|
68
|
+
}) {
|
|
69
|
+
const client = useZappie();
|
|
70
|
+
const [value, setValue] = useControllableState({
|
|
71
|
+
value: query,
|
|
72
|
+
defaultValue: defaultQuery,
|
|
73
|
+
onChange: onQueryChange
|
|
74
|
+
});
|
|
75
|
+
const results = source.results;
|
|
76
|
+
const combo = useCombobox(results, onSelect);
|
|
77
|
+
const setQuery = (next) => {
|
|
78
|
+
setValue(next);
|
|
79
|
+
combo.setActiveIndex(-1);
|
|
80
|
+
combo.setOpen(true);
|
|
81
|
+
};
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!value.trim()) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const timer = setTimeout(
|
|
87
|
+
() => client._internal.emit("search", {
|
|
88
|
+
query: value,
|
|
89
|
+
resultCount: results.length
|
|
90
|
+
}),
|
|
91
|
+
600
|
|
92
|
+
);
|
|
93
|
+
return () => clearTimeout(timer);
|
|
94
|
+
}, [client, value, results.length]);
|
|
95
|
+
const state = {
|
|
96
|
+
...combo,
|
|
97
|
+
query: value,
|
|
98
|
+
setQuery,
|
|
99
|
+
results,
|
|
100
|
+
setComposing: (value2) => source.onCompositionChange?.(value2),
|
|
101
|
+
...source.error ? { error: source.error } : {},
|
|
102
|
+
...source.retry ? { retry: source.retry } : {}
|
|
103
|
+
};
|
|
104
|
+
return /* @__PURE__ */ jsx(Context.Provider, { value: state, children: part(
|
|
105
|
+
"div",
|
|
106
|
+
withLiveRegion(
|
|
107
|
+
props,
|
|
108
|
+
combo.isOpen && value.trim() ? announcement(results.length) : ""
|
|
109
|
+
),
|
|
110
|
+
state,
|
|
111
|
+
{
|
|
112
|
+
"data-state": source.error ? "error" : source.isLoading ? "loading" : source.isStale ? "stale" : "valid",
|
|
113
|
+
onBlur: (event) => {
|
|
114
|
+
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
115
|
+
combo.setOpen(false);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
) });
|
|
120
|
+
}
|
|
121
|
+
var Input = forwardRef(
|
|
122
|
+
function Input2(props, ref) {
|
|
123
|
+
const state = useSearch();
|
|
124
|
+
return part(
|
|
125
|
+
"input",
|
|
126
|
+
props,
|
|
127
|
+
state,
|
|
128
|
+
{
|
|
129
|
+
...state.inputProps,
|
|
130
|
+
type: "search",
|
|
131
|
+
onCompositionStart: () => state.setComposing(true),
|
|
132
|
+
onCompositionEnd: () => state.setComposing(false),
|
|
133
|
+
value: state.query,
|
|
134
|
+
onChange: (event) => state.setQuery(event.target.value)
|
|
135
|
+
},
|
|
136
|
+
ref
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
var Results = forwardRef(
|
|
141
|
+
function Results2(props, ref) {
|
|
142
|
+
const state = useSearch();
|
|
143
|
+
return part(
|
|
144
|
+
"div",
|
|
145
|
+
props,
|
|
146
|
+
state,
|
|
147
|
+
state.listProps,
|
|
148
|
+
composeRefs(ref, (node) => {
|
|
149
|
+
if (node?.id && node.id !== state.listProps.id) {
|
|
150
|
+
state.setListId(node.id);
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
var Result = forwardRef(function Result2({ index, ...props }, ref) {
|
|
157
|
+
const state = useSearch();
|
|
158
|
+
return part(
|
|
159
|
+
"div",
|
|
160
|
+
props,
|
|
161
|
+
state,
|
|
162
|
+
state.getOptionProps(index),
|
|
163
|
+
composeRefs(ref, (node) => {
|
|
164
|
+
if (node?.id && node.id !== state.getOptionProps(index).id) {
|
|
165
|
+
state.registerOption(index, node.id);
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
var Search = { Root, Input, Results, Result };
|
|
171
|
+
export {
|
|
172
|
+
Search,
|
|
173
|
+
createSearchIndex2 as createSearchIndex
|
|
174
|
+
};
|
|
175
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/search/index.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\nimport { useCart, useMenu, useZappie } from \"@zappie/sdk/react\";\nimport {\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useMemo,\n type ChangeEvent,\n} from \"react\";\nimport {\n countAnnouncement,\n useCombobox,\n withLiveRegion,\n} from \"../internal/combobox\";\nimport { composeRefs, part, type PartProps } from \"../internal/slot\";\nimport { useControllableState } from \"../internal/use-controllable-state\";\nimport { createSearchIndex, type SearchResult } from \"./engine\";\n/**\n * Search query, results and combobox navigation exposed to search parts and render functions.\n */\nexport type SearchState = ReturnType<typeof useCombobox<SearchResult>> & {\n query: string;\n setQuery: (query: string) => void;\n results: SearchResult[];\n setComposing: (value: boolean) => void;\n error?: string;\n retry?: () => void;\n};\nconst Context = createContext<SearchState | null>(null);\nfunction useSearch() {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\"Search parts require Search.Root\");\n }\n return ctx;\n}\n/**\n * Externally supplied search results and optional loading, error, retry and composition controls.\n */\nexport type SearchSource = {\n results: SearchResult[];\n isLoading?: boolean;\n isStale?: boolean;\n error?: string;\n retry?: () => void;\n onCompositionChange?: (composing: boolean) => void;\n};\ntype RootProps = Omit<PartProps<SearchState>, \"onSelect\"> & {\n announcement?: (count: number) => string;\n query?: string;\n defaultQuery?: string;\n onQueryChange?: (query: string) => void;\n onSelect?: (result: SearchResult) => void;\n source?: SearchSource;\n};\nfunction Root(props: RootProps) {\n if (props.source) {\n return <ControlledRoot {...props} source={props.source} />;\n }\n return <UncontrolledRoot {...props} />;\n}\nfunction UncontrolledRoot(props: RootProps) {\n const cart = useCart();\n const menu = useMenu(cart.branchId ?? undefined);\n const [query, setQuery] = useControllableState({\n value: props.query,\n defaultValue: props.defaultQuery ?? \"\",\n onChange: props.onQueryChange,\n });\n const index = useMemo(\n () => (menu.data ? createSearchIndex(menu.data) : () => []),\n [menu.data],\n );\n const results = useMemo(() => index(query), [index, query]);\n return (\n <ControlledRoot\n {...props}\n query={query}\n onQueryChange={setQuery}\n source={{ results, isLoading: menu.isLoading, isStale: menu.isStale }}\n />\n );\n}\nfunction ControlledRoot({\n query,\n defaultQuery = \"\",\n onQueryChange,\n onSelect = () => {},\n announcement = countAnnouncement,\n source,\n ...props\n}: RootProps & { source: SearchSource }) {\n const client = useZappie();\n const [value, setValue] = useControllableState({\n value: query,\n defaultValue: defaultQuery,\n onChange: onQueryChange,\n });\n const results = source.results;\n const combo = useCombobox(results, onSelect);\n const setQuery = (next: string) => {\n setValue(next);\n combo.setActiveIndex(-1);\n combo.setOpen(true);\n };\n useEffect(() => {\n if (!value.trim()) {\n return;\n }\n const timer = setTimeout(\n () =>\n client._internal.emit(\"search\", {\n query: value,\n resultCount: results.length,\n }),\n 600,\n );\n return () => clearTimeout(timer);\n }, [client, value, results.length]);\n const state = {\n ...combo,\n query: value,\n setQuery,\n results,\n setComposing: (value: boolean) => source.onCompositionChange?.(value),\n ...(source.error ? { error: source.error } : {}),\n ...(source.retry ? { retry: source.retry } : {}),\n };\n return (\n <Context.Provider value={state}>\n {part(\n \"div\",\n withLiveRegion(\n props,\n combo.isOpen && value.trim() ? announcement(results.length) : \"\",\n ) as typeof props,\n state,\n {\n \"data-state\": source.error\n ? \"error\"\n : source.isLoading\n ? \"loading\"\n : source.isStale\n ? \"stale\"\n : \"valid\",\n onBlur: (event: {\n currentTarget: HTMLElement;\n relatedTarget: Node | null;\n }) => {\n if (!event.currentTarget.contains(event.relatedTarget)) {\n combo.setOpen(false);\n }\n },\n },\n )}\n </Context.Provider>\n );\n}\nconst Input = forwardRef<HTMLElement, PartProps<SearchState>>(\n function Input(props, ref) {\n const state = useSearch();\n return part(\n \"input\",\n props,\n state,\n {\n ...state.inputProps,\n type: \"search\",\n onCompositionStart: () => state.setComposing(true),\n onCompositionEnd: () => state.setComposing(false),\n value: state.query,\n onChange: (event: ChangeEvent<HTMLInputElement>) =>\n state.setQuery(event.target.value),\n },\n ref,\n );\n },\n);\nconst Results = forwardRef<HTMLElement, PartProps<SearchState>>(\n function Results(props, ref) {\n const state = useSearch();\n return part(\n \"div\",\n props,\n state,\n state.listProps,\n composeRefs(ref, (node) => {\n if (node?.id && node.id !== state.listProps.id) {\n state.setListId(node.id);\n }\n }),\n );\n },\n);\nconst Result = forwardRef<\n HTMLElement,\n PartProps<SearchState> & { index: number }\n>(function Result({ index, ...props }, ref) {\n const state = useSearch();\n return part(\n \"div\",\n props,\n state,\n state.getOptionProps(index),\n composeRefs(ref, (node) => {\n if (node?.id && node.id !== state.getOptionProps(index).id) {\n state.registerOption(index, node.id);\n }\n }),\n );\n});\n/**\n * Composable menu search parts with combobox navigation and result announcements.\n * Use Root with your own source or let it index the active branch menu through your ZappieProvider.\n */\nexport const Search = { Root, Input, Results, Result };\nexport { createSearchIndex } from \"./engine\";\nexport type { SearchResult } from \"./engine\";\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["createSearchIndex", "value", "Input", "Results", "Result"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// src/status/hours.ts
|
|
2
|
+
var DAY = 864e5;
|
|
3
|
+
var formatters = /* @__PURE__ */ new Map();
|
|
4
|
+
function formatter(timezone) {
|
|
5
|
+
let value = formatters.get(timezone);
|
|
6
|
+
if (!value) {
|
|
7
|
+
value = new Intl.DateTimeFormat("en-CA", {
|
|
8
|
+
timeZone: timezone,
|
|
9
|
+
year: "numeric",
|
|
10
|
+
month: "2-digit",
|
|
11
|
+
day: "2-digit",
|
|
12
|
+
hour: "2-digit",
|
|
13
|
+
minute: "2-digit",
|
|
14
|
+
second: "2-digit",
|
|
15
|
+
hourCycle: "h23"
|
|
16
|
+
});
|
|
17
|
+
formatters.set(timezone, value);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function localParts(date, timezone) {
|
|
22
|
+
const parts = Object.fromEntries(
|
|
23
|
+
formatter(timezone).formatToParts(date).map(({ type, value }) => [type, value])
|
|
24
|
+
);
|
|
25
|
+
return {
|
|
26
|
+
date: `${parts.year}-${parts.month}-${parts.day}`,
|
|
27
|
+
hour: Number(parts.hour),
|
|
28
|
+
minute: Number(parts.minute),
|
|
29
|
+
second: Number(parts.second)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function parseWallClock(value) {
|
|
33
|
+
const match = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
|
|
34
|
+
if (!match || Number(match[1]) > 23 || Number(match[2]) > 59 || Number(match[3] ?? 0) > 59) {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
const hour24 = Number(match[1]);
|
|
38
|
+
return {
|
|
39
|
+
hour24,
|
|
40
|
+
hour12: hour24 % 12 || 12,
|
|
41
|
+
minute: Number(match[2]),
|
|
42
|
+
second: Number(match[3] ?? 0),
|
|
43
|
+
period: hour24 >= 12 ? "PM" : "AM"
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function formatWallClock(value) {
|
|
47
|
+
const time = parseWallClock(value);
|
|
48
|
+
return time ? `${time.hour12}${time.minute ? `:${String(time.minute).padStart(2, "0")}` : ""} ${time.period}` : value;
|
|
49
|
+
}
|
|
50
|
+
function formatOpeningHours(times) {
|
|
51
|
+
const names = [
|
|
52
|
+
"Sunday",
|
|
53
|
+
"Monday",
|
|
54
|
+
"Tuesday",
|
|
55
|
+
"Wednesday",
|
|
56
|
+
"Thursday",
|
|
57
|
+
"Friday",
|
|
58
|
+
"Saturday"
|
|
59
|
+
];
|
|
60
|
+
const groups = [];
|
|
61
|
+
for (let day = 0; day < 7; day++) {
|
|
62
|
+
const intervals = times.filter((time) => !time.disabled && time.day === day).sort((a, b) => a.opens.localeCompare(b.opens));
|
|
63
|
+
if (!intervals.length) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const hoursLabel = intervals.map(
|
|
67
|
+
(time) => `${formatWallClock(time.opens)} \u2013 ${formatWallClock(time.closes)}`
|
|
68
|
+
).join(", ");
|
|
69
|
+
const last = groups[groups.length - 1];
|
|
70
|
+
if (last && last.days[last.days.length - 1] === day - 1 && last.hoursLabel === hoursLabel) {
|
|
71
|
+
last.days.push(day);
|
|
72
|
+
last.dayLabel = `${names[last.days[0]]} \u2013 ${names[day]}`;
|
|
73
|
+
} else {
|
|
74
|
+
groups.push({ days: [day], dayLabel: names[day], hoursLabel });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return groups;
|
|
78
|
+
}
|
|
79
|
+
function dateOffset(date, days) {
|
|
80
|
+
return new Date(Date.parse(`${date}T00:00:00Z`) + days * DAY).toISOString().slice(0, 10);
|
|
81
|
+
}
|
|
82
|
+
function wallInstants(date, clock, timezone) {
|
|
83
|
+
const time = parseWallClock(clock);
|
|
84
|
+
if (!time) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
const wall = Date.parse(`${date}T00:00:00Z`) + ((time.hour24 * 60 + time.minute) * 60 + time.second) * 1e3;
|
|
88
|
+
const offsets = /* @__PURE__ */ new Set();
|
|
89
|
+
for (const shift of [-DAY, 0, DAY]) {
|
|
90
|
+
const sample = wall + shift;
|
|
91
|
+
const local = localParts(new Date(sample), timezone);
|
|
92
|
+
const asUTC = Date.parse(`${local.date}T00:00:00Z`) + ((local.hour * 60 + local.minute) * 60 + local.second) * 1e3;
|
|
93
|
+
offsets.add(asUTC - sample);
|
|
94
|
+
}
|
|
95
|
+
const matches = [];
|
|
96
|
+
for (const offset of offsets) {
|
|
97
|
+
const instant = wall - offset;
|
|
98
|
+
const local = localParts(new Date(instant), timezone);
|
|
99
|
+
if (local.date === date && local.hour === time.hour24 && local.minute === time.minute && local.second === time.second) {
|
|
100
|
+
matches.push(instant);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return matches.sort((a, b) => a - b);
|
|
104
|
+
}
|
|
105
|
+
function openingIntervals(branch, timezone, now, days = 8) {
|
|
106
|
+
const today = localParts(now, timezone).date;
|
|
107
|
+
const intervals = [];
|
|
108
|
+
for (let offset = -1; offset < days; offset++) {
|
|
109
|
+
const date = dateOffset(today, offset);
|
|
110
|
+
const day = (/* @__PURE__ */ new Date(`${date}T00:00:00Z`)).getUTCDay();
|
|
111
|
+
const special = branch.specialHours?.find((hours) => hours.date === date);
|
|
112
|
+
const times = special ? special.closed || !special.opens || !special.closes ? [] : [{ opens: special.opens, closes: special.closes }] : branch.openingTimes.filter(
|
|
113
|
+
(hours) => hours.day === day && !hours.disabled
|
|
114
|
+
);
|
|
115
|
+
for (const hours of times) {
|
|
116
|
+
const starts = wallInstants(date, hours.opens, timezone);
|
|
117
|
+
const overnight = hours.closes <= hours.opens;
|
|
118
|
+
const endDate = overnight ? dateOffset(date, 1) : date;
|
|
119
|
+
const ends = wallInstants(endDate, hours.closes, timezone);
|
|
120
|
+
const start = starts[0];
|
|
121
|
+
let end = ends[ends.length - 1];
|
|
122
|
+
if (overnight && branch.specialHours?.some(
|
|
123
|
+
(hours2) => hours2.date === endDate && hours2.closed
|
|
124
|
+
)) {
|
|
125
|
+
end = wallInstants(endDate, "00:00", timezone)[0];
|
|
126
|
+
}
|
|
127
|
+
if (start !== void 0 && end !== void 0 && start < end) {
|
|
128
|
+
intervals.push({ start, end });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const closedUntil = branch.closedUntil ? Date.parse(branch.closedUntil) : -Infinity;
|
|
133
|
+
const sorted = intervals.map(({ start, end }) => ({ start: Math.max(start, closedUntil), end })).filter(({ start, end }) => start < end).sort((a, b) => a.start - b.start);
|
|
134
|
+
const merged = [];
|
|
135
|
+
for (const interval of sorted) {
|
|
136
|
+
const last = merged[merged.length - 1];
|
|
137
|
+
if (last && interval.start <= last.end) {
|
|
138
|
+
last.end = Math.max(last.end, interval.end);
|
|
139
|
+
} else {
|
|
140
|
+
merged.push({ ...interval });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return merged;
|
|
144
|
+
}
|
|
145
|
+
function getOpenStatus(branch, timezone, now = /* @__PURE__ */ new Date()) {
|
|
146
|
+
const intervals = openingIntervals(branch, timezone, now);
|
|
147
|
+
const current = intervals.find(
|
|
148
|
+
({ start, end }) => start <= now.getTime() && end > now.getTime()
|
|
149
|
+
);
|
|
150
|
+
const next = current?.end ?? intervals.find(({ start }) => start > now.getTime())?.start;
|
|
151
|
+
if (next === void 0) {
|
|
152
|
+
return { isOpen: false, label: "Closed" };
|
|
153
|
+
}
|
|
154
|
+
const nextChange = new Date(next);
|
|
155
|
+
const local = localParts(nextChange, timezone);
|
|
156
|
+
const clock = formatWallClock(
|
|
157
|
+
`${String(local.hour).padStart(2, "0")}:${String(local.minute).padStart(2, "0")}`
|
|
158
|
+
).toLowerCase().replace(" ", "");
|
|
159
|
+
const today = localParts(now, timezone).date;
|
|
160
|
+
const suffix = local.date === today ? "" : local.date === dateOffset(today, 1) ? " tomorrow" : ` ${new Intl.DateTimeFormat("en", { timeZone: timezone, weekday: "long" }).format(nextChange)}`;
|
|
161
|
+
return {
|
|
162
|
+
isOpen: Boolean(current),
|
|
163
|
+
label: current ? `Open until ${clock}` : `Opens ${clock}${suffix}`,
|
|
164
|
+
nextChange
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
export {
|
|
168
|
+
dateOffset,
|
|
169
|
+
formatOpeningHours,
|
|
170
|
+
formatWallClock,
|
|
171
|
+
getOpenStatus,
|
|
172
|
+
localParts,
|
|
173
|
+
openingIntervals,
|
|
174
|
+
parseWallClock,
|
|
175
|
+
wallInstants
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=hours.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/status/hours.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Branch, OpeningTime } from \"@zappie/sdk\";\nconst DAY = 86400000;\nconst formatters = new Map<string, Intl.DateTimeFormat>();\nfunction formatter(timezone: string) {\n let value = formatters.get(timezone);\n if (!value) {\n value = new Intl.DateTimeFormat(\"en-CA\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hourCycle: \"h23\",\n });\n formatters.set(timezone, value);\n }\n return value;\n}\nexport function localParts(date: Date, timezone: string) {\n const parts = Object.fromEntries(\n formatter(timezone)\n .formatToParts(date)\n .map(({ type, value }) => [type, value]),\n );\n return {\n date: `${parts.year}-${parts.month}-${parts.day}`,\n hour: Number(parts.hour),\n minute: Number(parts.minute),\n second: Number(parts.second),\n };\n}\n/**\n * Parse a zero-padded HH:mm or HH:mm:ss wall-clock value without applying a time zone.\n *\n * @returns Clock parts with 12-hour and 24-hour values, or undefined for an invalid time.\n * @example\n * ```ts\n * parseWallClock(\"14:30\"); // { hour24: 14, hour12: 2, minute: 30, second: 0, period: \"PM\" }\n * ```\n */\nexport function parseWallClock(value: string) {\n const match = /^(\\d{2}):(\\d{2})(?::(\\d{2}))?$/.exec(value);\n if (\n !match ||\n Number(match[1]) > 23 ||\n Number(match[2]) > 59 ||\n Number(match[3] ?? 0) > 59\n ) {\n return undefined;\n }\n const hour24 = Number(match[1]);\n return {\n hour24,\n hour12: hour24 % 12 || 12,\n minute: Number(match[2]),\n second: Number(match[3] ?? 0),\n period: hour24 >= 12 ? \"PM\" : \"AM\",\n };\n}\n/**\n * Format a valid wall-clock time as a 12-hour label with AM or PM.\n * Omits zero minutes and all seconds. Returns invalid input unchanged.\n *\n * @example\n * ```ts\n * formatWallClock(\"14:00\"); // \"2 PM\"\n * formatWallClock(\"09:30:15\"); // \"9:30 AM\"\n * ```\n */\nexport function formatWallClock(value: string) {\n const time = parseWallClock(value);\n return time\n ? `${time.hour12}${time.minute ? `:${String(time.minute).padStart(2, \"0\")}` : \"\"} ${time.period}`\n : value;\n}\n/**\n * Group consecutive days with identical opening intervals into display labels.\n * Skips disabled intervals and days with no enabled hours, and orders the groups from Sunday to Saturday.\n */\nexport function formatOpeningHours(times: OpeningTime[]) {\n const names = [\n \"Sunday\",\n \"Monday\",\n \"Tuesday\",\n \"Wednesday\",\n \"Thursday\",\n \"Friday\",\n \"Saturday\",\n ];\n const groups: { days: number[]; dayLabel: string; hoursLabel: string }[] = [];\n for (let day = 0; day < 7; day++) {\n const intervals = times\n .filter((time) => !time.disabled && time.day === day)\n .sort((a, b) => a.opens.localeCompare(b.opens));\n if (!intervals.length) {\n continue;\n }\n const hoursLabel = intervals\n .map(\n (time) =>\n `${formatWallClock(time.opens)} \u2013 ${formatWallClock(time.closes)}`,\n )\n .join(\", \");\n const last = groups[groups.length - 1];\n if (\n last &&\n last.days[last.days.length - 1] === day - 1 &&\n last.hoursLabel === hoursLabel\n ) {\n last.days.push(day);\n last.dayLabel = `${names[last.days[0]!]} \u2013 ${names[day]}`;\n } else {\n groups.push({ days: [day], dayLabel: names[day]!, hoursLabel });\n }\n }\n return groups;\n}\nexport function dateOffset(date: string, days: number) {\n return new Date(Date.parse(`${date}T00:00:00Z`) + days * DAY)\n .toISOString()\n .slice(0, 10);\n}\n/** Return all matching instants (two at a DST fold, none in a DST gap). */\nexport function wallInstants(date: string, clock: string, timezone: string) {\n const time = parseWallClock(clock);\n if (!time) {\n return [];\n }\n const wall =\n Date.parse(`${date}T00:00:00Z`) +\n ((time.hour24 * 60 + time.minute) * 60 + time.second) * 1000;\n const offsets = new Set<number>();\n for (const shift of [-DAY, 0, DAY]) {\n const sample = wall + shift;\n const local = localParts(new Date(sample), timezone);\n const asUTC =\n Date.parse(`${local.date}T00:00:00Z`) +\n ((local.hour * 60 + local.minute) * 60 + local.second) * 1000;\n offsets.add(asUTC - sample);\n }\n const matches = [];\n for (const offset of offsets) {\n const instant = wall - offset;\n const local = localParts(new Date(instant), timezone);\n if (\n local.date === date &&\n local.hour === time.hour24 &&\n local.minute === time.minute &&\n local.second === time.second\n ) {\n matches.push(instant);\n }\n }\n return matches.sort((a, b) => a - b);\n}\nexport type OpeningInterval = { start: number; end: number };\nexport function openingIntervals(\n branch: Branch,\n timezone: string,\n now: Date,\n days = 8,\n): OpeningInterval[] {\n const today = localParts(now, timezone).date;\n const intervals: OpeningInterval[] = [];\n for (let offset = -1; offset < days; offset++) {\n const date = dateOffset(today, offset);\n const day = new Date(`${date}T00:00:00Z`).getUTCDay();\n const special = branch.specialHours?.find((hours) => hours.date === date);\n const times = special\n ? special.closed || !special.opens || !special.closes\n ? []\n : [{ opens: special.opens, closes: special.closes }]\n : branch.openingTimes.filter(\n (hours) => hours.day === day && !hours.disabled,\n );\n for (const hours of times) {\n const starts = wallInstants(date, hours.opens, timezone);\n const overnight = hours.closes <= hours.opens;\n const endDate = overnight ? dateOffset(date, 1) : date;\n const ends = wallInstants(endDate, hours.closes, timezone);\n const start = starts[0];\n let end = ends[ends.length - 1];\n if (\n overnight &&\n branch.specialHours?.some(\n (hours) => hours.date === endDate && hours.closed,\n )\n ) {\n end = wallInstants(endDate, \"00:00\", timezone)[0];\n }\n if (start !== undefined && end !== undefined && start < end) {\n intervals.push({ start, end });\n }\n }\n }\n const closedUntil = branch.closedUntil\n ? Date.parse(branch.closedUntil)\n : -Infinity;\n const sorted = intervals\n .map(({ start, end }) => ({ start: Math.max(start, closedUntil), end }))\n .filter(({ start, end }) => start < end)\n .sort((a, b) => a.start - b.start);\n const merged: OpeningInterval[] = [];\n for (const interval of sorted) {\n const last = merged[merged.length - 1];\n if (last && interval.start <= last.end) {\n last.end = Math.max(last.end, interval.end);\n } else {\n merged.push({ ...interval });\n }\n }\n return merged;\n}\nexport type OpenStatusState = {\n isOpen: boolean;\n label: string;\n nextChange?: Date;\n};\n/**\n * Calculate whether a branch is open and provide its next opening or closing label.\n * Accounts for weekly hours, special hours, overnight intervals and closedUntil.\n *\n * @param branch - The branch whose opening rules apply.\n * @param timezone - The time zone used to interpret and display its hours.\n * @param now - The instant to check. Defaults to the current time.\n * @returns Open state, a display label and the next boundary found in the opening-hours window.\n */\nexport function getOpenStatus(\n branch: Branch,\n timezone: string,\n now = new Date(),\n): OpenStatusState {\n const intervals = openingIntervals(branch, timezone, now);\n const current = intervals.find(\n ({ start, end }) => start <= now.getTime() && end > now.getTime(),\n );\n const next =\n current?.end ?? intervals.find(({ start }) => start > now.getTime())?.start;\n if (next === undefined) {\n return { isOpen: false, label: \"Closed\" };\n }\n const nextChange = new Date(next);\n const local = localParts(nextChange, timezone);\n const clock = formatWallClock(\n `${String(local.hour).padStart(2, \"0\")}:${String(local.minute).padStart(2, \"0\")}`,\n )\n .toLowerCase()\n .replace(\" \", \"\");\n const today = localParts(now, timezone).date;\n const suffix =\n local.date === today\n ? \"\"\n : local.date === dateOffset(today, 1)\n ? \" tomorrow\"\n : ` ${new Intl.DateTimeFormat(\"en\", { timeZone: timezone, weekday: \"long\" }).format(nextChange)}`;\n return {\n isOpen: Boolean(current),\n label: current ? `Open until ${clock}` : `Opens ${clock}${suffix}`,\n nextChange,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["hours"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/status/open-status.tsx
|
|
4
|
+
import { useStore } from "@zappie/sdk/react";
|
|
5
|
+
import { forwardRef, useEffect, useState } from "react";
|
|
6
|
+
import { part } from "../internal/slot.js";
|
|
7
|
+
import { getOpenStatus } from "./hours.js";
|
|
8
|
+
function useOpenStatus(branch, timezone) {
|
|
9
|
+
const [now, setNow] = useState(() => /* @__PURE__ */ new Date());
|
|
10
|
+
const state = getOpenStatus(branch, timezone, now);
|
|
11
|
+
const boundary = state.nextChange?.getTime();
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const delay = boundary !== void 0 ? boundary - Date.now() : 864e5;
|
|
14
|
+
const timeout = setTimeout(
|
|
15
|
+
() => setNow(/* @__PURE__ */ new Date()),
|
|
16
|
+
Math.max(1, Math.min(2147483647, delay))
|
|
17
|
+
);
|
|
18
|
+
const refresh = () => setNow(/* @__PURE__ */ new Date());
|
|
19
|
+
window.addEventListener("focus", refresh);
|
|
20
|
+
return () => {
|
|
21
|
+
clearTimeout(timeout);
|
|
22
|
+
window.removeEventListener("focus", refresh);
|
|
23
|
+
};
|
|
24
|
+
}, [branch, timezone, boundary]);
|
|
25
|
+
return state;
|
|
26
|
+
}
|
|
27
|
+
var OpenStatus = forwardRef(function OpenStatus2({ branch, timezone, ...props }, ref) {
|
|
28
|
+
const store = useStore();
|
|
29
|
+
const state = useOpenStatus(
|
|
30
|
+
branch,
|
|
31
|
+
timezone ?? branch.timezone ?? store.data?.timezone ?? "UTC"
|
|
32
|
+
);
|
|
33
|
+
return part(
|
|
34
|
+
"span",
|
|
35
|
+
props,
|
|
36
|
+
state,
|
|
37
|
+
{ "data-state": state.isOpen ? "open" : "closed" },
|
|
38
|
+
ref
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
export {
|
|
42
|
+
OpenStatus,
|
|
43
|
+
useOpenStatus
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=open-status.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/status/open-status.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\";\nimport type { Branch } from \"@zappie/sdk\";\nimport { useStore } from \"@zappie/sdk/react\";\nimport { forwardRef, useEffect, useState } from \"react\";\nimport { part, type PartProps } from \"../internal/slot\";\nimport { getOpenStatus, type OpenStatusState } from \"./hours\";\nexport function useOpenStatus(branch: Branch, timezone: string) {\n const [now, setNow] = useState(() => new Date());\n const state = getOpenStatus(branch, timezone, now);\n const boundary = state.nextChange?.getTime();\n useEffect(() => {\n // With no weekly boundary, retry at the next day for future calendar exceptions.\n const delay = boundary !== undefined ? boundary - Date.now() : 86400000;\n const timeout = setTimeout(\n () => setNow(new Date()),\n Math.max(1, Math.min(2147483647, delay)),\n );\n const refresh = () => setNow(new Date());\n window.addEventListener(\"focus\", refresh);\n return () => {\n clearTimeout(timeout);\n window.removeEventListener(\"focus\", refresh);\n };\n }, [branch, timezone, boundary]);\n return state;\n}\n/**\n * Expose a branch opening label and open/closed state in a span or your render function.\n * Uses your time zone override, then the branch, store or UTC, and refreshes at boundaries and window focus.\n */\nexport const OpenStatus = forwardRef<\n HTMLElement,\n PartProps<OpenStatusState> & { branch: Branch; timezone?: string }\n>(function OpenStatus({ branch, timezone, ...props }, ref) {\n const store = useStore();\n const state = useOpenStatus(\n branch,\n timezone ?? branch.timezone ?? store.data?.timezone ?? \"UTC\",\n );\n return part(\n \"span\",\n props,\n state,\n { \"data-state\": state.isOpen ? \"open\" : \"closed\" },\n ref,\n );\n});\n"],
|
|
5
|
+
"mappings": ";;;AAEA,SAAS,gBAAgB;AACzB,SAAS,YAAY,WAAW,gBAAgB;AAChD,SAAS,YAA4B;AACrC,SAAS,qBAA2C;AAC7C,SAAS,cAAc,QAAgB,UAAkB;AAC9D,QAAM,CAAC,KAAK,MAAM,IAAI,SAAS,MAAM,oBAAI,KAAK,CAAC;AAC/C,QAAM,QAAQ,cAAc,QAAQ,UAAU,GAAG;AACjD,QAAM,WAAW,MAAM,YAAY,QAAQ;AAC3C,YAAU,MAAM;AAEd,UAAM,QAAQ,aAAa,SAAY,WAAW,KAAK,IAAI,IAAI;AAC/D,UAAM,UAAU;AAAA,MACd,MAAM,OAAO,oBAAI,KAAK,CAAC;AAAA,MACvB,KAAK,IAAI,GAAG,KAAK,IAAI,YAAY,KAAK,CAAC;AAAA,IACzC;AACA,UAAM,UAAU,MAAM,OAAO,oBAAI,KAAK,CAAC;AACvC,WAAO,iBAAiB,SAAS,OAAO;AACxC,WAAO,MAAM;AACX,mBAAa,OAAO;AACpB,aAAO,oBAAoB,SAAS,OAAO;AAAA,IAC7C;AAAA,EACF,GAAG,CAAC,QAAQ,UAAU,QAAQ,CAAC;AAC/B,SAAO;AACT;AAKO,IAAM,aAAa,WAGxB,SAASA,YAAW,EAAE,QAAQ,UAAU,GAAG,MAAM,GAAG,KAAK;AACzD,QAAM,QAAQ,SAAS;AACvB,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,YAAY,OAAO,YAAY,MAAM,MAAM,YAAY;AAAA,EACzD;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,cAAc,MAAM,SAAS,SAAS,SAAS;AAAA,IACjD;AAAA,EACF;AACF,CAAC;",
|
|
6
|
+
"names": ["OpenStatus"]
|
|
7
|
+
}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/version.ts"],
|
|
4
|
+
"sourcesContent": ["// Builds replace __ZAPPIE_FOUNDATIONS_VERSION__ with the version in package.json. Code run straight\n// from source, such as a plain Node script, has no build step and gets the\n// development label instead.\ndeclare const __ZAPPIE_FOUNDATIONS_VERSION__: string | undefined;\n\n/** The installed @zappie/foundations version. */\nexport const PACKAGE_VERSION: string =\n typeof __ZAPPIE_FOUNDATIONS_VERSION__ === \"string\"\n ? __ZAPPIE_FOUNDATIONS_VERSION__\n : \"0.0.0-development\";\n"],
|
|
5
|
+
"mappings": ";AAMO,IAAM,kBACX,OACI,UACA;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zappie/foundations",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "UI-less React primitives for Zappie storefronts: behaviour, state and accessibility without styles.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@base-ui/react": "^1.8.0",
|
|
35
|
-
"@zappie/sdk": "^0.1.
|
|
35
|
+
"@zappie/sdk": "^0.1.2"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"react": "^18.2.0 || ^19.0.0",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"url": "https://github.com/zappie/storefront/issues"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
|
-
"build": "tsup",
|
|
55
|
+
"build": "tsup && node ../../scripts/release-packages/build-modules.mjs",
|
|
56
56
|
"size": "node scripts/check-size.mjs"
|
|
57
57
|
}
|
|
58
58
|
}
|