ReferenceAPI

Time, dates, theme, and cursor

Reference for dd.time ticks, dd.dates calendar arithmetic, dd.theme tokens and accent helpers, and dd.cursor hover and movement events.

dd.time

Needs the time permission.

dd.time.now()

dd.time.now(): Promise<{ epochMs: number }>

dd.time.onTick()

dd.time.onTick(cb: (tick: { epochMs: number }) => void): () => void

A second-aligned wall-clock tick. Prefer it over your own setInterval for clock-like widgets: it stays aligned to the real second boundary.

dd.dates

Always available, no permission needed: calendar arithmetic in local time, with date-fns underneath. The namespace is curated rather than a re-export, because SDK surface is a permanent contract and the whole library would cost every widget 73 KB. Display formatting is left out on purpose: Intl.DateTimeFormat and Intl.NumberFormat with style: "unit" already localize.

Everything reads and writes local wall-clock fields. Never new Date("2027-03-14"): the date-only string form parses as UTC and lands a day early west of Greenwich. An Invalid Date passed in comes back out as NaN or an Invalid Date, the way JS does.

const { startOfDay, breakdown, daysBetween, fromDayKey } = dd.dates;
const target = fromDayKey(settings.date);            // null while unset
if (target) {
  const now = new Date(tick.epochMs);
  const sleeps = daysBetween(now, target);            // 3, not "2 days 23 hours"
  const parts = breakdown(startOfDay(now), target);   // { years: 0, months: 1, days: 14, ... }
}

dd.dates.startOfDay()

dd.dates.startOfDay(d: Date): Date

Local midnight of d's day.

dd.dates.startOfMonth()

dd.dates.startOfMonth(d: Date): Date

Local midnight of the first day of d's month.

dd.dates.startOfWeek()

dd.dates.startOfWeek(d: Date, weekStart?: Weekday): Date

type Weekday = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday"

Local midnight of the first day of d's week. weekStart is the setting string as stored, so startOfWeek(d, settings.weekStart) reads directly; a missing or off-list value means Monday.

dd.dates.addDays()

dd.dates.addDays(d: Date, n: number): Date

d moved n whole days at the same wall-clock time, so a daylight-saving day is one step.

dd.dates.addMonths()

dd.dates.addMonths(d: Date, n: number): Date

d moved n whole months, with the day clamped to the month it lands in: 31 January plus one is 28 or 29 February. Time of day is kept.

dd.dates.daysBetween()

dd.dates.daysBetween(from: Date, to: Date): number

Whole calendar days from from to to, midnight to midnight, so a 23-hour daylight-saving day still counts one. Negative when to is the earlier date. Use it for "3 sleeps away" and for day keys that must turn over at midnight, not at a 24-hour mark.

dd.dates.breakdown()

dd.dates.breakdown(
  from: Date,
  to: Date,
  opts?: { largestUnit?: "years" | "days" },
): { years: number; months: number; days: number; hours: number; minutes: number; seconds: number }

The span as calendar parts, every field present (zeros included). It is calendar arithmetic, not division: 31 January to 28 February is one month, 31 January to 1 March in a leap year is one month and one day, and 12:00 to 12:00 across a spring-forward is one day and zero hours. Sub-second remainders truncate. When to is before from, every non-zero part is negative, so order the pair yourself when you want a size and a direction.

largestUnit: "days" folds years and months into the day count, which is the shape a live "days hours minutes seconds" readout wants.

dd.dates.dayKey()

dd.dates.dayKey(d: Date): string

Local "YYYY-MM-DD" for d. Fixed width, so two keys compare as dates with < and >, and a key never passes through UTC. The right shape for storage keys and for a date setting's value.

dd.dates.fromDayKey()

dd.dates.fromDayKey(key: string): Date | null

Local midnight of a dayKey, or null when the key is malformed or names a day that does not exist ("2027-02-31" is refused rather than read as 3 March). A date setting's value goes through this; guard the null.

dd.theme

Always available, no permission needed. Themes are applied by the host; the SDK re-applies the token map to :root automatically, so plain CSS using var(--dd-*) restyles itself. These methods are for JS that resolves colors manually (canvas, inline styles).

dd.theme.tokens()

dd.theme.tokens(): Promise<Record<string, string>>

The current --dd-* token map for this widget instance, as the host knows it: the theme plus any per-widget style the user set on this instance. Note: manifest font pins are injected as CSS on top of this map, so tokens() does not see them; dd.ui.token() reads computed style and does. Resolve font faces through dd.ui.token().

dd.theme.onChange()

dd.theme.onChange(cb: (tokens: Record<string, string>) => void): () => void

Fires live on every theme change (theme picker, wallpaper auto-theme, user overrides) and on every per-widget style edit for this instance, previews included. If you resolve token values in JS, do not cache them across this event: re-read and redraw.

dd.theme.accent()

dd.theme.accent(settings: Record<string, unknown>): string

Resolves the accentColor settings preset pair: the theme accent while useAccent is on (the default), the user's custom color otherwise. It reads the current token value, so re-call it inside dd.theme.onChange (or use bindAccent) to stay theme-reactive.

dd.theme.bindAccent()

dd.theme.bindAccent(cb: (color: string) => void): Promise<() => void>

settings.bind and theme.onChange in one: cb fires with the resolved accent immediately and again on every settings or theme change. The returned promise resolves (with the combined unsubscribe) after that first call.

dd.cursor

Always available, no permission needed, and it works for non-interactive widgets too: the window stays click-through, you get the signal without capturing input.

dd.cursor.onHover()

dd.cursor.onHover(cb: (state: { inside: boolean }) => void): () => void

Fires when the real cursor enters or leaves this widget's footprint. While a popout is open, the footprint is the bounding box of widget plus pane (crossing the gap between them never flickers a leave), and both documents receive the event.

dd.cursor.onMove()

dd.cursor.onMove(cb: (pos: { x: number; y: number }) => void): () => void

Cursor position at roughly 30 Hz while the cursor is inside this widget's (or its pane's) rect, as fractions (0..1) of the receiving document's viewport. For element-level hover effects without input capture. For the common case, skip the subscription entirely: mark elements data-dd-hover and the SDK toggles a dd-hover class on them for you.