feat: Add Svelte 5 spatial navigation package#238
Conversation
Add @noriginmedia/norigin-spatial-navigation-svelte with full feature parity to the React binding. Components: - <SpatialRoot> - declarative init with initialFocusKey prop - <SpatialNode> - renderless, exposes use:spatial action - <Focusable> - convenience wrapper for simple leaf nodes Key design decisions: - No wrapper div (action-based DOM binding) - Automatic parent context (no manual Provider needed) - autoFocus prop for dynamic content - Two-tier API: Focusable (simple) vs SpatialNode (advanced) Also includes: - Demo app (apps/svelte-demo) mirroring the React demo - Svelte quick start guide and API reference - AI agent skill for Svelte usage - Changeset for initial release
🦋 Changeset detectedLatest commit: 5f064be The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Thanks a lot for this @yehia2amer - We'll make sure to get this reviewed ASAP and merged as soon as we can, since we've been wanting to add Svelte support. Appreciate the work! We'll get back to you. |
|
Really nice work @yehia2amer. One thing I want to flag before merging: Also the branch is a bit behind main, we have released a couple of things recently, so it's if you can merge main and resolve the conflict in lock file (or accept main and regen your lock file). Otherwise this is solid, I pulled the branch, ran the demo locally, initial focus and arrow navigation both work as expected and identical to React version. |
|
Thanks a lot for checking the PR. Glad it's working on your end too. mmm yeah that's a fair what I can do is:
Just One thing I'd love your input on: where should we take this discussion? I did the I'll keep this PR clean without them, and spin up a separate branch that includes |
# Conflicts: # package-lock.json
Regenerate lockfile on top of latest main (core@4.0.0, react/legacy@3.2.1, react-native-tvos) to include the svelte package + demo dependencies and resolve the lockfile conflict flagged in review.
Keep the Svelte package as a strict 1:1 with useFocusable (SpatialRoot + SpatialNode) per review feedback, avoiding per-framework API drift. - Remove Focusable.svelte component and its index export - Remove FocusableState type - Remove autoFocus prop and its setTimeout auto-focus logic from SpatialNode - Update docs (api-reference, quick-start) and README - Remove unused Focusable import from svelte demo Consumers needing mount-time focus can call setFocus(focusKey) explicitly, matching React. SpatialRoot's initialFocusKey remains as it solves the Svelte-specific mount-timing problem.
Bring the Svelte package to parity with useFocusable/core after rebasing onto main (core@4.0.0). - Add nextFocusResolver prop to SpatialNode (parity with useFocusable) - Add focusOnPresetKey prop to SpatialRoot (core 3.2.0 init option) - Add layoutAdapter prop to SpatialRoot and migrate off the deprecated useGetBoundingClientRect flag (still supported as a fallback) - Fix renamed core type re-export: SpatialNavigationServiceInit -> SpatialNavigationServiceOptions - Update API reference docs and changeset Verified: svelte-check 0 errors, package builds, demo type-checks, builds, and dev server serves cleanly against core 4.0.0.
|
@predikament @asgvard All done. pulled in the latest from main, removed |
What
Adds
@noriginmedia/norigin-spatial-navigation-svelte— a Svelte 5 binding with full feature parity to the React package.Components
<SpatialRoot>— Declarative init wrapper withinitialFocusKeyprop for timing-safe initial focus<SpatialNode>— Renderless component that exposes ause:spatialaction. No wrapper div injected. Consumer applies the action to their own element.<Focusable>— Convenience component for simple leaf nodes. Renders an element and handles the action internally.Key design decisions
use:spatial) for DOM registration, matching how React'suseFocusablereturns aref. Zero layout interference.<SpatialNode>provides itsnavKeyto descendants via Svelte context. No manual<FocusContext.Provider>needed (the onArrowPress How to use #1 source of bugs in the React version is gone).initialFocusKeyon<SpatialRoot>— Handles Svelte's bottom-up mount timing internally. NosetTimeouthacks for consumers.autoFocusprop — For modals/dynamic content that should grab focus on mount.<Focusable>for the 90% case (simple leaf),<SpatialNode>for full control (containers, custom layouts).All React features supported
Every option from
useFocusableis available:focusable,saveLastFocusedChild,trackChildren,autoRestoreFocus,forceFocus,isFocusBoundary,focusBoundaryDirections,preferredChildFocusKey,extraProps,accessibilityLabel, all callbacks (onEnterPress,onArrowPress,onFocus,onBlur, etc.), plus the full imperative API (setFocus,pause,resume,navigateByDirection, etc.).Also included
apps/svelte-demo) — Single-file demo mirroring the React demo (menu, content rows, scrolling, progress bar with arrow-hold, enter-to-select)skills/norigin-spatial-navigation-svelte/SKILL.mdConsumer usage
Notes
eslint-plugin-sveltefor linting..sveltefiles are excluded from Prettier (prettier v2 can't parse Svelte 5 syntax).checktask to turbo forsvelte-check.@sveltejs/packagehandles the build, shipping preprocessed.sveltefiles + type declarations.