@diegopluna/narvi 0.0.0-stage → 0.0.1
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 +82 -2
- package/dist/components/activity/ActivityContent.svelte +55 -0
- package/dist/components/activity/ActivityContent.svelte.d.ts +11 -0
- package/dist/components/activity/ActivityRoot.svelte +66 -0
- package/dist/components/activity/ActivityRoot.svelte.d.ts +17 -0
- package/dist/components/activity/ActivityStep.svelte +157 -0
- package/dist/components/activity/ActivityStep.svelte.d.ts +26 -0
- package/dist/components/activity/ActivityThought.svelte +60 -0
- package/dist/components/activity/ActivityThought.svelte.d.ts +10 -0
- package/dist/components/activity/ActivityTrigger.svelte +103 -0
- package/dist/components/activity/ActivityTrigger.svelte.d.ts +20 -0
- package/dist/components/activity/context.d.ts +7 -0
- package/dist/components/activity/context.js +11 -0
- package/dist/components/activity/index.d.ts +11 -0
- package/dist/components/activity/index.js +6 -0
- package/dist/components/announcer/Announcer.svelte +84 -0
- package/dist/components/announcer/Announcer.svelte.d.ts +13 -0
- package/dist/components/app-shell/AppShellHeader.svelte +54 -0
- package/dist/components/app-shell/AppShellHeader.svelte.d.ts +15 -0
- package/dist/components/app-shell/AppShellMain.svelte +49 -0
- package/dist/components/app-shell/AppShellMain.svelte.d.ts +11 -0
- package/dist/components/app-shell/AppShellPanel.svelte +107 -0
- package/dist/components/app-shell/AppShellPanel.svelte.d.ts +18 -0
- package/dist/components/app-shell/AppShellRoot.svelte +136 -0
- package/dist/components/app-shell/AppShellRoot.svelte.d.ts +28 -0
- package/dist/components/app-shell/AppShellSidebar.svelte +192 -0
- package/dist/components/app-shell/AppShellSidebar.svelte.d.ts +17 -0
- package/dist/components/app-shell/AppShellToggle.svelte +50 -0
- package/dist/components/app-shell/AppShellToggle.svelte.d.ts +10 -0
- package/dist/components/app-shell/context.d.ts +15 -0
- package/dist/components/app-shell/context.js +15 -0
- package/dist/components/app-shell/index.d.ts +15 -0
- package/dist/components/app-shell/index.js +8 -0
- package/dist/components/approval/Approval.svelte +213 -0
- package/dist/components/approval/Approval.svelte.d.ts +38 -0
- package/dist/components/avatar/AvatarFallback.svelte +73 -0
- package/dist/components/avatar/AvatarFallback.svelte.d.ts +11 -0
- package/dist/components/avatar/AvatarImage.svelte +75 -0
- package/dist/components/avatar/AvatarImage.svelte.d.ts +11 -0
- package/dist/components/avatar/AvatarRoot.svelte +108 -0
- package/dist/components/avatar/AvatarRoot.svelte.d.ts +19 -0
- package/dist/components/avatar/context.d.ts +11 -0
- package/dist/components/avatar/context.js +11 -0
- package/dist/components/avatar/index.d.ts +7 -0
- package/dist/components/avatar/index.js +6 -0
- package/dist/components/badge/Badge.svelte +78 -0
- package/dist/components/badge/Badge.svelte.d.ts +15 -0
- package/dist/components/button/Button.svelte +270 -0
- package/dist/components/button/Button.svelte.d.ts +22 -0
- package/dist/components/card/Card.svelte +65 -0
- package/dist/components/card/Card.svelte.d.ts +15 -0
- package/dist/components/change-stats/ChangeStats.svelte +52 -0
- package/dist/components/change-stats/ChangeStats.svelte.d.ts +14 -0
- package/dist/components/changes-summary/ChangesSummary.svelte +152 -0
- package/dist/components/changes-summary/ChangesSummary.svelte.d.ts +20 -0
- package/dist/components/checkbox/Checkbox.svelte +178 -0
- package/dist/components/checkbox/Checkbox.svelte.d.ts +19 -0
- package/dist/components/code/Code.svelte +33 -0
- package/dist/components/code/Code.svelte.d.ts +10 -0
- package/dist/components/command/CommandDialog.svelte +37 -0
- package/dist/components/command/CommandDialog.svelte.d.ts +12 -0
- package/dist/components/command/CommandEmpty.svelte +20 -0
- package/dist/components/command/CommandEmpty.svelte.d.ts +11 -0
- package/dist/components/command/CommandFooter.svelte +25 -0
- package/dist/components/command/CommandFooter.svelte.d.ts +11 -0
- package/dist/components/command/CommandGroup.svelte +25 -0
- package/dist/components/command/CommandGroup.svelte.d.ts +12 -0
- package/dist/components/command/CommandInput.svelte +33 -0
- package/dist/components/command/CommandInput.svelte.d.ts +8 -0
- package/dist/components/command/CommandItem.svelte +49 -0
- package/dist/components/command/CommandItem.svelte.d.ts +16 -0
- package/dist/components/command/CommandList.svelte +21 -0
- package/dist/components/command/CommandList.svelte.d.ts +10 -0
- package/dist/components/command/CommandRoot.svelte +20 -0
- package/dist/components/command/CommandRoot.svelte.d.ts +11 -0
- package/dist/components/command/index.d.ts +20 -0
- package/dist/components/command/index.js +12 -0
- package/dist/components/command/styles.d.ts +117 -0
- package/dist/components/command/styles.js +121 -0
- package/dist/components/composer/ComposerAttachButton.svelte +55 -0
- package/dist/components/composer/ComposerAttachButton.svelte.d.ts +14 -0
- package/dist/components/composer/ComposerAttachment.svelte +173 -0
- package/dist/components/composer/ComposerAttachment.svelte.d.ts +13 -0
- package/dist/components/composer/ComposerAttachments.svelte +63 -0
- package/dist/components/composer/ComposerAttachments.svelte.d.ts +13 -0
- package/dist/components/composer/ComposerModelPicker.svelte +475 -0
- package/dist/components/composer/ComposerModelPicker.svelte.d.ts +27 -0
- package/dist/components/composer/ComposerRoot.svelte +315 -0
- package/dist/components/composer/ComposerRoot.svelte.d.ts +42 -0
- package/dist/components/composer/ComposerSubmit.svelte +131 -0
- package/dist/components/composer/ComposerSubmit.svelte.d.ts +13 -0
- package/dist/components/composer/ComposerTextarea.svelte +109 -0
- package/dist/components/composer/ComposerTextarea.svelte.d.ts +9 -0
- package/dist/components/composer/ComposerToggle.svelte +106 -0
- package/dist/components/composer/ComposerToggle.svelte.d.ts +14 -0
- package/dist/components/composer/ComposerToolbar.svelte +29 -0
- package/dist/components/composer/ComposerToolbar.svelte.d.ts +11 -0
- package/dist/components/composer/context.d.ts +53 -0
- package/dist/components/composer/context.js +32 -0
- package/dist/components/composer/index.d.ts +21 -0
- package/dist/components/composer/index.js +13 -0
- package/dist/components/conversation/ConversationContent.svelte +94 -0
- package/dist/components/conversation/ConversationContent.svelte.d.ts +15 -0
- package/dist/components/conversation/ConversationEmpty.svelte +89 -0
- package/dist/components/conversation/ConversationEmpty.svelte.d.ts +16 -0
- package/dist/components/conversation/ConversationRoot.svelte +237 -0
- package/dist/components/conversation/ConversationRoot.svelte.d.ts +20 -0
- package/dist/components/conversation/ConversationScrollButton.svelte +114 -0
- package/dist/components/conversation/ConversationScrollButton.svelte.d.ts +14 -0
- package/dist/components/conversation/context.d.ts +28 -0
- package/dist/components/conversation/context.js +15 -0
- package/dist/components/conversation/index.d.ts +11 -0
- package/dist/components/conversation/index.js +8 -0
- package/dist/components/conversation/scrolling.d.ts +25 -0
- package/dist/components/conversation/scrolling.js +60 -0
- package/dist/components/dev-styles/DevStyles.svelte +21 -0
- package/dist/components/dev-styles/DevStyles.svelte.d.ts +3 -0
- package/dist/components/dialog/ConfirmDialog.svelte +157 -0
- package/dist/components/dialog/ConfirmDialog.svelte.d.ts +31 -0
- package/dist/components/dialog/DialogBody.svelte +19 -0
- package/dist/components/dialog/DialogBody.svelte.d.ts +10 -0
- package/dist/components/dialog/DialogContent.svelte +55 -0
- package/dist/components/dialog/DialogContent.svelte.d.ts +17 -0
- package/dist/components/dialog/DialogDescription.svelte +23 -0
- package/dist/components/dialog/DialogDescription.svelte.d.ts +10 -0
- package/dist/components/dialog/DialogFooter.svelte +19 -0
- package/dist/components/dialog/DialogFooter.svelte.d.ts +10 -0
- package/dist/components/dialog/DialogHeader.svelte +19 -0
- package/dist/components/dialog/DialogHeader.svelte.d.ts +10 -0
- package/dist/components/dialog/DialogTitle.svelte +20 -0
- package/dist/components/dialog/DialogTitle.svelte.d.ts +10 -0
- package/dist/components/dialog/index.d.ts +17 -0
- package/dist/components/dialog/index.js +12 -0
- package/dist/components/dialog/styles.d.ts +120 -0
- package/dist/components/dialog/styles.js +136 -0
- package/dist/components/divider/Divider.svelte +42 -0
- package/dist/components/divider/Divider.svelte.d.ts +11 -0
- package/dist/components/empty-state/EmptyState.svelte +82 -0
- package/dist/components/empty-state/EmptyState.svelte.d.ts +15 -0
- package/dist/components/field/Field.svelte +132 -0
- package/dist/components/field/Field.svelte.d.ts +23 -0
- package/dist/components/field/context.d.ts +12 -0
- package/dist/components/field/context.js +9 -0
- package/dist/components/frame/Frame.svelte +88 -0
- package/dist/components/frame/Frame.svelte.d.ts +19 -0
- package/dist/components/heading/Heading.svelte +82 -0
- package/dist/components/heading/Heading.svelte.d.ts +16 -0
- package/dist/components/input/Input.svelte +47 -0
- package/dist/components/input/Input.svelte.d.ts +14 -0
- package/dist/components/kbd/Kbd.svelte +40 -0
- package/dist/components/kbd/Kbd.svelte.d.ts +11 -0
- package/dist/components/link/Link.svelte +68 -0
- package/dist/components/link/Link.svelte.d.ts +16 -0
- package/dist/components/menu/ContextMenuContent.svelte +33 -0
- package/dist/components/menu/ContextMenuContent.svelte.d.ts +11 -0
- package/dist/components/menu/ContextMenuTrigger.svelte +44 -0
- package/dist/components/menu/ContextMenuTrigger.svelte.d.ts +11 -0
- package/dist/components/menu/MenuCheckboxItem.svelte +43 -0
- package/dist/components/menu/MenuCheckboxItem.svelte.d.ts +11 -0
- package/dist/components/menu/MenuContent.svelte +36 -0
- package/dist/components/menu/MenuContent.svelte.d.ts +10 -0
- package/dist/components/menu/MenuItem.svelte +41 -0
- package/dist/components/menu/MenuItem.svelte.d.ts +16 -0
- package/dist/components/menu/MenuLabel.svelte +24 -0
- package/dist/components/menu/MenuLabel.svelte.d.ts +10 -0
- package/dist/components/menu/MenuRadioItem.svelte +54 -0
- package/dist/components/menu/MenuRadioItem.svelte.d.ts +12 -0
- package/dist/components/menu/MenuSeparator.svelte +19 -0
- package/dist/components/menu/MenuSeparator.svelte.d.ts +8 -0
- package/dist/components/menu/MenuSubContent.svelte +34 -0
- package/dist/components/menu/MenuSubContent.svelte.d.ts +10 -0
- package/dist/components/menu/MenuSubTrigger.svelte +35 -0
- package/dist/components/menu/MenuSubTrigger.svelte.d.ts +11 -0
- package/dist/components/menu/context-menu.d.ts +11 -0
- package/dist/components/menu/context-menu.js +10 -0
- package/dist/components/menu/index.js +16 -0
- package/dist/components/message/MessageAction.svelte +62 -0
- package/dist/components/message/MessageAction.svelte.d.ts +16 -0
- package/dist/components/message/MessageActions.svelte +77 -0
- package/dist/components/message/MessageActions.svelte.d.ts +19 -0
- package/dist/components/message/MessageContent.svelte +68 -0
- package/dist/components/message/MessageContent.svelte.d.ts +12 -0
- package/dist/components/message/MessageCopy.svelte +106 -0
- package/dist/components/message/MessageCopy.svelte.d.ts +16 -0
- package/dist/components/message/MessageHeader.svelte +74 -0
- package/dist/components/message/MessageHeader.svelte.d.ts +15 -0
- package/dist/components/message/MessagePending.svelte +44 -0
- package/dist/components/message/MessagePending.svelte.d.ts +10 -0
- package/dist/components/message/MessageRoot.svelte +79 -0
- package/dist/components/message/MessageRoot.svelte.d.ts +20 -0
- package/dist/components/message/MessageTime.svelte +58 -0
- package/dist/components/message/MessageTime.svelte.d.ts +12 -0
- package/dist/components/message/context.d.ts +7 -0
- package/dist/components/message/context.js +11 -0
- package/dist/components/message/index.d.ts +18 -0
- package/dist/components/message/index.js +11 -0
- package/dist/components/notice/Notice.svelte +111 -0
- package/dist/components/notice/Notice.svelte.d.ts +26 -0
- package/dist/components/plan/Plan.svelte +198 -0
- package/dist/components/plan/Plan.svelte.d.ts +18 -0
- package/dist/components/popover/PopoverContent.svelte +42 -0
- package/dist/components/popover/PopoverContent.svelte.d.ts +10 -0
- package/dist/components/popover/index.d.ts +7 -0
- package/dist/components/popover/index.js +7 -0
- package/dist/components/progress/Progress.svelte +113 -0
- package/dist/components/progress/Progress.svelte.d.ts +11 -0
- package/dist/components/questionnaire/Questionnaire.svelte +315 -0
- package/dist/components/questionnaire/Questionnaire.svelte.d.ts +33 -0
- package/dist/components/queued-message/QueuedMessage.svelte +133 -0
- package/dist/components/queued-message/QueuedMessage.svelte.d.ts +23 -0
- package/dist/components/rail/RailItem.svelte +170 -0
- package/dist/components/rail/RailItem.svelte.d.ts +21 -0
- package/dist/components/rail/RailRoot.svelte +46 -0
- package/dist/components/rail/RailRoot.svelte.d.ts +13 -0
- package/dist/components/rail/RailSpacer.svelte +2 -0
- package/dist/components/rail/RailSpacer.svelte.d.ts +26 -0
- package/dist/components/rail/index.d.ts +6 -0
- package/dist/components/rail/index.js +4 -0
- package/dist/components/reasoning/ReasoningContent.svelte +56 -0
- package/dist/components/reasoning/ReasoningContent.svelte.d.ts +12 -0
- package/dist/components/reasoning/ReasoningRoot.svelte +102 -0
- package/dist/components/reasoning/ReasoningRoot.svelte.d.ts +25 -0
- package/dist/components/reasoning/ReasoningTrigger.svelte +106 -0
- package/dist/components/reasoning/ReasoningTrigger.svelte.d.ts +18 -0
- package/dist/components/reasoning/context.d.ts +8 -0
- package/dist/components/reasoning/context.js +11 -0
- package/dist/components/reasoning/index.d.ts +7 -0
- package/dist/components/reasoning/index.js +6 -0
- package/dist/components/resize-handle/ResizeHandle.svelte +177 -0
- package/dist/components/resize-handle/ResizeHandle.svelte.d.ts +29 -0
- package/dist/components/rolling-text/RollingText.svelte +51 -0
- package/dist/components/rolling-text/RollingText.svelte.d.ts +11 -0
- package/dist/components/segmented/SegmentedControl.svelte +171 -0
- package/dist/components/segmented/SegmentedControl.svelte.d.ts +20 -0
- package/dist/components/select/Select.svelte +146 -0
- package/dist/components/select/Select.svelte.d.ts +31 -0
- package/dist/components/separator/Separator.svelte +48 -0
- package/dist/components/separator/Separator.svelte.d.ts +11 -0
- package/dist/components/settings-row/SettingsRow.svelte +97 -0
- package/dist/components/settings-row/SettingsRow.svelte.d.ts +21 -0
- package/dist/components/shimmer/Shimmer.svelte +66 -0
- package/dist/components/shimmer/Shimmer.svelte.d.ts +12 -0
- package/dist/components/sidebar/SidebarContent.svelte +70 -0
- package/dist/components/sidebar/SidebarContent.svelte.d.ts +13 -0
- package/dist/components/sidebar/SidebarFooter.svelte +33 -0
- package/dist/components/sidebar/SidebarFooter.svelte.d.ts +10 -0
- package/dist/components/sidebar/SidebarGroup.svelte +123 -0
- package/dist/components/sidebar/SidebarGroup.svelte.d.ts +19 -0
- package/dist/components/sidebar/SidebarHeader.svelte +39 -0
- package/dist/components/sidebar/SidebarHeader.svelte.d.ts +12 -0
- package/dist/components/sidebar/SidebarItem.svelte +236 -0
- package/dist/components/sidebar/SidebarItem.svelte.d.ts +27 -0
- package/dist/components/sidebar/index.d.ts +11 -0
- package/dist/components/sidebar/index.js +6 -0
- package/dist/components/skeleton/Skeleton.svelte +61 -0
- package/dist/components/skeleton/Skeleton.svelte.d.ts +13 -0
- package/dist/components/spinner/Spinner.svelte +57 -0
- package/dist/components/spinner/Spinner.svelte.d.ts +12 -0
- package/dist/components/stack/Stack.svelte +99 -0
- package/dist/components/stack/Stack.svelte.d.ts +19 -0
- package/dist/components/status-indicator/StatusIndicator.svelte +97 -0
- package/dist/components/status-indicator/StatusIndicator.svelte.d.ts +14 -0
- package/dist/components/suggestions/SuggestionsItem.svelte +104 -0
- package/dist/components/suggestions/SuggestionsItem.svelte.d.ts +14 -0
- package/dist/components/suggestions/SuggestionsRoot.svelte +80 -0
- package/dist/components/suggestions/SuggestionsRoot.svelte.d.ts +20 -0
- package/dist/components/suggestions/context.d.ts +7 -0
- package/dist/components/suggestions/context.js +8 -0
- package/dist/components/suggestions/index.d.ts +5 -0
- package/dist/components/suggestions/index.js +5 -0
- package/dist/components/switch/Switch.svelte +175 -0
- package/dist/components/switch/Switch.svelte.d.ts +17 -0
- package/dist/components/tabs/Tabs.svelte +170 -0
- package/dist/components/tabs/Tabs.svelte.d.ts +22 -0
- package/dist/components/text/Text.svelte +88 -0
- package/dist/components/text/Text.svelte.d.ts +20 -0
- package/dist/components/textarea/Textarea.svelte +64 -0
- package/dist/components/textarea/Textarea.svelte.d.ts +13 -0
- package/dist/components/theme/Theme.svelte +49 -0
- package/dist/components/theme/Theme.svelte.d.ts +25 -0
- package/dist/components/toast/Toaster.svelte +223 -0
- package/dist/components/toast/Toaster.svelte.d.ts +9 -0
- package/dist/components/toast/toasts.svelte.d.ts +55 -0
- package/dist/components/toast/toasts.svelte.js +66 -0
- package/dist/components/tool/ToolContent.svelte +51 -0
- package/dist/components/tool/ToolContent.svelte.d.ts +11 -0
- package/dist/components/tool/ToolInput.svelte +21 -0
- package/dist/components/tool/ToolInput.svelte.d.ts +14 -0
- package/dist/components/tool/ToolOutput.svelte +33 -0
- package/dist/components/tool/ToolOutput.svelte.d.ts +16 -0
- package/dist/components/tool/ToolRoot.svelte +94 -0
- package/dist/components/tool/ToolRoot.svelte.d.ts +23 -0
- package/dist/components/tool/ToolSection.svelte +102 -0
- package/dist/components/tool/ToolSection.svelte.d.ts +16 -0
- package/dist/components/tool/ToolTrigger.svelte +191 -0
- package/dist/components/tool/ToolTrigger.svelte.d.ts +14 -0
- package/dist/components/tool/context.d.ts +18 -0
- package/dist/components/tool/context.js +11 -0
- package/dist/components/tool/index.d.ts +12 -0
- package/dist/components/tool/index.js +8 -0
- package/dist/components/tooltip/TooltipContent.svelte +76 -0
- package/dist/components/tooltip/TooltipContent.svelte.d.ts +12 -0
- package/dist/components/tooltip/TooltipRoot.svelte +37 -0
- package/dist/components/tooltip/TooltipRoot.svelte.d.ts +10 -0
- package/dist/components/tooltip/TooltipTrigger.svelte +25 -0
- package/dist/components/tooltip/TooltipTrigger.svelte.d.ts +5 -0
- package/dist/components/tooltip/index.d.ts +13 -0
- package/dist/components/tooltip/index.js +10 -0
- package/dist/components/tooltip/intent.d.ts +2 -0
- package/dist/components/tooltip/intent.js +29 -0
- package/dist/components/work-fold/WorkFold.svelte +100 -0
- package/dist/components/work-fold/WorkFold.svelte.d.ts +15 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +93 -0
- package/dist/internal/Chevron.svelte +28 -0
- package/dist/internal/Chevron.svelte.d.ts +7 -0
- package/dist/internal/Code.svelte +70 -0
- package/dist/internal/Code.svelte.d.ts +10 -0
- package/dist/internal/Collapse.svelte +57 -0
- package/dist/internal/Collapse.svelte.d.ts +12 -0
- package/dist/internal/Glyph.svelte +89 -0
- package/dist/internal/Glyph.svelte.d.ts +37 -0
- package/dist/internal/Tile.svelte +86 -0
- package/dist/internal/Tile.svelte.d.ts +15 -0
- package/dist/internal/control.d.ts +58 -0
- package/dist/internal/control.js +74 -0
- package/dist/internal/disclosure.d.ts +7 -0
- package/dist/internal/disclosure.js +1 -0
- package/dist/internal/json.d.ts +13 -0
- package/dist/internal/json.js +51 -0
- package/dist/internal/kinds.d.ts +9 -0
- package/dist/internal/kinds.js +25 -0
- package/dist/internal/layer.d.ts +103 -0
- package/dist/internal/layer.js +129 -0
- package/dist/internal/loop.d.ts +35 -0
- package/dist/internal/loop.js +72 -0
- package/dist/internal/markers.stylex.d.ts +14 -0
- package/dist/internal/markers.stylex.js +10 -0
- package/dist/internal/roving.d.ts +17 -0
- package/dist/internal/roving.js +68 -0
- package/dist/internal/shared.d.ts +36 -0
- package/dist/internal/shared.js +44 -0
- package/dist/internal/sx.d.ts +14 -0
- package/dist/internal/sx.js +11 -0
- package/dist/preset.d.ts +102 -0
- package/dist/preset.js +334 -0
- package/dist/tokens.stylex.d.ts +104 -0
- package/dist/tokens.stylex.js +125 -0
- package/dist/vite.d.ts +16 -0
- package/dist/vite.js +58 -0
- package/package.json +123 -5
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import * as stylex from '@stylexjs/stylex';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
5
|
+
import { attrs, type Sx } from '../../internal/sx.js';
|
|
6
|
+
import { colors, font } from '../../tokens.stylex.js';
|
|
7
|
+
import { setConversationContext } from './context.js';
|
|
8
|
+
|
|
9
|
+
export type ConversationRootProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
10
|
+
/**
|
|
11
|
+
* How near the bottom, in pixels, still counts as reading the latest. Within
|
|
12
|
+
* it, the conversation picks up following again and the scroll button hides.
|
|
13
|
+
*/
|
|
14
|
+
threshold?: number;
|
|
15
|
+
sx?: Sx;
|
|
16
|
+
ref?: HTMLDivElement | null;
|
|
17
|
+
/** A Conversation.Content and a Conversation.ScrollButton. */
|
|
18
|
+
children?: Snippet;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const styles = stylex.create({
|
|
22
|
+
root: {
|
|
23
|
+
position: 'relative',
|
|
24
|
+
display: 'flex',
|
|
25
|
+
flexDirection: 'column',
|
|
26
|
+
minHeight: 0,
|
|
27
|
+
color: colors.text,
|
|
28
|
+
fontFamily: font.body
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<script lang="ts">
|
|
34
|
+
import {
|
|
35
|
+
FOLLOW_BAND,
|
|
36
|
+
INTENT_WINDOW,
|
|
37
|
+
SELF_SCROLL_WINDOW,
|
|
38
|
+
isUpKey,
|
|
39
|
+
onScrollbar,
|
|
40
|
+
scrollsInside
|
|
41
|
+
} from './scrolling.js';
|
|
42
|
+
import type { ConversationMode } from './context.js';
|
|
43
|
+
|
|
44
|
+
let {
|
|
45
|
+
threshold = FOLLOW_BAND,
|
|
46
|
+
sx,
|
|
47
|
+
class: className,
|
|
48
|
+
ref = $bindable(null),
|
|
49
|
+
children,
|
|
50
|
+
...rest
|
|
51
|
+
}: ConversationRootProps = $props();
|
|
52
|
+
|
|
53
|
+
// How far the message before an anchored one peeks above it
|
|
54
|
+
const PEEK = 64;
|
|
55
|
+
|
|
56
|
+
let atBottom = $state(true);
|
|
57
|
+
let mode = $state<ConversationMode>('following');
|
|
58
|
+
let spacer = $state(0);
|
|
59
|
+
let viewport: HTMLElement | null = null;
|
|
60
|
+
let anchored: HTMLElement | null = null;
|
|
61
|
+
let lastTop = 0;
|
|
62
|
+
// When the person last made a gesture that scrolls up, and when the
|
|
63
|
+
// conversation last moved itself
|
|
64
|
+
let intentAt = -Infinity;
|
|
65
|
+
let selfAt = -Infinity;
|
|
66
|
+
|
|
67
|
+
const reduceMotion = () => matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
68
|
+
const distance = (el: HTMLElement) => el.scrollHeight - el.clientHeight - el.scrollTop;
|
|
69
|
+
|
|
70
|
+
function moveTo(top: number, behavior: ScrollBehavior = 'instant') {
|
|
71
|
+
if (!viewport) return;
|
|
72
|
+
selfAt = performance.now();
|
|
73
|
+
viewport.scrollTo({ top, behavior });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Scrolls to the newest content and follows it again as it grows.
|
|
78
|
+
* Smooth by default, unless the person has asked for less motion.
|
|
79
|
+
*/
|
|
80
|
+
export function scrollToBottom(behavior?: ScrollBehavior) {
|
|
81
|
+
if (!viewport) return;
|
|
82
|
+
anchored = null;
|
|
83
|
+
spacer = 0;
|
|
84
|
+
mode = 'following';
|
|
85
|
+
moveTo(viewport.scrollHeight, behavior ?? (reduceMotion() ? 'instant' : 'smooth'));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Where an anchored message sits in the scroller's own coordinates
|
|
89
|
+
function topOf(message: HTMLElement) {
|
|
90
|
+
if (!viewport) return 0;
|
|
91
|
+
return (
|
|
92
|
+
message.getBoundingClientRect().top -
|
|
93
|
+
viewport.getBoundingClientRect().top +
|
|
94
|
+
viewport.scrollTop
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The room needed beneath the content so the anchored message can reach the top
|
|
99
|
+
function settleSpacer() {
|
|
100
|
+
if (!viewport || !anchored) return;
|
|
101
|
+
if (!anchored.isConnected) {
|
|
102
|
+
anchored = null;
|
|
103
|
+
spacer = 0;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const target = Math.max(0, topOf(anchored) - PEEK);
|
|
107
|
+
const natural = viewport.scrollHeight - spacer;
|
|
108
|
+
const needed = Math.max(0, Math.ceil(target + viewport.clientHeight - natural));
|
|
109
|
+
if (needed === 0) {
|
|
110
|
+
// The reply has outgrown the room: follow it from here
|
|
111
|
+
anchored = null;
|
|
112
|
+
spacer = 0;
|
|
113
|
+
if (mode === 'anchored') mode = 'following';
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
spacer = needed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Lifts a message to the top of the view, with the one before it peeking
|
|
121
|
+
* above, and leaves room beneath for the reply.
|
|
122
|
+
*/
|
|
123
|
+
export function anchor(message: HTMLElement, behavior?: ScrollBehavior) {
|
|
124
|
+
if (!viewport) return;
|
|
125
|
+
anchored = message;
|
|
126
|
+
mode = 'anchored';
|
|
127
|
+
settleSpacer();
|
|
128
|
+
// The spacer is laid out before the scroll, so there is room to reach the target
|
|
129
|
+
requestAnimationFrame(() => {
|
|
130
|
+
if (!anchored) return;
|
|
131
|
+
moveTo(
|
|
132
|
+
Math.max(0, topOf(anchored) - PEEK),
|
|
133
|
+
behavior ?? (reduceMotion() ? 'instant' : 'smooth')
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function attach(el: HTMLElement, content: HTMLElement) {
|
|
139
|
+
viewport = el;
|
|
140
|
+
// Open at the latest message
|
|
141
|
+
moveTo(el.scrollHeight);
|
|
142
|
+
lastTop = el.scrollTop;
|
|
143
|
+
|
|
144
|
+
const intend = () => (intentAt = performance.now());
|
|
145
|
+
|
|
146
|
+
const onwheel = (event: WheelEvent) => {
|
|
147
|
+
if (event.deltaY < 0 && !scrollsInside(event.target, el, event.deltaY)) intend();
|
|
148
|
+
};
|
|
149
|
+
const onkeydown = (event: KeyboardEvent) => {
|
|
150
|
+
if (isUpKey(event)) intend();
|
|
151
|
+
};
|
|
152
|
+
const onpointerdown = (event: PointerEvent) => {
|
|
153
|
+
if (onScrollbar(event, el)) intend();
|
|
154
|
+
};
|
|
155
|
+
let touchY = 0;
|
|
156
|
+
const ontouchstart = (event: TouchEvent) => (touchY = event.touches[0]?.clientY ?? 0);
|
|
157
|
+
const ontouchmove = (event: TouchEvent) => {
|
|
158
|
+
const y = event.touches[0]?.clientY ?? 0;
|
|
159
|
+
// A finger moving down scrolls the content up
|
|
160
|
+
if (y > touchY && !scrollsInside(event.target, el, -1)) intend();
|
|
161
|
+
touchY = y;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const onscroll = () => {
|
|
165
|
+
const now = performance.now();
|
|
166
|
+
const top = el.scrollTop;
|
|
167
|
+
const away = distance(el);
|
|
168
|
+
const theirs = now - intentAt < INTENT_WINDOW && now - selfAt > SELF_SCROLL_WINDOW;
|
|
169
|
+
if (top < lastTop - 0.5 && theirs) {
|
|
170
|
+
// The person went up: let go, and stop holding a message at the top
|
|
171
|
+
mode = 'free';
|
|
172
|
+
} else if (mode === 'free' && away <= threshold) {
|
|
173
|
+
mode = 'following';
|
|
174
|
+
anchored = null;
|
|
175
|
+
spacer = 0;
|
|
176
|
+
}
|
|
177
|
+
atBottom = away <= threshold;
|
|
178
|
+
lastTop = top;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// New content, a streamed word or a resized window. The observer reports
|
|
182
|
+
// after layout and before paint, so correcting here never shows a frame
|
|
183
|
+
// at the old position.
|
|
184
|
+
const observer = new ResizeObserver(() => {
|
|
185
|
+
settleSpacer();
|
|
186
|
+
if (mode === 'following') moveTo(el.scrollHeight);
|
|
187
|
+
atBottom = mode === 'following' || distance(el) <= threshold;
|
|
188
|
+
lastTop = el.scrollTop;
|
|
189
|
+
});
|
|
190
|
+
observer.observe(content);
|
|
191
|
+
observer.observe(el);
|
|
192
|
+
|
|
193
|
+
el.addEventListener('scroll', onscroll, { passive: true });
|
|
194
|
+
el.addEventListener('wheel', onwheel, { passive: true });
|
|
195
|
+
el.addEventListener('keydown', onkeydown);
|
|
196
|
+
el.addEventListener('pointerdown', onpointerdown);
|
|
197
|
+
el.addEventListener('touchstart', ontouchstart, { passive: true });
|
|
198
|
+
el.addEventListener('touchmove', ontouchmove, { passive: true });
|
|
199
|
+
|
|
200
|
+
return () => {
|
|
201
|
+
observer.disconnect();
|
|
202
|
+
el.removeEventListener('scroll', onscroll);
|
|
203
|
+
el.removeEventListener('wheel', onwheel);
|
|
204
|
+
el.removeEventListener('keydown', onkeydown);
|
|
205
|
+
el.removeEventListener('pointerdown', onpointerdown);
|
|
206
|
+
el.removeEventListener('touchstart', ontouchstart);
|
|
207
|
+
el.removeEventListener('touchmove', ontouchmove);
|
|
208
|
+
if (viewport === el) viewport = null;
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
setConversationContext({
|
|
213
|
+
get atBottom() {
|
|
214
|
+
return atBottom;
|
|
215
|
+
},
|
|
216
|
+
get mode() {
|
|
217
|
+
return mode;
|
|
218
|
+
},
|
|
219
|
+
get spacer() {
|
|
220
|
+
return spacer;
|
|
221
|
+
},
|
|
222
|
+
scrollToBottom,
|
|
223
|
+
anchor,
|
|
224
|
+
attach
|
|
225
|
+
});
|
|
226
|
+
</script>
|
|
227
|
+
|
|
228
|
+
<div
|
|
229
|
+
bind:this={ref}
|
|
230
|
+
data-slot="conversation"
|
|
231
|
+
data-at-bottom={atBottom || undefined}
|
|
232
|
+
data-mode={mode}
|
|
233
|
+
{...rest}
|
|
234
|
+
{...attrs([styles.root, sx], className)}
|
|
235
|
+
>
|
|
236
|
+
{@render children?.()}
|
|
237
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Sx } from '../../internal/sx.js';
|
|
4
|
+
export type ConversationRootProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
5
|
+
/**
|
|
6
|
+
* How near the bottom, in pixels, still counts as reading the latest. Within
|
|
7
|
+
* it, the conversation picks up following again and the scroll button hides.
|
|
8
|
+
*/
|
|
9
|
+
threshold?: number;
|
|
10
|
+
sx?: Sx;
|
|
11
|
+
ref?: HTMLDivElement | null;
|
|
12
|
+
/** A Conversation.Content and a Conversation.ScrollButton. */
|
|
13
|
+
children?: Snippet;
|
|
14
|
+
};
|
|
15
|
+
declare const ConversationRoot: import("svelte").Component<ConversationRootProps, {
|
|
16
|
+
scrollToBottom: (behavior?: ScrollBehavior) => void;
|
|
17
|
+
anchor: (message: HTMLElement, behavior?: ScrollBehavior) => void;
|
|
18
|
+
}, "ref">;
|
|
19
|
+
type ConversationRoot = ReturnType<typeof ConversationRoot>;
|
|
20
|
+
export default ConversationRoot;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import * as stylex from '@stylexjs/stylex';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
5
|
+
import Glyph from '../../internal/Glyph.svelte';
|
|
6
|
+
import { shared } from '../../internal/shared.js';
|
|
7
|
+
import { attrs, type Sx } from '../../internal/sx.js';
|
|
8
|
+
import { colors, font, motion, radius } from '../../tokens.stylex.js';
|
|
9
|
+
import { useConversation } from './context.js';
|
|
10
|
+
|
|
11
|
+
export type ConversationScrollButtonProps = Omit<HTMLButtonAttributes, 'children'> & {
|
|
12
|
+
/** Accessible name. Also shown beside the arrow when there are `children`. */
|
|
13
|
+
label?: string;
|
|
14
|
+
sx?: Sx;
|
|
15
|
+
ref?: HTMLButtonElement | null;
|
|
16
|
+
/** Text beside the arrow, such as "New messages". Leave it out for a round arrow button. */
|
|
17
|
+
children?: Snippet;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const HOVER = '@media (hover: hover) and (pointer: fine)';
|
|
21
|
+
const MOTION = '@media (prefers-reduced-motion: no-preference)';
|
|
22
|
+
|
|
23
|
+
const styles = stylex.create({
|
|
24
|
+
button: {
|
|
25
|
+
position: 'absolute',
|
|
26
|
+
insetInline: 0,
|
|
27
|
+
bottom: '1rem',
|
|
28
|
+
zIndex: 1,
|
|
29
|
+
display: 'inline-flex',
|
|
30
|
+
alignItems: 'center',
|
|
31
|
+
justifyContent: 'center',
|
|
32
|
+
gap: '0.375rem',
|
|
33
|
+
width: 'fit-content',
|
|
34
|
+
height: '2rem',
|
|
35
|
+
minWidth: '2rem',
|
|
36
|
+
marginInline: 'auto',
|
|
37
|
+
paddingInline: '0.5rem',
|
|
38
|
+
borderWidth: 0,
|
|
39
|
+
borderRadius: radius.full,
|
|
40
|
+
backgroundColor: colors.bgRaised,
|
|
41
|
+
color: {
|
|
42
|
+
default: colors.textMuted,
|
|
43
|
+
':hover': { default: null, [HOVER]: colors.text }
|
|
44
|
+
},
|
|
45
|
+
boxShadow: `inset 0 0 0 1px ${colors.lineStrong}, inset 0 1px 0 ${colors.highlight}, 0 1px 2px ${colors.shade}, 0 8px 20px -8px ${colors.shade}`,
|
|
46
|
+
fontFamily: font.body,
|
|
47
|
+
fontSize: font.sizeSm,
|
|
48
|
+
fontWeight: font.weightMedium,
|
|
49
|
+
lineHeight: 1,
|
|
50
|
+
whiteSpace: 'nowrap',
|
|
51
|
+
cursor: 'pointer',
|
|
52
|
+
WebkitTapHighlightColor: 'transparent',
|
|
53
|
+
// It rises into place, and drops away a little faster than it came
|
|
54
|
+
opacity: 1,
|
|
55
|
+
translate: '0 0',
|
|
56
|
+
scale: { default: null, ':active': { default: null, [MOTION]: 0.96 } },
|
|
57
|
+
transitionProperty: {
|
|
58
|
+
default: 'opacity, color',
|
|
59
|
+
[MOTION]: 'opacity, translate, scale, color'
|
|
60
|
+
},
|
|
61
|
+
transitionDuration: motion.slow,
|
|
62
|
+
transitionTimingFunction: motion.easeOut
|
|
63
|
+
},
|
|
64
|
+
withText: {
|
|
65
|
+
paddingInlineStart: '0.625rem',
|
|
66
|
+
paddingInlineEnd: '0.75rem'
|
|
67
|
+
},
|
|
68
|
+
hidden: {
|
|
69
|
+
opacity: 0,
|
|
70
|
+
translate: { default: null, [MOTION]: '0 8px' },
|
|
71
|
+
scale: { default: null, [MOTION]: 0.94 },
|
|
72
|
+
pointerEvents: 'none',
|
|
73
|
+
transitionDuration: motion.base
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<script lang="ts">
|
|
79
|
+
let {
|
|
80
|
+
label = 'Scroll to the latest message',
|
|
81
|
+
sx,
|
|
82
|
+
class: className,
|
|
83
|
+
ref = $bindable(null),
|
|
84
|
+
children,
|
|
85
|
+
onclick,
|
|
86
|
+
...rest
|
|
87
|
+
}: ConversationScrollButtonProps = $props();
|
|
88
|
+
|
|
89
|
+
const conversation = useConversation('ScrollButton');
|
|
90
|
+
const hidden = $derived(conversation.atBottom);
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<!-- Hidden while the latest message is in view. It stays in the page so it can
|
|
94
|
+
fade, but is inert so it cannot be reached. -->
|
|
95
|
+
<button
|
|
96
|
+
bind:this={ref}
|
|
97
|
+
type="button"
|
|
98
|
+
aria-label={children ? undefined : label}
|
|
99
|
+
inert={hidden}
|
|
100
|
+
data-slot="conversation-scroll-button"
|
|
101
|
+
data-hidden={hidden || undefined}
|
|
102
|
+
{...rest}
|
|
103
|
+
{...attrs(
|
|
104
|
+
[styles.button, shared.focusable, children && styles.withText, hidden && styles.hidden, sx],
|
|
105
|
+
className
|
|
106
|
+
)}
|
|
107
|
+
onclick={(event) => {
|
|
108
|
+
onclick?.(event);
|
|
109
|
+
if (!event.defaultPrevented) conversation.scrollToBottom();
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<Glyph name="arrowDown" size={16} stroke={1.75} />
|
|
113
|
+
{@render children?.()}
|
|
114
|
+
</button>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Sx } from '../../internal/sx.js';
|
|
4
|
+
export type ConversationScrollButtonProps = Omit<HTMLButtonAttributes, 'children'> & {
|
|
5
|
+
/** Accessible name. Also shown beside the arrow when there are `children`. */
|
|
6
|
+
label?: string;
|
|
7
|
+
sx?: Sx;
|
|
8
|
+
ref?: HTMLButtonElement | null;
|
|
9
|
+
/** Text beside the arrow, such as "New messages". Leave it out for a round arrow button. */
|
|
10
|
+
children?: Snippet;
|
|
11
|
+
};
|
|
12
|
+
declare const ConversationScrollButton: import("svelte").Component<ConversationScrollButtonProps, {}, "ref">;
|
|
13
|
+
type ConversationScrollButton = ReturnType<typeof ConversationScrollButton>;
|
|
14
|
+
export default ConversationScrollButton;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the conversation moves as content arrives.
|
|
3
|
+
* - `following`: it keeps the newest content in view
|
|
4
|
+
* - `free`: the person scrolled away; nothing moves under them
|
|
5
|
+
* - `anchored`: a message was lifted to the top, and the reply grows beneath it
|
|
6
|
+
*/
|
|
7
|
+
export type ConversationMode = 'following' | 'free' | 'anchored';
|
|
8
|
+
export type ConversationContext = {
|
|
9
|
+
/** True while the newest content is in view. */
|
|
10
|
+
readonly atBottom: boolean;
|
|
11
|
+
readonly mode: ConversationMode;
|
|
12
|
+
/** Room left below the content while a message is anchored, in pixels. */
|
|
13
|
+
readonly spacer: number;
|
|
14
|
+
/** Scrolls to the newest content and follows it again as it grows. */
|
|
15
|
+
scrollToBottom(behavior?: ScrollBehavior): void;
|
|
16
|
+
/**
|
|
17
|
+
* Lifts a message to the top of the view, with the one before it peeking
|
|
18
|
+
* above, and leaves room beneath for the reply. Call it with the message the
|
|
19
|
+
* person just sent. Once the reply outgrows the room, the view follows it.
|
|
20
|
+
*/
|
|
21
|
+
anchor(message: HTMLElement, behavior?: ScrollBehavior): void;
|
|
22
|
+
/** @internal Conversation.Content hands over its scroller and its content. */
|
|
23
|
+
attach(viewport: HTMLElement, content: HTMLElement): () => void;
|
|
24
|
+
};
|
|
25
|
+
export declare function setConversationContext(context: ConversationContext): void;
|
|
26
|
+
/** The surrounding Conversation, for building parts of your own. */
|
|
27
|
+
export declare function getConversationContext(): ConversationContext | undefined;
|
|
28
|
+
export declare function useConversation(part: string): ConversationContext;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const KEY = Symbol('narvi.conversation');
|
|
3
|
+
export function setConversationContext(context) {
|
|
4
|
+
setContext(KEY, context);
|
|
5
|
+
}
|
|
6
|
+
/** The surrounding Conversation, for building parts of your own. */
|
|
7
|
+
export function getConversationContext() {
|
|
8
|
+
return getContext(KEY);
|
|
9
|
+
}
|
|
10
|
+
export function useConversation(part) {
|
|
11
|
+
const context = getConversationContext();
|
|
12
|
+
if (!context)
|
|
13
|
+
throw new Error(`<Conversation.${part}> must be placed inside <Conversation.Root>.`);
|
|
14
|
+
return context;
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Content from './ConversationContent.svelte';
|
|
2
|
+
import Empty from './ConversationEmpty.svelte';
|
|
3
|
+
import Root from './ConversationRoot.svelte';
|
|
4
|
+
import ScrollButton from './ConversationScrollButton.svelte';
|
|
5
|
+
export { Root, Content, ScrollButton, Empty, Root as ConversationRoot, Content as ConversationContent, ScrollButton as ConversationScrollButton, Empty as ConversationEmpty };
|
|
6
|
+
export { getConversationContext } from './context.js';
|
|
7
|
+
export type { ConversationContext, ConversationMode } from './context.js';
|
|
8
|
+
export type { ConversationContentProps } from './ConversationContent.svelte';
|
|
9
|
+
export type { ConversationEmptyProps } from './ConversationEmpty.svelte';
|
|
10
|
+
export type { ConversationRootProps } from './ConversationRoot.svelte';
|
|
11
|
+
export type { ConversationScrollButtonProps } from './ConversationScrollButton.svelte';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import Content from './ConversationContent.svelte';
|
|
2
|
+
import Empty from './ConversationEmpty.svelte';
|
|
3
|
+
import Root from './ConversationRoot.svelte';
|
|
4
|
+
import ScrollButton from './ConversationScrollButton.svelte';
|
|
5
|
+
export { Root, Content, ScrollButton, Empty,
|
|
6
|
+
//
|
|
7
|
+
Root as ConversationRoot, Content as ConversationContent, ScrollButton as ConversationScrollButton, Empty as ConversationEmpty };
|
|
8
|
+
export { getConversationContext } from './context.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telling the person's scrolling apart from the page's own.
|
|
3
|
+
*
|
|
4
|
+
* Content arriving, a code block folding open or the window resizing all move
|
|
5
|
+
* scrollTop too, and none of them should stop the conversation following. Only
|
|
6
|
+
* a gesture that means "take me up" does: a wheel or a swipe upward, the keys
|
|
7
|
+
* that scroll up, or a drag of the scrollbar. These rules come from Eregion,
|
|
8
|
+
* where each one fixed a reported jump.
|
|
9
|
+
*/
|
|
10
|
+
/** How long a gesture counts as the person's, after they make it. */
|
|
11
|
+
export declare const INTENT_WINDOW = 1500;
|
|
12
|
+
/** How long after the conversation moves itself its own scroll events are ignored. */
|
|
13
|
+
export declare const SELF_SCROLL_WINDOW = 180;
|
|
14
|
+
/** How near the end, in pixels, counts as at the end: following picks up again there. */
|
|
15
|
+
export declare const FOLLOW_BAND = 40;
|
|
16
|
+
/**
|
|
17
|
+
* Whether a wheel or swipe in this direction will be taken by something inside
|
|
18
|
+
* the conversation that can still scroll that way: a long code block, a table,
|
|
19
|
+
* a command's output. Then the conversation itself is not being scrolled.
|
|
20
|
+
*/
|
|
21
|
+
export declare function scrollsInside(target: EventTarget | null, viewport: HTMLElement, deltaY: number): boolean;
|
|
22
|
+
/** Whether a key press asks the conversation to scroll up. */
|
|
23
|
+
export declare function isUpKey(event: KeyboardEvent): boolean;
|
|
24
|
+
/** Whether a press landed on the viewport's own scrollbar rather than its content. */
|
|
25
|
+
export declare function onScrollbar(event: PointerEvent, viewport: HTMLElement): boolean;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telling the person's scrolling apart from the page's own.
|
|
3
|
+
*
|
|
4
|
+
* Content arriving, a code block folding open or the window resizing all move
|
|
5
|
+
* scrollTop too, and none of them should stop the conversation following. Only
|
|
6
|
+
* a gesture that means "take me up" does: a wheel or a swipe upward, the keys
|
|
7
|
+
* that scroll up, or a drag of the scrollbar. These rules come from Eregion,
|
|
8
|
+
* where each one fixed a reported jump.
|
|
9
|
+
*/
|
|
10
|
+
/** Keys that scroll a page up. Shift+Space is handled on its own. */
|
|
11
|
+
const UP_KEYS = new Set(['ArrowUp', 'PageUp', 'Home']);
|
|
12
|
+
/** How long a gesture counts as the person's, after they make it. */
|
|
13
|
+
export const INTENT_WINDOW = 1500;
|
|
14
|
+
/** How long after the conversation moves itself its own scroll events are ignored. */
|
|
15
|
+
export const SELF_SCROLL_WINDOW = 180;
|
|
16
|
+
/** How near the end, in pixels, counts as at the end: following picks up again there. */
|
|
17
|
+
export const FOLLOW_BAND = 40;
|
|
18
|
+
/**
|
|
19
|
+
* Whether a wheel or swipe in this direction will be taken by something inside
|
|
20
|
+
* the conversation that can still scroll that way: a long code block, a table,
|
|
21
|
+
* a command's output. Then the conversation itself is not being scrolled.
|
|
22
|
+
*/
|
|
23
|
+
export function scrollsInside(target, viewport, deltaY) {
|
|
24
|
+
let element = target instanceof Element ? target : null;
|
|
25
|
+
while (element && element !== viewport) {
|
|
26
|
+
if (element instanceof HTMLElement && element.scrollHeight > element.clientHeight) {
|
|
27
|
+
const overflow = getComputedStyle(element).overflowY;
|
|
28
|
+
if (overflow === 'auto' || overflow === 'scroll') {
|
|
29
|
+
if (deltaY < 0 && element.scrollTop > 0)
|
|
30
|
+
return true;
|
|
31
|
+
if (deltaY > 0 && element.scrollTop + element.clientHeight < element.scrollHeight - 1)
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
element = element.parentElement;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function isEditable(target) {
|
|
40
|
+
return (target instanceof HTMLElement &&
|
|
41
|
+
(target.isContentEditable || target.matches('input, textarea, select')));
|
|
42
|
+
}
|
|
43
|
+
/** Whether a key press asks the conversation to scroll up. */
|
|
44
|
+
export function isUpKey(event) {
|
|
45
|
+
if (event.defaultPrevented || isEditable(event.target))
|
|
46
|
+
return false;
|
|
47
|
+
if (event.key === ' ')
|
|
48
|
+
return event.shiftKey;
|
|
49
|
+
return UP_KEYS.has(event.key) && !event.altKey && !event.metaKey && !event.ctrlKey;
|
|
50
|
+
}
|
|
51
|
+
/** Whether a press landed on the viewport's own scrollbar rather than its content. */
|
|
52
|
+
export function onScrollbar(event, viewport) {
|
|
53
|
+
if (event.target !== viewport)
|
|
54
|
+
return false;
|
|
55
|
+
const box = viewport.getBoundingClientRect();
|
|
56
|
+
const inlineEnd = getComputedStyle(viewport).direction === 'rtl'
|
|
57
|
+
? event.clientX - box.left
|
|
58
|
+
: box.right - event.clientX;
|
|
59
|
+
return inlineEnd <= viewport.offsetWidth - viewport.clientWidth;
|
|
60
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Loads the stylesheet the Narvi Vite plugin serves during development, and keeps
|
|
3
|
+
it fresh on hot updates. Renders nothing in production, where the styles are
|
|
4
|
+
already part of your CSS bundle. Mount it once, in your root layout.
|
|
5
|
+
-->
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import { DEV } from 'esm-env';
|
|
8
|
+
|
|
9
|
+
if (DEV) {
|
|
10
|
+
$effect(() => {
|
|
11
|
+
// @ts-expect-error virtual module provided by the Narvi Vite plugin
|
|
12
|
+
void import('virtual:stylex:runtime');
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<svelte:head>
|
|
18
|
+
{#if DEV}
|
|
19
|
+
<link rel="stylesheet" href="/virtual:stylex.css" />
|
|
20
|
+
{/if}
|
|
21
|
+
</svelte:head>
|