Skip to Content

@stesura/core/stores

Per-editor nanostores  atoms, the registry that finds them, and the global user preferences.

createEditorStores({ defaults, extensionStores }) builds one FullEditorStores per editor scope: atoms for the editor mode, TOC, pagination output (page counts, page numbering, the first-layout gate), stylesheet, list configs and block handle. extensionStores is spread into the result; type it by augmenting EditorStoreExtensions. The root editor’s configPlugin (part of basePlugins / stesuraPlugins) creates and registers them with addEditorStoresToMap when its view mounts and removes them on unmount, so integrators never call these; commands and plugins reach the stores through getEditorStores(idOrState) instead of taking a stores parameter. A sub-editor’s state falls through to its parent’s stores. In React, read and write them with useEditorStoreValue / useEditorStore from @stesura/editor-react.

userPreferences holds global atoms shared by every editor. Seed their defaults with initUserPreferences before the first mount.

Functions & values

activeFloatingBoxAtom

The text box being edited in editorId, or null. Created on first use, stable per editor.

Parameters:
NameTypeDefault
editorIdstring
Returns:
WritableAtom<ActiveFloatingBox | null>

addEditorStoresToMap

Registers an editor’s stores under its id and bumps registryVersion.

A duplicate id keeps the FIRST registration — the second editor then reads and writes the first’s stores, which is never what the caller wants, so it is reported rather than silently accepted.

Returns whether THIS call created the entry. Callers must only unregister on teardown when it did: a refused duplicate’s destroy would otherwise take the surviving editor’s stores with it (CORE-132).

Parameters:
NameTypeDefault
idstring
atomsFullEditorStores
Returns:
boolean

addMountConfigToMap

Registers a root mount’s config. Mirrors addEditorStoresToMap’s first-wins rule so a refused duplicate id can’t hijack the entry.

Parameters:
NameTypeDefault
idstring
stateConfigPluginState
Returns:
boolean

closeFloatingBox

Unmounts editorId’s box sub-editor, if any.

Parameters:
NameTypeDefault
editorIdstring
Returns:
void

createEditorStores

Creates the reactive stores for one editor scope. User preferences are not here: they are global singletons in userPreferences (./user-preferences.ts), a few of which are also spread into the result for compatibility.

Parameters:
NameTypeDefault
props{ defaults: Partial<EditorStoreDefaults>; extensionStores: Record<string, PreinitializedWritableAtom<unknown>>; }
Returns:
FullEditorStores

disarmAll

Disarms every mode armed in editorId, except except.

except is how an arming control interacts with itself without cancelling the mode it just armed — its own button, and its own menu opening.

Parameters:
NameTypeDefault
editorIdstring
except"highlight" | "formatPainter"
Returns:
void

disposeFloatingBoxState

Resets editorId’s atom and evicts it once nothing listens, so apps minting an editor id per document don’t leak. Deferred a microtask: StrictMode re-subscribes within the unmounting commit (as disposeNotePanelState).

Parameters:
NameTypeDefault
editorIdstring
Returns:
void

floatingBoxEditorRegistry

Mounted text box sub-editors; one at most per main editor.

NameTypeDefault
atomWritableAtom<ActiveSubEditor<FloatingBoxEditorKey>[]>
keyId(key: FloatingBoxEditorKey) => string

Stable identity for a key within one main editor.

register(entry: ActiveSubEditor<FloatingBoxEditorKey>) => void
unregister(mainEditorId: string, key: FloatingBoxEditorKey) => void
getAll(mainEditorId: string) => ActiveSubEditor<FloatingBoxEditorKey>[]

Entries belonging to mainEditorId only.

get(mainEditorId: string, key: FloatingBoxEditorKey) => ActiveSubEditor<FloatingBoxEditorKey> | undefined

The registered editor bound to key under mainEditorId, if mounted.

getEditorStores

The stores for an editor id or state. A state whose editor has no stores of its own (a sub-editor) falls through to its parentEditorId’s; a raw id does not.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
Returns:
FullEditorStores | undefined

getFirstLayoutReady

Reads the first-layout gate; null when the editor has no stores.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
Returns:
boolean | null

getFirstLayoutReadyAtom

The editor’s first-layout gate atom.

null — nobody claims it (unpaginated mount, or pagination not loaded). false — pagination’s first pass is outstanding; the canvas holds. true — every section has a layout, or the hold timed out.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
Returns:
WritableAtom<boolean | null> | undefined

getMountConfig

The configPlugin state a root mount was created with, if it is still mounted.

Parameters:
NameTypeDefault
idstring
Returns:
ConfigPluginState | undefined

getNodePageNumbersStore

Page label per block id (“iv”, “12”), as the TOC renders it.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
Returns:
Map<string, string> | undefined

getPageCountsStore

Pages per section, in document order. Sum it for the document’s page count; sum the entries ahead of a section for that section’s page offset.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
Returns:
Map<string, number> | undefined

getPageNumberingStore

Sections’ own pageNumbering, keyed by section id — see the store’s docs.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
Returns:
Map<string, PageNumberingAttrs> | undefined

getProofReadApiFailedFor

Parameters:
NameTypeDefault
editorIdstring
Returns:
boolean

getProofReadLoadingFor

Parameters:
NameTypeDefault
editorIdstring
Returns:
boolean

getRootEditorView

The main editor view registered under id, or undefined.

Parameters:
NameTypeDefault
idstring
Returns:
EditorView | undefined

getTocBreaksStore

Where the TOC’s rows split across pages, as measured by pagination.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
Returns:
Map<string, TOCbreakEntry[]> | undefined

getTOCstore

The editor’s TOC state (its anchor registry and rendered entries).

Parameters:
NameTypeDefault
idOrStatestring | EditorState
Returns:
TOCstate | undefined

headerFooterEditorRegistry

Mounted header/footer sub-editors. Unlike notes, a header and a footer editor can be open at once for the same section and variant. Entries are registered by SubEditorRegister (editor-react); the binding plugin pushes main-editor changes into each.

NameTypeDefault
atomWritableAtom<ActiveSubEditor<HeaderFooterEditorKey>[]>
keyId(key: HeaderFooterEditorKey) => string

Stable identity for a key within one main editor.

register(entry: ActiveSubEditor<HeaderFooterEditorKey>) => void
unregister(mainEditorId: string, key: HeaderFooterEditorKey) => void
getAll(mainEditorId: string) => ActiveSubEditor<HeaderFooterEditorKey>[]

Entries belonging to mainEditorId only.

get(mainEditorId: string, key: HeaderFooterEditorKey) => ActiveSubEditor<HeaderFooterEditorKey> | undefined

The registered editor bound to key under mainEditorId, if mounted.

initUserPreferences

Applies default preferences for keys with no localStorage entry yet, so persisted user choices win. Call once at startup, before mounting an editor.

Parameters:
NameTypeDefault
defaultsPartial<UserPreferences>
Returns:
void

openFloatingBox

Mounts the sub-editor of box boxId in editorId; one box is active at a time.

Parameters:
NameTypeDefault
editorIdstring
boxIdstring
options{ at?: { left: number; top: number; }; focus?: boolean; }
Returns:
void

proofReadApiFailedByEditor

NameTypeDefault
valueRecord<string, boolean>
eqKey<Key extends string>(oldValue: (Key extends string ? boolean : never) | undefined, newValue: (Key extends string ? boolean : never) | undefined, key: Key) => boolean

Compares the previous and next values for a key on setKey.

Returning true means the values are the same, so the store keeps the old value and no listener is called.

One function serves every key of the map, so the key name comes as the third argument. The old value is undefined when the key is not in the map yet.

Object.is
listen(listener: (value: Readonly<Record<string, boolean>>, oldValue: Readonly<Record<string, boolean>> | undefined, changedKey: string | undefined) => void) => () => void

Subscribe to store changes.

In contrast with Store#subscribe it do not call listener immediately.

notify(oldValue?: Readonly<Record<string, boolean>> | undefined, changedKey?: string | undefined) => void

Low-level method to notify listeners about changes in the store.

Can cause unexpected behaviour when combined with frontend frameworks that perform equality checks for values, such as React.

set(newValue: Record<string, boolean>) => void

Change store value.

$settings.set({ theme: 'dark' })

Operation is atomic, subscribers will be notified once with the new value. changedKey will be undefined

setKey<Key extends string>(key: Key, value: (Key extends string ? boolean : never) | undefined) => void

Change key in store value.

$settings.setKey('theme', 'dark')

To delete key set undefined.

$settings.setKey('theme', undefined)
subscribe(listener: (value: Readonly<Record<string, boolean>>, oldValue: Readonly<Record<string, boolean>> | undefined, changedKey: string | undefined) => void) => () => void

Subscribe to store changes and call listener immediately.

