@zappie/theme-base 0.1.0 → 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.
Files changed (129) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +26 -53
  3. package/dist/address.js +99 -2
  4. package/dist/address.js.map +7 -1
  5. package/dist/cart/address-sheet.js +143 -0
  6. package/dist/cart/address-sheet.js.map +7 -0
  7. package/dist/cart/cart-issues.js +72 -0
  8. package/dist/cart/cart-issues.js.map +7 -0
  9. package/dist/cart/cart-line-groups.js +34 -0
  10. package/dist/cart/cart-line-groups.js.map +7 -0
  11. package/dist/{chunk-FB5DRCU7.js → cart/cart-line.js} +9 -70
  12. package/dist/cart/cart-line.js.map +7 -0
  13. package/dist/cart/cart-lines.js +27 -0
  14. package/dist/cart/cart-lines.js.map +7 -0
  15. package/dist/cart/cart-schedule.js +24 -0
  16. package/dist/cart/cart-schedule.js.map +7 -0
  17. package/dist/cart/cart-sheet.js +35 -0
  18. package/dist/cart/cart-sheet.js.map +7 -0
  19. package/dist/cart/checkout-cta.js +81 -0
  20. package/dist/cart/checkout-cta.js.map +7 -0
  21. package/dist/cart/checkout-state.js +74 -0
  22. package/dist/cart/checkout-state.js.map +7 -0
  23. package/dist/cart/clear-cart.js +44 -0
  24. package/dist/cart/clear-cart.js.map +7 -0
  25. package/dist/cart/customer-details.js +70 -0
  26. package/dist/cart/customer-details.js.map +7 -0
  27. package/dist/cart/delivery-distance.js +22 -0
  28. package/dist/cart/delivery-distance.js.map +7 -0
  29. package/dist/cart/empty-cart.js +14 -0
  30. package/dist/cart/empty-cart.js.map +7 -0
  31. package/dist/cart/fulfilment-panel.js +83 -0
  32. package/dist/cart/fulfilment-panel.js.map +7 -0
  33. package/dist/cart/line-editor.js +35 -0
  34. package/dist/cart/line-editor.js.map +7 -0
  35. package/dist/cart/line-options.js +96 -0
  36. package/dist/cart/line-options.js.map +7 -0
  37. package/dist/cart/loading.js +17 -0
  38. package/dist/cart/loading.js.map +7 -0
  39. package/dist/cart/promo-sheet.js +103 -0
  40. package/dist/cart/promo-sheet.js.map +7 -0
  41. package/dist/cart/region-picker.js +40 -0
  42. package/dist/cart/region-picker.js.map +7 -0
  43. package/dist/cart/rewards-sheet.js +86 -0
  44. package/dist/cart/rewards-sheet.js.map +7 -0
  45. package/dist/cart/schedule-sheet.js +87 -0
  46. package/dist/cart/schedule-sheet.js.map +7 -0
  47. package/dist/{chunk-JFEI2OUZ.js → cart/theme-class.js} +7 -5
  48. package/dist/cart/theme-class.js.map +7 -0
  49. package/dist/cart/totals.js +81 -0
  50. package/dist/cart/totals.js.map +7 -0
  51. package/dist/cart/use-address-selection.js +130 -0
  52. package/dist/cart/use-address-selection.js.map +7 -0
  53. package/dist/cart.js +21 -1069
  54. package/dist/cart.js.map +7 -1
  55. package/dist/catalogue/catalogue-cart.js +25 -0
  56. package/dist/catalogue/catalogue-cart.js.map +7 -0
  57. package/dist/catalogue/catalogue-header.js +88 -0
  58. package/dist/catalogue/catalogue-header.js.map +7 -0
  59. package/dist/catalogue/catalogue-pages.js +68 -0
  60. package/dist/catalogue/catalogue-pages.js.map +7 -0
  61. package/dist/catalogue/catalogue-search.js +89 -0
  62. package/dist/catalogue/catalogue-search.js.map +7 -0
  63. package/dist/catalogue/category-navigation.js +98 -0
  64. package/dist/catalogue/category-navigation.js.map +7 -0
  65. package/dist/catalogue/category-section.js +113 -0
  66. package/dist/catalogue/category-section.js.map +7 -0
  67. package/dist/catalogue/info-banners.js +148 -0
  68. package/dist/catalogue/info-banners.js.map +7 -0
  69. package/dist/catalogue/item-tile.js +55 -0
  70. package/dist/catalogue/item-tile.js.map +7 -0
  71. package/dist/catalogue/loader.js +136 -0
  72. package/dist/catalogue/loader.js.map +7 -0
  73. package/dist/{chunk-USSLDBDY.js → catalogue/presentation.js} +12 -9
  74. package/dist/catalogue/presentation.js.map +7 -0
  75. package/dist/catalogue/saved-catalogue-sync.js +115 -0
  76. package/dist/catalogue/saved-catalogue-sync.js.map +7 -0
  77. package/dist/catalogue/types.js +1 -0
  78. package/dist/catalogue/types.js.map +7 -0
  79. package/dist/catalogue/use-catalogue-data.js +88 -0
  80. package/dist/catalogue/use-catalogue-data.js.map +7 -0
  81. package/dist/catalogue/use-category-scroll.js +115 -0
  82. package/dist/catalogue/use-category-scroll.js.map +7 -0
  83. package/dist/catalogue/use-item-opener.js +46 -0
  84. package/dist/catalogue/use-item-opener.js.map +7 -0
  85. package/dist/catalogue/use-search-source.js +132 -0
  86. package/dist/catalogue/use-search-source.js.map +7 -0
  87. package/dist/catalogue.js +26 -1216
  88. package/dist/catalogue.js.map +7 -1
  89. package/dist/checkout.js +149 -2
  90. package/dist/checkout.js.map +7 -1
  91. package/dist/host-bridge.js +22 -2
  92. package/dist/host-bridge.js.map +7 -1
  93. package/dist/index.js +3 -3
  94. package/dist/index.js.map +7 -1
  95. package/dist/item/configurator-commit.js +15 -0
  96. package/dist/item/configurator-commit.js.map +7 -0
  97. package/dist/item/configurator-draft.js +63 -0
  98. package/dist/item/configurator-draft.js.map +7 -0
  99. package/dist/item/configurator.js +135 -0
  100. package/dist/item/configurator.js.map +7 -0
  101. package/dist/item/note.js +65 -0
  102. package/dist/item/note.js.map +7 -0
  103. package/dist/item/variant-status.js +23 -0
  104. package/dist/item/variant-status.js.map +7 -0
  105. package/dist/item-frame.js +60 -3
  106. package/dist/item-frame.js.map +7 -1
  107. package/dist/item.js +11 -11
  108. package/dist/item.js.map +7 -1
  109. package/dist/navigation.js +39 -2
  110. package/dist/navigation.js.map +7 -1
  111. package/dist/url-navigation-state.js +61 -0
  112. package/dist/url-navigation-state.js.map +7 -0
  113. package/dist/{chunk-ILOCSCZL.js → url-navigation.js} +12 -89
  114. package/dist/url-navigation.js.map +7 -0
  115. package/package.json +8 -7
  116. package/dist/chunk-2MV6KU3R.js +0 -352
  117. package/dist/chunk-2MV6KU3R.js.map +0 -1
  118. package/dist/chunk-FB5DRCU7.js.map +0 -1
  119. package/dist/chunk-IG63ONID.js +0 -139
  120. package/dist/chunk-IG63ONID.js.map +0 -1
  121. package/dist/chunk-ILOCSCZL.js.map +0 -1
  122. package/dist/chunk-JFEI2OUZ.js.map +0 -1
  123. package/dist/chunk-NTJONY3N.js +0 -95
  124. package/dist/chunk-NTJONY3N.js.map +0 -1
  125. package/dist/chunk-QWST2TA2.js +0 -22
  126. package/dist/chunk-QWST2TA2.js.map +0 -1
  127. package/dist/chunk-USSLDBDY.js.map +0 -1
  128. package/dist/chunk-VIAJ27YC.js +0 -59
  129. package/dist/chunk-VIAJ27YC.js.map +0 -1
