vocs 2.0.17 → 2.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 (306) hide show
  1. package/dist/config.d.ts +1 -0
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +1 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/globals.d.ts +16 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal/config.d.ts +28 -0
  11. package/dist/internal/config.d.ts.map +1 -1
  12. package/dist/internal/config.js +10 -2
  13. package/dist/internal/config.js.map +1 -1
  14. package/dist/internal/llms.d.ts +21 -2
  15. package/dist/internal/llms.d.ts.map +1 -1
  16. package/dist/internal/llms.js +41 -1
  17. package/dist/internal/llms.js.map +1 -1
  18. package/dist/internal/markdown-negotiation.d.ts +36 -0
  19. package/dist/internal/markdown-negotiation.d.ts.map +1 -0
  20. package/dist/internal/markdown-negotiation.js +81 -0
  21. package/dist/internal/markdown-negotiation.js.map +1 -0
  22. package/dist/internal/markdown.d.ts.map +1 -1
  23. package/dist/internal/markdown.js +5 -0
  24. package/dist/internal/markdown.js.map +1 -1
  25. package/dist/internal/openapi/anchors.d.ts +25 -0
  26. package/dist/internal/openapi/anchors.d.ts.map +1 -0
  27. package/dist/internal/openapi/anchors.js +37 -0
  28. package/dist/internal/openapi/anchors.js.map +1 -0
  29. package/dist/internal/openapi/app.d.ts +89 -0
  30. package/dist/internal/openapi/app.d.ts.map +1 -0
  31. package/dist/internal/openapi/app.js +62 -0
  32. package/dist/internal/openapi/app.js.map +1 -0
  33. package/dist/internal/openapi/index.d.ts +8 -0
  34. package/dist/internal/openapi/index.d.ts.map +1 -0
  35. package/dist/internal/openapi/index.js +5 -0
  36. package/dist/internal/openapi/index.js.map +1 -0
  37. package/dist/internal/openapi/markdown.d.ts +42 -0
  38. package/dist/internal/openapi/markdown.d.ts.map +1 -0
  39. package/dist/internal/openapi/markdown.js +235 -0
  40. package/dist/internal/openapi/markdown.js.map +1 -0
  41. package/dist/internal/openapi/openapi.d.ts +187 -0
  42. package/dist/internal/openapi/openapi.d.ts.map +1 -0
  43. package/dist/internal/openapi/openapi.js +44 -0
  44. package/dist/internal/openapi/openapi.js.map +1 -0
  45. package/dist/internal/openapi/openrpc.d.ts +90 -0
  46. package/dist/internal/openapi/openrpc.d.ts.map +1 -0
  47. package/dist/internal/openapi/openrpc.js +213 -0
  48. package/dist/internal/openapi/openrpc.js.map +1 -0
  49. package/dist/internal/openapi/parser.d.ts +181 -0
  50. package/dist/internal/openapi/parser.d.ts.map +1 -0
  51. package/dist/internal/openapi/parser.js +329 -0
  52. package/dist/internal/openapi/parser.js.map +1 -0
  53. package/dist/internal/openapi/registry.d.ts +36 -0
  54. package/dist/internal/openapi/registry.d.ts.map +1 -0
  55. package/dist/internal/openapi/registry.js +79 -0
  56. package/dist/internal/openapi/registry.js.map +1 -0
  57. package/dist/internal/openapi/sample.d.ts +115 -0
  58. package/dist/internal/openapi/sample.d.ts.map +1 -0
  59. package/dist/internal/openapi/sample.js +434 -0
  60. package/dist/internal/openapi/sample.js.map +1 -0
  61. package/dist/internal/openapi/search.d.ts +19 -0
  62. package/dist/internal/openapi/search.d.ts.map +1 -0
  63. package/dist/internal/openapi/search.js +98 -0
  64. package/dist/internal/openapi/search.js.map +1 -0
  65. package/dist/internal/openapi/sidebar.d.ts +30 -0
  66. package/dist/internal/openapi/sidebar.d.ts.map +1 -0
  67. package/dist/internal/openapi/sidebar.js +67 -0
  68. package/dist/internal/openapi/sidebar.js.map +1 -0
  69. package/dist/internal/openapi/union.d.ts +36 -0
  70. package/dist/internal/openapi/union.d.ts.map +1 -0
  71. package/dist/internal/openapi/union.js +69 -0
  72. package/dist/internal/openapi/union.js.map +1 -0
  73. package/dist/internal/search.d.ts.map +1 -1
  74. package/dist/internal/search.js +20 -0
  75. package/dist/internal/search.js.map +1 -1
  76. package/dist/internal/vite-plugins.d.ts +12 -0
  77. package/dist/internal/vite-plugins.d.ts.map +1 -1
  78. package/dist/internal/vite-plugins.js +102 -11
  79. package/dist/internal/vite-plugins.js.map +1 -1
  80. package/dist/react/Badge.d.ts +2 -3
  81. package/dist/react/Badge.d.ts.map +1 -1
  82. package/dist/react/Layout.client.d.ts.map +1 -1
  83. package/dist/react/Layout.client.js +2 -2
  84. package/dist/react/Layout.client.js.map +1 -1
  85. package/dist/react/OpenApi.d.ts +6 -0
  86. package/dist/react/OpenApi.d.ts.map +1 -0
  87. package/dist/react/OpenApi.js +6 -0
  88. package/dist/react/OpenApi.js.map +1 -0
  89. package/dist/react/internal/CodeToHtml.client.d.ts +53 -2
  90. package/dist/react/internal/CodeToHtml.client.d.ts.map +1 -1
  91. package/dist/react/internal/CodeToHtml.client.js +154 -21
  92. package/dist/react/internal/CodeToHtml.client.js.map +1 -1
  93. package/dist/react/internal/Sidebar.d.ts.map +1 -1
  94. package/dist/react/internal/Sidebar.js +99 -2
  95. package/dist/react/internal/Sidebar.js.map +1 -1
  96. package/dist/react/internal/openapi/CodeSample.client.d.ts +21 -0
  97. package/dist/react/internal/openapi/CodeSample.client.d.ts.map +1 -0
  98. package/dist/react/internal/openapi/CodeSample.client.js +134 -0
  99. package/dist/react/internal/openapi/CodeSample.client.js.map +1 -0
  100. package/dist/react/internal/openapi/CollapsibleChildren.client.d.ts +17 -0
  101. package/dist/react/internal/openapi/CollapsibleChildren.client.d.ts.map +1 -0
  102. package/dist/react/internal/openapi/CollapsibleChildren.client.js +18 -0
  103. package/dist/react/internal/openapi/CollapsibleChildren.client.js.map +1 -0
  104. package/dist/react/internal/openapi/Disclosure.client.d.ts +28 -0
  105. package/dist/react/internal/openapi/Disclosure.client.d.ts.map +1 -0
  106. package/dist/react/internal/openapi/Disclosure.client.js +38 -0
  107. package/dist/react/internal/openapi/Disclosure.client.js.map +1 -0
  108. package/dist/react/internal/openapi/Endpoints.d.ts +26 -0
  109. package/dist/react/internal/openapi/Endpoints.d.ts.map +1 -0
  110. package/dist/react/internal/openapi/Endpoints.js +33 -0
  111. package/dist/react/internal/openapi/Endpoints.js.map +1 -0
  112. package/dist/react/internal/openapi/EndpointsView.d.ts +24 -0
  113. package/dist/react/internal/openapi/EndpointsView.d.ts.map +1 -0
  114. package/dist/react/internal/openapi/EndpointsView.js +26 -0
  115. package/dist/react/internal/openapi/EndpointsView.js.map +1 -0
  116. package/dist/react/internal/openapi/EnumValues.client.d.ts +14 -0
  117. package/dist/react/internal/openapi/EnumValues.client.d.ts.map +1 -0
  118. package/dist/react/internal/openapi/EnumValues.client.js +20 -0
  119. package/dist/react/internal/openapi/EnumValues.client.js.map +1 -0
  120. package/dist/react/internal/openapi/HeadingAnchor.d.ts +15 -0
  121. package/dist/react/internal/openapi/HeadingAnchor.d.ts.map +1 -0
  122. package/dist/react/internal/openapi/HeadingAnchor.js +12 -0
  123. package/dist/react/internal/openapi/HeadingAnchor.js.map +1 -0
  124. package/dist/react/internal/openapi/OpenApiPage.d.ts +79 -0
  125. package/dist/react/internal/openapi/OpenApiPage.d.ts.map +1 -0
  126. package/dist/react/internal/openapi/OpenApiPage.js +72 -0
  127. package/dist/react/internal/openapi/OpenApiPage.js.map +1 -0
  128. package/dist/react/internal/openapi/Operation.d.ts +25 -0
  129. package/dist/react/internal/openapi/Operation.d.ts.map +1 -0
  130. package/dist/react/internal/openapi/Operation.js +101 -0
  131. package/dist/react/internal/openapi/Operation.js.map +1 -0
  132. package/dist/react/internal/openapi/Playground.client.d.ts +33 -0
  133. package/dist/react/internal/openapi/Playground.client.d.ts.map +1 -0
  134. package/dist/react/internal/openapi/Playground.client.js +170 -0
  135. package/dist/react/internal/openapi/Playground.client.js.map +1 -0
  136. package/dist/react/internal/openapi/PropertyExample.client.d.ts +17 -0
  137. package/dist/react/internal/openapi/PropertyExample.client.d.ts.map +1 -0
  138. package/dist/react/internal/openapi/PropertyExample.client.js +21 -0
  139. package/dist/react/internal/openapi/PropertyExample.client.js.map +1 -0
  140. package/dist/react/internal/openapi/Reference.d.ts +55 -0
  141. package/dist/react/internal/openapi/Reference.d.ts.map +1 -0
  142. package/dist/react/internal/openapi/Reference.js +42 -0
  143. package/dist/react/internal/openapi/Reference.js.map +1 -0
  144. package/dist/react/internal/openapi/Schema.d.ts +110 -0
  145. package/dist/react/internal/openapi/Schema.d.ts.map +1 -0
  146. package/dist/react/internal/openapi/Schema.js +239 -0
  147. package/dist/react/internal/openapi/Schema.js.map +1 -0
  148. package/dist/react/internal/openapi/SchemaUnion.client.d.ts +25 -0
  149. package/dist/react/internal/openapi/SchemaUnion.client.d.ts.map +1 -0
  150. package/dist/react/internal/openapi/SchemaUnion.client.js +48 -0
  151. package/dist/react/internal/openapi/SchemaUnion.client.js.map +1 -0
  152. package/dist/react/internal/openapi/anchor-navigation.client.d.ts +47 -0
  153. package/dist/react/internal/openapi/anchor-navigation.client.d.ts.map +1 -0
  154. package/dist/react/internal/openapi/anchor-navigation.client.js +120 -0
  155. package/dist/react/internal/openapi/anchor-navigation.client.js.map +1 -0
  156. package/dist/react/internal/openapi/auth.d.ts +28 -0
  157. package/dist/react/internal/openapi/auth.d.ts.map +1 -0
  158. package/dist/react/internal/openapi/auth.js +75 -0
  159. package/dist/react/internal/openapi/auth.js.map +1 -0
  160. package/dist/react/useLayout.d.ts +2 -0
  161. package/dist/react/useLayout.d.ts.map +1 -1
  162. package/dist/react/useLayout.js +2 -0
  163. package/dist/react/useLayout.js.map +1 -1
  164. package/dist/server/handlers.d.ts +1 -1
  165. package/dist/server/handlers.d.ts.map +1 -1
  166. package/dist/server/handlers.js +26 -5
  167. package/dist/server/handlers.js.map +1 -1
  168. package/dist/server/og-assets.d.ts +5 -0
  169. package/dist/server/og-assets.d.ts.map +1 -0
  170. package/dist/server/og-assets.js +11 -0
  171. package/dist/server/og-assets.js.map +1 -0
  172. package/dist/server/openapi/assets.d.ts +33 -0
  173. package/dist/server/openapi/assets.d.ts.map +1 -0
  174. package/dist/server/openapi/assets.generated.d.ts +9 -0
  175. package/dist/server/openapi/assets.generated.d.ts.map +1 -0
  176. package/dist/server/openapi/assets.generated.js +1091 -0
  177. package/dist/server/openapi/assets.generated.js.map +1 -0
  178. package/dist/server/openapi/assets.js +32 -0
  179. package/dist/server/openapi/assets.js.map +1 -0
  180. package/dist/server/openapi/handler.d.ts +103 -0
  181. package/dist/server/openapi/handler.d.ts.map +1 -0
  182. package/dist/server/openapi/handler.js +198 -0
  183. package/dist/server/openapi/handler.js.map +1 -0
  184. package/dist/server/openapi/handler.test.d.ts +2 -0
  185. package/dist/server/openapi/handler.test.d.ts.map +1 -0
  186. package/dist/server/openapi/handler.test.js +203 -0
  187. package/dist/server/openapi/handler.test.js.map +1 -0
  188. package/dist/server/openapi/html.d.ts +16 -0
  189. package/dist/server/openapi/html.d.ts.map +1 -0
  190. package/dist/server/openapi/html.js +75 -0
  191. package/dist/server/openapi/html.js.map +1 -0
  192. package/dist/server/openapi/pages.d.ts +33 -0
  193. package/dist/server/openapi/pages.d.ts.map +1 -0
  194. package/dist/server/openapi/pages.js +130 -0
  195. package/dist/server/openapi/pages.js.map +1 -0
  196. package/dist/server/openapi/pages.test.d.ts +2 -0
  197. package/dist/server/openapi/pages.test.d.ts.map +1 -0
  198. package/dist/server/openapi/pages.test.js +94 -0
  199. package/dist/server/openapi/pages.test.js.map +1 -0
  200. package/dist/server/openapi/state.d.ts +42 -0
  201. package/dist/server/openapi/state.d.ts.map +1 -0
  202. package/dist/server/openapi/state.js +101 -0
  203. package/dist/server/openapi/state.js.map +1 -0
  204. package/dist/styles/index.css +16 -0
  205. package/dist/styles/markdown.css +9 -7
  206. package/dist/styles/openapi-playground.css +80 -0
  207. package/dist/styles/openapi.css +660 -0
  208. package/dist/vite.d.ts.map +1 -1
  209. package/dist/vite.js +1 -0
  210. package/dist/vite.js.map +1 -1
  211. package/dist/waku/internal/middleware/md-router.d.ts +0 -4
  212. package/dist/waku/internal/middleware/md-router.d.ts.map +1 -1
  213. package/dist/waku/internal/middleware/md-router.js +3 -48
  214. package/dist/waku/internal/middleware/md-router.js.map +1 -1
  215. package/dist/waku/internal/patches/adapters/vercel-build-enhancer.js +1 -1
  216. package/dist/waku/internal/patches/adapters/vercel-build-enhancer.js.map +1 -1
  217. package/dist/waku/internal/patches/router.d.ts.map +1 -1
  218. package/dist/waku/internal/patches/router.js +114 -1
  219. package/dist/waku/internal/patches/router.js.map +1 -1
  220. package/package.json +5 -1
  221. package/src/config.ts +1 -0
  222. package/src/globals.d.ts +16 -0
  223. package/src/index.ts +1 -0
  224. package/src/internal/config.ts +40 -1
  225. package/src/internal/llms.ts +51 -1
  226. package/src/internal/markdown-negotiation.test.ts +42 -0
  227. package/src/internal/markdown-negotiation.ts +95 -0
  228. package/src/internal/markdown.ts +5 -0
  229. package/src/internal/openapi/anchors.ts +44 -0
  230. package/src/internal/openapi/app.ts +127 -0
  231. package/src/internal/openapi/index.ts +24 -0
  232. package/src/internal/openapi/markdown.test.ts +115 -0
  233. package/src/internal/openapi/markdown.ts +275 -0
  234. package/src/internal/openapi/openapi.ts +212 -0
  235. package/src/internal/openapi/openrpc.test.ts +239 -0
  236. package/src/internal/openapi/openrpc.ts +295 -0
  237. package/src/internal/openapi/parser.test.ts +203 -0
  238. package/src/internal/openapi/parser.ts +613 -0
  239. package/src/internal/openapi/registry.test.ts +89 -0
  240. package/src/internal/openapi/registry.ts +89 -0
  241. package/src/internal/openapi/sample.test.ts +283 -0
  242. package/src/internal/openapi/sample.ts +562 -0
  243. package/src/internal/openapi/search.test.ts +62 -0
  244. package/src/internal/openapi/search.ts +108 -0
  245. package/src/internal/openapi/sidebar.test.ts +131 -0
  246. package/src/internal/openapi/sidebar.ts +94 -0
  247. package/src/internal/openapi/union.test.ts +51 -0
  248. package/src/internal/openapi/union.ts +74 -0
  249. package/src/internal/search.ts +20 -0
  250. package/src/internal/test/virtual-config.stub.ts +14 -0
  251. package/src/internal/vite-plugins.ts +106 -11
  252. package/src/openapi-app/App.tsx +64 -0
  253. package/src/openapi-app/blocks.tsx +33 -0
  254. package/src/openapi-app/client.tsx +25 -0
  255. package/src/openapi-app/links.test.ts +84 -0
  256. package/src/openapi-app/links.ts +66 -0
  257. package/src/openapi-app/payload.ts +20 -0
  258. package/src/openapi-app/virtual/config.ts +7 -0
  259. package/src/openapi-app/virtual/group-icons.ts +2 -0
  260. package/src/openapi-app/virtual/langs.ts +6 -0
  261. package/src/openapi-app/virtual/openapi.ts +10 -0
  262. package/src/openapi-app/virtual/search-index.ts +21 -0
  263. package/src/openapi-app/virtual/slots.ts +4 -0
  264. package/src/openapi-app/virtual/user-styles.ts +2 -0
  265. package/src/openapi-app/waku.tsx +154 -0
  266. package/src/react/Badge.tsx +2 -3
  267. package/src/react/Layout.client.tsx +17 -4
  268. package/src/react/OpenApi.tsx +5 -0
  269. package/src/react/internal/CodeToHtml.client.tsx +283 -22
  270. package/src/react/internal/Sidebar.tsx +126 -22
  271. package/src/react/internal/openapi/CodeSample.client.tsx +294 -0
  272. package/src/react/internal/openapi/CollapsibleChildren.client.tsx +41 -0
  273. package/src/react/internal/openapi/Disclosure.client.tsx +67 -0
  274. package/src/react/internal/openapi/Endpoints.tsx +58 -0
  275. package/src/react/internal/openapi/EndpointsView.tsx +76 -0
  276. package/src/react/internal/openapi/EnumValues.client.tsx +49 -0
  277. package/src/react/internal/openapi/HeadingAnchor.tsx +28 -0
  278. package/src/react/internal/openapi/OpenApiPage.tsx +173 -0
  279. package/src/react/internal/openapi/Operation.test.tsx +101 -0
  280. package/src/react/internal/openapi/Operation.tsx +335 -0
  281. package/src/react/internal/openapi/Playground.client.tsx +234 -0
  282. package/src/react/internal/openapi/PropertyExample.client.tsx +55 -0
  283. package/src/react/internal/openapi/Reference.tsx +120 -0
  284. package/src/react/internal/openapi/Schema.tsx +467 -0
  285. package/src/react/internal/openapi/SchemaUnion.client.tsx +123 -0
  286. package/src/react/internal/openapi/anchor-navigation.client.ts +154 -0
  287. package/src/react/internal/openapi/auth.ts +69 -0
  288. package/src/react/useLayout.ts +4 -0
  289. package/src/server/handlers.ts +31 -6
  290. package/src/server/og-assets.ts +14 -0
  291. package/src/server/openapi/assets.generated.ts +1093 -0
  292. package/src/server/openapi/assets.ts +57 -0
  293. package/src/server/openapi/handler.test.ts +244 -0
  294. package/src/server/openapi/handler.ts +277 -0
  295. package/src/server/openapi/html.ts +84 -0
  296. package/src/server/openapi/pages.test.ts +111 -0
  297. package/src/server/openapi/pages.ts +153 -0
  298. package/src/server/openapi/state.ts +136 -0
  299. package/src/styles/index.css +16 -0
  300. package/src/styles/markdown.css +9 -7
  301. package/src/styles/openapi-playground.css +80 -0
  302. package/src/styles/openapi.css +660 -0
  303. package/src/vite.ts +1 -0
  304. package/src/waku/internal/middleware/md-router.ts +8 -52
  305. package/src/waku/internal/patches/adapters/vercel-build-enhancer.ts +1 -1
  306. package/src/waku/internal/patches/router.ts +131 -1