import { $router } from '../store' $router.subscribe(page => { console.log(page) })
eq(oldValue: Readonly<Record<string, boolean>> | undefined, newValue: Readonly<Record<string, boolean>>) => boolean

Compares the previous and next values on every set.

Returning true means the values are the same, so the store keeps the old value and no listener is called.

Object.is
get() => Record<string, boolean>

Get store value.

In contrast with ReadableAtom#value this value will be always initialized even if store had no listeners.

$store.get()
initRecord<string, boolean> | undefined

Low-level attribute to read store’s initial value.

lcnumber

Listeners count.

off() => void

Unbind all listeners.

proofReadLoadingByEditor

Per-root-editor proofread flags, keyed like the proofread cache. Write through the setters below, which keep the global aggregates in step.

NameTypeDefault
valueRecord<string, boolean>
eqKey<Key extends string>(oldValue: (Key extends string ? boolean : never) | undefined, newValue: (Key extends string ? boolean : never) | undefined, key: Key) => boolean

Compares the previous and next values for a key on setKey.

Returning true means the values are the same, so the store keeps the old value and no listener is called.

One function serves every key of the map, so the key name comes as the third argument. The old value is undefined when the key is not in the map yet.

Object.is
listen(listener: (value: Readonly<Record<string, boolean>>, oldValue: Readonly<Record<string, boolean>> | undefined, changedKey: string | undefined) => void) => () => void

Subscribe to store changes.

In contrast with Store#subscribe it do not call listener immediately.

notify(oldValue?: Readonly<Record<string, boolean>> | undefined, changedKey?: string | undefined) => void

Low-level method to notify listeners about changes in the store.

Can cause unexpected behaviour when combined with frontend frameworks that perform equality checks for values, such as React.

set(newValue: Record<string, boolean>) => void

Change store value.

$settings.set({ theme: 'dark' })

Operation is atomic, subscribers will be notified once with the new value. changedKey will be undefined

setKey<Key extends string>(key: Key, value: (Key extends string ? boolean : never) | undefined) => void

Change key in store value.

$settings.setKey('theme', 'dark')

To delete key set undefined.

$settings.setKey('theme', undefined)
subscribe(listener: (value: Readonly<Record<string, boolean>>, oldValue: Readonly<Record<string, boolean>> | undefined, changedKey: string | undefined) => void) => () => void

Subscribe to store changes and call listener immediately.

import { $router } from '../store' $router.subscribe(page => { console.log(page) })
eq(oldValue: Readonly<Record<string, boolean>> | undefined, newValue: Readonly<Record<string, boolean>>) => boolean

Compares the previous and next values on every set.

Returning true means the values are the same, so the store keeps the old value and no listener is called.

Object.is
get() => Record<string, boolean>

Get store value.

In contrast with ReadableAtom#value this value will be always initialized even if store had no listeners.

$store.get()
initRecord<string, boolean> | undefined

Low-level attribute to read store’s initial value.

lcnumber

Listeners count.

off() => void

Unbind all listeners.

registerArmedMode

Records that key is armed in editorId, together with the callback that disarms it.

Register when the mode arms and call the returned release when it disarms — an entry that outlives its view would disarm through a dead EditorView.

Parameters:
NameTypeDefault
editorIdstring
key"highlight" | "formatPainter"
disarm() => void
Returns:
() => void

registerRootEditorView

Records a main editor’s view under its id. Called on mount.

Parameters:
NameTypeDefault
idstring
viewEditorView
Returns:
void

registryVersion

Bumped on every registry change so hooks re-render when stores appear or go. Store consumers can mount before the editor registers its stores, and the plain map isn’t reactive, so without this they never saw the stores.

NameTypeDefault
valuenumber

Low-level method to read store’s value without calling onStart.

Try to use only ReadableAtom#get . Without subscribers, value can be undefined.

set(newValue: number) => void

Change store value.

$router.set({ path: location.pathname, page: parse(location.pathname) })
eq(oldValue: number | undefined, newValue: number) => boolean

Compares the previous and next values on every set.

Returning true means the values are the same, so the store keeps the old value and no listener is called.

Object.is
get() => number

Get store value.

In contrast with ReadableAtom#value this value will be always initialized even if store had no listeners.

$store.get()
initnumber | undefined

Low-level attribute to read store’s initial value.

lcnumber

Listeners count.

listen(listener: (value: number, oldValue: number | undefined) => void) => () => void

Subscribe to store changes.