@@ -1,9 +1,11 @@
1
- import { useBaseHostBridge } from './chunk-QWST2TA2.js';
2
- import { createSanitizeRichText } from '@zappie/foundations/rich-text';
3
- import { parseRichText } from '@zappie/foundations/rich-text/browser';
4
- import { useState, useEffect } from 'react';
5
- import { jsx } from 'react/jsx-runtime';
1
+ "use client";
6
2
 
3
+ // src/catalogue/presentation.tsx
4
+ import { createSanitizeRichText } from "@zappie/foundations/rich-text";
5
+ import { parseRichText } from "@zappie/foundations/rich-text/browser";
6
+ import { useEffect, useState } from "react";
7
+ import { useBaseHostBridge } from "../host-bridge.js";
8
+ import { jsx } from "react/jsx-runtime";
7
9
  var sanitize = createSanitizeRichText(parseRichText);
8
10
  function Description({ value }) {
9
11
  const [html, setHtml] = useState("");
@@ -42,7 +44,8 @@ function ItemImage({
42
44
  )
43
45
  ) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "Image unavailable" }) });
44
46
  }
45
-
46
- export { Description, ItemImage };
47
- //# sourceMappingURL=chunk-USSLDBDY.js.map
48
- //# sourceMappingURL=chunk-USSLDBDY.js.map
47
+ export {
48
+ Description,
49
+ ItemImage
50
+ };
51
+ //# sourceMappingURL=presentation.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/catalogue/presentation.tsx"],
4
+ "sourcesContent": ["\"use client\";\nimport { createSanitizeRichText } from \"@zappie/foundations/rich-text\";\nimport { parseRichText } from \"@zappie/foundations/rich-text/browser\";\nimport { useEffect, useState } from \"react\";\nimport { useBaseHostBridge } from \"../host-bridge\";\nconst sanitize = createSanitizeRichText(parseRichText);\nexport function Description({ value }: { value: string }) {\n const [html, setHtml] = useState(\"\");\n useEffect(() => setHtml(sanitize(value)), [value]);\n return (\n <div\n className=\"z-base-description\"\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n}\nexport function ItemImage({\n src,\n sizes,\n priority = false,\n}: {\n src?: string | undefined;\n /** Rendered width hint for responsive host images. */\n sizes: string;\n /** Tiles likely to be the largest paint load first instead of lazily. */\n priority?: boolean;\n}) {\n const bridge = useBaseHostBridge();\n const [failed, setFailed] = useState(false);\n useEffect(() => setFailed(false), [src]);\n const image = src ? (bridge.imageProps?.(src, sizes) ?? { src }) : undefined;\n return (\n <div className=\"z-base-item-image\">\n {image && !failed ? (\n // Public themes remain framework independent; the host may optimise\n // image URLs through the bridge.\n // eslint-disable-next-line @next/next/no-img-element\n <img\n {...image}\n loading={priority ? \"eager\" : \"lazy\"}\n fetchPriority={priority ? \"high\" : \"auto\"}\n decoding=\"async\"\n alt=\"\"\n onError={() => setFailed(true)}\n />\n ) : (\n <span aria-hidden=\"true\">Image unavailable</span>\n )}\n </div>\n );\n}\n"],
5
+ "mappings": ";;;AACA,SAAS,8BAA8B;AACvC,SAAS,qBAAqB;AAC9B,SAAS,WAAW,gBAAgB;AACpC,SAAS,yBAAyB;AAM9B;AALJ,IAAM,WAAW,uBAAuB,aAAa;AAC9C,SAAS,YAAY,EAAE,MAAM,GAAsB;AACxD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,EAAE;AACnC,YAAU,MAAM,QAAQ,SAAS,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;AACjD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,yBAAyB,EAAE,QAAQ,KAAK;AAAA;AAAA,EAC1C;AAEJ;AACO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAMG;AACD,QAAM,SAAS,kBAAkB;AACjC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,YAAU,MAAM,UAAU,KAAK,GAAG,CAAC,GAAG,CAAC;AACvC,QAAM,QAAQ,MAAO,OAAO,aAAa,KAAK,KAAK,KAAK,EAAE,IAAI,IAAK;AACnE,SACE,oBAAC,SAAI,WAAU,qBACZ,mBAAS,CAAC;AAAA;AAAA;AAAA;AAAA,IAIT;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ,SAAS,WAAW,UAAU;AAAA,QAC9B,eAAe,WAAW,SAAS;AAAA,QACnC,UAAS;AAAA,QACT,KAAI;AAAA,QACJ,SAAS,MAAM,UAAU,IAAI;AAAA;AAAA,IAC/B;AAAA,MAEA,oBAAC,UAAK,eAAY,QAAO,+BAAiB,GAE9C;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,115 @@
1
+ // src/catalogue/saved-catalogue-sync.ts
2
+ import {
3
+ pageKey,
4
+ pageState,
5
+ readSavedPages,
6
+ replaceRefreshedCategories,
7
+ replaceSavedPages
8
+ } from "./catalogue-pages.js";
9
+ async function loadResources(client, scope) {
10
+ const [persistence, selected] = await Promise.all([
11
+ import("@zappie/sdk/persist/catalogue"),
12
+ import("@zappie/sdk/transport/selected-menu")
13
+ ]);
14
+ return {
15
+ saved: persistence.createSavedCatalogue(client, scope),
16
+ selected: selected.createSelectedMenuReader(client)
17
+ };
18
+ }
19
+ function createSavedCatalogueSync({
20
+ client,
21
+ state,
22
+ scope,
23
+ revision,
24
+ publish
25
+ }) {
26
+ let resources;
27
+ function snapshot() {
28
+ return {
29
+ scope,
30
+ menu: state.menu,
31
+ coverage: { menuIds: [scope.menuId], complete: state.complete },
32
+ pages: [...state.savedPages],
33
+ etag: state.menu.version,
34
+ savedAt: Date.now(),
35
+ ...revision ? { revision } : {}
36
+ };
37
+ }
38
+ async function save() {
39
+ if (await resources.saved.publish(snapshot()).catch(() => true)) {
40
+ state.abort.signal.throwIfAborted();
41
+ publish?.(state.menu);
42
+ }
43
+ }
44
+ async function refresh() {
45
+ if (state.complete) {
46
+ const response = await resources.selected.read(scope, {
47
+ signal: state.abort.signal
48
+ });
49
+ state.abort.signal.throwIfAborted();
50
+ if ("notModified" in response) {
51
+ return;
52
+ }
53
+ state.menu = response.menu;
54
+ await save();
55
+ return;
56
+ }
57
+ const refreshed = await readSavedPages(
58
+ client,
59
+ scope.menuId,
60
+ scope.branchId,
61
+ state.savedPages,
62
+ state.abort.signal
63
+ );
64
+ state.abort.signal.throwIfAborted();
65
+ state.menu = replaceRefreshedCategories(state.menu, refreshed);
66
+ for (const { categoryId, page } of refreshed) {
67
+ state.pages.set(
68
+ pageKey(scope.menuId, categoryId),
69
+ pageState(page.nextCursor)
70
+ );
71
+ }
72
+ replaceSavedPages(state.savedPages, refreshed);
73
+ await save();
74
+ }
75
+ return {
76
+ get resources() {
77
+ return resources;
78
+ },
79
+ /** Shows the current menu now; saving runs behind it and never fails a read. */
80
+ emit() {
81
+ state.abort.signal.throwIfAborted();
82
+ publish?.(state.menu);
83
+ void resources?.saved.publish(snapshot()).catch(() => {
84
+ });
85
+ },
86
+ async restore() {
87
+ resources = await loadResources(client, scope);
88
+ const value = await resources.saved.restore();
89
+ state.abort.signal.throwIfAborted();
90
+ if (!value) {
91
+ return false;
92
+ }
93
+ state.menu = value.menu;
94
+ state.complete = value.coverage.complete;
95
+ state.mode = state.complete ? "eager" : "progressive";
96
+ state.savedPages.push(...value.pages);
97
+ for (const saved of value.pages) {
98
+ state.pages.set(
99
+ pageKey(scope.menuId, saved.categoryId),
100
+ pageState(saved.page.nextCursor)
101
+ );
102
+ }
103
+ publish?.(state.menu);
104
+ if (!revision || value.revision !== revision) {
105
+ void refresh().catch(() => {
106
+ });
107
+ }
108
+ return true;
109
+ }
110
+ };
111
+ }
112
+ export {
113
+ createSavedCatalogueSync
114
+ };
115
+ //# sourceMappingURL=saved-catalogue-sync.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/catalogue/saved-catalogue-sync.ts"],
4
+ "sourcesContent": ["import type { Menu, Zappie } from \"@zappie/sdk\";\nimport type { SavedCatalogue } from \"@zappie/sdk/persist/catalogue\";\nimport {\n pageKey,\n pageState,\n readSavedPages,\n replaceRefreshedCategories,\n replaceSavedPages,\n type PageState,\n type SavedPage,\n} from \"./catalogue-pages\";\n\n/** The loader's mutable read state, shared with the saved-catalogue sync. */\nexport type LoaderState = {\n menu: Menu;\n mode: \"eager\" | \"progressive\" | null;\n complete: boolean;\n // Replaced on resume: an effect can be cleaned up and run again (React\n // StrictMode, <Activity>) without the loader being recreated.\n abort: AbortController;\n readonly pages: Map<string, PageState>;\n readonly savedPages: SavedPage[];\n};\n\ntype Scope = SavedCatalogue[\"scope\"];\n\nasync function loadResources(client: Zappie, scope: Scope) {\n const [persistence, selected] = await Promise.all([\n import(\"@zappie/sdk/persist/catalogue\"),\n import(\"@zappie/sdk/transport/selected-menu\"),\n ]);\n return {\n saved: persistence.createSavedCatalogue(client, scope),\n selected: selected.createSelectedMenuReader(client),\n };\n}\n\nexport type CatalogueResources = Awaited<ReturnType<typeof loadResources>>;\n\nexport function createSavedCatalogueSync({\n client,\n state,\n scope,\n revision,\n publish,\n}: {\n client: Zappie;\n state: LoaderState;\n scope: Scope;\n /** Host catalogue revision; a saved copy at this revision is not refreshed. */\n revision?: string | undefined;\n publish?: ((menu: Menu) => void) | undefined;\n}) {\n let resources: CatalogueResources | undefined;\n function snapshot(): SavedCatalogue {\n return {\n scope,\n menu: state.menu,\n coverage: { menuIds: [scope.menuId], complete: state.complete },\n pages: [...state.savedPages],\n etag: state.menu.version,\n savedAt: Date.now(),\n ...(revision ? { revision } : {}),\n };\n }\n async function save() {\n if (await resources!.saved.publish(snapshot()).catch(() => true)) {\n state.abort.signal.throwIfAborted();\n publish?.(state.menu);\n }\n }\n async function refresh() {\n if (state.complete) {\n const response = await resources!.selected.read(scope, {\n signal: state.abort.signal,\n });\n state.abort.signal.throwIfAborted();\n if (\"notModified\" in response) {\n return;\n }\n state.menu = response.menu;\n await save();\n return;\n }\n const refreshed = await readSavedPages(\n client,\n scope.menuId,\n scope.branchId,\n state.savedPages,\n state.abort.signal,\n );\n state.abort.signal.throwIfAborted();\n state.menu = replaceRefreshedCategories(state.menu, refreshed);\n for (const { categoryId, page } of refreshed) {\n state.pages.set(\n pageKey(scope.menuId, categoryId),\n pageState(page.nextCursor),\n );\n }\n replaceSavedPages(state.savedPages, refreshed);\n await save();\n }\n return {\n get resources() {\n return resources;\n },\n /** Shows the current menu now; saving runs behind it and never fails a read. */\n emit() {\n state.abort.signal.throwIfAborted();\n publish?.(state.menu);\n void resources?.saved.publish(snapshot()).catch(() => {});\n },\n async restore() {\n resources = await loadResources(client, scope);\n const value = await resources.saved.restore();\n state.abort.signal.throwIfAborted();\n if (!value) {\n return false;\n }\n state.menu = value.menu;\n state.complete = value.coverage.complete;\n state.mode = state.complete ? \"eager\" : \"progressive\";\n state.savedPages.push(...value.pages);\n for (const saved of value.pages) {\n state.pages.set(\n pageKey(scope.menuId, saved.categoryId),\n pageState(saved.page.nextCursor),\n );\n }\n publish?.(state.menu);\n if (!revision || value.revision !== revision) {\n void refresh().catch(() => {});\n }\n return true;\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,88 @@
1
+ // src/catalogue/use-catalogue-data.ts
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { createCatalogueLoader } from "./loader.js";
4
+ function useCatalogueData({
5
+ client,
6
+ initialMenu,
7
+ selectedMenuId,
8
+ fulfilmentMode,
9
+ resolveStrategy,
10
+ revision
11
+ }) {
12
+ const [catalogue, setCatalogue] = useState(initialMenu);
13
+ const show = useCallback(
14
+ (value) => setCatalogue({
15
+ ...value,
16
+ ...initialMenu.menus ? { menus: initialMenu.menus } : {},
17
+ categories: initialMenu.categories.map(
18
+ (category) => value.categories.find((loaded) => loaded.id === category.id) ?? category
19
+ )
20
+ }),
21
+ [initialMenu]
22
+ );
23
+ const loader = useMemo(() => {
24
+ const selected = initialMenu.menus?.find(
25
+ (source2) => source2.id === selectedMenuId
26
+ );
27
+ const scoped = {
28
+ ...initialMenu,
29
+ ...selected ? { menus: [selected] } : {},
30
+ categories: initialMenu.categories.filter(
31
+ (category) => !selected || selected.categoryIds.includes(category.id)
32
+ )
33
+ };
34
+ return createCatalogueLoader(
35
+ client,
36
+ scoped,
37
+ resolveStrategy,
38
+ show,
39
+ fulfilmentMode,
40
+ revision
41
+ );
42
+ }, [
43
+ client,
44
+ initialMenu,
45
+ selectedMenuId,
46
+ fulfilmentMode,
47
+ resolveStrategy,
48
+ revision,
49
+ show
50
+ ]);
51
+ useEffect(() => {
52
+ loader.resume();
53
+ return () => loader.dispose();
54
+ }, [loader]);
55
+ const source = catalogue.menus?.find((value) => value.id === selectedMenuId) ?? catalogue.menus?.[0];
56
+ const categories = useMemo(
57
+ () => catalogue.categories.filter(
58
+ (category) => source?.categoryIds.includes(category.id)
59
+ ),
60
+ [catalogue.categories, source]
61
+ );
62
+ const [error, setError] = useState();
63
+ const [busy, setBusy] = useState(false);
64
+ const reach = useCallback(
65
+ async (categoryId, more = false) => {
66
+ if (!source || !more && loader.isLoaded(source.id, categoryId)) {
67
+ return;
68
+ }
69
+ setBusy(true);
70
+ setError(void 0);
71
+ try {
72
+ show(await loader.reach(source.id, categoryId, more));
73
+ } catch (failure) {
74
+ if (!(failure instanceof Error) || failure.name !== "AbortError") {
75
+ setError("Catalogue could not load. Try again.");
76
+ }
77
+ } finally {
78
+ setBusy(false);
79
+ }
80
+ },
81
+ [loader, source, show]
82
+ );
83
+ return { catalogue, source, categories, loader, error, busy, reach };
84
+ }
85
+ export {
86
+ useCatalogueData
87
+ };
88
+ //# sourceMappingURL=use-catalogue-data.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/catalogue/use-catalogue-data.ts"],
4
+ "sourcesContent": ["import type { Menu, Zappie } from \"@zappie/sdk\";\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { createCatalogueLoader, type CatalogueStrategy } from \"./loader\";\n\nexport function useCatalogueData({\n client,\n initialMenu,\n selectedMenuId,\n fulfilmentMode,\n resolveStrategy,\n revision,\n}: {\n client: Zappie;\n initialMenu: Menu;\n selectedMenuId: string;\n fulfilmentMode: \"pickup\" | \"delivery\";\n resolveStrategy: CatalogueStrategy;\n revision?: string;\n}) {\n const [catalogue, setCatalogue] = useState(initialMenu);\n const show = useCallback(\n (value: Menu) =>\n setCatalogue({\n ...value,\n ...(initialMenu.menus ? { menus: initialMenu.menus } : {}),\n categories: initialMenu.categories.map(\n (category) =>\n value.categories.find((loaded) => loaded.id === category.id) ??\n category,\n ),\n }),\n [initialMenu],\n );\n const loader = useMemo(() => {\n const selected = initialMenu.menus?.find(\n (source) => source.id === selectedMenuId,\n );\n const scoped = {\n ...initialMenu,\n ...(selected ? { menus: [selected] } : {}),\n categories: initialMenu.categories.filter(\n (category) => !selected || selected.categoryIds.includes(category.id),\n ),\n };\n return createCatalogueLoader(\n client,\n scoped,\n resolveStrategy,\n show,\n fulfilmentMode,\n revision,\n );\n }, [\n client,\n initialMenu,\n selectedMenuId,\n fulfilmentMode,\n resolveStrategy,\n revision,\n show,\n ]);\n useEffect(() => {\n loader.resume();\n return () => loader.dispose();\n }, [loader]);\n const source =\n catalogue.menus?.find((value) => value.id === selectedMenuId) ??\n catalogue.menus?.[0];\n const categories = useMemo(\n () =>\n catalogue.categories.filter((category) =>\n source?.categoryIds.includes(category.id),\n ),\n [catalogue.categories, source],\n );\n const [error, setError] = useState<string>();\n const [busy, setBusy] = useState(false);\n const reach = useCallback(\n async (categoryId: string, more = false) => {\n if (!source || (!more && loader.isLoaded(source.id, categoryId))) {\n return;\n }\n setBusy(true);\n setError(undefined);\n try {\n show(await loader.reach(source.id, categoryId, more));\n } catch (failure) {\n if (!(failure instanceof Error) || failure.name !== \"AbortError\") {\n setError(\"Catalogue could not load. Try again.\");\n }\n } finally {\n setBusy(false);\n }\n },\n [loader, source, show],\n );\n return { catalogue, source, categories, loader, error, busy, reach };\n}\n"],
5
+ "mappings": ";AACA,SAAS,aAAa,WAAW,SAAS,gBAAgB;AAC1D,SAAS,6BAAqD;AAEvD,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AACD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,WAAW;AACtD,QAAM,OAAO;AAAA,IACX,CAAC,UACC,aAAa;AAAA,MACX,GAAG;AAAA,MACH,GAAI,YAAY,QAAQ,EAAE,OAAO,YAAY,MAAM,IAAI,CAAC;AAAA,MACxD,YAAY,YAAY,WAAW;AAAA,QACjC,CAAC,aACC,MAAM,WAAW,KAAK,CAAC,WAAW,OAAO,OAAO,SAAS,EAAE,KAC3D;AAAA,MACJ;AAAA,IACF,CAAC;AAAA,IACH,CAAC,WAAW;AAAA,EACd;AACA,QAAM,SAAS,QAAQ,MAAM;AAC3B,UAAM,WAAW,YAAY,OAAO;AAAA,MAClC,CAACA,YAAWA,QAAO,OAAO;AAAA,IAC5B;AACA,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH,GAAI,WAAW,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC;AAAA,MACxC,YAAY,YAAY,WAAW;AAAA,QACjC,CAAC,aAAa,CAAC,YAAY,SAAS,YAAY,SAAS,SAAS,EAAE;AAAA,MACtE;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,YAAU,MAAM;AACd,WAAO,OAAO;AACd,WAAO,MAAM,OAAO,QAAQ;AAAA,EAC9B,GAAG,CAAC,MAAM,CAAC;AACX,QAAM,SACJ,UAAU,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO,cAAc,KAC5D,UAAU,QAAQ,CAAC;AACrB,QAAM,aAAa;AAAA,IACjB,MACE,UAAU,WAAW;AAAA,MAAO,CAAC,aAC3B,QAAQ,YAAY,SAAS,SAAS,EAAE;AAAA,IAC1C;AAAA,IACF,CAAC,UAAU,YAAY,MAAM;AAAA,EAC/B;AACA,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAiB;AAC3C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,QAAQ;AAAA,IACZ,OAAO,YAAoB,OAAO,UAAU;AAC1C,UAAI,CAAC,UAAW,CAAC,QAAQ,OAAO,SAAS,OAAO,IAAI,UAAU,GAAI;AAChE;AAAA,MACF;AACA,cAAQ,IAAI;AACZ,eAAS,MAAS;AAClB,UAAI;AACF,aAAK,MAAM,OAAO,MAAM,OAAO,IAAI,YAAY,IAAI,CAAC;AAAA,MACtD,SAAS,SAAS;AAChB,YAAI,EAAE,mBAAmB,UAAU,QAAQ,SAAS,cAAc;AAChE,mBAAS,sCAAsC;AAAA,QACjD;AAAA,MACF,UAAE;AACA,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF;AAAA,IACA,CAAC,QAAQ,QAAQ,IAAI;AAAA,EACvB;AACA,SAAO,EAAE,WAAW,QAAQ,YAAY,QAAQ,OAAO,MAAM,MAAM;AACrE;",
6
+ "names": ["source"]
7
+ }
@@ -0,0 +1,115 @@
1
+ // src/catalogue/use-category-scroll.ts
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ function useCategoryScroll({
4
+ categories,
5
+ selectedCategoryId,
6
+ itemOpen,
7
+ menuId,
8
+ menuLayout,
9
+ reach,
10
+ onSelectCategory
11
+ }) {
12
+ const [active, setActive] = useState(selectedCategoryId ?? categories[0]?.id);
13
+ const activeRef = useRef(active);
14
+ activeRef.current = active;
15
+ const sections = useRef(/* @__PURE__ */ new Map());
16
+ const wanted = useRef(void 0);
17
+ const reading = useRef(false);
18
+ const reachLatest = useCallback(
19
+ (categoryId) => {
20
+ wanted.current = categoryId;
21
+ if (reading.current) {
22
+ return;
23
+ }
24
+ reading.current = true;
25
+ void (async () => {
26
+ try {
27
+ while (wanted.current) {
28
+ const next = wanted.current;
29
+ wanted.current = void 0;
30
+ await reach(next);
31
+ }
32
+ } finally {
33
+ reading.current = false;
34
+ }
35
+ })();
36
+ },
37
+ [reach]
38
+ );
39
+ useEffect(() => {
40
+ if (itemOpen) {
41
+ return;
42
+ }
43
+ const id = selectedCategoryId ?? categories[0]?.id;
44
+ if (id) {
45
+ setActive(id);
46
+ void reach(id);
47
+ }
48
+ }, [menuId, selectedCategoryId, itemOpen, reach, categories]);
49
+ useEffect(() => {
50
+ if (itemOpen) {
51
+ return;
52
+ }
53
+ let frame = 0;
54
+ const select = () => {
55
+ frame = 0;
56
+ let id = categories[0]?.id;
57
+ for (const category of categories) {
58
+ const top = sections.current.get(category.id)?.getBoundingClientRect().top;
59
+ if (top === void 0) {
60
+ continue;
61
+ }
62
+ if (top > 200) {
63
+ break;
64
+ }
65
+ id = category.id;
66
+ }
67
+ if (window.scrollY + window.innerHeight >= document.documentElement.scrollHeight - 2) {
68
+ id = categories.at(-1)?.id ?? id;
69
+ }
70
+ if (id && id !== activeRef.current) {
71
+ setActive(id);
72
+ reachLatest(id);
73
+ }
74
+ };
75
+ const onScroll = () => {
76
+ if (!frame) {
77
+ frame = requestAnimationFrame(select);
78
+ }
79
+ };
80
+ window.addEventListener("scroll", onScroll, { passive: true });
81
+ return () => {
82
+ window.removeEventListener("scroll", onScroll);
83
+ cancelAnimationFrame(frame);
84
+ };
85
+ }, [categories, itemOpen, reachLatest]);
86
+ const selectCategory = useCallback(
87
+ (id) => {
88
+ setActive(id);
89
+ onSelectCategory(id);
90
+ sections.current.get(id)?.scrollIntoView({ block: "start" });
91
+ void reach(id);
92
+ },
93
+ [onSelectCategory, reach]
94
+ );
95
+ const previousLayout = useRef(menuLayout);
96
+ useEffect(() => {
97
+ if (previousLayout.current !== menuLayout && active) {
98
+ sections.current.get(active)?.scrollIntoView({ block: "start" });
99
+ }
100
+ previousLayout.current = menuLayout;
101
+ }, [menuLayout, active]);
102
+ const focusCategory = useCallback((id) => {
103
+ sections.current.get(id)?.querySelector("h3")?.focus();
104
+ }, []);
105
+ return {
106
+ active,
107
+ sections,
108
+ selectCategory,
109
+ focusCategory
110
+ };
111
+ }
112
+ export {
113
+ useCategoryScroll
114
+ };
115
+ //# sourceMappingURL=use-category-scroll.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/catalogue/use-category-scroll.ts"],
4
+ "sourcesContent": ["import type { Category } from \"@zappie/sdk\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport function useCategoryScroll({\n categories,\n selectedCategoryId,\n itemOpen,\n menuId,\n menuLayout,\n reach,\n onSelectCategory,\n}: {\n categories: readonly Category[];\n selectedCategoryId?: string;\n itemOpen: boolean;\n menuId?: string;\n menuLayout?: string;\n reach(categoryId: string): Promise<void>;\n onSelectCategory(categoryId: string): void;\n}) {\n const [active, setActive] = useState(selectedCategoryId ?? categories[0]?.id);\n const activeRef = useRef(active);\n activeRef.current = active;\n const sections = useRef(new Map<string, HTMLElement>());\n const wanted = useRef<string>(undefined);\n const reading = useRef(false);\n const reachLatest = useCallback(\n (categoryId: string) => {\n wanted.current = categoryId;\n if (reading.current) {\n return;\n }\n reading.current = true;\n void (async () => {\n try {\n while (wanted.current) {\n const next = wanted.current;\n wanted.current = undefined;\n await reach(next);\n }\n } finally {\n reading.current = false;\n }\n })();\n },\n [reach],\n );\n useEffect(() => {\n if (itemOpen) {\n return;\n }\n const id = selectedCategoryId ?? categories[0]?.id;\n if (id) {\n setActive(id);\n void reach(id);\n }\n }, [menuId, selectedCategoryId, itemOpen, reach, categories]);\n useEffect(() => {\n if (itemOpen) {\n return;\n }\n let frame = 0;\n const select = () => {\n frame = 0;\n let id = categories[0]?.id;\n for (const category of categories) {\n const top = sections.current\n .get(category.id)\n ?.getBoundingClientRect().top;\n if (top === undefined) {\n continue;\n }\n if (top > 200) {\n break;\n }\n id = category.id;\n }\n if (\n window.scrollY + window.innerHeight >=\n document.documentElement.scrollHeight - 2\n ) {\n id = categories.at(-1)?.id ?? id;\n }\n if (id && id !== activeRef.current) {\n setActive(id);\n reachLatest(id);\n }\n };\n const onScroll = () => {\n if (!frame) {\n frame = requestAnimationFrame(select);\n }\n };\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => {\n window.removeEventListener(\"scroll\", onScroll);\n cancelAnimationFrame(frame);\n };\n }, [categories, itemOpen, reachLatest]);\n const selectCategory = useCallback(\n (id: string) => {\n setActive(id);\n onSelectCategory(id);\n sections.current.get(id)?.scrollIntoView({ block: \"start\" });\n void reach(id);\n },\n [onSelectCategory, reach],\n );\n const previousLayout = useRef(menuLayout);\n useEffect(() => {\n if (previousLayout.current !== menuLayout && active) {\n sections.current.get(active)?.scrollIntoView({ block: \"start\" });\n }\n previousLayout.current = menuLayout;\n }, [menuLayout, active]);\n const focusCategory = useCallback((id: string) => {\n sections.current.get(id)?.querySelector(\"h3\")?.focus();\n }, []);\n return {\n active,\n sections,\n selectCategory,\n focusCategory,\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,46 @@
1
+ // src/catalogue/use-item-opener.ts
2
+ import { useOverlay } from "@zappie/foundations";
3
+ import { useCallback, useEffect, useState } from "react";
4
+ function useItemOpener({
5
+ navigation,
6
+ source,
7
+ clearSearch
8
+ }) {
9
+ const searchOverlay = useOverlay("catalogue_search", "search");
10
+ const [pendingItem, setPendingItem] = useState();
11
+ const open = useCallback(
12
+ async (item) => {
13
+ if (searchOverlay.isOpen) {
14
+ setPendingItem(item);
15
+ searchOverlay.close();
16
+ return;
17
+ }
18
+ const placement = item.placements?.find((value) => value.menuId === source?.id) ?? item.placements?.[0];
19
+ const menuId = placement?.menuId ?? source?.id;
20
+ if (menuId) {
21
+ clearSearch();
22
+ await navigation.openItem({
23
+ itemId: item.id,
24
+ item,
25
+ menuId,
26
+ ...placement ? { categoryId: placement.categoryId } : {}
27
+ });
28
+ }
29
+ },
30
+ [clearSearch, navigation, searchOverlay, source]
31
+ );
32
+ useEffect(() => {
33
+ if (pendingItem && !searchOverlay.isOpen) {
34
+ const frame = requestAnimationFrame(() => {
35
+ setPendingItem(void 0);
36
+ void open(pendingItem);
37
+ });
38
+ return () => cancelAnimationFrame(frame);
39
+ }
40
+ }, [pendingItem, searchOverlay.isOpen, open]);
41
+ return open;
42
+ }
43
+ export {
44
+ useItemOpener
45
+ };
46
+ //# sourceMappingURL=use-item-opener.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/catalogue/use-item-opener.ts"],
4
+ "sourcesContent": ["import { useOverlay } from \"@zappie/foundations\";\nimport type { Item, MenuSection } from \"@zappie/sdk\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport type { CatalogueNavigation } from \"../navigation\";\n\nexport function useItemOpener({\n navigation,\n source,\n clearSearch,\n}: {\n navigation: CatalogueNavigation;\n source?: MenuSection;\n clearSearch(): void;\n}) {\n const searchOverlay = useOverlay(\"catalogue_search\", \"search\");\n const [pendingItem, setPendingItem] = useState<Item>();\n const open = useCallback(\n async (item: Item) => {\n if (searchOverlay.isOpen) {\n setPendingItem(item);\n searchOverlay.close();\n return;\n }\n const placement =\n item.placements?.find((value) => value.menuId === source?.id) ??\n item.placements?.[0];\n const menuId = placement?.menuId ?? source?.id;\n if (menuId) {\n clearSearch();\n await navigation.openItem({\n itemId: item.id,\n item,\n menuId,\n ...(placement ? { categoryId: placement.categoryId } : {}),\n });\n }\n },\n [clearSearch, navigation, searchOverlay, source],\n );\n useEffect(() => {\n if (pendingItem && !searchOverlay.isOpen) {\n const frame = requestAnimationFrame(() => {\n setPendingItem(undefined);\n void open(pendingItem);\n });\n return () => cancelAnimationFrame(frame);\n }\n }, [pendingItem, searchOverlay.isOpen, open]);\n return open;\n}\n"],
5
+ "mappings": ";AAAA,SAAS,kBAAkB;AAE3B,SAAS,aAAa,WAAW,gBAAgB;AAG1C,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,gBAAgB,WAAW,oBAAoB,QAAQ;AAC7D,QAAM,CAAC,aAAa,cAAc,IAAI,SAAe;AACrD,QAAM,OAAO;AAAA,IACX,OAAO,SAAe;AACpB,UAAI,cAAc,QAAQ;AACxB,uBAAe,IAAI;AACnB,sBAAc,MAAM;AACpB;AAAA,MACF;AACA,YAAM,YACJ,KAAK,YAAY,KAAK,CAAC,UAAU,MAAM,WAAW,QAAQ,EAAE,KAC5D,KAAK,aAAa,CAAC;AACrB,YAAM,SAAS,WAAW,UAAU,QAAQ;AAC5C,UAAI,QAAQ;AACV,oBAAY;AACZ,cAAM,WAAW,SAAS;AAAA,UACxB,QAAQ,KAAK;AAAA,UACb;AAAA,UACA;AAAA,UACA,GAAI,YAAY,EAAE,YAAY,UAAU,WAAW,IAAI,CAAC;AAAA,QAC1D,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IACA,CAAC,aAAa,YAAY,eAAe,MAAM;AAAA,EACjD;AACA,YAAU,MAAM;AACd,QAAI,eAAe,CAAC,cAAc,QAAQ;AACxC,YAAM,QAAQ,sBAAsB,MAAM;AACxC,uBAAe,MAAS;AACxB,aAAK,KAAK,WAAW;AAAA,MACvB,CAAC;AACD,aAAO,MAAM,qBAAqB,KAAK;AAAA,IACzC;AAAA,EACF,GAAG,CAAC,aAAa,cAAc,QAAQ,IAAI,CAAC;AAC5C,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,132 @@
1
+ "use client";
2
+
3
+ // src/catalogue/use-search-source.ts
4
+ import { createSearchIndex } from "@zappie/foundations";
5
+ import { useZappie } from "@zappie/sdk/react";
6
+ import { useEffect, useMemo, useState } from "react";
7
+ function useSearchSource(menu, enabled) {
8
+ const client = useZappie();
9
+ const [query, setQuery] = useState("");
10
+ const [composing, setComposing] = useState(false);
11
+ const [attempt, setAttempt] = useState(0);
12
+ const [cursor, setCursor] = useState();
13
+ const [remote, setRemote] = useState({ query: "" });
14
+ const index = useMemo(() => createSearchIndex(menu), [menu]);
15
+ const local = useMemo(() => index(query), [index, query]);
16
+ const hasLocalItems = local.some((result) => result.type === "item");
17
+ useEffect(() => {
18
+ if (!enabled || composing || !query.trim() || hasLocalItems) {
19
+ return;
20
+ }
21
+ const abort = new AbortController();
22
+ const timer = setTimeout(() => {
23
+ setRemote((previous) => ({
24
+ ...cursor && previous.query === query ? previous : {},
25
+ query,
26
+ loading: true
27
+ }));
28
+ void client.menu.search(query, {
29
+ branch: menu.branchId,
30
+ signal: abort.signal,
31
+ ...cursor ? { cursor } : {}
32
+ }).then(
33
+ (page) => {
34
+ if (!abort.signal.aborted) {
35
+ setRemote((previous) => {
36
+ if (!cursor || previous.query !== query || !previous.page) {
37
+ return { query, page };
38
+ }
39
+ return {
40
+ query,
41
+ page: {
42
+ ...page,
43
+ menu: {
44
+ ...page.menu,
45
+ items: {
46
+ ...previous.page.menu.items,
47
+ ...page.menu.items
48
+ },
49
+ categories: [
50
+ ...new Map(
51
+ [
52
+ ...previous.page.menu.categories,
53
+ ...page.menu.categories
54
+ ].map((category) => [category.id, category])
55
+ ).values()
56
+ ]
57
+ }
58
+ }
59
+ };
60
+ });
61
+ }
62
+ },
63
+ () => {
64
+ if (!abort.signal.aborted) {
65
+ setRemote((previous) => ({
66
+ ...cursor && previous.query === query ? previous : {},
67
+ query,
68
+ loading: false,
69
+ error: "Search could not load. Try again."
70
+ }));
71
+ }
72
+ }
73
+ );
74
+ }, 500);
75
+ return () => {
76
+ clearTimeout(timer);
77
+ abort.abort();
78
+ };
79
+ }, [
80
+ client,
81
+ menu.branchId,
82
+ query,
83
+ enabled,
84
+ composing,
85
+ hasLocalItems,
86
+ attempt,
87
+ cursor
88
+ ]);
89
+ const current = !composing && remote.query === query && !hasLocalItems ? remote : void 0;
90
+ const results = useMemo(() => {
91
+ const values = [...local];
92
+ for (const item of Object.values(current?.page?.menu.items ?? {})) {
93
+ if (!values.some(
94
+ (result) => result.type === "item" && result.item.id === item.id
95
+ )) {
96
+ values.push({ type: "item", item, score: 0 });
97
+ }
98
+ }
99
+ for (const category of current?.page?.menu.categories ?? []) {
100
+ if (category.name.toLocaleLowerCase().includes(query.trim().toLocaleLowerCase()) && !values.some(
101
+ (result) => result.type === "category" && result.category.id === category.id
102
+ )) {
103
+ values.push({ type: "category", category, score: 0 });
104
+ }
105
+ }
106
+ return values;
107
+ }, [local, current?.page, query]);
108
+ return {
109
+ query,
110
+ setQuery: (value) => {
111
+ setCursor(void 0);
112
+ setQuery(value);
113
+ },
114
+ hasMore: Boolean(current?.page?.nextCursor),
115
+ loadMore: () => {
116
+ if (!current?.loading && current?.page?.nextCursor) {
117
+ setCursor(current.page.nextCursor);
118
+ }
119
+ },
120
+ source: {
121
+ results,
122
+ isLoading: current?.loading ?? false,
123
+ ...current?.error ? { error: current.error } : {},
124
+ retry: () => setAttempt((value) => value + 1),
125
+ onCompositionChange: setComposing
126
+ }
127
+ };
128
+ }
129
+ export {
130
+ useSearchSource
131
+ };
132
+ //# sourceMappingURL=use-search-source.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/catalogue/use-search-source.ts"],
4
+ "sourcesContent": ["\"use client\";\nimport { createSearchIndex, type SearchResult } from \"@zappie/foundations\";\nimport type { Menu, SearchPage } from \"@zappie/sdk\";\nimport { useZappie } from \"@zappie/sdk/react\";\nimport { useEffect, useMemo, useState } from \"react\";\n\n/** Loaded matches win; remote fallback never hydrates a complete catalogue. */\nexport function useSearchSource(menu: Menu, enabled: boolean) {\n const client = useZappie();\n const [query, setQuery] = useState(\"\");\n const [composing, setComposing] = useState(false);\n const [attempt, setAttempt] = useState(0);\n const [cursor, setCursor] = useState<string>();\n const [remote, setRemote] = useState<{\n query: string;\n page?: SearchPage;\n error?: string;\n loading?: boolean;\n }>({ query: \"\" });\n const index = useMemo(() => createSearchIndex(menu), [menu]);\n const local = useMemo(() => index(query), [index, query]);\n const hasLocalItems = local.some((result) => result.type === \"item\");\n useEffect(() => {\n if (!enabled || composing || !query.trim() || hasLocalItems) {\n return;\n }\n const abort = new AbortController();\n const timer = setTimeout(() => {\n setRemote((previous) => ({\n ...(cursor && previous.query === query ? previous : {}),\n query,\n loading: true,\n }));\n void client.menu\n .search(query, {\n branch: menu.branchId,\n signal: abort.signal,\n ...(cursor ? { cursor } : {}),\n })\n .then(\n (page) => {\n if (!abort.signal.aborted) {\n setRemote((previous) => {\n if (!cursor || previous.query !== query || !previous.page) {\n return { query, page };\n }\n return {\n query,\n page: {\n ...page,\n menu: {\n ...page.menu,\n items: {\n ...previous.page.menu.items,\n ...page.menu.items,\n },\n categories: [\n ...new Map(\n [\n ...previous.page.menu.categories,\n ...page.menu.categories,\n ].map((category) => [category.id, category]),\n ).values(),\n ],\n },\n },\n };\n });\n }\n },\n () => {\n if (!abort.signal.aborted) {\n setRemote((previous) => ({\n ...(cursor && previous.query === query ? previous : {}),\n query,\n loading: false,\n error: \"Search could not load. Try again.\",\n }));\n }\n },\n );\n }, 500);\n return () => {\n clearTimeout(timer);\n abort.abort();\n };\n }, [\n client,\n menu.branchId,\n query,\n enabled,\n composing,\n hasLocalItems,\n attempt,\n cursor,\n ]);\n const current =\n !composing && remote.query === query && !hasLocalItems ? remote : undefined;\n const results = useMemo(() => {\n const values: SearchResult[] = [...local];\n for (const item of Object.values(current?.page?.menu.items ?? {})) {\n if (\n !values.some(\n (result) => result.type === \"item\" && result.item.id === item.id,\n )\n ) {\n values.push({ type: \"item\", item, score: 0 });\n }\n }\n for (const category of current?.page?.menu.categories ?? []) {\n if (\n category.name\n .toLocaleLowerCase()\n .includes(query.trim().toLocaleLowerCase()) &&\n !values.some(\n (result) =>\n result.type === \"category\" && result.category.id === category.id,\n )\n ) {\n values.push({ type: \"category\", category, score: 0 });\n }\n }\n return values;\n }, [local, current?.page, query]);\n return {\n query,\n setQuery: (value: string) => {\n setCursor(undefined);\n setQuery(value);\n },\n hasMore: Boolean(current?.page?.nextCursor),\n loadMore: () => {\n if (!current?.loading && current?.page?.nextCursor) {\n setCursor(current.page.nextCursor);\n }\n },\n source: {\n results,\n isLoading: current?.loading ?? false,\n ...(current?.error ? { error: current.error } : {}),\n retry: () => setAttempt((value) => value + 1),\n onCompositionChange: setComposing,\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }