Bridge: withBridge property sync
Variables
Section titled “Variables”InternalWriteSymbol
Section titled “InternalWriteSymbol”
constInternalWriteSymbol: typeofInternalWriteSymbol
Defined in: src/bridge/loop-guard.ts:7
Symbol-keyed re-entrancy flag marking an element inside a state-to-DOM
transaction. Set by writeWithTransaction() and observed by
isInternalWrite(); stored on the element itself so concurrent bridges on
different elements never interfere.
Functions
Section titled “Functions”isInternalWrite()
Section titled “isInternalWrite()”Reports whether an element is currently inside a writeWithTransaction()
lock.
The bridge DOM-to-state listener consults this first and bails out when
true, so programmatic state-to-DOM writes never echo back into update().
export function isInternalWrite(element: HTMLElement): boolean;Defined in: src/bridge/loop-guard.ts
Example
Section titled “Example”import { isInternalWrite } from '@sandlada/document-context'
el.addEventListener('input', () => { if (isInternalWrite(el)) { return }})parseBoolean()
Section titled “parseBoolean()”Parses DOM-sourced values to boolean with bridge type fidelity.
Returns true only for true, 'true', and '1'; everything else
(including 'false', '', null, and undefined) is false. Use as the
parse codec for aria-* / dataset.* boolean bindings.
export function parseBoolean(value: unknown): boolean;Defined in: src/bridge/sanitize.ts
Example
Section titled “Example”import { parseBoolean } from '@sandlada/document-context'
withBridge({ properties: { open: { target: 'aria-expanded', parse: parseBoolean } } })parseJSON()
Section titled “parseJSON()”Parses JSON-serialized DOM values with a safe fallback.
Non-strings pass through untouched; strings are JSON.parse()-ed, and
parse failures return the original string instead of throwing. Ideal for
dataset.* bindings carrying objects or arrays.
export function parseJSON(value: unknown): unknown;Defined in: src/bridge/sanitize.ts
Example
Section titled “Example”import { parseJSON } from '@sandlada/document-context'
withBridge({ properties: { filters: { target: 'dataset.filters', parse: parseJSON } } })parseNumber()
Section titled “parseNumber()”Parses DOM-sourced values to number via Number(value).
Empty strings coerce to 0 and non-numeric input to NaN; apply a fallback
(Number(x) || 0) in the binding when that matters. Use as the parse
codec for dataset.* numeric bindings.
export function parseNumber(value: unknown): number;Defined in: src/bridge/sanitize.ts
Example
Section titled “Example”import { parseNumber } from '@sandlada/document-context'
withBridge({ properties: { count: { target: 'dataset.count', parse: parseNumber } } })readDomProperty()
Section titled “readDomProperty()”Reads a value from a host element through a bridge dot-path.
Supported grammars, in precedence order: dataset.* (string or
undefined), style.* including style.--* custom properties,
aria-* attributes, elementInternals.value | elementInternals.state
(falls back to .value), hidden (attribute-or-property boolean), any
native property present via in, and finally a plain attribute fallback.
Every path is validated first; dangerous segments throw
PropertySyncSecurityError.
export function readDomProperty(element: HTMLElement, path: string): any;Defined in: src/bridge/property-path.ts
Throws
Section titled “Throws”On prototype-pollution or XSS-sink paths.
Example
Section titled “Example”import { readDomProperty } from '@sandlada/document-context'
readDomProperty(el, 'dataset.count')readDomProperty(el, 'style.--accent')readDomProperty(input, 'value')safeWriteValueWithCursor()
Section titled “safeWriteValueWithCursor()”Writes an input/textarea value without moving the user’s caret or causing redundant reflows.
No-ops when inputElement.value already Object.is-equals nextValue.
When the element is the focused document.activeElement with a numeric
selection, the caret is captured and restored via setSelectionRange();
input types that reject selection APIs (for example email, number) are
shielded by try/catch. Background (unfocused) inputs are written plainly.
export function safeWriteValueWithCursor( inputElement: HTMLInputElement | HTMLTextAreaElement, nextValue: string): void;Defined in: src/bridge/loop-guard.ts
Example
Section titled “Example”import { safeWriteValueWithCursor } from '@sandlada/document-context'
safeWriteValueWithCursor(input, String(state.query))setupBridge()
Section titled “setupBridge()”Activates one bidirectional bridge on an already-mounted host element.
Normally invoked by the withBridge mount hook, not by hand. The pipeline
is: initial state-to-DOM sync inside a writeWithTransaction lock →
reactive state-to-DOM subscription (coalesced via queueMicrotask unless
batch === false, with value paths written through
safeWriteValueWithCursor) → DOM-to-state listener on each events entry
that re-reads every bound path, diffs with Object.is, and pushes changes
via update(). Internal (transaction-locked) writes are ignored on the way
back, breaking echo loops; disposed sessions stop syncing.
export function setupBridge<S extends Record<PropertyKey, any>>( element: HTMLElement, session: ISession<S, any>, bridgeOptions: IBridgeOptions<S>): () => void;Defined in: src/bridge/with-bridge.ts
Example
Section titled “Example”import { setupBridge } from '@sandlada/document-context'
const cleanup = setupBridge(element, session, { properties: { count: 'dataset.count' } })cleanup()validatePropertyPath()
Section titled “validatePropertyPath()”Rejects bridge paths that enable prototype pollution or target XSS sinks.
Splits path on ., trims, and lowercases each segment: segments
__proto__, prototype, constructor throw with the offending
blockedSegment; segments innerhtml, outerhtml, insertadjacenthtml,
srcdoc, script, eval throw with the offending blockedSink. Called
by both readDomProperty() and writeDomProperty(), so bad mappings fail
fast at mount or first sync rather than writing somewhere dangerous.
export function validatePropertyPath(path: string, targetNode?: HTMLElement): void;Defined in: src/bridge/sanitize.ts
Throws
Section titled “Throws”When any segment is blocked.
Example
Section titled “Example”import { validatePropertyPath } from '@sandlada/document-context'
validatePropertyPath('dataset.count', el)withBridge()
Section titled “withBridge()”Pure Phase 1 operator that registers one bidirectional DOM bridge.
Appends options to blueprint.bridges and adds a mount hook that calls
setupBridge(session.target, session, options) at mount time. Multiple
withBridge() calls accumulate in order; each activates its own
subscription and listeners. The operator itself is side-effect free.
export function withBridge<S extends Record<PropertyKey, any> = Record<PropertyKey, any>, Services = {}>( options: IBridgeOptions<S>): <ActualState extends S, ActualServices extends Services>( blueprint: IContextBlueprint<ActualState, ActualServices>) => IContextBlueprint<ActualState, ActualServices>;Defined in: src/bridge/with-bridge.ts
Example
Section titled “Example”import { withBridge, parseNumber } from '@sandlada/document-context'
const blueprint = pipe( createContext({ count: 0, query: '' }), withBridge({ properties: { count: { target: 'dataset.count', parse: parseNumber }, query: 'value' }, events: ['input'] }))writeDomProperty()
Section titled “writeDomProperty()”Writes a state value onto a host element through a bridge dot-path.
Mirrors readDomProperty grammars with DOM-appropriate null handling:
null / undefined deletes dataset.* entries, removes style.--* and
aria-* / plain attributes, and clears value and style properties to
''. Booleans for checked | disabled | readOnly set both the IDL
property and the content attribute; hidden toggles both together.
Native properties win over attributes when the path exists via in.
Every path is validated first; dangerous segments throw
PropertySyncSecurityError before any write.
export function writeDomProperty( element: HTMLElement, path: string, value: any): void;Defined in: src/bridge/property-path.ts
Throws
Section titled “Throws”On prototype-pollution or XSS-sink paths.
Example
Section titled “Example”import { writeDomProperty } from '@sandlada/document-context'
writeDomProperty(el, 'dataset.count', 3)writeDomProperty(el, 'aria-pressed', true)writeDomProperty(el, 'dataset.count', null)writeWithTransaction()
Section titled “writeWithTransaction()”Runs a DOM write callback under the internal-transaction lock.
Sets the element flag, invokes writeFn, and always clears the flag in a
finally block, so bridge-originated writes are recognizable to the
DOM-to-state listener, which skips them and breaks echo loops. Synchronous
only; exceptions from writeFn propagate after the flag is cleared.
export function writeWithTransaction( element: HTMLElement, writeFn: () => void): void;Defined in: src/bridge/loop-guard.ts
Example
Section titled “Example”import { writeWithTransaction } from '@sandlada/document-context'
writeWithTransaction(el, () => { el.dataset.count = String(state.count)})References
Section titled “References”IBridgeOptions
Section titled “IBridgeOptions”Re-exports IBridgeOptions
IBridgePropertyRule
Section titled “IBridgePropertyRule”Re-exports IBridgePropertyRule