tempest-react-sdk 0.76.0 → 0.78.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 (173) hide show
  1. package/README.md +2 -2
  2. package/bin/create-tempest-app.e2e.test.mjs +112 -0
  3. package/bin/create-tempest-app.mjs +69 -28
  4. package/bin/lib/alias/collect.mjs +20 -3
  5. package/bin/lib/alias/index.mjs +12 -1
  6. package/bin/lib/alias/index.test.mjs +24 -0
  7. package/bin/lib/component/generate.mjs +191 -0
  8. package/bin/lib/component/generate.test.mjs +336 -0
  9. package/bin/lib/css/analyze.test.mjs +21 -1
  10. package/bin/lib/css/collect.mjs +14 -0
  11. package/bin/lib/css/extract.mjs +63 -5
  12. package/bin/lib/css/extract.test.mjs +110 -1
  13. package/bin/lib/css/fix.e2e.test.mjs +50 -0
  14. package/bin/lib/css/index.mjs +6 -1
  15. package/bin/lib/fs/contain.mjs +50 -0
  16. package/bin/lib/icons/generate.mjs +28 -3
  17. package/bin/lib/icons/generate.test.mjs +57 -1
  18. package/bin/lib/openapi/escape.mjs +224 -0
  19. package/bin/lib/openapi/generate.encode.test.mjs +120 -0
  20. package/bin/lib/openapi/generate.hostile.test.mjs +621 -0
  21. package/bin/lib/openapi/generate.mjs +129 -18
  22. package/bin/lib/openapi/generate.test.mjs +7 -2
  23. package/bin/lib/openapi/schema-to-zod.mjs +43 -4
  24. package/bin/tempest-gen.test.mjs +188 -0
  25. package/bin/tempest.mjs +263 -6
  26. package/dist/auth/create-tempest-auth.cjs +1 -1
  27. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  28. package/dist/auth/create-tempest-auth.js +203 -58
  29. package/dist/auth/create-tempest-auth.js.map +1 -1
  30. package/dist/auth/refresh-queue.cjs +1 -1
  31. package/dist/auth/refresh-queue.cjs.map +1 -1
  32. package/dist/auth/refresh-queue.js +13 -6
  33. package/dist/auth/refresh-queue.js.map +1 -1
  34. package/dist/br/pix.cjs +1 -1
  35. package/dist/br/pix.cjs.map +1 -1
  36. package/dist/br/pix.js +85 -72
  37. package/dist/br/pix.js.map +1 -1
  38. package/dist/br.d.ts +19 -4
  39. package/dist/components/Chat/ChatAttachments.cjs +1 -1
  40. package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
  41. package/dist/components/Chat/ChatAttachments.js +46 -38
  42. package/dist/components/Chat/ChatAttachments.js.map +1 -1
  43. package/dist/components/CodeBlock/CodeBlock.cjs +1 -1
  44. package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
  45. package/dist/components/CodeBlock/CodeBlock.js +1 -1
  46. package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
  47. package/dist/components/CodeBlock/tokenize.cjs +1 -1
  48. package/dist/components/CodeBlock/tokenize.cjs.map +1 -1
  49. package/dist/components/CodeBlock/tokenize.js +54 -25
  50. package/dist/components/CodeBlock/tokenize.js.map +1 -1
  51. package/dist/components/Markdown/markdown-parse.cjs +7 -5
  52. package/dist/components/Markdown/markdown-parse.cjs.map +1 -1
  53. package/dist/components/Markdown/markdown-parse.js +171 -90
  54. package/dist/components/Markdown/markdown-parse.js.map +1 -1
  55. package/dist/components/Toast/Toast.module.cjs +1 -1
  56. package/dist/components/Toast/Toast.module.cjs.map +1 -1
  57. package/dist/components/Toast/Toast.module.js +7 -3
  58. package/dist/components/Toast/Toast.module.js.map +1 -1
  59. package/dist/components/Toast/ToastProvider.cjs +1 -1
  60. package/dist/components/Toast/ToastProvider.cjs.map +1 -1
  61. package/dist/components/Toast/ToastProvider.js +77 -51
  62. package/dist/components/Toast/ToastProvider.js.map +1 -1
  63. package/dist/data/create-data-provider.cjs +1 -1
  64. package/dist/data/create-data-provider.cjs.map +1 -1
  65. package/dist/data/create-data-provider.js +32 -21
  66. package/dist/data/create-data-provider.js.map +1 -1
  67. package/dist/http/api-client.cjs +1 -1
  68. package/dist/http/api-client.cjs.map +1 -1
  69. package/dist/http/api-client.js +81 -73
  70. package/dist/http/api-client.js.map +1 -1
  71. package/dist/http/errors.cjs +1 -1
  72. package/dist/http/errors.cjs.map +1 -1
  73. package/dist/http/errors.js +39 -35
  74. package/dist/http/errors.js.map +1 -1
  75. package/dist/http/resumable-upload.cjs +1 -1
  76. package/dist/http/resumable-upload.cjs.map +1 -1
  77. package/dist/http/resumable-upload.js +141 -105
  78. package/dist/http/resumable-upload.js.map +1 -1
  79. package/dist/http/retry.cjs +1 -1
  80. package/dist/http/retry.cjs.map +1 -1
  81. package/dist/http/retry.js +5 -6
  82. package/dist/http/retry.js.map +1 -1
  83. package/dist/oauth/callback.cjs +2 -0
  84. package/dist/oauth/callback.cjs.map +1 -0
  85. package/dist/oauth/callback.js +42 -0
  86. package/dist/oauth/callback.js.map +1 -0
  87. package/dist/oauth/use-oauth-callback.cjs +1 -1
  88. package/dist/oauth/use-oauth-callback.cjs.map +1 -1
  89. package/dist/oauth/use-oauth-callback.js +13 -20
  90. package/dist/oauth/use-oauth-callback.js.map +1 -1
  91. package/dist/push/use-push-subscription.cjs +1 -1
  92. package/dist/push/use-push-subscription.cjs.map +1 -1
  93. package/dist/push/use-push-subscription.js +42 -42
  94. package/dist/push/use-push-subscription.js.map +1 -1
  95. package/dist/push/web-push-client.cjs +1 -1
  96. package/dist/push/web-push-client.cjs.map +1 -1
  97. package/dist/push/web-push-client.js +23 -3
  98. package/dist/push/web-push-client.js.map +1 -1
  99. package/dist/query/offline-persistence.cjs +1 -1
  100. package/dist/query/offline-persistence.cjs.map +1 -1
  101. package/dist/query/offline-persistence.js +68 -23
  102. package/dist/query/offline-persistence.js.map +1 -1
  103. package/dist/styles/component/Toast.css +6 -1
  104. package/dist/styles/feedback.css +6 -1
  105. package/dist/styles.css +1 -1
  106. package/dist/sw/background-sync.cjs +1 -1
  107. package/dist/sw/background-sync.cjs.map +1 -1
  108. package/dist/sw/background-sync.js +272 -60
  109. package/dist/sw/background-sync.js.map +1 -1
  110. package/dist/sw/cache-inspect.cjs.map +1 -1
  111. package/dist/sw/cache-inspect.js.map +1 -1
  112. package/dist/sw/cache.cjs +1 -1
  113. package/dist/sw/cache.cjs.map +1 -1
  114. package/dist/sw/cache.js +91 -64
  115. package/dist/sw/cache.js.map +1 -1
  116. package/dist/sw/credential-headers.cjs +2 -0
  117. package/dist/sw/credential-headers.cjs.map +1 -0
  118. package/dist/sw/credential-headers.js +17 -0
  119. package/dist/sw/credential-headers.js.map +1 -0
  120. package/dist/sw/session-data.cjs +2 -0
  121. package/dist/sw/session-data.cjs.map +1 -0
  122. package/dist/sw/session-data.js +17 -0
  123. package/dist/sw/session-data.js.map +1 -0
  124. package/dist/sw.cjs +1 -1
  125. package/dist/sw.d.ts +309 -23
  126. package/dist/sw.js +7 -5
  127. package/dist/telemetry/app-error-queue.cjs +1 -1
  128. package/dist/telemetry/app-error-queue.cjs.map +1 -1
  129. package/dist/telemetry/app-error-queue.js +19 -14
  130. package/dist/telemetry/app-error-queue.js.map +1 -1
  131. package/dist/telemetry/app-error-reporter.cjs +1 -1
  132. package/dist/telemetry/app-error-reporter.cjs.map +1 -1
  133. package/dist/telemetry/app-error-reporter.js +75 -34
  134. package/dist/telemetry/app-error-reporter.js.map +1 -1
  135. package/dist/telemetry/app-error-transport.cjs +1 -1
  136. package/dist/telemetry/app-error-transport.cjs.map +1 -1
  137. package/dist/telemetry/app-error-transport.js +16 -3
  138. package/dist/telemetry/app-error-transport.js.map +1 -1
  139. package/dist/tempest-react-sdk.cjs +1 -1
  140. package/dist/tempest-react-sdk.d.ts +1183 -64
  141. package/dist/tempest-react-sdk.js +160 -156
  142. package/dist/utils/csv.cjs +3 -3
  143. package/dist/utils/csv.cjs.map +1 -1
  144. package/dist/utils/csv.js +18 -14
  145. package/dist/utils/csv.js.map +1 -1
  146. package/dist/utils/owner-fingerprint.cjs +2 -0
  147. package/dist/utils/owner-fingerprint.cjs.map +1 -0
  148. package/dist/utils/owner-fingerprint.js +18 -0
  149. package/dist/utils/owner-fingerprint.js.map +1 -0
  150. package/dist/utils/safe-url.cjs +2 -0
  151. package/dist/utils/safe-url.cjs.map +1 -0
  152. package/dist/utils/safe-url.js +39 -0
  153. package/dist/utils/safe-url.js.map +1 -0
  154. package/dist/vite/tempest-csp.cjs +2 -2
  155. package/dist/vite/tempest-csp.cjs.map +1 -1
  156. package/dist/vite/tempest-csp.js +130 -64
  157. package/dist/vite/tempest-csp.js.map +1 -1
  158. package/dist/vite.d.ts +23 -5
  159. package/dist/webrtc/peer-link.cjs +1 -1
  160. package/dist/webrtc/peer-link.cjs.map +1 -1
  161. package/dist/webrtc/peer-link.js +2 -10
  162. package/dist/webrtc/peer-link.js.map +1 -1
  163. package/dist/webrtc/peer-mesh.cjs +1 -1
  164. package/dist/webrtc/peer-mesh.cjs.map +1 -1
  165. package/dist/webrtc/peer-mesh.js +110 -72
  166. package/dist/webrtc/peer-mesh.js.map +1 -1
  167. package/package.json +1 -1
  168. package/template-pwa/src/pwa.tsx +22 -0
  169. package/template-pwa/src/sw.ts +27 -14
  170. package/dist/components/Markdown/markdown-url.cjs +0 -2
  171. package/dist/components/Markdown/markdown-url.cjs.map +0 -1
  172. package/dist/components/Markdown/markdown-url.js +0 -28
  173. package/dist/components/Markdown/markdown-url.js.map +0 -1
