zudoku 0.32.6 → 0.33.0

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 (186) hide show
  1. package/cli.js +3 -0
  2. package/dist/app/main.js +1 -1
  3. package/dist/app/main.js.map +1 -1
  4. package/dist/cli/build/handler.d.ts +1 -3
  5. package/dist/cli/build/handler.js +7 -0
  6. package/dist/cli/build/handler.js.map +1 -1
  7. package/dist/cli/cli.js +5 -0
  8. package/dist/cli/cli.js.map +1 -1
  9. package/dist/cli/cmds/build.d.ts +11 -3
  10. package/dist/cli/cmds/build.js +20 -13
  11. package/dist/cli/cmds/build.js.map +1 -1
  12. package/dist/cli/cmds/preview.d.ts +16 -0
  13. package/dist/cli/cmds/preview.js +25 -0
  14. package/dist/cli/cmds/preview.js.map +1 -0
  15. package/dist/cli/preview/handler.d.ts +3 -0
  16. package/dist/cli/preview/handler.js +37 -0
  17. package/dist/cli/preview/handler.js.map +1 -0
  18. package/dist/config/common.d.ts +5 -2
  19. package/dist/config/config.d.ts +2 -7
  20. package/dist/config/loader.d.ts +4 -4
  21. package/dist/config/loader.js +7 -4
  22. package/dist/config/loader.js.map +1 -1
  23. package/dist/config/validators/validate.d.ts +1 -1
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/lib/authentication/providers/auth0.d.ts +2 -2
  28. package/dist/lib/authentication/providers/auth0.js +1 -0
  29. package/dist/lib/authentication/providers/auth0.js.map +1 -1
  30. package/dist/lib/authentication/providers/openid.d.ts +4 -3
  31. package/dist/lib/authentication/providers/openid.js +4 -2
  32. package/dist/lib/authentication/providers/openid.js.map +1 -1
  33. package/dist/lib/components/ThemeSwitch.js +1 -1
  34. package/dist/lib/components/ThemeSwitch.js.map +1 -1
  35. package/dist/lib/components/Zudoku.d.ts +1 -1
  36. package/dist/lib/components/Zudoku.js +7 -6
  37. package/dist/lib/components/Zudoku.js.map +1 -1
  38. package/dist/lib/components/context/RouterEventsEmitter.d.ts +1 -0
  39. package/dist/lib/components/context/RouterEventsEmitter.js +17 -0
  40. package/dist/lib/components/context/RouterEventsEmitter.js.map +1 -0
  41. package/dist/lib/core/ZudokuContext.d.ts +10 -0
  42. package/dist/lib/core/ZudokuContext.js +17 -1
  43. package/dist/lib/core/ZudokuContext.js.map +1 -1
  44. package/dist/lib/core/plugins.d.ts +8 -2
  45. package/dist/lib/core/plugins.js +1 -0
  46. package/dist/lib/core/plugins.js.map +1 -1
  47. package/dist/lib/hooks/index.d.ts +3 -0
  48. package/dist/lib/hooks/index.js +5 -0
  49. package/dist/lib/hooks/index.js.map +1 -0
  50. package/dist/lib/hooks/useEvent.d.ts +11 -0
  51. package/dist/lib/hooks/useEvent.js +19 -0
  52. package/dist/lib/hooks/useEvent.js.map +1 -0
  53. package/dist/lib/hooks/useEvent.test.d.ts +1 -0
  54. package/dist/lib/hooks/useEvent.test.js +100 -0
  55. package/dist/lib/hooks/useEvent.test.js.map +1 -0
  56. package/dist/lib/ui/Stepper.d.ts +4 -0
  57. package/dist/lib/ui/Stepper.js +7 -0
  58. package/dist/lib/ui/Stepper.js.map +1 -0
  59. package/dist/lib/util/MdxComponents.d.ts +3 -1
  60. package/dist/lib/util/MdxComponents.js +3 -0
  61. package/dist/lib/util/MdxComponents.js.map +1 -1
  62. package/dist/vite/config.d.ts +2 -5
  63. package/dist/vite/config.js +21 -28
  64. package/dist/vite/config.js.map +1 -1
  65. package/dist/vite/config.test.js +1 -1
  66. package/dist/vite/config.test.js.map +1 -1
  67. package/dist/vite/css/plugin.d.ts +2 -2
  68. package/dist/vite/css/plugin.js.map +1 -1
  69. package/dist/vite/dev-server.js.map +1 -1
  70. package/dist/vite/plugin-api-keys.d.ts +2 -2
  71. package/dist/vite/plugin-api-keys.js +3 -3
  72. package/dist/vite/plugin-api-keys.js.map +1 -1
  73. package/dist/vite/plugin-api.d.ts +2 -2
  74. package/dist/vite/plugin-api.js +5 -5
  75. package/dist/vite/plugin-api.js.map +1 -1
  76. package/dist/vite/plugin-auth.d.ts +2 -2
  77. package/dist/vite/plugin-auth.js +3 -3
  78. package/dist/vite/plugin-auth.js.map +1 -1
  79. package/dist/vite/plugin-component.d.ts +2 -2
  80. package/dist/vite/plugin-component.js +3 -2
  81. package/dist/vite/plugin-component.js.map +1 -1
  82. package/dist/vite/plugin-config-reload.d.ts +4 -3
  83. package/dist/vite/plugin-config-reload.js +11 -8
  84. package/dist/vite/plugin-config-reload.js.map +1 -1
  85. package/dist/vite/plugin-config.d.ts +2 -2
  86. package/dist/vite/plugin-config.js +2 -2
  87. package/dist/vite/plugin-config.js.map +1 -1
  88. package/dist/vite/plugin-custom-pages.d.ts +2 -2
  89. package/dist/vite/plugin-custom-pages.js +3 -3
  90. package/dist/vite/plugin-custom-pages.js.map +1 -1
  91. package/dist/vite/plugin-docs.d.ts +2 -2
  92. package/dist/vite/plugin-docs.js +5 -5
  93. package/dist/vite/plugin-docs.js.map +1 -1
  94. package/dist/vite/plugin-frontmatter.d.ts +2 -2
  95. package/dist/vite/plugin-frontmatter.js +5 -4
  96. package/dist/vite/plugin-frontmatter.js.map +1 -1
  97. package/dist/vite/plugin-mdx.d.ts +2 -2
  98. package/dist/vite/plugin-mdx.js +1 -1
  99. package/dist/vite/plugin-mdx.js.map +1 -1
  100. package/dist/vite/plugin-redirect.d.ts +2 -2
  101. package/dist/vite/plugin-redirect.js +3 -3
  102. package/dist/vite/plugin-redirect.js.map +1 -1
  103. package/dist/vite/plugin-search.d.ts +2 -2
  104. package/dist/vite/plugin-search.js +1 -1
  105. package/dist/vite/plugin-search.js.map +1 -1
  106. package/dist/vite/plugin-sidebar.d.ts +2 -2
  107. package/dist/vite/plugin-sidebar.js +2 -2
  108. package/dist/vite/plugin-sidebar.js.map +1 -1
  109. package/dist/vite/plugin-theme-css.d.ts +2 -2
  110. package/dist/vite/plugin-theme-css.js.map +1 -1
  111. package/dist/vite/plugin.d.ts +2 -2
  112. package/dist/vite/plugin.js.map +1 -1
  113. package/dist/vite/prerender/FileWritingResponse.d.ts +2 -1
  114. package/dist/vite/prerender/FileWritingResponse.js +4 -1
  115. package/dist/vite/prerender/FileWritingResponse.js.map +1 -1
  116. package/dist/vite/prerender/worker.d.ts +1 -1
  117. package/dist/vite/prerender/worker.js +3 -0
  118. package/dist/vite/prerender/worker.js.map +1 -1
  119. package/lib/{AuthenticationPlugin-BlxA4Mbn.js → AuthenticationPlugin-_gUMnGxb.js} +2 -2
  120. package/lib/{AuthenticationPlugin-BlxA4Mbn.js.map → AuthenticationPlugin-_gUMnGxb.js.map} +1 -1
  121. package/lib/{Markdown-Cr9sYpR_.js → Markdown-DePfm7oQ.js} +1384 -1381
  122. package/lib/{Markdown-Cr9sYpR_.js.map → Markdown-DePfm7oQ.js.map} +1 -1
  123. package/lib/{MdxPage-Dt-UEQl8.js → MdxPage-DM9mE-G-.js} +4 -4
  124. package/lib/{MdxPage-Dt-UEQl8.js.map → MdxPage-DM9mE-G-.js.map} +1 -1
  125. package/lib/{OasProvider-WVtvHP5H.js → OasProvider-Bvu4dDpX.js} +3 -3
  126. package/lib/{OasProvider-WVtvHP5H.js.map → OasProvider-Bvu4dDpX.js.map} +1 -1
  127. package/lib/{OperationList-DhOwupvv.js → OperationList-DWnNbwVg.js} +241 -240
  128. package/lib/OperationList-DWnNbwVg.js.map +1 -0
  129. package/lib/{Select-D9hI1G-y.js → Select-BmoX1iTH.js} +36 -36
  130. package/lib/{Select-D9hI1G-y.js.map → Select-BmoX1iTH.js.map} +1 -1
  131. package/lib/{SlotletProvider-CEfNOA8i.js → SlotletProvider-DdtIOUi6.js} +2 -2
  132. package/lib/{SlotletProvider-CEfNOA8i.js.map → SlotletProvider-DdtIOUi6.js.map} +1 -1
  133. package/lib/{createServer-DMf6O2Rz.js → createServer-DmusVVsi.js} +987 -967
  134. package/lib/createServer-DmusVVsi.js.map +1 -0
  135. package/lib/{hook-CWwSAAlH.js → hook-4_6pQSo4.js} +29 -29
  136. package/lib/{hook-CWwSAAlH.js.map → hook-4_6pQSo4.js.map} +1 -1
  137. package/lib/{index-Do_30Hpk.js → index-DVBlM15k.js} +83 -82
  138. package/lib/{index-Do_30Hpk.js.map → index-DVBlM15k.js.map} +1 -1
  139. package/lib/index-DwT-v3zK.js +86 -0
  140. package/lib/index-DwT-v3zK.js.map +1 -0
  141. package/lib/{mutation-B0wxqzSN.js → mutation-DTunCQKB.js} +2 -2
  142. package/lib/{mutation-B0wxqzSN.js.map → mutation-DTunCQKB.js.map} +1 -1
  143. package/lib/objectEntries-yMIkr2mI.js +5 -0
  144. package/lib/objectEntries-yMIkr2mI.js.map +1 -0
  145. package/lib/ui/Stepper.js +6 -0
  146. package/lib/ui/Stepper.js.map +1 -0
  147. package/lib/ui/SyntaxHighlight.js +2902 -7
  148. package/lib/ui/SyntaxHighlight.js.map +1 -1
  149. package/lib/{useScrollToAnchor-C-sRxs9o.js → useScrollToAnchor-BW8y_cwU.js} +3 -3
  150. package/lib/{useScrollToAnchor-C-sRxs9o.js.map → useScrollToAnchor-BW8y_cwU.js.map} +1 -1
  151. package/lib/zudoku.auth-auth0.js +15 -14
  152. package/lib/zudoku.auth-auth0.js.map +1 -1
  153. package/lib/zudoku.auth-clerk.js +2 -2
  154. package/lib/zudoku.auth-openid.js +126 -124
  155. package/lib/zudoku.auth-openid.js.map +1 -1
  156. package/lib/zudoku.components.js +489 -454
  157. package/lib/zudoku.components.js.map +1 -1
  158. package/lib/zudoku.css +1 -0
  159. package/lib/zudoku.hooks.js +19 -0
  160. package/lib/zudoku.hooks.js.map +1 -0
  161. package/lib/zudoku.plugin-api-catalog.js +2 -2
  162. package/lib/zudoku.plugin-api-keys.js +5 -5
  163. package/lib/zudoku.plugin-custom-pages.js +1 -1
  164. package/lib/zudoku.plugin-markdown.js +1 -1
  165. package/lib/zudoku.plugin-openapi.js +2 -2
  166. package/lib/zudoku.plugins.js +9 -8
  167. package/lib/zudoku.plugins.js.map +1 -1
  168. package/package.json +12 -4
  169. package/src/app/main.tsx +1 -1
  170. package/src/lib/authentication/providers/auth0.tsx +3 -2
  171. package/src/lib/authentication/providers/openid.tsx +8 -5
  172. package/src/lib/components/ThemeSwitch.tsx +1 -1
  173. package/src/lib/components/Zudoku.tsx +9 -5
  174. package/src/lib/components/context/RouterEventsEmitter.tsx +19 -0
  175. package/src/lib/core/ZudokuContext.ts +31 -0
  176. package/src/lib/core/plugins.ts +16 -2
  177. package/src/lib/hooks/index.ts +5 -0
  178. package/src/lib/hooks/useEvent.test.tsx +149 -0
  179. package/src/lib/hooks/useEvent.ts +41 -0
  180. package/src/lib/ui/Stepper.css +43 -0
  181. package/src/lib/ui/Stepper.tsx +8 -0
  182. package/src/lib/util/MdxComponents.tsx +4 -1
  183. package/lib/OperationList-DhOwupvv.js.map +0 -1
  184. package/lib/SyntaxHighlight-CcnUjERD.js +0 -2986
  185. package/lib/SyntaxHighlight-CcnUjERD.js.map +0 -1
  186. package/lib/createServer-DMf6O2Rz.js.map +0 -1
