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): () => voidA 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): DateLocal midnight of d's day.
dd.dates.startOfMonth()
dd.dates.startOfMonth(d: Date): DateLocal 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): Dated 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): Dated 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): numberWhole 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): stringLocal "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 | nullLocal 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): () => voidFires 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>): stringResolves 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): () => voidFires 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): () => voidCursor 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.