Something went wrong

Thank you for being patient! We're working hard on resolving the issue

Preferences - Lona Docs Log in

Preferences

The SDK manages user preferences through client.preferences. Preferences are persisted to a storage backend and exposed as reactive LiveData properties that notify listeners on change.

Storage

The client accepts an optional PreferencesStorage backend. In the browser, pass localStorage. In tests, pass an in-memory implementation. If omitted, preferences are held in memory only and lost on reload.

const client = new LonaSdk.Client({
  accessToken: "lona_pat_...",
  storage: localStorage,
  platform: SheetPlatform.TEST,
});

PreferencesStorage matches the Storage web API subset:

interface PreferencesStorage {
  getItem(key: string): string | null;
  setItem(key: string, value: string): void;
  removeItem(key: string): void;
}

Calendar preferences

client.preferences.calendar()

Returns the calendar preferences singleton. Each property is a LiveData<T> that can be read synchronously and observed for changes.

const cal = client.preferences.calendar();

// Read current values
cal.firstDay.get();            // "sunday" | "monday"
cal.timeFormat.get();          // "12h" | "24h"
cal.dateFormat.get();          // "month-day" | "day-month"
cal.secondaryTimezones.get();  // string[]

Setting values

Setters persist to storage and notify listeners in one step:

cal.setFirstDay("monday");
cal.setTimeFormat("24h");
cal.setDateFormat("day-month");
cal.setSecondaryTimezones(["America/New_York", "Asia/Tokyo"]);

Derived helpers

MethodReturnsDescription
cal.startOfWeek()WeekdayWeekday.MON or Weekday.SUN based on firstDay
cal.use24Hour()booleantrue when timeFormat is "24h"
cal.useDayMonth()booleantrue when dateFormat is "day-month"

Sheet view config

client.preferences.sheetView(sheetId)

Returns the stored view configuration for a sheet. The config is a discriminated union — day views carry a timezone mode, other timescales do not:

const config = client.preferences.sheetView(sheetId).get("week");
// { timeScale: "week", count: 3 }

const dayConfig = client.preferences.sheetView(sheetId).get("day");
// { timeScale: "day", count: 7, tzType: "default" }

The argument to get() is the default timescale used when no stored preference exists.

SheetViewConfig

type SheetViewConfig =
  | { timeScale: "day"; count: number; tzType: RendererTz }
  | { timeScale: "year" | "month" | "week"; count: number };

type RendererTz = "default" | "time-traveler";

Updating

update() persists the new config and merges per-timescale count defaults:

client.preferences.sheetView(sheetId).update({
  timeScale: "month",
  count: 6,
});

Per-timescale defaults

Each sheet remembers the last-used count for every timescale. When switching from months back to weeks, the previous week count is restored:

const defaults = client.preferences.sheetView(sheetId).getDefaults();
// Map { "year" => 3, "month" => 6, "week" => 3, "day" => 7 }

SheetViewConfig.label()

Formats a config for display:

SheetViewConfig.label({ timeScale: "week", count: 3 });  // "Weeks"
SheetViewConfig.label({ timeScale: "day", count: 1 });    // "Day"

Calendar visibility

client.preferences.calendarVisibility(userId)

Manages which calendars are visible by default for a user. Each entry pairs a calendar ID with a boolean:

const viz = client.preferences.calendarVisibility(userId);

// Read all
const entries = viz.getAll();
// [{ acid: "google:cal_abc", visible: true }, ...]

// Set one
viz.set(calendarId, false);

Sidebar width

client.preferences.sidebarWidth()

Manages sidebar dimensions with automatic min/max clamping:

const sidebar = client.preferences.sidebarWidth();

// Read (returns null if never set)
const dims = sidebar.get();
// { expandedWidth: 360, unexpandedWidth: 300 } | null

// Set
sidebar.set(400, 320);

Widths are clamped to [300, window.innerWidth - 100] on read.

Listening to changes

All LiveData properties support listener registration:

const cal = client.preferences.calendar();

cal.firstDay.addListener(this, (newValue) => {
  // Re-render calendar starting from newValue
});

Listeners are cleaned up when the owner is garbage collected or when explicitly removed:

cal.firstDay.removeListener(this);

Row height overrides

client.preferences.rowHeight()