@@ -0,0 +1,294 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useMemo, useState } from 'react'
4
+ import LucideCheck from '~icons/lucide/check'
5
+ import LucideChevronDown from '~icons/lucide/chevron-down'
6
+ import LucideChevronUp from '~icons/lucide/chevron-up'
7
+ import LucideClipboard from '~icons/lucide/clipboard'
8
+ import { schemaPropertyId } from '../../../internal/openapi/anchors.js'
9
+ import type * as OpenApi from '../../../internal/openapi/index.js'
10
+ import { CodeToHtml } from '../CodeToHtml.client.js'
11
+ import { registerSampleAnchors, revealAnchor } from './anchor-navigation.client.js'
12
+
13
+ /**
14
+ * The sticky right-hand panel for an operation: a request code sample with a
15
+ * language selector, an action slot (the `Test Request` button), and the
16
+ * response examples grouped by status code.
17
+ *
18
+ * Snippets are generated server-side with `@scalar/snippetz`; this component
19
+ * only handles tab selection and defers highlighting to Vocs's own Shiki
20
+ * highlighter via `CodeToHtml`. All styling lives in `openapi.css` keyed on the
21
+ * `data-v-openapi-sample*` attributes below.
22
+ */
23
+ export function CodeSample(props: CodeSample.Props) {
24
+ const { samples, responses, action } = props
25
+ const [sampleId, setSampleId] = useState(samples[0]?.id)
26
+ const [status, setStatus] = useState(responses[0]?.status)
27
+ // When a request has many query params, the extras collapse by default.
28
+ const [queryExpanded, setQueryExpanded] = useState(false)
29
+
30
+ const sample = samples.find((entry) => entry.id === sampleId) ?? samples[0]
31
+ const response = responses.find((entry) => entry.status === status) ?? responses[0]
32
+
33
+ // Map each response to the set of schema anchor ids its example lines target,
34
+ // so a left-hand "Example" click can switch to the owning response tab before
35
+ // the matching line is revealed (see `revealResponseLine`).
36
+ const responseAnchors = useMemo(
37
+ () =>
38
+ responses.map((entry) => ({
39
+ status: entry.status,
40
+ ids: new Set(
41
+ entry.linePaths
42
+ .map((path) => (path ? schemaPropertyId(entry.idBase, path) : undefined))
43
+ .filter((value): value is string => Boolean(value)),
44
+ ),
45
+ })),
46
+ [responses],
47
+ )
48
+
49
+ // Request sample anchor ids (path + query parameters). Path params are inline
50
+ // spans; query params are whole lines. Both share the operation-scoped id used
51
+ // by the left-hand parameter rows, so a left "Example" can reveal them too.
52
+ // Query params beyond the collapse threshold only render once expanded, so we
53
+ // track which ids are hidden to expand the snippet before revealing them.
54
+ const requestAnchors = useMemo(() => {
55
+ const ids = new Set<string>()
56
+ const collapsedIds = new Set<string>()
57
+ let hasCollapsed = false
58
+ for (const entry of samples) {
59
+ for (const anchor of entry.anchors) ids.add(anchor.id)
60
+ for (const id of entry.lineAnchors) if (id) ids.add(id)
61
+ if (entry.collapsed) {
62
+ hasCollapsed = true
63
+ for (const anchor of entry.collapsed.anchors) collapsedIds.add(anchor.id)
64
+ for (const id of entry.collapsed.lineAnchors) if (id) collapsedIds.add(id)
65
+ }
66
+ }
67
+ const hiddenIds = hasCollapsed
68
+ ? new Set([...ids].filter((id) => !collapsedIds.has(id)))
69
+ : new Set<string>()
70
+ return { ids, hiddenIds }
71
+ }, [samples])
72
+
73
+ useEffect(
74
+ () =>
75
+ registerSampleAnchors({
76
+ has: (id) =>
77
+ requestAnchors.ids.has(id) || responseAnchors.some((entry) => entry.ids.has(id)),
78
+ select: (id) => {
79
+ const match = responseAnchors.find((entry) => entry.ids.has(id))
80
+ if (match) {
81
+ setStatus(match.status)
82
+ return
83
+ }
84
+ if (requestAnchors.hiddenIds.has(id)) setQueryExpanded(true)
85
+ },
86
+ }),
87
+ [responseAnchors, requestAnchors],
88
+ )
89
+
90
+ // Use the collapsed snippet variant (first few query params) unless expanded.
91
+ const view = sample?.collapsed && !queryExpanded ? sample.collapsed : sample
92
+
93
+ return (
94
+ <div data-v-openapi-sample>
95
+ {sample && view && (
96
+ <div data-v-openapi-sample-request>
97
+ <div data-v-openapi-sample-header>
98
+ <LanguageDropdown
99
+ samples={samples}
100
+ selected={sample}
101
+ onSelect={(id) => setSampleId(id)}
102
+ />
103
+ <div data-v-openapi-sample-actions>
104
+ <CopyButton code={sample.code} />
105
+ {action}
106
+ </div>
107
+ </div>
108
+ {/* biome-ignore lint/a11y/useKeyWithClickEvents: param spans are also reachable via copyable property anchors */}
109
+ {/* biome-ignore lint/a11y/noStaticElementInteractions: progressive enhancement over already-linkable property anchors */}
110
+ <div data-v-openapi-sample-request-body onClick={onAnchorClick}>
111
+ <CodeToHtml
112
+ code={view.display}
113
+ lang={sample.lang}
114
+ shrinkIndent={false}
115
+ anchorRanges={view.anchors}
116
+ colorRanges={view.colorRanges}
117
+ lineAnchors={view.lineAnchors}
118
+ />
119
+ </div>
120
+ {sample.collapsed && (
121
+ <button
122
+ type="button"
123
+ data-v-openapi-sample-more
124
+ onClick={() => setQueryExpanded((value) => !value)}
125
+ >
126
+ {queryExpanded ? (
127
+ <>
128
+ <LucideChevronUp data-v-openapi-sample-more-icon />
129
+ Show less
130
+ </>
131
+ ) : (
132
+ <>
133
+ <LucideChevronDown data-v-openapi-sample-more-icon />
134
+ Show {sample.hiddenQueryCount} more
135
+ </>
136
+ )}
137
+ </button>
138
+ )}
139
+ </div>
140
+ )}
141
+
142
+ {response && (
143
+ <div data-v-openapi-sample-response>
144
+ <div data-v-openapi-sample-tabs>
145
+ {responses.map((entry) => (
146
+ <Tab
147
+ key={entry.status}
148
+ active={entry.status === response.status}
149
+ onClick={() => setStatus(entry.status)}
150
+ >
151
+ {entry.status}
152
+ </Tab>
153
+ ))}
154
+ </div>
155
+ {/* biome-ignore lint/a11y/useKeyWithClickEvents: rows are also reachable via copyable property anchors */}
156
+ {/* biome-ignore lint/a11y/noStaticElementInteractions: progressive enhancement over already-linkable property anchors */}
157
+ <div data-v-openapi-sample-response-body onClick={onAnchorClick}>
158
+ <CodeToHtml
159
+ code={response.code}
160
+ lang={response.lang}
161
+ shrinkIndent={false}
162
+ dimRanges={response.placeholders}
163
+ lineAnchors={response.linePaths.map((path) =>
164
+ path ? schemaPropertyId(response.idBase, path) : undefined,
165
+ )}
166
+ />
167
+ </div>
168
+ </div>
169
+ )}
170
+ </div>
171
+ )
172
+ }
173
+
174
+ /**
175
+ * Navigates to the schema row matching a clicked element carrying a
176
+ * `data-anchor` id. Used for both response example lines (per-line anchors) and
177
+ * request sample path/query parameter spans (per-range anchors).
178
+ */
179
+ function onAnchorClick(event: React.MouseEvent<HTMLDivElement>) {
180
+ // Don't hijack text selection.
181
+ if (window.getSelection()?.toString()) return
182
+ if (!(event.target instanceof Element)) return
183
+ const line = event.target.closest<HTMLElement>('[data-anchor]')
184
+ if (!line || !event.currentTarget.contains(line)) return
185
+ const anchor = line.dataset['anchor']
186
+ if (!anchor) return
187
+ event.preventDefault()
188
+ void revealAnchor(anchor)
189
+ }
190
+
191
+ /** Copies the current request sample's source to the clipboard. */
192
+ function CopyButton(props: { code: string }) {
193
+ const { code } = props
194
+ const [copied, setCopied] = useState(false)
195
+
196
+ useEffect(() => {
197
+ if (!copied) return
198
+ const timeout = setTimeout(() => setCopied(false), 1000)
199
+ return () => clearTimeout(timeout)
200
+ }, [copied])
201
+
202
+ return (
203
+ <button
204
+ type="button"
205
+ onClick={() => {
206
+ navigator.clipboard?.writeText(code).then(
207
+ () => setCopied(true),
208
+ () => {},
209
+ )
210
+ }}
211
+ data-v-openapi-action
212
+ >
213
+ {copied ? (
214
+ <LucideCheck data-v-openapi-action-icon data-copied />
215
+ ) : (
216
+ <LucideClipboard data-v-openapi-action-icon />
217
+ )}
218
+ Copy
219
+ </button>
220
+ )
221
+ }
222
+
223
+ /**
224
+ * Language selector for the request sample, rendered as a dropdown (defaulting
225
+ * to the first sample, i.e. cURL) rather than a row of tabs.
226
+ */
227
+ function LanguageDropdown(props: {
228
+ samples: OpenApi.CodeSample[]
229
+ selected: OpenApi.CodeSample
230
+ onSelect: (id: string) => void
231
+ }) {
232
+ const { samples, selected, onSelect } = props
233
+ const [open, setOpen] = useState(false)
234
+ if (samples.length <= 1) return <span data-v-openapi-lang-label>{selected.label}</span>
235
+ return (
236
+ <div data-v-openapi-lang>
237
+ <button type="button" onClick={() => setOpen((value) => !value)} data-v-openapi-lang-trigger>
238
+ {selected.label}
239
+ <LucideChevronDown data-v-openapi-lang-chevron />
240
+ </button>
241
+ {open && (
242
+ <>
243
+ <button
244
+ type="button"
245
+ aria-label="Close"
246
+ tabIndex={-1}
247
+ data-v-openapi-dropdown-backdrop
248
+ onClick={() => setOpen(false)}
249
+ />
250
+ <ul data-v-openapi-lang-menu>
251
+ {samples.map((entry) => (
252
+ <li key={entry.id}>
253
+ <button
254
+ type="button"
255
+ onClick={() => {
256
+ onSelect(entry.id)
257
+ setOpen(false)
258
+ }}
259
+ data-v-openapi-lang-option
260
+ data-selected={entry.id === selected.id || undefined}
261
+ >
262
+ <LucideCheck data-v-openapi-lang-option-check />
263
+ <span data-v-openapi-lang-option-label>{entry.label}</span>
264
+ </button>
265
+ </li>
266
+ ))}
267
+ </ul>
268
+ </>
269
+ )}
270
+ </div>
271
+ )
272
+ }
273
+
274
+ function Tab(props: { active: boolean; onClick: () => void; children: React.ReactNode }) {
275
+ return (
276
+ <button
277
+ type="button"
278
+ onClick={props.onClick}
279
+ data-v-openapi-tab
280
+ data-active={props.active || undefined}
281
+ >
282
+ {props.children}
283
+ </button>
284
+ )
285
+ }
286
+
287
+ export declare namespace CodeSample {
288
+ type Props = {
289
+ samples: OpenApi.CodeSample[]
290
+ responses: OpenApi.ResponseSample[]
291
+ /** Action slot rendered between the request and response (the test button). */
292
+ action?: React.ReactNode
293
+ }
294
+ }
@@ -0,0 +1,41 @@
1
+ 'use client'
2
+
3
+ import LucidePlus from '~icons/lucide/plus'
4
+ import LucideX from '~icons/lucide/x'
5
+ import { Disclosure } from './Disclosure.client.js'
6
+
7
+ /**
8
+ * Collapses nested schema properties (e.g. `error.details`) behind a toggle,
9
+ * matching the "Show/Hide Child Attributes" pattern from Scalar/Stripe.
10
+ * Built on the shared {@link Disclosure} accordion. The wrapping
11
+ * `data-v-openapi-collapse` element scopes the trigger/panel styling (see
12
+ * `openapi.css`); the Show/Hide icons and labels swap on the trigger's Base UI
13
+ * `data-panel-open` state.
14
+ */
15
+ export function CollapsibleChildren(props: CollapsibleChildren.Props) {
16
+ const { children, label = 'Child Attributes' } = props
17
+ return (
18
+ <div data-v-openapi-collapse>
19
+ <Disclosure
20
+ trigger={
21
+ <>
22
+ <LucidePlus data-v-openapi-collapse-icon-show />
23
+ <LucideX data-v-openapi-collapse-icon-hide />
24
+ <span data-v-openapi-collapse-label-show>Show {label}</span>
25
+ <span data-v-openapi-collapse-label-hide>Hide {label}</span>
26
+ </>
27
+ }
28
+ >
29
+ {children}
30
+ </Disclosure>
31
+ </div>
32
+ )
33
+ }
34
+
35
+ export declare namespace CollapsibleChildren {
36
+ type Props = {
37
+ children: React.ReactNode
38
+ /** Text after the Show/Hide verb. @default 'Child Attributes' */
39
+ label?: string | undefined
40
+ }
41
+ }
@@ -0,0 +1,67 @@
1
+ 'use client'
2
+
3
+ import { Accordion } from '@base-ui/react/accordion'
4
+ import { useCallback, useEffect, useRef, useState } from 'react'
5
+ import { registerDisclosure } from './anchor-navigation.client.js'
6
+
7
+ /**
8
+ * A single collapsible disclosure built on the Base UI Accordion primitive,
9
+ * used for the toggleable sections across OpenAPI pages (nested "child
10
+ * attributes", responses, etc.). Collapsed by default.
11
+ *
12
+ * The panel is kept mounted so its content is always present in the DOM; this
13
+ * lets {@link revealAnchor} scroll to a property inside a collapsed disclosure
14
+ * (e.g. when clicking a response example line). The disclosure registers its
15
+ * panel + an `open()` callback so navigation can reveal it on demand.
16
+ *
17
+ * The trigger is a plain node so it can be passed from server components.
18
+ * Styling is attached via stable data attributes — `data-v-openapi-disclosure`,
19
+ * `-disclosure-trigger`, `-disclosure-panel` — which consumers scope by
20
+ * ancestor (e.g. `[data-v-openapi-response] [data-v-openapi-disclosure-trigger]`)
21
+ * so no Tailwind classes are passed through props. Open-state styling is driven
22
+ * via the Base UI `data-panel-open` attribute on the trigger.
23
+ */
24
+ export function Disclosure(props: Disclosure.Props) {
25
+ const { trigger, defaultOpen = false, children } = props
26
+ const [value, setValue] = useState<string[]>(defaultOpen ? ['disclosure'] : [])
27
+ const panelRef = useRef<HTMLDivElement | null>(null)
28
+
29
+ const open = useCallback(() => {
30
+ setValue((current) => (current.includes('disclosure') ? current : ['disclosure']))
31
+ }, [])
32
+
33
+ useEffect(() => {
34
+ const panel = panelRef.current
35
+ if (!panel) return
36
+ return registerDisclosure(panel, open)
37
+ }, [open])
38
+
39
+ return (
40
+ <Accordion.Root
41
+ data-v-openapi-disclosure
42
+ value={value}
43
+ onValueChange={(next) => setValue(next as string[])}
44
+ multiple
45
+ keepMounted
46
+ >
47
+ <Accordion.Item value="disclosure">
48
+ <Accordion.Header data-v-openapi-disclosure-header>
49
+ <Accordion.Trigger data-v-openapi-disclosure-trigger>{trigger}</Accordion.Trigger>
50
+ </Accordion.Header>
51
+ <Accordion.Panel ref={panelRef} data-v-openapi-disclosure-panel>
52
+ {children}
53
+ </Accordion.Panel>
54
+ </Accordion.Item>
55
+ </Accordion.Root>
56
+ )
57
+ }
58
+
59
+ export declare namespace Disclosure {
60
+ type Props = {
61
+ /** Content of the trigger button. Style open state via `data-panel-open`. */
62
+ trigger: React.ReactNode
63
+ /** @default false */
64
+ defaultOpen?: boolean | undefined
65
+ children: React.ReactNode
66
+ }
67
+ }
@@ -0,0 +1,58 @@
1
+ import { specs } from 'virtual:vocs/openapi'
2
+ import { Link } from '../../Link.js'
3
+ import { EndpointsView } from './EndpointsView.js'
4
+
5
+ /**
6
+ * Renders the domain/category list for an OpenAPI spec as an accordion (Vite/RSC
7
+ * site integration). The UI lives in the framework-agnostic
8
+ * {@link file://./EndpointsView.tsx `EndpointsView`}; this is the Waku adapter
9
+ * that resolves the spec from `virtual:vocs/openapi` and links with the Vocs
10
+ * `Link`.
11
+ *
12
+ * This is an opt-in component — OpenAPI landing pages no longer render the list
13
+ * automatically. Drop `<OpenApi.Endpoints />` into a consumer override page
14
+ * (e.g. `pages/api.mdx`) to surface it.
15
+ *
16
+ * The target spec is resolved from `path` (the OpenAPI mount, e.g. `/api`).
17
+ * When omitted, it defaults to the only configured spec; if multiple specs
18
+ * exist, `path` is required.
19
+ */
20
+ export function Endpoints(props: Endpoints.Props) {
21
+ const mounts = Object.keys(specs)
22
+ const mount = props.path ?? (mounts.length === 1 ? mounts[0] : undefined)
23
+
24
+ if (!mount) {
25
+ if (mounts.length === 0) return <p>No OpenAPI spec is configured.</p>
26
+ return (
27
+ <p>
28
+ Multiple OpenAPI specs are configured. Pass a <code>path</code> to{' '}
29
+ <code>&lt;Endpoints /&gt;</code> (one of: {mounts.join(', ')}).
30
+ </p>
31
+ )
32
+ }
33
+
34
+ const ir = specs[mount]
35
+ if (!ir) return <p>No OpenAPI spec is mounted at {mount}.</p>
36
+
37
+ return (
38
+ <EndpointsView
39
+ ir={ir}
40
+ href={(to) => to}
41
+ Link={({ href, children, ...rest }) => (
42
+ <Link to={href ?? ''} {...rest}>
43
+ {children}
44
+ </Link>
45
+ )}
46
+ />
47
+ )
48
+ }
49
+
50
+ export declare namespace Endpoints {
51
+ type Props = {
52
+ /**
53
+ * OpenAPI mount path identifying which spec to render (e.g. `/api`).
54
+ * Optional when only one spec is configured.
55
+ */
56
+ path?: string | undefined
57
+ }
58
+ }
@@ -0,0 +1,76 @@
1
+ import type { ComponentType } from 'react'
2
+ import LucideChevronRight from '~icons/lucide/chevron-right'
3
+ import type { Ir } from '../../../internal/openapi/parser.js'
4
+ import { methodVariant } from '../../../internal/openapi/sidebar.js'
5
+ import { Badge } from '../../Badge.js'
6
+ import { Disclosure } from './Disclosure.client.js'
7
+
8
+ /**
9
+ * Prop-driven domain/category accordion for an OpenAPI spec: each category
10
+ * expands to its operations (method badge + summary), every entry linking to
11
+ * that operation's anchor on its category page.
12
+ *
13
+ * Framework-agnostic — the caller supplies the parsed {@link Ir}, an `href`
14
+ * mapper (so links resolve correctly under any mount), and an optional `Link`
15
+ * component (the Vocs/Waku `Link` for the site, a plain `<a>` for the standalone
16
+ * client).
17
+ */
18
+ export function EndpointsView(props: EndpointsView.Props) {
19
+ const { ir, href, Link = DefaultLink } = props
20
+ // Root-mounted specs have `ir.path === '/'`; collapse it to '' so links read
21
+ // `/group#op` instead of `//group#op` (which the browser treats as a host).
22
+ const base = ir.path === '/' ? '' : ir.path.replace(/\/$/, '')
23
+ return (
24
+ <div data-v-openapi-overview>
25
+ {ir.groups.map((category) => (
26
+ <div key={category.id} data-v-openapi-overview-category>
27
+ <Disclosure
28
+ trigger={
29
+ <>
30
+ <LucideChevronRight data-v-openapi-overview-chevron />
31
+ <span data-v-openapi-overview-category-head>
32
+ <span data-v-openapi-overview-category-name>{category.name}</span>
33
+ {category.description && (
34
+ <span data-v-openapi-overview-category-description>{category.description}</span>
35
+ )}
36
+ </span>
37
+ </>
38
+ }
39
+ >
40
+ <ul data-v-openapi-overview-endpoints>
41
+ {category.operations.map((operation) => (
42
+ <li key={operation.id}>
43
+ <Link
44
+ href={href(`${base}/${category.id}#${operation.id}`)}
45
+ data-v-openapi-overview-endpoint
46
+ >
47
+ <Badge variant={methodVariant(operation.method)}>{operation.method}</Badge>
48
+ <span data-v-openapi-overview-endpoint-title>
49
+ {operation.summary || operation.path}
50
+ </span>
51
+ <LucideChevronRight data-v-openapi-overview-endpoint-chevron />
52
+ </Link>
53
+ </li>
54
+ ))}
55
+ </ul>
56
+ </Disclosure>
57
+ </div>
58
+ ))}
59
+ </div>
60
+ )
61
+ }
62
+
63
+ function DefaultLink(props: React.ComponentProps<'a'>) {
64
+ return <a {...props} />
65
+ }
66
+
67
+ export declare namespace EndpointsView {
68
+ type Props = {
69
+ /** Parsed OpenAPI spec. */
70
+ ir: Ir
71
+ /** Maps a section-relative href to a final href (e.g. mount-prefixed). */
72
+ href: (to: string) => string
73
+ /** Link component. Defaults to a plain anchor. */
74
+ Link?: ComponentType<React.ComponentProps<'a'>> | undefined
75
+ }
76
+ }
@@ -0,0 +1,49 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import LucidePlus from '~icons/lucide/plus'
5
+ import LucideX from '~icons/lucide/x'
6
+
7
+ const previewCount = 5
8
+
9
+ /**
10
+ * Renders a schema's enum literals as a vertical "values" list (one per row)
11
+ * rather than a wrapping inline union. Shows the first {@link previewCount}
12
+ * values and reveals the rest behind a "Show all values" toggle, matching the
13
+ * Scalar/Stripe reference UI.
14
+ */
15
+ export function EnumValues(props: EnumValues.Props) {
16
+ const { values } = props
17
+ const [expanded, setExpanded] = useState(false)
18
+ const hasMore = values.length > previewCount
19
+ const visible = expanded ? values : values.slice(0, previewCount)
20
+ return (
21
+ <div data-v-openapi-values>
22
+ <span data-v-openapi-values-label>Values</span>
23
+ <div data-v-openapi-values-list>
24
+ {visible.map((value) => (
25
+ <code key={value} data-v-openapi-values-item>
26
+ {value}
27
+ </code>
28
+ ))}
29
+ </div>
30
+ {hasMore && (
31
+ <button type="button" onClick={() => setExpanded((value) => !value)} data-v-openapi-pill>
32
+ {expanded ? (
33
+ <LucideX data-v-openapi-pill-icon />
34
+ ) : (
35
+ <LucidePlus data-v-openapi-pill-icon />
36
+ )}
37
+ {expanded ? 'Hide values' : 'Show all values'}
38
+ </button>
39
+ )}
40
+ </div>
41
+ )
42
+ }
43
+
44
+ export declare namespace EnumValues {
45
+ type Props = {
46
+ /** Enum literal values as display strings. */
47
+ values: string[]
48
+ }
49
+ }
@@ -0,0 +1,28 @@
1
+ import LucideLink from '~icons/lucide/link'
2
+
3
+ /**
4
+ * A copy-link anchor appended to an OpenAPI heading. Reuses the markdown
5
+ * `heading-anchor`/`heading-anchor-icon` classes so it inherits the same
6
+ * hover-reveal and "copied" styling, and is handled by the global
7
+ * `HeadingAnchors` client (which copies the anchor's URL on click).
8
+ */
9
+ export function HeadingAnchor(props: HeadingAnchor.Props) {
10
+ return (
11
+ <a
12
+ href={`#${props.id}`}
13
+ className={`heading-anchor${props.className ? ` ${props.className}` : ''}`}
14
+ aria-label="Copy link and go to this section"
15
+ title="Copy link and go to this section"
16
+ >
17
+ <LucideLink className="heading-anchor-icon vocs:size-[0.75em]" />
18
+ </a>
19
+ )
20
+ }
21
+
22
+ export declare namespace HeadingAnchor {
23
+ type Props = {
24
+ id: string
25
+ /** Extra classes appended to the anchor (e.g. to override the left margin). */
26
+ className?: string | undefined
27
+ }
28
+ }