package/lib/zudoku.css ADDED
@@ -0,0 +1 @@
1
+ @tailwind components;@layer components{.stepper>ol{--bullet-size: 1.75rem;--line-spacing: .25rem;@apply flex flex-col list-none p-0 m-0;counter-reset:step-counter}.stepper>ol>li{@apply relative ps-12 pb-6 my-0;counter-increment:step-counter}.stepper>ol>li:last-child{@apply pb-0;}.stepper>ol>li:before{@apply absolute left-0 flex items-center justify-center size-[--bullet-size];@apply bg-muted/80 border border-border text-muted-foreground text-[calc(var(--bullet-size)/2)] font-semibold rounded-full;content:counter(step-counter)}.stepper>ol>li:after{@apply absolute content-[""] w-[2px] bg-border -translate-x-1/2;left:calc(var(--bullet-size) / 2);top:calc(var(--bullet-size) + var(--line-spacing));height:calc(100% - var(--bullet-size) - (2 * var(--line-spacing)))}.stepper>ol>li>:first-child{@apply mt-0 pt-0;transform:translateY(calc((var(--bullet-size) - 1lh) / 2))}}
@@ -0,0 +1,19 @@
1
+ import { z as f } from "./index-DwT-v3zK.js";
2
+ import { useState as m, useEffect as i } from "react";
3
+ import { a } from "./hook-4_6pQSo4.js";
4
+ function d(e, t) {
5
+ const s = a(), [n, o] = m();
6
+ return i(() => s.addEventListener(e, (...u) => {
7
+ if (t) {
8
+ const r = t(...u);
9
+ o(r);
10
+ } else
11
+ o(u);
12
+ }), [s, e, t]), n;
13
+ }
14
+ const v = d, z = f;
15
+ export {
16
+ v as useEvent,
17
+ z as useTheme
18
+ };
19
+ //# sourceMappingURL=zudoku.hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zudoku.hooks.js","sources":["../src/lib/hooks/useEvent.ts","../src/lib/hooks/index.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useZudoku } from \"../components/context/ZudokuContext.js\";\nimport type { ZudokuEvents } from \"../core/ZudokuContext.js\";\n\ntype EventParameters<Event extends keyof ZudokuEvents> = Parameters<\n ZudokuEvents[Event]\n>;\n\n/**\n * Hook to subscribe to Zudoku events with automatic cleanup\n * @param event The event to subscribe to\n * @param callback Optional callback to be called when the event is emitted\n * @returns The latest event data if no callback is provided, or the callback's return value if it returns something\n */\nexport function useEvent<E extends keyof ZudokuEvents>(\n event: E,\n): EventParameters<E>;\nexport function useEvent<E extends keyof ZudokuEvents, R>(\n event: E,\n callback: (...args: EventParameters<E>) => R,\n): R;\nexport function useEvent<E extends keyof ZudokuEvents, R>(\n event: E,\n callback?: (...args: EventParameters<E>) => R,\n) {\n const zudoku = useZudoku();\n const [latestData, setLatestData] = useState<R | EventParameters<E>>();\n\n useEffect(() => {\n return zudoku.addEventListener(event, ((...args: EventParameters<E>) => {\n if (callback) {\n const result = callback(...args);\n setLatestData(result);\n } else {\n setLatestData(args);\n }\n }) as ZudokuEvents[E]);\n }, [zudoku, event, callback]);\n\n return latestData;\n}\n","import { useTheme as useThemeImport } from \"next-themes\";\nimport { useEvent as useEventImport } from \"./useEvent.js\";\n\nexport const useEvent = /*@__PURE__*/ useEventImport;\nexport const useTheme = /*@__PURE__*/ useThemeImport;\n"],"names":["useEvent","event","callback","zudoku","useZudoku","latestData","setLatestData","useState","useEffect","args","result","useEventImport","useTheme","useThemeImport"],"mappings":";;;AAqBgB,SAAAA,EACdC,GACAC,GACA;AACA,QAAMC,IAASC,EAAU,GACnB,CAACC,GAAYC,CAAa,IAAIC,EAAiC;AAErE,SAAAC,EAAU,MACDL,EAAO,iBAAiBF,GAAQ,IAAIQ,MAA6B;AACtE,QAAIP,GAAU;AACN,YAAAQ,IAASR,EAAS,GAAGO,CAAI;AAC/B,MAAAH,EAAcI,CAAM;AAAA,IAAA;AAEpB,MAAAJ,EAAcG,CAAI;AAAA,EACpB,CACmB,GACpB,CAACN,GAAQF,GAAOC,CAAQ,CAAC,GAErBG;AACT;ACrCO,MAAML,IAAyBW,GACzBC,IAAyBC;"}
@@ -2,9 +2,9 @@ import { j as e } from "./jsx-runtime-CYK1ROHF.js";
2
2
  import { s as f } from "./index-LNp6rxyU.js";
