@hex-core/components 1.9.0 → 1.10.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 (146) hide show
  1. package/dist/_tsup-dts-rollup.d.ts +575 -18
  2. package/dist/accordion.js.map +1 -1
  3. package/dist/alert-dialog.js.map +1 -1
  4. package/dist/alert.js.map +1 -1
  5. package/dist/arc.js.map +1 -1
  6. package/dist/attachment.js.map +1 -1
  7. package/dist/audio-player.js.map +1 -1
  8. package/dist/audio-waveform.js.map +1 -1
  9. package/dist/auth-forgot-password.js.map +1 -1
  10. package/dist/auth-reset-password.js.map +1 -1
  11. package/dist/auth-sign-in-split.js.map +1 -1
  12. package/dist/auth-sign-up-card.js.map +1 -1
  13. package/dist/auth-verify-email.js.map +1 -1
  14. package/dist/auth-verify-otp.js.map +1 -1
  15. package/dist/avatar.js.map +1 -1
  16. package/dist/badge.js.map +1 -1
  17. package/dist/branch.d.ts +2 -0
  18. package/dist/branch.js +136 -0
  19. package/dist/branch.js.map +1 -0
  20. package/dist/breadcrumb.js.map +1 -1
  21. package/dist/button.js.map +1 -1
  22. package/dist/calendar.js.map +1 -1
  23. package/dist/canvas.js.map +1 -1
  24. package/dist/card.js.map +1 -1
  25. package/dist/chain-of-thought.d.ts +3 -0
  26. package/dist/chain-of-thought.js +119 -0
  27. package/dist/chain-of-thought.js.map +1 -0
  28. package/dist/checkbox.js.map +1 -1
  29. package/dist/chord.js.map +1 -1
  30. package/dist/citation.js.map +1 -1
  31. package/dist/cloze.js.map +1 -1
  32. package/dist/cluster.js.map +1 -1
  33. package/dist/code-block-copy.js.map +1 -1
  34. package/dist/code-block.js.map +1 -1
  35. package/dist/color-picker.js.map +1 -1
  36. package/dist/combobox.js.map +1 -1
  37. package/dist/command.js.map +1 -1
  38. package/dist/compare-table.js.map +1 -1
  39. package/dist/composer.js.map +1 -1
  40. package/dist/container.js.map +1 -1
  41. package/dist/context-menu.js.map +1 -1
  42. package/dist/conversation.d.ts +3 -0
  43. package/dist/conversation.js +358 -0
  44. package/dist/conversation.js.map +1 -0
  45. package/dist/data-table.js.map +1 -1
  46. package/dist/date-picker.js.map +1 -1
  47. package/dist/deck.js.map +1 -1
  48. package/dist/dendrogram.js.map +1 -1
  49. package/dist/diagram.js.map +1 -1
  50. package/dist/dialog.js.map +1 -1
  51. package/dist/drawer.js.map +1 -1
  52. package/dist/dropdown-menu.js.map +1 -1
  53. package/dist/dropzone.js.map +1 -1
  54. package/dist/empty.js.map +1 -1
  55. package/dist/error-state.js.map +1 -1
  56. package/dist/file-tree.js.map +1 -1
  57. package/dist/flashcard.js.map +1 -1
  58. package/dist/flowchart.js.map +1 -1
  59. package/dist/form.js.map +1 -1
  60. package/dist/funnel.js.map +1 -1
  61. package/dist/gantt.js.map +1 -1
  62. package/dist/grid.js.map +1 -1
  63. package/dist/hover-card.js.map +1 -1
  64. package/dist/image-occlusion.js.map +1 -1
  65. package/dist/index.d.ts +21 -0
  66. package/dist/index.js +1011 -13
  67. package/dist/index.js.map +1 -1
  68. package/dist/inline-citation.d.ts +2 -0
  69. package/dist/inline-citation.js +108 -0
  70. package/dist/inline-citation.js.map +1 -0
  71. package/dist/input-otp.js.map +1 -1
  72. package/dist/input.js.map +1 -1
  73. package/dist/label.js.map +1 -1
  74. package/dist/loading-indicator.js.map +1 -1
  75. package/dist/loading.js.map +1 -1
  76. package/dist/markdown.d.ts +1 -0
  77. package/dist/markdown.js +784 -4
  78. package/dist/markdown.js.map +1 -1
  79. package/dist/matrix.js.map +1 -1
  80. package/dist/menubar.js.map +1 -1
  81. package/dist/message-actions.js.map +1 -1
  82. package/dist/message-list.js.map +1 -1
  83. package/dist/message.js.map +1 -1
  84. package/dist/mind-map.js.map +1 -1
  85. package/dist/multi-combobox.js.map +1 -1
  86. package/dist/navigation-menu.js.map +1 -1
  87. package/dist/org-chart.js.map +1 -1
  88. package/dist/pagination.js.map +1 -1
  89. package/dist/plan.d.ts +3 -0
  90. package/dist/plan.js +183 -0
  91. package/dist/plan.js.map +1 -0
  92. package/dist/popover.js.map +1 -1
  93. package/dist/progress.js.map +1 -1
  94. package/dist/pyramid.js.map +1 -1
  95. package/dist/quiz.js.map +1 -1
  96. package/dist/radio-group.js.map +1 -1
  97. package/dist/reasoning.js.map +1 -1
  98. package/dist/resizable.js.map +1 -1
  99. package/dist/sankey.js.map +1 -1
  100. package/dist/schemas.d.ts +8 -0
  101. package/dist/schemas.js +774 -17
  102. package/dist/schemas.js.map +1 -1
  103. package/dist/scroll-area.js.map +1 -1
  104. package/dist/select.js.map +1 -1
  105. package/dist/separator.js.map +1 -1
  106. package/dist/sequence.js.map +1 -1
  107. package/dist/sheet.js.map +1 -1
  108. package/dist/shimmer.d.ts +2 -0
  109. package/dist/shimmer.js +39 -0
  110. package/dist/shimmer.js.map +1 -0
  111. package/dist/sidebar.js.map +1 -1
  112. package/dist/skeleton.js.map +1 -1
  113. package/dist/slider.js.map +1 -1
  114. package/dist/sources.d.ts +3 -0
  115. package/dist/sources.js +164 -0
  116. package/dist/sources.js.map +1 -0
  117. package/dist/spaced-repetition.js.map +1 -1
  118. package/dist/spacer.js.map +1 -1
  119. package/dist/speech-recognition.js.map +1 -1
  120. package/dist/stack.js.map +1 -1
  121. package/dist/stepper.js.map +1 -1
  122. package/dist/suggestion.js.map +1 -1
  123. package/dist/sunburst.js.map +1 -1
  124. package/dist/switch.js.map +1 -1
  125. package/dist/table.js.map +1 -1
  126. package/dist/tabs.js.map +1 -1
  127. package/dist/tag.js.map +1 -1
  128. package/dist/task.d.ts +3 -0
  129. package/dist/task.js +189 -0
  130. package/dist/task.js.map +1 -0
  131. package/dist/terminal.js +11 -0
  132. package/dist/terminal.js.map +1 -1
  133. package/dist/textarea.js.map +1 -1
  134. package/dist/time-axis.js.map +1 -1
  135. package/dist/time-picker.js.map +1 -1
  136. package/dist/timeline.js.map +1 -1
  137. package/dist/toggle-group.js.map +1 -1
  138. package/dist/toggle.js.map +1 -1
  139. package/dist/tool-call.js +5 -6
  140. package/dist/tool-call.js.map +1 -1
  141. package/dist/toolbar.js.map +1 -1
  142. package/dist/tooltip.js.map +1 -1
  143. package/dist/tree-map.js.map +1 -1
  144. package/dist/tree.js.map +1 -1
  145. package/dist/venn.js.map +1 -1
  146. package/package.json +8 -3
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/utils.ts","../src/primitives/switch/switch.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACEA,IAAM,MAAA,GAAe,KAAA,CAAA,UAAA;AAAA,EACpB,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,qBACzB,GAAA;AAAA,IAAiB,eAAA,CAAA,IAAA;AAAA,IAAhB;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACV,yIAAA;AAAA,QACA,iEAAA;AAAA,QACA,WAAA;AAAA,QACA,0IAAA;AAAA,QACA,iDAAA;AAAA,QACA,iEAAA;AAAA;AAAA;AAAA,QAGA,yFAAA;AAAA,QACA,iBAAA;AAAA,QACA;AAAA,OACD;AAAA,MACC,GAAG,KAAA;AAAA,MACJ,GAAA;AAAA,MAEA,QAAA,kBAAA,GAAA;AAAA,QAAiB,eAAA,CAAA,KAAA;AAAA,QAAhB;AAAA,UACA,SAAA,EAAW,EAAA;AAAA,YACV,+EAAA;AAAA,YACA,uEAAA;AAAA,YACA;AAAA;AACD;AAAA;AACD;AAAA;AAGH;AACA,MAAA,CAAO,WAAA,GAAc,QAAA","file":"switch.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as SwitchPrimitive from \"@radix-ui/react-switch\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * An accessible toggle switch built on Radix UI.\n * Use for instant on/off settings that take effect immediately.\n */\nexport type SwitchProps = React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>;\n\nconst Switch = React.forwardRef<React.ComponentRef<typeof SwitchPrimitive.Root>, SwitchProps>(\n\t({ className, ...props }, ref) => (\n\t\t<SwitchPrimitive.Root\n\t\t\tclassName={cn(\n\t\t\t\t\"peer inline-flex h-6 w-[var(--control-height-lg,2.75rem)] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent\",\n\t\t\t\t\"transition-all duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\"shadow-sm\",\n\t\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n\t\t\t\t\"disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\t\t\"data-[state=checked]:bg-primary data-[state=unchecked]:bg-input\",\n\t\t\t\t// Unchecked track shares --color-input with form borders (~1.27:1 vs white) so\n\t\t\t\t// add an inset ring to make the track visible on flat surfaces.\n\t\t\t\t\"data-[state=unchecked]:inset-ring-1 data-[state=unchecked]:inset-ring-foreground/[0.08]\",\n\t\t\t\t\"hover:shadow-md\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t\tref={ref}\n\t\t>\n\t\t\t<SwitchPrimitive.Thumb\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0\",\n\t\t\t\t\t\"transition-transform duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\t\"data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0\",\n\t\t\t\t)}\n\t\t\t/>\n\t\t</SwitchPrimitive.Root>\n\t),\n);\nSwitch.displayName = \"Switch\";\n\nexport { Switch };\n"]}
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/primitives/switch/switch.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACEA,IAAM,MAAA,GAAe,KAAA,CAAA,UAAA;AAAA,EACpB,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,qBACzB,GAAA;AAAA,IAAiB,eAAA,CAAA,IAAA;AAAA,IAAhB;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACV,yIAAA;AAAA,QACA,iEAAA;AAAA,QACA,WAAA;AAAA,QACA,0IAAA;AAAA,QACA,iDAAA;AAAA,QACA,iEAAA;AAAA;AAAA;AAAA,QAGA,yFAAA;AAAA,QACA,iBAAA;AAAA,QACA;AAAA,OACD;AAAA,MACC,GAAG,KAAA;AAAA,MACJ,GAAA;AAAA,MAEA,QAAA,kBAAA,GAAA;AAAA,QAAiB,eAAA,CAAA,KAAA;AAAA,QAAhB;AAAA,UACA,SAAA,EAAW,EAAA;AAAA,YACV,+EAAA;AAAA,YACA,uEAAA;AAAA,YACA;AAAA;AACD;AAAA;AACD;AAAA;AAGH;AACA,MAAA,CAAO,WAAA,GAAc,QAAA","file":"switch.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n\nconst SAFE_URL_SCHEMES = [\"http:\", \"https:\", \"mailto:\"] as const;\n\n/**\n * Allowlist a URL for use as an `<a href>` against untrusted input.\n *\n * Returns the raw string when it's `http(s):` / `mailto:` / a relative\n * URL (no scheme); returns `undefined` otherwise. Defends against\n * `javascript:` / `data:` / `vbscript:` injection from streamed model\n * output or third-party JSON payloads that flow into citation chips.\n *\n * Inside a Markdown render path, `rehype-sanitize` already strips\n * `javascript:` from inline-link hrefs — but it does NOT introspect\n * JSON nested inside attribute values (e.g. `<sources data='[…]'/>`),\n * so the components that consume that data must guard themselves.\n *\n * @param raw - The candidate URL, or `undefined` when none was supplied.\n * @returns The URL when safe to render, otherwise `undefined`.\n */\nexport function safeUrl(raw: string | undefined): string | undefined {\n\tif (raw === undefined || raw === \"\") return undefined;\n\t// Relative URLs have no scheme — `new URL(relative)` throws without a base,\n\t// so a thrown URL means either malformed input OR a relative path. Treat\n\t// \"throws + does not contain a colon\" as a relative path (safe).\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(raw);\n\t} catch {\n\t\treturn raw.includes(\":\") ? undefined : raw;\n\t}\n\treturn SAFE_URL_SCHEMES.some((scheme) => scheme === parsed.protocol) ? raw : undefined;\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as SwitchPrimitive from \"@radix-ui/react-switch\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * An accessible toggle switch built on Radix UI.\n * Use for instant on/off settings that take effect immediately.\n */\nexport type SwitchProps = React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>;\n\nconst Switch = React.forwardRef<React.ComponentRef<typeof SwitchPrimitive.Root>, SwitchProps>(\n\t({ className, ...props }, ref) => (\n\t\t<SwitchPrimitive.Root\n\t\t\tclassName={cn(\n\t\t\t\t\"peer inline-flex h-6 w-[var(--control-height-lg,2.75rem)] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent\",\n\t\t\t\t\"transition-all duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\"shadow-sm\",\n\t\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n\t\t\t\t\"disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\t\t\"data-[state=checked]:bg-primary data-[state=unchecked]:bg-input\",\n\t\t\t\t// Unchecked track shares --color-input with form borders (~1.27:1 vs white) so\n\t\t\t\t// add an inset ring to make the track visible on flat surfaces.\n\t\t\t\t\"data-[state=unchecked]:inset-ring-1 data-[state=unchecked]:inset-ring-foreground/[0.08]\",\n\t\t\t\t\"hover:shadow-md\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\t{...props}\n\t\t\tref={ref}\n\t\t>\n\t\t\t<SwitchPrimitive.Thumb\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0\",\n\t\t\t\t\t\"transition-transform duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\t\"data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0\",\n\t\t\t\t)}\n\t\t\t/>\n\t\t</SwitchPrimitive.Root>\n\t),\n);\nSwitch.displayName = \"Switch\";\n\nexport { Switch };\n"]}
package/dist/table.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/utils.ts","../src/components/table/table.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACNA,IAAM,KAAA,GAAc,KAAA,CAAA,UAAA;AAAA,EACnB,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,qBACzB,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,+BAAA,EACd,QAAA,kBAAA,GAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACA,GAAA;AAAA,MACA,SAAA,EAAW,EAAA,CAAG,+BAAA,EAAiC,SAAS,CAAA;AAAA,MACvD,GAAG;AAAA;AAAA,GACL,EACD;AAEF;AACA,KAAA,CAAM,WAAA,GAAc,OAAA;AAGpB,IAAM,cAAoB,KAAA,CAAA,UAAA,CAGxB,CAAC,EAAE,SAAA,EAAW,GAAG,OAAM,EAAG,GAAA,yBAC1B,OAAA,EAAA,EAAM,GAAA,EAAU,WAAW,EAAA,CAAG,mDAAA,EAAqD,SAAS,CAAA,EAAI,GAAG,OAAO,CAC3G;AACD,WAAA,CAAY,WAAA,GAAc,aAAA;AAG1B,IAAM,YAAkB,KAAA,CAAA,UAAA,CAGtB,CAAC,EAAE,SAAA,EAAW,GAAG,OAAM,EAAG,GAAA,yBAC1B,OAAA,EAAA,EAAM,GAAA,EAAU,WAAW,EAAA,CAAG,4BAAA,EAA8B,SAAS,CAAA,EAAI,GAAG,OAAO,CACpF;AACD,SAAA,CAAU,WAAA,GAAc,WAAA;AAGxB,IAAM,WAAA,GAAoB,iBAGxB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAC,OAAA;AAAA,EAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,oFAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,WAAA,CAAY,WAAA,GAAc,aAAA;AAG1B,IAAM,QAAA,GAAiB,iBAGrB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAC,IAAA;AAAA,EAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,sJAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,QAAA,CAAS,WAAA,GAAc,UAAA;AAGvB,IAAM,SAAA,GAAkB,iBAGtB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAC,IAAA;AAAA,EAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,qJAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,SAAA,CAAU,WAAA,GAAc,WAAA;AAGxB,IAAM,SAAA,GAAkB,iBAGtB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAC,IAAA;AAAA,EAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA,CAAG,oEAAA,EAAsE,SAAS,CAAA;AAAA,IAC5F,GAAG;AAAA;AACL,CACA;AACD,SAAA,CAAU,WAAA,GAAc,WAAA;AAOxB,IAAM,YAAA,GAAqB,iBAGzB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAC,SAAA;AAAA,EAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,uEAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,YAAA,CAAa,WAAA,GAAc,cAAA","file":"table.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/** A responsive container + styled HTML table. */\nconst Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(\n\t({ className, ...props }, ref) => (\n\t\t<div className=\"relative w-full overflow-auto\">\n\t\t\t<table\n\t\t\t\tref={ref}\n\t\t\t\tclassName={cn(\"w-full caption-bottom text-sm\", className)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</div>\n\t),\n);\nTable.displayName = \"Table\";\n\n/** `<thead>` wrapper with bottom border. */\nconst TableHeader = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<thead ref={ref} className={cn(\"[&_tr]:border-b [&_tr]:border-b-foreground/[0.08]\", className)} {...props} />\n));\nTableHeader.displayName = \"TableHeader\";\n\n/** `<tbody>` wrapper removing bottom border on last row. */\nconst TableBody = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<tbody ref={ref} className={cn(\"[&_tr:last-child]:border-0\", className)} {...props} />\n));\nTableBody.displayName = \"TableBody\";\n\n/** `<tfoot>` wrapper with muted background. */\nconst TableFooter = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<tfoot\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"border-t border-t-foreground/[0.08] bg-muted/50 font-medium [&>tr]:last:border-b-0\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableFooter.displayName = \"TableFooter\";\n\n/** `<tr>` with hover + selected states. */\nconst TableRow = React.forwardRef<\n\tHTMLTableRowElement,\n\tReact.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n\t<tr\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"border-b border-b-foreground/[0.08] transition-all duration-[var(--duration-normal,200ms)] ease-out hover:bg-muted/50 data-[state=selected]:bg-muted\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableRow.displayName = \"TableRow\";\n\n/** `<th>` with left-aligned muted text. */\nconst TableHead = React.forwardRef<\n\tHTMLTableCellElement,\n\tReact.ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n\t<th\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"h-[var(--control-height-md,2.5rem)] px-[var(--space-4,1rem)] text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableHead.displayName = \"TableHead\";\n\n/** `<td>` with consistent padding. */\nconst TableCell = React.forwardRef<\n\tHTMLTableCellElement,\n\tReact.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n\t<td\n\t\tref={ref}\n\t\tclassName={cn(\"p-[var(--space-4,1rem)] align-middle [&:has([role=checkbox])]:pr-0\", className)}\n\t\t{...props}\n\t/>\n));\nTableCell.displayName = \"TableCell\";\n\n/**\n * Visible `<caption>` rendered below the table. The parent `<Table>` sets\n * `caption-bottom`, so the caption is announced first by screen readers when\n * entering the table, then visually placed below the rows.\n */\nconst TableCaption = React.forwardRef<\n\tHTMLTableCaptionElement,\n\tReact.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n\t<caption\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"caption-bottom mt-[var(--space-4,1rem)] text-sm text-muted-foreground\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableCaption.displayName = \"TableCaption\";\n\nexport {\n\tTable,\n\tTableHeader,\n\tTableBody,\n\tTableFooter,\n\tTableHead,\n\tTableRow,\n\tTableCell,\n\tTableCaption,\n};\n"]}
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/components/table/table.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACNA,IAAM,KAAA,GAAc,KAAA,CAAA,UAAA;AAAA,EACnB,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,qBACzB,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,+BAAA,EACd,QAAA,kBAAA,GAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACA,GAAA;AAAA,MACA,SAAA,EAAW,EAAA,CAAG,+BAAA,EAAiC,SAAS,CAAA;AAAA,MACvD,GAAG;AAAA;AAAA,GACL,EACD;AAEF;AACA,KAAA,CAAM,WAAA,GAAc,OAAA;AAGpB,IAAM,cAAoB,KAAA,CAAA,UAAA,CAGxB,CAAC,EAAE,SAAA,EAAW,GAAG,OAAM,EAAG,GAAA,yBAC1B,OAAA,EAAA,EAAM,GAAA,EAAU,WAAW,EAAA,CAAG,mDAAA,EAAqD,SAAS,CAAA,EAAI,GAAG,OAAO,CAC3G;AACD,WAAA,CAAY,WAAA,GAAc,aAAA;AAG1B,IAAM,YAAkB,KAAA,CAAA,UAAA,CAGtB,CAAC,EAAE,SAAA,EAAW,GAAG,OAAM,EAAG,GAAA,yBAC1B,OAAA,EAAA,EAAM,GAAA,EAAU,WAAW,EAAA,CAAG,4BAAA,EAA8B,SAAS,CAAA,EAAI,GAAG,OAAO,CACpF;AACD,SAAA,CAAU,WAAA,GAAc,WAAA;AAGxB,IAAM,WAAA,GAAoB,iBAGxB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAC,OAAA;AAAA,EAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,oFAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,WAAA,CAAY,WAAA,GAAc,aAAA;AAG1B,IAAM,QAAA,GAAiB,iBAGrB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAC,IAAA;AAAA,EAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,sJAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,QAAA,CAAS,WAAA,GAAc,UAAA;AAGvB,IAAM,SAAA,GAAkB,iBAGtB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAC,IAAA;AAAA,EAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,qJAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,SAAA,CAAU,WAAA,GAAc,WAAA;AAGxB,IAAM,SAAA,GAAkB,iBAGtB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAC,IAAA;AAAA,EAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA,CAAG,oEAAA,EAAsE,SAAS,CAAA;AAAA,IAC5F,GAAG;AAAA;AACL,CACA;AACD,SAAA,CAAU,WAAA,GAAc,WAAA;AAOxB,IAAM,YAAA,GAAqB,iBAGzB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAC,SAAA;AAAA,EAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,uEAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,YAAA,CAAa,WAAA,GAAc,cAAA","file":"table.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n\nconst SAFE_URL_SCHEMES = [\"http:\", \"https:\", \"mailto:\"] as const;\n\n/**\n * Allowlist a URL for use as an `<a href>` against untrusted input.\n *\n * Returns the raw string when it's `http(s):` / `mailto:` / a relative\n * URL (no scheme); returns `undefined` otherwise. Defends against\n * `javascript:` / `data:` / `vbscript:` injection from streamed model\n * output or third-party JSON payloads that flow into citation chips.\n *\n * Inside a Markdown render path, `rehype-sanitize` already strips\n * `javascript:` from inline-link hrefs — but it does NOT introspect\n * JSON nested inside attribute values (e.g. `<sources data='[…]'/>`),\n * so the components that consume that data must guard themselves.\n *\n * @param raw - The candidate URL, or `undefined` when none was supplied.\n * @returns The URL when safe to render, otherwise `undefined`.\n */\nexport function safeUrl(raw: string | undefined): string | undefined {\n\tif (raw === undefined || raw === \"\") return undefined;\n\t// Relative URLs have no scheme — `new URL(relative)` throws without a base,\n\t// so a thrown URL means either malformed input OR a relative path. Treat\n\t// \"throws + does not contain a colon\" as a relative path (safe).\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(raw);\n\t} catch {\n\t\treturn raw.includes(\":\") ? undefined : raw;\n\t}\n\treturn SAFE_URL_SCHEMES.some((scheme) => scheme === parsed.protocol) ? raw : undefined;\n}\n","import * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/** A responsive container + styled HTML table. */\nconst Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(\n\t({ className, ...props }, ref) => (\n\t\t<div className=\"relative w-full overflow-auto\">\n\t\t\t<table\n\t\t\t\tref={ref}\n\t\t\t\tclassName={cn(\"w-full caption-bottom text-sm\", className)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</div>\n\t),\n);\nTable.displayName = \"Table\";\n\n/** `<thead>` wrapper with bottom border. */\nconst TableHeader = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<thead ref={ref} className={cn(\"[&_tr]:border-b [&_tr]:border-b-foreground/[0.08]\", className)} {...props} />\n));\nTableHeader.displayName = \"TableHeader\";\n\n/** `<tbody>` wrapper removing bottom border on last row. */\nconst TableBody = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<tbody ref={ref} className={cn(\"[&_tr:last-child]:border-0\", className)} {...props} />\n));\nTableBody.displayName = \"TableBody\";\n\n/** `<tfoot>` wrapper with muted background. */\nconst TableFooter = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<tfoot\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"border-t border-t-foreground/[0.08] bg-muted/50 font-medium [&>tr]:last:border-b-0\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableFooter.displayName = \"TableFooter\";\n\n/** `<tr>` with hover + selected states. */\nconst TableRow = React.forwardRef<\n\tHTMLTableRowElement,\n\tReact.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n\t<tr\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"border-b border-b-foreground/[0.08] transition-all duration-[var(--duration-normal,200ms)] ease-out hover:bg-muted/50 data-[state=selected]:bg-muted\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableRow.displayName = \"TableRow\";\n\n/** `<th>` with left-aligned muted text. */\nconst TableHead = React.forwardRef<\n\tHTMLTableCellElement,\n\tReact.ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n\t<th\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"h-[var(--control-height-md,2.5rem)] px-[var(--space-4,1rem)] text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableHead.displayName = \"TableHead\";\n\n/** `<td>` with consistent padding. */\nconst TableCell = React.forwardRef<\n\tHTMLTableCellElement,\n\tReact.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n\t<td\n\t\tref={ref}\n\t\tclassName={cn(\"p-[var(--space-4,1rem)] align-middle [&:has([role=checkbox])]:pr-0\", className)}\n\t\t{...props}\n\t/>\n));\nTableCell.displayName = \"TableCell\";\n\n/**\n * Visible `<caption>` rendered below the table. The parent `<Table>` sets\n * `caption-bottom`, so the caption is announced first by screen readers when\n * entering the table, then visually placed below the rows.\n */\nconst TableCaption = React.forwardRef<\n\tHTMLTableCaptionElement,\n\tReact.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n\t<caption\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"caption-bottom mt-[var(--space-4,1rem)] text-sm text-muted-foreground\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableCaption.displayName = \"TableCaption\";\n\nexport {\n\tTable,\n\tTableHeader,\n\tTableBody,\n\tTableFooter,\n\tTableHead,\n\tTableRow,\n\tTableCell,\n\tTableCaption,\n};\n"]}
package/dist/tabs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/utils.ts","../src/components/tabs/tabs.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACHA,IAAM,IAAA,GAAqB,aAAA,CAAA;AAG3B,IAAM,QAAA,GAAiB,iBAGrB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAe,aAAA,CAAA,IAAA;AAAA,EAAd;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,kLAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,QAAA,CAAS,WAAA,GAAc,UAAA;AAGvB,IAAM,WAAA,GAAoB,iBAGxB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAe,aAAA,CAAA,OAAA;AAAA,EAAd;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,6HAAA;AAAA,MACA,iEAAA;AAAA,MACA,8CAAA;AAAA,MACA,qGAAA;AAAA,MACA,kDAAA;AAAA,MACA,qGAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,WAAA,CAAY,WAAA,GAAc,aAAA;AAG1B,IAAM,WAAA,GAAoB,iBAGxB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAe,aAAA,CAAA,OAAA;AAAA,EAAd;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,mDAAA;AAAA,MACA,qGAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,WAAA,CAAY,WAAA,GAAc,aAAA","file":"tabs.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport { cn } from \"../../lib/utils.js\";\n\n/** Root container for a tabbed interface. */\nconst Tabs = TabsPrimitive.Root;\n\n/** A horizontal list of tab triggers. */\nconst TabsList = React.forwardRef<\n\tReact.ComponentRef<typeof TabsPrimitive.List>,\n\tReact.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n\t<TabsPrimitive.List\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"inline-flex h-[var(--control-height-md,2.5rem)] items-center justify-center rounded-md border border-foreground/[0.06] bg-muted p-[var(--space-1,0.25rem)] text-muted-foreground\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTabsList.displayName = \"TabsList\";\n\n/** A clickable tab trigger that activates its associated content panel. */\nconst TabsTrigger = React.forwardRef<\n\tReact.ComponentRef<typeof TabsPrimitive.Trigger>,\n\tReact.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n\t<TabsPrimitive.Trigger\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-[var(--space-3,0.75rem)] py-1.5 text-sm font-medium\",\n\t\t\t\"transition-all duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\"ring-offset-background hover:text-foreground\",\n\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\t\"disabled:pointer-events-none disabled:opacity-50\",\n\t\t\t\"data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTabsTrigger.displayName = \"TabsTrigger\";\n\n/** The content panel associated with a tab trigger. */\nconst TabsContent = React.forwardRef<\n\tReact.ComponentRef<typeof TabsPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n\t<TabsPrimitive.Content\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"mt-[var(--space-2,0.5rem)] ring-offset-background\",\n\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTabsContent.displayName = \"TabsContent\";\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent };\n"]}
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/components/tabs/tabs.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACHA,IAAM,IAAA,GAAqB,aAAA,CAAA;AAG3B,IAAM,QAAA,GAAiB,iBAGrB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAe,aAAA,CAAA,IAAA;AAAA,EAAd;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,kLAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,QAAA,CAAS,WAAA,GAAc,UAAA;AAGvB,IAAM,WAAA,GAAoB,iBAGxB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAe,aAAA,CAAA,OAAA;AAAA,EAAd;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,6HAAA;AAAA,MACA,iEAAA;AAAA,MACA,8CAAA;AAAA,MACA,qGAAA;AAAA,MACA,kDAAA;AAAA,MACA,qGAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,WAAA,CAAY,WAAA,GAAc,aAAA;AAG1B,IAAM,WAAA,GAAoB,iBAGxB,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC3B,GAAA;AAAA,EAAe,aAAA,CAAA,OAAA;AAAA,EAAd;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACV,mDAAA;AAAA,MACA,qGAAA;AAAA,MACA;AAAA,KACD;AAAA,IACC,GAAG;AAAA;AACL,CACA;AACD,WAAA,CAAY,WAAA,GAAc,aAAA","file":"tabs.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n\nconst SAFE_URL_SCHEMES = [\"http:\", \"https:\", \"mailto:\"] as const;\n\n/**\n * Allowlist a URL for use as an `<a href>` against untrusted input.\n *\n * Returns the raw string when it's `http(s):` / `mailto:` / a relative\n * URL (no scheme); returns `undefined` otherwise. Defends against\n * `javascript:` / `data:` / `vbscript:` injection from streamed model\n * output or third-party JSON payloads that flow into citation chips.\n *\n * Inside a Markdown render path, `rehype-sanitize` already strips\n * `javascript:` from inline-link hrefs — but it does NOT introspect\n * JSON nested inside attribute values (e.g. `<sources data='[…]'/>`),\n * so the components that consume that data must guard themselves.\n *\n * @param raw - The candidate URL, or `undefined` when none was supplied.\n * @returns The URL when safe to render, otherwise `undefined`.\n */\nexport function safeUrl(raw: string | undefined): string | undefined {\n\tif (raw === undefined || raw === \"\") return undefined;\n\t// Relative URLs have no scheme — `new URL(relative)` throws without a base,\n\t// so a thrown URL means either malformed input OR a relative path. Treat\n\t// \"throws + does not contain a colon\" as a relative path (safe).\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(raw);\n\t} catch {\n\t\treturn raw.includes(\":\") ? undefined : raw;\n\t}\n\treturn SAFE_URL_SCHEMES.some((scheme) => scheme === parsed.protocol) ? raw : undefined;\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport { cn } from \"../../lib/utils.js\";\n\n/** Root container for a tabbed interface. */\nconst Tabs = TabsPrimitive.Root;\n\n/** A horizontal list of tab triggers. */\nconst TabsList = React.forwardRef<\n\tReact.ComponentRef<typeof TabsPrimitive.List>,\n\tReact.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n\t<TabsPrimitive.List\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"inline-flex h-[var(--control-height-md,2.5rem)] items-center justify-center rounded-md border border-foreground/[0.06] bg-muted p-[var(--space-1,0.25rem)] text-muted-foreground\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTabsList.displayName = \"TabsList\";\n\n/** A clickable tab trigger that activates its associated content panel. */\nconst TabsTrigger = React.forwardRef<\n\tReact.ComponentRef<typeof TabsPrimitive.Trigger>,\n\tReact.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n\t<TabsPrimitive.Trigger\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-[var(--space-3,0.75rem)] py-1.5 text-sm font-medium\",\n\t\t\t\"transition-all duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\"ring-offset-background hover:text-foreground\",\n\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\t\"disabled:pointer-events-none disabled:opacity-50\",\n\t\t\t\"data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTabsTrigger.displayName = \"TabsTrigger\";\n\n/** The content panel associated with a tab trigger. */\nconst TabsContent = React.forwardRef<\n\tReact.ComponentRef<typeof TabsPrimitive.Content>,\n\tReact.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n\t<TabsPrimitive.Content\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"mt-[var(--space-2,0.5rem)] ring-offset-background\",\n\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTabsContent.displayName = \"TabsContent\";\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent };\n"]}
package/dist/tag.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/utils.ts","../src/primitives/tag/tag.tsx"],"names":[],"mappings":"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","file":"tag.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","import { type VariantProps, cva } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\nconst tagVariants = cva(\n\t[\n\t\t\"inline-flex items-center gap-[var(--gap-xs,0.25rem)] rounded-full border px-2.5 py-0.5 text-xs font-medium\",\n\t\t\"transition-all duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t].join(\" \"),\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"border-transparent bg-primary text-primary-foreground\",\n\t\t\t\tsecondary:\n\t\t\t\t\t\"border-foreground/15 bg-secondary text-secondary-foreground hover:border-foreground/20\",\n\t\t\t\tdestructive: \"border-transparent bg-destructive text-destructive-foreground\",\n\t\t\t\toutline: \"border-foreground/20 text-foreground hover:border-foreground/30\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: { variant: \"default\" },\n\t},\n);\n\nexport interface TagProps\n\textends Omit<React.HTMLAttributes<HTMLSpanElement>, \"onRemove\">,\n\t\tVariantProps<typeof tagVariants> {\n\t/** Forwarded ref onto the root span element. */\n\tref?: React.Ref<HTMLSpanElement>;\n\t/** Optional leading icon (`<svg>` or component). Sized 12×12. */\n\ticon?: React.ReactNode;\n\t/**\n\t * Click handler for the close button. When provided, an inline ✕ button\n\t * is rendered after the children with an `aria-label` derived from the\n\t * children's string content (or a generic \"Remove\" if no string can be\n\t * extracted). Pass undefined for a non-interactive Tag — at that point,\n\t * prefer Badge directly.\n\t */\n\tonRemove?: () => void;\n\t/** Override the auto-derived `aria-label` on the close button. */\n\tremoveLabel?: string;\n}\n\n/**\n * Walk a `React.ReactNode` tree depth-first and collect all string +\n * number leaves into a single space-separated label. Used to derive\n * the close button's `aria-label` even when children are JSX\n * (`<strong>Bold</strong>` → `\"Bold\"`).\n *\n * @param children - React children passed to `<Tag>`.\n * @returns Concatenated string content, or null if no string leaves found.\n */\nfunction extractStringLabel(children: React.ReactNode): string | null {\n\tconst parts: string[] = [];\n\tconst visit = (node: React.ReactNode): void => {\n\t\tif (node === null || node === undefined || typeof node === \"boolean\") return;\n\t\tif (typeof node === \"string\") {\n\t\t\tparts.push(node);\n\t\t\treturn;\n\t\t}\n\t\tif (typeof node === \"number\") {\n\t\t\tparts.push(String(node));\n\t\t\treturn;\n\t\t}\n\t\tif (Array.isArray(node)) {\n\t\t\tfor (const item of node) visit(item);\n\t\t\treturn;\n\t\t}\n\t\tif (React.isValidElement(node)) {\n\t\t\tconst props = node.props as { children?: React.ReactNode };\n\t\t\tvisit(props.children);\n\t\t}\n\t};\n\tvisit(children);\n\tconst joined = parts.join(\" \").replace(/\\s+/g, \" \").trim();\n\treturn joined.length > 0 ? joined : null;\n}\n\n/**\n * An interactive tag / chip primitive — Badge with an optional dismiss\n * affordance. Mirrors {@link Badge}'s CVA variants so the visual sibling\n * is obvious; adds a built-in close button when `onRemove` is provided.\n *\n * For non-interactive labels (status indicators, counts) use {@link Badge}\n * directly. For \"click to filter\" state-bearing chips, use Toggle or\n * ToggleGroup — Tag is for \"this token represents a value the user can\n * dismiss\" (filters, multi-select selections, draft attachments).\n *\n * @example\n * ```tsx\n * <Tag variant=\"secondary\" onRemove={() => removeFilter(\"urgent\")}>\n * Urgent\n * </Tag>\n * ```\n *\n * @returns A span containing the label + optional icon + optional close button.\n */\nfunction Tag({\n\tclassName,\n\tvariant,\n\ticon,\n\tonRemove,\n\tremoveLabel,\n\tchildren,\n\tref,\n\t...props\n}: TagProps) {\n\tconst labelText = extractStringLabel(children);\n\tconst ariaLabel = removeLabel ?? (labelText ? `Remove ${labelText}` : \"Remove\");\n\n\treturn (\n\t\t<span ref={ref} className={cn(tagVariants({ variant }), className)} {...props}>\n\t\t\t{icon ? (\n\t\t\t\t<span aria-hidden=\"true\" className=\"-ml-0.5 [&_svg]:size-3 [&_svg]:shrink-0\">\n\t\t\t\t\t{icon}\n\t\t\t\t</span>\n\t\t\t) : null}\n\t\t\t<span>{children}</span>\n\t\t\t{onRemove ? (\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={onRemove}\n\t\t\t\t\taria-label={ariaLabel}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"-mr-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full\",\n\t\t\t\t\t\t\"transition-colors duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\t\t\"hover:bg-foreground/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n\t\t\t\t\t\t\"active:scale-[0.92]\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t<svg\n\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\tstrokeWidth=\"2.5\"\n\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\tclassName=\"size-3\"\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n\t\t\t\t\t\t<line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n\t\t\t\t\t</svg>\n\t\t\t\t</button>\n\t\t\t) : null}\n\t\t</span>\n\t);\n}\n\nexport { Tag, tagVariants };\n"]}
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/primitives/tag/tag.tsx"],"names":[],"mappings":"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","file":"tag.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n\nconst SAFE_URL_SCHEMES = [\"http:\", \"https:\", \"mailto:\"] as const;\n\n/**\n * Allowlist a URL for use as an `<a href>` against untrusted input.\n *\n * Returns the raw string when it's `http(s):` / `mailto:` / a relative\n * URL (no scheme); returns `undefined` otherwise. Defends against\n * `javascript:` / `data:` / `vbscript:` injection from streamed model\n * output or third-party JSON payloads that flow into citation chips.\n *\n * Inside a Markdown render path, `rehype-sanitize` already strips\n * `javascript:` from inline-link hrefs — but it does NOT introspect\n * JSON nested inside attribute values (e.g. `<sources data='[…]'/>`),\n * so the components that consume that data must guard themselves.\n *\n * @param raw - The candidate URL, or `undefined` when none was supplied.\n * @returns The URL when safe to render, otherwise `undefined`.\n */\nexport function safeUrl(raw: string | undefined): string | undefined {\n\tif (raw === undefined || raw === \"\") return undefined;\n\t// Relative URLs have no scheme — `new URL(relative)` throws without a base,\n\t// so a thrown URL means either malformed input OR a relative path. Treat\n\t// \"throws + does not contain a colon\" as a relative path (safe).\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(raw);\n\t} catch {\n\t\treturn raw.includes(\":\") ? undefined : raw;\n\t}\n\treturn SAFE_URL_SCHEMES.some((scheme) => scheme === parsed.protocol) ? raw : undefined;\n}\n","import { type VariantProps, cva } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\nconst tagVariants = cva(\n\t[\n\t\t\"inline-flex items-center gap-[var(--gap-xs,0.25rem)] rounded-full border px-2.5 py-0.5 text-xs font-medium\",\n\t\t\"transition-all duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t].join(\" \"),\n\t{\n\t\tvariants: {\n\t\t\tvariant: {\n\t\t\t\tdefault: \"border-transparent bg-primary text-primary-foreground\",\n\t\t\t\tsecondary:\n\t\t\t\t\t\"border-foreground/15 bg-secondary text-secondary-foreground hover:border-foreground/20\",\n\t\t\t\tdestructive: \"border-transparent bg-destructive text-destructive-foreground\",\n\t\t\t\toutline: \"border-foreground/20 text-foreground hover:border-foreground/30\",\n\t\t\t},\n\t\t},\n\t\tdefaultVariants: { variant: \"default\" },\n\t},\n);\n\nexport interface TagProps\n\textends Omit<React.HTMLAttributes<HTMLSpanElement>, \"onRemove\">,\n\t\tVariantProps<typeof tagVariants> {\n\t/** Forwarded ref onto the root span element. */\n\tref?: React.Ref<HTMLSpanElement>;\n\t/** Optional leading icon (`<svg>` or component). Sized 12×12. */\n\ticon?: React.ReactNode;\n\t/**\n\t * Click handler for the close button. When provided, an inline ✕ button\n\t * is rendered after the children with an `aria-label` derived from the\n\t * children's string content (or a generic \"Remove\" if no string can be\n\t * extracted). Pass undefined for a non-interactive Tag — at that point,\n\t * prefer Badge directly.\n\t */\n\tonRemove?: () => void;\n\t/** Override the auto-derived `aria-label` on the close button. */\n\tremoveLabel?: string;\n}\n\n/**\n * Walk a `React.ReactNode` tree depth-first and collect all string +\n * number leaves into a single space-separated label. Used to derive\n * the close button's `aria-label` even when children are JSX\n * (`<strong>Bold</strong>` → `\"Bold\"`).\n *\n * @param children - React children passed to `<Tag>`.\n * @returns Concatenated string content, or null if no string leaves found.\n */\nfunction extractStringLabel(children: React.ReactNode): string | null {\n\tconst parts: string[] = [];\n\tconst visit = (node: React.ReactNode): void => {\n\t\tif (node === null || node === undefined || typeof node === \"boolean\") return;\n\t\tif (typeof node === \"string\") {\n\t\t\tparts.push(node);\n\t\t\treturn;\n\t\t}\n\t\tif (typeof node === \"number\") {\n\t\t\tparts.push(String(node));\n\t\t\treturn;\n\t\t}\n\t\tif (Array.isArray(node)) {\n\t\t\tfor (const item of node) visit(item);\n\t\t\treturn;\n\t\t}\n\t\tif (React.isValidElement(node)) {\n\t\t\tconst props = node.props as { children?: React.ReactNode };\n\t\t\tvisit(props.children);\n\t\t}\n\t};\n\tvisit(children);\n\tconst joined = parts.join(\" \").replace(/\\s+/g, \" \").trim();\n\treturn joined.length > 0 ? joined : null;\n}\n\n/**\n * An interactive tag / chip primitive — Badge with an optional dismiss\n * affordance. Mirrors {@link Badge}'s CVA variants so the visual sibling\n * is obvious; adds a built-in close button when `onRemove` is provided.\n *\n * For non-interactive labels (status indicators, counts) use {@link Badge}\n * directly. For \"click to filter\" state-bearing chips, use Toggle or\n * ToggleGroup — Tag is for \"this token represents a value the user can\n * dismiss\" (filters, multi-select selections, draft attachments).\n *\n * @example\n * ```tsx\n * <Tag variant=\"secondary\" onRemove={() => removeFilter(\"urgent\")}>\n * Urgent\n * </Tag>\n * ```\n *\n * @returns A span containing the label + optional icon + optional close button.\n */\nfunction Tag({\n\tclassName,\n\tvariant,\n\ticon,\n\tonRemove,\n\tremoveLabel,\n\tchildren,\n\tref,\n\t...props\n}: TagProps) {\n\tconst labelText = extractStringLabel(children);\n\tconst ariaLabel = removeLabel ?? (labelText ? `Remove ${labelText}` : \"Remove\");\n\n\treturn (\n\t\t<span ref={ref} className={cn(tagVariants({ variant }), className)} {...props}>\n\t\t\t{icon ? (\n\t\t\t\t<span aria-hidden=\"true\" className=\"-ml-0.5 [&_svg]:size-3 [&_svg]:shrink-0\">\n\t\t\t\t\t{icon}\n\t\t\t\t</span>\n\t\t\t) : null}\n\t\t\t<span>{children}</span>\n\t\t\t{onRemove ? (\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tonClick={onRemove}\n\t\t\t\t\taria-label={ariaLabel}\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"-mr-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full\",\n\t\t\t\t\t\t\"transition-colors duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\t\t\"hover:bg-foreground/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1\",\n\t\t\t\t\t\t\"active:scale-[0.92]\",\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t<svg\n\t\t\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\tstrokeWidth=\"2.5\"\n\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\tclassName=\"size-3\"\n\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n\t\t\t\t\t\t<line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n\t\t\t\t\t</svg>\n\t\t\t\t</button>\n\t\t\t) : null}\n\t\t</span>\n\t);\n}\n\nexport { Tag, tagVariants };\n"]}
package/dist/task.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { TaskProps_alias_1 as TaskProps } from './_tsup-dts-rollup.js';
2
+ export { TaskStep_alias_1 as TaskStep } from './_tsup-dts-rollup.js';
3
+ export { Task_alias_1 as Task } from './_tsup-dts-rollup.js';
package/dist/task.js ADDED
@@ -0,0 +1,189 @@
1
+ "use client";
2
+ import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ function cn(...inputs) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+ function Task({ label, steps, durationMs, defaultOpen = true, className }) {
11
+ const summary = summarize(steps, durationMs);
12
+ return /* @__PURE__ */ jsxs(
13
+ CollapsiblePrimitive.Root,
14
+ {
15
+ defaultOpen,
16
+ className: cn(
17
+ "overflow-hidden rounded-md border border-border bg-card text-card-foreground",
18
+ className
19
+ ),
20
+ children: [
21
+ /* @__PURE__ */ jsxs(
22
+ CollapsiblePrimitive.Trigger,
23
+ {
24
+ className: cn(
25
+ "group flex w-full items-center gap-2 px-3 py-2 text-left",
26
+ "transition-all duration-[var(--duration-normal,200ms)] ease-out",
27
+ "hover:bg-muted/30",
28
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
29
+ ),
30
+ children: [
31
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [
32
+ label ? /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: label }) : null,
33
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: summary })
34
+ ] }),
35
+ /* @__PURE__ */ jsx(Chevron, {})
36
+ ]
37
+ }
38
+ ),
39
+ /* @__PURE__ */ jsx(CollapsiblePrimitive.Content, { className: "overflow-hidden border-t border-foreground/[0.06]", children: /* @__PURE__ */ jsx("ol", { className: "my-0 ml-0 flex list-none flex-col pl-0", children: steps.map((step) => /* @__PURE__ */ jsxs(
40
+ "li",
41
+ {
42
+ className: "flex items-start gap-3 px-3 py-2 text-sm before:content-none last:pb-3",
43
+ children: [
44
+ /* @__PURE__ */ jsx(StepIcon, { state: step.state }),
45
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [
46
+ /* @__PURE__ */ jsx(
47
+ "span",
48
+ {
49
+ className: cn(
50
+ "leading-snug",
51
+ step.state === "result" && "text-muted-foreground line-through decoration-muted-foreground/50",
52
+ step.state === "running" && "text-foreground font-medium",
53
+ step.state === "error" && "text-destructive",
54
+ step.state === "pending" && "text-muted-foreground"
55
+ ),
56
+ children: step.label
57
+ }
58
+ ),
59
+ step.detail ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: step.detail }) : null
60
+ ] })
61
+ ]
62
+ },
63
+ step.id
64
+ )) }) })
65
+ ]
66
+ }
67
+ );
68
+ }
69
+ function summarize(steps, durationMs) {
70
+ const total = steps.length;
71
+ const done = steps.filter((s) => s.state === "result").length;
72
+ const running = steps.some((s) => s.state === "running");
73
+ const errored = steps.some((s) => s.state === "error");
74
+ if (errored) return `${done} of ${total} steps \xB7 failed`;
75
+ if (running) return `${done} of ${total} steps`;
76
+ if (done === total && total > 0 && typeof durationMs === "number") {
77
+ return `Done in ${formatDuration(durationMs)}`;
78
+ }
79
+ if (done === total && total > 0) return "Done";
80
+ return `${done} of ${total} steps`;
81
+ }
82
+ function formatDuration(ms) {
83
+ if (ms < 1e3) return `${ms}ms`;
84
+ const seconds = ms / 1e3;
85
+ const formatted = seconds >= 10 ? Math.round(seconds).toString() : seconds.toFixed(1);
86
+ return `${formatted}s`;
87
+ }
88
+ function StepIcon({ state }) {
89
+ const cls = "mt-0.5 shrink-0";
90
+ if (state === "result") {
91
+ return /* @__PURE__ */ jsxs(
92
+ "svg",
93
+ {
94
+ "aria-label": "Done",
95
+ role: "img",
96
+ viewBox: "0 0 16 16",
97
+ width: "14",
98
+ height: "14",
99
+ fill: "none",
100
+ stroke: "currentColor",
101
+ strokeWidth: "2",
102
+ strokeLinecap: "round",
103
+ strokeLinejoin: "round",
104
+ className: cn(cls, "text-emerald-500"),
105
+ children: [
106
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6" }),
107
+ /* @__PURE__ */ jsx("path", { d: "M5 8.25l2.25 2.25L11 6" })
108
+ ]
109
+ }
110
+ );
111
+ }
112
+ if (state === "running") {
113
+ return /* @__PURE__ */ jsx(
114
+ "svg",
115
+ {
116
+ "aria-label": "Running",
117
+ role: "img",
118
+ viewBox: "0 0 16 16",
119
+ width: "14",
120
+ height: "14",
121
+ fill: "none",
122
+ stroke: "currentColor",
123
+ strokeWidth: "2",
124
+ strokeLinecap: "round",
125
+ className: cn(cls, "text-primary motion-safe:animate-spin"),
126
+ children: /* @__PURE__ */ jsx("path", { d: "M8 1.5a6.5 6.5 0 1 1-6.5 6.5" })
127
+ }
128
+ );
129
+ }
130
+ if (state === "error") {
131
+ return /* @__PURE__ */ jsxs(
132
+ "svg",
133
+ {
134
+ "aria-label": "Error",
135
+ role: "img",
136
+ viewBox: "0 0 16 16",
137
+ width: "14",
138
+ height: "14",
139
+ fill: "none",
140
+ stroke: "currentColor",
141
+ strokeWidth: "2",
142
+ strokeLinecap: "round",
143
+ strokeLinejoin: "round",
144
+ className: cn(cls, "text-destructive"),
145
+ children: [
146
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6" }),
147
+ /* @__PURE__ */ jsx("path", { d: "M5 5l6 6M11 5l-6 6" })
148
+ ]
149
+ }
150
+ );
151
+ }
152
+ return /* @__PURE__ */ jsx(
153
+ "svg",
154
+ {
155
+ "aria-label": "Pending",
156
+ role: "img",
157
+ viewBox: "0 0 16 16",
158
+ width: "14",
159
+ height: "14",
160
+ fill: "none",
161
+ stroke: "currentColor",
162
+ strokeWidth: "2",
163
+ className: cn(cls, "text-muted-foreground"),
164
+ children: /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6", strokeDasharray: "2 2" })
165
+ }
166
+ );
167
+ }
168
+ function Chevron() {
169
+ return /* @__PURE__ */ jsx(
170
+ "svg",
171
+ {
172
+ "aria-hidden": true,
173
+ viewBox: "0 0 16 16",
174
+ width: "12",
175
+ height: "12",
176
+ fill: "none",
177
+ stroke: "currentColor",
178
+ strokeWidth: "1.5",
179
+ strokeLinecap: "round",
180
+ strokeLinejoin: "round",
181
+ className: "ml-auto shrink-0 text-muted-foreground transition-transform duration-200 group-data-[state=open]:rotate-180",
182
+ children: /* @__PURE__ */ jsx("path", { d: "M4 6l4 4 4-4" })
183
+ }
184
+ );
185
+ }
186
+
187
+ export { Task };
188
+ //# sourceMappingURL=task.js.map
189
+ //# sourceMappingURL=task.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/ai/task/task.tsx"],"names":[],"mappings":"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","file":"task.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n\nconst SAFE_URL_SCHEMES = [\"http:\", \"https:\", \"mailto:\"] as const;\n\n/**\n * Allowlist a URL for use as an `<a href>` against untrusted input.\n *\n * Returns the raw string when it's `http(s):` / `mailto:` / a relative\n * URL (no scheme); returns `undefined` otherwise. Defends against\n * `javascript:` / `data:` / `vbscript:` injection from streamed model\n * output or third-party JSON payloads that flow into citation chips.\n *\n * Inside a Markdown render path, `rehype-sanitize` already strips\n * `javascript:` from inline-link hrefs — but it does NOT introspect\n * JSON nested inside attribute values (e.g. `<sources data='[…]'/>`),\n * so the components that consume that data must guard themselves.\n *\n * @param raw - The candidate URL, or `undefined` when none was supplied.\n * @returns The URL when safe to render, otherwise `undefined`.\n */\nexport function safeUrl(raw: string | undefined): string | undefined {\n\tif (raw === undefined || raw === \"\") return undefined;\n\t// Relative URLs have no scheme — `new URL(relative)` throws without a base,\n\t// so a thrown URL means either malformed input OR a relative path. Treat\n\t// \"throws + does not contain a colon\" as a relative path (safe).\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(raw);\n\t} catch {\n\t\treturn raw.includes(\":\") ? undefined : raw;\n\t}\n\treturn SAFE_URL_SCHEMES.some((scheme) => scheme === parsed.protocol) ? raw : undefined;\n}\n","\"use client\";\n\nimport * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Lifecycle of a Task step. Mirrors `@hex-core/components`'\n * `ToolCallState` exactly — kept inline (rather than imported from\n * `../types.js`) so the registry CLI distribution path (`npx hex add\n * task`) ships a self-contained file. ToolCall has the same enum\n * imported from `../types.js` for backwards-compat with its earlier\n * shape; new consumers should treat the two unions as one vocabulary.\n *\n * KEEP IN SYNC with `../types.js`'s `ToolCallState` enum. Drift is\n * locked down by an `expectTypeOf` assertion in `task.test.tsx`.\n */\ntype ToolCallState = \"pending\" | \"running\" | \"result\" | \"error\";\n\n/**\n * Multi-step task progress for an AI workflow.\n *\n * Each step's `state` re-uses the canonical `ToolCallState` enum\n * (`pending` / `running` / `result` / `error`) so the vocabulary stays\n * consistent across the AI surface. The header tracks aggregate\n * progress (\"3 of 5 steps\", or \"Done in X.Xs\" once `durationMs` is\n * set).\n *\n * Composes well with `<Reasoning>` for in-step thinking traces and\n * `<ToolCall>` for individual tool invocations.\n *\n * @example\n * <Task\n * label=\"Refactoring auth\"\n * steps={[\n * { id: \"read\", label: \"Read existing auth\", state: \"result\" },\n * { id: \"write\", label: \"Apply changes\", state: \"running\" },\n * { id: \"test\", label: \"Run tests\", state: \"pending\" },\n * ]}\n * durationMs={12_400}\n * />\n */\nexport interface TaskProps {\n\t/** Optional title shown above the step list. Skipped if absent. */\n\tlabel?: string;\n\t/** Ordered list of steps. Each carries an id, label, and state. */\n\tsteps: TaskStep[];\n\t/** Time spent on the task in milliseconds. Renders \"Done in X.Xs\" when set. */\n\tdurationMs?: number;\n\t/** Whether the step list is expanded by default. */\n\tdefaultOpen?: boolean;\n\tclassName?: string;\n}\n\n/** A single row in a Task. */\nexport interface TaskStep {\n\t/** Stable identifier — used for the React key and any consumer tracking. */\n\tid: string;\n\t/** Human-readable step label. */\n\tlabel: string;\n\t/** Lifecycle status — same vocabulary as `<ToolCall>`'s `state`. */\n\tstate: ToolCallState;\n\t/** Optional detail line shown beneath the label. */\n\tdetail?: string;\n}\n\n/**\n * Render a multi-step task progress card.\n * @param props - The task label, steps, and optional duration.\n * @returns A Collapsible wrapping a step list.\n */\nfunction Task({ label, steps, durationMs, defaultOpen = true, className }: TaskProps) {\n\tconst summary = summarize(steps, durationMs);\n\n\treturn (\n\t\t<CollapsiblePrimitive.Root\n\t\t\tdefaultOpen={defaultOpen}\n\t\t\tclassName={cn(\n\t\t\t\t\"overflow-hidden rounded-md border border-border bg-card text-card-foreground\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t>\n\t\t\t<CollapsiblePrimitive.Trigger\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"group flex w-full items-center gap-2 px-3 py-2 text-left\",\n\t\t\t\t\t\"transition-all duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\t\"hover:bg-muted/30\",\n\t\t\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\t\t)}\n\t\t\t>\n\t\t\t\t<div className=\"flex min-w-0 flex-col gap-0.5\">\n\t\t\t\t\t{label ? (\n\t\t\t\t\t\t<span className=\"text-sm font-medium text-foreground\">{label}</span>\n\t\t\t\t\t) : null}\n\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">{summary}</span>\n\t\t\t\t</div>\n\t\t\t\t<Chevron />\n\t\t\t</CollapsiblePrimitive.Trigger>\n\t\t\t<CollapsiblePrimitive.Content className=\"overflow-hidden border-t border-foreground/[0.06]\">\n\t\t\t\t<ol className=\"my-0 ml-0 flex list-none flex-col pl-0\">\n\t\t\t\t\t{steps.map((step) => (\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tkey={step.id}\n\t\t\t\t\t\t\tclassName=\"flex items-start gap-3 px-3 py-2 text-sm before:content-none last:pb-3\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<StepIcon state={step.state} />\n\t\t\t\t\t\t\t<div className=\"flex min-w-0 flex-col gap-0.5\">\n\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\"leading-snug\",\n\t\t\t\t\t\t\t\t\t\tstep.state === \"result\" && \"text-muted-foreground line-through decoration-muted-foreground/50\",\n\t\t\t\t\t\t\t\t\t\tstep.state === \"running\" && \"text-foreground font-medium\",\n\t\t\t\t\t\t\t\t\t\tstep.state === \"error\" && \"text-destructive\",\n\t\t\t\t\t\t\t\t\t\tstep.state === \"pending\" && \"text-muted-foreground\",\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{step.label}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t{step.detail ? (\n\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">{step.detail}</span>\n\t\t\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t))}\n\t\t\t\t</ol>\n\t\t\t</CollapsiblePrimitive.Content>\n\t\t</CollapsiblePrimitive.Root>\n\t);\n}\n\nfunction summarize(steps: TaskStep[], durationMs: number | undefined): string {\n\tconst total = steps.length;\n\tconst done = steps.filter((s) => s.state === \"result\").length;\n\tconst running = steps.some((s) => s.state === \"running\");\n\tconst errored = steps.some((s) => s.state === \"error\");\n\tif (errored) return `${done} of ${total} steps · failed`;\n\tif (running) return `${done} of ${total} steps`;\n\tif (done === total && total > 0 && typeof durationMs === \"number\") {\n\t\treturn `Done in ${formatDuration(durationMs)}`;\n\t}\n\tif (done === total && total > 0) return \"Done\";\n\treturn `${done} of ${total} steps`;\n}\n\nfunction formatDuration(ms: number): string {\n\tif (ms < 1000) return `${ms}ms`;\n\tconst seconds = ms / 1000;\n\tconst formatted = seconds >= 10 ? Math.round(seconds).toString() : seconds.toFixed(1);\n\treturn `${formatted}s`;\n}\n\nfunction StepIcon({ state }: { state: ToolCallState }) {\n\tconst cls = \"mt-0.5 shrink-0\";\n\tif (state === \"result\") {\n\t\treturn (\n\t\t\t<svg\n\t\t\t\taria-label=\"Done\"\n\t\t\t\trole=\"img\"\n\t\t\t\tviewBox=\"0 0 16 16\"\n\t\t\t\twidth=\"14\"\n\t\t\t\theight=\"14\"\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\tclassName={cn(cls, \"text-emerald-500\")}\n\t\t\t>\n\t\t\t\t<circle cx=\"8\" cy=\"8\" r=\"6\" />\n\t\t\t\t<path d=\"M5 8.25l2.25 2.25L11 6\" />\n\t\t\t</svg>\n\t\t);\n\t}\n\tif (state === \"running\") {\n\t\treturn (\n\t\t\t<svg\n\t\t\t\taria-label=\"Running\"\n\t\t\t\trole=\"img\"\n\t\t\t\tviewBox=\"0 0 16 16\"\n\t\t\t\twidth=\"14\"\n\t\t\t\theight=\"14\"\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tclassName={cn(cls, \"text-primary motion-safe:animate-spin\")}\n\t\t\t>\n\t\t\t\t<path d=\"M8 1.5a6.5 6.5 0 1 1-6.5 6.5\" />\n\t\t\t</svg>\n\t\t);\n\t}\n\tif (state === \"error\") {\n\t\treturn (\n\t\t\t<svg\n\t\t\t\taria-label=\"Error\"\n\t\t\t\trole=\"img\"\n\t\t\t\tviewBox=\"0 0 16 16\"\n\t\t\t\twidth=\"14\"\n\t\t\t\theight=\"14\"\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstrokeWidth=\"2\"\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\tclassName={cn(cls, \"text-destructive\")}\n\t\t\t>\n\t\t\t\t<circle cx=\"8\" cy=\"8\" r=\"6\" />\n\t\t\t\t<path d=\"M5 5l6 6M11 5l-6 6\" />\n\t\t\t</svg>\n\t\t);\n\t}\n\t// pending\n\treturn (\n\t\t<svg\n\t\t\taria-label=\"Pending\"\n\t\t\trole=\"img\"\n\t\t\tviewBox=\"0 0 16 16\"\n\t\t\twidth=\"14\"\n\t\t\theight=\"14\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tclassName={cn(cls, \"text-muted-foreground\")}\n\t\t>\n\t\t\t<circle cx=\"8\" cy=\"8\" r=\"6\" strokeDasharray=\"2 2\" />\n\t\t</svg>\n\t);\n}\n\nfunction Chevron() {\n\treturn (\n\t\t<svg\n\t\t\taria-hidden\n\t\t\tviewBox=\"0 0 16 16\"\n\t\t\twidth=\"12\"\n\t\t\theight=\"12\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"1.5\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t\tclassName=\"ml-auto shrink-0 text-muted-foreground transition-transform duration-200 group-data-[state=open]:rotate-180\"\n\t\t>\n\t\t\t<path d=\"M4 6l4 4 4-4\" />\n\t\t</svg>\n\t);\n}\n\nexport { Task };\n"]}
package/dist/terminal.js CHANGED
@@ -138,6 +138,17 @@ function Terminal({
138
138
  className: cn(
139
139
  "overflow-hidden rounded-md border p-2",
140
140
  "font-mono text-sm leading-tight",
141
+ // xterm.js mounts hidden helper elements (`<textarea>` for
142
+ // keyboard / IME input, `.xterm-char-measure-element` for
143
+ // sizing). Both inherit the page's foreground token, which
144
+ // axe flags as sub-AA against the locked-dark surface even
145
+ // though they're visually offscreen. Force their visible
146
+ // color transparent so axe stops complaining; AT can still
147
+ // read the textarea (xterm's intended IME path), but the
148
+ // rendered output appears in xterm's canvas — not the
149
+ // textarea — so AT won't see meaningful content there.
150
+ "[&_textarea]:!text-transparent [&_textarea]:!caret-transparent",
151
+ "[&_.xterm-char-measure-element]:!text-transparent",
141
152
  className
142
153
  )
143
154
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/color.ts","../src/lib/utils.ts","../src/ai/terminal/terminal.tsx"],"names":[],"mappings":"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","file":"terminal.js","sourcesContent":["/**\n * Color conversion utilities for the HSL-triplet token format used across\n * `@hex-core/tokens` themes (`H S% L%`, e.g. `\"240 5.9% 10%\"` — no `hsl()`\n * wrapper, no commas).\n *\n * The triplet is the round-trip-safe serialization for Hex Core: tokens flow\n * triplet → CSS `hsl(var(--token))` → rendered color, and the ColorPicker\n * component edits triplets directly. Hex/RGB conversions are display\n * adapters, not the source of truth.\n */\n\n/** Parsed HSL components. `h` is degrees (0–360); `s` and `l` are percentages (0–100). */\nexport interface HslTriplet {\n\th: number;\n\ts: number;\n\tl: number;\n}\n\n/** Parsed RGB components. Each channel is 0–255. */\nexport interface RgbColor {\n\tr: number;\n\tg: number;\n\tb: number;\n}\n\n/**\n * Parse an HSL triplet string into numeric components.\n *\n * Note: malformed input silently coerces to `{0,0,0}` (pure black) rather than\n * returning an error signal. Callers that need to distinguish \"user typed\n * black\" from \"user typed garbage\" should validate the input format first.\n * `hexToHslTriplet` returns `null` for malformed hex; this asymmetry is\n * intentional — triplets feed CSS variables where any non-color value would\n * already break rendering.\n *\n * @param triplet - String in the form `\"<H> <S>% <L>%\"` (e.g. `\"240 5.9% 10%\"`).\n * @returns Numeric components, or `{0,0,0}` if the input is malformed.\n */\nexport function parseHslTriplet(triplet: string): HslTriplet {\n\tconst parts = triplet.trim().split(/\\s+/);\n\treturn {\n\t\th: Number.parseFloat(parts[0]) || 0,\n\t\ts: Number.parseFloat(parts[1]) || 0,\n\t\tl: Number.parseFloat(parts[2]) || 0,\n\t};\n}\n\n/**\n * Format HSL components into an HSL triplet string (the canonical token format).\n * @param hsl - Numeric components.\n * @returns Triplet in the form `\"<H> <S>% <L>%\"`.\n */\nexport function formatHslTriplet({ h, s, l }: HslTriplet): string {\n\t// Tolerant integer check: rgbToHsl can produce values like 5.0000000001 due\n\t// to float arithmetic; format those as \"5\" rather than \"5.0\".\n\tconst round = (n: number) =>\n\t\tMath.abs(n - Math.round(n)) < 1e-6 ? `${Math.round(n)}` : n.toFixed(1);\n\treturn `${Math.round(h)} ${round(s)}% ${round(l)}%`;\n}\n\n/**\n * Convert HSL components to RGB.\n * @param h - Hue (0–360).\n * @param s - Saturation (0–100).\n * @param l - Lightness (0–100).\n * @returns RGB channels (0–255, rounded).\n */\nexport function hslToRgb(h: number, s: number, l: number): RgbColor {\n\tconst sN = s / 100;\n\tconst lN = l / 100;\n\tconst k = (n: number) => (n + h / 30) % 12;\n\tconst a = sN * Math.min(lN, 1 - lN);\n\tconst f = (n: number) => lN - a * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1));\n\treturn {\n\t\tr: Math.round(255 * f(0)),\n\t\tg: Math.round(255 * f(8)),\n\t\tb: Math.round(255 * f(4)),\n\t};\n}\n\n/**\n * Convert RGB components to HSL.\n * @param r - Red (0–255).\n * @param g - Green (0–255).\n * @param b - Blue (0–255).\n * @returns HSL components (h: 0–360, s: 0–100, l: 0–100).\n */\nexport function rgbToHsl(r: number, g: number, b: number): HslTriplet {\n\tconst rN = r / 255;\n\tconst gN = g / 255;\n\tconst bN = b / 255;\n\tconst max = Math.max(rN, gN, bN);\n\tconst min = Math.min(rN, gN, bN);\n\tlet h = 0;\n\tlet s = 0;\n\tconst l = (max + min) / 2;\n\tif (max !== min) {\n\t\tconst d = max - min;\n\t\ts = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n\t\tif (max === rN) h = (gN - bN) / d + (gN < bN ? 6 : 0);\n\t\telse if (max === gN) h = (bN - rN) / d + 2;\n\t\telse h = (rN - gN) / d + 4;\n\t\th /= 6;\n\t}\n\treturn { h: h * 360, s: s * 100, l: l * 100 };\n}\n\n/**\n * Convert an HSL triplet to a 6-digit hex string.\n * @param triplet - HSL triplet (e.g. `\"240 5.9% 10%\"`).\n * @returns Lowercase hex string with leading `#` (e.g. `\"#181a1f\"`).\n */\nexport function hslTripletToHex(triplet: string): string {\n\tconst { h, s, l } = parseHslTriplet(triplet);\n\tconst { r, g, b } = hslToRgb(h, s, l);\n\tconst toHex = (n: number) => n.toString(16).padStart(2, \"0\");\n\treturn `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n}\n\n/**\n * Convert a hex string to an HSL triplet.\n * Accepts 3-digit (`#abc`) or 6-digit (`#aabbcc`) hex with optional `#`.\n * @param hex - Hex color string.\n * @returns HSL triplet, or `null` if the input is malformed.\n */\nexport function hexToHslTriplet(hex: string): string | null {\n\tconst clean = hex.trim().replace(/^#/, \"\");\n\tlet normalized: string;\n\tif (/^[0-9a-fA-F]{3}$/.test(clean)) {\n\t\tnormalized = clean\n\t\t\t.split(\"\")\n\t\t\t.map((c) => c + c)\n\t\t\t.join(\"\");\n\t} else if (/^[0-9a-fA-F]{6}$/.test(clean)) {\n\t\tnormalized = clean;\n\t} else {\n\t\treturn null;\n\t}\n\tconst r = Number.parseInt(normalized.slice(0, 2), 16);\n\tconst g = Number.parseInt(normalized.slice(2, 4), 16);\n\tconst b = Number.parseInt(normalized.slice(4, 6), 16);\n\treturn formatHslTriplet(rgbToHsl(r, g, b));\n}\n","import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { hslTripletToHex } from \"../../lib/color.js\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Headless terminal display backed by xterm.js. Renders an xterm grid\n * inside a div the consumer styles. No PTY, no shell — the consumer\n * owns the data-flow:\n *\n * - Pass `output` (string or string[]) to write to the display. Each\n * change is diffed against the prior render and only the new tail\n * is `term.write()`-ed, so feeding a streaming buffer doesn't redraw.\n * - Pass `onInput` to receive bytes the user typed. Wire it to a\n * WebSocket / IPC / fetch stream — terminal doesn't care.\n *\n * Heavy peer: requires `@xterm/xterm` (~150 KB gzip). The hex-core CLI's\n * `add` flow prompts before installing.\n *\n * @example\n * <Terminal\n * output={[\"$ ls\\r\\n\", \"package.json src/\\r\\n\", \"$ \"]}\n * onInput={(data) => ws.send(data)}\n * rows={24}\n * cols={80}\n * />\n */\nexport interface TerminalProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\" | \"onInput\"> {\n\t/**\n\t * Bytes to display. String is written verbatim; string[] is joined.\n\t * On change, only the suffix beyond the prior render is emitted, so\n\t * appending to a streaming buffer is O(delta).\n\t */\n\toutput?: string | string[];\n\t/** Receive bytes the user typed (incl. control sequences). */\n\tonInput?: (data: string) => void;\n\t/** Initial cols. xterm allows runtime resize via fit-addon (not bundled). Default 80. */\n\tcols?: number;\n\t/** Initial rows. Default 24. */\n\trows?: number;\n\t/** Theme tokens — defaults to neutral light/dark via CSS vars. */\n\ttheme?: \"dark\" | \"light\";\n\t/** Enable cursor blink. Default true. */\n\tcursorBlink?: boolean;\n\t/** Whether the user can type into the terminal. Default true. */\n\tdisableInput?: boolean;\n}\n\n// Fallback xterm themes used when the consumer hasn't loaded `@hex-core/tokens`\n// or hasn't defined the standard `--background` / `--foreground` CSS vars\n// (e.g. consumer mounted Terminal in isolation). Match the `Terminal`\n// wrapper's inline `themeBg` defaults below — both must agree or there's\n// a visible seam between the xterm canvas and the wrapper.\nconst DARK_FALLBACK = {\n\tbackground: \"#0a0a0a\",\n\tforeground: \"#e5e5e5\",\n\tcursor: \"#e5e5e5\",\n\tselectionBackground: \"#404040\",\n};\nconst LIGHT_FALLBACK = {\n\tbackground: \"#fafafa\",\n\tforeground: \"#171717\",\n\tcursor: \"#171717\",\n\tselectionBackground: \"#d4d4d4\",\n};\n\n/**\n * Read a CSS HSL-triplet variable from `:root` (or the nearest theme\n * scope) and convert it to a 6-digit hex string suitable for xterm's\n * `theme: { background: \"#...\" }` option, which accepts hex/rgb but NOT\n * CSS variables. Returns `null` if the variable is unset, so the caller\n * can fall back to a hand-tuned theme without rendering pure-black.\n *\n * @param name - CSS variable name without the leading `--` (e.g. `\"background\"`).\n * @returns A `#xxxxxx` hex color string, or `null` if the var isn't defined.\n */\nfunction readCssVarAsHex(name: string): string | null {\n\tif (typeof document === \"undefined\") return null;\n\tconst triplet = getComputedStyle(document.documentElement)\n\t\t.getPropertyValue(`--${name}`)\n\t\t.trim();\n\tif (!triplet) return null;\n\treturn hslTripletToHex(triplet);\n}\n\n/**\n * Renders an xterm.js terminal display.\n * @param props - Terminal output + input handler + display options\n * @returns A div containing the xterm grid\n */\nfunction Terminal({\n\toutput,\n\tonInput,\n\tcols = 80,\n\trows = 24,\n\ttheme = \"dark\",\n\tcursorBlink = true,\n\tdisableInput = false,\n\tclassName,\n\t...rest\n}: TerminalProps) {\n\tconst containerRef = React.useRef<HTMLDivElement | null>(null);\n\tconst termRef = React.useRef<import(\"@xterm/xterm\").Terminal | null>(null);\n\tconst writtenRef = React.useRef<string>(\"\");\n\tconst onInputRef = React.useRef(onInput);\n\tonInputRef.current = onInput;\n\t// Latest-output mirror so the dynamic-import callback can read the value\n\t// at the moment the engine actually mounts (not the value at first render\n\t// — those can differ when the parent's `output` prop is set lazily).\n\tconst outputRef = React.useRef(output);\n\toutputRef.current = output;\n\n\t// Initialize xterm once on mount. Dynamic import keeps the engine out\n\t// of consumers' bundles unless they actually mount the component.\n\tReact.useEffect(() => {\n\t\tif (!containerRef.current) return;\n\n\t\tlet disposed = false;\n\t\tlet inputDispose: { dispose: () => void } | null = null;\n\n\t\tvoid (async () => {\n\t\t\tconst xtermModule = await import(\"@xterm/xterm\");\n\t\t\tif (disposed || !containerRef.current) return;\n\n\t\t\t// xterm needs hex colors (it can't accept CSS vars), so read\n\t\t\t// the consumer's `--background` / `--foreground` triplets at\n\t\t\t// mount time and convert. A consumer who themes those tokens\n\t\t\t// gets a terminal that follows the page; a consumer mounting\n\t\t\t// Terminal in isolation falls back to the hand-tuned defaults.\n\t\t\tconst fallback = theme === \"dark\" ? DARK_FALLBACK : LIGHT_FALLBACK;\n\t\t\tconst bgHex = readCssVarAsHex(\"background\") ?? fallback.background;\n\t\t\tconst fgHex = readCssVarAsHex(\"foreground\") ?? fallback.foreground;\n\n\t\t\tconst term = new xtermModule.Terminal({\n\t\t\t\tcols,\n\t\t\t\trows,\n\t\t\t\tcursorBlink,\n\t\t\t\tdisableStdin: disableInput,\n\t\t\t\ttheme: {\n\t\t\t\t\tbackground: bgHex,\n\t\t\t\t\tforeground: fgHex,\n\t\t\t\t\tcursor: fgHex,\n\t\t\t\t\tselectionBackground: fallback.selectionBackground,\n\t\t\t\t},\n\t\t\t\tfontFamily: \"ui-monospace, SFMono-Regular, Menlo, monospace\",\n\t\t\t\tfontSize: 13,\n\t\t\t});\n\t\t\tterm.open(containerRef.current);\n\t\t\ttermRef.current = term;\n\n\t\t\t// Read the LATEST output via the ref — between mount and\n\t\t\t// import-resolve, the parent may have updated the prop. Without\n\t\t\t// this, fast prop changes get clobbered.\n\t\t\tconst latest = normalizeOutput(outputRef.current);\n\t\t\tif (latest) {\n\t\t\t\tterm.write(latest);\n\t\t\t\twrittenRef.current = latest;\n\t\t\t}\n\n\t\t\tinputDispose = term.onData((data) => {\n\t\t\t\tonInputRef.current?.(data);\n\t\t\t});\n\t\t})();\n\n\t\treturn () => {\n\t\t\tdisposed = true;\n\t\t\tinputDispose?.dispose();\n\t\t\ttermRef.current?.dispose();\n\t\t\ttermRef.current = null;\n\t\t\twrittenRef.current = \"\";\n\t\t};\n\t\t// cols/rows/theme/cursorBlink/disableInput are mount-time options.\n\t\t// Changing them mid-session would force a full re-init that'd lose\n\t\t// scrollback — out of scope for v1.\n\t}, []);\n\n\t// Diff `output` against what's already on screen and write only the new tail.\n\tReact.useEffect(() => {\n\t\tconst term = termRef.current;\n\t\tif (!term) return;\n\t\tconst next = normalizeOutput(output);\n\t\tif (next === writtenRef.current) return;\n\t\tif (next.startsWith(writtenRef.current)) {\n\t\t\tconst delta = next.slice(writtenRef.current.length);\n\t\t\tif (delta) term.write(delta);\n\t\t} else {\n\t\t\t// Non-suffix change (e.g. consumer cleared the buffer or replaced it\n\t\t\t// with unrelated content) — reset the screen and write fresh.\n\t\t\tterm.reset();\n\t\t\tif (next) term.write(next);\n\t\t}\n\t\twrittenRef.current = next;\n\t}, [output]);\n\n\t// Inline background matches the xterm theme bg. xterm renders its grid\n\t// into a canvas (or DOM rows without their own background-color), so\n\t// a11y tools walk up the DOM looking for a background and would\n\t// otherwise hit the docs-page bg, producing a contrast false positive\n\t// against the xterm fg. The CSS var fallback is an HSL triplet — `hsl()`\n\t// rejects hex literals as a fallback, so the triplet is what makes this\n\t// work when a consumer mounts Terminal without loading `@hex-core/tokens`.\n\tconst fallbackTriplet = theme === \"dark\" ? \"0 0% 4%\" : \"0 0% 98%\";\n\tconst themeBg = `hsl(var(--background, ${fallbackTriplet}))`;\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tref={containerRef}\n\t\t\tdata-hex-terminal\n\t\t\tdata-theme={theme}\n\t\t\tstyle={{ backgroundColor: themeBg, ...(rest.style ?? {}) }}\n\t\t\tclassName={cn(\n\t\t\t\t\"overflow-hidden rounded-md border p-2\",\n\t\t\t\t\"font-mono text-sm leading-tight\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t/>\n\t);\n}\n\nfunction normalizeOutput(value: string | string[] | undefined): string {\n\tif (value == null) return \"\";\n\treturn Array.isArray(value) ? value.join(\"\") : value;\n}\n\nexport { Terminal };\n"]}
1
+ {"version":3,"sources":["../src/lib/color.ts","../src/lib/utils.ts","../src/ai/terminal/terminal.tsx"],"names":[],"mappings":"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","file":"terminal.js","sourcesContent":["/**\n * Color conversion utilities for the HSL-triplet token format used across\n * `@hex-core/tokens` themes (`H S% L%`, e.g. `\"240 5.9% 10%\"` — no `hsl()`\n * wrapper, no commas).\n *\n * The triplet is the round-trip-safe serialization for Hex Core: tokens flow\n * triplet → CSS `hsl(var(--token))` → rendered color, and the ColorPicker\n * component edits triplets directly. Hex/RGB conversions are display\n * adapters, not the source of truth.\n */\n\n/** Parsed HSL components. `h` is degrees (0–360); `s` and `l` are percentages (0–100). */\nexport interface HslTriplet {\n\th: number;\n\ts: number;\n\tl: number;\n}\n\n/** Parsed RGB components. Each channel is 0–255. */\nexport interface RgbColor {\n\tr: number;\n\tg: number;\n\tb: number;\n}\n\n/**\n * Parse an HSL triplet string into numeric components.\n *\n * Note: malformed input silently coerces to `{0,0,0}` (pure black) rather than\n * returning an error signal. Callers that need to distinguish \"user typed\n * black\" from \"user typed garbage\" should validate the input format first.\n * `hexToHslTriplet` returns `null` for malformed hex; this asymmetry is\n * intentional — triplets feed CSS variables where any non-color value would\n * already break rendering.\n *\n * @param triplet - String in the form `\"<H> <S>% <L>%\"` (e.g. `\"240 5.9% 10%\"`).\n * @returns Numeric components, or `{0,0,0}` if the input is malformed.\n */\nexport function parseHslTriplet(triplet: string): HslTriplet {\n\tconst parts = triplet.trim().split(/\\s+/);\n\treturn {\n\t\th: Number.parseFloat(parts[0]) || 0,\n\t\ts: Number.parseFloat(parts[1]) || 0,\n\t\tl: Number.parseFloat(parts[2]) || 0,\n\t};\n}\n\n/**\n * Format HSL components into an HSL triplet string (the canonical token format).\n * @param hsl - Numeric components.\n * @returns Triplet in the form `\"<H> <S>% <L>%\"`.\n */\nexport function formatHslTriplet({ h, s, l }: HslTriplet): string {\n\t// Tolerant integer check: rgbToHsl can produce values like 5.0000000001 due\n\t// to float arithmetic; format those as \"5\" rather than \"5.0\".\n\tconst round = (n: number) =>\n\t\tMath.abs(n - Math.round(n)) < 1e-6 ? `${Math.round(n)}` : n.toFixed(1);\n\treturn `${Math.round(h)} ${round(s)}% ${round(l)}%`;\n}\n\n/**\n * Convert HSL components to RGB.\n * @param h - Hue (0–360).\n * @param s - Saturation (0–100).\n * @param l - Lightness (0–100).\n * @returns RGB channels (0–255, rounded).\n */\nexport function hslToRgb(h: number, s: number, l: number): RgbColor {\n\tconst sN = s / 100;\n\tconst lN = l / 100;\n\tconst k = (n: number) => (n + h / 30) % 12;\n\tconst a = sN * Math.min(lN, 1 - lN);\n\tconst f = (n: number) => lN - a * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1));\n\treturn {\n\t\tr: Math.round(255 * f(0)),\n\t\tg: Math.round(255 * f(8)),\n\t\tb: Math.round(255 * f(4)),\n\t};\n}\n\n/**\n * Convert RGB components to HSL.\n * @param r - Red (0–255).\n * @param g - Green (0–255).\n * @param b - Blue (0–255).\n * @returns HSL components (h: 0–360, s: 0–100, l: 0–100).\n */\nexport function rgbToHsl(r: number, g: number, b: number): HslTriplet {\n\tconst rN = r / 255;\n\tconst gN = g / 255;\n\tconst bN = b / 255;\n\tconst max = Math.max(rN, gN, bN);\n\tconst min = Math.min(rN, gN, bN);\n\tlet h = 0;\n\tlet s = 0;\n\tconst l = (max + min) / 2;\n\tif (max !== min) {\n\t\tconst d = max - min;\n\t\ts = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n\t\tif (max === rN) h = (gN - bN) / d + (gN < bN ? 6 : 0);\n\t\telse if (max === gN) h = (bN - rN) / d + 2;\n\t\telse h = (rN - gN) / d + 4;\n\t\th /= 6;\n\t}\n\treturn { h: h * 360, s: s * 100, l: l * 100 };\n}\n\n/**\n * Convert an HSL triplet to a 6-digit hex string.\n * @param triplet - HSL triplet (e.g. `\"240 5.9% 10%\"`).\n * @returns Lowercase hex string with leading `#` (e.g. `\"#181a1f\"`).\n */\nexport function hslTripletToHex(triplet: string): string {\n\tconst { h, s, l } = parseHslTriplet(triplet);\n\tconst { r, g, b } = hslToRgb(h, s, l);\n\tconst toHex = (n: number) => n.toString(16).padStart(2, \"0\");\n\treturn `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n}\n\n/**\n * Convert a hex string to an HSL triplet.\n * Accepts 3-digit (`#abc`) or 6-digit (`#aabbcc`) hex with optional `#`.\n * @param hex - Hex color string.\n * @returns HSL triplet, or `null` if the input is malformed.\n */\nexport function hexToHslTriplet(hex: string): string | null {\n\tconst clean = hex.trim().replace(/^#/, \"\");\n\tlet normalized: string;\n\tif (/^[0-9a-fA-F]{3}$/.test(clean)) {\n\t\tnormalized = clean\n\t\t\t.split(\"\")\n\t\t\t.map((c) => c + c)\n\t\t\t.join(\"\");\n\t} else if (/^[0-9a-fA-F]{6}$/.test(clean)) {\n\t\tnormalized = clean;\n\t} else {\n\t\treturn null;\n\t}\n\tconst r = Number.parseInt(normalized.slice(0, 2), 16);\n\tconst g = Number.parseInt(normalized.slice(2, 4), 16);\n\tconst b = Number.parseInt(normalized.slice(4, 6), 16);\n\treturn formatHslTriplet(rgbToHsl(r, g, b));\n}\n","import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n\nconst SAFE_URL_SCHEMES = [\"http:\", \"https:\", \"mailto:\"] as const;\n\n/**\n * Allowlist a URL for use as an `<a href>` against untrusted input.\n *\n * Returns the raw string when it's `http(s):` / `mailto:` / a relative\n * URL (no scheme); returns `undefined` otherwise. Defends against\n * `javascript:` / `data:` / `vbscript:` injection from streamed model\n * output or third-party JSON payloads that flow into citation chips.\n *\n * Inside a Markdown render path, `rehype-sanitize` already strips\n * `javascript:` from inline-link hrefs — but it does NOT introspect\n * JSON nested inside attribute values (e.g. `<sources data='[…]'/>`),\n * so the components that consume that data must guard themselves.\n *\n * @param raw - The candidate URL, or `undefined` when none was supplied.\n * @returns The URL when safe to render, otherwise `undefined`.\n */\nexport function safeUrl(raw: string | undefined): string | undefined {\n\tif (raw === undefined || raw === \"\") return undefined;\n\t// Relative URLs have no scheme — `new URL(relative)` throws without a base,\n\t// so a thrown URL means either malformed input OR a relative path. Treat\n\t// \"throws + does not contain a colon\" as a relative path (safe).\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(raw);\n\t} catch {\n\t\treturn raw.includes(\":\") ? undefined : raw;\n\t}\n\treturn SAFE_URL_SCHEMES.some((scheme) => scheme === parsed.protocol) ? raw : undefined;\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { hslTripletToHex } from \"../../lib/color.js\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Headless terminal display backed by xterm.js. Renders an xterm grid\n * inside a div the consumer styles. No PTY, no shell — the consumer\n * owns the data-flow:\n *\n * - Pass `output` (string or string[]) to write to the display. Each\n * change is diffed against the prior render and only the new tail\n * is `term.write()`-ed, so feeding a streaming buffer doesn't redraw.\n * - Pass `onInput` to receive bytes the user typed. Wire it to a\n * WebSocket / IPC / fetch stream — terminal doesn't care.\n *\n * Heavy peer: requires `@xterm/xterm` (~150 KB gzip). The hex-core CLI's\n * `add` flow prompts before installing.\n *\n * @example\n * <Terminal\n * output={[\"$ ls\\r\\n\", \"package.json src/\\r\\n\", \"$ \"]}\n * onInput={(data) => ws.send(data)}\n * rows={24}\n * cols={80}\n * />\n */\nexport interface TerminalProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\" | \"onInput\"> {\n\t/**\n\t * Bytes to display. String is written verbatim; string[] is joined.\n\t * On change, only the suffix beyond the prior render is emitted, so\n\t * appending to a streaming buffer is O(delta).\n\t */\n\toutput?: string | string[];\n\t/** Receive bytes the user typed (incl. control sequences). */\n\tonInput?: (data: string) => void;\n\t/** Initial cols. xterm allows runtime resize via fit-addon (not bundled). Default 80. */\n\tcols?: number;\n\t/** Initial rows. Default 24. */\n\trows?: number;\n\t/** Theme tokens — defaults to neutral light/dark via CSS vars. */\n\ttheme?: \"dark\" | \"light\";\n\t/** Enable cursor blink. Default true. */\n\tcursorBlink?: boolean;\n\t/** Whether the user can type into the terminal. Default true. */\n\tdisableInput?: boolean;\n}\n\n// Fallback xterm themes used when the consumer hasn't loaded `@hex-core/tokens`\n// or hasn't defined the standard `--background` / `--foreground` CSS vars\n// (e.g. consumer mounted Terminal in isolation). Match the `Terminal`\n// wrapper's inline `themeBg` defaults below — both must agree or there's\n// a visible seam between the xterm canvas and the wrapper.\nconst DARK_FALLBACK = {\n\tbackground: \"#0a0a0a\",\n\tforeground: \"#e5e5e5\",\n\tcursor: \"#e5e5e5\",\n\tselectionBackground: \"#404040\",\n};\nconst LIGHT_FALLBACK = {\n\tbackground: \"#fafafa\",\n\tforeground: \"#171717\",\n\tcursor: \"#171717\",\n\tselectionBackground: \"#d4d4d4\",\n};\n\n/**\n * Read a CSS HSL-triplet variable from `:root` (or the nearest theme\n * scope) and convert it to a 6-digit hex string suitable for xterm's\n * `theme: { background: \"#...\" }` option, which accepts hex/rgb but NOT\n * CSS variables. Returns `null` if the variable is unset, so the caller\n * can fall back to a hand-tuned theme without rendering pure-black.\n *\n * @param name - CSS variable name without the leading `--` (e.g. `\"background\"`).\n * @returns A `#xxxxxx` hex color string, or `null` if the var isn't defined.\n */\nfunction readCssVarAsHex(name: string): string | null {\n\tif (typeof document === \"undefined\") return null;\n\tconst triplet = getComputedStyle(document.documentElement)\n\t\t.getPropertyValue(`--${name}`)\n\t\t.trim();\n\tif (!triplet) return null;\n\treturn hslTripletToHex(triplet);\n}\n\n/**\n * Renders an xterm.js terminal display.\n * @param props - Terminal output + input handler + display options\n * @returns A div containing the xterm grid\n */\nfunction Terminal({\n\toutput,\n\tonInput,\n\tcols = 80,\n\trows = 24,\n\ttheme = \"dark\",\n\tcursorBlink = true,\n\tdisableInput = false,\n\tclassName,\n\t...rest\n}: TerminalProps) {\n\tconst containerRef = React.useRef<HTMLDivElement | null>(null);\n\tconst termRef = React.useRef<import(\"@xterm/xterm\").Terminal | null>(null);\n\tconst writtenRef = React.useRef<string>(\"\");\n\tconst onInputRef = React.useRef(onInput);\n\tonInputRef.current = onInput;\n\t// Latest-output mirror so the dynamic-import callback can read the value\n\t// at the moment the engine actually mounts (not the value at first render\n\t// — those can differ when the parent's `output` prop is set lazily).\n\tconst outputRef = React.useRef(output);\n\toutputRef.current = output;\n\n\t// Initialize xterm once on mount. Dynamic import keeps the engine out\n\t// of consumers' bundles unless they actually mount the component.\n\tReact.useEffect(() => {\n\t\tif (!containerRef.current) return;\n\n\t\tlet disposed = false;\n\t\tlet inputDispose: { dispose: () => void } | null = null;\n\n\t\tvoid (async () => {\n\t\t\tconst xtermModule = await import(\"@xterm/xterm\");\n\t\t\tif (disposed || !containerRef.current) return;\n\n\t\t\t// xterm needs hex colors (it can't accept CSS vars), so read\n\t\t\t// the consumer's `--background` / `--foreground` triplets at\n\t\t\t// mount time and convert. A consumer who themes those tokens\n\t\t\t// gets a terminal that follows the page; a consumer mounting\n\t\t\t// Terminal in isolation falls back to the hand-tuned defaults.\n\t\t\tconst fallback = theme === \"dark\" ? DARK_FALLBACK : LIGHT_FALLBACK;\n\t\t\tconst bgHex = readCssVarAsHex(\"background\") ?? fallback.background;\n\t\t\tconst fgHex = readCssVarAsHex(\"foreground\") ?? fallback.foreground;\n\n\t\t\tconst term = new xtermModule.Terminal({\n\t\t\t\tcols,\n\t\t\t\trows,\n\t\t\t\tcursorBlink,\n\t\t\t\tdisableStdin: disableInput,\n\t\t\t\ttheme: {\n\t\t\t\t\tbackground: bgHex,\n\t\t\t\t\tforeground: fgHex,\n\t\t\t\t\tcursor: fgHex,\n\t\t\t\t\tselectionBackground: fallback.selectionBackground,\n\t\t\t\t},\n\t\t\t\tfontFamily: \"ui-monospace, SFMono-Regular, Menlo, monospace\",\n\t\t\t\tfontSize: 13,\n\t\t\t});\n\t\t\tterm.open(containerRef.current);\n\t\t\ttermRef.current = term;\n\n\t\t\t// Read the LATEST output via the ref — between mount and\n\t\t\t// import-resolve, the parent may have updated the prop. Without\n\t\t\t// this, fast prop changes get clobbered.\n\t\t\tconst latest = normalizeOutput(outputRef.current);\n\t\t\tif (latest) {\n\t\t\t\tterm.write(latest);\n\t\t\t\twrittenRef.current = latest;\n\t\t\t}\n\n\t\t\tinputDispose = term.onData((data) => {\n\t\t\t\tonInputRef.current?.(data);\n\t\t\t});\n\t\t})();\n\n\t\treturn () => {\n\t\t\tdisposed = true;\n\t\t\tinputDispose?.dispose();\n\t\t\ttermRef.current?.dispose();\n\t\t\ttermRef.current = null;\n\t\t\twrittenRef.current = \"\";\n\t\t};\n\t\t// cols/rows/theme/cursorBlink/disableInput are mount-time options.\n\t\t// Changing them mid-session would force a full re-init that'd lose\n\t\t// scrollback — out of scope for v1.\n\t}, []);\n\n\t// Diff `output` against what's already on screen and write only the new tail.\n\tReact.useEffect(() => {\n\t\tconst term = termRef.current;\n\t\tif (!term) return;\n\t\tconst next = normalizeOutput(output);\n\t\tif (next === writtenRef.current) return;\n\t\tif (next.startsWith(writtenRef.current)) {\n\t\t\tconst delta = next.slice(writtenRef.current.length);\n\t\t\tif (delta) term.write(delta);\n\t\t} else {\n\t\t\t// Non-suffix change (e.g. consumer cleared the buffer or replaced it\n\t\t\t// with unrelated content) — reset the screen and write fresh.\n\t\t\tterm.reset();\n\t\t\tif (next) term.write(next);\n\t\t}\n\t\twrittenRef.current = next;\n\t}, [output]);\n\n\t// Inline background matches the xterm theme bg. xterm renders its grid\n\t// into a canvas (or DOM rows without their own background-color), so\n\t// a11y tools walk up the DOM looking for a background and would\n\t// otherwise hit the docs-page bg, producing a contrast false positive\n\t// against the xterm fg. The CSS var fallback is an HSL triplet — `hsl()`\n\t// rejects hex literals as a fallback, so the triplet is what makes this\n\t// work when a consumer mounts Terminal without loading `@hex-core/tokens`.\n\tconst fallbackTriplet = theme === \"dark\" ? \"0 0% 4%\" : \"0 0% 98%\";\n\tconst themeBg = `hsl(var(--background, ${fallbackTriplet}))`;\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tref={containerRef}\n\t\t\tdata-hex-terminal\n\t\t\tdata-theme={theme}\n\t\t\tstyle={{ backgroundColor: themeBg, ...(rest.style ?? {}) }}\n\t\t\tclassName={cn(\n\t\t\t\t\"overflow-hidden rounded-md border p-2\",\n\t\t\t\t\"font-mono text-sm leading-tight\",\n\t\t\t\t// xterm.js mounts hidden helper elements (`<textarea>` for\n\t\t\t\t// keyboard / IME input, `.xterm-char-measure-element` for\n\t\t\t\t// sizing). Both inherit the page's foreground token, which\n\t\t\t\t// axe flags as sub-AA against the locked-dark surface even\n\t\t\t\t// though they're visually offscreen. Force their visible\n\t\t\t\t// color transparent so axe stops complaining; AT can still\n\t\t\t\t// read the textarea (xterm's intended IME path), but the\n\t\t\t\t// rendered output appears in xterm's canvas — not the\n\t\t\t\t// textarea — so AT won't see meaningful content there.\n\t\t\t\t\"[&_textarea]:!text-transparent [&_textarea]:!caret-transparent\",\n\t\t\t\t\"[&_.xterm-char-measure-element]:!text-transparent\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t/>\n\t);\n}\n\nfunction normalizeOutput(value: string | string[] | undefined): string {\n\tif (value == null) return \"\";\n\treturn Array.isArray(value) ? value.join(\"\") : value;\n}\n\nexport { Terminal };\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/utils.ts","../src/primitives/textarea/textarea.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACDA,IAAM,QAAA,GAAiB,KAAA,CAAA,UAAA;AAAA,EACtB,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,KAAQ;AACjC,IAAA,uBACC,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACV,sIAAA;AAAA,UACA,iEAAA;AAAA,UACA,qDAAA;AAAA,UACA,mCAAA;AAAA,UACA,qGAAA;AAAA,UACA,sDAAA;AAAA,UACA,sCAAA;AAAA,UACA,iDAAA;AAAA,UACA;AAAA,SACD;AAAA,QACA,GAAA;AAAA,QACC,GAAG;AAAA;AAAA,KACL;AAAA,EAEF;AACD;AACA,QAAA,CAAS,WAAA,GAAc,UAAA","file":"textarea.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * A styled multi-line text input with smooth focus transitions and shadow effects.\n * Extends the native HTML textarea element with Hex Core styling.\n */\nexport type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n\t({ className, ...props }, ref) => {\n\t\treturn (\n\t\t\t<textarea\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"flex min-h-[80px] w-full rounded-md border border-input bg-background px-[var(--space-3,0.75rem)] py-[var(--space-2,0.5rem)] text-sm\",\n\t\t\t\t\t\"transition-all duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\t\"shadow-sm inset-ring-1 inset-ring-foreground/[0.06]\",\n\t\t\t\t\t\"placeholder:text-muted-foreground\",\n\t\t\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\t\t\t\"focus-visible:shadow-md focus-visible:border-ring/50\",\n\t\t\t\t\t\"hover:border-ring/30 hover:shadow-md\",\n\t\t\t\t\t\"disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\tref={ref}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t},\n);\nTextarea.displayName = \"Textarea\";\n\nexport { Textarea };\n"]}
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/primitives/textarea/textarea.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC3C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC5B;ACDA,IAAM,QAAA,GAAiB,KAAA,CAAA,UAAA;AAAA,EACtB,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,KAAQ;AACjC,IAAA,uBACC,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACV,sIAAA;AAAA,UACA,iEAAA;AAAA,UACA,qDAAA;AAAA,UACA,mCAAA;AAAA,UACA,qGAAA;AAAA,UACA,sDAAA;AAAA,UACA,sCAAA;AAAA,UACA,iDAAA;AAAA,UACA;AAAA,SACD;AAAA,QACA,GAAA;AAAA,QACC,GAAG;AAAA;AAAA,KACL;AAAA,EAEF;AACD;AACA,QAAA,CAAS,WAAA,GAAc,UAAA","file":"textarea.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n\nconst SAFE_URL_SCHEMES = [\"http:\", \"https:\", \"mailto:\"] as const;\n\n/**\n * Allowlist a URL for use as an `<a href>` against untrusted input.\n *\n * Returns the raw string when it's `http(s):` / `mailto:` / a relative\n * URL (no scheme); returns `undefined` otherwise. Defends against\n * `javascript:` / `data:` / `vbscript:` injection from streamed model\n * output or third-party JSON payloads that flow into citation chips.\n *\n * Inside a Markdown render path, `rehype-sanitize` already strips\n * `javascript:` from inline-link hrefs — but it does NOT introspect\n * JSON nested inside attribute values (e.g. `<sources data='[…]'/>`),\n * so the components that consume that data must guard themselves.\n *\n * @param raw - The candidate URL, or `undefined` when none was supplied.\n * @returns The URL when safe to render, otherwise `undefined`.\n */\nexport function safeUrl(raw: string | undefined): string | undefined {\n\tif (raw === undefined || raw === \"\") return undefined;\n\t// Relative URLs have no scheme — `new URL(relative)` throws without a base,\n\t// so a thrown URL means either malformed input OR a relative path. Treat\n\t// \"throws + does not contain a colon\" as a relative path (safe).\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(raw);\n\t} catch {\n\t\treturn raw.includes(\":\") ? undefined : raw;\n\t}\n\treturn SAFE_URL_SCHEMES.some((scheme) => scheme === parsed.protocol) ? raw : undefined;\n}\n","import * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * A styled multi-line text input with smooth focus transitions and shadow effects.\n * Extends the native HTML textarea element with Hex Core styling.\n */\nexport type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n\t({ className, ...props }, ref) => {\n\t\treturn (\n\t\t\t<textarea\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"flex min-h-[80px] w-full rounded-md border border-input bg-background px-[var(--space-3,0.75rem)] py-[var(--space-2,0.5rem)] text-sm\",\n\t\t\t\t\t\"transition-all duration-[var(--duration-normal,200ms)] ease-out\",\n\t\t\t\t\t\"shadow-sm inset-ring-1 inset-ring-foreground/[0.06]\",\n\t\t\t\t\t\"placeholder:text-muted-foreground\",\n\t\t\t\t\t\"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n\t\t\t\t\t\"focus-visible:shadow-md focus-visible:border-ring/50\",\n\t\t\t\t\t\"hover:border-ring/30 hover:shadow-md\",\n\t\t\t\t\t\"disabled:cursor-not-allowed disabled:opacity-50\",\n\t\t\t\t\tclassName,\n\t\t\t\t)}\n\t\t\t\tref={ref}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t);\n\t},\n);\nTextarea.displayName = \"Textarea\";\n\nexport { Textarea };\n"]}