@@ -1,2 +1,2 @@
1
- const e=require("../../utils/numbers.cjs"),t=require("../VideoPlayer/VideoPlayer.cjs"),n=require("../Lightbox/Lightbox.cjs"),r=require("./Chat.module.cjs"),i=require("../AudioPlayer/AudioPlayer.cjs");let a=require("react"),o=require("react/jsx-runtime");var s=Array.from({length:28},()=>.25);function c(e){let t=Math.round(e/1e3),n=Math.floor(t/60),r=t%60;return`${n}:${String(r).padStart(2,`0`)}`}function l({attachments:l,strings:u}){let d=l.filter(e=>e.kind===`image`),[f,p]=(0,a.useState)(null);return(0,o.jsxs)(`div`,{className:r.default.attachments,children:[l.map((n,a)=>{let l=`${n.kind}-${n.url}-${a}`;if(n.kind===`image`){let e=d.indexOf(n);return(0,o.jsx)(`button`,{type:`button`,className:r.default.imageAttachment,onClick:()=>p(e),children:(0,o.jsx)(`img`,{src:n.thumbnailUrl??n.url,alt:n.alt??n.name??``,loading:`lazy`})},l)}return n.kind===`video`?(0,o.jsx)(t.VideoPlayer,{src:n.url,durationMs:n.durationMs,poster:n.thumbnailUrl,className:r.default.videoAttachment},l):n.kind===`voice`?(0,o.jsxs)(`div`,{className:r.default.voiceAttachment,children:[(0,o.jsx)(`div`,{className:r.default.waveform,"aria-hidden":`true`,children:(n.waveform??s).map((e,t)=>(0,o.jsx)(`span`,{style:{height:`${Math.max(8,Math.min(100,e*100))}%`}},t))}),(0,o.jsx)(i.AudioPlayer,{src:n.url,durationMs:n.durationMs,"aria-label":u.voiceNote})]},l):n.kind===`audio`?(0,o.jsx)(i.AudioPlayer,{src:n.url,durationMs:n.durationMs},l):(0,o.jsxs)(`a`,{className:r.default.fileAttachment,href:n.url,download:n.name??!0,children:[(0,o.jsx)(`span`,{className:r.default.fileName,children:n.name??u.download}),(0,o.jsx)(`span`,{className:r.default.fileMeta,children:[n.sizeBytes===void 0?null:e.formatBytes(n.sizeBytes),n.durationMs===void 0?null:c(n.durationMs)].filter(Boolean).join(` · `)})]},l)}),d.length>0&&(0,o.jsx)(n.Lightbox,{items:d.map(e=>({src:e.url,alt:e.alt??e.name??``,thumbnail:e.thumbnailUrl})),open:f!==null,index:f??0,onIndexChange:p,onClose:()=>p(null)})]})}exports.ChatAttachments=l;
1
+ const e=require("../../utils/numbers.cjs"),t=require("../VideoPlayer/VideoPlayer.cjs"),n=require("../Lightbox/Lightbox.cjs"),r=require("./Chat.module.cjs"),i=require("../AudioPlayer/AudioPlayer.cjs"),a=require("../../utils/safe-url.cjs");let o=require("react"),s=require("react/jsx-runtime");var c=Array.from({length:28},()=>.25);function l(e){let t=Math.round(e/1e3),n=Math.floor(t/60),r=t%60;return`${n}:${String(r).padStart(2,`0`)}`}function u({attachments:u,strings:d}){let f=u.filter(e=>e.kind===`image`),[p,m]=(0,o.useState)(null);return(0,s.jsxs)(`div`,{className:r.default.attachments,children:[u.map((n,o)=>{let u=`${n.kind}-${n.url}-${o}`;if(n.kind===`image`){let e=f.indexOf(n);return(0,s.jsx)(`button`,{type:`button`,className:r.default.imageAttachment,onClick:()=>m(e),children:(0,s.jsx)(`img`,{src:n.thumbnailUrl??n.url,alt:n.alt??n.name??``,loading:`lazy`})},u)}if(n.kind===`video`)return(0,s.jsx)(t.VideoPlayer,{src:n.url,durationMs:n.durationMs,poster:n.thumbnailUrl,className:r.default.videoAttachment},u);if(n.kind===`voice`)return(0,s.jsxs)(`div`,{className:r.default.voiceAttachment,children:[(0,s.jsx)(`div`,{className:r.default.waveform,"aria-hidden":`true`,children:(n.waveform??c).map((e,t)=>(0,s.jsx)(`span`,{style:{height:`${Math.max(8,Math.min(100,e*100))}%`}},t))}),(0,s.jsx)(i.AudioPlayer,{src:n.url,durationMs:n.durationMs,"aria-label":d.voiceNote})]},u);if(n.kind===`audio`)return(0,s.jsx)(i.AudioPlayer,{src:n.url,durationMs:n.durationMs},u);let p=a.safeAttachmentUrl(n.url),h=(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(`span`,{className:r.default.fileName,children:n.name??d.download}),(0,s.jsx)(`span`,{className:r.default.fileMeta,children:[n.sizeBytes===void 0?null:e.formatBytes(n.sizeBytes),n.durationMs===void 0?null:l(n.durationMs)].filter(Boolean).join(` · `)})]});return p?(0,s.jsx)(`a`,{className:r.default.fileAttachment,href:p,download:n.name??!0,children:h},u):(0,s.jsx)(`div`,{className:r.default.fileAttachment,children:h},u)}),f.length>0&&(0,s.jsx)(n.Lightbox,{items:f.map(e=>({src:e.url,alt:e.alt??e.name??``,thumbnail:e.thumbnailUrl})),open:p!==null,index:p??0,onIndexChange:m,onClose:()=>m(null)})]})}exports.ChatAttachments=u;
2
2
  //# sourceMappingURL=ChatAttachments.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatAttachments.cjs","names":[],"sources":["../../../src/components/Chat/ChatAttachments.tsx"],"sourcesContent":["import { useState } from \"react\";\n\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { AudioPlayer } from \"../AudioPlayer\";\nimport { Lightbox } from \"../Lightbox\";\nimport { VideoPlayer } from \"../VideoPlayer\";\nimport type { ChatAttachment } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** Strings this view needs, passed in so the locale table stays in one file. */\nexport interface ChatAttachmentStrings {\n voiceNote: string;\n download: string;\n}\n\nexport interface ChatAttachmentsProps {\n /** What this message carries. */\n attachments: readonly ChatAttachment[];\n /** Labels, from `chatStrings(locale)`. */\n strings: ChatAttachmentStrings;\n}\n\n/** Bars a voice note draws when the app gave no peaks. */\nconst FLAT_WAVEFORM = Array.from({ length: 28 }, () => 0.25);\n\n/** `1:04` from milliseconds — a duration, never a clock time. */\nfunction durationLabel(durationMs: number): string {\n const total = Math.round(durationMs / 1000);\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n return `${minutes}:${String(seconds).padStart(2, \"0\")}`;\n}\n\n/**\n * The media half of a bubble.\n *\n * Kept in its own module, and reached through `lazy()` from the thread, because\n * it pulls three of the heaviest components in the SDK — `Lightbox`,\n * `VideoPlayer`, `AudioPlayer` — and a support thread of plain text should not\n * pay for a photo viewer it never opens. The thread renders it only for messages\n * that actually carry an attachment, so that chunk is requested the first time\n * one appears.\n *\n * Nothing here is new UI: an image opens the SDK's lightbox, a video and an audio\n * clip use the SDK's players, and a file is a download row. That composition is\n * the whole point — every app was writing it again, the same way.\n *\n * @param props - The attachments and the locale strings.\n * @returns The rendered attachment list.\n */\nexport function ChatAttachments({ attachments, strings }: ChatAttachmentsProps) {\n const images = attachments.filter((attachment) => attachment.kind === \"image\");\n const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);\n\n return (\n <div className={styles.attachments}>\n {attachments.map((attachment, index) => {\n const key = `${attachment.kind}-${attachment.url}-${index}`;\n if (attachment.kind === \"image\") {\n const position = images.indexOf(attachment);\n return (\n <button\n key={key}\n type=\"button\"\n className={styles.imageAttachment}\n onClick={() => setLightboxIndex(position)}\n >\n <img\n src={attachment.thumbnailUrl ?? attachment.url}\n alt={attachment.alt ?? attachment.name ?? \"\"}\n loading=\"lazy\"\n />\n </button>\n );\n }\n if (attachment.kind === \"video\") {\n return (\n <VideoPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n poster={attachment.thumbnailUrl}\n className={styles.videoAttachment}\n />\n );\n }\n if (attachment.kind === \"voice\") {\n return (\n <div key={key} className={styles.voiceAttachment}>\n <div className={styles.waveform} aria-hidden=\"true\">\n {(attachment.waveform ?? FLAT_WAVEFORM).map((peak, bar) => (\n <span\n key={bar}\n style={{\n height: `${Math.max(8, Math.min(100, peak * 100))}%`,\n }}\n />\n ))}\n </div>\n <AudioPlayer\n src={attachment.url}\n durationMs={attachment.durationMs}\n aria-label={strings.voiceNote}\n />\n </div>\n );\n }\n if (attachment.kind === \"audio\") {\n return (\n <AudioPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n />\n );\n }\n return (\n <a\n key={key}\n className={styles.fileAttachment}\n href={attachment.url}\n download={attachment.name ?? true}\n >\n <span className={styles.fileName}>\n {attachment.name ?? strings.download}\n </span>\n <span className={styles.fileMeta}>\n {[\n attachment.sizeBytes !== undefined\n ? formatBytes(attachment.sizeBytes)\n : null,\n attachment.durationMs !== undefined\n ? durationLabel(attachment.durationMs)\n : null,\n ]\n .filter(Boolean)\n .join(\" · \")}\n </span>\n </a>\n );\n })}\n\n {images.length > 0 && (\n <Lightbox\n items={images.map((image) => ({\n src: image.url,\n alt: image.alt ?? image.name ?? \"\",\n thumbnail: image.thumbnailUrl,\n }))}\n open={lightboxIndex !== null}\n index={lightboxIndex ?? 0}\n onIndexChange={setLightboxIndex}\n onClose={() => setLightboxIndex(null)}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ChatAttachments.cjs","names":[],"sources":["../../../src/components/Chat/ChatAttachments.tsx"],"sourcesContent":["import { useState } from \"react\";\n\nimport { formatBytes } from \"@/utils/numbers\";\nimport { safeAttachmentUrl } from \"@/utils/safe-url\";\n\nimport { AudioPlayer } from \"../AudioPlayer\";\nimport { Lightbox } from \"../Lightbox\";\nimport { VideoPlayer } from \"../VideoPlayer\";\nimport type { ChatAttachment } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** Strings this view needs, passed in so the locale table stays in one file. */\nexport interface ChatAttachmentStrings {\n voiceNote: string;\n download: string;\n}\n\nexport interface ChatAttachmentsProps {\n /** What this message carries. */\n attachments: readonly ChatAttachment[];\n /** Labels, from `chatStrings(locale)`. */\n strings: ChatAttachmentStrings;\n}\n\n/** Bars a voice note draws when the app gave no peaks. */\nconst FLAT_WAVEFORM = Array.from({ length: 28 }, () => 0.25);\n\n/** `1:04` from milliseconds — a duration, never a clock time. */\nfunction durationLabel(durationMs: number): string {\n const total = Math.round(durationMs / 1000);\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n return `${minutes}:${String(seconds).padStart(2, \"0\")}`;\n}\n\n/**\n * The media half of a bubble.\n *\n * Kept in its own module, and reached through `lazy()` from the thread, because\n * it pulls three of the heaviest components in the SDK — `Lightbox`,\n * `VideoPlayer`, `AudioPlayer` — and a support thread of plain text should not\n * pay for a photo viewer it never opens. The thread renders it only for messages\n * that actually carry an attachment, so that chunk is requested the first time\n * one appears.\n *\n * Nothing here is new UI: an image opens the SDK's lightbox, a video and an audio\n * clip use the SDK's players, and a file is a download row. That composition is\n * the whole point — every app was writing it again, the same way.\n *\n * The file row is the one place a sender's URL becomes an `href`, so it goes\n * through {@link safeAttachmentUrl}: `http:`, `https:`, `blob:` and relative URLs\n * are linked, anything else renders the same row without a link (#447). Media\n * `src` is left as given — an `<img>`, `<video>` or `<audio>` does not run a\n * `javascript:` URL.\n *\n * @param props - The attachments and the locale strings.\n * @returns The rendered attachment list.\n */\nexport function ChatAttachments({ attachments, strings }: ChatAttachmentsProps) {\n const images = attachments.filter((attachment) => attachment.kind === \"image\");\n const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);\n\n return (\n <div className={styles.attachments}>\n {attachments.map((attachment, index) => {\n const key = `${attachment.kind}-${attachment.url}-${index}`;\n if (attachment.kind === \"image\") {\n const position = images.indexOf(attachment);\n return (\n <button\n key={key}\n type=\"button\"\n className={styles.imageAttachment}\n onClick={() => setLightboxIndex(position)}\n >\n <img\n src={attachment.thumbnailUrl ?? attachment.url}\n alt={attachment.alt ?? attachment.name ?? \"\"}\n loading=\"lazy\"\n />\n </button>\n );\n }\n if (attachment.kind === \"video\") {\n return (\n <VideoPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n poster={attachment.thumbnailUrl}\n className={styles.videoAttachment}\n />\n );\n }\n if (attachment.kind === \"voice\") {\n return (\n <div key={key} className={styles.voiceAttachment}>\n <div className={styles.waveform} aria-hidden=\"true\">\n {(attachment.waveform ?? FLAT_WAVEFORM).map((peak, bar) => (\n <span\n key={bar}\n style={{\n height: `${Math.max(8, Math.min(100, peak * 100))}%`,\n }}\n />\n ))}\n </div>\n <AudioPlayer\n src={attachment.url}\n durationMs={attachment.durationMs}\n aria-label={strings.voiceNote}\n />\n </div>\n );\n }\n if (attachment.kind === \"audio\") {\n return (\n <AudioPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n />\n );\n }\n const href = safeAttachmentUrl(attachment.url);\n const row = (\n <>\n <span className={styles.fileName}>\n {attachment.name ?? strings.download}\n </span>\n <span className={styles.fileMeta}>\n {[\n attachment.sizeBytes !== undefined\n ? formatBytes(attachment.sizeBytes)\n : null,\n attachment.durationMs !== undefined\n ? durationLabel(attachment.durationMs)\n : null,\n ]\n .filter(Boolean)\n .join(\" · \")}\n </span>\n </>\n );\n return href ? (\n <a\n key={key}\n className={styles.fileAttachment}\n href={href}\n download={attachment.name ?? true}\n >\n {row}\n </a>\n ) : (\n <div key={key} className={styles.fileAttachment}>\n {row}\n </div>\n );\n })}\n\n {images.length > 0 && (\n <Lightbox\n items={images.map((image) => ({\n src: image.url,\n alt: image.alt ?? image.name ?? \"\",\n thumbnail: image.thumbnailUrl,\n }))}\n open={lightboxIndex !== null}\n index={lightboxIndex ?? 0}\n onIndexChange={setLightboxIndex}\n onClose={() => setLightboxIndex(null)}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -3,78 +3,86 @@ import { VideoPlayer as t } from "../VideoPlayer/VideoPlayer.js";
3
3
  import { Lightbox as n } from "../Lightbox/Lightbox.js";
4
4
  import r from "./Chat.module.js";
5
5
  import { AudioPlayer as i } from "../AudioPlayer/AudioPlayer.js";
6
- import { useState as a } from "react";
7
- import { jsx as o, jsxs as s } from "react/jsx-runtime";
6
+ import { safeAttachmentUrl as a } from "../../utils/safe-url.js";
7
+ import { useState as o } from "react";
8
+ import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
8
9
  //#region src/components/Chat/ChatAttachments.tsx