Stores per-row height overrides in local preferences. Users can customize row heights even without row write access — changes are stored locally and applied automatically when opening a sheet.

const rowHeight = client.preferences.rowHeight();

// Get stored height for a row (null if none)
const height = rowHeight.get(rowId);

// Set a custom height
rowHeight.set(rowId, 64);

// Clear the override (reverts to default)
rowHeight.set(rowId, null);

The SDK hydrates rows with stored height overrides during sheet opening, before layout computation.

Theme

client.preferences.theme()

Stores the selected theme name. The theme name (e.g., "grass", "solarized") is persisted locally. Full theme resolution (colors, CSS) is handled by the app layer.

const theme = client.preferences.theme();

// Read current theme name (null = default)
const name = theme.current.get();

// Set a theme
theme.setCurrent("grass");

// Clear (revert to default)
theme.setCurrent(null);

client.themes (ThemeStore)

ThemeStore manages theme resolution, caching, and visual application. The current property is reactive:

const unsubscribe = client.themes.current.addListener(this, (theme) => {
  // Theme changed — re-render
});

// Set the active theme
client.themes.setCurrent(themeObject);

Timezone

client.preferences.timezone()

Manages the user's preferred timezone. Falls back to the browser's default timezone when no preference is set.

const tz = client.preferences.timezone();

// Get the effective timezone name (preference or browser default)
const tzName = tz.getEffectiveTimezoneName();

// Resolve to a TimezoneRegion
const tzRegion = await tz.getEffectiveTimezone();

// Set a preferred timezone
tz.setTimezone("America/New_York");

// Clear (use browser default)
tz.setTimezone(null);

// Check if using browser default
tz.isUsingBrowserDefault(); // true when no preference set

The timezone property is a LiveData<Tzname | null> that can be observed for changes.

Timezone transitions

client.preferences.transitions()

Manages the user's timezone transitions — trips where the user changes timezones. The SDK resolves these into flight segments for the calendar's time-traveler rendering mode.

Adding transitions

const transitions = client.preferences.transitions();

transitions.add({
  start: DateTime.utc.parse("2026-06-23T22:40:00Z"),
  end: DateTime.utc.parse("2026-06-24T14:15:00Z"),
  timezone: "Asia/Tokyo",
  label: "SFO → NRT",
});

transitions.add({
  start: DateTime.utc.parse("2026-07-08T05:00:00Z"),
  end: DateTime.utc.parse("2026-07-08T20:30:00Z"),
  timezone: "America/Los_Angeles",
  label: "NRT → SFO",
});

Each transition represents the departure-to-arrival window. The timezone field is the destination timezone — the timezone the user transitions into after arrival.

Listing and removing

const all = transitions.list;
// [{ id: "abc", start: "2026-06-23T22:40:00Z", timezone: "Asia/Tokyo", ... }, ...]

transitions.remove(all[0].id);

Resolving into flights

resolve() converts stored transitions into FlightInfo[] — the SDK's internal representation of timezone-crossing segments. Each FlightInfo pairs a departure timezone with an arrival timezone using wall-clock times:

const flights = await transitions.resolve("America/Los_Angeles");
// FlightInfo<TransitionEntry>[]

for (const flight of flights) {
  console.log(flight.start.tz.name, "→", flight.end.tz.name);
  // "America/Los_Angeles" → "Asia/Tokyo"
}

The argument is the user's home timezone — used as the departure timezone for the first transition and the implicit "return to" timezone after the last transition.

Building a FlightMapper

mapper() produces a FlightMapper anchored at today's date, ready for calendar rendering. The mapper breaks flights into day-level segments (departure, in-flight, arrival, collapsed) and maps calendar column indices to ResolvedIndex:

const mapper = await transitions.mapper("America/Los_Angeles");

// Look up what timezone state a specific calendar index is in
const resolved = mapper.find(columnIndex);
// { type: "ground", tz: TimezoneRegion }
// { type: "departure", ... }
// { type: "in-flight", ... }
// { type: "arrival", ... }

Integration with sheet view config

When tzType is "time-traveler", the calendar uses the transitions mapper to render timezone-aware columns:

client.preferences.sheetView(sheetId).update({
  timeScale: "day",
  count: 7,
  tzType: "time-traveler",
});

The sheet renderer automatically reads from transitions() when time-traveler mode is active.

See also