{"id":19489360,"url":"https://github.com/salute-developers/spatial","last_synced_at":"2026-06-12T04:31:01.704Z","repository":{"id":65122996,"uuid":"580056149","full_name":"salute-developers/spatial","owner":"salute-developers","description":"Spatial Navigation Lite","archived":false,"fork":false,"pushed_at":"2023-10-31T08:04:46.000Z","size":1355,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2026-03-04T09:27:13.595Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/salute-developers.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-19T15:57:49.000Z","updated_at":"2024-04-30T12:25:34.000Z","dependencies_parsed_at":"2025-01-08T08:34:49.729Z","dependency_job_id":null,"html_url":"https://github.com/salute-developers/spatial","commit_stats":{"total_commits":16,"total_committers":3,"mean_commits":5.333333333333333,"dds":0.375,"last_synced_commit":"a5826e240d1638c15f37ac0f896dd3c38ece6479"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/salute-developers/spatial","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salute-developers%2Fspatial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salute-developers%2Fspatial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salute-developers%2Fspatial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salute-developers%2Fspatial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salute-developers","download_url":"https://codeload.github.com/salute-developers/spatial/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salute-developers%2Fspatial/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34229624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-12T02:00:06.859Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T21:08:18.664Z","updated_at":"2026-06-12T04:31:01.173Z","avatar_url":"https://github.com/salute-developers.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spatial navigation для Canvas Apps\n\nПространственная навигация (Spatial navigation) — это возможность перемещаться между фокусируемыми элементами в зависимости от их положения в документе. Пространственная навигация часто называется «направленной навигацией», которая обеспечивает четырехнаправленную навигацию: сверху, слева, снизу, справа. Пользователи обычно знакомы с двусторонней навигацией с использованием «клавиш табуляции» для направления вперед, так и «клавиши Shift+Tab» для обратного направления.\n\n[Есть спецификация для браузеров](https://drafts.csswg.org/css-nav-1/), которая пока в статусе \"draft\".\n\nМы рекомендуем использовать эту библиотеку для Canvas Apps на наших девайсах: TV, SberBox и др,\nа [библиотеку](https://github.com/salute-developers/spatial-navigation) считать устаревшей.\n\nДля мобильных устройств или других устройств с сенсорным экраном это не надо.\n\nДокументации с аннотацией типов также доступна на сайте - https://plasma.sberdevices.ru/spatial/\n\n## Оглавление\n\n-   [Установка](#установка)\n-   [Минимальная настройка приложения для работы с `@salutejs/spatial`](#минимальная-настройка-приложения-для-работы-с-salutejsspatial`)\n    -   [Инициализация в родительском компоненте всего приложения](#инициализация-в-родительском-компоненте-всего-приложения)\n    -   [Добавление секции](#добавление-секции)\n    -   [Включение навигации на DOM элементе](#включение-навигации-на-dom-элементе)\n-   [Углубление в `@salutejs/spatial`](#углубление-в-salutejsspatial`)\n    -   [Варианты инициализации](#варианты-инициализации)\n    -   [Подробнее о секциях](#подробнее-о-секциях)\n        -   [Настройка параметров секции](#настройка-параметров-секции)\n-   [Хуки](#хуки)\n-   [Полезные методы SpatialNavigation](#полезные-методы-spatialnavigation)\n-   [Оптимизация и ускорение работы](#оптимизация-и-ускорение-работы)\n    -   [Intersection и Mutation observer](#intersection-и-mutation-observer)\n    -   [Простые секции](#простые-секции)\n-   [Запуск тестов](#запуск-тестов)\n-   [Pitfalls](#pitfalls)\n    -   [Next.js \u003e= 13.0.0](#nextjs--1300)\n    -   [Next.js \u003c 13.0.0](#nextjs--1300-1)\n\n## Установка\n\n```sh\nnpm install --save @salutejs/spatial\n```\n\n## Минимальная настройка приложения для работы с `@salutejs/spatial`\n\nНужно выполнить три обязательных шага.\n\n### Инициализация в родительском компоненте всего приложения\n\n```jsx\nimport { useSpatnavInitialization } from '@salutejs/spatial';\n\nimport { Page } from './pages/Page';\n\nconst App = () =\u003e {\n    useSpatnavInitialization();\n\n    return \u003cPage /\u003e;\n};\n```\n\n### Добавление секции\n\nДля навигации `@salutejs/spatial` использует [секции](#подробнее-о-секциях). Секцию можно добавить с помощью хука `useSection`.\n\n```jsx\nimport { useSection } from '@salutejs/spatial';\n\nconst Page = ({ children }) =\u003e {\n    const [sectionProps] = useSection('sectionName');\n\n    return \u003cdiv {...sectionProps} /\u003e;\n};\n\nexport default Page;\n```\n\n### Включение навигации на DOM элементе\n\nДля того, чтобы браузер имел возможность фокусироваться на DOM элемент, этот элемент должен иметь атрибут tabindex=\"-1\".\n\nДалее для работы внутренних функций `@salutejs/spatial` необходимо добавить DOM элементу CSS класс \"sn-section-item\".\n\n```jsx\n\u003c\u003e\n    \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n        навигация работает\n    \u003c/div\u003e\n    \u003cdiv className=\"sn-section-item any-class my-class\" tabIndex={-1}\u003e\n        навигация работает\n    \u003c/div\u003e\n\u003c/\u003e\n```\n\nЕсли убрать класс `sn-section-item`, то элемент исключается из навигации.\n\n```jsx\n\u003c\u003e\n    \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n        навигация работает\n    \u003c/div\u003e\n    \u003cdiv className=\"any-class my-class\" tabIndex={-1}\u003e\n        навигация НЕ работает\n    \u003c/div\u003e\n\u003c/\u003e\n```\n\nДобавим элементы в секцию.\n\n```jsx\nimport { useEffect, useRef } from 'react';\nimport { useSection } from '@salutejs/spatial';\n\nconst Page = ({ children }) =\u003e {\n    // создание секции\n    const [sectionProps] = useSection('sectionName');\n\n    // установка фокуса на элемент\n    const ref = useRef(null);\n\n    useEffect(() =\u003e {\n        const focusable = ref.current;\n\n        if (focusable) {\n            focusable.focus();\n        }\n    }, []);\n\n    return (\n        \u003cdiv {...sectionProps}\u003e\n            \u003cdiv ref={ref} className=\"sn-section-item\" tabIndex={-1}\u003e\n                навигация работает (после выполнения useEffect, фокус будет установлен на этот элемент)\n            \u003c/div\u003e\n            \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n                навигация работает\n            \u003c/div\u003e\n            \u003cdiv\u003eнавигация НЕ работает\u003c/div\u003e\n        \u003c/div\u003e\n    );\n};\n\nexport default Page;\n```\n\nГотово! Навигация настроена. Но надо подчеркнуть, что после инициализации и добавления секций фокус автоматически не устанавливается ни на один элемент. Это надо делать вручную или с помощью хука `useDefaultSectionFocus`.\n\nЗдесь были рассмотрены только необходимые действия. Для более гибкой настройки секций и навигации читайте далее.\n\n## Углубление в `@salutejs/spatial`\n\n### Варианты инициализации\n\nМожно инициализировать `@salutejs/spatial` без использования хука `useSpatnavInitialization`, если такое требуется.\n\n```js\nimport { spatnavInstance } from '@salutejs/spatial';\n\n// вызывать только на клиенте\nspatnavInstance.init();\n```\n\nАналогично можно и отменить инициализацию. Например, при переходе на страницу, где пространственная навигация не нужна.\n\n```js\nspatnavInstance.unInit();\n```\n\n### Подробнее о секциях\n\nСекция — это элементы навигации, объединённые в группу. У секции есть корневой элемент.\nВключение секции в навигацию происходит с помощью хука `useSection`. У корневого элемента секции должны быть установлены аттрибуты `id=\"имя секции, переданное в useSection\"` и `className=\"sn-section-root\"`, которые возвращает хук `useSection`.\nЭлементы секции должны быть потомками корневого элемента и иметь аттрибут `className=\"sn-section-item\"`.\n\n```jsx\nimport { useSection } from '@salutejs/spatial';\n\nconst Page = ({ children }) =\u003e {\n    const [section1] = useSection('section1');\n    const [section2] = useSection('section2');\n\n    return (\n        \u003c\u003e\n            \u003cdiv {...section1}\u003e\n                \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n                    принадлежит секции section1\n                \u003c/div\u003e\n                \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n                    принадлежит секции section1\n                \u003c/div\u003e\n                \u003cdiv\u003e\n                    \u003cdiv\u003e\n                        \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n                            принадлежит секции section1\n                        \u003c/div\u003e\n                    \u003c/div\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n            \u003cdiv {...section2}\u003e\n                \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n                    принадлежит секции section2\n                \u003c/div\u003e\n                \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n                    принадлежит секции section2\n                \u003c/div\u003e\n            \u003c/div\u003e\n        \u003c/\u003e\n    );\n};\n```\n\n#### Настройка параметров секции\n\nТакже хук `useSection` возвращает функцию кастомизации. С её помощью можно гибко настроить правила навигации внутри и между секциями. А также включить или выключить секцию целиком.\n\n```jsx\nimport { useSection } from '@salutejs/spatial';\n\nconst Page = ({ children }) =\u003e {\n    const [section1, customize1] = useSection('section1');\n    const [section2, customize2] = useSection('section2');\n\n    useEffect(() =\u003e {\n        // выключаем навигацию в секции section1 целиком\n        customize1({\n            disabled: true,\n        });\n\n        customize2({\n            simpleSectionOptions: { type: 'row' },\n            // установка элемента по умолчанию для секции\n            getDefaultElement: (section2Root) =\u003e section2Root.firstElementChild,\n            enterTo: 'default-element',\n        });\n    }, [[customize1, customize2]]);\n\n    // установка секции по умолчанию и установка фокуса на элемент из этой секции, выбранный по правилам определённым в её конфиге\n    // https://plasma.sberdevices.ru/spatial/functions/useDefaultSectionFocus.html\n    useDefaultSectionFocus('section2');\n\n    return (\n        \u003c\u003e\n            \u003cdiv {...section1}\u003e\n                \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n                    принадлежит секции section1, навигация выключена\n                \u003c/div\u003e\n                \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n                    принадлежит секции section1, навигация выключена\n                \u003c/div\u003e\n                \u003cdiv\u003e\n                    \u003cdiv\u003e\n                        \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n                            принадлежит секции section1, навигация выключена\n                        \u003c/div\u003e\n                    \u003c/div\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n            \u003cdiv {...section2}\u003e\n                \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n                    принадлежит секции section2. После выполнения всех хуков, фокус будет установлен на этот элемент\n                \u003c/div\u003e\n                \u003cdiv className=\"sn-section-item\" tabIndex={-1}\u003e\n                    принадлежит секции section2\n                \u003c/div\u003e\n            \u003c/div\u003e\n        \u003c/\u003e\n    );\n};\n```\n\nО всех параметрах секции можно почитать в [документации к типу `Config`](https://plasma.sberdevices.ru/spatial/types/Config.html). Параметры передаются в функцию `customize`.\n\n## Хуки\n\n-   [`useSpatnavInitialization`](https://plasma.sberdevices.ru/spatial/functions/useSpatnavInitialization.html) - инициализация навигации;\n-   [`useSection`](https://plasma.sberdevices.ru/spatial/functions/useSection.html) - создание секции;\n-   [`useSelfSection`](https://plasma.sberdevices.ru/spatial/functions/useSelfSection.html) - создание секции, состоящей только из одного элемента;\n-   [`useDefaultSectionFocus`](https://plasma.sberdevices.ru/spatial/functions/useDefaultSectionFocus.html).\n\n## Полезные методы SpatialNavigation\n\nУ инстанса SpatialNavigation есть ряд методов для упрощения некоторых сценариев:\n\n-   Включение и выключение навигации для данной секции или полностью\n-   Удаление или добавление секции в навигацию\n-   Изменение глобального конфига или конфига секции\n-   Фокусирование указанной секции\n\nНапример, фокусирование на определенной секции на монтирование компонента.\n\n```jsx\nimport { useSection, spatnavInstance } from '@salutejs/spatial';\n\nconst Page = ({ children }) =\u003e {\n\n   const [sectionProps] = useSection('suggest');\n\n   useEffect(() =\u003e {\n       spatnavInstance.focus('suggest');\n   }, []);\n\n   \u003cSuggest {...sectionProps}\u003e\n       ...\n   \u003cSuggest/\u003e\n};\n```\n\nПолный список методов и их сигнатуры смотрите в коде [SpatialNavigation](https://github.com/salute-developers/spatial/blob/master/src/core/SpatialNavigation.ts) или на [советующей странице документации](https://plasma.sberdevices.ru/spatial/classes/SpatialNavigation.html)\n\n## Оптимизация и ускорение работы\n\n### Intersection и Mutation observer\n\nДля ускорения расчётов в `@salutejs/spatial` используются Intersection и Mutation observer. Первый следит за тем какой элемент находится во вьюпорте браузера. `@salutejs/spatial` в первую очередь будет пытаться найти подходящий для навигации элемент именно среди видимых элементов.\nMutation observer нужен для того, чтобы при обновлении DOM дерева, новые элементы навигации были обработаны Intersection observer'ом.\n\n### Простые секции\n\n`@salutejs/spatial` делает довольно много расчётов, чтобы понять какой элемент больше подходит для навигации.\nНо если в вашей вёрстке есть списки, в которых элементы всегда расположенны в ряд или столбик, то лучше включить режим простых секций. Этот режим переопределяет поведение навигации при выборе следующего или предыдущего элемента.\nВместо расчётов `@salutejs/spatial` просто возьмёт нужный элемент из DOM с помощью `nextSiblingElement` или `previousSiblingElement`.\nДля того, чтобы этот режим работал необходимо передать соответствующую опцию в конфиг секции. И, обратите внимание, что все элементы секции должны быть на одном уровне в DOM.\n\nВ обоих примерах ниже режим простых секций будет работать.\n\n```jsx\nimport { useSection } from '@salutejs/spatial';\n\nconst Page = ({ children }) =\u003e {\n    const [section1, customize1] = useSection('section1');\n    const [section2, customize2] = useSection('section2');\n\n    useEffect(() =\u003e {\n        customize1({\n            simpleSectionOptions: { type: 'column' },\n        });\n\n        customize2({\n            simpleSectionOptions: { type: 'row' },\n        });\n    }, [customize1, customize2]);\n\n    return (\n        \u003c\u003e\n            \u003cdiv {...section1}\u003e\n                \u003cdiv\u003e\n                    \u003cdiv\u003e\n                        \u003cdiv className=\"sn-section-item\" tabIndex={-1} /\u003e\n                        \u003cdiv className=\"sn-section-item\" tabIndex={-1} /\u003e\n                    \u003c/div\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n            \u003cdiv {...section2}\u003e\n                \u003cdiv className=\"sn-section-item\" tabIndex={-1} /\u003e\n                \u003cdiv className=\"sn-section-item\" tabIndex={-1} /\u003e\n            \u003c/div\u003e\n        \u003c/\u003e\n    );\n};\n```\n\n## Запуск тестов\n\nДля запуска тесов нужно собрать пакет spatial, запустить `test-app` и `cypress`.\n\n```sh\nnpm run build\n\nnpm run test-app:start\n\nnpm run cypress:open\n```\n\nВ открывшемся окне Cypress выбрать `E2E Testing`, тестировать можно как в Chrome так и в Electron.\n\n## Pitfalls\n\n@salutejs/spatial не компилируется в CommonJS, поэтому если вы используете Next.js выполните следующее:\n\n### Next.js \u003e= 13.0.0\n\nВ `next.config.js` вам необходимо добавить свойство `transpilePackages`.\n\n```js\nconst config = {\n    transpilePackages: ['@salutejs/spatial'],\n};\n```\n\nДокументация по [transpilePackages](https://nextjs.org/docs/app/api-reference/next-config-js/transpilePackages).\n\n### Next.js \u003c 13.0.0\n\nВоспользуйтесь пакетом [next-transpile-modules](https://www.npmjs.com/package/next-transpile-modules).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalute-developers%2Fspatial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalute-developers%2Fspatial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalute-developers%2Fspatial/lists"}