3
3
  import { d as b, m as x } from "./chunk-IR6S3I6Y-D_3UmFIn.js";
4
4
  import { j as d } from "./joinUrl-10po2Jdj.js";
5
- import { u as j, a as y } from "./hook-CWwSAAlH.js";
5
+ import { u as j, b as y } from "./hook-4_6pQSo4.js";
6
6
  import { Head as v, Link as N } from "./zudoku.components.js";
7
- import { H as S, M as w } from "./Markdown-Cr9sYpR_.js";
7
+ import { H as S, M as w } from "./Markdown-DePfm7oQ.js";
8
8
  const M = ({
9
9
  items: a,
10
10
  filterCatalogItems: r = (s) => s,
@@ -1,9 +1,9 @@
1
1
  import { j as e } from "./jsx-runtime-CYK1ROHF.js";
2
- import { RotateCwIcon as j, TrashIcon as v, EyeOffIcon as w, EyeIcon as K, CheckIcon as k, CopyIcon as b, FileKey2Icon as N } from "lucide-react";
3
- import { D as I, S as x, R as S } from "./SlotletProvider-CEfNOA8i.js";
2
+ import { RotateCwIcon as j, TrashIcon as v, EyeOffIcon as w, EyeIcon as K, CheckIcon as b, CopyIcon as k, FileKey2Icon as N } from "lucide-react";
3
+ import { D as I, S as x, R as S } from "./SlotletProvider-DdtIOUi6.js";
4
4
  import { i as c } from "./invariant-Caa8-XvF.js";
5
- import { i as h, e as g, k as A, a as C } from "./hook-CWwSAAlH.js";
6
- import { u as d, S as E, a as P, b as D, c as q, d as R, e as p } from "./Select-D9hI1G-y.js";
5
+ import { a as h, f as g, k as A, b as C } from "./hook-4_6pQSo4.js";
6
+ import { u as d, S as E, a as P, b as D, c as q, d as R, e as p } from "./Select-BmoX1iTH.js";
7
7
  import { a as O } from "./index.esm--gIChbWs.js";
8
8
  import { a as z, L as u, O as F } from "./chunk-IR6S3I6Y-D_3UmFIn.js";
9
9
  import { Button as l } from "./ui/Button.js";
@@ -195,7 +195,7 @@ const V = ({ service: t }) => {
195
195
  });
196
196
  },
197
197
  size: "icon",
198
- children: r ? /* @__PURE__ */ e.jsx(k, { size: 16 }) : /* @__PURE__ */ e.jsx(b, { size: 16 })
198
+ children: r ? /* @__PURE__ */ e.jsx(b, { size: 16 }) : /* @__PURE__ */ e.jsx(k, { size: 16 })
199
199
  }
200
200
  )
201
201
  ] });
@@ -1,6 +1,6 @@
1
1
  import { j as o } from "./jsx-runtime-CYK1ROHF.js";
