@mkbabb/glass-ui 0.2.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 +172 -0
- package/dist/glass-ui.css +1 -0
- package/dist/glass-ui.js +10019 -0
- package/dist/index.d.ts +6619 -0
- package/package.json +65 -0
- package/src/components/custom/aurora/Aurora.vue +34 -0
- package/src/components/custom/aurora/composables/color.ts +122 -0
- package/src/components/custom/aurora/composables/useAurora.ts +355 -0
- package/src/components/custom/aurora/index.ts +8 -0
- package/src/components/custom/confirm-dialog/ConfirmDialog.vue +88 -0
- package/src/components/custom/confirm-dialog/index.ts +1 -0
- package/src/components/custom/controls/DarkModeToggle.vue +96 -0
- package/src/components/custom/controls/index.ts +1 -0
- package/src/components/custom/dock/DockLayerGroup.vue +21 -0
- package/src/components/custom/dock/DockPopover.vue +263 -0
- package/src/components/custom/dock/GlassDock.vue +276 -0
- package/src/components/custom/dock/composables/index.ts +16 -0
- package/src/components/custom/dock/composables/isTeleportedTarget.ts +19 -0
- package/src/components/custom/dock/composables/useDockActionBar.ts +33 -0
- package/src/components/custom/dock/composables/useDockState.ts +301 -0
- package/src/components/custom/dock/composables/useDockTransition.ts +146 -0
- package/src/components/custom/dock/composables/useLayerTransition.ts +135 -0
- package/src/components/custom/dock/composables/usePopupMutex.ts +83 -0
- package/src/components/custom/dock/index.ts +9 -0
- package/src/components/custom/expandable-container/ExpandableContainer.vue +64 -0
- package/src/components/custom/expandable-container/index.ts +1 -0
- package/src/components/custom/glass-panel/GlassPanel.vue +98 -0
- package/src/components/custom/glass-panel/index.ts +2 -0
- package/src/components/custom/icon-tooltip/IconTooltip.vue +20 -0
- package/src/components/custom/icon-tooltip/index.ts +1 -0
- package/src/components/custom/index.ts +15 -0
- package/src/components/custom/infinite-scroll/InfiniteScroll.vue +55 -0
- package/src/components/custom/infinite-scroll/composables/index.ts +2 -0
- package/src/components/custom/infinite-scroll/composables/types.ts +23 -0
- package/src/components/custom/infinite-scroll/composables/useInfiniteScroll.ts +73 -0
- package/src/components/custom/infinite-scroll/index.ts +1 -0
- package/src/components/custom/labeled-field/LabeledInput.vue +29 -0
- package/src/components/custom/labeled-field/LabeledSelect.vue +59 -0
- package/src/components/custom/labeled-field/LabeledSlider.vue +32 -0
- package/src/components/custom/labeled-field/LabeledSwitch.vue +27 -0
- package/src/components/custom/labeled-field/index.ts +4 -0
- package/src/components/custom/metaballs/MetaballCanvas.vue +23 -0
- package/src/components/custom/metaballs/index.ts +4 -0
- package/src/components/custom/metaballs/shaders.ts +63 -0
- package/src/components/custom/metaballs/types.ts +29 -0
- package/src/components/custom/metaballs/useMetaballs.ts +252 -0
- package/src/components/custom/search/FuzzySearch.vue +589 -0
- package/src/components/custom/search/SearchBar.vue +44 -0
- package/src/components/custom/search/composables/fuzzySearchIndex.ts +224 -0
- package/src/components/custom/search/composables/index.ts +5 -0
- package/src/components/custom/search/composables/types.ts +34 -0
- package/src/components/custom/search/composables/useFuzzySearch.ts +115 -0
- package/src/components/custom/search/index.ts +7 -0
- package/src/components/custom/sidebar/ProgressiveSidebar.vue +256 -0
- package/src/components/custom/sidebar/composables/index.ts +6 -0
- package/src/components/custom/sidebar/composables/useScrollTracker.ts +242 -0
- package/src/components/custom/sidebar/composables/useSidebarFollow.ts +247 -0
- package/src/components/custom/sidebar/composables/useSidebarState.ts +72 -0
- package/src/components/custom/sidebar/composables/useTreeIndex.ts +152 -0
- package/src/components/custom/sidebar/index.ts +15 -0
- package/src/components/custom/sidebar/types.ts +50 -0
- package/src/components/custom/tabs/BouncyTabs.vue +39 -0
- package/src/components/custom/tabs/BouncyToggle.vue +352 -0
- package/src/components/custom/tabs/UnderlineTabs.vue +115 -0
- package/src/components/custom/tabs/index.ts +5 -0
- package/src/components/custom/timeline/GlassTimeline.vue +174 -0
- package/src/components/custom/timeline/index.ts +1 -0
- package/src/components/custom/typewriter/TypewriterText.vue +239 -0
- package/src/components/custom/typewriter/composables/index.ts +1 -0
- package/src/components/custom/typewriter/composables/useTypewriter.ts +413 -0
- package/src/components/custom/typewriter/index.ts +7 -0
- package/src/components/custom/typewriter/types.ts +159 -0
- package/src/components/custom/typewriter/utils/keyboard.ts +213 -0
- package/src/components/custom/typewriter/utils/pausePatterns.ts +55 -0
- package/src/components/custom/typewriter/utils/timing.ts +104 -0
- package/src/components/custom/typewriter/utils/typoStateMachine.ts +197 -0
- package/src/components/index.ts +2 -0
- package/src/components/ui/accordion/Accordion.vue +19 -0
- package/src/components/ui/accordion/AccordionContent.vue +24 -0
- package/src/components/ui/accordion/AccordionItem.vue +24 -0
- package/src/components/ui/accordion/AccordionTrigger.vue +39 -0
- package/src/components/ui/accordion/index.ts +4 -0
- package/src/components/ui/alert/Alert.vue +20 -0
- package/src/components/ui/alert/AlertDescription.vue +17 -0
- package/src/components/ui/alert/AlertTitle.vue +17 -0
- package/src/components/ui/alert/index.ts +23 -0
- package/src/components/ui/avatar/Avatar.vue +21 -0
- package/src/components/ui/avatar/AvatarFallback.vue +11 -0
- package/src/components/ui/avatar/AvatarImage.vue +9 -0
- package/src/components/ui/avatar/index.ts +24 -0
- package/src/components/ui/badge/Badge.vue +16 -0
- package/src/components/ui/badge/index.ts +25 -0
- package/src/components/ui/button/Button.vue +26 -0
- package/src/components/ui/button/index.ts +43 -0
- package/src/components/ui/card/Card.vue +28 -0
- package/src/components/ui/card/CardContent.vue +14 -0
- package/src/components/ui/card/CardDescription.vue +14 -0
- package/src/components/ui/card/CardFooter.vue +14 -0
- package/src/components/ui/card/CardHeader.vue +14 -0
- package/src/components/ui/card/CardTitle.vue +21 -0
- package/src/components/ui/card/index.ts +6 -0
- package/src/components/ui/carousel/Carousel.vue +53 -0
- package/src/components/ui/carousel/CarouselContent.vue +35 -0
- package/src/components/ui/carousel/CarouselItem.vue +24 -0
- package/src/components/ui/carousel/CarouselNext.vue +40 -0
- package/src/components/ui/carousel/CarouselPrevious.vue +40 -0
- package/src/components/ui/carousel/index.ts +10 -0
- package/src/components/ui/carousel/interface.ts +26 -0
- package/src/components/ui/carousel/useCarousel.ts +56 -0
- package/src/components/ui/checkbox/Checkbox.vue +33 -0
- package/src/components/ui/checkbox/index.ts +1 -0
- package/src/components/ui/collapsible/Collapsible.vue +15 -0
- package/src/components/ui/collapsible/CollapsibleContent.vue +11 -0
- package/src/components/ui/collapsible/CollapsibleTrigger.vue +11 -0
- package/src/components/ui/collapsible/index.ts +3 -0
- package/src/components/ui/combobox/Combobox.vue +17 -0
- package/src/components/ui/combobox/ComboboxAnchor.vue +23 -0
- package/src/components/ui/combobox/ComboboxEmpty.vue +21 -0
- package/src/components/ui/combobox/ComboboxGroup.vue +27 -0
- package/src/components/ui/combobox/ComboboxInput.vue +41 -0
- package/src/components/ui/combobox/ComboboxItem.vue +24 -0
- package/src/components/ui/combobox/ComboboxItemIndicator.vue +23 -0
- package/src/components/ui/combobox/ComboboxList.vue +29 -0
- package/src/components/ui/combobox/ComboboxSeparator.vue +21 -0
- package/src/components/ui/combobox/ComboboxViewport.vue +23 -0
- package/src/components/ui/combobox/index.ts +12 -0
- package/src/components/ui/command/Command.vue +30 -0
- package/src/components/ui/command/CommandDialog.vue +21 -0
- package/src/components/ui/command/CommandEmpty.vue +20 -0
- package/src/components/ui/command/CommandGroup.vue +29 -0
- package/src/components/ui/command/CommandInput.vue +33 -0
- package/src/components/ui/command/CommandItem.vue +26 -0
- package/src/components/ui/command/CommandList.vue +27 -0
- package/src/components/ui/command/CommandSeparator.vue +23 -0
- package/src/components/ui/command/CommandShortcut.vue +14 -0
- package/src/components/ui/command/index.ts +9 -0
- package/src/components/ui/context-menu/ContextMenu.vue +15 -0
- package/src/components/ui/context-menu/ContextMenuCheckboxItem.vue +40 -0
- package/src/components/ui/context-menu/ContextMenuContent.vue +36 -0
- package/src/components/ui/context-menu/ContextMenuGroup.vue +11 -0
- package/src/components/ui/context-menu/ContextMenuItem.vue +34 -0
- package/src/components/ui/context-menu/ContextMenuLabel.vue +25 -0
- package/src/components/ui/context-menu/ContextMenuPortal.vue +11 -0
- package/src/components/ui/context-menu/ContextMenuRadioGroup.vue +19 -0
- package/src/components/ui/context-menu/ContextMenuRadioItem.vue +40 -0
- package/src/components/ui/context-menu/ContextMenuSeparator.vue +20 -0
- package/src/components/ui/context-menu/ContextMenuShortcut.vue +14 -0
- package/src/components/ui/context-menu/ContextMenuSub.vue +19 -0
- package/src/components/ui/context-menu/ContextMenuSubContent.vue +35 -0
- package/src/components/ui/context-menu/ContextMenuSubTrigger.vue +34 -0
- package/src/components/ui/context-menu/ContextMenuTrigger.vue +13 -0
- package/src/components/ui/context-menu/index.ts +14 -0
- package/src/components/ui/data-table/DataTable.vue +167 -0
- package/src/components/ui/data-table/DataTablePagination.vue +112 -0
- package/src/components/ui/data-table/index.ts +3 -0
- package/src/components/ui/data-table/types.ts +48 -0
- package/src/components/ui/dialog/Dialog.vue +14 -0
- package/src/components/ui/dialog/DialogClose.vue +11 -0
- package/src/components/ui/dialog/DialogContent.vue +61 -0
- package/src/components/ui/dialog/DialogDescription.vue +24 -0
- package/src/components/ui/dialog/DialogFooter.vue +19 -0
- package/src/components/ui/dialog/DialogHeader.vue +16 -0
- package/src/components/ui/dialog/DialogScrollContent.vue +65 -0
- package/src/components/ui/dialog/DialogTitle.vue +29 -0
- package/src/components/ui/dialog/DialogTrigger.vue +11 -0
- package/src/components/ui/dialog/index.ts +9 -0
- package/src/components/ui/drawer/Drawer.vue +19 -0
- package/src/components/ui/drawer/DrawerContent.vue +28 -0
- package/src/components/ui/drawer/DrawerDescription.vue +20 -0
- package/src/components/ui/drawer/DrawerFooter.vue +14 -0
- package/src/components/ui/drawer/DrawerHeader.vue +14 -0
- package/src/components/ui/drawer/DrawerOverlay.vue +18 -0
- package/src/components/ui/drawer/DrawerTitle.vue +20 -0
- package/src/components/ui/drawer/index.ts +8 -0
- package/src/components/ui/dropdown-menu/DropdownMenu.vue +14 -0
- package/src/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +40 -0
- package/src/components/ui/dropdown-menu/DropdownMenuContent.vue +44 -0
- package/src/components/ui/dropdown-menu/DropdownMenuGroup.vue +11 -0
- package/src/components/ui/dropdown-menu/DropdownMenuItem.vue +28 -0
- package/src/components/ui/dropdown-menu/DropdownMenuLabel.vue +24 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +19 -0
- package/src/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +40 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +22 -0
- package/src/components/ui/dropdown-menu/DropdownMenuShortcut.vue +14 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubContent.vue +36 -0
- package/src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +33 -0
- package/src/components/ui/dropdown-menu/DropdownMenuTrigger.vue +13 -0
- package/src/components/ui/dropdown-menu/index.ts +16 -0
- package/src/components/ui/hover-card/HoverCard.vue +14 -0
- package/src/components/ui/hover-card/HoverCardContent.vue +41 -0
- package/src/components/ui/hover-card/HoverCardTrigger.vue +11 -0
- package/src/components/ui/hover-card/index.ts +3 -0
- package/src/components/ui/index.ts +41 -0
- package/src/components/ui/input/Input.vue +24 -0
- package/src/components/ui/input/index.ts +1 -0
- package/src/components/ui/label/Label.vue +27 -0
- package/src/components/ui/label/index.ts +1 -0
- package/src/components/ui/multi-select/MultiSelect.vue +141 -0
- package/src/components/ui/multi-select/index.ts +7 -0
- package/src/components/ui/notification/Notification.vue +85 -0
- package/src/components/ui/notification/index.ts +1 -0
- package/src/components/ui/number-field/NumberField.vue +23 -0
- package/src/components/ui/number-field/NumberFieldContent.vue +14 -0
- package/src/components/ui/number-field/NumberFieldDecrement.vue +25 -0
- package/src/components/ui/number-field/NumberFieldIncrement.vue +25 -0
- package/src/components/ui/number-field/NumberFieldInput.vue +8 -0
- package/src/components/ui/number-field/index.ts +5 -0
- package/src/components/ui/popover/Popover.vue +15 -0
- package/src/components/ui/popover/PopoverContent.vue +61 -0
- package/src/components/ui/popover/PopoverTrigger.vue +11 -0
- package/src/components/ui/popover/index.ts +3 -0
- package/src/components/ui/progress/Progress.vue +39 -0
- package/src/components/ui/progress/index.ts +1 -0
- package/src/components/ui/radio-group/RadioGroup.vue +25 -0
- package/src/components/ui/radio-group/RadioGroupItem.vue +39 -0
- package/src/components/ui/radio-group/index.ts +2 -0
- package/src/components/ui/scroll-area/ScrollArea.vue +29 -0
- package/src/components/ui/scroll-area/ScrollBar.vue +30 -0
- package/src/components/ui/scroll-area/index.ts +2 -0
- package/src/components/ui/scroll-pane/ScrollPane.vue +25 -0
- package/src/components/ui/scroll-pane/ScrollPaneHeader.vue +75 -0
- package/src/components/ui/scroll-pane/index.ts +2 -0
- package/src/components/ui/select/Select.vue +15 -0
- package/src/components/ui/select/SelectContent.vue +57 -0
- package/src/components/ui/select/SelectGroup.vue +19 -0
- package/src/components/ui/select/SelectItem.vue +47 -0
- package/src/components/ui/select/SelectItemText.vue +11 -0
- package/src/components/ui/select/SelectLabel.vue +13 -0
- package/src/components/ui/select/SelectScrollDownButton.vue +24 -0
- package/src/components/ui/select/SelectScrollUpButton.vue +24 -0
- package/src/components/ui/select/SelectSeparator.vue +17 -0
- package/src/components/ui/select/SelectTrigger.vue +45 -0
- package/src/components/ui/select/SelectValue.vue +11 -0
- package/src/components/ui/select/index.ts +11 -0
- package/src/components/ui/separator/Separator.vue +35 -0
- package/src/components/ui/separator/index.ts +1 -0
- package/src/components/ui/sheet/Sheet.vue +14 -0
- package/src/components/ui/sheet/SheetClose.vue +11 -0
- package/src/components/ui/sheet/SheetContent.vue +56 -0
- package/src/components/ui/sheet/SheetDescription.vue +22 -0
- package/src/components/ui/sheet/SheetFooter.vue +19 -0
- package/src/components/ui/sheet/SheetHeader.vue +16 -0
- package/src/components/ui/sheet/SheetTitle.vue +22 -0
- package/src/components/ui/sheet/SheetTrigger.vue +11 -0
- package/src/components/ui/sheet/index.ts +31 -0
- package/src/components/ui/skeleton/Skeleton.vue +14 -0
- package/src/components/ui/skeleton/index.ts +1 -0
- package/src/components/ui/slider/Slider.vue +66 -0
- package/src/components/ui/slider/index.ts +1 -0
- package/src/components/ui/switch/Switch.vue +37 -0
- package/src/components/ui/switch/index.ts +1 -0
- package/src/components/ui/table/Table.vue +16 -0
- package/src/components/ui/table/TableBody.vue +14 -0
- package/src/components/ui/table/TableCaption.vue +14 -0
- package/src/components/ui/table/TableCell.vue +14 -0
- package/src/components/ui/table/TableEmpty.vue +39 -0
- package/src/components/ui/table/TableFooter.vue +16 -0
- package/src/components/ui/table/TableHead.vue +21 -0
- package/src/components/ui/table/TableHeader.vue +14 -0
- package/src/components/ui/table/TableRow.vue +21 -0
- package/src/components/ui/table/index.ts +9 -0
- package/src/components/ui/tabs/Tabs.vue +15 -0
- package/src/components/ui/tabs/TabsContent.vue +22 -0
- package/src/components/ui/tabs/TabsIndicator.vue +22 -0
- package/src/components/ui/tabs/TabsList.vue +25 -0
- package/src/components/ui/tabs/TabsTrigger.vue +29 -0
- package/src/components/ui/tabs/index.ts +5 -0
- package/src/components/ui/tags-input/TagsInput.vue +22 -0
- package/src/components/ui/tags-input/TagsInputInput.vue +19 -0
- package/src/components/ui/tags-input/TagsInputItem.vue +22 -0
- package/src/components/ui/tags-input/TagsInputItemDelete.vue +24 -0
- package/src/components/ui/tags-input/TagsInputItemText.vue +19 -0
- package/src/components/ui/tags-input/index.ts +5 -0
- package/src/components/ui/textarea/Textarea.vue +24 -0
- package/src/components/ui/textarea/index.ts +1 -0
- package/src/components/ui/toast/Toast.vue +57 -0
- package/src/components/ui/toast/ToastAction.vue +30 -0
- package/src/components/ui/toast/ToastClose.vue +31 -0
- package/src/components/ui/toast/ToastDescription.vue +25 -0
- package/src/components/ui/toast/ToastTitle.vue +25 -0
- package/src/components/ui/toast/Toaster.vue +31 -0
- package/src/components/ui/toast/index.ts +8 -0
- package/src/components/ui/toast/use-toast.ts +136 -0
- package/src/components/ui/toggle/Toggle.vue +35 -0
- package/src/components/ui/toggle/index.ts +27 -0
- package/src/components/ui/toggle-group/ToggleGroup.vue +34 -0
- package/src/components/ui/toggle-group/ToggleGroupItem.vue +35 -0
- package/src/components/ui/toggle-group/index.ts +2 -0
- package/src/components/ui/tooltip/Tooltip.vue +14 -0
- package/src/components/ui/tooltip/TooltipContent.vue +31 -0
- package/src/components/ui/tooltip/TooltipProvider.vue +11 -0
- package/src/components/ui/tooltip/TooltipTrigger.vue +11 -0
- package/src/components/ui/tooltip/index.ts +4 -0
- package/src/composables/glass/index.ts +8 -0
- package/src/composables/glass/useGlassRenderer.ts +252 -0
- package/src/composables/glass/webgl/frostShader.ts +221 -0
- package/src/composables/glass/webgpu/glassShader.wgsl +173 -0
- package/src/composables/index.ts +32 -0
- package/src/composables/infinite-scroll/index.ts +2 -0
- package/src/composables/infinite-scroll/types.ts +25 -0
- package/src/composables/infinite-scroll/useInfiniteScroll.ts +101 -0
- package/src/composables/interaction/index.ts +5 -0
- package/src/composables/interaction/useHeightTransition.ts +82 -0
- package/src/composables/interaction/useHoverPopover.ts +64 -0
- package/src/composables/interaction/useHoverToggle.ts +103 -0
- package/src/composables/interaction/useLeaveTimer.ts +17 -0
- package/src/composables/interaction/useTouchGate.ts +207 -0
- package/src/composables/pagination/index.ts +2 -0
- package/src/composables/pagination/useOffsetPagination.ts +70 -0
- package/src/composables/prng.ts +32 -0
- package/src/composables/useCharSplit.ts +31 -0
- package/src/composables/useClipboard.ts +46 -0
- package/src/composables/useGlobalDark.ts +61 -0
- package/src/composables/useKeyboardShortcuts.ts +205 -0
- package/src/composables/useWatercolorBlob.ts +136 -0
- package/src/composables/virtual/index.ts +22 -0
- package/src/composables/virtual/useVirtualSectionWindow.ts +338 -0
- package/src/composables/virtual/useWindowedStore.ts +86 -0
- package/src/composables/virtual/virtualSectionLayout.ts +212 -0
- package/src/index.ts +9 -0
- package/src/styles/animations.css +233 -0
- package/src/styles/cards.css +66 -0
- package/src/styles/dock.css +221 -0
- package/src/styles/floating-panel.css +49 -0
- package/src/styles/glass.css +266 -0
- package/src/styles/index.css +26 -0
- package/src/styles/scroll-pane.css +10 -0
- package/src/styles/theme.css +138 -0
- package/src/styles/tokens.css +333 -0
- package/src/styles/transitions.css +226 -0
- package/src/styles/typography.css +277 -0
- package/src/styles/utilities.css +697 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/index.ts +1 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { ref, computed } from "vue";
|
|
2
|
+
import type { TypewriterOptions, TypewriterWord, CancellationToken } from "../types";
|
|
3
|
+
import { DEFAULTS } from "../types";
|
|
4
|
+
import { calculateKeyDelay } from "../utils/keyboard";
|
|
5
|
+
import { getPauseDelay, PAUSE_PATTERNS } from "../utils/pausePatterns";
|
|
6
|
+
import {
|
|
7
|
+
createCancellationToken,
|
|
8
|
+
sleep,
|
|
9
|
+
stochasticDelay,
|
|
10
|
+
backspaceDelay as calcBackspaceDelay,
|
|
11
|
+
pickNgramSize,
|
|
12
|
+
prefersReducedMotion,
|
|
13
|
+
} from "../utils/timing";
|
|
14
|
+
import {
|
|
15
|
+
nextTypoAction,
|
|
16
|
+
createTypoContext,
|
|
17
|
+
DEFAULT_TYPO_CONFIG,
|
|
18
|
+
type TypoConfig,
|
|
19
|
+
} from "../utils/typoStateMachine";
|
|
20
|
+
|
|
21
|
+
export function useTypewriter(options: TypewriterOptions) {
|
|
22
|
+
// Merge options with defaults
|
|
23
|
+
const opts = { ...DEFAULTS, ...options };
|
|
24
|
+
|
|
25
|
+
// Determine mode
|
|
26
|
+
const isWordRotation = opts.words != null && opts.words.length > 0;
|
|
27
|
+
|
|
28
|
+
// --- Reactive state ---
|
|
29
|
+
const displayText = ref("");
|
|
30
|
+
const isTyping = ref(false);
|
|
31
|
+
const isFirstAnimation = ref(true);
|
|
32
|
+
const isDeleting = ref(false);
|
|
33
|
+
|
|
34
|
+
// Word-rotation state
|
|
35
|
+
const currentWordIndex = ref(0);
|
|
36
|
+
const currentWord = ref<TypewriterWord>(
|
|
37
|
+
isWordRotation ? opts.words![0] : { text: opts.text ?? "" },
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// --- Internal state (non-reactive) ---
|
|
41
|
+
let targetText = isWordRotation ? opts.words![0].text : (opts.text ?? "");
|
|
42
|
+
let currentToken: CancellationToken | null = null;
|
|
43
|
+
|
|
44
|
+
// --- Derived typo config ---
|
|
45
|
+
function getTypoConfig(): TypoConfig {
|
|
46
|
+
return {
|
|
47
|
+
...DEFAULT_TYPO_CONFIG,
|
|
48
|
+
maxCharsBeforeNotice: opts.maxCharsBeforeNotice,
|
|
49
|
+
continueAfterTypoProbability: opts.continueAfterTypoProbability,
|
|
50
|
+
sequentialTypoDecay: opts.sequentialTypoDecay,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- Core typing loop ---
|
|
55
|
+
|
|
56
|
+
async function typeTextWithNgrams(token: CancellationToken): Promise<void> {
|
|
57
|
+
const chars = targetText;
|
|
58
|
+
let typoCtx = createTypoContext();
|
|
59
|
+
const typoConfig = getTypoConfig();
|
|
60
|
+
const speedFactor = isFirstAnimation.value ? opts.firstAnimationSpeedFactor : 1;
|
|
61
|
+
const skipTypos = isFirstAnimation.value;
|
|
62
|
+
|
|
63
|
+
while (displayText.value.length < chars.length) {
|
|
64
|
+
if (token.cancelled) return;
|
|
65
|
+
|
|
66
|
+
const pos = displayText.value.length;
|
|
67
|
+
const currentChar = chars[pos];
|
|
68
|
+
const prevChar = pos > 0 ? chars[pos - 1] : "";
|
|
69
|
+
|
|
70
|
+
// If FSM is in a non-normal state, drive it single-char
|
|
71
|
+
if (typoCtx.state !== "normal") {
|
|
72
|
+
const result = nextTypoAction(typoCtx, currentChar, typoConfig, opts.errorRate);
|
|
73
|
+
typoCtx = result.ctx;
|
|
74
|
+
|
|
75
|
+
const ok = await executeAction(result.action, token, prevChar, speedFactor);
|
|
76
|
+
if (!ok) return;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Normal state: decide typo or n-gram
|
|
81
|
+
if (!skipTypos && pos > 3 && pos < chars.length - 3) {
|
|
82
|
+
const result = nextTypoAction(typoCtx, currentChar, typoConfig, opts.errorRate);
|
|
83
|
+
typoCtx = result.ctx;
|
|
84
|
+
|
|
85
|
+
if (result.action.type === "type_wrong") {
|
|
86
|
+
const ok = await executeAction(result.action, token, prevChar, speedFactor);
|
|
87
|
+
if (!ok) return;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// FSM said type_correct -- fall through to n-gram path
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// N-gram typing
|
|
95
|
+
const ngramSize = Math.min(pickNgramSize(opts.ngramSize), chars.length - pos);
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < ngramSize; i++) {
|
|
98
|
+
if (token.cancelled) return;
|
|
99
|
+
const charPos = pos + i;
|
|
100
|
+
const ch = chars[charPos];
|
|
101
|
+
const prev = charPos > 0 ? chars[charPos - 1] : "";
|
|
102
|
+
|
|
103
|
+
displayText.value += ch;
|
|
104
|
+
|
|
105
|
+
// Only delay after the last char of the n-gram
|
|
106
|
+
if (i === ngramSize - 1) {
|
|
107
|
+
const nextCh = chars[charPos + 1] ?? "";
|
|
108
|
+
const keyDelay = calculateKeyDelay(prev, ch) * speedFactor;
|
|
109
|
+
const typingDelay = stochasticDelay(keyDelay, opts.variance);
|
|
110
|
+
|
|
111
|
+
// Punctuation pauses (skip on first animation)
|
|
112
|
+
let pauseDelay = 0;
|
|
113
|
+
if (!isFirstAnimation.value) {
|
|
114
|
+
pauseDelay = getPauseDelay(ch, nextCh, PAUSE_PATTERNS);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const ok = await sleep(typingDelay + pauseDelay, token);
|
|
118
|
+
if (!ok) return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function executeAction(
|
|
125
|
+
action: ReturnType<typeof nextTypoAction>["action"],
|
|
126
|
+
token: CancellationToken,
|
|
127
|
+
prevChar: string,
|
|
128
|
+
speedFactor: number,
|
|
129
|
+
): Promise<boolean> {
|
|
130
|
+
switch (action.type) {
|
|
131
|
+
case "type_correct": {
|
|
132
|
+
if (action.char === "") return true; // resume placeholder
|
|
133
|
+
displayText.value += action.char;
|
|
134
|
+
const delay = calculateKeyDelay(prevChar, action.char) * speedFactor;
|
|
135
|
+
return sleep(stochasticDelay(delay, opts.variance), token);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
case "type_wrong": {
|
|
139
|
+
displayText.value += action.char;
|
|
140
|
+
const delay = calculateKeyDelay(prevChar, action.char) * speedFactor;
|
|
141
|
+
return sleep(stochasticDelay(delay, opts.variance), token);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
case "type_past_correct": {
|
|
145
|
+
displayText.value += action.char;
|
|
146
|
+
const delay = calculateKeyDelay(prevChar, action.char) * speedFactor;
|
|
147
|
+
return sleep(
|
|
148
|
+
stochasticDelay(delay * opts.correctionSpeedMultiplier, opts.variance),
|
|
149
|
+
token,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case "notice": {
|
|
154
|
+
return sleep(action.pauseMs, token);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
case "backspace": {
|
|
158
|
+
return animateBackspaceSequence(action.count, token, action.frantic);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
case "resume": {
|
|
162
|
+
return sleep(action.pauseMs, token);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// --- Backspace animation ---
|
|
168
|
+
|
|
169
|
+
async function animateBackspaceSequence(
|
|
170
|
+
count: number,
|
|
171
|
+
token: CancellationToken,
|
|
172
|
+
frantic: boolean,
|
|
173
|
+
): Promise<boolean> {
|
|
174
|
+
const base = frantic ? opts.correctionBaseDelay : opts.backspaceBaseDelay;
|
|
175
|
+
const accel = frantic ? opts.backspaceAcceleration * 2 : opts.backspaceAcceleration;
|
|
176
|
+
|
|
177
|
+
for (let i = 0; i < count; i++) {
|
|
178
|
+
if (token.cancelled) return false;
|
|
179
|
+
displayText.value = displayText.value.slice(0, -1);
|
|
180
|
+
const delay = calcBackspaceDelay(base, opts.variance * 0.5, i, accel);
|
|
181
|
+
const ok = await sleep(delay, token);
|
|
182
|
+
if (!ok) return false;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// --- Simple character-by-character delete (word-rotation style) ---
|
|
188
|
+
|
|
189
|
+
async function simpleDeleteSequence(token: CancellationToken): Promise<boolean> {
|
|
190
|
+
isDeleting.value = true;
|
|
191
|
+
const speed = opts.deletingSpeed;
|
|
192
|
+
|
|
193
|
+
while (displayText.value.length > 0) {
|
|
194
|
+
if (token.cancelled) {
|
|
195
|
+
isDeleting.value = false;
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
displayText.value = displayText.value.slice(0, -1);
|
|
199
|
+
const jitter = Math.random() * 20;
|
|
200
|
+
const ok = await sleep(speed + jitter, token);
|
|
201
|
+
if (!ok) {
|
|
202
|
+
isDeleting.value = false;
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
isDeleting.value = false;
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// --- Word rotation loop ---
|
|
211
|
+
|
|
212
|
+
async function wordRotationLoop(token: CancellationToken): Promise<void> {
|
|
213
|
+
const words = opts.words!;
|
|
214
|
+
let wordIdx = currentWordIndex.value;
|
|
215
|
+
|
|
216
|
+
while (!token.cancelled) {
|
|
217
|
+
// Set current word
|
|
218
|
+
currentWord.value = words[wordIdx];
|
|
219
|
+
currentWordIndex.value = wordIdx;
|
|
220
|
+
targetText = words[wordIdx].text;
|
|
221
|
+
|
|
222
|
+
// Type the word
|
|
223
|
+
await typeTextWithNgrams(token);
|
|
224
|
+
if (token.cancelled) return;
|
|
225
|
+
|
|
226
|
+
isFirstAnimation.value = false;
|
|
227
|
+
opts.onWordComplete?.(wordIdx);
|
|
228
|
+
|
|
229
|
+
// Pause after typing
|
|
230
|
+
const ok1 = await sleep(opts.pauseAfterType, token);
|
|
231
|
+
if (!ok1) return;
|
|
232
|
+
|
|
233
|
+
// Delete the word
|
|
234
|
+
const ok2 = await simpleDeleteSequence(token);
|
|
235
|
+
if (!ok2) return;
|
|
236
|
+
|
|
237
|
+
// Pause after deleting
|
|
238
|
+
const ok3 = await sleep(opts.pauseAfterDelete, token);
|
|
239
|
+
if (!ok3) return;
|
|
240
|
+
|
|
241
|
+
// Advance to next word
|
|
242
|
+
wordIdx = (wordIdx + 1) % words.length;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// --- Public API ---
|
|
247
|
+
|
|
248
|
+
async function startTyping(): Promise<void> {
|
|
249
|
+
// Cancel any in-flight animation
|
|
250
|
+
currentToken?.cancel();
|
|
251
|
+
const token = createCancellationToken();
|
|
252
|
+
currentToken = token;
|
|
253
|
+
isTyping.value = true;
|
|
254
|
+
|
|
255
|
+
// Reduced motion: show final text immediately
|
|
256
|
+
if (opts.respectReducedMotion && prefersReducedMotion()) {
|
|
257
|
+
if (isWordRotation) {
|
|
258
|
+
displayText.value = opts.words![0].text;
|
|
259
|
+
currentWord.value = opts.words![0];
|
|
260
|
+
} else {
|
|
261
|
+
displayText.value = targetText;
|
|
262
|
+
}
|
|
263
|
+
isTyping.value = false;
|
|
264
|
+
isFirstAnimation.value = false;
|
|
265
|
+
opts.onComplete?.();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (isWordRotation) {
|
|
270
|
+
// Word-rotation mode: type/delete/cycle indefinitely
|
|
271
|
+
await wordRotationLoop(token);
|
|
272
|
+
|
|
273
|
+
if (!token.cancelled) {
|
|
274
|
+
isTyping.value = false;
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
// Single-text mode: backspace existing text if not first animation, then type
|
|
278
|
+
if (!isFirstAnimation.value && displayText.value.length > 0) {
|
|
279
|
+
const ok = await sleep(opts.preBackspacePause, token);
|
|
280
|
+
if (!ok) return;
|
|
281
|
+
|
|
282
|
+
const ok2 = await animateBackspaceSequence(
|
|
283
|
+
displayText.value.length,
|
|
284
|
+
token,
|
|
285
|
+
false,
|
|
286
|
+
);
|
|
287
|
+
if (!ok2) return;
|
|
288
|
+
|
|
289
|
+
const ok3 = await sleep(opts.postBackspacePause, token);
|
|
290
|
+
if (!ok3) return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Type the text
|
|
294
|
+
await typeTextWithNgrams(token);
|
|
295
|
+
|
|
296
|
+
// Complete (only if we were not cancelled)
|
|
297
|
+
if (!token.cancelled) {
|
|
298
|
+
isFirstAnimation.value = false;
|
|
299
|
+
isTyping.value = false;
|
|
300
|
+
opts.onComplete?.();
|
|
301
|
+
|
|
302
|
+
if (opts.loop) {
|
|
303
|
+
await sleep(2000, token);
|
|
304
|
+
if (!token.cancelled) {
|
|
305
|
+
startTyping();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function stopTyping(): void {
|
|
313
|
+
currentToken?.cancel();
|
|
314
|
+
currentToken = null;
|
|
315
|
+
isTyping.value = false;
|
|
316
|
+
isDeleting.value = false;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function reset(): void {
|
|
320
|
+
stopTyping();
|
|
321
|
+
displayText.value = "";
|
|
322
|
+
isFirstAnimation.value = true;
|
|
323
|
+
if (isWordRotation) {
|
|
324
|
+
currentWordIndex.value = 0;
|
|
325
|
+
currentWord.value = opts.words![0];
|
|
326
|
+
targetText = opts.words![0].text;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function updateText(newText: string): void {
|
|
331
|
+
targetText = newText;
|
|
332
|
+
if (!isWordRotation) {
|
|
333
|
+
opts.text = newText;
|
|
334
|
+
}
|
|
335
|
+
if (displayText.value.length > 0) {
|
|
336
|
+
isFirstAnimation.value = false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function backspaceToPosition(targetLength: number): Promise<void> {
|
|
341
|
+
if (isTyping.value || targetLength >= displayText.value.length || targetLength < 0) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
currentToken?.cancel();
|
|
346
|
+
const token = createCancellationToken();
|
|
347
|
+
currentToken = token;
|
|
348
|
+
isTyping.value = true;
|
|
349
|
+
|
|
350
|
+
const charsToRemove = displayText.value.length - targetLength;
|
|
351
|
+
await animateBackspaceSequence(charsToRemove, token, false);
|
|
352
|
+
|
|
353
|
+
isTyping.value = false;
|
|
354
|
+
if (!token.cancelled) {
|
|
355
|
+
await sleep(200, token);
|
|
356
|
+
if (!token.cancelled) {
|
|
357
|
+
startTyping();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// --- Word-rotation convenience methods ---
|
|
363
|
+
|
|
364
|
+
function pause(): void {
|
|
365
|
+
stopTyping();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function resume(): void {
|
|
369
|
+
if (!isTyping.value) {
|
|
370
|
+
startTyping();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function setCharPosition(n: number): void {
|
|
375
|
+
const maxLen = targetText.length;
|
|
376
|
+
const clamped = Math.max(0, Math.min(n, maxLen));
|
|
377
|
+
displayText.value = targetText.slice(0, clamped);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function forceWord(idx: number, charPos: number): void {
|
|
381
|
+
if (!isWordRotation) return;
|
|
382
|
+
stopTyping();
|
|
383
|
+
const words = opts.words!;
|
|
384
|
+
const safeIdx = idx % words.length;
|
|
385
|
+
currentWordIndex.value = safeIdx;
|
|
386
|
+
currentWord.value = words[safeIdx];
|
|
387
|
+
targetText = words[safeIdx].text;
|
|
388
|
+
setCharPosition(charPos);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
// State
|
|
393
|
+
displayText,
|
|
394
|
+
isTyping,
|
|
395
|
+
isFirstAnimation,
|
|
396
|
+
isDeleting,
|
|
397
|
+
currentWordIndex: computed(() => currentWordIndex.value),
|
|
398
|
+
currentWord: computed(() => currentWord.value),
|
|
399
|
+
|
|
400
|
+
// Single-text API
|
|
401
|
+
startTyping,
|
|
402
|
+
stopTyping,
|
|
403
|
+
reset,
|
|
404
|
+
updateText,
|
|
405
|
+
backspaceToPosition,
|
|
406
|
+
|
|
407
|
+
// Word-rotation API
|
|
408
|
+
pause,
|
|
409
|
+
resume,
|
|
410
|
+
setCharPosition,
|
|
411
|
+
forceWord,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as TypewriterText } from "./TypewriterText.vue";
|
|
2
|
+
export { useTypewriter } from "./composables/useTypewriter";
|
|
3
|
+
export * from "./types";
|
|
4
|
+
export * from "./utils/keyboard";
|
|
5
|
+
export * from "./utils/pausePatterns";
|
|
6
|
+
export * from "./utils/timing";
|
|
7
|
+
export * from "./utils/typoStateMachine";
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { KeyPosition } from "./utils/keyboard";
|
|
2
|
+
|
|
3
|
+
export type { KeyPosition };
|
|
4
|
+
|
|
5
|
+
// --- Word rotation types (from bbnf-lang pattern) ---
|
|
6
|
+
|
|
7
|
+
/** A single word entry for word-rotation mode. */
|
|
8
|
+
export interface TypewriterWord {
|
|
9
|
+
/** The text to type. */
|
|
10
|
+
text: string;
|
|
11
|
+
/** Optional CSS class applied to the word span. */
|
|
12
|
+
className?: string;
|
|
13
|
+
/** Whether to render with a monospace/code style. */
|
|
14
|
+
isCode?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// --- Typo FSM types ---
|
|
18
|
+
|
|
19
|
+
export type TypoState =
|
|
20
|
+
| "normal"
|
|
21
|
+
| "typo_injected"
|
|
22
|
+
| "typing_past"
|
|
23
|
+
| "noticed"
|
|
24
|
+
| "correcting"
|
|
25
|
+
| "resuming";
|
|
26
|
+
|
|
27
|
+
export interface TypoContext {
|
|
28
|
+
state: TypoState;
|
|
29
|
+
/** Number of chars typed past the typo position */
|
|
30
|
+
charsPastTypo: number;
|
|
31
|
+
/** Total chars to delete when correcting (typo char + chars past) */
|
|
32
|
+
charsToDelete: number;
|
|
33
|
+
/** Number of sequential typos in this burst */
|
|
34
|
+
sequentialTypoCount: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type TypoAction =
|
|
38
|
+
| { type: "type_correct"; char: string }
|
|
39
|
+
| { type: "type_wrong"; char: string; intended: string }
|
|
40
|
+
| { type: "type_past_correct"; char: string }
|
|
41
|
+
| { type: "notice"; pauseMs: number }
|
|
42
|
+
| { type: "backspace"; count: number; frantic: boolean }
|
|
43
|
+
| { type: "resume"; pauseMs: number };
|
|
44
|
+
|
|
45
|
+
// --- Cancellation ---
|
|
46
|
+
|
|
47
|
+
export interface CancellationToken {
|
|
48
|
+
readonly cancelled: boolean;
|
|
49
|
+
cancel(): void;
|
|
50
|
+
onCancel(fn: () => void): void;
|
|
51
|
+
offCancel(fn: () => void): void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- Options ---
|
|
55
|
+
|
|
56
|
+
export interface TypewriterOptions {
|
|
57
|
+
/** Single text to type. Mutually exclusive with `words`. */
|
|
58
|
+
text?: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Array of words to cycle through (word-rotation mode).
|
|
62
|
+
* When provided, the typewriter types each word, pauses, backspaces,
|
|
63
|
+
* then types the next word in a loop.
|
|
64
|
+
* Mutually exclusive with `text`.
|
|
65
|
+
*/
|
|
66
|
+
words?: TypewriterWord[];
|
|
67
|
+
|
|
68
|
+
/** N-gram chunk size for typing. Fixed number or {min, max} range. */
|
|
69
|
+
ngramSize?: number | { min: number; max: number };
|
|
70
|
+
/** Base typing speed in ms per keystroke. */
|
|
71
|
+
baseSpeed?: number;
|
|
72
|
+
/** Speed variance factor (0-1). */
|
|
73
|
+
variance?: number;
|
|
74
|
+
/** Probability of introducing a typo per character (0-1). */
|
|
75
|
+
errorRate?: number;
|
|
76
|
+
/** Speed multiplier for the first animation pass (lower = faster). */
|
|
77
|
+
firstAnimationSpeedFactor?: number;
|
|
78
|
+
|
|
79
|
+
// Typo behavior
|
|
80
|
+
/** Maximum chars typed past a typo before noticing. */
|
|
81
|
+
maxCharsBeforeNotice?: number;
|
|
82
|
+
/** Probability of continuing to type after a typo (0-1). */
|
|
83
|
+
continueAfterTypoProbability?: number;
|
|
84
|
+
/** Decay factor for sequential typo probability (0-1). */
|
|
85
|
+
sequentialTypoDecay?: number;
|
|
86
|
+
/** Speed multiplier for correction typing (lower = faster). */
|
|
87
|
+
correctionSpeedMultiplier?: number;
|
|
88
|
+
/** Base delay for correction backspace in ms. */
|
|
89
|
+
correctionBaseDelay?: number;
|
|
90
|
+
|
|
91
|
+
// Backspace
|
|
92
|
+
/** Base delay for normal backspace in ms. */
|
|
93
|
+
backspaceBaseDelay?: number;
|
|
94
|
+
/** Acceleration factor for consecutive backspaces. */
|
|
95
|
+
backspaceAcceleration?: number;
|
|
96
|
+
/** Pause before starting backspace animation in ms. */
|
|
97
|
+
preBackspacePause?: number;
|
|
98
|
+
/** Pause after backspace animation before retyping in ms. */
|
|
99
|
+
postBackspacePause?: number;
|
|
100
|
+
|
|
101
|
+
// Cursor
|
|
102
|
+
/** Whether to show the cursor. */
|
|
103
|
+
cursorVisible?: boolean;
|
|
104
|
+
/** Whether the cursor blinks when idle. */
|
|
105
|
+
cursorBlink?: boolean;
|
|
106
|
+
/** Cursor character. */
|
|
107
|
+
cursorChar?: string;
|
|
108
|
+
|
|
109
|
+
// Word-rotation mode options
|
|
110
|
+
/** Pause after fully typing a word before deleting (ms). Only for word-rotation mode. */
|
|
111
|
+
pauseAfterType?: number;
|
|
112
|
+
/** Pause after deleting a word before typing the next (ms). Only for word-rotation mode. */
|
|
113
|
+
pauseAfterDelete?: number;
|
|
114
|
+
/** Typing speed for deletion in word-rotation mode (ms). Falls back to backspaceBaseDelay. */
|
|
115
|
+
deletingSpeed?: number;
|
|
116
|
+
|
|
117
|
+
// Misc
|
|
118
|
+
/** Respect prefers-reduced-motion media query. */
|
|
119
|
+
respectReducedMotion?: boolean;
|
|
120
|
+
/** Loop the single-text typing animation. */
|
|
121
|
+
loop?: boolean;
|
|
122
|
+
/** Callback when typing completes. */
|
|
123
|
+
onComplete?: () => void;
|
|
124
|
+
/** Callback when a word rotation completes (word-rotation mode). Receives the word index. */
|
|
125
|
+
onWordComplete?: (index: number) => void;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const DEFAULTS = {
|
|
129
|
+
ngramSize: { min: 1, max: 3 } as { min: number; max: number },
|
|
130
|
+
baseSpeed: 150,
|
|
131
|
+
variance: 0.4,
|
|
132
|
+
errorRate: 0.015,
|
|
133
|
+
firstAnimationSpeedFactor: 0.6,
|
|
134
|
+
|
|
135
|
+
maxCharsBeforeNotice: 4,
|
|
136
|
+
continueAfterTypoProbability: 0.6,
|
|
137
|
+
sequentialTypoDecay: 0.3,
|
|
138
|
+
correctionSpeedMultiplier: 0.5,
|
|
139
|
+
correctionBaseDelay: 40,
|
|
140
|
+
|
|
141
|
+
backspaceBaseDelay: 30,
|
|
142
|
+
backspaceAcceleration: 0.08,
|
|
143
|
+
preBackspacePause: 200,
|
|
144
|
+
postBackspacePause: 150,
|
|
145
|
+
|
|
146
|
+
cursorVisible: true,
|
|
147
|
+
cursorBlink: true,
|
|
148
|
+
cursorChar: "|",
|
|
149
|
+
|
|
150
|
+
pauseAfterType: 3000,
|
|
151
|
+
pauseAfterDelete: 800,
|
|
152
|
+
deletingSpeed: 70,
|
|
153
|
+
|
|
154
|
+
respectReducedMotion: true,
|
|
155
|
+
loop: false,
|
|
156
|
+
} as const satisfies Omit<
|
|
157
|
+
Required<TypewriterOptions>,
|
|
158
|
+
"text" | "words" | "onComplete" | "onWordComplete"
|
|
159
|
+
>;
|