fancy-ui-svelte 0.11.0 → 0.13.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 +4 -4
- package/dist/fancy-ui/_internals/ScrollLockHarness.test.svelte +29 -0
- package/dist/fancy-ui/_internals/ScrollLockHarness.test.svelte.d.ts +3 -0
- package/dist/fancy-ui/_internals/ScrollLockPanel.test.svelte +43 -0
- package/dist/fancy-ui/_internals/ScrollLockPanel.test.svelte.d.ts +8 -0
- package/dist/fancy-ui/_internals/anchor-position.d.ts +18 -7
- package/dist/fancy-ui/_internals/anchor-position.js +45 -10
- package/dist/fancy-ui/_internals/anchor-position.test.js +48 -1
- package/dist/fancy-ui/_internals/dismissable.d.ts +16 -0
- package/dist/fancy-ui/_internals/dismissable.js +23 -6
- package/dist/fancy-ui/_internals/dismissable.test.js +115 -6
- package/dist/fancy-ui/_internals/focus-trap.d.ts +36 -0
- package/dist/fancy-ui/_internals/focus-trap.js +106 -39
- package/dist/fancy-ui/_internals/focus-trap.test.js +356 -1
- package/dist/fancy-ui/_internals/motion/AnchoredHarness.test.svelte +42 -0
- package/dist/fancy-ui/_internals/motion/AnchoredHarness.test.svelte.d.ts +11 -0
- package/dist/fancy-ui/_internals/motion/TransitionsHarness.test.svelte +32 -0
- package/dist/fancy-ui/_internals/motion/TransitionsHarness.test.svelte.d.ts +10 -0
- package/dist/fancy-ui/_internals/motion/anchored.d.ts +104 -0
- package/dist/fancy-ui/_internals/motion/anchored.js +141 -0
- package/dist/fancy-ui/_internals/motion/anchored.test.d.ts +1 -0
- package/dist/fancy-ui/_internals/motion/anchored.test.js +250 -0
- package/dist/fancy-ui/_internals/motion/haptics.d.ts +38 -0
- package/dist/fancy-ui/_internals/motion/haptics.js +59 -0
- package/dist/fancy-ui/_internals/motion/haptics.test.d.ts +1 -0
- package/dist/fancy-ui/_internals/motion/haptics.test.js +70 -0
- package/dist/fancy-ui/_internals/motion/in-view.d.ts +24 -0
- package/dist/fancy-ui/_internals/motion/in-view.js +99 -0
- package/dist/fancy-ui/_internals/motion/in-view.test.d.ts +1 -0
- package/dist/fancy-ui/_internals/motion/in-view.test.js +149 -0
- package/dist/fancy-ui/_internals/motion/media-query.svelte.d.ts +26 -0
- package/dist/fancy-ui/_internals/motion/media-query.svelte.js +69 -0
- package/dist/fancy-ui/_internals/motion/media-query.test.d.ts +1 -0
- package/dist/fancy-ui/_internals/motion/media-query.test.js +175 -0
- package/dist/fancy-ui/_internals/motion/presets.d.ts +34 -0
- package/dist/fancy-ui/_internals/motion/presets.js +33 -0
- package/dist/fancy-ui/_internals/motion/presets.test.d.ts +1 -0
- package/dist/fancy-ui/_internals/motion/presets.test.js +48 -0
- package/dist/fancy-ui/_internals/motion/raf.d.ts +35 -0
- package/dist/fancy-ui/_internals/motion/raf.js +54 -0
- package/dist/fancy-ui/_internals/motion/raf.test.d.ts +1 -0
- package/dist/fancy-ui/_internals/motion/raf.test.js +104 -0
- package/dist/fancy-ui/_internals/motion/stagger.d.ts +32 -0
- package/dist/fancy-ui/_internals/motion/stagger.js +51 -0
- package/dist/fancy-ui/_internals/motion/stagger.test.d.ts +1 -0
- package/dist/fancy-ui/_internals/motion/stagger.test.js +58 -0
- package/dist/fancy-ui/_internals/motion/tokens.d.ts +97 -0
- package/dist/fancy-ui/_internals/motion/tokens.js +83 -0
- package/dist/fancy-ui/_internals/motion/tokens.test.d.ts +1 -0
- package/dist/fancy-ui/_internals/motion/tokens.test.js +28 -0
- package/dist/fancy-ui/_internals/motion/transitions.d.ts +52 -0
- package/dist/fancy-ui/_internals/motion/transitions.js +91 -0
- package/dist/fancy-ui/_internals/motion/transitions.test.d.ts +1 -0
- package/dist/fancy-ui/_internals/motion/transitions.test.js +143 -0
- package/dist/fancy-ui/_internals/motion/types.d.ts +17 -0
- package/dist/fancy-ui/_internals/motion/types.js +15 -0
- package/dist/fancy-ui/_internals/motion/types.test.d.ts +1 -0
- package/dist/fancy-ui/_internals/motion/types.test.js +19 -0
- package/dist/fancy-ui/_internals/scroll-lock.d.ts +21 -0
- package/dist/fancy-ui/_internals/scroll-lock.js +20 -0
- package/dist/fancy-ui/_internals/scroll-lock.test.js +82 -1
- package/dist/fancy-ui/agent-plan/AgentPlan.svelte +16 -1
- package/dist/fancy-ui/agent-plan/AgentPlan.svelte.d.ts +5 -0
- package/dist/fancy-ui/agent-plan/AgentPlan.test.js +55 -1
- package/dist/fancy-ui/agent-plan/README.md +11 -0
- package/dist/fancy-ui/alert-dialog/AlertDialog.svelte +20 -0
- package/dist/fancy-ui/alert-dialog/AlertDialog.svelte.d.ts +5 -0
- package/dist/fancy-ui/alert-dialog/AlertDialog.test.js +145 -2
- package/dist/fancy-ui/alert-dialog/README.md +45 -14
- package/dist/fancy-ui/animated-beam/AnimatedBeam.svelte +4 -0
- package/dist/fancy-ui/animated-beam/AnimatedBeam.test.js +16 -0
- package/dist/fancy-ui/animated-beam/README.md +1 -0
- package/dist/fancy-ui/animated-testimonials/AnimatedTestimonials.svelte +13 -3
- package/dist/fancy-ui/animated-testimonials/AnimatedTestimonials.svelte.d.ts +5 -0
- package/dist/fancy-ui/animated-testimonials/AnimatedTestimonials.test.js +70 -0
- package/dist/fancy-ui/animated-testimonials/README.md +17 -6
- package/dist/fancy-ui/animated-tooltip/AnimatedTooltip.svelte +21 -1
- package/dist/fancy-ui/animated-tooltip/AnimatedTooltip.test.js +25 -1
- package/dist/fancy-ui/animated-tooltip/README.md +1 -0
- package/dist/fancy-ui/apple-card-carousel/AppleCard.svelte +20 -0
- package/dist/fancy-ui/apple-card-carousel/AppleCard.svelte.d.ts +5 -0
- package/dist/fancy-ui/apple-card-carousel/AppleCardCarousel.svelte +7 -1
- package/dist/fancy-ui/apple-card-carousel/AppleCardCarousel.svelte.d.ts +5 -0
- package/dist/fancy-ui/apple-card-carousel/AppleCardCarousel.test.js +111 -1
- package/dist/fancy-ui/apple-card-carousel/README.md +16 -5
- package/dist/fancy-ui/approval-card/ApprovalCard.svelte +10 -0
- package/dist/fancy-ui/approval-card/ApprovalCard.svelte.d.ts +5 -0
- package/dist/fancy-ui/approval-card/ApprovalCard.test.js +51 -1
- package/dist/fancy-ui/approval-card/README.md +30 -14
- package/dist/fancy-ui/artifact-card/ArtifactCard.svelte +19 -1
- package/dist/fancy-ui/artifact-card/ArtifactCard.svelte.d.ts +5 -0
- package/dist/fancy-ui/artifact-card/ArtifactCard.test.js +80 -1
- package/dist/fancy-ui/artifact-card/README.md +24 -13
- package/dist/fancy-ui/autocomplete/Autocomplete.svelte +16 -0
- package/dist/fancy-ui/autocomplete/Autocomplete.svelte.d.ts +5 -0
- package/dist/fancy-ui/autocomplete/Autocomplete.test.js +245 -1
- package/dist/fancy-ui/autocomplete/AutocompletePanel.svelte +60 -3
- package/dist/fancy-ui/autocomplete/AutocompletePanel.svelte.d.ts +2 -17
- package/dist/fancy-ui/autocomplete/README.md +74 -17
- package/dist/fancy-ui/autocomplete/types.d.ts +8 -0
- package/dist/fancy-ui/breadcrumb/Breadcrumb.svelte +16 -0
- package/dist/fancy-ui/breadcrumb/Breadcrumb.svelte.d.ts +6 -0
- package/dist/fancy-ui/breadcrumb/Breadcrumb.test.js +43 -2
- package/dist/fancy-ui/breadcrumb/README.md +11 -0
- package/dist/fancy-ui/button/Button.svelte +174 -9
- package/dist/fancy-ui/button/Button.test.js +117 -3
- package/dist/fancy-ui/button/ButtonLeadHarness.test.svelte +32 -0
- package/dist/fancy-ui/button/ButtonLeadHarness.test.svelte.d.ts +3 -0
- package/dist/fancy-ui/button/README.md +13 -2
- package/dist/fancy-ui/chat-error/ChatError.svelte +19 -1
- package/dist/fancy-ui/chat-error/ChatError.svelte.d.ts +6 -0
- package/dist/fancy-ui/chat-error/ChatError.test.js +40 -0
- package/dist/fancy-ui/chat-error/README.md +11 -0
- package/dist/fancy-ui/chat-message/ChatMessage.svelte +13 -0
- package/dist/fancy-ui/chat-message/ChatMessage.svelte.d.ts +9 -0
- package/dist/fancy-ui/chat-message/ChatMessageAction.svelte +10 -0
- package/dist/fancy-ui/chat-message/ChatMessageBranches.svelte +14 -3
- package/dist/fancy-ui/chat-message/ChatMessageHarness.test.svelte +3 -1
- package/dist/fancy-ui/chat-message/ChatMessageHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/chat-message/ChatMessageParts.test.js +68 -0
- package/dist/fancy-ui/chat-message/README.md +25 -0
- package/dist/fancy-ui/chat-message/types.d.ts +7 -0
- package/dist/fancy-ui/chat-panel/ChatPanel.svelte +9 -0
- package/dist/fancy-ui/chat-panel/ChatPanel.svelte.d.ts +6 -0
- package/dist/fancy-ui/chat-panel/ChatPanel.test.js +46 -0
- package/dist/fancy-ui/chat-panel/README.md +15 -0
- package/dist/fancy-ui/checkbox/Checkbox.svelte +139 -42
- package/dist/fancy-ui/checkbox/Checkbox.test.js +108 -0
- package/dist/fancy-ui/checkbox/README.md +70 -11
- package/dist/fancy-ui/code-diff/CodeDiff.svelte +23 -2
- package/dist/fancy-ui/code-diff/CodeDiff.svelte.d.ts +5 -0
- package/dist/fancy-ui/code-diff/CodeDiff.test.js +61 -1
- package/dist/fancy-ui/code-diff/README.md +21 -10
- package/dist/fancy-ui/combobox/Combobox.svelte +16 -0
- package/dist/fancy-ui/combobox/Combobox.svelte.d.ts +5 -0
- package/dist/fancy-ui/combobox/Combobox.test.js +210 -0
- package/dist/fancy-ui/combobox/ComboboxPanel.svelte +57 -3
- package/dist/fancy-ui/combobox/ComboboxPanel.svelte.d.ts +2 -17
- package/dist/fancy-ui/combobox/README.md +52 -0
- package/dist/fancy-ui/combobox/types.d.ts +8 -0
- package/dist/fancy-ui/command-menu/CommandMenu.svelte +122 -47
- package/dist/fancy-ui/command-menu/CommandMenu.svelte.d.ts +5 -0
- package/dist/fancy-ui/command-menu/CommandMenu.test.js +242 -4
- package/dist/fancy-ui/command-menu/README.md +43 -2
- package/dist/fancy-ui/composer/Composer.svelte +14 -0
- package/dist/fancy-ui/composer/Composer.svelte.d.ts +5 -0
- package/dist/fancy-ui/composer/Composer.test.js +107 -1
- package/dist/fancy-ui/composer/ComposerAttachment.svelte +7 -0
- package/dist/fancy-ui/composer/ComposerAttachments.test.js +49 -1
- package/dist/fancy-ui/composer/ComposerCommandMenu.svelte +5 -0
- package/dist/fancy-ui/composer/ComposerCommandMenu.test.js +57 -2
- package/dist/fancy-ui/composer/ComposerHarness.test.svelte +3 -0
- package/dist/fancy-ui/composer/ComposerHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/composer/ComposerModelPicker.svelte +14 -2
- package/dist/fancy-ui/composer/ComposerToolbar.test.js +64 -2
- package/dist/fancy-ui/composer/README.md +31 -0
- package/dist/fancy-ui/composer/types.d.ts +6 -0
- package/dist/fancy-ui/confetti/Confetti.test.js +55 -11
- package/dist/fancy-ui/confetti/ConfettiButton.svelte +8 -1
- package/dist/fancy-ui/confetti/ConfettiButton.svelte.d.ts +5 -0
- package/dist/fancy-ui/confetti/README.md +22 -9
- package/dist/fancy-ui/context-menu/ContextMenu.svelte +27 -3
- package/dist/fancy-ui/context-menu/ContextMenu.svelte.d.ts +5 -0
- package/dist/fancy-ui/context-menu/ContextMenu.test.js +324 -0
- package/dist/fancy-ui/context-menu/ContextMenuContent.svelte +77 -22
- package/dist/fancy-ui/context-menu/ContextMenuHarness.test.svelte +3 -1
- package/dist/fancy-ui/context-menu/ContextMenuHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/context-menu/README.md +86 -2
- package/dist/fancy-ui/context-menu/types.d.ts +5 -3
- package/dist/fancy-ui/context-ring/ContextRing.svelte +24 -3
- package/dist/fancy-ui/context-ring/ContextRing.svelte.d.ts +5 -0
- package/dist/fancy-ui/context-ring/ContextRing.test.js +75 -1
- package/dist/fancy-ui/context-ring/README.md +11 -0
- package/dist/fancy-ui/copy-button/CopyButton.svelte +171 -52
- package/dist/fancy-ui/copy-button/CopyButton.svelte.d.ts +6 -4
- package/dist/fancy-ui/copy-button/CopyButton.test.js +237 -16
- package/dist/fancy-ui/copy-button/README.md +111 -44
- package/dist/fancy-ui/date-picker/DatePicker.svelte +74 -22
- package/dist/fancy-ui/date-picker/DatePicker.svelte.d.ts +5 -0
- package/dist/fancy-ui/date-picker/DatePicker.test.js +230 -0
- package/dist/fancy-ui/date-picker/README.md +50 -0
- package/dist/fancy-ui/dialog/Dialog.svelte +16 -0
- package/dist/fancy-ui/dialog/Dialog.svelte.d.ts +5 -0
- package/dist/fancy-ui/dialog/Dialog.test.js +234 -3
- package/dist/fancy-ui/dialog/DialogSurface.svelte +101 -48
- package/dist/fancy-ui/dialog/README.md +43 -16
- package/dist/fancy-ui/dim-siblings/DimSiblings.svelte +184 -0
- package/dist/fancy-ui/dim-siblings/DimSiblings.svelte.d.ts +35 -0
- package/dist/fancy-ui/dim-siblings/DimSiblings.test.d.ts +1 -0
- package/dist/fancy-ui/dim-siblings/DimSiblings.test.js +127 -0
- package/dist/fancy-ui/dim-siblings/DimSiblingsHarness.test.svelte +18 -0
- package/dist/fancy-ui/dim-siblings/DimSiblingsHarness.test.svelte.d.ts +18 -0
- package/dist/fancy-ui/dim-siblings/README.md +77 -0
- package/dist/fancy-ui/dim-siblings/index.d.ts +3 -0
- package/dist/fancy-ui/dim-siblings/index.js +2 -0
- package/dist/fancy-ui/displacement-text/DisplacementText.svelte +6 -1
- package/dist/fancy-ui/displacement-text/DisplacementText.test.js +5 -1
- package/dist/fancy-ui/displacement-text/README.md +1 -0
- package/dist/fancy-ui/dock/Dock.svelte +44 -4
- package/dist/fancy-ui/dock/Dock.test.js +133 -2
- package/dist/fancy-ui/dock/DockHarness.test.svelte +36 -0
- package/dist/fancy-ui/dock/DockHarness.test.svelte.d.ts +8 -0
- package/dist/fancy-ui/dock/DockIcon.svelte +6 -0
- package/dist/fancy-ui/dock/README.md +49 -22
- package/dist/fancy-ui/dock/types.d.ts +8 -0
- package/dist/fancy-ui/drawer/Drawer.svelte +176 -50
- package/dist/fancy-ui/drawer/Drawer.svelte.d.ts +12 -0
- package/dist/fancy-ui/drawer/Drawer.test.js +228 -10
- package/dist/fancy-ui/drawer/DrawerHarness.test.svelte +6 -2
- package/dist/fancy-ui/drawer/DrawerHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/drawer/README.md +70 -26
- package/dist/fancy-ui/dropdown-menu/DropdownMenu.test.js +295 -8
- package/dist/fancy-ui/dropdown-menu/DropdownMenuContent.svelte +67 -21
- package/dist/fancy-ui/dropdown-menu/DropdownMenuSub.svelte +14 -3
- package/dist/fancy-ui/dropdown-menu/DropdownMenuSubContent.svelte +68 -20
- package/dist/fancy-ui/dropdown-menu/DropdownMenuSubTrigger.svelte +1 -0
- package/dist/fancy-ui/dropdown-menu/README.md +80 -5
- package/dist/fancy-ui/dropdown-menu/types.d.ts +26 -5
- package/dist/fancy-ui/editorial-engine/EditorialEngine.svelte +37 -15
- package/dist/fancy-ui/editorial-engine/EditorialEngine.test.js +34 -2
- package/dist/fancy-ui/editorial-engine/README.md +9 -0
- package/dist/fancy-ui/file-upload/FileUpload.svelte +137 -15
- package/dist/fancy-ui/file-upload/FileUpload.svelte.d.ts +5 -0
- package/dist/fancy-ui/file-upload/FileUpload.test.js +302 -1
- package/dist/fancy-ui/file-upload/README.md +69 -5
- package/dist/fancy-ui/fireworks-hdr/FireworksHdr.svelte +12 -2
- package/dist/fancy-ui/fireworks-hdr/FireworksHdr.test.js +70 -0
- package/dist/fancy-ui/fireworks-hdr/README.md +4 -2
- package/dist/fancy-ui/fluid-cursor/FluidCursor.svelte +97 -11
- package/dist/fancy-ui/fluid-cursor/README.md +3 -1
- package/dist/fancy-ui/fluid-cursor/fluid-shared.d.ts +18 -0
- package/dist/fancy-ui/fluid-cursor/fluid-shared.js +22 -0
- package/dist/fancy-ui/fluid-cursor/fluid-shared.test.js +17 -1
- package/dist/fancy-ui/fluid-cursor/webgpu-engine.d.ts +6 -0
- package/dist/fancy-ui/fluid-cursor/webgpu-engine.js +32 -9
- package/dist/fancy-ui/form-field/FormField.svelte +38 -2
- package/dist/fancy-ui/form-field/FormField.test.js +118 -1
- package/dist/fancy-ui/form-field/README.md +26 -0
- package/dist/fancy-ui/gradient-button/GradientButton.svelte +14 -0
- package/dist/fancy-ui/gradient-button/GradientButton.svelte.d.ts +5 -0
- package/dist/fancy-ui/gradient-button/GradientButton.test.js +40 -2
- package/dist/fancy-ui/gradient-button/README.md +10 -0
- package/dist/fancy-ui/hover-card/HoverCard.svelte +76 -29
- package/dist/fancy-ui/hover-card/HoverCard.test.js +227 -8
- package/dist/fancy-ui/hover-card/README.md +47 -1
- package/dist/fancy-ui/icon-button/IconButton.svelte +7 -0
- package/dist/fancy-ui/icon-button/IconButton.svelte.d.ts +5 -0
- package/dist/fancy-ui/icon-button/IconButton.test.js +40 -1
- package/dist/fancy-ui/icon-button/README.md +13 -0
- package/dist/fancy-ui/image-generation/ImageGeneration.svelte +18 -1
- package/dist/fancy-ui/image-generation/ImageGeneration.svelte.d.ts +6 -0
- package/dist/fancy-ui/image-generation/ImageGeneration.test.js +46 -0
- package/dist/fancy-ui/image-generation/README.md +11 -0
- package/dist/fancy-ui/index.d.ts +13 -0
- package/dist/fancy-ui/index.js +14 -0
- package/dist/fancy-ui/interactive-hover-button/InteractiveHoverButton.svelte +26 -3
- package/dist/fancy-ui/interactive-hover-button/InteractiveHoverButton.svelte.d.ts +5 -0
- package/dist/fancy-ui/interactive-hover-button/InteractiveHoverButton.test.js +69 -2
- package/dist/fancy-ui/interactive-hover-button/README.md +30 -5
- package/dist/fancy-ui/line-hover-link/LineHoverLink.svelte +181 -51
- package/dist/fancy-ui/line-hover-link/LineHoverLink.svelte.d.ts +5 -0
- package/dist/fancy-ui/line-hover-link/LineHoverLink.test.js +69 -2
- package/dist/fancy-ui/line-hover-link/README.md +42 -8
- package/dist/fancy-ui/line-reveal/LineReveal.svelte +10 -6
- package/dist/fancy-ui/line-reveal/LineReveal.test.js +20 -16
- package/dist/fancy-ui/link/Link.svelte +54 -1
- package/dist/fancy-ui/link/Link.svelte.d.ts +5 -0
- package/dist/fancy-ui/link/Link.test.js +67 -1
- package/dist/fancy-ui/link/README.md +35 -0
- package/dist/fancy-ui/magnetic/Magnetic.svelte +337 -0
- package/dist/fancy-ui/magnetic/Magnetic.svelte.d.ts +40 -0
- package/dist/fancy-ui/magnetic/Magnetic.test.d.ts +1 -0
- package/dist/fancy-ui/magnetic/Magnetic.test.js +354 -0
- package/dist/fancy-ui/magnetic/README.md +175 -0
- package/dist/fancy-ui/magnetic/index.d.ts +2 -0
- package/dist/fancy-ui/magnetic/index.js +2 -0
- package/dist/fancy-ui/mosaic-glow/MosaicGlow.ssr.test.d.ts +1 -0
- package/dist/fancy-ui/mosaic-glow/MosaicGlow.ssr.test.js +22 -0
- package/dist/fancy-ui/mosaic-glow/MosaicGlow.svelte +528 -0
- package/dist/fancy-ui/mosaic-glow/MosaicGlow.svelte.d.ts +53 -0
- package/dist/fancy-ui/mosaic-glow/MosaicGlow.test.d.ts +1 -0
- package/dist/fancy-ui/mosaic-glow/MosaicGlow.test.js +319 -0
- package/dist/fancy-ui/mosaic-glow/README.md +60 -0
- package/dist/fancy-ui/mosaic-glow/index.d.ts +2 -0
- package/dist/fancy-ui/mosaic-glow/index.js +2 -0
- package/dist/fancy-ui/mosaic-glow/mosaic-glow-core.d.ts +87 -0
- package/dist/fancy-ui/mosaic-glow/mosaic-glow-core.js +221 -0
- package/dist/fancy-ui/mosaic-glow/mosaic-glow-core.test.d.ts +1 -0
- package/dist/fancy-ui/mosaic-glow/mosaic-glow-core.test.js +201 -0
- package/dist/fancy-ui/navbar/Navbar.test.js +76 -1
- package/dist/fancy-ui/navbar/NavbarLink.svelte +55 -1
- package/dist/fancy-ui/navbar/NavbarLink.svelte.d.ts +5 -0
- package/dist/fancy-ui/navbar/README.md +36 -0
- package/dist/fancy-ui/navigation-menu/NavigationMenu.test.js +151 -12
- package/dist/fancy-ui/navigation-menu/NavigationMenuContent.svelte +74 -26
- package/dist/fancy-ui/navigation-menu/README.md +58 -0
- package/dist/fancy-ui/number-input/NumberInput.svelte +110 -3
- package/dist/fancy-ui/number-input/NumberInput.svelte.d.ts +5 -0
- package/dist/fancy-ui/number-input/NumberInput.test.js +234 -0
- package/dist/fancy-ui/number-input/README.md +65 -0
- package/dist/fancy-ui/number-ticker/NumberTicker.svelte +18 -3
- package/dist/fancy-ui/number-ticker/NumberTicker.test.js +94 -0
- package/dist/fancy-ui/pagination/Pagination.svelte +63 -1
- package/dist/fancy-ui/pagination/Pagination.svelte.d.ts +5 -0
- package/dist/fancy-ui/pagination/Pagination.test.js +90 -2
- package/dist/fancy-ui/pagination/PaginationHarness.test.svelte +3 -0
- package/dist/fancy-ui/pagination/PaginationHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/pagination/README.md +38 -0
- package/dist/fancy-ui/password-input/PasswordInput.svelte +109 -9
- package/dist/fancy-ui/password-input/PasswordInput.svelte.d.ts +5 -0
- package/dist/fancy-ui/password-input/PasswordInput.test.js +187 -0
- package/dist/fancy-ui/password-input/README.md +52 -0
- package/dist/fancy-ui/popover/Popover.svelte +17 -0
- package/dist/fancy-ui/popover/Popover.svelte.d.ts +5 -0
- package/dist/fancy-ui/popover/Popover.test.js +361 -2
- package/dist/fancy-ui/popover/PopoverContent.svelte +86 -22
- package/dist/fancy-ui/popover/README.md +70 -12
- package/dist/fancy-ui/popover/types.d.ts +11 -0
- package/dist/fancy-ui/presence/Presence.svelte +162 -0
- package/dist/fancy-ui/presence/Presence.svelte.d.ts +29 -0
- package/dist/fancy-ui/presence/Presence.test.d.ts +1 -0
- package/dist/fancy-ui/presence/Presence.test.js +274 -0
- package/dist/fancy-ui/presence/PresenceHarness.test.svelte +23 -0
- package/dist/fancy-ui/presence/PresenceHarness.test.svelte.d.ts +7 -0
- package/dist/fancy-ui/presence/README.md +121 -0
- package/dist/fancy-ui/presence/index.d.ts +2 -0
- package/dist/fancy-ui/presence/index.js +1 -0
- package/dist/fancy-ui/pressable/Pressable.svelte +217 -0
- package/dist/fancy-ui/pressable/Pressable.svelte.d.ts +32 -0
- package/dist/fancy-ui/pressable/Pressable.test.d.ts +1 -0
- package/dist/fancy-ui/pressable/Pressable.test.js +285 -0
- package/dist/fancy-ui/pressable/README.md +80 -0
- package/dist/fancy-ui/pressable/index.d.ts +3 -0
- package/dist/fancy-ui/pressable/index.js +2 -0
- package/dist/fancy-ui/prompt-suggestions/PromptSuggestions.svelte +14 -1
- package/dist/fancy-ui/prompt-suggestions/PromptSuggestions.svelte.d.ts +5 -0
- package/dist/fancy-ui/prompt-suggestions/PromptSuggestions.test.js +41 -1
- package/dist/fancy-ui/prompt-suggestions/README.md +11 -0
- package/dist/fancy-ui/pulse-beam/PulseBeam.ssr.test.d.ts +1 -0
- package/dist/fancy-ui/pulse-beam/PulseBeam.ssr.test.js +15 -0
- package/dist/fancy-ui/pulse-beam/PulseBeam.svelte +373 -0
- package/dist/fancy-ui/pulse-beam/PulseBeam.svelte.d.ts +50 -0
- package/dist/fancy-ui/pulse-beam/PulseBeam.test.d.ts +1 -0
- package/dist/fancy-ui/pulse-beam/PulseBeam.test.js +254 -0
- package/dist/fancy-ui/pulse-beam/README.md +72 -0
- package/dist/fancy-ui/pulse-beam/index.d.ts +2 -0
- package/dist/fancy-ui/pulse-beam/index.js +2 -0
- package/dist/fancy-ui/pulse-beam/pulse-beam-data.d.ts +81 -0
- package/dist/fancy-ui/pulse-beam/pulse-beam-data.js +331 -0
- package/dist/fancy-ui/pulse-beam/pulse-beam-data.test.d.ts +1 -0
- package/dist/fancy-ui/pulse-beam/pulse-beam-data.test.js +155 -0
- package/dist/fancy-ui/pulse-beam/pulse-beam-loop.d.ts +26 -0
- package/dist/fancy-ui/pulse-beam/pulse-beam-loop.js +84 -0
- package/dist/fancy-ui/radio-group/README.md +27 -1
- package/dist/fancy-ui/radio-group/RadioGroup.test.js +43 -0
- package/dist/fancy-ui/radio-group/RadioGroupItem.svelte +31 -2
- package/dist/fancy-ui/rainbow-button/README.md +23 -9
- package/dist/fancy-ui/rainbow-button/RainbowButton.svelte +25 -1
- package/dist/fancy-ui/rainbow-button/RainbowButton.svelte.d.ts +5 -0
- package/dist/fancy-ui/rainbow-button/RainbowButton.test.js +49 -2
- package/dist/fancy-ui/reasoning-panel/README.md +11 -0
- package/dist/fancy-ui/reasoning-panel/ReasoningPanel.svelte +13 -1
- package/dist/fancy-ui/reasoning-panel/ReasoningPanel.svelte.d.ts +5 -0
- package/dist/fancy-ui/reasoning-panel/ReasoningPanel.test.js +47 -1
- package/dist/fancy-ui/recommendation-card/README.md +25 -14
- package/dist/fancy-ui/recommendation-card/RecommendationCard.svelte +10 -0
- package/dist/fancy-ui/recommendation-card/RecommendationCard.svelte.d.ts +5 -0
- package/dist/fancy-ui/recommendation-card/RecommendationCard.test.js +39 -1
- package/dist/fancy-ui/registry.d.ts +7 -0
- package/dist/fancy-ui/registry.js +1278 -99
- package/dist/fancy-ui/registry.test.d.ts +1 -0
- package/dist/fancy-ui/registry.test.js +32 -0
- package/dist/fancy-ui/reveal/README.md +186 -0
- package/dist/fancy-ui/reveal/Reveal.svelte +353 -0
- package/dist/fancy-ui/reveal/Reveal.svelte.d.ts +41 -0
- package/dist/fancy-ui/reveal/Reveal.test.d.ts +1 -0
- package/dist/fancy-ui/reveal/Reveal.test.js +421 -0
- package/dist/fancy-ui/reveal/RevealHarness.test.svelte +49 -0
- package/dist/fancy-ui/reveal/RevealHarness.test.svelte.d.ts +9 -0
- package/dist/fancy-ui/reveal/index.d.ts +2 -0
- package/dist/fancy-ui/reveal/index.js +1 -0
- package/dist/fancy-ui/ripple-button/README.md +17 -6
- package/dist/fancy-ui/ripple-button/RippleButton.svelte +10 -1
- package/dist/fancy-ui/ripple-button/RippleButton.svelte.d.ts +5 -0
- package/dist/fancy-ui/ripple-button/RippleButton.test.js +81 -2
- package/dist/fancy-ui/scroll-anchor/README.md +11 -0
- package/dist/fancy-ui/scroll-anchor/ScrollAnchor.svelte +9 -0
- package/dist/fancy-ui/scroll-anchor/ScrollAnchor.svelte.d.ts +6 -0
- package/dist/fancy-ui/scroll-anchor/ScrollAnchor.test.js +39 -0
- package/dist/fancy-ui/scroll-progress/README.md +73 -0
- package/dist/fancy-ui/scroll-progress/ScrollProgress.svelte +291 -0
- package/dist/fancy-ui/scroll-progress/ScrollProgress.svelte.d.ts +38 -0
- package/dist/fancy-ui/scroll-progress/ScrollProgress.test.d.ts +1 -0
- package/dist/fancy-ui/scroll-progress/ScrollProgress.test.js +275 -0
- package/dist/fancy-ui/scroll-progress/index.d.ts +3 -0
- package/dist/fancy-ui/scroll-progress/index.js +2 -0
- package/dist/fancy-ui/search-input/README.md +44 -0
- package/dist/fancy-ui/search-input/SearchInput.svelte +31 -0
- package/dist/fancy-ui/search-input/SearchInput.svelte.d.ts +5 -0
- package/dist/fancy-ui/search-input/SearchInput.test.js +131 -0
- package/dist/fancy-ui/select/README.md +32 -0
- package/dist/fancy-ui/select/Select.svelte +6 -0
- package/dist/fancy-ui/select/Select.test.js +162 -4
- package/dist/fancy-ui/select/SelectPanel.svelte +56 -18
- package/dist/fancy-ui/select/types.d.ts +14 -0
- package/dist/fancy-ui/sheet/README.md +63 -27
- package/dist/fancy-ui/sheet/Sheet.svelte +150 -85
- package/dist/fancy-ui/sheet/Sheet.svelte.d.ts +12 -0
- package/dist/fancy-ui/sheet/Sheet.test.js +192 -8
- package/dist/fancy-ui/sheet/SheetHarness.test.svelte +3 -2
- package/dist/fancy-ui/sheet/SheetHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/shimmer-button/README.md +10 -0
- package/dist/fancy-ui/shimmer-button/ShimmerButton.svelte +14 -0
- package/dist/fancy-ui/shimmer-button/ShimmerButton.svelte.d.ts +5 -0
- package/dist/fancy-ui/shimmer-button/ShimmerButton.test.js +39 -2
- package/dist/fancy-ui/sidebar/README.md +42 -0
- package/dist/fancy-ui/sidebar/Sidebar.svelte +14 -1
- package/dist/fancy-ui/sidebar/Sidebar.test.js +94 -1
- package/dist/fancy-ui/sidebar/SidebarItem.svelte +51 -1
- package/dist/fancy-ui/sidebar/SidebarItem.svelte.d.ts +5 -0
- package/dist/fancy-ui/skeleton/README.md +166 -0
- package/dist/fancy-ui/skeleton/Skeleton.svelte +390 -0
- package/dist/fancy-ui/skeleton/Skeleton.svelte.d.ts +38 -0
- package/dist/fancy-ui/skeleton/Skeleton.test.d.ts +1 -0
- package/dist/fancy-ui/skeleton/Skeleton.test.js +347 -0
- package/dist/fancy-ui/skeleton/SkeletonRevealHarness.test.svelte +48 -0
- package/dist/fancy-ui/skeleton/SkeletonRevealHarness.test.svelte.d.ts +11 -0
- package/dist/fancy-ui/skeleton/index.d.ts +3 -0
- package/dist/fancy-ui/skeleton/index.js +2 -0
- package/dist/fancy-ui/slider/README.md +19 -0
- package/dist/fancy-ui/slider/Slider.svelte +19 -0
- package/dist/fancy-ui/slider/Slider.svelte.d.ts +5 -0
- package/dist/fancy-ui/slider/Slider.test.js +37 -0
- package/dist/fancy-ui/smooth-cursor/SmoothCursor.svelte +2 -1
- package/dist/fancy-ui/smooth-cursor/SmoothCursor.test.js +6 -0
- package/dist/fancy-ui/sound/README.md +72 -13
- package/dist/fancy-ui/sources/README.md +12 -1
- package/dist/fancy-ui/sources/Sources.svelte +9 -0
- package/dist/fancy-ui/sources/Sources.svelte.d.ts +6 -0
- package/dist/fancy-ui/sources/Sources.test.js +34 -0
- package/dist/fancy-ui/sources/SourcesHarness.test.svelte +10 -2
- package/dist/fancy-ui/sources/SourcesHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/sparkles/Sparkles.svelte +6 -3
- package/dist/fancy-ui/sparkles/Sparkles.test.js +51 -0
- package/dist/fancy-ui/status-morph/README.md +175 -0
- package/dist/fancy-ui/status-morph/StatusMorph.svelte +449 -0
- package/dist/fancy-ui/status-morph/StatusMorph.svelte.d.ts +45 -0
- package/dist/fancy-ui/status-morph/StatusMorph.test.d.ts +1 -0
- package/dist/fancy-ui/status-morph/StatusMorph.test.js +243 -0
- package/dist/fancy-ui/status-morph/StatusMorphHarness.test.svelte +33 -0
- package/dist/fancy-ui/status-morph/StatusMorphHarness.test.svelte.d.ts +7 -0
- package/dist/fancy-ui/status-morph/index.d.ts +3 -0
- package/dist/fancy-ui/status-morph/index.js +2 -0
- package/dist/fancy-ui/stepper/README.md +40 -0
- package/dist/fancy-ui/stepper/Step.svelte +26 -0
- package/dist/fancy-ui/stepper/Stepper.svelte +9 -0
- package/dist/fancy-ui/stepper/Stepper.svelte.d.ts +5 -0
- package/dist/fancy-ui/stepper/Stepper.test.js +98 -1
- package/dist/fancy-ui/stepper/StepperHarness.test.svelte +11 -1
- package/dist/fancy-ui/stepper/StepperHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/sticky-scroll/README.md +91 -0
- package/dist/fancy-ui/sticky-scroll/StickyScroll.svelte +255 -0
- package/dist/fancy-ui/sticky-scroll/StickyScroll.svelte.d.ts +62 -0
- package/dist/fancy-ui/sticky-scroll/StickyScroll.test.d.ts +1 -0
- package/dist/fancy-ui/sticky-scroll/StickyScroll.test.js +267 -0
- package/dist/fancy-ui/sticky-scroll/StickyScrollHarness.test.svelte +73 -0
- package/dist/fancy-ui/sticky-scroll/StickyScrollHarness.test.svelte.d.ts +12 -0
- package/dist/fancy-ui/sticky-scroll/index.d.ts +3 -0
- package/dist/fancy-ui/sticky-scroll/index.js +2 -0
- package/dist/fancy-ui/subagent-list/README.md +11 -0
- package/dist/fancy-ui/subagent-list/SubagentList.svelte +15 -1
- package/dist/fancy-ui/subagent-list/SubagentList.svelte.d.ts +5 -0
- package/dist/fancy-ui/subagent-list/SubagentList.test.js +50 -1
- package/dist/fancy-ui/switch/README.md +27 -6
- package/dist/fancy-ui/switch/Switch.svelte +18 -4
- package/dist/fancy-ui/switch/Switch.test.js +41 -0
- package/dist/fancy-ui/tabs/README.md +141 -11
- package/dist/fancy-ui/tabs/Tabs.svelte +9 -0
- package/dist/fancy-ui/tabs/Tabs.svelte.d.ts +5 -0
- package/dist/fancy-ui/tabs/Tabs.test.js +407 -2
- package/dist/fancy-ui/tabs/TabsContent.svelte +34 -0
- package/dist/fancy-ui/tabs/TabsHarness.test.svelte +3 -1
- package/dist/fancy-ui/tabs/TabsHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/tabs/TabsList.svelte +330 -0
- package/dist/fancy-ui/tabs/TabsTrigger.svelte +46 -23
- package/dist/fancy-ui/terminal-text/README.md +1 -1
- package/dist/fancy-ui/terminal-text/TerminalText.svelte +31 -2
- package/dist/fancy-ui/terminal-text/TerminalText.test.js +70 -0
- package/dist/fancy-ui/terminal-text/TerminalTextHarness.test.svelte +36 -0
- package/dist/fancy-ui/terminal-text/TerminalTextHarness.test.svelte.d.ts +6 -0
- package/dist/fancy-ui/text-roll/README.md +152 -0
- package/dist/fancy-ui/text-roll/TextRoll.svelte +455 -0
- package/dist/fancy-ui/text-roll/TextRoll.svelte.d.ts +53 -0
- package/dist/fancy-ui/text-roll/TextRoll.test.d.ts +1 -0
- package/dist/fancy-ui/text-roll/TextRoll.test.js +574 -0
- package/dist/fancy-ui/text-roll/TextRollHarness.test.svelte +49 -0
- package/dist/fancy-ui/text-roll/TextRollHarness.test.svelte.d.ts +7 -0
- package/dist/fancy-ui/text-roll/index.d.ts +2 -0
- package/dist/fancy-ui/text-roll/index.js +2 -0
- package/dist/fancy-ui/text-roll/roll-transitions.d.ts +74 -0
- package/dist/fancy-ui/text-roll/roll-transitions.js +66 -0
- package/dist/fancy-ui/thread-list/README.md +11 -0
- package/dist/fancy-ui/thread-list/ThreadList.svelte +15 -1
- package/dist/fancy-ui/thread-list/ThreadList.svelte.d.ts +6 -0
- package/dist/fancy-ui/thread-list/ThreadList.test.js +43 -0
- package/dist/fancy-ui/thread-list/ThreadListHarness.test.svelte +3 -0
- package/dist/fancy-ui/thread-list/ThreadListHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/time-picker/README.md +51 -1
- package/dist/fancy-ui/time-picker/TimePicker.svelte +47 -10
- package/dist/fancy-ui/time-picker/TimePicker.svelte.d.ts +5 -0
- package/dist/fancy-ui/time-picker/TimePicker.test.js +254 -1
- package/dist/fancy-ui/time-picker/TimePickerPanel.svelte +55 -20
- package/dist/fancy-ui/time-picker/types.d.ts +8 -0
- package/dist/fancy-ui/toast/README.md +62 -6
- package/dist/fancy-ui/toast/Toast.svelte +32 -15
- package/dist/fancy-ui/toast/Toast.test.js +243 -4
- package/dist/fancy-ui/toast/Toaster.svelte +48 -0
- package/dist/fancy-ui/toast/Toaster.svelte.d.ts +6 -0
- package/dist/fancy-ui/toggle/README.md +48 -11
- package/dist/fancy-ui/toggle/Toggle.svelte +89 -10
- package/dist/fancy-ui/toggle/Toggle.svelte.d.ts +5 -0
- package/dist/fancy-ui/toggle/Toggle.test.js +78 -1
- package/dist/fancy-ui/toggle-group/README.md +50 -12
- package/dist/fancy-ui/toggle-group/ToggleGroup.svelte +8 -0
- package/dist/fancy-ui/toggle-group/ToggleGroup.svelte.d.ts +5 -0
- package/dist/fancy-ui/toggle-group/ToggleGroup.test.js +97 -1
- package/dist/fancy-ui/toggle-group/ToggleGroupHarness.test.svelte +3 -1
- package/dist/fancy-ui/toggle-group/ToggleGroupHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/toggle-group/ToggleGroupItem.svelte +53 -1
- package/dist/fancy-ui/tool-call/README.md +21 -10
- package/dist/fancy-ui/tool-call/ToolCall.svelte +13 -1
- package/dist/fancy-ui/tool-call/ToolCall.svelte.d.ts +5 -0
- package/dist/fancy-ui/tool-call/ToolCall.test.js +50 -1
- package/dist/fancy-ui/tool-timeline/README.md +11 -0
- package/dist/fancy-ui/tool-timeline/ToolTimeline.svelte +15 -1
- package/dist/fancy-ui/tool-timeline/ToolTimeline.svelte.d.ts +5 -0
- package/dist/fancy-ui/tool-timeline/ToolTimeline.test.js +56 -2
- package/dist/fancy-ui/tooltip/README.md +26 -0
- package/dist/fancy-ui/tooltip/Tooltip.svelte +48 -20
- package/dist/fancy-ui/tooltip/Tooltip.test.js +118 -0
- package/dist/fancy-ui/voice-input/README.md +25 -13
- package/dist/fancy-ui/voice-input/VoiceInput.svelte +42 -1
- package/dist/fancy-ui/voice-input/VoiceInput.svelte.d.ts +5 -0
- package/dist/fancy-ui/voice-input/VoiceInput.test.js +99 -1
- package/dist/fancy-ui/voice-input/VoiceInputHarness.test.svelte +3 -1
- package/dist/fancy-ui/voice-input/VoiceInputHarness.test.svelte.d.ts +1 -0
- package/dist/fancy-ui/web-search/README.md +11 -0
- package/dist/fancy-ui/web-search/WebSearch.svelte +22 -2
- package/dist/fancy-ui/web-search/WebSearch.svelte.d.ts +5 -0
- package/dist/fancy-ui/web-search/WebSearch.test.js +60 -1
- package/package.json +3 -2
- package/dist/fancy-ui/themes.d.ts +0 -38
- package/dist/fancy-ui/themes.js +0 -356
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
// (the default) — see `fallbackFocus` below for what happens when that
|
|
8
8
|
// element is gone by the time destroy runs.
|
|
9
9
|
//
|
|
10
|
+
// An overlay that animates its exit does not wait for destroy: `onActivate`
|
|
11
|
+
// hands it a function that runs the same return chain at the dismiss instant,
|
|
12
|
+
// and destroy then stands down. It hands over a second function that re-arms
|
|
13
|
+
// the trap when the surface comes back, because a reopen during the exit
|
|
14
|
+
// reverses the outro instead of remounting and this action is therefore never
|
|
15
|
+
// re-created. See `onActivate` below for why an animated close cannot use the
|
|
16
|
+
// destroy path, and why the eager return needs an undo.
|
|
17
|
+
//
|
|
10
18
|
// Ordering requirement for callers combining this with `portal`: put
|
|
11
19
|
// `use:portal` and `use:focusTrap` on the exact same element, with
|
|
12
20
|
// `use:portal` written first. A child node's `use:` action can run before
|
|
@@ -45,7 +53,14 @@ export const focusTrap = (node, opts = {}) => {
|
|
|
45
53
|
}
|
|
46
54
|
let returnFocus = opts.returnFocus ?? true;
|
|
47
55
|
let fallbackFocus = opts.fallbackFocus;
|
|
48
|
-
|
|
56
|
+
// Tracked like the other two rather than read off the original `opts`:
|
|
57
|
+
// a caller that retargets the initial focus between form steps updates
|
|
58
|
+
// the action, and `rearm()` below has to honour the CURRENT target, not
|
|
59
|
+
// the one this instance happened to mount with.
|
|
60
|
+
let initialFocus = opts.initialFocus;
|
|
61
|
+
// Not `const`: this is the element the trap displaced, and a surface
|
|
62
|
+
// reopened mid-exit is displacing a different one. `rearm()` recaptures.
|
|
63
|
+
let previouslyFocused = document.activeElement;
|
|
49
64
|
function focusContainerFallback() {
|
|
50
65
|
// No focusable descendants (empty/loading dialog): contain focus on the
|
|
51
66
|
// node itself so it does not leak to the page behind the trap.
|
|
@@ -60,6 +75,85 @@ export const focusTrap = (node, opts = {}) => {
|
|
|
60
75
|
else
|
|
61
76
|
focusContainerFallback();
|
|
62
77
|
}
|
|
78
|
+
// The three-step return chain, extracted so it can be run EAGERLY at the
|
|
79
|
+
// dismiss instant (via `onActivate`'s handle) as well as at destroy. The
|
|
80
|
+
// latch makes it run exactly once whichever path gets there first.
|
|
81
|
+
let returned = false;
|
|
82
|
+
function returnFocus_() {
|
|
83
|
+
// The latch guards as well as records: the handle may be called more
|
|
84
|
+
// than once (a reopen mid-exit, a caller being careful), and destroy()
|
|
85
|
+
// may still follow it. Focus moves exactly once either way.
|
|
86
|
+
if (returned)
|
|
87
|
+
return;
|
|
88
|
+
returned = true;
|
|
89
|
+
// `previouslyFocused` is a raw reference, captured at mount and
|
|
90
|
+
// recaptured by `rearm()` on a reopen —
|
|
91
|
+
// `.focus()` on it is the same silent no-op a detached node always
|
|
92
|
+
// is if whatever it pointed at has since left the document, so it
|
|
93
|
+
// is checked rather than trusted. Three-step chain, each step only
|
|
94
|
+
// reached if the one before it failed:
|
|
95
|
+
// 1. The original element, if it is still connected — the
|
|
96
|
+
// common case, unchanged from before this fallback existed.
|
|
97
|
+
// 2. `fallbackFocus()`, if the caller supplied one and it points
|
|
98
|
+
// at a still-connected element — typically the overlay's own
|
|
99
|
+
// trigger: a real, findable place for focus to land even
|
|
100
|
+
// though it is not literally "back where it was."
|
|
101
|
+
// 3. `document.body` — always connected, so unlike the previous
|
|
102
|
+
// two steps this one cannot fail by being disconnected. It
|
|
103
|
+
// still needs a `tabindex` to be programmatically focusable
|
|
104
|
+
// at all (the same reason `focusContainerFallback` above
|
|
105
|
+
// gives the trap node one) — real browsers implicitly treat
|
|
106
|
+
// `<body>` as tabindex="-1" for this purpose, but that
|
|
107
|
+
// default is not universal across environments, so it is
|
|
108
|
+
// set explicitly rather than assumed. This is a last
|
|
109
|
+
// resort, not a good outcome: it clears focus rather than
|
|
110
|
+
// pretending some unrelated element is where the user was,
|
|
111
|
+
// but it is never nothing — a user's focus should not
|
|
112
|
+
// silently vanish.
|
|
113
|
+
if (previouslyFocused?.isConnected) {
|
|
114
|
+
previouslyFocused.focus();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const fallback = fallbackFocus?.();
|
|
118
|
+
if (fallback?.isConnected) {
|
|
119
|
+
fallback.focus();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!document.body.hasAttribute("tabindex")) {
|
|
123
|
+
document.body.setAttribute("tabindex", "-1");
|
|
124
|
+
}
|
|
125
|
+
document.body.focus();
|
|
126
|
+
}
|
|
127
|
+
// The counterpart to the handle, run when the surface comes BACK — a
|
|
128
|
+
// reopen mid-exit reverses the outro rather than remounting, so this
|
|
129
|
+
// action, and the latch above, survive the aborted close (see
|
|
130
|
+
// `onActivate`). Clearing the latch re-arms both the handle and
|
|
131
|
+
// `destroy()` for the next dismiss; focus is pulled back only when it has
|
|
132
|
+
// left the node, which makes the ordinary first intro a no-op (focus is
|
|
133
|
+
// already where `focusInitial` just put it) and never yanks focus away
|
|
134
|
+
// from a user who has moved it inside the panel themselves.
|
|
135
|
+
function rearm() {
|
|
136
|
+
returned = false;
|
|
137
|
+
const active = document.activeElement;
|
|
138
|
+
if (node.contains(active))
|
|
139
|
+
return;
|
|
140
|
+
// A reopen mid-exit is a NEW opening, and it can come from a different
|
|
141
|
+
// control than the one that opened the surface the first time. Without
|
|
142
|
+
// recapturing, the next dismissal would return focus to the original
|
|
143
|
+
// opener — or fall through to the fallback, if that opener has since
|
|
144
|
+
// been removed. `document.body` is excluded because it is exactly what
|
|
145
|
+
// `activeElement` reports while the exiting panel is still `inert`:
|
|
146
|
+
// adopting it would throw away a perfectly good return target in
|
|
147
|
+
// favour of one that clears focus.
|
|
148
|
+
if (active && active !== document.body && active.isConnected) {
|
|
149
|
+
previouslyFocused = active;
|
|
150
|
+
}
|
|
151
|
+
focusInitial(initialFocus);
|
|
152
|
+
}
|
|
153
|
+
// No `active` gate on the Tab handler, deliberately. Once the exit starts
|
|
154
|
+
// the panel is `inert`, `.focus()` on its descendants is a browser no-op,
|
|
155
|
+
// and focus has already been returned by `returnFocusNow()` — so the trap
|
|
156
|
+
// has nothing left to trap and needs no flag nobody reads.
|
|
63
157
|
function handleKeydown(event) {
|
|
64
158
|
if (event.key !== "Tab")
|
|
65
159
|
return;
|
|
@@ -83,53 +177,26 @@ export const focusTrap = (node, opts = {}) => {
|
|
|
83
177
|
first.focus();
|
|
84
178
|
}
|
|
85
179
|
}
|
|
86
|
-
focusInitial(
|
|
180
|
+
focusInitial(initialFocus);
|
|
181
|
+
// Handed over AFTER the initial focus move, so a caller that calls the
|
|
182
|
+
// handle synchronously inside `onActivate` still restores the element the
|
|
183
|
+
// trap displaced rather than racing it.
|
|
184
|
+
opts.onActivate?.(returnFocus_, rearm);
|
|
87
185
|
node.addEventListener("keydown", handleKeydown);
|
|
88
186
|
return {
|
|
89
187
|
update(newOpts = {}) {
|
|
90
188
|
returnFocus = newOpts.returnFocus ?? true;
|
|
91
189
|
fallbackFocus = newOpts.fallbackFocus;
|
|
190
|
+
initialFocus = newOpts.initialFocus;
|
|
92
191
|
},
|
|
93
192
|
destroy() {
|
|
94
193
|
node.removeEventListener("keydown", handleKeydown);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
// is if whatever it pointed at has since left the document, so it
|
|
100
|
-
// is checked rather than trusted. Three-step chain, each step only
|
|
101
|
-
// reached if the one before it failed:
|
|
102
|
-
// 1. The original element, if it is still connected — the
|
|
103
|
-
// common case, unchanged from before this fallback existed.
|
|
104
|
-
// 2. `fallbackFocus()`, if the caller supplied one and it points
|
|
105
|
-
// at a still-connected element — typically the overlay's own
|
|
106
|
-
// trigger: a real, findable place for focus to land even
|
|
107
|
-
// though it is not literally "back where it was."
|
|
108
|
-
// 3. `document.body` — always connected, so unlike the previous
|
|
109
|
-
// two steps this one cannot fail by being disconnected. It
|
|
110
|
-
// still needs a `tabindex` to be programmatically focusable
|
|
111
|
-
// at all (the same reason `focusContainerFallback` above
|
|
112
|
-
// gives the trap node one) — real browsers implicitly treat
|
|
113
|
-
// `<body>` as tabindex="-1" for this purpose, but that
|
|
114
|
-
// default is not universal across environments, so it is
|
|
115
|
-
// set explicitly rather than assumed. This is a last
|
|
116
|
-
// resort, not a good outcome: it clears focus rather than
|
|
117
|
-
// pretending some unrelated element is where the user was,
|
|
118
|
-
// but it is never nothing — a user's focus should not
|
|
119
|
-
// silently vanish.
|
|
120
|
-
if (previouslyFocused?.isConnected) {
|
|
121
|
-
previouslyFocused.focus();
|
|
194
|
+
// `returned` disarms this path once the eager handle has already run
|
|
195
|
+
// it — an animated exit calls the handle at `outrostart` and only
|
|
196
|
+
// gets here when the fade finishes, and focus must move exactly once.
|
|
197
|
+
if (!returnFocus || returned)
|
|
122
198
|
return;
|
|
123
|
-
|
|
124
|
-
const fallback = fallbackFocus?.();
|
|
125
|
-
if (fallback?.isConnected) {
|
|
126
|
-
fallback.focus();
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (!document.body.hasAttribute("tabindex")) {
|
|
130
|
-
document.body.setAttribute("tabindex", "-1");
|
|
131
|
-
}
|
|
132
|
-
document.body.focus();
|
|
199
|
+
returnFocus_();
|
|
133
200
|
},
|
|
134
201
|
};
|
|
135
202
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, describe, it, expect } from "vitest";
|
|
1
|
+
import { afterEach, describe, it, expect, vi } from "vitest";
|
|
2
2
|
import { focusTrap } from "./focus-trap";
|
|
3
3
|
function pressTab(options = {}) {
|
|
4
4
|
const event = new KeyboardEvent("keydown", {
|
|
@@ -227,3 +227,358 @@ describe("focusTrap — visibility and empty containers", () => {
|
|
|
227
227
|
action?.destroy?.();
|
|
228
228
|
});
|
|
229
229
|
});
|
|
230
|
+
// `onActivate` is the answer to a close that ANIMATES. An action's
|
|
231
|
+
// `destroy()` is delayed by the exit transition, so a trap that only returns
|
|
232
|
+
// focus there would leave a keyboard user on `<body>` for the whole fade —
|
|
233
|
+
// Svelte sets `inert` on the panel the instant the exit starts, which drops
|
|
234
|
+
// focus out of it. The handle runs the same chain at the dismiss instant.
|
|
235
|
+
describe("focusTrap — the eager return handle", () => {
|
|
236
|
+
let container;
|
|
237
|
+
afterEach(() => {
|
|
238
|
+
container?.remove();
|
|
239
|
+
document.body.innerHTML = "";
|
|
240
|
+
vi.restoreAllMocks();
|
|
241
|
+
});
|
|
242
|
+
function setup(innerHtml) {
|
|
243
|
+
container = document.createElement("div");
|
|
244
|
+
container.innerHTML = innerHtml;
|
|
245
|
+
document.body.appendChild(container);
|
|
246
|
+
return container.querySelector("#trap");
|
|
247
|
+
}
|
|
248
|
+
function withOutsideTrigger() {
|
|
249
|
+
const outside = document.createElement("button");
|
|
250
|
+
outside.id = "outside";
|
|
251
|
+
document.body.appendChild(outside);
|
|
252
|
+
outside.focus();
|
|
253
|
+
return outside;
|
|
254
|
+
}
|
|
255
|
+
it("hands the caller a function exactly once, synchronously, during action creation", () => {
|
|
256
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
257
|
+
const handles = [];
|
|
258
|
+
const trap = focusTrap(trapNode, { onActivate: (r) => handles.push(r) });
|
|
259
|
+
expect(handles).toHaveLength(1);
|
|
260
|
+
expect(typeof handles[0]).toBe("function");
|
|
261
|
+
trap?.destroy?.();
|
|
262
|
+
});
|
|
263
|
+
it("hands it over AFTER the initial focus move, so the handle restores what the trap displaced", () => {
|
|
264
|
+
const outside = withOutsideTrigger();
|
|
265
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
266
|
+
let activeAtHandover = null;
|
|
267
|
+
const trap = focusTrap(trapNode, {
|
|
268
|
+
onActivate: () => (activeAtHandover = document.activeElement),
|
|
269
|
+
});
|
|
270
|
+
expect(activeAtHandover).toBe(trapNode.querySelector("#a"));
|
|
271
|
+
trap?.destroy?.();
|
|
272
|
+
outside.remove();
|
|
273
|
+
});
|
|
274
|
+
it("returns focus immediately when called, long before destroy()", () => {
|
|
275
|
+
const outside = withOutsideTrigger();
|
|
276
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
277
|
+
let returnNow = null;
|
|
278
|
+
const trap = focusTrap(trapNode, { onActivate: (r) => (returnNow = r) });
|
|
279
|
+
expect(document.activeElement?.id).toBe("a");
|
|
280
|
+
returnNow();
|
|
281
|
+
// The node is still mounted and destroy() has not run — this is the
|
|
282
|
+
// window an animated exit lives in.
|
|
283
|
+
expect(trapNode.isConnected).toBe(true);
|
|
284
|
+
expect(document.activeElement).toBe(outside);
|
|
285
|
+
trap?.destroy?.();
|
|
286
|
+
outside.remove();
|
|
287
|
+
});
|
|
288
|
+
it("is idempotent: calling the handle twice moves focus exactly once", () => {
|
|
289
|
+
const outside = withOutsideTrigger();
|
|
290
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
291
|
+
let returnNow = null;
|
|
292
|
+
const trap = focusTrap(trapNode, { onActivate: (r) => (returnNow = r) });
|
|
293
|
+
const focusSpy = vi.spyOn(outside, "focus");
|
|
294
|
+
returnNow();
|
|
295
|
+
returnNow();
|
|
296
|
+
expect(focusSpy).toHaveBeenCalledTimes(1);
|
|
297
|
+
trap?.destroy?.();
|
|
298
|
+
outside.remove();
|
|
299
|
+
});
|
|
300
|
+
it("disarms destroy(): the handle then the unmount still moves focus exactly once", () => {
|
|
301
|
+
const outside = withOutsideTrigger();
|
|
302
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
303
|
+
let returnNow = null;
|
|
304
|
+
const trap = focusTrap(trapNode, { onActivate: (r) => (returnNow = r) });
|
|
305
|
+
const focusSpy = vi.spyOn(outside, "focus");
|
|
306
|
+
returnNow();
|
|
307
|
+
trap?.destroy?.();
|
|
308
|
+
expect(focusSpy).toHaveBeenCalledTimes(1);
|
|
309
|
+
expect(document.activeElement).toBe(outside);
|
|
310
|
+
outside.remove();
|
|
311
|
+
});
|
|
312
|
+
// The owner-frozen answer to a caller that contradicts itself: asking for
|
|
313
|
+
// the handle IS asking for the return, so the handle wins. `returnFocus`
|
|
314
|
+
// governs the unmount path only.
|
|
315
|
+
it("returnFocus: false does not disable the handle — it only disables the unmount path", () => {
|
|
316
|
+
const outside = withOutsideTrigger();
|
|
317
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
318
|
+
let returnNow = null;
|
|
319
|
+
const trap = focusTrap(trapNode, {
|
|
320
|
+
returnFocus: false,
|
|
321
|
+
onActivate: (r) => (returnNow = r),
|
|
322
|
+
});
|
|
323
|
+
expect(document.activeElement?.id).toBe("a");
|
|
324
|
+
returnNow();
|
|
325
|
+
expect(document.activeElement).toBe(outside);
|
|
326
|
+
trap?.destroy?.();
|
|
327
|
+
outside.remove();
|
|
328
|
+
});
|
|
329
|
+
it("returnFocus: false with no handle still returns nothing on destroy, as before", () => {
|
|
330
|
+
const outside = withOutsideTrigger();
|
|
331
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
332
|
+
const trap = focusTrap(trapNode, { returnFocus: false });
|
|
333
|
+
trap?.destroy?.();
|
|
334
|
+
expect(document.activeElement?.id).toBe("a");
|
|
335
|
+
outside.remove();
|
|
336
|
+
});
|
|
337
|
+
it("runs the same three-step chain as destroy(), falling back when the original is gone", () => {
|
|
338
|
+
const outside = withOutsideTrigger();
|
|
339
|
+
const fallback = document.createElement("button");
|
|
340
|
+
fallback.id = "fallback";
|
|
341
|
+
document.body.appendChild(fallback);
|
|
342
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
343
|
+
let returnNow = null;
|
|
344
|
+
const trap = focusTrap(trapNode, {
|
|
345
|
+
fallbackFocus: () => fallback,
|
|
346
|
+
onActivate: (r) => (returnNow = r),
|
|
347
|
+
});
|
|
348
|
+
outside.remove();
|
|
349
|
+
returnNow();
|
|
350
|
+
expect(document.activeElement).toBe(fallback);
|
|
351
|
+
trap?.destroy?.();
|
|
352
|
+
fallback.remove();
|
|
353
|
+
});
|
|
354
|
+
it("omitting onActivate leaves the destroy-time return exactly as it was", () => {
|
|
355
|
+
const outside = withOutsideTrigger();
|
|
356
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
357
|
+
const trap = focusTrap(trapNode, {});
|
|
358
|
+
expect(document.activeElement?.id).toBe("a");
|
|
359
|
+
trap?.destroy?.();
|
|
360
|
+
expect(document.activeElement).toBe(outside);
|
|
361
|
+
outside.remove();
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
// A reopen DURING the exit is not a new mount: Svelte reverses the outro and
|
|
365
|
+
// resumes the same branch, so the action is never re-created and neither the
|
|
366
|
+
// initial focus move nor `onActivate` runs again. `rearm` — the second
|
|
367
|
+
// function handed to `onActivate` — is what puts focus back inside the panel
|
|
368
|
+
// and un-spends the eager return, so the NEXT dismiss still returns focus.
|
|
369
|
+
// Without it a reopened `aria-modal` panel sits open with focus on the
|
|
370
|
+
// trigger behind it, and every later close of that instance returns focus
|
|
371
|
+
// nowhere at all.
|
|
372
|
+
describe("focusTrap — re-arming after a reopen mid-exit", () => {
|
|
373
|
+
let container;
|
|
374
|
+
afterEach(() => {
|
|
375
|
+
container?.remove();
|
|
376
|
+
document.body.innerHTML = "";
|
|
377
|
+
vi.restoreAllMocks();
|
|
378
|
+
});
|
|
379
|
+
function setup(innerHtml) {
|
|
380
|
+
container = document.createElement("div");
|
|
381
|
+
container.innerHTML = innerHtml;
|
|
382
|
+
document.body.appendChild(container);
|
|
383
|
+
return container.querySelector("#trap");
|
|
384
|
+
}
|
|
385
|
+
function withOutsideTrigger() {
|
|
386
|
+
const outside = document.createElement("button");
|
|
387
|
+
outside.id = "outside";
|
|
388
|
+
document.body.appendChild(outside);
|
|
389
|
+
outside.focus();
|
|
390
|
+
return outside;
|
|
391
|
+
}
|
|
392
|
+
it("hands over a re-arm function alongside the return handle, at the same moment", () => {
|
|
393
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
394
|
+
const args = [];
|
|
395
|
+
const trap = focusTrap(trapNode, { onActivate: (...rest) => args.push(rest) });
|
|
396
|
+
expect(args).toHaveLength(1);
|
|
397
|
+
expect(args[0]).toHaveLength(2);
|
|
398
|
+
expect(typeof args[0][0]).toBe("function");
|
|
399
|
+
expect(typeof args[0][1]).toBe("function");
|
|
400
|
+
trap?.destroy?.();
|
|
401
|
+
});
|
|
402
|
+
it("puts focus back inside the trap after the eager return moved it out", () => {
|
|
403
|
+
const outside = withOutsideTrigger();
|
|
404
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
405
|
+
let returnNow = null;
|
|
406
|
+
let rearm = null;
|
|
407
|
+
const trap = focusTrap(trapNode, {
|
|
408
|
+
onActivate: (r, again) => {
|
|
409
|
+
returnNow = r;
|
|
410
|
+
rearm = again;
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
// Dismiss: the eager return puts focus on the trigger while the panel
|
|
414
|
+
// is still mounted and fading.
|
|
415
|
+
returnNow();
|
|
416
|
+
expect(document.activeElement).toBe(outside);
|
|
417
|
+
// Reopen mid-fade: the same action instance is still mounted.
|
|
418
|
+
rearm();
|
|
419
|
+
expect(trapNode.contains(document.activeElement)).toBe(true);
|
|
420
|
+
expect(document.activeElement?.id).toBe("a");
|
|
421
|
+
trap?.destroy?.();
|
|
422
|
+
outside.remove();
|
|
423
|
+
});
|
|
424
|
+
it("re-arms destroy(): the dismiss after a reopen still returns focus", () => {
|
|
425
|
+
const outside = withOutsideTrigger();
|
|
426
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
427
|
+
let returnNow = null;
|
|
428
|
+
let rearm = null;
|
|
429
|
+
const trap = focusTrap(trapNode, {
|
|
430
|
+
onActivate: (r, again) => {
|
|
431
|
+
returnNow = r;
|
|
432
|
+
rearm = again;
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
returnNow();
|
|
436
|
+
rearm();
|
|
437
|
+
trap?.destroy?.();
|
|
438
|
+
expect(document.activeElement).toBe(outside);
|
|
439
|
+
outside.remove();
|
|
440
|
+
});
|
|
441
|
+
it("re-arms the handle itself: the next outrostart returns focus again", () => {
|
|
442
|
+
const outside = withOutsideTrigger();
|
|
443
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
444
|
+
let returnNow = null;
|
|
445
|
+
let rearm = null;
|
|
446
|
+
const trap = focusTrap(trapNode, {
|
|
447
|
+
onActivate: (r, again) => {
|
|
448
|
+
returnNow = r;
|
|
449
|
+
rearm = again;
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
const focusSpy = vi.spyOn(outside, "focus");
|
|
453
|
+
returnNow(); // first dismiss
|
|
454
|
+
rearm(); // reopened mid-fade
|
|
455
|
+
returnNow(); // second, genuine dismiss
|
|
456
|
+
expect(focusSpy).toHaveBeenCalledTimes(2);
|
|
457
|
+
expect(document.activeElement).toBe(outside);
|
|
458
|
+
// And it is still idempotent within that second activation.
|
|
459
|
+
returnNow();
|
|
460
|
+
expect(focusSpy).toHaveBeenCalledTimes(2);
|
|
461
|
+
trap?.destroy?.();
|
|
462
|
+
outside.remove();
|
|
463
|
+
});
|
|
464
|
+
it("does not move focus when it is already inside the trap", () => {
|
|
465
|
+
const outside = withOutsideTrigger();
|
|
466
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button><button id="b">B</button></div>`);
|
|
467
|
+
let rearm = null;
|
|
468
|
+
const trap = focusTrap(trapNode, { onActivate: (_r, again) => (rearm = again) });
|
|
469
|
+
// The very first `introstart` fires with focus already where
|
|
470
|
+
// `focusInitial` just put it, and a user may since have tabbed on.
|
|
471
|
+
const second = trapNode.querySelector("#b");
|
|
472
|
+
second.focus();
|
|
473
|
+
rearm();
|
|
474
|
+
expect(document.activeElement).toBe(second);
|
|
475
|
+
trap?.destroy?.();
|
|
476
|
+
outside.remove();
|
|
477
|
+
});
|
|
478
|
+
it("honours initialFocus when it re-arms, not just the first focusable", () => {
|
|
479
|
+
const outside = withOutsideTrigger();
|
|
480
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button><button id="b">B</button></div>`);
|
|
481
|
+
const initial = trapNode.querySelector("#b");
|
|
482
|
+
let returnNow = null;
|
|
483
|
+
let rearm = null;
|
|
484
|
+
const trap = focusTrap(trapNode, {
|
|
485
|
+
initialFocus: initial,
|
|
486
|
+
onActivate: (r, again) => {
|
|
487
|
+
returnNow = r;
|
|
488
|
+
rearm = again;
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
expect(document.activeElement).toBe(initial);
|
|
492
|
+
returnNow();
|
|
493
|
+
rearm();
|
|
494
|
+
expect(document.activeElement).toBe(initial);
|
|
495
|
+
trap?.destroy?.();
|
|
496
|
+
outside.remove();
|
|
497
|
+
});
|
|
498
|
+
// `update()` used to store `returnFocus` and `fallbackFocus` but drop
|
|
499
|
+
// `initialFocus`, so `rearm()` kept honouring whatever target the action
|
|
500
|
+
// mounted with. A dialog that retargets between form steps would then
|
|
501
|
+
// reopen onto the previous step's field — or onto nothing, if that field
|
|
502
|
+
// had been removed with its step.
|
|
503
|
+
it("re-arms onto the CURRENT initialFocus, not the one it mounted with", () => {
|
|
504
|
+
const outside = withOutsideTrigger();
|
|
505
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button><button id="b">B</button></div>`);
|
|
506
|
+
const a = trapNode.querySelector("#a");
|
|
507
|
+
const b = trapNode.querySelector("#b");
|
|
508
|
+
let returnNow = null;
|
|
509
|
+
let rearm = null;
|
|
510
|
+
const trap = focusTrap(trapNode, {
|
|
511
|
+
initialFocus: a,
|
|
512
|
+
onActivate: (r, again) => {
|
|
513
|
+
returnNow = r;
|
|
514
|
+
rearm = again;
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
expect(document.activeElement).toBe(a);
|
|
518
|
+
// The caller retargets while the surface is still mounted.
|
|
519
|
+
trap?.update?.({ initialFocus: b });
|
|
520
|
+
returnNow();
|
|
521
|
+
rearm();
|
|
522
|
+
expect(document.activeElement).toBe(b);
|
|
523
|
+
trap?.destroy?.();
|
|
524
|
+
outside.remove();
|
|
525
|
+
});
|
|
526
|
+
// `previouslyFocused` was captured once at mount, so a surface reopened
|
|
527
|
+
// from a DIFFERENT control returned focus to the first opener on its next
|
|
528
|
+
// dismissal — or fell through to the fallback, if that opener was gone.
|
|
529
|
+
it("returns focus to the control that reopened it, not the original opener", () => {
|
|
530
|
+
const first = withOutsideTrigger();
|
|
531
|
+
const second = document.createElement("button");
|
|
532
|
+
second.id = "second";
|
|
533
|
+
document.body.appendChild(second);
|
|
534
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
535
|
+
let returnNow = null;
|
|
536
|
+
let rearm = null;
|
|
537
|
+
const trap = focusTrap(trapNode, {
|
|
538
|
+
onActivate: (r, again) => {
|
|
539
|
+
returnNow = r;
|
|
540
|
+
rearm = again;
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
returnNow();
|
|
544
|
+
expect(document.activeElement).toBe(first);
|
|
545
|
+
// A different control reopens the surface mid-exit.
|
|
546
|
+
second.focus();
|
|
547
|
+
rearm();
|
|
548
|
+
expect(trapNode.contains(document.activeElement)).toBe(true);
|
|
549
|
+
trap?.destroy?.();
|
|
550
|
+
expect(document.activeElement).toBe(second);
|
|
551
|
+
first.remove();
|
|
552
|
+
second.remove();
|
|
553
|
+
});
|
|
554
|
+
// Svelte marks the exiting panel `inert`, which drops focus to <body>.
|
|
555
|
+
// Adopting that as the new return target would trade a real opener for
|
|
556
|
+
// one that clears focus, so the recapture skips it.
|
|
557
|
+
it("does not adopt document.body as the return target when re-arming", () => {
|
|
558
|
+
const outside = withOutsideTrigger();
|
|
559
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
560
|
+
let returnNow = null;
|
|
561
|
+
let rearm = null;
|
|
562
|
+
const trap = focusTrap(trapNode, {
|
|
563
|
+
onActivate: (r, again) => {
|
|
564
|
+
returnNow = r;
|
|
565
|
+
rearm = again;
|
|
566
|
+
},
|
|
567
|
+
});
|
|
568
|
+
returnNow();
|
|
569
|
+
document.body.focus();
|
|
570
|
+
rearm();
|
|
571
|
+
trap?.destroy?.();
|
|
572
|
+
expect(document.activeElement).toBe(outside);
|
|
573
|
+
outside.remove();
|
|
574
|
+
});
|
|
575
|
+
it("leaves a trap that never took the handle untouched", () => {
|
|
576
|
+
const outside = withOutsideTrigger();
|
|
577
|
+
const trapNode = setup(`<div id="trap"><button id="a">A</button></div>`);
|
|
578
|
+
const trap = focusTrap(trapNode, {});
|
|
579
|
+
expect(document.activeElement?.id).toBe("a");
|
|
580
|
+
trap?.destroy?.();
|
|
581
|
+
expect(document.activeElement).toBe(outside);
|
|
582
|
+
outside.remove();
|
|
583
|
+
});
|
|
584
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Test-only rig: mounts a `{#if open}` block whose element carries the
|
|
3
|
+
`anchored` transition and the matching static `style:transform-origin`,
|
|
4
|
+
i.e. the exact call-site shape the twelve floating panels use. A test can
|
|
5
|
+
toggle `open` via rerender() and prove the transition drives the node
|
|
6
|
+
through a real mount/unmount cycle — either through the
|
|
7
|
+
`Element.prototype.animate` stub in `src/test-setup.ts` (non-zero
|
|
8
|
+
duration), or through Svelte's own duration-falsy short-circuit (the
|
|
9
|
+
reduced-motion path: duration 0, synchronous, animate() never called).
|
|
10
|
+
|
|
11
|
+
It uses one bidirectional `transition:` directive so a single toggle
|
|
12
|
+
exercises both directions; the shipping panels use `in:` only, which is a
|
|
13
|
+
call-site decision (an entrance-only panel must never delay its own
|
|
14
|
+
unmount), not a limitation of the transition itself.
|
|
15
|
+
|
|
16
|
+
Not exported from index.ts, and not collected by Vitest directly (the run
|
|
17
|
+
includes `*.test.ts` only) — imported and rendered from anchored.test.ts.
|
|
18
|
+
-->
|
|
19
|
+
<script lang="ts">
|
|
20
|
+
import { anchored, originFor, type AnchoredParams } from "./anchored.js";
|
|
21
|
+
import type { Side, Align } from "../anchor-position.js";
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
open: boolean;
|
|
25
|
+
side?: Side;
|
|
26
|
+
align?: Align;
|
|
27
|
+
params?: AnchoredParams;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let { open, side = "bottom", align = "center", params }: Props = $props();
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
{#if open}
|
|
34
|
+
<div
|
|
35
|
+
data-testid="node"
|
|
36
|
+
transition:anchored={params}
|
|
37
|
+
data-side={side}
|
|
38
|
+
style:transform-origin={originFor(side, align)}
|
|
39
|
+
>
|
|
40
|
+
content
|
|
41
|
+
</div>
|
|
42
|
+
{/if}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AnchoredParams } from "./anchored.js";
|
|
2
|
+
import type { Side, Align } from "../anchor-position.js";
|
|
3
|
+
interface Props {
|
|
4
|
+
open: boolean;
|
|
5
|
+
side?: Side;
|
|
6
|
+
align?: Align;
|
|
7
|
+
params?: AnchoredParams;
|
|
8
|
+
}
|
|
9
|
+
declare const AnchoredHarness: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type AnchoredHarness = ReturnType<typeof AnchoredHarness>;
|
|
11
|
+
export default AnchoredHarness;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Test-only rig: mounts a `{#if open}` block whose element carries a
|
|
3
|
+
`preset()` transition, so a test can toggle `open` via rerender() and
|
|
4
|
+
prove the transition actually drives the node through a real mount/unmount
|
|
5
|
+
cycle — either through the `Element.prototype.animate` stub in
|
|
6
|
+
`src/test-setup.ts` (non-zero duration), or through Svelte's own
|
|
7
|
+
duration-falsy short-circuit (duration: 0, synchronous, animate() never
|
|
8
|
+
called). Not exported from index.ts, and not collected by Vitest directly
|
|
9
|
+
(the run includes `*.test.ts` only) — imported and rendered from
|
|
10
|
+
transitions.test.ts.
|
|
11
|
+
-->
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { preset, type PresetParams } from "./transitions.js";
|
|
14
|
+
import type { PresetName } from "./presets.js";
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
open: boolean;
|
|
18
|
+
name: PresetName;
|
|
19
|
+
params?: PresetParams;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let { open, name, params }: Props = $props();
|
|
23
|
+
// $derived.by, not a plain `const`, because `name` is a reactive prop —
|
|
24
|
+
// no test currently changes it mid-lifetime, but a bare
|
|
25
|
+
// `const fn = preset(name)` would silently freeze on whatever `name` was
|
|
26
|
+
// at first render if one ever did.
|
|
27
|
+
const fn = $derived.by(() => preset(name));
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
{#if open}
|
|
31
|
+
<div data-testid="node" transition:fn={params}>content</div>
|
|
32
|
+
{/if}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type PresetParams } from "./transitions.js";
|
|
2
|
+
import type { PresetName } from "./presets.js";
|
|
3
|
+
interface Props {
|
|
4
|
+
open: boolean;
|
|
5
|
+
name: PresetName;
|
|
6
|
+
params?: PresetParams;
|
|
7
|
+
}
|
|
8
|
+
declare const TransitionsHarness: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type TransitionsHarness = ReturnType<typeof TransitionsHarness>;
|
|
10
|
+
export default TransitionsHarness;
|