In contrast with Store#subscribe it do not call listener immediately.

notify(oldValue?: number | undefined) => void

Low-level method to notify listeners about changes in the store.

Can cause unexpected behaviour when combined with frontend frameworks that perform equality checks for values, such as React.

off() => void

Unbind all listeners.

subscribe(listener: (value: number, oldValue?: number | undefined) => void) => () => void

Subscribe to store changes and call listener immediately.

import { $router } from '../store' $router.subscribe(page => { console.log(page) })

removeEditorStoresFromMap

Unregisters an editor’s stores on unmount and bumps registryVersion.

Parameters:
NameTypeDefault
idstring
Returns:
void

removeMountConfigFromMap

Unregisters a root mount’s config on unmount.

Parameters:
NameTypeDefault
idstring
Returns:
void

resolveEditorId

The id as given, or read from the state’s config plugin (undefined without one).

Parameters:
NameTypeDefault
idOrStatestring | EditorState
Returns:
string | undefined

setFirstLayoutReady

Sets the first-layout gate: false claims the hold, true releases it, null disclaims it (pagination off or unmounted). No-op when unchanged.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
readyboolean
Returns:
void

setNodePageNumbersStore

Replaces the block-id to page-number map.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
pageNumbersMap<string, string>
Returns:
void

setPageCountsStore

Replaces the section-id to page-count map. undefined when pagination is off.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
pageCountsMap<string, number>
Returns:
void

setPageNumberingStore

Replaces the section-id to page-numbering map.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
pageNumberingMap<string, PageNumberingAttrs>
Returns:
void

setProofReadApiFailedFor

Raises or clears editorId’s proofread api-failure flag, and the global aggregate.

Parameters:
NameTypeDefault
editorIdstring
valueboolean
Returns:
void

setProofReadLoadingFor

Raises or clears editorId’s proofread loading flag, and the global aggregate.

Parameters:
NameTypeDefault
editorIdstring
valueboolean
Returns:
void

setTocBreaksStore

Replaces the TOC row-break map.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
breaksMap<string, TOCbreakEntry[]>
Returns:
void

setTOCstore

Replaces the editor’s TOC state.

Parameters:
NameTypeDefault
idOrStatestring | EditorState
tocTOCstate
Returns:
void

unregisterRootEditorView

Drops a main editor’s view. Called on unmount.

Parameters:
NameTypeDefault
idstring
Returns:
void

userPreferences

Global user-preference atoms, shared by every editor. All but the proofread status flags persist to localStorage. Set startup defaults with initUserPreferences().

NameTypeDefault
showOutlinePreinitializedWritableAtom<boolean> & object
showRulerPreinitializedWritableAtom<boolean> & object
zoomLevelPreinitializedWritableAtom<number> & object
trackChangesPreinitializedWritableAtom<{ show: boolean; enabled: boolean; }> & object
showCommentsPreinitializedWritableAtom<CommentDisplayMode> & object
displayUnitsPreinitializedWritableAtom<DisplayUnits> & object
proofReadEnabledPreinitializedWritableAtom<boolean> & object
proofReadFloatingMenuPreinitializedWritableAtom<boolean> & object
proofReadLoadingPreinitializedWritableAtom<boolean> & object
proofReadApiFailedPreinitializedWritableAtom<boolean> & object
formatLintEnabledPreinitializedWritableAtom<boolean> & object

Types

ActiveFloatingBox

The text box being edited in one main editor. at is the viewport point to put the caret at (a double-click), null for the start of the box. focus false leaves focus where it is (search navigation).

NameTypeDefault
boxIdstring
at{ left: number; top: number; } | null
focusboolean

FloatingBoxEditorKey

Identifies the text box a sub-editor is bound to, and the main editor it belongs to.

NameTypeDefault
boxIdstring
mainEditorIdstring

HeaderFooterEditorKey

Identifies which header/footer content a sub-editor is bound to.

NameTypeDefault
sectionIdstring
target"header" | "footer"
variantHeaderFooterVariant

Other exports

ExportTypeDescription
ARMED_MODE_KEYSreadonly ["formatPainter", "highlight"]The modes that can be armed. ArmedModeKey is its element type.
ArmedModeKey"formatPainter" | "highlight"
UserPreferenceKey"showOutline" | "showRuler" | "showComments" | "displayUnits" | "proofReadEnabled" | "zoomLevel" | "trackChanges" | "proofReadLoading" | "proofReadApiFailed"
Last updated on