2
2
  import a from "react";
3
- import { P as n } from "./Markdown-Cr9sYpR_.js";
3
+ import { P as n } from "./Markdown-DePfm7oQ.js";
4
4
  import { c } from "./cn-qaFjX9_3.js";
5
5
  import { u as p } from "./useExposedProps-RIvey2Oy.js";
6
6
  const u = ({
@@ -53,7 +53,7 @@ const P = (e) => ({
53
53
  const u = {
54
54
  path: r,
55
55
  lazy: async () => {
56
- const { MdxPage: p } = await import("./MdxPage-Dt-UEQl8.js"), { default: f, ...l } = await i();
56
+ const { MdxPage: p } = await import("./MdxPage-DM9mE-G-.js"), { default: f, ...l } = await i();
57
57
  return {
58
58
  element: /* @__PURE__ */ d.jsx(
59
59
  p,
@@ -2,10 +2,10 @@ import "./jsx-runtime-CYK1ROHF.js";
2
2
  import "./index-LNp6rxyU.js";
3
3
  import "lucide-react";
4
4
  import "./chunk-IR6S3I6Y-D_3UmFIn.js";
5
- import "./hook-CWwSAAlH.js";
5
+ import "./hook-4_6pQSo4.js";
6
6
  import "./ui/Button.js";
7
7
  import "./joinUrl-10po2Jdj.js";
8
- import { U as n, o as s } from "./index-Do_30Hpk.js";
8
+ import { U as n, o as s } from "./index-DVBlM15k.js";
9
9
  export {
10
10
  n as UNTAGGED_PATH,
11
11
  s as openApiPlugin
@@ -1,13 +1,14 @@
1
- const n = (e) => e, t = (e) => e, i = (e) => "getProfileMenuItems" in e && typeof e.getProfileMenuItems == "function", o = (e) => "getRoutes" in e && typeof e.getRoutes == "function", s = (e) => "renderSearch" in e && typeof e.renderSearch == "function", c = (e) => "initialize" in e && typeof e.initialize == "function", u = (e) => "getHead" in e && typeof e.getHead == "function", f = (e) => "getMdxComponents" in e && typeof e.getMdxComponents == "function", g = (e) => "getIdentities" in e && typeof e.getIdentities == "function";
1
+ const n = (e) => e, t = (e) => e, i = (e) => "events" in e && typeof e.events == "object", o = (e) => "getProfileMenuItems" in e && typeof e.getProfileMenuItems == "function", s = (e) => "getRoutes" in e && typeof e.getRoutes == "function", c = (e) => "renderSearch" in e && typeof e.renderSearch == "function", u = (e) => "initialize" in e && typeof e.initialize == "function", f = (e) => "getHead" in e && typeof e.getHead == "function", g = (e) => "getMdxComponents" in e && typeof e.getMdxComponents == "function", r = (e) => "getIdentities" in e && typeof e.getIdentities == "function";
2
2
  export {
3
3
  n as createApiIdentityPlugin,
4
4
  t as createProfileMenuPlugin,
5
- u as hasHead,
6
- g as isApiIdentityPlugin,
7
- f as isMdxProviderPlugin,
8
- o as isNavigationPlugin,
9
- i as isProfileMenuPlugin,
10
- s as isSearchPlugin,
11
- c as needsInitialization
5
+ f as hasHead,
6
+ r as isApiIdentityPlugin,
7
+ i as isEventConsumerPlugin,
8
+ g as isMdxProviderPlugin,
9
+ s as isNavigationPlugin,
10
+ o as isProfileMenuPlugin,
11
+ c as isSearchPlugin,
12
+ u as needsInitialization
12
13
  };
13
14
  //# sourceMappingURL=zudoku.plugins.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"zudoku.plugins.js","sources":["../src/lib/core/plugins.ts"],"sourcesContent":["import type { LucideIcon } from \"lucide-react\";\nimport { type ReactElement } from \"react\";\nimport { type RouteObject } from \"react-router\";\nimport type { Sidebar } from \"../../config/validators/SidebarSchema.js\";\nimport { MdxComponentsType } from \"../util/MdxComponents.js\";\nimport { ZudokuContext, type ApiIdentity } from \"./ZudokuContext.js\";\n\nexport type ZudokuPlugin =\n | CommonPlugin\n | ProfileMenuPlugin\n | NavigationPlugin\n | ApiIdentityPlugin\n | SearchProviderPlugin;\n\nexport type { RouteObject };\n\nexport interface NavigationPlugin {\n getRoutes: () => RouteObject[];\n getSidebar?: (path: string) => Promise<Sidebar>;\n}\n\nexport const createApiIdentityPlugin = (\n plugin: ApiIdentityPlugin,\n): ApiIdentityPlugin => plugin;\n\nexport const createProfileMenuPlugin = (\n plugin: ProfileMenuPlugin,\n): ProfileMenuPlugin => plugin;\n\nexport interface ApiIdentityPlugin {\n getIdentities: (context: ZudokuContext) => Promise<ApiIdentity[]>;\n}\n\nexport interface SearchProviderPlugin {\n renderSearch: (o: {\n isOpen: boolean;\n onClose: () => void;\n }) => React.JSX.Element | null;\n}\n\nexport interface ProfileMenuPlugin {\n getProfileMenuItems: (context: ZudokuContext) => ProfileNavigationItem[];\n}\n\nexport type ProfileNavigationItem = {\n label: string;\n path?: string;\n weight?: number;\n category?: \"top\" | \"middle\" | \"bottom\";\n children?: ProfileNavigationItem[];\n icon?: LucideIcon;\n};\n\nexport interface CommonPlugin {\n initialize?: (\n context: ZudokuContext,\n ) => Promise<void | boolean> | void | boolean;\n getHead?: () => ReactElement | undefined;\n getMdxComponents?: () => MdxComponentsType;\n}\n\nexport const isProfileMenuPlugin = (\n obj: ZudokuPlugin,\n): obj is ProfileMenuPlugin =>\n \"getProfileMenuItems\" in obj && typeof obj.getProfileMenuItems === \"function\";\n\nexport const isNavigationPlugin = (\n obj: ZudokuPlugin,\n): obj is NavigationPlugin =>\n \"getRoutes\" in obj && typeof obj.getRoutes === \"function\";\n\nexport const isSearchPlugin = (\n obj: ZudokuPlugin,\n): obj is SearchProviderPlugin =>\n \"renderSearch\" in obj && typeof obj.renderSearch === \"function\";\n\nexport const needsInitialization = (obj: ZudokuPlugin): obj is CommonPlugin =>\n \"initialize\" in obj && typeof obj.initialize === \"function\";\n\nexport const hasHead = (obj: ZudokuPlugin): obj is CommonPlugin =>\n \"getHead\" in obj && typeof obj.getHead === \"function\";\n\nexport const isMdxProviderPlugin = (obj: ZudokuPlugin): obj is CommonPlugin =>\n \"getMdxComponents\" in obj && typeof obj.getMdxComponents === \"function\";\n\nexport const isApiIdentityPlugin = (\n obj: ZudokuPlugin,\n): obj is ApiIdentityPlugin =>\n \"getIdentities\" in obj && typeof obj.getIdentities === \"function\";\n"],"names":["createApiIdentityPlugin","plugin","createProfileMenuPlugin","isProfileMenuPlugin","obj","isNavigationPlugin","isSearchPlugin","needsInitialization","hasHead","isMdxProviderPlugin","isApiIdentityPlugin"],"mappings":"AAqBa,MAAAA,IAA0B,CACrCC,MACsBA,GAEXC,IAA0B,CACrCD,MACsBA,GAkCXE,IAAsB,CACjCC,MAEA,yBAAyBA,KAAO,OAAOA,EAAI,uBAAwB,YAExDC,IAAqB,CAChCD,MAEA,eAAeA,KAAO,OAAOA,EAAI,aAAc,YAEpCE,IAAiB,CAC5BF,MAEA,kBAAkBA,KAAO,OAAOA,EAAI,gBAAiB,YAE1CG,IAAsB,CAACH,MAClC,gBAAgBA,KAAO,OAAOA,EAAI,cAAe,YAEtCI,IAAU,CAACJ,MACtB,aAAaA,KAAO,OAAOA,EAAI,WAAY,YAEhCK,IAAsB,CAACL,MAClC,sBAAsBA,KAAO,OAAOA,EAAI,oBAAqB,YAElDM,IAAsB,CACjCN,MAEA,mBAAmBA,KAAO,OAAOA,EAAI,iBAAkB;"}
1
+ {"version":3,"file":"zudoku.plugins.js","sources":["../src/lib/core/plugins.ts"],"sourcesContent":["import type { LucideIcon } from \"lucide-react\";\nimport { type ReactElement } from \"react\";\nimport { type RouteObject } from \"react-router\";\nimport type { Sidebar } from \"../../config/validators/SidebarSchema.js\";\nimport { MdxComponentsType } from \"../util/MdxComponents.js\";\nimport {\n ZudokuContext,\n ZudokuEvents,\n type ApiIdentity,\n} from \"./ZudokuContext.js\";\n\nexport type ZudokuPlugin =\n | CommonPlugin\n | ProfileMenuPlugin\n | NavigationPlugin\n | ApiIdentityPlugin\n | SearchProviderPlugin\n | EventConsumerPlugin;\n\nexport type { RouteObject };\n\nexport interface NavigationPlugin {\n getRoutes: () => RouteObject[];\n getSidebar?: (path: string) => Promise<Sidebar>;\n}\n\nexport const createApiIdentityPlugin = (\n plugin: ApiIdentityPlugin,\n): ApiIdentityPlugin => plugin;\n\nexport const createProfileMenuPlugin = (\n plugin: ProfileMenuPlugin,\n): ProfileMenuPlugin => plugin;\n\nexport interface ApiIdentityPlugin {\n getIdentities: (context: ZudokuContext) => Promise<ApiIdentity[]>;\n}\n\nexport interface SearchProviderPlugin {\n renderSearch: (o: {\n isOpen: boolean;\n onClose: () => void;\n }) => React.JSX.Element | null;\n}\n\nexport interface ProfileMenuPlugin {\n getProfileMenuItems: (context: ZudokuContext) => ProfileNavigationItem[];\n}\n\nexport type ProfileNavigationItem = {\n label: string;\n path?: string;\n weight?: number;\n category?: \"top\" | \"middle\" | \"bottom\";\n children?: ProfileNavigationItem[];\n icon?: LucideIcon;\n};\n\nexport interface CommonPlugin {\n initialize?: (\n context: ZudokuContext,\n ) => Promise<void | boolean> | void | boolean;\n getHead?: () => ReactElement | undefined;\n getMdxComponents?: () => MdxComponentsType;\n}\n\nexport type EventConsumerPlugin<Event extends ZudokuEvents = ZudokuEvents> = {\n events: { [K in keyof Event]: Event[K] };\n};\n\nexport const isEventConsumerPlugin = (\n obj: ZudokuPlugin,\n): obj is EventConsumerPlugin =>\n \"events\" in obj && typeof obj.events === \"object\";\n\nexport const isProfileMenuPlugin = (\n obj: ZudokuPlugin,\n): obj is ProfileMenuPlugin =>\n \"getProfileMenuItems\" in obj && typeof obj.getProfileMenuItems === \"function\";\n\nexport const isNavigationPlugin = (\n obj: ZudokuPlugin,\n): obj is NavigationPlugin =>\n \"getRoutes\" in obj && typeof obj.getRoutes === \"function\";\n\nexport const isSearchPlugin = (\n obj: ZudokuPlugin,\n): obj is SearchProviderPlugin =>\n \"renderSearch\" in obj && typeof obj.renderSearch === \"function\";\n\nexport const needsInitialization = (obj: ZudokuPlugin): obj is CommonPlugin =>\n \"initialize\" in obj && typeof obj.initialize === \"function\";\n\nexport const hasHead = (obj: ZudokuPlugin): obj is CommonPlugin =>\n \"getHead\" in obj && typeof obj.getHead === \"function\";\n\nexport const isMdxProviderPlugin = (obj: ZudokuPlugin): obj is CommonPlugin =>\n \"getMdxComponents\" in obj && typeof obj.getMdxComponents === \"function\";\n\nexport const isApiIdentityPlugin = (\n obj: ZudokuPlugin,\n): obj is ApiIdentityPlugin =>\n \"getIdentities\" in obj && typeof obj.getIdentities === \"function\";\n"],"names":["createApiIdentityPlugin","plugin","createProfileMenuPlugin","isEventConsumerPlugin","obj","isProfileMenuPlugin","isNavigationPlugin","isSearchPlugin","needsInitialization","hasHead","isMdxProviderPlugin","isApiIdentityPlugin"],"mappings":"AA0Ba,MAAAA,IAA0B,CACrCC,MACsBA,GAEXC,IAA0B,CACrCD,MACsBA,GAsCXE,IAAwB,CACnCC,MAEA,YAAYA,KAAO,OAAOA,EAAI,UAAW,UAE9BC,IAAsB,CACjCD,MAEA,yBAAyBA,KAAO,OAAOA,EAAI,uBAAwB,YAExDE,IAAqB,CAChCF,MAEA,eAAeA,KAAO,OAAOA,EAAI,aAAc,YAEpCG,IAAiB,CAC5BH,MAEA,kBAAkBA,KAAO,OAAOA,EAAI,gBAAiB,YAE1CI,IAAsB,CAACJ,MAClC,gBAAgBA,KAAO,OAAOA,EAAI,cAAe,YAEtCK,IAAU,CAACL,MACtB,aAAaA,KAAO,OAAOA,EAAI,WAAY,YAEhCM,IAAsB,CAACN,MAClC,sBAAsBA,KAAO,OAAOA,EAAI,oBAAqB,YAElDO,IAAsB,CACjCP,MAEA,mBAAmBA,KAAO,OAAOA,EAAI,iBAAkB;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.32.6",
3
+ "version": "0.33.0",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -35,6 +35,10 @@
35
35
  "import": "./lib/ui/*.js",
36
36
  "types": "./dist/lib/ui/*.d.ts"
37
37
  },
38
+ "./hooks": {
39
+ "import": "./lib/hooks/index.js",
40
+ "types": "./dist/lib/hooks/index.d.ts"
41
+ },
38
42
  "./client": {
39
43
  "types": "./client.d.ts"
40
44
  },
@@ -168,7 +172,7 @@
168
172
  "glob": "11.0.1",
169
173
  "graphql": "16.10.0",
170
174
  "graphql-type-json": "0.3.2",
171
- "graphql-yoga": "5.11.0",
175
+ "graphql-yoga": "5.12.0",
172
176
  "gray-matter": "4.0.3",
173
177
  "hast-util-to-jsx-runtime": "^2.3.2",
174
178
  "hast-util-to-string": "3.0.1",
@@ -177,6 +181,7 @@
177
181
  "loglevel": "1.9.2",
178
182
  "lru-cache": "11.0.2",
179
183
  "lucide-react": "0.475.0",
184
+ "nanoevents": "^9.1.0",
180
185
  "next-themes": "0.4.4",
181
186
  "oauth4webapi": "2.17.0",
182
187
  "object-hash": "3.0.0",
@@ -221,11 +226,13 @@
221
226
  "yargs": "17.7.2",
222
227
  "zod": "3.24.2",
223
228
  "zod-validation-error": "3.4.0",
224
- "zustand": "5.0.3"
229
+ "zustand": "5.0.3",
230
+ "esm-loader-css": "^1.0.3"
225
231
  },
226
232
  "devDependencies": {
227
233
  "@graphql-codegen/cli": "5.0.5",
228
234
  "@graphql-codegen/client-preset": "4.6.2",
235
+ "@testing-library/react": "16.2.0",
229
236
  "@types/estree": "1.0.6",
230
237
  "@types/express": "5.0.0",
231
238
  "@types/har-format": "1.2.16",
@@ -233,13 +240,14 @@
233
240
  "@types/json-schema": "7.0.15",
234
241
  "@types/mdast": "4.0.4",
235
242
  "@types/mdx": "2.0.13",
236
- "@types/node": "20.16.11",
243
+ "@types/node": "22.13.5",
237
244
  "@types/object-hash": "3.0.6",
238
245
  "@types/react-is": "19.0.0",
239
246
  "@types/semver": "7.5.8",
240
247
  "@types/unist": "^3.0.3",
241
248
  "@types/yargs": "17.0.33",
242
249
  "@vitest/coverage-v8": "3.0.5",
250
+ "happy-dom": "17.1.1",
243
251
  "mdast-util-mdx": "3.0.0",
244
252
  "react": "19.0.0",
245
253
  "react-dom": "19.0.0",
package/src/app/main.tsx CHANGED
@@ -81,7 +81,7 @@ export const getRoutesByOptions = (
81
81
  enableStatusPages = false,
82
82
  ) => {
83
83
  const allPlugins = [
84
- ...(options.plugins ? options.plugins : []),
84
+ ...(options.plugins ?? []),
85
85
  ...(options.authentication?.getAuthenticationPlugin
86
86
  ? [options.authentication.getAuthenticationPlugin()]
87
87
  : []),
@@ -1,5 +1,5 @@
1
- import { Auth0AuthenticationConfig } from "../../../config/config.js";
2
- import { AuthenticationProviderInitializer } from "../authentication.js";
1
+ import { type Auth0AuthenticationConfig } from "../../../config/config.js";
2
+ import { type AuthenticationProviderInitializer } from "../authentication.js";
3
3
  import { useAuthState } from "../state.js";
4
4
  import { OpenIDAuthenticationProvider } from "./openid.js";
5
5
 
@@ -11,6 +11,7 @@ class Auth0AuthenticationProvider extends OpenIDAuthenticationProvider {
11
11
  issuer: `https://${config.domain}`,
12
12
  clientId: config.clientId,
13
13
  audience: config.audience,
14
+ scopes: config.scopes,
14
15
  });
15
16
  }
16
17
 
@@ -1,16 +1,16 @@
1
1
  import logger from "loglevel";
2
2
  import * as oauth from "oauth4webapi";
3
- import { OpenIDAuthenticationConfig } from "../../../config/config.js";
3
+ import { type OpenIDAuthenticationConfig } from "../../../config/config.js";
4
4
  import { ClientOnly } from "../../components/ClientOnly.js";
5
5
  import { joinUrl } from "../../util/joinUrl.js";
6
6
  import {
7
- AuthenticationProvider,
8
- AuthenticationProviderInitializer,
7
+ type AuthenticationProvider,
8
+ type AuthenticationProviderInitializer,
9
9
  } from "../authentication.js";
10
10
  import { AuthenticationPlugin } from "../AuthenticationPlugin.js";
11
11
  import { CallbackHandler } from "../components/CallbackHandler.js";
12
12
  import { AuthorizationError, OAuthAuthorizationError } from "../errors.js";
13
- import { useAuthState, UserProfile } from "../state.js";
13
+ import { useAuthState, type UserProfile } from "../state.js";
14
14
 
15
15
  const CODE_VERIFIER_KEY = "code-verifier";
16
16
 
@@ -60,6 +60,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
60
60
  private readonly redirectToAfterSignIn: string;
61
61
  private readonly redirectToAfterSignOut: string;
62
62
  private readonly audience?: string;
63
+ private readonly scopes: string[];
63
64
 
64
65
  constructor({
65
66
  issuer,
@@ -69,6 +70,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
69
70
  redirectToAfterSignIn,
70
71
  redirectToAfterSignOut,
71
72
  basePath,
73
+ scopes,
72
74
  }: OpenIDAuthenticationConfig) {
73
75
  this.client = {
74
76
  client_id: clientId,
@@ -77,6 +79,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
77
79
  this.audience = audience;
78
80
  this.issuer = issuer;
79
81
  this.callbackUrlPath = joinUrl(basePath, "/oauth/callback");
82
+ this.scopes = scopes ?? ["openid", "profile", "email"];
80
83
 
81
84
  const root = joinUrl(basePath, "/");
82
85
 
@@ -178,7 +181,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
178
181
  authorizationUrl.searchParams.set("client_id", this.client.client_id);
179
182
  authorizationUrl.searchParams.set("redirect_uri", redirectUrl.toString());
180
183
  authorizationUrl.searchParams.set("response_type", "code");
181
- authorizationUrl.searchParams.set("scope", "openid profile email");
184
+ authorizationUrl.searchParams.set("scope", this.scopes.join(" "));
182
185
  authorizationUrl.searchParams.set("code_challenge", codeChallenge);
183
186
  authorizationUrl.searchParams.set(
184
187
  "code_challenge_method",
@@ -22,7 +22,7 @@ export const ThemeSwitch = () => {
22
22
  <div
23
23
  className={cn(
24
24
  "border border-transparent rounded-full p-0.5 [&>svg>circle]:transition-colors [&>svg>path]:transition-transform transition-all [&>svg>path]:duration-200 [&>svg>circle]:duration-500 [&>svg>circle]:fill-transparent",
25
- resolvedTheme === "light" && "border-border bg-muted/50",
25
+ resolvedTheme === "light" && "border-border bg-muted",
26
26
  resolvedTheme === "dark" &&
27
27
  "group-hover:[&>svg>path]:scale-110 group-hover:[&>svg>path]:-translate-x-[1px] group-hover:[&>svg>path]:-translate-y-[1px] group-hover:rotate-[15deg] ",
28
28
  )}
@@ -2,7 +2,6 @@ import { MDXProvider } from "@mdx-js/react";
2
2
  import { Helmet } from "@zudoku/react-helmet-async";
3
3
  import { ThemeProvider } from "next-themes";
4
4
  import {
5
- Fragment,
6
5
  memo,
7
6
  type PropsWithChildren,
8
7
  useContext,
@@ -13,7 +12,10 @@ import {
13
12
  import { ErrorBoundary } from "react-error-boundary";
14
13
  import { Outlet, useNavigation } from "react-router";
15
14
  import { hasHead, isMdxProviderPlugin } from "../core/plugins.js";
16
- import { ZudokuContext, ZudokuContextOptions } from "../core/ZudokuContext.js";
15
+ import {
16
+ ZudokuContext,
17
+ type ZudokuContextOptions,
18
+ } from "../core/ZudokuContext.js";
17
19
  import { TopLevelError } from "../errors/TopLevelError.js";
18
20
  import { StaggeredRenderContext } from "../plugins/openapi/StaggeredRender.js";
19
21
  import { MdxComponents } from "../util/MdxComponents.js";
@@ -22,6 +24,7 @@ import {
22
24
  ComponentsProvider,
23
25
  DEFAULT_COMPONENTS,
24
26
  } from "./context/ComponentsContext.js";
27
+ import { RouterEventsEmitter } from "./context/RouterEventsEmitter.js";
25
28
  import { ViewportAnchorProvider } from "./context/ViewportAnchorContext.js";
26
29
  import { ZudokuProvider } from "./context/ZudokuProvider.js";
27
30
  import { SlotletProvider } from "./SlotletProvider.js";
@@ -67,15 +70,16 @@ const ZudokoInner = memo(
67
70
  const [zudokuContext] = useState(() => new ZudokuContext(props));
68
71
 
69
72
  const heads = props.plugins
70
- ?.filter(hasHead)
73
+ ?.flatMap((plugin) => (hasHead(plugin) ? (plugin.getHead?.() ?? []) : []))
71
74
  // eslint-disable-next-line react/no-array-index-key
72
- .map((plugin, i) => <Fragment key={i}>{plugin.getHead?.()}</Fragment>);
75
+ .map((entry, i) => <Helmet key={i}>{entry}</Helmet>);
73
76
 
74
77
  return (
75
78
  <>
76
- <Helmet>{heads}</Helmet>
79
+ {heads}
77
80
  <StaggeredRenderContext.Provider value={staggeredValue}>
78
81
  <ZudokuProvider context={zudokuContext}>
82
+ <RouterEventsEmitter />
79
83
  <MDXProvider components={mdxComponents}>
80
84
  <ThemeProvider attribute="class" disableTransitionOnChange>
81
85
  <ComponentsProvider value={components}>
@@ -0,0 +1,19 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useLocation, type Location } from "react-router";
3
+ import { useZudoku } from "./ZudokuContext.js";
4
+
5
+ export const RouterEventsEmitter = () => {
6
+ const location = useLocation();
7
+ const zudoku = useZudoku();
8
+ const previousLocation = useRef<Location | undefined>(undefined);
9
+
10
+ useEffect(() => {
11
+ zudoku.emitEvent("location", {
12
+ from: previousLocation.current,
13
+ to: location,
14
+ });
15
+ previousLocation.current = location;
16
+ }, [zudoku, location]);
17
+
18
+ return null;
19
+ };
@@ -1,4 +1,6 @@
1
+ import { createNanoEvents } from "nanoevents";
1
2
  import { ReactNode } from "react";
3
+ import { Location } from "react-router";
2
4
  import { TopNavigationItem } from "../../config/validators/common.js";
3
5
  import type { SidebarConfig } from "../../config/validators/SidebarSchema.js";
4
6
  import { type AuthenticationProvider } from "../authentication/authentication.js";
@@ -6,14 +8,20 @@ import type { ComponentsContextType } from "../components/context/ComponentsCont
6
8
  import { Slotlets } from "../components/SlotletProvider.js";
7
9
  import { joinPath } from "../util/joinPath.js";
8
10
  import type { MdxComponentsType } from "../util/MdxComponents.js";
11
+ import { objectEntries } from "../util/objectEntries.js";
9
12
  import {
10
13
  isApiIdentityPlugin,
14
+ isEventConsumerPlugin,
11
15
  isNavigationPlugin,
12
16
  type NavigationPlugin,
13
17
  needsInitialization,
14
18
  type ZudokuPlugin,
15
19
  } from "./plugins.js";
16
20
 
21
+ export interface ZudokuEvents {
22
+ location: (event: { from?: Location; to: Location }) => void;
23
+ }
24
+
17
25
  export interface ApiIdentity {
18
26
  authorizeRequest: (request: Request) => Promise<Request> | Request;
19
27
  label: string;
@@ -76,6 +84,7 @@ export class ZudokuContext {
76
84
  public page: ZudokuContextOptions["page"];
77
85
  public authentication?: ZudokuContextOptions["authentication"];
78
86
  private readonly navigationPlugins: NavigationPlugin[];
87
+ private emitter = createNanoEvents<ZudokuEvents>();
79
88
 
80
89
  constructor(public readonly options: ZudokuContextOptions) {
81
90
  this.plugins = options.plugins ?? [];
@@ -85,6 +94,14 @@ export class ZudokuContext {
85
94
  this.authentication = options.authentication;
86
95
  this.meta = options.metadata;
87
96
  this.page = options.page;
97
+
98
+ this.plugins.forEach((plugin) => {
99
+ if (!isEventConsumerPlugin(plugin)) return;
100
+
101
+ objectEntries(plugin.events).forEach(([event, handler]) => {
102
+ this.emitter.on(event, handler);
103
+ });
104
+ });
88
105
  }
89
106
 
90
107
  initialize = async (): Promise<void> => {
@@ -105,6 +122,20 @@ export class ZudokuContext {
105
122
  return keys.flat();
106
123
  };
107
124
 
125
+ addEventListener<E extends keyof ZudokuEvents>(
126
+ event: E,
127
+ callback: ZudokuEvents[E],
128
+ ) {
129
+ return this.emitter.on(event, callback);
130
+ }
131
+
132
+ emitEvent = <E extends keyof ZudokuEvents>(
133
+ event: E,
134
+ ...data: Parameters<ZudokuEvents[E]>
135
+ ) => {
136
+ return this.emitter.emit(event, ...data);
137
+ };
138
+
108
139
  getPluginSidebar = async (path: string) => {
109
140
  const navigations = await Promise.all(
110
141
  this.navigationPlugins.map((plugin) =>
@@ -3,14 +3,19 @@ import { type ReactElement } from "react";
3
3
  import { type RouteObject } from "react-router";
4
4
  import type { Sidebar } from "../../config/validators/SidebarSchema.js";
5
5
  import { MdxComponentsType } from "../util/MdxComponents.js";
6
- import { ZudokuContext, type ApiIdentity } from "./ZudokuContext.js";
6
+ import {
7
+ ZudokuContext,
8
+ ZudokuEvents,
9
+ type ApiIdentity,
10
+ } from "./ZudokuContext.js";
7
11
 
8
12
  export type ZudokuPlugin =
9
13
  | CommonPlugin
10
14
  | ProfileMenuPlugin
11
15
  | NavigationPlugin
12
16
  | ApiIdentityPlugin
13
- | SearchProviderPlugin;
17
+ | SearchProviderPlugin
18
+ | EventConsumerPlugin;
14
19
 
15
20
  export type { RouteObject };
16
21
 
@@ -59,6 +64,15 @@ export interface CommonPlugin {
59
64
  getMdxComponents?: () => MdxComponentsType;
60
65
  }
61
66
 
67
+ export type EventConsumerPlugin<Event extends ZudokuEvents = ZudokuEvents> = {
68
+ events: { [K in keyof Event]: Event[K] };
69
+ };
70
+
71
+ export const isEventConsumerPlugin = (
72
+ obj: ZudokuPlugin,
73
+ ): obj is EventConsumerPlugin =>
74
+ "events" in obj && typeof obj.events === "object";
75
+
62
76
  export const isProfileMenuPlugin = (
63
77
  obj: ZudokuPlugin,
64
78
  ): obj is ProfileMenuPlugin =>
@@ -0,0 +1,5 @@
1
+ import { useTheme as useThemeImport } from "next-themes";
2
+ import { useEvent as useEventImport } from "./useEvent.js";
3
+
4
+ export const useEvent = /*@__PURE__*/ useEventImport;
5
+ export const useTheme = /*@__PURE__*/ useThemeImport;
@@ -0,0 +1,149 @@
1
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2
+ import { act, renderHook, waitFor } from "@testing-library/react";
3
+ import { type PropsWithChildren } from "react";
4
+ import { type Location } from "react-router";
5
+ import { assertType, describe, expect, it, vi } from "vitest";
6
+ import { ZudokuProvider } from "../components/context/ZudokuProvider.js";
7
+ import { ZudokuContext, type ZudokuEvents } from "../core/ZudokuContext.js";
8
+ import { useEvent } from "./useEvent.js";
9
+
10
+ /**
11
+ * @vitest-environment happy-dom
12
+ */
13
+
14
+ const createTestContext = () => {
15
+ const context = new ZudokuContext({});
16
+ const queryClient = new QueryClient();
17
+ const wrapper = ({ children }: PropsWithChildren) => (
18
+ <QueryClientProvider client={queryClient}>
19
+ <ZudokuProvider context={context}>{children}</ZudokuProvider>
20
+ </QueryClientProvider>
21
+ );
22
+
23
+ return { context, wrapper };
24
+ };
25
+
26
+ const locationData = {
27
+ pathname: "/test",
28
+ hash: "",
29
+ search: "",
30
+ key: "",
31
+ state: {},
32
+ } satisfies Location;
33
+
34
+ describe("useEvent", () => {
35
+ it("returns latest event data without callback", async () => {
36
+ const { context, wrapper } = createTestContext();
37
+ const { result } = renderHook(() => useEvent("location"), { wrapper });
38
+
39
+ await act(() => Promise.resolve());
40
+ await act(async () => {
41
+ context.emitEvent("location", { to: locationData });
42
+ });
43
+
44
+ expect(result.current).toEqual([{ to: locationData }]);
45
+ });
46
+
47
+ it("transforms event data with callback", async () => {
48
+ const { context, wrapper } = createTestContext();
49
+ const { result } = renderHook(
50
+ () => useEvent("location", ({ to }) => to.pathname),
51
+ { wrapper },
52
+ );
53
+
54
+ await act(() => Promise.resolve());
55
+ await act(async () => {
56
+ context.emitEvent("location", { to: locationData });
57
+ });
58
+
59
+ await waitFor(() => expect(result.current).toEqual("/test"));
60
+ });
61
+
62
+ it("handles side effects without return value", async () => {
63
+ const { context, wrapper } = createTestContext();
64
+ const sideEffect = vi.fn();
65
+
66
+ const { result } = renderHook(
67
+ () =>
68
+ useEvent("location", (event) => {
69
+ sideEffect(event);
70
+ }),
71
+ { wrapper },
72
+ );
73
+
74
+ await act(() => Promise.resolve());
75
+ await act(async () => {
76
+ context.emitEvent("location", { to: locationData });
77
+ });
78
+
79
+ expect(result.current).toBeUndefined();
80
+ expect(sideEffect).toHaveBeenCalledWith({ to: locationData });
81
+ expect(sideEffect).toHaveBeenCalledTimes(1);
82
+ });
83
+
84
+ it("removes event listener on cleanup", async () => {
85
+ const { context, wrapper } = createTestContext();
86
+ const sideEffect = vi.fn();
87
+
88
+ const { unmount } = renderHook(
89
+ () =>
90
+ useEvent("location", (event) => {
91
+ sideEffect(event);
92
+ }),
93
+ { wrapper },
94
+ );
95
+
96
+ await act(() => Promise.resolve());
97
+
98
+ // First event emission
99
+ await act(async () => {
100
+ context.emitEvent("location", { to: locationData });
101
+ });
102
+ expect(sideEffect).toHaveBeenCalledTimes(1);
103
+
104
+ // Unmount the hook
105
+ unmount();
106
+
107
+ // Second event emission after unmount
108
+ await act(async () => {
109
+ context.emitEvent("location", { to: locationData });
110
+ });
111
+
112
+ // The callback should not have been called again
113
+ expect(sideEffect).toHaveBeenCalledTimes(1);
114
+ });
115
+
116
+ describe("types", () => {
117
+ const { wrapper } = createTestContext();
118
+
119
+ it("infers event type when no callback is provided", () => {
120
+ const hook = renderHook(() => useEvent("location"), { wrapper });
121
+ assertType<Parameters<ZudokuEvents["location"]> | undefined>(
122
+ hook.result.current,
123
+ );
124
+ });
125
+
126
+ it("infers string type from pathname callback", () => {
127
+ const hook = renderHook(
128
+ () => useEvent("location", ({ to }) => to.pathname),
129
+ { wrapper },
130
+ );
131
+ assertType<string>(hook.result.current);
132
+ });
133
+
134
+ it("infers object type from object callback", () => {
135
+ const hook = renderHook(
136
+ () => useEvent("location", ({ to }) => ({ query: to.search })),
137
+ { wrapper },
138
+ );
139
+ assertType<{ query: string }>(hook.result.current);
140
+ });
141
+
142
+ it("infers void type from empty callback", () => {
143
+ const hook = renderHook(() => useEvent("location", () => {}), {
144
+ wrapper,
145
+ });
146
+ assertType<void>(hook.result.current);
147
+ });
148
+ });
149
+ });