@sukooru/core
v0.2.2
Published
Framework-agnostic scroll restoration core for browser apps.
Downloads
446
Readme
@sukooru/core
Framework-agnostic scroll restoration core for browser apps.
Install
npm install @sukooru/coreAgent Skill
npx skills add https://github.com/jglee96/sukooru --skill sukooru-integrationUse the repo skill when you want an AI coding agent to choose the right Sukooru package and wire full-window or element restoration correctly.
When To Use This Package
Use @sukooru/core when you want full control over routing integration in a vanilla app or you are building your own adapter on top of Sukooru.
Restore The Full Window Scroll Position
import { createSukooru } from '@sukooru/core'
const sukooru = createSukooru({
getKey: () => window.location.pathname,
})
const stop = sukooru.mount()
window.history.scrollRestoration = 'manual'
export const mountProductsPage = async () => {
const handle = sukooru.registerContainer(window, 'window')
const scrollKey = '/products'
const status = await sukooru.restore(scrollKey)
console.log('restore status:', status)
return async () => {
await sukooru.save(scrollKey)
handle.unregister()
}
}
// Later, when your app shuts down:
// stop()Leave containerId as window when the browser viewport itself is the thing that scrolls.
Restore A Specific Element
import { createSukooru } from '@sukooru/core'
const sukooru = createSukooru({
getKey: () => window.location.pathname,
})
const stop = sukooru.mount()
export const mountProductsPanel = async () => {
const container = document.querySelector<HTMLElement>('#product-list')
if (!container) {
throw new Error('Missing #product-list')
}
const handle = sukooru.registerContainer(container, 'product-list')
const scrollKey = '/products'
await sukooru.restore(scrollKey)
return async () => {
await sukooru.save(scrollKey)
handle.unregister()
}
}
// Later, when your app shuts down:
// stop()Use a stable containerId for every scrollable element that should keep its own position.
Advanced: Restore Custom List State Before Scroll
import { createSukooru } from '@sukooru/core'
const sukooru = createSukooru({
getKey: () => window.location.pathname,
})
export const mountInfiniteProducts = async () => {
const container = document.querySelector<HTMLElement>('#product-list')
if (!container) {
throw new Error('Missing #product-list')
}
const stateHandle = sukooru.setScrollStateHandler('product-list', {
captureState: () => ({
loadedPageCount,
}),
applyState: async (state) => {
await loadPages(state.loadedPageCount)
},
})
const containerHandle = sukooru.registerContainer(container, 'product-list')
const scrollKey = '/products'
await sukooru.restore(scrollKey)
return async () => {
await sukooru.save(scrollKey)
containerHandle.unregister()
stateHandle.unregister()
}
}ScrollStateHandler lets you restore list data first, then apply scroll after the DOM is ready again.
Use A Custom Storage Adapter
storage accepts any adapter that implements get, set, delete, and keys. Each method may return either a plain value or a promise, so both sync backends like localStorage and async backends like IndexedDB wrappers are supported.
import { createSukooru, type StorageAdapter } from '@sukooru/core'
const localStorageAdapter: StorageAdapter = {
get: (key) => window.localStorage.getItem(key),
set: (key, value) => {
window.localStorage.setItem(key, value)
},
delete: (key) => {
window.localStorage.removeItem(key)
},
keys: () => Object.keys(window.localStorage),
}
const sukooru = createSukooru({
getKey: () => window.location.pathname,
storage: localStorageAdapter,
})import { createSukooru, type StorageAdapter } from '@sukooru/core'
const indexedDbLikeAdapter: StorageAdapter = {
get: async (key) => await db.get('scroll-state', key),
set: async (key, value) => {
await db.put('scroll-state', value, key)
},
delete: async (key) => {
await db.delete('scroll-state', key)
},
keys: async () => await db.getAllKeys('scroll-state'),
}
const sukooru = createSukooru({
getKey: () => window.location.pathname,
storage: indexedDbLikeAdapter,
})Key Exports
createSukoorucreateSessionStorageAdaptersessionStorageAdaptercreateMemoryStorageAdaptercreateDefaultSerializer- Types such as
SukooruOptions,SukooruInstance, andScrollStateHandler
Notes
- Use
await sukooru.getKeys()when you need the authoritative key list.sukooru.keysis a synchronous snapshot for convenience. sessionStorageAdapterfalls back to in-memory storage when the browser blocks storage access for the current page session.- Set
window.history.scrollRestoration = 'manual'once on the client if the browser's native restoration conflicts with your app. - Use a stable
scrollKeysuch as/productswhen you want the saved position to belong to a list route instead of the current detail URL.
See Also
- Root docs: https://github.com/jglee96/sukooru/blob/main/README.en.md
- Vanilla example: https://github.com/jglee96/sukooru/tree/main/examples/vanilla