9
- var c = Array.from({ length: 28 }, () => .25);
10
- function l(e) {
10
+ var u = Array.from({ length: 28 }, () => .25);
11
+ function d(e) {
11
12
  let t = Math.round(e / 1e3), n = Math.floor(t / 60), r = t % 60;
12
13
  return `${n}:${String(r).padStart(2, "0")}`;
13
14
  }
14
- function u({ attachments: u, strings: d }) {
15
- let f = u.filter((e) => e.kind === "image"), [p, m] = a(null);
16
- return /* @__PURE__ */ s("div", {
15
+ function f({ attachments: f, strings: p }) {
16
+ let m = f.filter((e) => e.kind === "image"), [h, g] = o(null);
17
+ return /* @__PURE__ */ l("div", {
17
18
  className: r.attachments,
18
- children: [u.map((n, a) => {
19
- let u = `${n.kind}-${n.url}-${a}`;
19
+ children: [f.map((n, o) => {
20
+ let f = `${n.kind}-${n.url}-${o}`;
20
21
  if (n.kind === "image") {
21
- let e = f.indexOf(n);
22
- return /* @__PURE__ */ o("button", {
22
+ let e = m.indexOf(n);
23
+ return /* @__PURE__ */ c("button", {
23
24
  type: "button",
24
25
  className: r.imageAttachment,
25
- onClick: () => m(e),
26
- children: /* @__PURE__ */ o("img", {
26
+ onClick: () => g(e),
27
+ children: /* @__PURE__ */ c("img", {
27
28
  src: n.thumbnailUrl ?? n.url,
28
29
  alt: n.alt ?? n.name ?? "",
29
30
  loading: "lazy"
30
31
  })
31
- }, u);
32
+ }, f);
32
33
  }
33
- return n.kind === "video" ? /* @__PURE__ */ o(t, {
34
+ if (n.kind === "video") return /* @__PURE__ */ c(t, {
34
35
  src: n.url,
35
36
  durationMs: n.durationMs,
36
37
  poster: n.thumbnailUrl,
37
38
  className: r.videoAttachment
38
- }, u) : n.kind === "voice" ? /* @__PURE__ */ s("div", {
39
+ }, f);
40
+ if (n.kind === "voice") return /* @__PURE__ */ l("div", {
39
41
  className: r.voiceAttachment,
40
- children: [/* @__PURE__ */ o("div", {
42
+ children: [/* @__PURE__ */ c("div", {
41
43
  className: r.waveform,
42
44
  "aria-hidden": "true",
43
- children: (n.waveform ?? c).map((e, t) => /* @__PURE__ */ o("span", { style: { height: `${Math.max(8, Math.min(100, e * 100))}%` } }, t))
44
- }), /* @__PURE__ */ o(i, {
45
+ children: (n.waveform ?? u).map((e, t) => /* @__PURE__ */ c("span", { style: { height: `${Math.max(8, Math.min(100, e * 100))}%` } }, t))
46
+ }), /* @__PURE__ */ c(i, {
45
47
  src: n.url,
46
48
  durationMs: n.durationMs,
47
- "aria-label": d.voiceNote
49
+ "aria-label": p.voiceNote
48
50
  })]
49
- }, u) : n.kind === "audio" ? /* @__PURE__ */ o(i, {
51
+ }, f);
52
+ if (n.kind === "audio") return /* @__PURE__ */ c(i, {
50
53
  src: n.url,
51
54
  durationMs: n.durationMs
52
- }, u) : /* @__PURE__ */ s("a", {
55
+ }, f);
56
+ let h = a(n.url), _ = /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c("span", {
57
+ className: r.fileName,
58
+ children: n.name ?? p.download
59
+ }), /* @__PURE__ */ c("span", {
60
+ className: r.fileMeta,
61
+ children: [n.sizeBytes === void 0 ? null : e(n.sizeBytes), n.durationMs === void 0 ? null : d(n.durationMs)].filter(Boolean).join(" · ")
62
+ })] });
63
+ return h ? /* @__PURE__ */ c("a", {
53
64
  className: r.fileAttachment,
54
- href: n.url,
65
+ href: h,
55
66
  download: n.name ?? !0,
56
- children: [/* @__PURE__ */ o("span", {
57
- className: r.fileName,
58
- children: n.name ?? d.download
59
- }), /* @__PURE__ */ o("span", {
60
- className: r.fileMeta,
61
- children: [n.sizeBytes === void 0 ? null : e(n.sizeBytes), n.durationMs === void 0 ? null : l(n.durationMs)].filter(Boolean).join(" · ")
62
- })]
63
- }, u);
64
- }), f.length > 0 && /* @__PURE__ */ o(n, {
65
- items: f.map((e) => ({
67
+ children: _
68
+ }, f) : /* @__PURE__ */ c("div", {
69
+ className: r.fileAttachment,
70
+ children: _
71
+ }, f);
72
+ }), m.length > 0 && /* @__PURE__ */ c(n, {
73
+ items: m.map((e) => ({
66
74
  src: e.url,
67
75
  alt: e.alt ?? e.name ?? "",
68
76
  thumbnail: e.thumbnailUrl
69
77
  })),
70
- open: p !== null,
71
- index: p ?? 0,
72
- onIndexChange: m,
73
- onClose: () => m(null)
78
+ open: h !== null,
79
+ index: h ?? 0,
80
+ onIndexChange: g,
81
+ onClose: () => g(null)
74
82
  })]
75
83
  });
76
84
  }
77
85
  //#endregion
78
- export { u as ChatAttachments };
86
+ export { f as ChatAttachments };
79
87
 
80
88
  //# sourceMappingURL=ChatAttachments.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatAttachments.js","names":[],"sources":["../../../src/components/Chat/ChatAttachments.tsx"],"sourcesContent":["import { useState } from \"react\";\n\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { AudioPlayer } from \"../AudioPlayer\";\nimport { Lightbox } from \"../Lightbox\";\nimport { VideoPlayer } from \"../VideoPlayer\";\nimport type { ChatAttachment } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** Strings this view needs, passed in so the locale table stays in one file. */\nexport interface ChatAttachmentStrings {\n voiceNote: string;\n download: string;\n}\n\nexport interface ChatAttachmentsProps {\n /** What this message carries. */\n attachments: readonly ChatAttachment[];\n /** Labels, from `chatStrings(locale)`. */\n strings: ChatAttachmentStrings;\n}\n\n/** Bars a voice note draws when the app gave no peaks. */\nconst FLAT_WAVEFORM = Array.from({ length: 28 }, () => 0.25);\n\n/** `1:04` from milliseconds — a duration, never a clock time. */\nfunction durationLabel(durationMs: number): string {\n const total = Math.round(durationMs / 1000);\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n return `${minutes}:${String(seconds).padStart(2, \"0\")}`;\n}\n\n/**\n * The media half of a bubble.\n *\n * Kept in its own module, and reached through `lazy()` from the thread, because\n * it pulls three of the heaviest components in the SDK — `Lightbox`,\n * `VideoPlayer`, `AudioPlayer` — and a support thread of plain text should not\n * pay for a photo viewer it never opens. The thread renders it only for messages\n * that actually carry an attachment, so that chunk is requested the first time\n * one appears.\n *\n * Nothing here is new UI: an image opens the SDK's lightbox, a video and an audio\n * clip use the SDK's players, and a file is a download row. That composition is\n * the whole point — every app was writing it again, the same way.\n *\n * @param props - The attachments and the locale strings.\n * @returns The rendered attachment list.\n */\nexport function ChatAttachments({ attachments, strings }: ChatAttachmentsProps) {\n const images = attachments.filter((attachment) => attachment.kind === \"image\");\n const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);\n\n return (\n <div className={styles.attachments}>\n {attachments.map((attachment, index) => {\n const key = `${attachment.kind}-${attachment.url}-${index}`;\n if (attachment.kind === \"image\") {\n const position = images.indexOf(attachment);\n return (\n <button\n key={key}\n type=\"button\"\n className={styles.imageAttachment}\n onClick={() => setLightboxIndex(position)}\n >\n <img\n src={attachment.thumbnailUrl ?? attachment.url}\n alt={attachment.alt ?? attachment.name ?? \"\"}\n loading=\"lazy\"\n />\n </button>\n );\n }\n if (attachment.kind === \"video\") {\n return (\n <VideoPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n poster={attachment.thumbnailUrl}\n className={styles.videoAttachment}\n />\n );\n }\n if (attachment.kind === \"voice\") {\n return (\n <div key={key} className={styles.voiceAttachment}>\n <div className={styles.waveform} aria-hidden=\"true\">\n {(attachment.waveform ?? FLAT_WAVEFORM).map((peak, bar) => (\n <span\n key={bar}\n style={{\n height: `${Math.max(8, Math.min(100, peak * 100))}%`,\n }}\n />\n ))}\n </div>\n <AudioPlayer\n src={attachment.url}\n durationMs={attachment.durationMs}\n aria-label={strings.voiceNote}\n />\n </div>\n );\n }\n if (attachment.kind === \"audio\") {\n return (\n <AudioPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n />\n );\n }\n return (\n <a\n key={key}\n className={styles.fileAttachment}\n href={attachment.url}\n download={attachment.name ?? true}\n >\n <span className={styles.fileName}>\n {attachment.name ?? strings.download}\n </span>\n <span className={styles.fileMeta}>\n {[\n attachment.sizeBytes !== undefined\n ? formatBytes(attachment.sizeBytes)\n : null,\n attachment.durationMs !== undefined\n ? durationLabel(attachment.durationMs)\n : null,\n ]\n .filter(Boolean)\n .join(\" · \")}\n </span>\n </a>\n );\n })}\n\n {images.length > 0 && (\n <Lightbox\n items={images.map((image) => ({\n src: image.url,\n alt: image.alt ?? image.name ?? \"\",\n thumbnail: image.thumbnailUrl,\n }))}\n open={lightboxIndex !== null}\n index={lightboxIndex ?? 0}\n onIndexChange={setLightboxIndex}\n onClose={() => setLightboxIndex(null)}\n />\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAwBA,IAAM,IAAgB,MAAM,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAI;AAG3D,SAAS,EAAc,GAA4B;CAC/C,IAAM,IAAQ,KAAK,MAAM,IAAa,GAAI,GACpC,IAAU,KAAK,MAAM,IAAQ,EAAE,GAC/B,IAAU,IAAQ;CACxB,OAAO,GAAG,EAAQ,GAAG,OAAO,CAAO,CAAC,CAAC,SAAS,GAAG,GAAG;AACxD;AAmBA,SAAgB,EAAgB,EAAE,gBAAa,cAAiC;CAC5E,IAAM,IAAS,EAAY,QAAQ,MAAe,EAAW,SAAS,OAAO,GACvE,CAAC,GAAe,KAAoB,EAAwB,IAAI;CAEtE,OACI,kBAAC,OAAD;EAAK,WAAW,EAAO;EAAvB,UAAA,CACK,EAAY,KAAK,GAAY,MAAU;GACpC,IAAM,IAAM,GAAG,EAAW,KAAK,GAAG,EAAW,IAAI,GAAG;GACpD,IAAI,EAAW,SAAS,SAAS;IAC7B,IAAM,IAAW,EAAO,QAAQ,CAAU;IAC1C,OACI,kBAAC,UAAD;KAEI,MAAK;KACL,WAAW,EAAO;KAClB,eAAe,EAAiB,CAAQ;KAExC,UAAA,kBAAC,OAAD;MACI,KAAK,EAAW,gBAAgB,EAAW;MAC3C,KAAK,EAAW,OAAO,EAAW,QAAQ;MAC1C,SAAQ;KACX,CAAA;IACG,GAVC,CAUD;GAEhB;GA0CA,OAzCI,EAAW,SAAS,UAEhB,kBAAC,GAAD;IAEI,KAAK,EAAW;IAChB,YAAY,EAAW;IACvB,QAAQ,EAAW;IACnB,WAAW,EAAO;GACrB,GALQ,CAKR,IAGL,EAAW,SAAS,UAEhB,kBAAC,OAAD;IAAe,WAAW,EAAO;IAAjC,UAAA,CACI,kBAAC,OAAD;KAAK,WAAW,EAAO;KAAU,eAAY;KACvC,WAAA,EAAW,YAAY,EAAA,CAAe,KAAK,GAAM,MAC/C,kBAAC,QAAD,EAEI,OAAO,EACH,QAAQ,GAAG,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,IAAO,GAAG,CAAC,EAAE,GACtD,EACH,GAJQ,CAIR,CACJ;IACA,CAAA,GACL,kBAAC,GAAD;KACI,KAAK,EAAW;KAChB,YAAY,EAAW;KACvB,cAAY,EAAQ;IACvB,CAAA,CACA;GAhBK,GAAA,CAgBL,IAGT,EAAW,SAAS,UAEhB,kBAAC,GAAD;IAEI,KAAK,EAAW;IAChB,YAAY,EAAW;GAC1B,GAHQ,CAGR,IAIL,kBAAC,KAAD;IAEI,WAAW,EAAO;IAClB,MAAM,EAAW;IACjB,UAAU,EAAW,QAAQ;IAJjC,UAAA,CAMI,kBAAC,QAAD;KAAM,WAAW,EAAO;KACnB,UAAA,EAAW,QAAQ,EAAQ;IAC1B,CAAA,GACN,kBAAC,QAAD;KAAM,WAAW,EAAO;KACnB,UAAA,CACG,EAAW,cAAc,KAAA,IAEnB,OADA,EAAY,EAAW,SAAS,GAEtC,EAAW,eAAe,KAAA,IAEpB,OADA,EAAc,EAAW,UAAU,CAE7C,CAAC,CACI,OAAO,OAAO,CAAC,CACf,KAAK,KAAK;IACb,CAAA,CACP;GApBM,GAAA,CAoBN;EAEX,CAAC,GAEA,EAAO,SAAS,KACb,kBAAC,GAAD;GACI,OAAO,EAAO,KAAK,OAAW;IAC1B,KAAK,EAAM;IACX,KAAK,EAAM,OAAO,EAAM,QAAQ;IAChC,WAAW,EAAM;GACrB,EAAE;GACF,MAAM,MAAkB;GACxB,OAAO,KAAiB;GACxB,eAAe;GACf,eAAe,EAAiB,IAAI;EACvC,CAAA,CAEJ;;AAEb"}
1
+ {"version":3,"file":"ChatAttachments.js","names":[],"sources":["../../../src/components/Chat/ChatAttachments.tsx"],"sourcesContent":["import { useState } from \"react\";\n\nimport { formatBytes } from \"@/utils/numbers\";\nimport { safeAttachmentUrl } from \"@/utils/safe-url\";\n\nimport { AudioPlayer } from \"../AudioPlayer\";\nimport { Lightbox } from \"../Lightbox\";\nimport { VideoPlayer } from \"../VideoPlayer\";\nimport type { ChatAttachment } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** Strings this view needs, passed in so the locale table stays in one file. */\nexport interface ChatAttachmentStrings {\n voiceNote: string;\n download: string;\n}\n\nexport interface ChatAttachmentsProps {\n /** What this message carries. */\n attachments: readonly ChatAttachment[];\n /** Labels, from `chatStrings(locale)`. */\n strings: ChatAttachmentStrings;\n}\n\n/** Bars a voice note draws when the app gave no peaks. */\nconst FLAT_WAVEFORM = Array.from({ length: 28 }, () => 0.25);\n\n/** `1:04` from milliseconds — a duration, never a clock time. */\nfunction durationLabel(durationMs: number): string {\n const total = Math.round(durationMs / 1000);\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n return `${minutes}:${String(seconds).padStart(2, \"0\")}`;\n}\n\n/**\n * The media half of a bubble.\n *\n * Kept in its own module, and reached through `lazy()` from the thread, because\n * it pulls three of the heaviest components in the SDK — `Lightbox`,\n * `VideoPlayer`, `AudioPlayer` — and a support thread of plain text should not\n * pay for a photo viewer it never opens. The thread renders it only for messages\n * that actually carry an attachment, so that chunk is requested the first time\n * one appears.\n *\n * Nothing here is new UI: an image opens the SDK's lightbox, a video and an audio\n * clip use the SDK's players, and a file is a download row. That composition is\n * the whole point — every app was writing it again, the same way.\n *\n * The file row is the one place a sender's URL becomes an `href`, so it goes\n * through {@link safeAttachmentUrl}: `http:`, `https:`, `blob:` and relative URLs\n * are linked, anything else renders the same row without a link (#447). Media\n * `src` is left as given — an `<img>`, `<video>` or `<audio>` does not run a\n * `javascript:` URL.\n *\n * @param props - The attachments and the locale strings.\n * @returns The rendered attachment list.\n */\nexport function ChatAttachments({ attachments, strings }: ChatAttachmentsProps) {\n const images = attachments.filter((attachment) => attachment.kind === \"image\");\n const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);\n\n return (\n <div className={styles.attachments}>\n {attachments.map((attachment, index) => {\n const key = `${attachment.kind}-${attachment.url}-${index}`;\n if (attachment.kind === \"image\") {\n const position = images.indexOf(attachment);\n return (\n <button\n key={key}\n type=\"button\"\n className={styles.imageAttachment}\n onClick={() => setLightboxIndex(position)}\n >\n <img\n src={attachment.thumbnailUrl ?? attachment.url}\n alt={attachment.alt ?? attachment.name ?? \"\"}\n loading=\"lazy\"\n />\n </button>\n );\n }\n if (attachment.kind === \"video\") {\n return (\n <VideoPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n poster={attachment.thumbnailUrl}\n className={styles.videoAttachment}\n />\n );\n }\n if (attachment.kind === \"voice\") {\n return (\n <div key={key} className={styles.voiceAttachment}>\n <div className={styles.waveform} aria-hidden=\"true\">\n {(attachment.waveform ?? FLAT_WAVEFORM).map((peak, bar) => (\n <span\n key={bar}\n style={{\n height: `${Math.max(8, Math.min(100, peak * 100))}%`,\n }}\n />\n ))}\n </div>\n <AudioPlayer\n src={attachment.url}\n durationMs={attachment.durationMs}\n aria-label={strings.voiceNote}\n />\n </div>\n );\n }\n if (attachment.kind === \"audio\") {\n return (\n <AudioPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n />\n );\n }\n const href = safeAttachmentUrl(attachment.url);\n const row = (\n <>\n <span className={styles.fileName}>\n {attachment.name ?? strings.download}\n </span>\n <span className={styles.fileMeta}>\n {[\n attachment.sizeBytes !== undefined\n ? formatBytes(attachment.sizeBytes)\n : null,\n attachment.durationMs !== undefined\n ? durationLabel(attachment.durationMs)\n : null,\n ]\n .filter(Boolean)\n .join(\" · \")}\n </span>\n </>\n );\n return href ? (\n <a\n key={key}\n className={styles.fileAttachment}\n href={href}\n download={attachment.name ?? true}\n >\n {row}\n </a>\n ) : (\n <div key={key} className={styles.fileAttachment}>\n {row}\n </div>\n );\n })}\n\n {images.length > 0 && (\n <Lightbox\n items={images.map((image) => ({\n src: image.url,\n alt: image.alt ?? image.name ?? \"\",\n thumbnail: image.thumbnailUrl,\n }))}\n open={lightboxIndex !== null}\n index={lightboxIndex ?? 0}\n onIndexChange={setLightboxIndex}\n onClose={() => setLightboxIndex(null)}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- const e=require("../../utils/cn.cjs"),t=require("../CopyButton/CopyButton.cjs"),n=require("./tokenize.cjs"),r=require("./CodeBlock.module.cjs");let i=require("react"),a=require("react/jsx-runtime");function o({code:o,language:s,filename:c,showLineNumbers:l=!1,highlightLines:u,copyable:d=!0,maxHeight:f,wrap:p=!1,label:m,className:h,...g}){let _=(0,i.useMemo)(()=>o.replace(/^\n+/,``).replace(/\s+$/,``),[o]),v=(0,i.useMemo)(()=>n.tokenizeLines(_,s),[_,s]),y=n.resolveLanguage(s),b=(0,i.useMemo)(()=>new Set(u??[]),[u]),x=m??(c&&typeof c==`string`?`Código: ${c}`:`Bloco de código${y===`plain`?``:` em ${y}`}`),S=!!c||d;return(0,a.jsxs)(`div`,{className:e.cn(r.default.wrapper,h),...g,children:[S&&(0,a.jsxs)(`div`,{className:r.default.header,children:[(0,a.jsx)(`span`,{className:r.default.filename,children:c??(y===`plain`?``:y)}),d&&(0,a.jsx)(t.CopyButton,{value:_,className:r.default.copy})]}),(0,a.jsx)(`pre`,{className:e.cn(r.default.pre,p&&r.default.wrap),style:{maxHeight:f},tabIndex:0,role:`group`,"aria-label":x,children:(0,a.jsx)(`code`,{className:r.default.code,"data-language":y,children:v.map((t,n)=>(0,a.jsxs)(i.Fragment,{children:[(0,a.jsxs)(`span`,{className:e.cn(r.default.line,b.has(n+1)&&r.default.marked),children:[l&&(0,a.jsx)(`span`,{className:r.default.lineNumber,"aria-hidden":`true`,children:n+1}),(0,a.jsx)(`span`,{className:r.default.lineContent,children:t.map((e,t)=>(0,a.jsx)(`span`,{className:r.default[e.kind],"data-token":e.kind,children:e.value},t))})]}),n<v.length-1?`
1
+ const e=require("../../utils/cn.cjs"),t=require("../CopyButton/CopyButton.cjs"),n=require("./tokenize.cjs"),r=require("./CodeBlock.module.cjs");let i=require("react"),a=require("react/jsx-runtime");function o({code:o,language:s,filename:c,showLineNumbers:l=!1,highlightLines:u,copyable:d=!0,maxHeight:f,wrap:p=!1,label:m,className:h,...g}){let _=(0,i.useMemo)(()=>o.replace(/^\n+/,``).trimEnd(),[o]),v=(0,i.useMemo)(()=>n.tokenizeLines(_,s),[_,s]),y=n.resolveLanguage(s),b=(0,i.useMemo)(()=>new Set(u??[]),[u]),x=m??(c&&typeof c==`string`?`Código: ${c}`:`Bloco de código${y===`plain`?``:` em ${y}`}`),S=!!c||d;return(0,a.jsxs)(`div`,{className:e.cn(r.default.wrapper,h),...g,children:[S&&(0,a.jsxs)(`div`,{className:r.default.header,children:[(0,a.jsx)(`span`,{className:r.default.filename,children:c??(y===`plain`?``:y)}),d&&(0,a.jsx)(t.CopyButton,{value:_,className:r.default.copy})]}),(0,a.jsx)(`pre`,{className:e.cn(r.default.pre,p&&r.default.wrap),style:{maxHeight:f},tabIndex:0,role:`group`,"aria-label":x,children:(0,a.jsx)(`code`,{className:r.default.code,"data-language":y,children:v.map((t,n)=>(0,a.jsxs)(i.Fragment,{children:[(0,a.jsxs)(`span`,{className:e.cn(r.default.line,b.has(n+1)&&r.default.marked),children:[l&&(0,a.jsx)(`span`,{className:r.default.lineNumber,"aria-hidden":`true`,children:n+1}),(0,a.jsx)(`span`,{className:r.default.lineContent,children:t.map((e,t)=>(0,a.jsx)(`span`,{className:r.default[e.kind],"data-token":e.kind,children:e.value},t))})]}),n<v.length-1?`
2
2
  `:null]},n))})})]})}exports.CodeBlock=o;
3
3
  //# sourceMappingURL=CodeBlock.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"CodeBlock.cjs","names":[],"sources":["../../../src/components/CodeBlock/CodeBlock.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — code and language drive the scanner; filename,\n * showLineNumbers, highlightLines, maxHeight and wrap are the presentation a docs\n * page picks per block, and copyable plus label are the affordance and its\n * accessible name. All independent, all used in the SDK's own docs gallery.\n */\nimport { Fragment, useMemo } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { CopyButton } from \"../CopyButton\";\nimport { resolveLanguage, tokenizeLines } from \"./tokenize\";\nimport styles from \"./CodeBlock.module.css\";\n\nexport interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The source to display. Leading and trailing blank lines are trimmed. */\n code: string;\n /** Grammar name or alias (`ts`, `bash`, `json`…). Unknown values render plain. */\n language?: string;\n /** Shown in the header, usually a file path. */\n filename?: ReactNode;\n /** Number the lines. Default `false`. */\n showLineNumbers?: boolean;\n /** 1-based line numbers to mark as the point of the snippet. */\n highlightLines?: readonly number[];\n /** Offer a copy button. Default `true`. */\n copyable?: boolean;\n /** Cap the height in px or any CSS length; the body scrolls past it. */\n maxHeight?: number | string;\n /** Wrap long lines instead of scrolling sideways. Default `false`. */\n wrap?: boolean;\n /**\n * Accessible name for the code region. Defaults to naming the language, and\n * to the filename when there is one.\n */\n label?: string;\n}\n\n/**\n * A read-only code sample: syntax colours, optional line numbers, copy button.\n *\n * The `<pre>` is always focusable. A code block is scrollable and holds nothing\n * focusable inside, so without a tab stop a keyboard user can see there is more\n * code past the edge and has no way to reach it. It is the one scroll container\n * where the stop is unconditional rather than measured — a code sample is\n * expected to be reachable, read and selected on its own.\n *\n * Colours come from an approximate scanner, not a parser — see {@link tokenize}.\n *\n * @example\n * <CodeBlock code={snippet} language=\"ts\" filename=\"src/main.ts\" showLineNumbers />\n * <CodeBlock code={log} language=\"bash\" maxHeight={280} />\n */\nexport function CodeBlock({\n code,\n language,\n filename,\n showLineNumbers = false,\n highlightLines,\n copyable = true,\n maxHeight,\n wrap = false,\n label,\n className,\n ...rest\n}: CodeBlockProps) {\n const trimmed = useMemo(() => code.replace(/^\\n+/, \"\").replace(/\\s+$/, \"\"), [code]);\n const lines = useMemo(() => tokenizeLines(trimmed, language), [trimmed, language]);\n const resolved = resolveLanguage(language);\n const marked = useMemo(() => new Set(highlightLines ?? []), [highlightLines]);\n\n const name =\n label ??\n (filename && typeof filename === \"string\"\n ? `Código: ${filename}`\n : `Bloco de código${resolved === \"plain\" ? \"\" : ` em ${resolved}`}`);\n\n const hasHeader = Boolean(filename) || copyable;\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {hasHeader && (\n <div className={styles.header}>\n <span className={styles.filename}>\n {filename ?? (resolved === \"plain\" ? \"\" : resolved)}\n </span>\n {copyable && <CopyButton value={trimmed} className={styles.copy} />}\n </div>\n )}\n\n <pre\n className={cn(styles.pre, wrap && styles.wrap)}\n style={{ maxHeight }}\n tabIndex={0}\n role=\"group\"\n aria-label={name}\n >\n <code className={styles.code} data-language={resolved}>\n {lines.map((tokens, index) => (\n <Fragment key={index}>\n <span\n className={cn(styles.line, marked.has(index + 1) && styles.marked)}\n >\n {showLineNumbers && (\n /*\n * The number is decoration: it must not land in the\n * clipboard when the reader selects the snippet, and a\n * screen reader announcing \"one const two import\" adds\n * nothing. `user-select: none` in CSS plus this.\n */\n <span className={styles.lineNumber} aria-hidden=\"true\">\n {index + 1}\n </span>\n )}\n <span className={styles.lineContent}>\n {tokens.map((token, tokenIndex) => (\n <span\n key={tokenIndex}\n className={styles[token.kind]}\n data-token={token.kind}\n >\n {token.value}\n </span>\n ))}\n </span>\n </span>\n {/*\n * A real newline character, and outside the line box.\n * It has to be real so that selecting the snippet by\n * hand and copying yields the source instead of one\n * run-together line. It has to be outside because the\n * line is an inline-block: a newline within one is\n * consumed inside that box, and every line ends up\n * side by side on a single row. The last one is\n * dropped so the block does not end in blank space.\n */}\n {index < lines.length - 1 ? \"\\n\" : null}\n </Fragment>\n ))}\n </code>\n </pre>\n </div>\n );\n}\n"],"mappings":"sMAsDA,SAAgB,EAAU,CACtB,OACA,WACA,WACA,kBAAkB,GAClB,iBACA,WAAW,GACX,YACA,OAAO,GACP,QACA,YACA,GAAG,GACY,CACf,IAAM,GAAA,EAAU,EAAA,QAAA,KAAc,EAAK,QAAQ,OAAQ,EAAE,CAAC,CAAC,QAAQ,OAAQ,EAAE,EAAG,CAAC,CAAI,CAAC,EAC5E,GAAA,EAAQ,EAAA,QAAA,KAAc,EAAA,cAAc,EAAS,CAAQ,EAAG,CAAC,EAAS,CAAQ,CAAC,EAC3E,EAAW,EAAA,gBAAgB,CAAQ,EACnC,GAAA,EAAS,EAAA,QAAA,KAAc,IAAI,IAAI,GAAkB,CAAC,CAAC,EAAG,CAAC,CAAc,CAAC,EAEtE,EACF,IACC,GAAY,OAAO,GAAa,SAC3B,WAAW,IACX,kBAAkB,IAAa,QAAU,GAAK,OAAO,OAEzD,EAAY,EAAQ,GAAa,EAEvC,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,CAAS,EAAG,GAAI,EAAnD,SAAA,CACK,IACG,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAvB,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,SACnB,SAAA,IAAa,IAAa,QAAU,GAAK,EACxC,CAAA,EACL,IAAY,EAAA,EAAA,IAAA,CAAC,EAAA,WAAD,CAAY,MAAO,EAAS,UAAW,EAAA,QAAO,IAAO,CAAA,CACjE,CAGT,CAAA,GAAA,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,GAAG,EAAA,QAAO,IAAK,GAAQ,EAAA,QAAO,IAAI,EAC7C,MAAO,CAAE,WAAU,EACnB,SAAU,EACV,KAAK,QACL,aAAY,EAEZ,UAAA,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,KAAM,gBAAe,EACxC,SAAA,EAAM,KAAK,EAAQ,KAChB,EAAA,EAAA,KAAA,CAAC,EAAA,SAAD,CAAA,SAAA,EACI,EAAA,EAAA,KAAA,CAAC,OAAD,CACI,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,EAAO,IAAI,EAAQ,CAAC,GAAK,EAAA,QAAO,MAAM,EADrE,SAAA,CAGK,IAOG,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,WAAY,cAAY,OAC3C,SAAA,EAAQ,CACP,CAAA,GAEV,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,YACnB,SAAA,EAAO,KAAK,EAAO,KAChB,EAAA,EAAA,IAAA,CAAC,OAAD,CAEI,UAAW,EAAA,QAAO,EAAM,MACxB,aAAY,EAAM,KAEjB,SAAA,EAAM,KACL,EALG,CAKH,CACT,CACC,CAAA,CACJ,CAWL,CAAA,EAAA,EAAQ,EAAM,OAAS,EAAI;EAAO,IAC7B,CAAA,EAtCK,CAsCL,CACb,CACC,CAAA,CACL,CAAA,CACJ,GAEb"}
1
+ {"version":3,"file":"CodeBlock.cjs","names":[],"sources":["../../../src/components/CodeBlock/CodeBlock.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — code and language drive the scanner; filename,\n * showLineNumbers, highlightLines, maxHeight and wrap are the presentation a docs\n * page picks per block, and copyable plus label are the affordance and its\n * accessible name. All independent, all used in the SDK's own docs gallery.\n */\nimport { Fragment, useMemo } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { CopyButton } from \"../CopyButton\";\nimport { resolveLanguage, tokenizeLines } from \"./tokenize\";\nimport styles from \"./CodeBlock.module.css\";\n\nexport interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The source to display. Leading and trailing blank lines are trimmed. */\n code: string;\n /** Grammar name or alias (`ts`, `bash`, `json`…). Unknown values render plain. */\n language?: string;\n /** Shown in the header, usually a file path. */\n filename?: ReactNode;\n /** Number the lines. Default `false`. */\n showLineNumbers?: boolean;\n /** 1-based line numbers to mark as the point of the snippet. */\n highlightLines?: readonly number[];\n /** Offer a copy button. Default `true`. */\n copyable?: boolean;\n /** Cap the height in px or any CSS length; the body scrolls past it. */\n maxHeight?: number | string;\n /** Wrap long lines instead of scrolling sideways. Default `false`. */\n wrap?: boolean;\n /**\n * Accessible name for the code region. Defaults to naming the language, and\n * to the filename when there is one.\n */\n label?: string;\n}\n\n/**\n * A read-only code sample: syntax colours, optional line numbers, copy button.\n *\n * The `<pre>` is always focusable. A code block is scrollable and holds nothing\n * focusable inside, so without a tab stop a keyboard user can see there is more\n * code past the edge and has no way to reach it. It is the one scroll container\n * where the stop is unconditional rather than measured — a code sample is\n * expected to be reachable, read and selected on its own.\n *\n * Colours come from an approximate scanner, not a parser — see {@link tokenize}.\n *\n * @example\n * <CodeBlock code={snippet} language=\"ts\" filename=\"src/main.ts\" showLineNumbers />\n * <CodeBlock code={log} language=\"bash\" maxHeight={280} />\n */\nexport function CodeBlock({\n code,\n language,\n filename,\n showLineNumbers = false,\n highlightLines,\n copyable = true,\n maxHeight,\n wrap = false,\n label,\n className,\n ...rest\n}: CodeBlockProps) {\n const trimmed = useMemo(() => code.replace(/^\\n+/, \"\").trimEnd(), [code]);\n const lines = useMemo(() => tokenizeLines(trimmed, language), [trimmed, language]);\n const resolved = resolveLanguage(language);\n const marked = useMemo(() => new Set(highlightLines ?? []), [highlightLines]);\n\n const name =\n label ??\n (filename && typeof filename === \"string\"\n ? `Código: ${filename}`\n : `Bloco de código${resolved === \"plain\" ? \"\" : ` em ${resolved}`}`);\n\n const hasHeader = Boolean(filename) || copyable;\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {hasHeader && (\n <div className={styles.header}>\n <span className={styles.filename}>\n {filename ?? (resolved === \"plain\" ? \"\" : resolved)}\n </span>\n {copyable && <CopyButton value={trimmed} className={styles.copy} />}\n </div>\n )}\n\n <pre\n className={cn(styles.pre, wrap && styles.wrap)}\n style={{ maxHeight }}\n tabIndex={0}\n role=\"group\"\n aria-label={name}\n >\n <code className={styles.code} data-language={resolved}>\n {lines.map((tokens, index) => (\n <Fragment key={index}>\n <span\n className={cn(styles.line, marked.has(index + 1) && styles.marked)}\n >\n {showLineNumbers && (\n /*\n * The number is decoration: it must not land in the\n * clipboard when the reader selects the snippet, and a\n * screen reader announcing \"one const two import\" adds\n * nothing. `user-select: none` in CSS plus this.\n */\n <span className={styles.lineNumber} aria-hidden=\"true\">\n {index + 1}\n </span>\n )}\n <span className={styles.lineContent}>\n {tokens.map((token, tokenIndex) => (\n <span\n key={tokenIndex}\n className={styles[token.kind]}\n data-token={token.kind}\n >\n {token.value}\n </span>\n ))}\n </span>\n </span>\n {/*\n * A real newline character, and outside the line box.\n * It has to be real so that selecting the snippet by\n * hand and copying yields the source instead of one\n * run-together line. It has to be outside because the\n * line is an inline-block: a newline within one is\n * consumed inside that box, and every line ends up\n * side by side on a single row. The last one is\n * dropped so the block does not end in blank space.\n */}\n {index < lines.length - 1 ? \"\\n\" : null}\n </Fragment>\n ))}\n </code>\n </pre>\n </div>\n );\n}\n"],"mappings":"sMAsDA,SAAgB,EAAU,CACtB,OACA,WACA,WACA,kBAAkB,GAClB,iBACA,WAAW,GACX,YACA,OAAO,GACP,QACA,YACA,GAAG,GACY,CACf,IAAM,GAAA,EAAU,EAAA,QAAA,KAAc,EAAK,QAAQ,OAAQ,EAAE,CAAC,CAAC,QAAQ,EAAG,CAAC,CAAI,CAAC,EAClE,GAAA,EAAQ,EAAA,QAAA,KAAc,EAAA,cAAc,EAAS,CAAQ,EAAG,CAAC,EAAS,CAAQ,CAAC,EAC3E,EAAW,EAAA,gBAAgB,CAAQ,EACnC,GAAA,EAAS,EAAA,QAAA,KAAc,IAAI,IAAI,GAAkB,CAAC,CAAC,EAAG,CAAC,CAAc,CAAC,EAEtE,EACF,IACC,GAAY,OAAO,GAAa,SAC3B,WAAW,IACX,kBAAkB,IAAa,QAAU,GAAK,OAAO,OAEzD,EAAY,EAAQ,GAAa,EAEvC,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,CAAS,EAAG,GAAI,EAAnD,SAAA,CACK,IACG,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAvB,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,SACnB,SAAA,IAAa,IAAa,QAAU,GAAK,EACxC,CAAA,EACL,IAAY,EAAA,EAAA,IAAA,CAAC,EAAA,WAAD,CAAY,MAAO,EAAS,UAAW,EAAA,QAAO,IAAO,CAAA,CACjE,CAGT,CAAA,GAAA,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,GAAG,EAAA,QAAO,IAAK,GAAQ,EAAA,QAAO,IAAI,EAC7C,MAAO,CAAE,WAAU,EACnB,SAAU,EACV,KAAK,QACL,aAAY,EAEZ,UAAA,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,KAAM,gBAAe,EACxC,SAAA,EAAM,KAAK,EAAQ,KAChB,EAAA,EAAA,KAAA,CAAC,EAAA,SAAD,CAAA,SAAA,EACI,EAAA,EAAA,KAAA,CAAC,OAAD,CACI,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,EAAO,IAAI,EAAQ,CAAC,GAAK,EAAA,QAAO,MAAM,EADrE,SAAA,CAGK,IAOG,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,WAAY,cAAY,OAC3C,SAAA,EAAQ,CACP,CAAA,GAEV,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,YACnB,SAAA,EAAO,KAAK,EAAO,KAChB,EAAA,EAAA,IAAA,CAAC,OAAD,CAEI,UAAW,EAAA,QAAO,EAAM,MACxB,aAAY,EAAM,KAEjB,SAAA,EAAM,KACL,EALG,CAKH,CACT,CACC,CAAA,CACJ,CAWL,CAAA,EAAA,EAAQ,EAAM,OAAS,EAAI;EAAO,IAC7B,CAAA,EAtCK,CAsCL,CACb,CACC,CAAA,CACL,CAAA,CACJ,GAEb"}
@@ -6,7 +6,7 @@ import { Fragment as a, useMemo as o } from "react";
6
6
  import { jsx as s, jsxs as c } from "react/jsx-runtime";
7
7
  //#region src/components/CodeBlock/CodeBlock.tsx
8
8
  function l({ code: l, language: u, filename: d, showLineNumbers: f = !1, highlightLines: p, copyable: m = !0, maxHeight: h, wrap: g = !1, label: _, className: v, ...y }) {
9
- let b = o(() => l.replace(/^\n+/, "").replace(/\s+$/, ""), [l]), x = o(() => r(b, u), [b, u]), S = n(u), C = o(() => new Set(p ?? []), [p]), w = _ ?? (d && typeof d == "string" ? `Código: ${d}` : `Bloco de código${S === "plain" ? "" : ` em ${S}`}`), T = !!d || m;
9
+ let b = o(() => l.replace(/^\n+/, "").trimEnd(), [l]), x = o(() => r(b, u), [b, u]), S = n(u), C = o(() => new Set(p ?? []), [p]), w = _ ?? (d && typeof d == "string" ? `Código: ${d}` : `Bloco de código${S === "plain" ? "" : ` em ${S}`}`), T = !!d || m;
10
10
  return /* @__PURE__ */ c("div", {
11
11
  className: e(i.wrapper, v),
12
12
  ...y,
@@ -1 +1 @@
1
- {"version":3,"file":"CodeBlock.js","names":[],"sources":["../../../src/components/CodeBlock/CodeBlock.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — code and language drive the scanner; filename,\n * showLineNumbers, highlightLines, maxHeight and wrap are the presentation a docs\n * page picks per block, and copyable plus label are the affordance and its\n * accessible name. All independent, all used in the SDK's own docs gallery.\n */\nimport { Fragment, useMemo } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { CopyButton } from \"../CopyButton\";\nimport { resolveLanguage, tokenizeLines } from \"./tokenize\";\nimport styles from \"./CodeBlock.module.css\";\n\nexport interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The source to display. Leading and trailing blank lines are trimmed. */\n code: string;\n /** Grammar name or alias (`ts`, `bash`, `json`…). Unknown values render plain. */\n language?: string;\n /** Shown in the header, usually a file path. */\n filename?: ReactNode;\n /** Number the lines. Default `false`. */\n showLineNumbers?: boolean;\n /** 1-based line numbers to mark as the point of the snippet. */\n highlightLines?: readonly number[];\n /** Offer a copy button. Default `true`. */\n copyable?: boolean;\n /** Cap the height in px or any CSS length; the body scrolls past it. */\n maxHeight?: number | string;\n /** Wrap long lines instead of scrolling sideways. Default `false`. */\n wrap?: boolean;\n /**\n * Accessible name for the code region. Defaults to naming the language, and\n * to the filename when there is one.\n */\n label?: string;\n}\n\n/**\n * A read-only code sample: syntax colours, optional line numbers, copy button.\n *\n * The `<pre>` is always focusable. A code block is scrollable and holds nothing\n * focusable inside, so without a tab stop a keyboard user can see there is more\n * code past the edge and has no way to reach it. It is the one scroll container\n * where the stop is unconditional rather than measured — a code sample is\n * expected to be reachable, read and selected on its own.\n *\n * Colours come from an approximate scanner, not a parser — see {@link tokenize}.\n *\n * @example\n * <CodeBlock code={snippet} language=\"ts\" filename=\"src/main.ts\" showLineNumbers />\n * <CodeBlock code={log} language=\"bash\" maxHeight={280} />\n */\nexport function CodeBlock({\n code,\n language,\n filename,\n showLineNumbers = false,\n highlightLines,\n copyable = true,\n maxHeight,\n wrap = false,\n label,\n className,\n ...rest\n}: CodeBlockProps) {\n const trimmed = useMemo(() => code.replace(/^\\n+/, \"\").replace(/\\s+$/, \"\"), [code]);\n const lines = useMemo(() => tokenizeLines(trimmed, language), [trimmed, language]);\n const resolved = resolveLanguage(language);\n const marked = useMemo(() => new Set(highlightLines ?? []), [highlightLines]);\n\n const name =\n label ??\n (filename && typeof filename === \"string\"\n ? `Código: ${filename}`\n : `Bloco de código${resolved === \"plain\" ? \"\" : ` em ${resolved}`}`);\n\n const hasHeader = Boolean(filename) || copyable;\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {hasHeader && (\n <div className={styles.header}>\n <span className={styles.filename}>\n {filename ?? (resolved === \"plain\" ? \"\" : resolved)}\n </span>\n {copyable && <CopyButton value={trimmed} className={styles.copy} />}\n </div>\n )}\n\n <pre\n className={cn(styles.pre, wrap && styles.wrap)}\n style={{ maxHeight }}\n tabIndex={0}\n role=\"group\"\n aria-label={name}\n >\n <code className={styles.code} data-language={resolved}>\n {lines.map((tokens, index) => (\n <Fragment key={index}>\n <span\n className={cn(styles.line, marked.has(index + 1) && styles.marked)}\n >\n {showLineNumbers && (\n /*\n * The number is decoration: it must not land in the\n * clipboard when the reader selects the snippet, and a\n * screen reader announcing \"one const two import\" adds\n * nothing. `user-select: none` in CSS plus this.\n */\n <span className={styles.lineNumber} aria-hidden=\"true\">\n {index + 1}\n </span>\n )}\n <span className={styles.lineContent}>\n {tokens.map((token, tokenIndex) => (\n <span\n key={tokenIndex}\n className={styles[token.kind]}\n data-token={token.kind}\n >\n {token.value}\n </span>\n ))}\n </span>\n </span>\n {/*\n * A real newline character, and outside the line box.\n * It has to be real so that selecting the snippet by\n * hand and copying yields the source instead of one\n * run-together line. It has to be outside because the\n * line is an inline-block: a newline within one is\n * consumed inside that box, and every line ends up\n * side by side on a single row. The last one is\n * dropped so the block does not end in blank space.\n */}\n {index < lines.length - 1 ? \"\\n\" : null}\n </Fragment>\n ))}\n </code>\n </pre>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAsDA,SAAgB,EAAU,EACtB,SACA,aACA,aACA,qBAAkB,IAClB,mBACA,cAAW,IACX,cACA,UAAO,IACP,UACA,cACA,GAAG,KACY;CACf,IAAM,IAAU,QAAc,EAAK,QAAQ,QAAQ,EAAE,CAAC,CAAC,QAAQ,QAAQ,EAAE,GAAG,CAAC,CAAI,CAAC,GAC5E,IAAQ,QAAc,EAAc,GAAS,CAAQ,GAAG,CAAC,GAAS,CAAQ,CAAC,GAC3E,IAAW,EAAgB,CAAQ,GACnC,IAAS,QAAc,IAAI,IAAI,KAAkB,CAAC,CAAC,GAAG,CAAC,CAAc,CAAC,GAEtE,IACF,MACC,KAAY,OAAO,KAAa,WAC3B,WAAW,MACX,kBAAkB,MAAa,UAAU,KAAK,OAAO,QAEzD,IAAY,EAAQ,KAAa;CAEvC,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,SAAS,CAAS;EAAG,GAAI;EAAnD,UAAA,CACK,KACG,kBAAC,OAAD;GAAK,WAAW,EAAO;GAAvB,UAAA,CACI,kBAAC,QAAD;IAAM,WAAW,EAAO;IACnB,UAAA,MAAa,MAAa,UAAU,KAAK;GACxC,CAAA,GACL,KAAY,kBAAC,GAAD;IAAY,OAAO;IAAS,WAAW,EAAO;GAAO,CAAA,CACjE;EAGT,CAAA,GAAA,kBAAC,OAAD;GACI,WAAW,EAAG,EAAO,KAAK,KAAQ,EAAO,IAAI;GAC7C,OAAO,EAAE,aAAU;GACnB,UAAU;GACV,MAAK;GACL,cAAY;GAEZ,UAAA,kBAAC,QAAD;IAAM,WAAW,EAAO;IAAM,iBAAe;IACxC,UAAA,EAAM,KAAK,GAAQ,MAChB,kBAAC,GAAD,EAAA,UAAA,CACI,kBAAC,QAAD;KACI,WAAW,EAAG,EAAO,MAAM,EAAO,IAAI,IAAQ,CAAC,KAAK,EAAO,MAAM;KADrE,UAAA,CAGK,KAOG,kBAAC,QAAD;MAAM,WAAW,EAAO;MAAY,eAAY;MAC3C,UAAA,IAAQ;KACP,CAAA,GAEV,kBAAC,QAAD;MAAM,WAAW,EAAO;MACnB,UAAA,EAAO,KAAK,GAAO,MAChB,kBAAC,QAAD;OAEI,WAAW,EAAO,EAAM;OACxB,cAAY,EAAM;OAEjB,UAAA,EAAM;MACL,GALG,CAKH,CACT;KACC,CAAA,CACJ;IAWL,CAAA,GAAA,IAAQ,EAAM,SAAS,IAAI,OAAO,IAC7B,EAAA,GAtCK,CAsCL,CACb;GACC,CAAA;EACL,CAAA,CACJ;;AAEb"}
1
+ {"version":3,"file":"CodeBlock.js","names":[],"sources":["../../../src/components/CodeBlock/CodeBlock.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — code and language drive the scanner; filename,\n * showLineNumbers, highlightLines, maxHeight and wrap are the presentation a docs\n * page picks per block, and copyable plus label are the affordance and its\n * accessible name. All independent, all used in the SDK's own docs gallery.\n */\nimport { Fragment, useMemo } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { CopyButton } from \"../CopyButton\";\nimport { resolveLanguage, tokenizeLines } from \"./tokenize\";\nimport styles from \"./CodeBlock.module.css\";\n\nexport interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** The source to display. Leading and trailing blank lines are trimmed. */\n code: string;\n /** Grammar name or alias (`ts`, `bash`, `json`…). Unknown values render plain. */\n language?: string;\n /** Shown in the header, usually a file path. */\n filename?: ReactNode;\n /** Number the lines. Default `false`. */\n showLineNumbers?: boolean;\n /** 1-based line numbers to mark as the point of the snippet. */\n highlightLines?: readonly number[];\n /** Offer a copy button. Default `true`. */\n copyable?: boolean;\n /** Cap the height in px or any CSS length; the body scrolls past it. */\n maxHeight?: number | string;\n /** Wrap long lines instead of scrolling sideways. Default `false`. */\n wrap?: boolean;\n /**\n * Accessible name for the code region. Defaults to naming the language, and\n * to the filename when there is one.\n */\n label?: string;\n}\n\n/**\n * A read-only code sample: syntax colours, optional line numbers, copy button.\n *\n * The `<pre>` is always focusable. A code block is scrollable and holds nothing\n * focusable inside, so without a tab stop a keyboard user can see there is more\n * code past the edge and has no way to reach it. It is the one scroll container\n * where the stop is unconditional rather than measured — a code sample is\n * expected to be reachable, read and selected on its own.\n *\n * Colours come from an approximate scanner, not a parser — see {@link tokenize}.\n *\n * @example\n * <CodeBlock code={snippet} language=\"ts\" filename=\"src/main.ts\" showLineNumbers />\n * <CodeBlock code={log} language=\"bash\" maxHeight={280} />\n */\nexport function CodeBlock({\n code,\n language,\n filename,\n showLineNumbers = false,\n highlightLines,\n copyable = true,\n maxHeight,\n wrap = false,\n label,\n className,\n ...rest\n}: CodeBlockProps) {\n const trimmed = useMemo(() => code.replace(/^\\n+/, \"\").trimEnd(), [code]);\n const lines = useMemo(() => tokenizeLines(trimmed, language), [trimmed, language]);\n const resolved = resolveLanguage(language);\n const marked = useMemo(() => new Set(highlightLines ?? []), [highlightLines]);\n\n const name =\n label ??\n (filename && typeof filename === \"string\"\n ? `Código: ${filename}`\n : `Bloco de código${resolved === \"plain\" ? \"\" : ` em ${resolved}`}`);\n\n const hasHeader = Boolean(filename) || copyable;\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {hasHeader && (\n <div className={styles.header}>\n <span className={styles.filename}>\n {filename ?? (resolved === \"plain\" ? \"\" : resolved)}\n </span>\n {copyable && <CopyButton value={trimmed} className={styles.copy} />}\n </div>\n )}\n\n <pre\n className={cn(styles.pre, wrap && styles.wrap)}\n style={{ maxHeight }}\n tabIndex={0}\n role=\"group\"\n aria-label={name}\n >\n <code className={styles.code} data-language={resolved}>\n {lines.map((tokens, index) => (\n <Fragment key={index}>\n <span\n className={cn(styles.line, marked.has(index + 1) && styles.marked)}\n >\n {showLineNumbers && (\n /*\n * The number is decoration: it must not land in the\n * clipboard when the reader selects the snippet, and a\n * screen reader announcing \"one const two import\" adds\n * nothing. `user-select: none` in CSS plus this.\n */\n <span className={styles.lineNumber} aria-hidden=\"true\">\n {index + 1}\n </span>\n )}\n <span className={styles.lineContent}>\n {tokens.map((token, tokenIndex) => (\n <span\n key={tokenIndex}\n className={styles[token.kind]}\n data-token={token.kind}\n >\n {token.value}\n </span>\n ))}\n </span>\n </span>\n {/*\n * A real newline character, and outside the line box.\n * It has to be real so that selecting the snippet by\n * hand and copying yields the source instead of one\n * run-together line. It has to be outside because the\n * line is an inline-block: a newline within one is\n * consumed inside that box, and every line ends up\n * side by side on a single row. The last one is\n * dropped so the block does not end in blank space.\n */}\n {index < lines.length - 1 ? \"\\n\" : null}\n </Fragment>\n ))}\n </code>\n </pre>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAsDA,SAAgB,EAAU,EACtB,SACA,aACA,aACA,qBAAkB,IAClB,mBACA,cAAW,IACX,cACA,UAAO,IACP,UACA,cACA,GAAG,KACY;CACf,IAAM,IAAU,QAAc,EAAK,QAAQ,QAAQ,EAAE,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAI,CAAC,GAClE,IAAQ,QAAc,EAAc,GAAS,CAAQ,GAAG,CAAC,GAAS,CAAQ,CAAC,GAC3E,IAAW,EAAgB,CAAQ,GACnC,IAAS,QAAc,IAAI,IAAI,KAAkB,CAAC,CAAC,GAAG,CAAC,CAAc,CAAC,GAEtE,IACF,MACC,KAAY,OAAO,KAAa,WAC3B,WAAW,MACX,kBAAkB,MAAa,UAAU,KAAK,OAAO,QAEzD,IAAY,EAAQ,KAAa;CAEvC,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,SAAS,CAAS;EAAG,GAAI;EAAnD,UAAA,CACK,KACG,kBAAC,OAAD;GAAK,WAAW,EAAO;GAAvB,UAAA,CACI,kBAAC,QAAD;IAAM,WAAW,EAAO;IACnB,UAAA,MAAa,MAAa,UAAU,KAAK;GACxC,CAAA,GACL,KAAY,kBAAC,GAAD;IAAY,OAAO;IAAS,WAAW,EAAO;GAAO,CAAA,CACjE;EAGT,CAAA,GAAA,kBAAC,OAAD;GACI,WAAW,EAAG,EAAO,KAAK,KAAQ,EAAO,IAAI;GAC7C,OAAO,EAAE,aAAU;GACnB,UAAU;GACV,MAAK;GACL,cAAY;GAEZ,UAAA,kBAAC,QAAD;IAAM,WAAW,EAAO;IAAM,iBAAe;IACxC,UAAA,EAAM,KAAK,GAAQ,MAChB,kBAAC,GAAD,EAAA,UAAA,CACI,kBAAC,QAAD;KACI,WAAW,EAAG,EAAO,MAAM,EAAO,IAAI,IAAQ,CAAC,KAAK,EAAO,MAAM;KADrE,UAAA,CAGK,KAOG,kBAAC,QAAD;MAAM,WAAW,EAAO;MAAY,eAAY;MAC3C,UAAA,IAAQ;KACP,CAAA,GAEV,kBAAC,QAAD;MAAM,WAAW,EAAO;MACnB,UAAA,EAAO,KAAK,GAAO,MAChB,kBAAC,QAAD;OAEI,WAAW,EAAO,EAAM;OACxB,cAAY,EAAM;OAEjB,UAAA,EAAM;MACL,GALG,CAKH,CACT;KACC,CAAA,CACJ;IAWL,CAAA,GAAA,IAAQ,EAAM,SAAS,IAAI,OAAO,IAC7B,EAAA,GAtCK,CAsCL,CACb;GACC,CAAA;EACL,CAAA,CACJ;;AAEb"}
@@ -1,3 +1,3 @@
1
- var e={ts:`typescript`,typescript:`typescript`,js:`javascript`,javascript:`javascript`,mjs:`javascript`,cjs:`javascript`,tsx:`tsx`,jsx:`jsx`,json:`json`,jsonc:`json`,css:`css`,scss:`css`,html:`html`,xml:`html`,svg:`html`,sh:`bash`,bash:`bash`,shell:`bash`,zsh:`bash`,console:`bash`,py:`python`,python:`python`,sql:`sql`};function t(t){return t?e[t.toLowerCase()]??`plain`:`plain`}var n=`as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield`,r=`true|false|null|undefined|NaN|Infinity`,i={typescript:a(!1),javascript:a(!1),tsx:a(!0),jsx:a(!0),json:[{kind:`property`,pattern:/"(?:[^"\\]|\\.)*"(?=\s*:)/y},{kind:`string`,pattern:/"(?:[^"\\]|\\.)*"/y},{kind:`number`,pattern:/-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/y},{kind:`literal`,pattern:/\b(?:true|false|null)\b/y},{kind:`punctuation`,pattern:/[{}[\],:]/y}],css:[{kind:`comment`,pattern:/\/\*[\s\S]*?\*\//y},{kind:`string`,pattern:/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/y},{kind:`keyword`,pattern:/@[a-zA-Z-]+/y},{kind:`property`,pattern:/--?[a-zA-Z][\w-]*(?=\s*:)/y},{kind:`function`,pattern:/[a-zA-Z-]+(?=\()/y},{kind:`number`,pattern:/-?\d+(?:\.\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y},{kind:`punctuation`,pattern:/[{}();:,>+~]/y}],html:[{kind:`comment`,pattern:/<!--[\s\S]*?-->/y},{kind:`string`,pattern:/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/y},{kind:`tag`,pattern:/<\/?[a-zA-Z][\w:-]*/y},{kind:`attribute`,pattern:/[a-zA-Z][\w:-]*(?==)/y},{kind:`punctuation`,pattern:/\/?>|=/y}],bash:[{kind:`comment`,pattern:/#[^\n]*/y},{kind:`string`,pattern:/"(?:[^"\\]|\\.)*"|'[^']*'/y},{kind:`keyword`,pattern:/\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|cd|echo)\b/y},{kind:`property`,pattern:/\$\{?[A-Za-z_][\w]*\}?/y},{kind:`attribute`,pattern:/(?<=\s)--?[a-zA-Z][\w-]*/y},{kind:`punctuation`,pattern:/[|&;()<>]/y}],python:[{kind:`comment`,pattern:/#[^\n]*/y},{kind:`string`,pattern:/"""[\s\S]*?"""|'''[\s\S]*?'''|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/y},{kind:`keyword`,pattern:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\b/y},{kind:`literal`,pattern:/\b(?:True|False|None|self)\b/y},{kind:`number`,pattern:/\b\d+(?:\.\d+)?\b/y},{kind:`function`,pattern:/[A-Za-z_]\w*(?=\()/y},{kind:`punctuation`,pattern:/[{}[\]().,:;=+\-*/<>!]/y}],sql:[{kind:`comment`,pattern:/--[^\n]*|\/\*[\s\S]*?\*\//y},{kind:`string`,pattern:/'(?:[^']|'')*'/y},{kind:`keyword`,pattern:/\b(?:select|from|where|insert|into|values|update|set|delete|create|table|alter|drop|index|join|inner|left|right|outer|on|group|by|order|having|limit|offset|as|and|or|not|in|is|null|distinct|union|all|with|returning|primary|key|foreign|references|default)\b/iy},{kind:`number`,pattern:/\b\d+(?:\.\d+)?\b/y},{kind:`punctuation`,pattern:/[(),;*=<>]/y}],plain:[]};function a(e){let t=[{kind:`comment`,pattern:/\/\/[^\n]*|\/\*[\s\S]*?\*\//y},{kind:`string`,pattern:/`(?:[^`\\]|\\.)*`|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/y},{kind:`keyword`,pattern:RegExp(`\\b(?:${n})\\b`,`y`)},{kind:`literal`,pattern:RegExp(`\\b(?:${r})\\b`,`y`)},{kind:`number`,pattern:/\b(?:0[xX][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?)\b/y},{kind:`function`,pattern:/[A-Za-z_$][\w$]*(?=\s*\()/y},{kind:`punctuation`,pattern:/[{}[\]().,;:?!=<>+\-*/%&|^~]/y}];return e&&t.splice(2,0,{kind:`tag`,pattern:/<\/?[A-Za-z][\w.]*|\/>/y}),t}function o(e,n){let r=i[t(n)];if(r.length===0)return e===``?[]:[{kind:`plain`,value:e}];let a=[],o=0,s=0,c=t=>{t>o&&a.push({kind:`plain`,value:e.slice(o,t)})};for(;s<e.length;){let t=null;for(let n of r){n.pattern.lastIndex=s;let r=n.pattern.exec(e);if(r&&r[0].length>0){t={kind:n.kind,value:r[0]};break}}t?(c(s),a.push(t),s+=t.value.length,o=s):s++}return c(e.length),a}function s(e,t){let n=[[]];for(let r of o(e,t))r.value.split(`
1
+ var e={ts:`typescript`,typescript:`typescript`,js:`javascript`,javascript:`javascript`,mjs:`javascript`,cjs:`javascript`,tsx:`tsx`,jsx:`jsx`,json:`json`,jsonc:`json`,css:`css`,scss:`css`,html:`html`,xml:`html`,svg:`html`,sh:`bash`,bash:`bash`,shell:`bash`,zsh:`bash`,console:`bash`,py:`python`,python:`python`,sql:`sql`};function t(t){return t?e[t.toLowerCase()]??`plain`:`plain`}var n=`as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield`,r=`true|false|null|undefined|NaN|Infinity`,i={typescript:a(!1),javascript:a(!1),tsx:a(!0),jsx:a(!0),json:[{kind:`property`,pattern:/"(?:[^"\\\n]|\\[\s\S])*"(?=\s*:)/y},{kind:`string`,pattern:/"(?:[^"\\\n]|\\[\s\S])*"?/y},{kind:`number`,pattern:/-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/y},{kind:`literal`,pattern:/\b(?:true|false|null)\b/y},{kind:`punctuation`,pattern:/[{}[\],:]/y}],css:[{kind:`comment`,pattern:/\/\*[\s\S]*?(?:\*\/|$)/y},{kind:`string`,pattern:/"(?:[^"\\\n]|\\[\s\S])*"?|'(?:[^'\\\n]|\\[\s\S])*'?/y},{kind:`keyword`,pattern:/@[a-zA-Z-]+/y},{kind:`property`,pattern:/(?<![.#[\](),>+~:])[\w-]+(?=\s*:(?![\w/:]))/y},{kind:`function`,pattern:/[\w-]+(?=\()/y},{kind:`number`,pattern:/#[0-9a-fA-F]{3,8}\b/y},{kind:`number`,pattern:/(?<![\w-])-?\d+(?:\.\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y},{kind:`punctuation`,pattern:/[{}();:,>+~]/y},{kind:`plain`,pattern:/[\w-]+/y}],html:[{kind:`comment`,pattern:/<!--[\s\S]*?(?:-->|$)/y},{kind:`string`,pattern:/"(?:[^"\\]|\\[\s\S])*"?|'(?:[^'\\]|\\[\s\S])*'?/y},{kind:`tag`,pattern:/<\/?[a-zA-Z][\w:-]*/y},{kind:`attribute`,pattern:/[a-zA-Z][\w:-]*(?==)/y},{kind:`punctuation`,pattern:/\/?>|=/y},{kind:`plain`,pattern:/[a-zA-Z][\w:-]*/y}],bash:[{kind:`comment`,pattern:/#[^\n]*/y},{kind:`string`,pattern:/"(?:[^"\\]|\\[\s\S])*"?|'[^']*'?/y},{kind:`keyword`,pattern:/\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|cd|echo)\b/y},{kind:`property`,pattern:/\$\{?[A-Za-z_][\w]*\}?/y},{kind:`attribute`,pattern:/(?<=\s)--?[a-zA-Z][\w-]*/y},{kind:`punctuation`,pattern:/[|&;()<>]/y}],python:[{kind:`comment`,pattern:/#[^\n]*/y},{kind:`string`,pattern:/"""[\s\S]*?(?:"""|$)|'''[\s\S]*?(?:'''|$)|"(?:[^"\\\n]|\\[\s\S])*"?|'(?:[^'\\\n]|\\[\s\S])*'?/y},{kind:`keyword`,pattern:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\b/y},{kind:`literal`,pattern:/\b(?:True|False|None|self)\b/y},{kind:`number`,pattern:/\b\d+(?:\.\d+)?\b/y},{kind:`function`,pattern:/[A-Za-z_]\w*(?=\()/y},{kind:`punctuation`,pattern:/[{}[\]().,:;=+\-*/<>!]/y},{kind:`plain`,pattern:/[A-Za-z_]\w*/y}],sql:[{kind:`comment`,pattern:/--[^\n]*|\/\*[\s\S]*?(?:\*\/|$)/y},{kind:`string`,pattern:/'(?:[^']|'')*'?/y},{kind:`keyword`,pattern:/\b(?:select|from|where|insert|into|values|update|set|delete|create|table|alter|drop|index|join|inner|left|right|outer|on|group|by|order|having|limit|offset|as|and|or|not|in|is|null|distinct|union|all|with|returning|primary|key|foreign|references|default)\b/iy},{kind:`number`,pattern:/\b\d+(?:\.\d+)?\b/y},{kind:`punctuation`,pattern:/[(),;*=<>]/y}],plain:[]};function a(e){let t=[{kind:`comment`,pattern:/\/\/[^\n]*|\/\*[\s\S]*?(?:\*\/|$)/y},{kind:`string`,pattern:/`(?:[^`\\]|\\[\s\S])*`?|"(?:[^"\\\n]|\\[\s\S])*"?|(?<![\w$])'(?:[^'\\\n]|\\[\s\S])*'?/y},{kind:`keyword`,pattern:RegExp(`\\b(?:${n})(?![\\w$])`,`y`)},{kind:`literal`,pattern:RegExp(`\\b(?:${r})(?![\\w$])`,`y`)},{kind:`number`,pattern:/\b(?:0[xX][\da-fA-F_]+|\d[\d_]*(?:\.[\d_]+)?(?:[eE][+-]?\d+)?)(?![\w$])/y},{kind:`function`,pattern:/(?<![\w$])[A-Za-z_$][\w$]*(?=\s*\()/y},{kind:`punctuation`,pattern:/[{}[\]().,;:?!=<>+\-*/%&|^~]/y},{kind:`plain`,pattern:/(?:[A-Za-z_][\w$]*|\$+)/y}];return e&&(t.splice(1,0,{kind:`string`,pattern:/(?:(?<==)|(?<==\s))"[^"]*"?/y},{kind:`string`,pattern:/(?:(?<==)|(?<==\s))'[^']*'?/y}),t.splice(4,0,{kind:`tag`,pattern:/<\/?[A-Za-z][\w.]*|\/>/y})),t}function o(e,n){let r=i[t(n)];if(r.length===0)return e===``?[]:[{kind:`plain`,value:e}];let a=[],o=0,s=0,c=e=>{let t=a[a.length-1];t&&t.kind===`plain`&&e.kind===`plain`?t.value+=e.value:a.push(e)},l=t=>{t>o&&c({kind:`plain`,value:e.slice(o,t)})};for(;s<e.length;){let t=null;for(let n of r){n.pattern.lastIndex=s;let r=n.pattern.exec(e);if(r&&r[0].length>0){t={kind:n.kind,value:r[0]};break}}t?(l(s),c(t),s+=t.value.length,o=s):s++}return l(e.length),a}function s(e,t){let n=[[]];for(let r of o(e,t))r.value.split(`
2
2
  `).forEach((e,t)=>{t>0&&n.push([]),e!==``&&n[n.length-1].push({kind:r.kind,value:e})});return n}exports.resolveLanguage=t,exports.tokenize=o,exports.tokenizeLines=s;
3
3
  //# sourceMappingURL=tokenize.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokenize.cjs","names":[],"sources":["../../../src/components/CodeBlock/tokenize.ts"],"sourcesContent":["/** What a token is painted as. */\nexport type TokenKind =\n | \"plain\"\n | \"comment\"\n | \"string\"\n | \"number\"\n | \"keyword\"\n | \"literal\"\n | \"function\"\n | \"punctuation\"\n | \"tag\"\n | \"attribute\"\n | \"property\";\n\nexport interface Token {\n kind: TokenKind;\n value: string;\n}\n\n/** Languages the tokenizer knows. Anything else renders as plain text. */\nexport type CodeLanguage =\n | \"typescript\"\n | \"javascript\"\n | \"tsx\"\n | \"jsx\"\n | \"json\"\n | \"css\"\n | \"html\"\n | \"bash\"\n | \"python\"\n | \"sql\"\n | \"plain\";\n\n/** Aliases people actually type, mapped onto the grammars above. */\nconst ALIASES: Record<string, CodeLanguage> = {\n ts: \"typescript\",\n typescript: \"typescript\",\n js: \"javascript\",\n javascript: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n tsx: \"tsx\",\n jsx: \"jsx\",\n json: \"json\",\n jsonc: \"json\",\n css: \"css\",\n scss: \"css\",\n html: \"html\",\n xml: \"html\",\n svg: \"html\",\n sh: \"bash\",\n bash: \"bash\",\n shell: \"bash\",\n zsh: \"bash\",\n console: \"bash\",\n py: \"python\",\n python: \"python\",\n sql: \"sql\",\n};\n\n/**\n * Normalise a language name onto a known grammar.\n *\n * @param language - Whatever the caller passed, e.g. `\"ts\"` or `\"Shell\"`.\n * @returns The grammar to use; `\"plain\"` when there is no match.\n */\nexport function resolveLanguage(language: string | undefined): CodeLanguage {\n if (!language) return \"plain\";\n return ALIASES[language.toLowerCase()] ?? \"plain\";\n}\n\n/** One grammar rule: a sticky pattern and the kind it produces. */\ninterface Rule {\n kind: TokenKind;\n pattern: RegExp;\n}\n\nconst JS_KEYWORDS =\n \"as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield\";\n\nconst JS_LITERALS = \"true|false|null|undefined|NaN|Infinity\";\n\n/**\n * Rules per grammar, tried in order at each position.\n *\n * Order carries meaning: comments and strings come first so a keyword inside a\n * string stays a string. Every pattern is sticky (`y`) and anchored by\n * `lastIndex`, so a rule can only match where the scanner currently stands.\n */\nconst GRAMMARS: Record<CodeLanguage, Rule[]> = {\n typescript: jsRules(false),\n javascript: jsRules(false),\n tsx: jsRules(true),\n jsx: jsRules(true),\n json: [\n { kind: \"property\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"(?=\\s*:)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"/y },\n { kind: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?/y },\n { kind: \"literal\", pattern: /\\b(?:true|false|null)\\b/y },\n { kind: \"punctuation\", pattern: /[{}[\\],:]/y },\n ],\n css: [\n { kind: \"comment\", pattern: /\\/\\*[\\s\\S]*?\\*\\//y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y },\n { kind: \"keyword\", pattern: /@[a-zA-Z-]+/y },\n { kind: \"property\", pattern: /--?[a-zA-Z][\\w-]*(?=\\s*:)/y },\n { kind: \"function\", pattern: /[a-zA-Z-]+(?=\\()/y },\n { kind: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y },\n { kind: \"punctuation\", pattern: /[{}();:,>+~]/y },\n ],\n html: [\n { kind: \"comment\", pattern: /<!--[\\s\\S]*?-->/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y },\n { kind: \"tag\", pattern: /<\\/?[a-zA-Z][\\w:-]*/y },\n { kind: \"attribute\", pattern: /[a-zA-Z][\\w:-]*(?==)/y },\n { kind: \"punctuation\", pattern: /\\/?>|=/y },\n ],\n bash: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\.)*\"|'[^']*'/y },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|cd|echo)\\b/y,\n },\n { kind: \"property\", pattern: /\\$\\{?[A-Za-z_][\\w]*\\}?/y },\n { kind: \"attribute\", pattern: /(?<=\\s)--?[a-zA-Z][\\w-]*/y },\n { kind: \"punctuation\", pattern: /[|&;()<>]/y },\n ],\n python: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n {\n kind: \"string\",\n pattern: /\"\"\"[\\s\\S]*?\"\"\"|'''[\\s\\S]*?'''|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y,\n },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\\b/y,\n },\n { kind: \"literal\", pattern: /\\b(?:True|False|None|self)\\b/y },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"function\", pattern: /[A-Za-z_]\\w*(?=\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,:;=+\\-*/<>!]/y },\n ],\n sql: [\n { kind: \"comment\", pattern: /--[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//y },\n { kind: \"string\", pattern: /'(?:[^']|'')*'/y },\n {\n kind: \"keyword\",\n // Case-insensitive: SQL is written both ways and both should paint.\n pattern:\n /\\b(?:select|from|where|insert|into|values|update|set|delete|create|table|alter|drop|index|join|inner|left|right|outer|on|group|by|order|having|limit|offset|as|and|or|not|in|is|null|distinct|union|all|with|returning|primary|key|foreign|references|default)\\b/iy,\n },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"punctuation\", pattern: /[(),;*=<>]/y },\n ],\n plain: [],\n};\n\n/** The shared JavaScript-family rules, with JSX tags added for tsx/jsx. */\nfunction jsRules(withJsx: boolean): Rule[] {\n const rules: Rule[] = [\n { kind: \"comment\", pattern: /\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?\\*\\//y },\n {\n kind: \"string\",\n pattern: /`(?:[^`\\\\]|\\\\.)*`|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y,\n },\n { kind: \"keyword\", pattern: new RegExp(`\\\\b(?:${JS_KEYWORDS})\\\\b`, \"y\") },\n { kind: \"literal\", pattern: new RegExp(`\\\\b(?:${JS_LITERALS})\\\\b`, \"y\") },\n {\n kind: \"number\",\n pattern: /\\b(?:0[xX][\\da-fA-F_]+|\\d[\\d_]*(?:\\.[\\d_]+)?(?:[eE][+-]?\\d+)?)\\b/y,\n },\n { kind: \"function\", pattern: /[A-Za-z_$][\\w$]*(?=\\s*\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,;:?!=<>+\\-*/%&|^~]/y },\n ];\n if (withJsx) {\n // Before the keyword rule, so `<Button` reads as a tag and not as `<`.\n rules.splice(2, 0, { kind: \"tag\", pattern: /<\\/?[A-Za-z][\\w.]*|\\/>/y });\n }\n return rules;\n}\n\n/**\n * Split source into coloured tokens.\n *\n * This is a **scanner, not a parser**: it recognises comments, strings, numbers,\n * keywords and punctuation by pattern, and knows nothing about scope, types or\n * grammar. That is a deliberate ceiling. A real parser per language is a\n * dependency the size of the rest of the SDK, and the payoff — being right about\n * the corner cases in a documentation snippet — is small. Where it is unsure it\n * emits `plain`, which renders as ordinary text rather than as something wrong.\n *\n * Unknown languages produce a single `plain` token, so an unhighlighted block is\n * a normal outcome and never an error.\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns Tokens covering the input exactly, in order.\n *\n * @example\n * tokenize(\"const x = 1;\", \"ts\");\n * // [{kind: \"keyword\", value: \"const\"}, {kind: \"plain\", value: \" x \"}, …]\n */\nexport function tokenize(code: string, language: string | undefined): Token[] {\n const rules = GRAMMARS[resolveLanguage(language)];\n if (rules.length === 0) return code === \"\" ? [] : [{ kind: \"plain\", value: code }];\n\n const tokens: Token[] = [];\n let plainFrom = 0;\n let at = 0;\n\n const flushPlain = (until: number) => {\n if (until > plainFrom) tokens.push({ kind: \"plain\", value: code.slice(plainFrom, until) });\n };\n\n while (at < code.length) {\n let matched: Token | null = null;\n for (const rule of rules) {\n rule.pattern.lastIndex = at;\n const found = rule.pattern.exec(code);\n if (found && found[0].length > 0) {\n matched = { kind: rule.kind, value: found[0] };\n break;\n }\n }\n if (matched) {\n flushPlain(at);\n tokens.push(matched);\n at += matched.value.length;\n plainFrom = at;\n } else {\n at++;\n }\n }\n flushPlain(code.length);\n return tokens;\n}\n\n/**\n * The same tokens, split at newlines so each line can be rendered on its own.\n *\n * Line numbers and highlighted lines both need a per-line structure, and a token\n * is free to span a line break — a block comment usually does. Splitting here\n * keeps that out of the component.\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns One token array per line. Always at least one line.\n */\nexport function tokenizeLines(code: string, language: string | undefined): Token[][] {\n const lines: Token[][] = [[]];\n for (const token of tokenize(code, language)) {\n const pieces = token.value.split(\"\\n\");\n pieces.forEach((piece, index) => {\n if (index > 0) lines.push([]);\n if (piece !== \"\") lines[lines.length - 1].push({ kind: token.kind, value: piece });\n });\n }\n return lines;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tokenize.cjs","names":[],"sources":["../../../src/components/CodeBlock/tokenize.ts"],"sourcesContent":["/** What a token is painted as. */\nexport type TokenKind =\n | \"plain\"\n | \"comment\"\n | \"string\"\n | \"number\"\n | \"keyword\"\n | \"literal\"\n | \"function\"\n | \"punctuation\"\n | \"tag\"\n | \"attribute\"\n | \"property\";\n\nexport interface Token {\n kind: TokenKind;\n value: string;\n}\n\n/** Languages the tokenizer knows. Anything else renders as plain text. */\nexport type CodeLanguage =\n | \"typescript\"\n | \"javascript\"\n | \"tsx\"\n | \"jsx\"\n | \"json\"\n | \"css\"\n | \"html\"\n | \"bash\"\n | \"python\"\n | \"sql\"\n | \"plain\";\n\n/** Aliases people actually type, mapped onto the grammars above. */\nconst ALIASES: Record<string, CodeLanguage> = {\n ts: \"typescript\",\n typescript: \"typescript\",\n js: \"javascript\",\n javascript: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n tsx: \"tsx\",\n jsx: \"jsx\",\n json: \"json\",\n jsonc: \"json\",\n css: \"css\",\n scss: \"css\",\n html: \"html\",\n xml: \"html\",\n svg: \"html\",\n sh: \"bash\",\n bash: \"bash\",\n shell: \"bash\",\n zsh: \"bash\",\n console: \"bash\",\n py: \"python\",\n python: \"python\",\n sql: \"sql\",\n};\n\n/**\n * Normalise a language name onto a known grammar.\n *\n * @param language - Whatever the caller passed, e.g. `\"ts\"` or `\"Shell\"`.\n * @returns The grammar to use; `\"plain\"` when there is no match.\n */\nexport function resolveLanguage(language: string | undefined): CodeLanguage {\n if (!language) return \"plain\";\n return ALIASES[language.toLowerCase()] ?? \"plain\";\n}\n\n/** One grammar rule: a sticky pattern and the kind it produces. */\ninterface Rule {\n kind: TokenKind;\n pattern: RegExp;\n}\n\nconst JS_KEYWORDS =\n \"as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|keyof|let|new|of|private|protected|public|readonly|return|satisfies|set|static|super|switch|this|throw|try|type|typeof|var|void|while|with|yield\";\n\nconst JS_LITERALS = \"true|false|null|undefined|NaN|Infinity\";\n\n/**\n * Rules per grammar, tried in order at each position.\n *\n * Order carries meaning: comments and strings come first so a keyword inside a\n * string stays a string. Every pattern is sticky (`y`) and anchored by\n * `lastIndex`, so a rule can only match where the scanner currently stands.\n *\n * Every string and comment pattern makes its closing delimiter **optional** —\n * `\"...\"?`, `/*...*?` / `<!--...--?` — so an unterminated opener is consumed as a\n * single token instead of failing and being rescanned from the next opener.\n * Without that, a `\"` followed by many escaped quotes was re-read to the end of\n * the input from each opening quote, quadratic in the input (#477).\n *\n * A well-formed string that closes on a later line stays one token where the\n * language allows the newline: bash, SQL, HTML and the JavaScript template\n * literal carry a raw `\\n`, and every grammar lets an escape cross the line feed\n * (`\\\\[\\s\\S]`), so `\"a\\` + LF + `b\"` is one string. A quote string that cannot\n * hold a raw newline — the JavaScript and JSON `\"`/`'`, CSS, the Python\n * `\"`/`'` — still ends at the line when it is unterminated, which is the only\n * case the optional close cuts at the line. A JSX attribute value is the\n * exception in the JavaScript family: `=\"…\"` has its own rule, ahead of the\n * plain JS strings, so it may hold a raw line feed — `<input placeholder=\"a` +\n * LF + `b\">` is one string where the plain JS rule would have ended it at the\n * line. The rule matches at the `=` and looks behind for it, so `=` keeps its\n * paint; `(?<==\\s)` covers the spaced `= \"…\"` form too.\n *\n * Each grammar also ends with a `plain` rule that consumes a run of identifier\n * characters in one step. The rules that need a lookahead — the JavaScript\n * `function`, the HTML `attribute`, the CSS `property` and `function`, the\n * Python `function` — match a whole run and then fail if the character after it\n * is not the expected one; with only `at++` to fall back on, the scanner retried\n * that run from every character in it. A code fence holding a long run of word\n * characters and no punctuation (a base64 blob, a minified token) took 6 s at\n * 80 KB and 25 s at 160 KB; the fallback consumes the run once, so the scan\n * stays linear (#477 sweep).\n *\n * The `plain` rule matches an identifier run while the scanner's fallback\n * collects whatever no rule matched, so one plain stretch arrives in pieces;\n * `tokenize` fuses adjacent `plain` tokens back into one, which is the array\n * shape — and the one span per run — that predates the fallback (#477).\n *\n * The JavaScript fallback does **not** start at `$` — it takes a `$` run and an\n * identifier run separately — so a leading `$type`, `$default` and `$this` keep\n * the `$` plain and the name a keyword. Inside an identifier the `$` is an\n * ordinary character, so `foo$this`, `a$true` and `x$1` are one `plain` token\n * instead of painting `this`, `true` and `1` out of them; the `'` string rule\n * carries the same boundary in a lookbehind, so an apostrophe in prose\n * (`Don't`) does not open a string that runs to the end of the input.\n *\n * The same boundary closes the JavaScript keyword, literal and number rules:\n * they end at `(?![\\w$])` rather than `\\b`, because `\\b` holds before a `$`, so\n * `if$foo` would paint `if` and leave the scanner standing on the `$` in the\n * middle of the identifier. The `function` rule only starts where no identifier\n * character precedes it (`(?<![\\w$])`). Without that lookbehind a rule that\n * stopped inside a run — the leading-`$` split above stops at every `$1` of\n * `$1$1$1…` — handed the rest of the run to `function`, which read it to the end\n * and failed for want of a `(`, once per `$`: 80 KB of `$1` took 2.8 s and\n * 120 KB of `if$` 4.2 s (#498). With both boundaries every rule that can start\n * inside a run fails at once or consumes a bounded piece of it, so each run is\n * read a constant number of times and `if$foo(`, `this$x(` and `true$y(` paint\n * as the one called identifier they are.\n *\n * The CSS `property` and `function` rules match a whole word where it stands —\n * `font-size` and `translate3d` paint as one token rather than splitting the\n * tail — and a `#rgb`/`#rrggbb` colour paints as a number. A word before a\n * colon only counts as a property when the colon starts a value boundary\n * (whitespace, `;`, `}`): after a `.`/`#`/`(`/`,`/`>`/`+`/`~`/`:` it is a\n * selector, and when the colon is followed by an identifier character it could\n * be a pseudo-class (`li:hover`) or a declaration with no space (`color:red`) —\n * neither can be told apart without a parser, so both stay `plain`, which is\n * what `origin/main` painted (`#477`).\n */\nconst GRAMMARS: Record<CodeLanguage, Rule[]> = {\n typescript: jsRules(false),\n javascript: jsRules(false),\n tsx: jsRules(true),\n jsx: jsRules(true),\n json: [\n { kind: \"property\", pattern: /\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"(?=\\s*:)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?/y },\n { kind: \"number\", pattern: /-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?/y },\n { kind: \"literal\", pattern: /\\b(?:true|false|null)\\b/y },\n { kind: \"punctuation\", pattern: /[{}[\\],:]/y },\n ],\n css: [\n { kind: \"comment\", pattern: /\\/\\*[\\s\\S]*?(?:\\*\\/|$)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?|'(?:[^'\\\\\\n]|\\\\[\\s\\S])*'?/y },\n { kind: \"keyword\", pattern: /@[a-zA-Z-]+/y },\n { kind: \"property\", pattern: /(?<![.#[\\](),>+~:])[\\w-]+(?=\\s*:(?![\\w/:]))/y },\n { kind: \"function\", pattern: /[\\w-]+(?=\\()/y },\n { kind: \"number\", pattern: /#[0-9a-fA-F]{3,8}\\b/y },\n { kind: \"number\", pattern: /(?<![\\w-])-?\\d+(?:\\.\\d+)?(?:px|rem|em|%|s|ms|vh|vw|fr|deg)?/y },\n { kind: \"punctuation\", pattern: /[{}();:,>+~]/y },\n { kind: \"plain\", pattern: /[\\w-]+/y },\n ],\n html: [\n { kind: \"comment\", pattern: /<!--[\\s\\S]*?(?:-->|$)/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\[\\s\\S])*\"?|'(?:[^'\\\\]|\\\\[\\s\\S])*'?/y },\n { kind: \"tag\", pattern: /<\\/?[a-zA-Z][\\w:-]*/y },\n { kind: \"attribute\", pattern: /[a-zA-Z][\\w:-]*(?==)/y },\n { kind: \"punctuation\", pattern: /\\/?>|=/y },\n { kind: \"plain\", pattern: /[a-zA-Z][\\w:-]*/y },\n ],\n bash: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n { kind: \"string\", pattern: /\"(?:[^\"\\\\]|\\\\[\\s\\S])*\"?|'[^']*'?/y },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|cd|echo)\\b/y,\n },\n { kind: \"property\", pattern: /\\$\\{?[A-Za-z_][\\w]*\\}?/y },\n { kind: \"attribute\", pattern: /(?<=\\s)--?[a-zA-Z][\\w-]*/y },\n { kind: \"punctuation\", pattern: /[|&;()<>]/y },\n ],\n python: [\n { kind: \"comment\", pattern: /#[^\\n]*/y },\n {\n kind: \"string\",\n pattern:\n /\"\"\"[\\s\\S]*?(?:\"\"\"|$)|'''[\\s\\S]*?(?:'''|$)|\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?|'(?:[^'\\\\\\n]|\\\\[\\s\\S])*'?/y,\n },\n {\n kind: \"keyword\",\n pattern:\n /\\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield)\\b/y,\n },\n { kind: \"literal\", pattern: /\\b(?:True|False|None|self)\\b/y },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"function\", pattern: /[A-Za-z_]\\w*(?=\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,:;=+\\-*/<>!]/y },\n { kind: \"plain\", pattern: /[A-Za-z_]\\w*/y },\n ],\n sql: [\n { kind: \"comment\", pattern: /--[^\\n]*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)/y },\n { kind: \"string\", pattern: /'(?:[^']|'')*'?/y },\n {\n kind: \"keyword\",\n // Case-insensitive: SQL is written both ways and both should paint.\n pattern:\n /\\b(?:select|from|where|insert|into|values|update|set|delete|create|table|alter|drop|index|join|inner|left|right|outer|on|group|by|order|having|limit|offset|as|and|or|not|in|is|null|distinct|union|all|with|returning|primary|key|foreign|references|default)\\b/iy,\n },\n { kind: \"number\", pattern: /\\b\\d+(?:\\.\\d+)?\\b/y },\n { kind: \"punctuation\", pattern: /[(),;*=<>]/y },\n ],\n plain: [],\n};\n\n/** The shared JavaScript-family rules, with JSX tags added for tsx/jsx. */\nfunction jsRules(withJsx: boolean): Rule[] {\n const rules: Rule[] = [\n { kind: \"comment\", pattern: /\\/\\/[^\\n]*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)/y },\n {\n kind: \"string\",\n pattern:\n /`(?:[^`\\\\]|\\\\[\\s\\S])*`?|\"(?:[^\"\\\\\\n]|\\\\[\\s\\S])*\"?|(?<![\\w$])'(?:[^'\\\\\\n]|\\\\[\\s\\S])*'?/y,\n },\n { kind: \"keyword\", pattern: new RegExp(`\\\\b(?:${JS_KEYWORDS})(?![\\\\w$])`, \"y\") },\n { kind: \"literal\", pattern: new RegExp(`\\\\b(?:${JS_LITERALS})(?![\\\\w$])`, \"y\") },\n {\n kind: \"number\",\n pattern: /\\b(?:0[xX][\\da-fA-F_]+|\\d[\\d_]*(?:\\.[\\d_]+)?(?:[eE][+-]?\\d+)?)(?![\\w$])/y,\n },\n { kind: \"function\", pattern: /(?<![\\w$])[A-Za-z_$][\\w$]*(?=\\s*\\()/y },\n { kind: \"punctuation\", pattern: /[{}[\\]().,;:?!=<>+\\-*/%&|^~]/y },\n { kind: \"plain\", pattern: /(?:[A-Za-z_][\\w$]*|\\$+)/y },\n ];\n if (withJsx) {\n rules.splice(\n 1,\n 0,\n { kind: \"string\", pattern: /(?:(?<==)|(?<==\\s))\"[^\"]*\"?/y },\n { kind: \"string\", pattern: /(?:(?<==)|(?<==\\s))'[^']*'?/y },\n );\n rules.splice(4, 0, { kind: \"tag\", pattern: /<\\/?[A-Za-z][\\w.]*|\\/>/y });\n }\n return rules;\n}\n\n/**\n * Split source into coloured tokens.\n *\n * This is a **scanner, not a parser**: it recognises comments, strings, numbers,\n * keywords and punctuation by pattern, and knows nothing about scope, types or\n * grammar. That is a deliberate ceiling. A real parser per language is a\n * dependency the size of the rest of the SDK, and the payoff — being right about\n * the corner cases in a documentation snippet — is small. Where it is unsure it\n * emits `plain`, which renders as ordinary text rather than as something wrong.\n *\n * Unknown languages produce a single `plain` token, so an unhighlighted block is\n * a normal outcome and never an error.\n *\n * The scan is linear in the source: an unterminated string or comment becomes a\n * single token that runs to its close, or to the end of the input — or, for a\n * quote that cannot hold a newline and never closes, to the end of the line — so\n * the openers inside it are never rescanned (#477).\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns Tokens covering the input exactly, in order.\n *\n * @example\n * tokenize(\"const x = 1;\", \"ts\");\n * // [{kind: \"keyword\", value: \"const\"}, {kind: \"plain\", value: \" \"}, …]\n */\nexport function tokenize(code: string, language: string | undefined): Token[] {\n const rules = GRAMMARS[resolveLanguage(language)];\n if (rules.length === 0) return code === \"\" ? [] : [{ kind: \"plain\", value: code }];\n\n const tokens: Token[] = [];\n let plainFrom = 0;\n let at = 0;\n\n /**\n * Append a token, fusing it with the previous one when both are `plain`.\n *\n * The `plain` rule matches an identifier run while the scanner's fallback\n * collects the characters no rule matched, so one plain stretch of source\n * arrives in pieces. Fusing them keeps the array shape `origin/main`\n * produced, where a plain run was a single token and each got one span in\n * the DOM rather than one per identifier (#477).\n */\n const push = (token: Token) => {\n const last = tokens[tokens.length - 1];\n if (last && last.kind === \"plain\" && token.kind === \"plain\") {\n last.value += token.value;\n } else {\n tokens.push(token);\n }\n };\n\n const flushPlain = (until: number) => {\n if (until > plainFrom) push({ kind: \"plain\", value: code.slice(plainFrom, until) });\n };\n\n while (at < code.length) {\n let matched: Token | null = null;\n for (const rule of rules) {\n rule.pattern.lastIndex = at;\n const found = rule.pattern.exec(code);\n if (found && found[0].length > 0) {\n matched = { kind: rule.kind, value: found[0] };\n break;\n }\n }\n if (matched) {\n flushPlain(at);\n push(matched);\n at += matched.value.length;\n plainFrom = at;\n } else {\n at++;\n }\n }\n flushPlain(code.length);\n return tokens;\n}\n\n/**\n * The same tokens, split at newlines so each line can be rendered on its own.\n *\n * Line numbers and highlighted lines both need a per-line structure, and a token\n * is free to span a line break — a block comment usually does. Splitting here\n * keeps that out of the component.\n *\n * @param code - The source.\n * @param language - Grammar name or alias.\n * @returns One token array per line. Always at least one line.\n */\nexport function tokenizeLines(code: string, language: string | undefined): Token[][] {\n const lines: Token[][] = [[]];\n for (const token of tokenize(code, language)) {\n const pieces = token.value.split(\"\\n\");\n pieces.forEach((piece, index) => {\n if (index > 0) lines.push([]);\n if (piece !== \"\") lines[lines.length - 1].push({ kind: token.kind, value: piece });\n });\n }\n return lines;\n}\n"],"mappings":"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"}