@digithings-ai/ui 0.0.0-stage → 0.1.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.
- package/README.md +48 -2
- package/package.json +143 -4
- package/src/components/NavShell.tsx +776 -0
- package/src/components/SocialRow.tsx +87 -0
- package/src/components/Spinner.tsx +27 -0
- package/src/components/StackLogo.tsx +40 -0
- package/src/components/Terminal.tsx +110 -0
- package/src/components/TerminalManifest.tsx +212 -0
- package/src/components/ThemeProvider.tsx +271 -0
- package/src/components/account/AuthCard.tsx +309 -0
- package/src/components/assistant-ui/elements/mermaid-diagram.aui.tsx +63 -0
- package/src/components/assistant-ui/elements/mermaid-diagram.tsx +340 -0
- package/src/components/brand/BrandWordmark.tsx +32 -0
- package/src/components/changelog/ReleaseRail.tsx +81 -0
- package/src/components/changelog/index.ts +1 -0
- package/src/components/chat/ChatCodeBlock.tsx +67 -0
- package/src/components/chat/ChatMarkdown.tsx +66 -0
- package/src/components/chat/ChatMarkdownSource.tsx +203 -0
- package/src/components/chat/ChatMermaidBlock.tsx +202 -0
- package/src/components/chat/ChatMessage.tsx +77 -0
- package/src/components/chat/ChatPlayback.tsx +422 -0
- package/src/components/chat/ChatStreamCursor.tsx +25 -0
- package/src/components/chat/ChatThinking.tsx +113 -0
- package/src/components/chat/ChatToolCall.tsx +190 -0
- package/src/components/chat/ChatTranscript.tsx +60 -0
- package/src/components/chat/ChatWidgetFrame.tsx +107 -0
- package/src/components/chat/DigichatBootLoader.tsx +683 -0
- package/src/components/chat/DigichatLauncher.tsx +361 -0
- package/src/components/chat/DigichatThread.tsx +118 -0
- package/src/components/chat/DigichatThreadList.tsx +54 -0
- package/src/components/chat/DigichatWordmark.tsx +24 -0
- package/src/components/chat/DotMatrix.tsx +650 -0
- package/src/components/chat/boot-lab.tsx +766 -0
- package/src/components/chat/boot-signal.ts +144 -0
- package/src/components/chat/chat-surfaces.ts +52 -0
- package/src/components/chat/connection-status.ts +40 -0
- package/src/components/chat/digichat-wordmark.ts +355 -0
- package/src/components/chat/disclosure-chevron.tsx +49 -0
- package/src/components/chat/followable-href.ts +9 -0
- package/src/components/chat/gallery-thread/action-icons.tsx +25 -0
- package/src/components/chat/gallery-thread/attachment.aui.tsx +262 -0
- package/src/components/chat/gallery-thread/block-caret.tsx +439 -0
- package/src/components/chat/gallery-thread/cn.ts +1 -0
- package/src/components/chat/gallery-thread/composer-keyboard.ts +106 -0
- package/src/components/chat/gallery-thread/composer-trigger-popover.aui.tsx +235 -0
- package/src/components/chat/gallery-thread/css.d.ts +6 -0
- package/src/components/chat/gallery-thread/file.tsx +238 -0
- package/src/components/chat/gallery-thread/follow-up-suggestions.aui.tsx +92 -0
- package/src/components/chat/gallery-thread/format-json-dump.ts +112 -0
- package/src/components/chat/gallery-thread/hooks/use-attachment-src.ts +46 -0
- package/src/components/chat/gallery-thread/hooks/use-copy-to-clipboard.ts +9 -0
- package/src/components/chat/gallery-thread/image.tsx +519 -0
- package/src/components/chat/gallery-thread/markdown-text.tsx +278 -0
- package/src/components/chat/gallery-thread/message-error.aui.tsx +105 -0
- package/src/components/chat/gallery-thread/message-timing.aui.tsx +143 -0
- package/src/components/chat/gallery-thread/private-work-step.tsx +188 -0
- package/src/components/chat/gallery-thread/reasoning.tsx +332 -0
- package/src/components/chat/gallery-thread/scroll-mode.ts +174 -0
- package/src/components/chat/gallery-thread/source.tsx +76 -0
- package/src/components/chat/gallery-thread/thread.aui.tsx +1619 -0
- package/src/components/chat/gallery-thread/tooltip-icon-button.tsx +53 -0
- package/src/components/chat/gallery-thread/typed-welcome-copy.tsx +18 -0
- package/src/components/chat/gallery-thread/ui/tooltip.tsx +22 -0
- package/src/components/chat/gallery-thread/work-chain-summary.tsx +48 -0
- package/src/components/chat/gallery-thread/work-chain.tsx +110 -0
- package/src/components/chat/gallery-thread/work-status.ts +66 -0
- package/src/components/chat/overlay-scrollbar.tsx +86 -0
- package/src/components/chat/sanitize-mermaid-svg.ts +38 -0
- package/src/components/chat/skins/SOURCE.md +36 -0
- package/src/components/chat/skins/base/attachment.aui.tsx +287 -0
- package/src/components/chat/skins/base/aui-default/README.md +45 -0
- package/src/components/chat/skins/base/aui-default/collapsible-root.tsx +19 -0
- package/src/components/chat/skins/base/aui-default/collapsible.tsx +21 -0
- package/src/components/chat/skins/base/aui-default/reasoning.aui.tsx +120 -0
- package/src/components/chat/skins/base/aui-default/reasoning.tsx +331 -0
- package/src/components/chat/skins/base/aui-default/surfaces.ts +7 -0
- package/src/components/chat/skins/base/aui-default/tool-fallback.aui.tsx +772 -0
- package/src/components/chat/skins/base/aui-default/tool-group.aui.tsx +241 -0
- package/src/components/chat/skins/base/docs-model-options.ts +6 -0
- package/src/components/chat/skins/base/elements/assistant-trace.aui.tsx +169 -0
- package/src/components/chat/skins/base/elements/composer-trigger-popover.aui.tsx +271 -0
- package/src/components/chat/skins/base/elements/directive-text.aui.tsx +29 -0
- package/src/components/chat/skins/base/elements/directive-text.tsx +76 -0
- package/src/components/chat/skins/base/elements/message-timing.aui.tsx +104 -0
- package/src/components/chat/skins/base/elements/model-selector.aui.tsx +140 -0
- package/src/components/chat/skins/base/elements/model-selector.tsx +720 -0
- package/src/components/chat/skins/base/elements/quote.aui.tsx +290 -0
- package/src/components/chat/skins/base/elements/work-chain.tsx +100 -0
- package/src/components/chat/skins/base/markdown-text.tsx +294 -0
- package/src/components/chat/skins/base/model.ts +1 -0
- package/src/components/chat/skins/base/thread.tsx +1050 -0
- package/src/components/chat/skins/base/tooltip-icon-button.tsx +56 -0
- package/src/components/chat/skins/base/ui/avatar.tsx +115 -0
- package/src/components/chat/skins/base/ui/badge.tsx +51 -0
- package/src/components/chat/skins/base/ui/button.tsx +67 -0
- package/src/components/chat/skins/base/ui/command.tsx +185 -0
- package/src/components/chat/skins/base/ui/dialog.tsx +168 -0
- package/src/components/chat/skins/base/ui/dot-matrix.tsx +267 -0
- package/src/components/chat/skins/base/ui/popover.tsx +97 -0
- package/src/components/chat/skins/base/ui/skeleton.tsx +20 -0
- package/src/components/chat/skins/base/ui/textarea.tsx +24 -0
- package/src/components/chat/skins/base/ui/tooltip.tsx +73 -0
- package/src/components/chat/skins/base-assistant-ui/config-provider.tsx +90 -0
- package/src/components/chat/skins/base-assistant-ui/defaults.ts +419 -0
- package/src/components/chat/skins/base-assistant-ui/index.tsx +44 -0
- package/src/components/chat/skins/chatgpt.tsx +448 -0
- package/src/components/chat/skins/claude.tsx +380 -0
- package/src/components/chat/skins/digichat-composer-toolbar.tsx +151 -0
- package/src/components/chat/skins/digichat-context-meter-shares.ts +158 -0
- package/src/components/chat/skins/digichat-context-meter.tsx +243 -0
- package/src/components/chat/skins/digichat-meter-tone.ts +26 -0
- package/src/components/chat/skins/digichat-usage-meter.tsx +103 -0
- package/src/components/chat/skins/digichat.tsx +144 -0
- package/src/components/chat/skins/expo-react-native.tsx +29 -0
- package/src/components/chat/skins/gemini.tsx +322 -0
- package/src/components/chat/skins/grok-icon.tsx +34 -0
- package/src/components/chat/skins/grok.tsx +369 -0
- package/src/components/chat/skins/index.ts +46 -0
- package/src/components/chat/skins/perplexity.tsx +462 -0
- package/src/components/chat/skins/product-page-assistant/assistant-modal.tsx +73 -0
- package/src/components/chat/skins/product-page-assistant/index.tsx +19 -0
- package/src/components/chat/skins/product-page-assistant/product-dashboard.tsx +164 -0
- package/src/components/chat/skins/react-ink.tsx +97 -0
- package/src/components/chat/skins/thread-skin-host-contract.ts +102 -0
- package/src/components/chat/skins/thread-skin-view.tsx +156 -0
- package/src/components/chat/skins/thread-skins.ts +143 -0
- package/src/components/chat/skins/tooltip-icon-button.tsx +35 -0
- package/src/components/chat/skins/ui/dropdown-menu.tsx +272 -0
- package/src/components/chat/skins/webpage-assistant/assistant-config.ts +238 -0
- package/src/components/chat/skins/webpage-assistant/assistant-modal.tsx +78 -0
- package/src/components/chat/skins/webpage-assistant/config-provider.tsx +132 -0
- package/src/components/chat/skins/webpage-assistant/docs-app.tsx +106 -0
- package/src/components/chat/skins/webpage-assistant/docs-article.tsx +109 -0
- package/src/components/chat/skins/webpage-assistant/docs-assistant.tsx +73 -0
- package/src/components/chat/skins/webpage-assistant/docs-nav.tsx +81 -0
- package/src/components/chat/skins/webpage-assistant/host-ui.ts +79 -0
- package/src/components/chat/skins/webpage-assistant/index.tsx +12 -0
- package/src/components/chat/skins/webpage-assistant/markdown-page.tsx +11 -0
- package/src/components/chat/skins/webpage-assistant/pages.ts +9 -0
- package/src/components/chat/skins/webpage-assistant/types.ts +178 -0
- package/src/components/chat/skins/webpage-assistant/use-mobile.ts +19 -0
- package/src/components/chat/stock/SOURCE.md +90 -0
- package/src/components/chat/stock/artifacts-panel.tsx +305 -0
- package/src/components/chat/stock/attachment.aui.tsx +280 -0
- package/src/components/chat/stock/chat-notice.tsx +143 -0
- package/src/components/chat/stock/chat-private-mode.tsx +64 -0
- package/src/components/chat/stock/deploy-ui-context.tsx +89 -0
- package/src/components/chat/stock/embed-chat-prefs.tsx +471 -0
- package/src/components/chat/stock/file.tsx +265 -0
- package/src/components/chat/stock/follow-up-suggestions.aui.tsx +83 -0
- package/src/components/chat/stock/image.tsx +531 -0
- package/src/components/chat/stock/index.ts +129 -0
- package/src/components/chat/stock/json-view.tsx +140 -0
- package/src/components/chat/stock/markdown-text.tsx +319 -0
- package/src/components/chat/stock/message-error.aui.tsx +96 -0
- package/src/components/chat/stock/message-export-menu.tsx +156 -0
- package/src/components/chat/stock/palette-row-preview.tsx +26 -0
- package/src/components/chat/stock/reasoning.aui.tsx +120 -0
- package/src/components/chat/stock/reasoning.tsx +335 -0
- package/src/components/chat/stock/skin-chrome.tsx +81 -0
- package/src/components/chat/stock/skin-credit.tsx +63 -0
- package/src/components/chat/stock/skin-palette-bridge.tsx +235 -0
- package/src/components/chat/stock/skin-palette-context.ts +16 -0
- package/src/components/chat/stock/skin-runtime.tsx +52 -0
- package/src/components/chat/stock/skin-slash-palette.tsx +393 -0
- package/src/components/chat/stock/skin-types.ts +217 -0
- package/src/components/chat/stock/stock-chat-prefs-host.tsx +501 -0
- package/src/components/chat/stock/stock-send-gate.tsx +94 -0
- package/src/components/chat/stock/stock-shape.tsx +39 -0
- package/src/components/chat/stock/thread.aui.tsx +645 -0
- package/src/components/chat/stock/tool-display.ts +6 -0
- package/src/components/chat/stock/tool-fallback.aui.tsx +866 -0
- package/src/components/chat/stock/tool-group.aui.tsx +244 -0
- package/src/components/chat/stock/tool-surface.ts +120 -0
- package/src/components/chat/stock/tooltip-icon-button.tsx +49 -0
- package/src/components/chat/stock/ui/avatar.tsx +108 -0
- package/src/components/chat/stock/ui/button.tsx +81 -0
- package/src/components/chat/stock/ui/collapsible.tsx +21 -0
- package/src/components/chat/stock/ui/dialog.tsx +163 -0
- package/src/components/chat/stock/ui/skeleton.tsx +13 -0
- package/src/components/chat/stock/ui/textarea.tsx +20 -0
- package/src/components/chat/stock/ui/tooltip.tsx +68 -0
- package/src/components/chat/stock/use-attachment-src.ts +42 -0
- package/src/components/chat/stock/use-copy-to-clipboard.ts +29 -0
- package/src/components/chat/strip-model-boilerplate.ts +42 -0
- package/src/components/chat/suggestion-cycle.tsx +230 -0
- package/src/components/chat/suggestion-scroll-row.tsx +81 -0
- package/src/components/chat/trace-grouping/chain-group-by.ts +44 -0
- package/src/components/chat/trace-grouping/chain-state.ts +141 -0
- package/src/components/chat/trace-grouping/index.ts +32 -0
- package/src/components/chat/trace-grouping/tool-row.ts +74 -0
- package/src/components/chat/trace-grouping/use-deploy-work-disclosure.ts +35 -0
- package/src/components/chat/trace-grouping/use-work-chain.ts +103 -0
- package/src/components/chat/trace-i18n/core.ts +48 -0
- package/src/components/chat/trace-i18n/index.tsx +23 -0
- package/src/components/chat/trace-i18n/locales/en.ts +24 -0
- package/src/components/chat/trace-i18n/locales/fr.ts +28 -0
- package/src/components/chat/trace-i18n/types.ts +35 -0
- package/src/components/chat/trace-spacing.ts +249 -0
- package/src/components/chat/transcript/activity-view.ts +375 -0
- package/src/components/chat/transcript/chat-export.ts +133 -0
- package/src/components/chat/transcript/index.ts +10 -0
- package/src/components/chat/transcript/save-file.ts +209 -0
- package/src/components/chat/transcript/transcript-markdown.ts +428 -0
- package/src/components/chat/transcript/types.ts +81 -0
- package/src/components/chat/turn-outcome.ts +52 -0
- package/src/components/chat/use-warmup-phase.ts +18 -0
- package/src/components/chat/work-status-lead.tsx +49 -0
- package/src/components/chrome.tsx +264 -0
- package/src/components/command-palette/CommandPalette.tsx +404 -0
- package/src/components/command-palette/index.ts +6 -0
- package/src/components/contact/ContactMailto.tsx +112 -0
- package/src/components/contact/index.ts +5 -0
- package/src/components/conviction/ConvictionMeter.tsx +40 -0
- package/src/components/conviction/SignedConvictionBadge.tsx +26 -0
- package/src/components/conviction/index.ts +7 -0
- package/src/components/data-layout/BentoGrid.tsx +52 -0
- package/src/components/data-layout/CardRail.tsx +150 -0
- package/src/components/data-layout/DotMatrixStat.tsx +65 -0
- package/src/components/data-layout/FeatureCell.tsx +80 -0
- package/src/components/data-layout/MediaFrame.tsx +125 -0
- package/src/components/data-layout/ModuleGrid.tsx +735 -0
- package/src/components/data-layout/Odometer.tsx +149 -0
- package/src/components/data-layout/ProductFrame.tsx +79 -0
- package/src/components/data-layout/TestimonialWall.tsx +139 -0
- package/src/components/data-layout/index.ts +19 -0
- package/src/components/data-layout/treemap.ts +368 -0
- package/src/components/deck/DeckStack.tsx +243 -0
- package/src/components/desk/DataTable.tsx +373 -0
- package/src/components/desk/KpiGrid.tsx +80 -0
- package/src/components/desk/Sparkline.tsx +63 -0
- package/src/components/desk/StateBlock.tsx +132 -0
- package/src/components/desk/index.ts +18 -0
- package/src/components/diagrams/ArchitectureDiagram.tsx +350 -0
- package/src/components/diagrams/ArchitectureTour.tsx +912 -0
- package/src/components/diagrams/TerminalSchematic.tsx +192 -0
- package/src/components/diagrams/architecture-svg.tsx +384 -0
- package/src/components/diagrams/index.ts +30 -0
- package/src/components/diagrams/mermaid-theme.ts +115 -0
- package/src/components/diagrams/tour-camera.ts +171 -0
- package/src/components/docs/CodeTabs.tsx +90 -0
- package/src/components/docs/CopyCommand.tsx +151 -0
- package/src/components/docs/DocsLayout.tsx +197 -0
- package/src/components/docs/DocsSearch.tsx +97 -0
- package/src/components/docs/Endpoint.tsx +135 -0
- package/src/components/document/DocumentFrame.tsx +43 -0
- package/src/components/document/GlyphList.tsx +51 -0
- package/src/components/document/PageTitle.tsx +44 -0
- package/src/components/document/Prose.tsx +38 -0
- package/src/components/document/Section.tsx +55 -0
- package/src/components/document/index.ts +13 -0
- package/src/components/effects-chrome/AnnouncementBar.tsx +88 -0
- package/src/components/effects-chrome/HorizontalScrollTrack.tsx +461 -0
- package/src/components/effects-chrome/Pipeline.tsx +226 -0
- package/src/components/effects-chrome/RotatingPrompts.tsx +116 -0
- package/src/components/effects-chrome/StackingPanels.tsx +101 -0
- package/src/components/effects-chrome/TabStrip.tsx +232 -0
- package/src/components/effects-chrome/ToastStack.tsx +201 -0
- package/src/components/effects-chrome/horizontal-track-core.ts +70 -0
- package/src/components/effects-chrome/index.ts +35 -0
- package/src/components/emblems.tsx +188 -0
- package/src/components/faviconTheme.ts +23 -0
- package/src/components/figure/Figure.tsx +38 -0
- package/src/components/figure/index.ts +2 -0
- package/src/components/finance-charts/DrawdownPlot.tsx +76 -0
- package/src/components/finance-charts/EquityCurve.tsx +71 -0
- package/src/components/finance-charts/PriceChart.tsx +119 -0
- package/src/components/finance-charts/chart-host.tsx +236 -0
- package/src/components/finance-charts/chart-scale.ts +93 -0
- package/src/components/finance-charts/demo-data.ts +88 -0
- package/src/components/finance-charts/index.ts +46 -0
- package/src/components/finance-charts/lw-chart.tsx +292 -0
- package/src/components/finance-composites/MarketBar.tsx +243 -0
- package/src/components/finance-composites/OrderBook.tsx +133 -0
- package/src/components/finance-composites/PerformanceDashboard.tsx +185 -0
- package/src/components/finance-composites/SortableTable.tsx +144 -0
- package/src/components/finance-composites/StockTicker.tsx +65 -0
- package/src/components/finance-composites/SyncedTearsheet.tsx +226 -0
- package/src/components/finance-composites/index.ts +28 -0
- package/src/components/finance-tearsheet/KpiStrip.tsx +49 -0
- package/src/components/finance-tearsheet/LiveBadge.tsx +29 -0
- package/src/components/finance-tearsheet/ReturnsMatrix.tsx +321 -0
- package/src/components/finance-tearsheet/StrategyTearsheet.tsx +99 -0
- package/src/components/finance-tearsheet/TearsheetCard.tsx +54 -0
- package/src/components/finance-tearsheet/TradeLogTable.tsx +73 -0
- package/src/components/finance-tearsheet/charts.tsx +2624 -0
- package/src/components/finance-tearsheet/demo-data.ts +177 -0
- package/src/components/finance-tearsheet/format.ts +41 -0
- package/src/components/finance-tearsheet/index.ts +120 -0
- package/src/components/finance-tearsheet/print.ts +52 -0
- package/src/components/finance-tearsheet/risk-bands.ts +48 -0
- package/src/components/finance-tearsheet/stats.ts +35 -0
- package/src/components/finance-tearsheet/strategy-sheet-model.ts +238 -0
- package/src/components/finance-tearsheet/types.ts +58 -0
- package/src/components/footer/FooterCells.tsx +109 -0
- package/src/components/footer/index.ts +6 -0
- package/src/components/graph.tsx +158 -0
- package/src/components/icons.tsx +98 -0
- package/src/components/landing/PixelWordmark.tsx +36 -0
- package/src/components/logos.ts +39 -0
- package/src/components/marquee/Marquee.tsx +204 -0
- package/src/components/metrics/PerfMetrics.tsx +68 -0
- package/src/components/metrics/StatCounter.tsx +104 -0
- package/src/components/page-geometry.tsx +177 -0
- package/src/components/pricing/Pricing.tsx +174 -0
- package/src/components/pricing/PricingMatrix.tsx +219 -0
- package/src/components/prose/index.ts +2 -0
- package/src/components/prose/prose.tsx +86 -0
- package/src/components/repo-activity/RepoActivity.tsx +599 -0
- package/src/components/repo-activity/RepoHeatmap.tsx +225 -0
- package/src/components/repo-activity/demo.ts +201 -0
- package/src/components/repo-activity/fetch.ts +211 -0
- package/src/components/repo-activity/heatmap.ts +123 -0
- package/src/components/repo-activity/index.ts +28 -0
- package/src/components/repo-activity/types.ts +99 -0
- package/src/components/stages/NumberedStages.tsx +116 -0
- package/src/components/symbols/marks.tsx +116 -0
- package/src/components/symbols/terminal-lockup.tsx +136 -0
- package/src/components/symbols/terminal-marks.tsx +226 -0
- package/src/components/terminal/ChatResponseLoader.tsx +67 -0
- package/src/components/terminal/ContainerBootLoader.tsx +82 -0
- package/src/components/terminal/TerminalStepCaret.tsx +202 -0
- package/src/components/themeInit.ts +24 -0
- package/src/components/typography/MotionReveal.tsx +108 -0
- package/src/components/typography/TypewriterReveal.tsx +101 -0
- package/src/components/typography/WordReveal.tsx +143 -0
- package/src/data/modules.ts +378 -0
- package/src/data/subsystems.ts +101 -0
- package/src/index.ts +587 -0
- package/src/lib/base-path.ts +60 -0
- package/src/lib/cx.ts +25 -0
- package/src/lib/gloomberb.ts +69 -0
- package/src/lib/record.ts +19 -0
- package/src/lib/stub-payload.ts +183 -0
- package/src/lib/useBodyScrollLock.ts +44 -0
- package/src/lib/utils.ts +6 -0
- package/src/motion/primitives.tsx +105 -0
- package/src/motion/scroll-glide.ts +62 -0
- package/src/motion/scrolly-core.ts +38 -0
- package/src/motion/scrolly.tsx +95 -0
- package/src/navigation/HashScrollManager.tsx +167 -0
- package/src/navigation/hashScroll.ts +78 -0
- package/src/replay/browser.ts +280 -0
- package/src/replay/chat-export-run.ts +121 -0
- package/src/replay/export-format.ts +25 -0
- package/src/replay/export-mount.ts +32 -0
- package/src/replay/filename.ts +54 -0
- package/src/replay/index.ts +21 -0
- package/src/replay/redact-dom.ts +178 -0
- package/src/replay/redact.ts +113 -0
- package/src/replay/snapshot.ts +659 -0
- package/src/styles/account-auth.css +339 -0
- package/src/styles/brand-wordmark.css +11 -0
- package/src/styles/card-rail.css +120 -0
- package/src/styles/chat-aui.css +1143 -0
- package/src/styles/chat-core.css +469 -0
- package/src/styles/chat-digichat.css +2531 -0
- package/src/styles/chat-math.css +70 -0
- package/src/styles/chat-skin-palette.css +138 -0
- package/src/styles/chat-stock-menu.css +37 -0
- package/src/styles/chat-widgets.css +190 -0
- package/src/styles/command-palette.css +372 -0
- package/src/styles/controls-core.css +2261 -0
- package/src/styles/controls-overlay.css +603 -0
- package/src/styles/data-layout.css +288 -0
- package/src/styles/deck.css +141 -0
- package/src/styles/diagrams.css +1860 -0
- package/src/styles/digichat-app-theme.css +250 -0
- package/src/styles/digichat-boot-loader.css +487 -0
- package/src/styles/digichat-launcher.css +377 -0
- package/src/styles/docs.css +165 -0
- package/src/styles/effects-chrome.css +888 -0
- package/src/styles/finance-composites.css +214 -0
- package/src/styles/finance-tearsheet-overlay.css +90 -0
- package/src/styles/finance-tearsheet.css +945 -0
- package/src/styles/marquee.css +168 -0
- package/src/styles/metrics.css +82 -0
- package/src/styles/nav-shell.css +551 -0
- package/src/styles/pricing.css +79 -0
- package/src/styles/repo-activity.css +575 -0
- package/src/styles/stages.css +69 -0
- package/src/styles/terminal-loaders.css +122 -0
- package/src/styles/terminal-manifest.css +228 -0
- package/src/styles/web-theme.css +1007 -0
- package/src/styles/word-reveal.css +204 -0
- package/src/theme/apply-theme.ts +214 -0
- package/src/theme/index.ts +27 -0
- package/src/theme/palette-registry.ts +855 -0
- package/src/theme/tui-prefs.ts +64 -0
- package/src/ui/alert.tsx +78 -0
- package/src/ui/avatar.tsx +104 -0
- package/src/ui/badge.tsx +75 -0
- package/src/ui/breadcrumb.tsx +61 -0
- package/src/ui/button.tsx +67 -0
- package/src/ui/card.tsx +139 -0
- package/src/ui/checkbox.tsx +27 -0
- package/src/ui/collapsible.tsx +32 -0
- package/src/ui/date-pager.tsx +325 -0
- package/src/ui/dialog.tsx +157 -0
- package/src/ui/dropdown-menu.tsx +267 -0
- package/src/ui/empty-state.tsx +165 -0
- package/src/ui/field.tsx +104 -0
- package/src/ui/form.tsx +36 -0
- package/src/ui/icon-button.tsx +36 -0
- package/src/ui/index.ts +34 -0
- package/src/ui/input.tsx +33 -0
- package/src/ui/kbd.tsx +30 -0
- package/src/ui/label.tsx +32 -0
- package/src/ui/pager.tsx +119 -0
- package/src/ui/pagination.tsx +126 -0
- package/src/ui/radio-group.tsx +51 -0
- package/src/ui/search-bar.tsx +85 -0
- package/src/ui/segmented-control.tsx +76 -0
- package/src/ui/select.tsx +257 -0
- package/src/ui/separator.tsx +24 -0
- package/src/ui/sheet.tsx +141 -0
- package/src/ui/skeleton.tsx +76 -0
- package/src/ui/slider.tsx +76 -0
- package/src/ui/switch.tsx +31 -0
- package/src/ui/table.tsx +179 -0
- package/src/ui/tabs.tsx +89 -0
- package/src/ui/tags-input.tsx +168 -0
- package/src/ui/textarea.tsx +17 -0
- package/src/ui/tooltip.tsx +71 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, type CSSProperties, type RefObject } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Block caret for the gallery-thread composer.
|
|
7
|
+
*
|
|
8
|
+
* `caret-shape: block` is Chromium-only, so on engines without it the native
|
|
9
|
+
* caret is made transparent (see the `@supports not (caret-shape: block)`
|
|
10
|
+
* rules in the reference chatbot.css) and this painted block tracks it. The
|
|
11
|
+
* digichat embed likewise promotes the painted block ONLY on engines without a
|
|
12
|
+
* native block caret (inside the same `@supports not (caret-shape: block)`
|
|
13
|
+
* guard in product-chrome.css), so the tracking must be runtime-robust.
|
|
14
|
+
*
|
|
15
|
+
* Positioning is measured in pixels from the live textarea: the current
|
|
16
|
+
* logical line is sliced from the value + selection offset, its width is
|
|
17
|
+
* measured with a canvas 2d context using the textarea's own computed font,
|
|
18
|
+
* and the block is placed at padding + measured width (minus scroll). The old
|
|
19
|
+
* `col * 1ch` math drifted because the overlay's font-size (0.88rem) never
|
|
20
|
+
* matched the input's (`text-sm`/`text-base`), so the error grew per
|
|
21
|
+
* keystroke — behind on some skins, ahead on others. Soft-wrapped visual
|
|
22
|
+
* lines are followed with a hidden mirror div (see mirrorCaretPx); the
|
|
23
|
+
* logical-line canvas math is only the no-layout fallback.
|
|
24
|
+
*
|
|
25
|
+
* The textarea can mount after this overlay (the composer renders it from its
|
|
26
|
+
* own lifecycle), so listeners live on the document and the textarea is
|
|
27
|
+
* re-resolved per event; the initial paint retries briefly until it exists.
|
|
28
|
+
* Syncs run on the next animation frame so React-controlled value /
|
|
29
|
+
* selection updates have landed before we measure.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
export function caretRowCol(text: string, offset: number): { row: number; col: number } {
|
|
33
|
+
const safe = Math.max(0, Math.min(Math.trunc(offset), text.length));
|
|
34
|
+
const before = text.slice(0, safe);
|
|
35
|
+
const row = (before.match(/\n/g) ?? []).length;
|
|
36
|
+
return { row, col: safe - before.lastIndexOf("\n") - 1 };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const CARET_EVENTS = [
|
|
40
|
+
"select",
|
|
41
|
+
"input",
|
|
42
|
+
"beforeinput",
|
|
43
|
+
"click",
|
|
44
|
+
"keydown",
|
|
45
|
+
"keyup",
|
|
46
|
+
"compositionupdate",
|
|
47
|
+
"focusin",
|
|
48
|
+
"scroll",
|
|
49
|
+
] as const;
|
|
50
|
+
const SYNC_RETRY_MS = 50;
|
|
51
|
+
const SYNC_RETRY_LIMIT = 40;
|
|
52
|
+
|
|
53
|
+
let measureCanvas: HTMLCanvasElement | null = null;
|
|
54
|
+
|
|
55
|
+
function measureTextWidth(text: string, font: string): number | null {
|
|
56
|
+
if (!text) return 0;
|
|
57
|
+
try {
|
|
58
|
+
if (!measureCanvas && typeof document !== "undefined") {
|
|
59
|
+
measureCanvas = document.createElement("canvas");
|
|
60
|
+
}
|
|
61
|
+
const ctx = measureCanvas?.getContext("2d");
|
|
62
|
+
if (!ctx) return null;
|
|
63
|
+
ctx.font = font;
|
|
64
|
+
return ctx.measureText(text).width;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The ink height of a capital/digit: how far the glyphs rise above the
|
|
72
|
+
* baseline. Measured from the font, not guessed, because the caret is placed
|
|
73
|
+
* against it.
|
|
74
|
+
*/
|
|
75
|
+
function fontInkAscent(font: string, fontSize: number): number {
|
|
76
|
+
const fallback = fontSize * 0.72;
|
|
77
|
+
try {
|
|
78
|
+
if (!measureCanvas && typeof document !== "undefined") {
|
|
79
|
+
measureCanvas = document.createElement("canvas");
|
|
80
|
+
}
|
|
81
|
+
const ctx = measureCanvas?.getContext("2d");
|
|
82
|
+
if (!ctx) return fallback;
|
|
83
|
+
ctx.font = font;
|
|
84
|
+
const digit = ctx.measureText("0").actualBoundingBoxAscent;
|
|
85
|
+
const cap = ctx.measureText("H").actualBoundingBoxAscent;
|
|
86
|
+
const ink = Math.max(
|
|
87
|
+
Number.isFinite(digit) ? digit : 0,
|
|
88
|
+
Number.isFinite(cap) ? cap : 0,
|
|
89
|
+
);
|
|
90
|
+
return ink > 0 ? ink : fallback;
|
|
91
|
+
} catch {
|
|
92
|
+
return fallback;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The font's own vertical metrics: the ascent and descent the browser uses to
|
|
98
|
+
* place the baseline inside a line box. Measured, not guessed — the block
|
|
99
|
+
* caret has to sit on the same baseline the text does, and a hardcoded
|
|
100
|
+
* ascent ratio lands off the glyphs on other families.
|
|
101
|
+
*/
|
|
102
|
+
function fontVerticalMetrics(
|
|
103
|
+
font: string,
|
|
104
|
+
fontSize: number,
|
|
105
|
+
): { ascent: number; descent: number } {
|
|
106
|
+
const fallback = { ascent: fontSize * 0.8, descent: fontSize * 0.2 };
|
|
107
|
+
try {
|
|
108
|
+
if (!measureCanvas && typeof document !== "undefined") {
|
|
109
|
+
measureCanvas = document.createElement("canvas");
|
|
110
|
+
}
|
|
111
|
+
const ctx = measureCanvas?.getContext("2d");
|
|
112
|
+
if (!ctx) return fallback;
|
|
113
|
+
ctx.font = font;
|
|
114
|
+
const m = ctx.measureText("Hxg0");
|
|
115
|
+
const ascent = m.fontBoundingBoxAscent;
|
|
116
|
+
const descent = m.fontBoundingBoxDescent;
|
|
117
|
+
return {
|
|
118
|
+
ascent: Number.isFinite(ascent) && ascent > 0 ? ascent : fallback.ascent,
|
|
119
|
+
descent: Number.isFinite(descent) && descent >= 0 ? descent : fallback.descent,
|
|
120
|
+
};
|
|
121
|
+
} catch {
|
|
122
|
+
return fallback;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function px(value: string | null | undefined, fallback: number): number {
|
|
127
|
+
if (!value) return fallback;
|
|
128
|
+
const n = Number.parseFloat(value);
|
|
129
|
+
return Number.isFinite(n) ? n : fallback;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Canvas 2d font string for the textarea. Built from the individual longhands
|
|
134
|
+
* — NEVER from the `font` shorthand: computed `font` includes `/line-height`
|
|
135
|
+
* (`400 14px/22px …`), which `ctx.font` rejects, silently leaving the canvas
|
|
136
|
+
* at default 10px sans-serif. Every glyph then measures wrong and the error
|
|
137
|
+
* compounds per character (block drifts further the longer the line).
|
|
138
|
+
*/
|
|
139
|
+
export function canvasFont(cs: CSSStyleDeclaration | null, fontSize: number): string {
|
|
140
|
+
const style = cs?.fontStyle && cs.fontStyle !== "" ? cs.fontStyle : "normal";
|
|
141
|
+
const variant = cs?.fontVariant && cs.fontVariant !== "" ? cs.fontVariant : "normal";
|
|
142
|
+
const weight = cs?.fontWeight && cs.fontWeight !== "" ? cs.fontWeight : "400";
|
|
143
|
+
const family = cs?.fontFamily && cs.fontFamily !== "" ? cs.fontFamily : "monospace";
|
|
144
|
+
return `${style} ${variant} ${weight} ${fontSize}px ${family}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Expand tabs the way a textarea lays them out (advance to next tab stop). */
|
|
148
|
+
export function expandTabs(line: string, tabSize: number): string {
|
|
149
|
+
if (!line.includes("\t")) return line;
|
|
150
|
+
const stops = Math.max(1, Math.trunc(tabSize) || 8);
|
|
151
|
+
let out = "";
|
|
152
|
+
let col = 0;
|
|
153
|
+
for (const ch of line) {
|
|
154
|
+
if (ch === "\t") {
|
|
155
|
+
const next = col + (stops - (col % stops));
|
|
156
|
+
out += " ".repeat(next - col);
|
|
157
|
+
col = next;
|
|
158
|
+
} else {
|
|
159
|
+
out += ch;
|
|
160
|
+
col += 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const MIRROR_PROPS = [
|
|
167
|
+
"boxSizing",
|
|
168
|
+
"width",
|
|
169
|
+
"borderTopWidth",
|
|
170
|
+
"borderRightWidth",
|
|
171
|
+
"borderBottomWidth",
|
|
172
|
+
"borderLeftWidth",
|
|
173
|
+
"paddingTop",
|
|
174
|
+
"paddingRight",
|
|
175
|
+
"paddingBottom",
|
|
176
|
+
"paddingLeft",
|
|
177
|
+
"fontStyle",
|
|
178
|
+
"fontVariant",
|
|
179
|
+
"fontWeight",
|
|
180
|
+
"fontStretch",
|
|
181
|
+
"fontSize",
|
|
182
|
+
"fontFamily",
|
|
183
|
+
"lineHeight",
|
|
184
|
+
"letterSpacing",
|
|
185
|
+
"wordSpacing",
|
|
186
|
+
"textTransform",
|
|
187
|
+
"textIndent",
|
|
188
|
+
"tabSize",
|
|
189
|
+
] as const;
|
|
190
|
+
|
|
191
|
+
let mirrorDiv: HTMLDivElement | null = null;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Wrapped caret position via a hidden mirror of the textarea (same width,
|
|
195
|
+
* padding and font, `pre-wrap`), so soft-wrapped lines are followed: the old
|
|
196
|
+
* logical-line math kept growing `left` past the right edge on a long single
|
|
197
|
+
* line instead of dropping to the next visual row. Returns null when the
|
|
198
|
+
* textarea has no layout (jsdom, display:none) so the canvas path still runs.
|
|
199
|
+
*
|
|
200
|
+
* The returned baseline is the text's own baseline, read with a zero-height
|
|
201
|
+
* inline-block probe (its bottom edge sits on the baseline). Measuring it
|
|
202
|
+
* beats modelling it: the browser's line layout and the canvas metrics
|
|
203
|
+
* disagree by a pixel or two, and a caret that misses the baseline by that
|
|
204
|
+
* much reads as "sitting below the letters".
|
|
205
|
+
*/
|
|
206
|
+
function mirrorCaretPx(
|
|
207
|
+
area: HTMLTextAreaElement,
|
|
208
|
+
cs: CSSStyleDeclaration | null,
|
|
209
|
+
): { left: number; baseline: number; lineHeight: number } | null {
|
|
210
|
+
if (!cs || typeof document === "undefined" || !area.clientWidth) return null;
|
|
211
|
+
try {
|
|
212
|
+
if (!mirrorDiv) {
|
|
213
|
+
mirrorDiv = document.createElement("div");
|
|
214
|
+
mirrorDiv.setAttribute("aria-hidden", "true");
|
|
215
|
+
document.body.appendChild(mirrorDiv);
|
|
216
|
+
}
|
|
217
|
+
const style = mirrorDiv.style;
|
|
218
|
+
for (const prop of MIRROR_PROPS) {
|
|
219
|
+
(style as unknown as Record<string, string>)[prop] = cs[prop] as string;
|
|
220
|
+
}
|
|
221
|
+
style.position = "absolute";
|
|
222
|
+
style.visibility = "hidden";
|
|
223
|
+
style.top = "0";
|
|
224
|
+
style.left = "-9999px";
|
|
225
|
+
style.whiteSpace = "pre-wrap";
|
|
226
|
+
style.overflowWrap = "break-word";
|
|
227
|
+
style.wordBreak = cs.wordBreak || "normal";
|
|
228
|
+
style.overflow = "hidden";
|
|
229
|
+
style.height = "auto";
|
|
230
|
+
// The textarea's width includes a vertical scrollbar once it scrolls.
|
|
231
|
+
style.width = `${area.offsetWidth}px`;
|
|
232
|
+
const offset = area.selectionStart ?? area.value.length;
|
|
233
|
+
mirrorDiv.textContent = area.value.slice(0, offset);
|
|
234
|
+
const marker = document.createElement("span");
|
|
235
|
+
// A zero-width glyph keeps the marker on the line the next char would use.
|
|
236
|
+
marker.textContent = area.value.slice(offset) ? area.value.slice(offset, offset + 1) : "\u200b";
|
|
237
|
+
mirrorDiv.appendChild(marker);
|
|
238
|
+
// Zero-height inline-block: its bottom margin edge sits on the baseline.
|
|
239
|
+
const probe = document.createElement("span");
|
|
240
|
+
probe.style.display = "inline-block";
|
|
241
|
+
probe.style.width = "0";
|
|
242
|
+
probe.style.height = "0";
|
|
243
|
+
mirrorDiv.appendChild(probe);
|
|
244
|
+
const fontSize = px(cs.fontSize, 14);
|
|
245
|
+
const lh = cs.lineHeight === "normal" || !cs.lineHeight ? fontSize * 1.5 : px(cs.lineHeight, fontSize * 1.5);
|
|
246
|
+
return {
|
|
247
|
+
left: marker.offsetLeft,
|
|
248
|
+
baseline: probe.offsetTop,
|
|
249
|
+
lineHeight: lh,
|
|
250
|
+
};
|
|
251
|
+
} catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Pixel position of the caret relative to the container. Falls back to null when unmeasurable. */
|
|
257
|
+
export function caretPx(
|
|
258
|
+
area: HTMLTextAreaElement,
|
|
259
|
+
container: HTMLElement | null,
|
|
260
|
+
): { left: number; top: number; height: number; width: number } | null {
|
|
261
|
+
const { row, col } = caretRowCol(area.value, area.selectionStart ?? area.value.length);
|
|
262
|
+
const lines = area.value.split("\n");
|
|
263
|
+
const lineText = (lines[row] ?? "").slice(0, col);
|
|
264
|
+
|
|
265
|
+
let cs: CSSStyleDeclaration | null = null;
|
|
266
|
+
try {
|
|
267
|
+
cs = window.getComputedStyle(area);
|
|
268
|
+
} catch {
|
|
269
|
+
cs = null;
|
|
270
|
+
}
|
|
271
|
+
const fontSize = px(cs?.fontSize, 14);
|
|
272
|
+
const rawLineHeight = cs?.lineHeight ?? "";
|
|
273
|
+
const lineHeight = rawLineHeight === "normal" || !rawLineHeight ? fontSize * 1.5 : px(rawLineHeight, fontSize * 1.5);
|
|
274
|
+
const font = canvasFont(cs, fontSize);
|
|
275
|
+
|
|
276
|
+
const tabSizeRaw = cs?.tabSize ?? "";
|
|
277
|
+
const tabSize = tabSizeRaw.endsWith("px") ? px(tabSizeRaw, 8) / Math.max(1, fontSize * 0.6) : px(tabSizeRaw, 8);
|
|
278
|
+
const lineExpanded = expandTabs(lineText, tabSize);
|
|
279
|
+
const measured = measureTextWidth(lineExpanded, font);
|
|
280
|
+
const chWidth = measureTextWidth("0", font) ?? fontSize * 0.6;
|
|
281
|
+
// Canvas measureText ignores CSS letter/word-spacing — add them per unit
|
|
282
|
+
// or the block drifts a fraction of a pixel per character/space.
|
|
283
|
+
const letterSpacing = px(cs?.letterSpacing, 0);
|
|
284
|
+
const wordSpacing = px(cs?.wordSpacing, 0);
|
|
285
|
+
const spaces = (lineExpanded.match(/ /g) ?? []).length;
|
|
286
|
+
const textWidth =
|
|
287
|
+
(measured ?? [...lineExpanded].length * chWidth) + [...lineExpanded].length * letterSpacing + spaces * wordSpacing;
|
|
288
|
+
|
|
289
|
+
const paddingLeft = px(cs?.paddingLeft, 0);
|
|
290
|
+
const paddingTop = px(cs?.paddingTop, 0);
|
|
291
|
+
const borderLeft = px(cs?.borderLeftWidth, 0);
|
|
292
|
+
const borderTop = px(cs?.borderTopWidth, 0);
|
|
293
|
+
|
|
294
|
+
// Overlay is absolutely positioned in the relatively-positioned wrapper;
|
|
295
|
+
// offsetLeft/Top pin the textarea's border box inside that wrapper.
|
|
296
|
+
const baseLeft = (area.offsetLeft || 0) + borderLeft + paddingLeft;
|
|
297
|
+
const baseTop = (area.offsetTop || 0) + borderTop + paddingTop;
|
|
298
|
+
void container;
|
|
299
|
+
|
|
300
|
+
// Native-cursor geometry: the caret is as tall as the font's ascent +
|
|
301
|
+
// descent (what a real text cursor spans), and it is centred on the
|
|
302
|
+
// letters' ink box — half way between standing on the baseline and
|
|
303
|
+
// aligning with the top of the glyphs, so it neither hangs below the text
|
|
304
|
+
// nor floats above it.
|
|
305
|
+
const { ascent, descent } = fontVerticalMetrics(font, fontSize);
|
|
306
|
+
const contentHeight = ascent + descent;
|
|
307
|
+
const caretHeight = Math.max(8, Math.round(contentHeight));
|
|
308
|
+
const inkAscent = fontInkAscent(font, fontSize);
|
|
309
|
+
// Distance from the baseline down to the caret's top edge.
|
|
310
|
+
const caretDrop = inkAscent / 2 + caretHeight / 2;
|
|
311
|
+
|
|
312
|
+
const mirrored = mirrorCaretPx(area, cs);
|
|
313
|
+
if (mirrored) {
|
|
314
|
+
return {
|
|
315
|
+
left: (area.offsetLeft || 0) + borderLeft + mirrored.left - (area.scrollLeft || 0),
|
|
316
|
+
// The mirror's baseline is measured from the textarea's padding edge, so
|
|
317
|
+
// the block hangs from it: bottom edge on the baseline, height upward.
|
|
318
|
+
top:
|
|
319
|
+
(area.offsetTop || 0) +
|
|
320
|
+
borderTop +
|
|
321
|
+
mirrored.baseline -
|
|
322
|
+
caretDrop -
|
|
323
|
+
(area.scrollTop || 0),
|
|
324
|
+
height: caretHeight,
|
|
325
|
+
width: Math.max(1, chWidth),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// No layout to measure (jsdom, display:none): fall back to the font model.
|
|
330
|
+
const halfLeading = (lineHeight - contentHeight) / 2;
|
|
331
|
+
const baseline = baseTop + row * lineHeight + halfLeading + ascent;
|
|
332
|
+
return {
|
|
333
|
+
left: baseLeft + textWidth - (area.scrollLeft || 0),
|
|
334
|
+
top: baseline - caretDrop - (area.scrollTop || 0),
|
|
335
|
+
height: caretHeight,
|
|
336
|
+
width: Math.max(1, chWidth),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function ComposerBlockCaret({
|
|
341
|
+
containerRef,
|
|
342
|
+
}: {
|
|
343
|
+
/** Ref of the wrapper holding the composer textarea. */
|
|
344
|
+
containerRef: RefObject<HTMLDivElement | null>;
|
|
345
|
+
}) {
|
|
346
|
+
const [pos, setPos] = useState({ row: 0, col: 0, left: 0, top: 0, height: 22, width: 8 });
|
|
347
|
+
|
|
348
|
+
useEffect(() => {
|
|
349
|
+
let retries = 0;
|
|
350
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
351
|
+
let raf = 0;
|
|
352
|
+
let queued = false;
|
|
353
|
+
|
|
354
|
+
const textarea = () => containerRef.current?.querySelector("textarea") ?? null;
|
|
355
|
+
|
|
356
|
+
const sync = () => {
|
|
357
|
+
const area = textarea();
|
|
358
|
+
if (!area) return false;
|
|
359
|
+
const { row, col } = caretRowCol(area.value, area.selectionStart ?? area.value.length);
|
|
360
|
+
const measured = caretPx(area, containerRef.current);
|
|
361
|
+
setPos(
|
|
362
|
+
measured
|
|
363
|
+
? { row, col, left: measured.left, top: measured.top, height: measured.height, width: measured.width }
|
|
364
|
+
: { row, col, left: col * 8, top: row * 22, height: 22, width: 8 },
|
|
365
|
+
);
|
|
366
|
+
return true;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const schedule = () => {
|
|
370
|
+
if (queued) return;
|
|
371
|
+
queued = true;
|
|
372
|
+
const run = () => {
|
|
373
|
+
queued = false;
|
|
374
|
+
sync();
|
|
375
|
+
};
|
|
376
|
+
if (typeof requestAnimationFrame === "function") {
|
|
377
|
+
raf = requestAnimationFrame(run);
|
|
378
|
+
} else {
|
|
379
|
+
run();
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const onEvent = (event: Event) => {
|
|
384
|
+
const area = textarea();
|
|
385
|
+
if (!area) return;
|
|
386
|
+
if (event.target === area) {
|
|
387
|
+
// Immediate sync keeps unit-test determinism (assert right after
|
|
388
|
+
// dispatch); the rAF pass then re-measures after React-controlled
|
|
389
|
+
// value/selection updates land, which is the live-typing fix.
|
|
390
|
+
sync();
|
|
391
|
+
schedule();
|
|
392
|
+
} else if (event.type === "scroll") {
|
|
393
|
+
schedule();
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
if (!sync()) {
|
|
398
|
+
const retry = () => {
|
|
399
|
+
if (sync() || retries >= SYNC_RETRY_LIMIT) return;
|
|
400
|
+
retries += 1;
|
|
401
|
+
timer = setTimeout(retry, SYNC_RETRY_MS);
|
|
402
|
+
};
|
|
403
|
+
timer = setTimeout(retry, SYNC_RETRY_MS);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
for (const name of CARET_EVENTS) {
|
|
407
|
+
document.addEventListener(name, onEvent, true);
|
|
408
|
+
}
|
|
409
|
+
window.addEventListener("resize", schedule);
|
|
410
|
+
|
|
411
|
+
return () => {
|
|
412
|
+
if (timer) clearTimeout(timer);
|
|
413
|
+
if (raf) cancelAnimationFrame(raf);
|
|
414
|
+
queued = false;
|
|
415
|
+
for (const name of CARET_EVENTS) {
|
|
416
|
+
document.removeEventListener(name, onEvent, true);
|
|
417
|
+
}
|
|
418
|
+
window.removeEventListener("resize", schedule);
|
|
419
|
+
};
|
|
420
|
+
}, [containerRef]);
|
|
421
|
+
|
|
422
|
+
return (
|
|
423
|
+
<span
|
|
424
|
+
aria-hidden="true"
|
|
425
|
+
data-slot="aui_block-caret"
|
|
426
|
+
className="aui-block-caret"
|
|
427
|
+
style={
|
|
428
|
+
{
|
|
429
|
+
"--caret-col": pos.col,
|
|
430
|
+
"--caret-row": pos.row,
|
|
431
|
+
left: `${pos.left}px`,
|
|
432
|
+
top: `${pos.top}px`,
|
|
433
|
+
height: `${pos.height}px`,
|
|
434
|
+
width: `${pos.width}px`,
|
|
435
|
+
} as CSSProperties
|
|
436
|
+
}
|
|
437
|
+
/>
|
|
438
|
+
);
|
|
439
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { cn } from "../../../lib/utils";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer keyboard dismissal for phones.
|
|
3
|
+
*
|
|
4
|
+
* On a phone the software keyboard covers most of the viewport, so sending a
|
|
5
|
+
* message leaves the visitor looking at the first lines of the answer with the
|
|
6
|
+
* keyboard still up. Dropping focus from the textarea retracts the keyboard and
|
|
7
|
+
* gives the streaming answer the whole screen.
|
|
8
|
+
*
|
|
9
|
+
* Scoped to a coarse pointer on purpose: on a desktop the caret belongs in the
|
|
10
|
+
* composer for the follow-up turn, and losing focus there would be a
|
|
11
|
+
* regression.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
type MatchMedia = ((query: string) => MediaQueryList) | undefined;
|
|
15
|
+
|
|
16
|
+
function defaultMatchMedia(): MatchMedia {
|
|
17
|
+
return typeof window === "undefined" ? undefined : window.matchMedia;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** True on a phone or tablet, where the pointer is a finger. */
|
|
21
|
+
export function isCoarsePointer(matchMedia: MatchMedia = defaultMatchMedia()): boolean {
|
|
22
|
+
if (typeof matchMedia !== "function") return false;
|
|
23
|
+
try {
|
|
24
|
+
return matchMedia("(pointer: coarse)").matches;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* True when the composer holds something the user meant to send.
|
|
32
|
+
*
|
|
33
|
+
* Read *before* the submit is handed to the runtime: a successful send clears
|
|
34
|
+
* the textarea, so asking afterwards would answer "empty" for every real send.
|
|
35
|
+
* Enter on an empty composer also submits through the same form, and that one
|
|
36
|
+
* must not steal focus.
|
|
37
|
+
*/
|
|
38
|
+
export function composerHasText(root: HTMLElement | null): boolean {
|
|
39
|
+
if (!root) return false;
|
|
40
|
+
const area = root.querySelector<HTMLTextAreaElement>("textarea");
|
|
41
|
+
return area ? area.value.trim().length > 0 : false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Blur the composer's textarea so the on-screen keyboard retracts.
|
|
46
|
+
*
|
|
47
|
+
* Returns true when the textarea had focus and was blurred, so a caller (or a
|
|
48
|
+
* test) can tell a dismissal from a no-op. Never throws: a missing textarea on
|
|
49
|
+
* a skin that does not use one is a valid state.
|
|
50
|
+
*/
|
|
51
|
+
export function dismissComposerKeyboard(
|
|
52
|
+
root: HTMLElement | null,
|
|
53
|
+
options: { coarse?: boolean } = {},
|
|
54
|
+
): boolean {
|
|
55
|
+
const coarse = options.coarse ?? isCoarsePointer();
|
|
56
|
+
if (!coarse || !root) return false;
|
|
57
|
+
const area = root.querySelector<HTMLTextAreaElement>("textarea");
|
|
58
|
+
if (!area) return false;
|
|
59
|
+
const wasFocused = area.ownerDocument.activeElement === area;
|
|
60
|
+
area.blur();
|
|
61
|
+
return wasFocused;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const TEXT_INPUT_TYPES = new Set([
|
|
65
|
+
"text",
|
|
66
|
+
"search",
|
|
67
|
+
"email",
|
|
68
|
+
"url",
|
|
69
|
+
"tel",
|
|
70
|
+
"password",
|
|
71
|
+
"number",
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
/** True when focusing this element is what opens the software keyboard. */
|
|
75
|
+
function isTextEntry(element: Element | null): boolean {
|
|
76
|
+
if (!element) return false;
|
|
77
|
+
if (element.tagName === "TEXTAREA") return true;
|
|
78
|
+
if (element.tagName === "INPUT") {
|
|
79
|
+
const type = (element as HTMLInputElement).type || "text";
|
|
80
|
+
return TEXT_INPUT_TYPES.has(type);
|
|
81
|
+
}
|
|
82
|
+
return (element as HTMLElement).isContentEditable === true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Blur whatever text field currently holds focus, so the keyboard retracts.
|
|
87
|
+
*
|
|
88
|
+
* The composer is not the only way to send: an example question, a redo, a
|
|
89
|
+
* retry and an edit all start a turn without going through the composer's
|
|
90
|
+
* submit, and each one leaves the keyboard up over the answer. This is the
|
|
91
|
+
* entry-point-shaped dismissal those paths call, and it deliberately works
|
|
92
|
+
* from the document's active element rather than from a composer ref.
|
|
93
|
+
*
|
|
94
|
+
* Only a text field is blurred. A focused button (an example question the
|
|
95
|
+
* visitor tapped) is left alone: it does not open a keyboard, and blurring it
|
|
96
|
+
* would drop the focus ring for a switch or keyboard user on a hybrid device.
|
|
97
|
+
*/
|
|
98
|
+
export function dismissKeyboardIfCoarse(options: { coarse?: boolean } = {}): boolean {
|
|
99
|
+
if (typeof document === "undefined") return false;
|
|
100
|
+
const coarse = options.coarse ?? isCoarsePointer();
|
|
101
|
+
if (!coarse) return false;
|
|
102
|
+
const active = document.activeElement;
|
|
103
|
+
if (!isTextEntry(active)) return false;
|
|
104
|
+
(active as HTMLElement).blur();
|
|
105
|
+
return true;
|
|
106
|
+
}
|