Download
70 lines 2.3 KiBJavaScriptModified 2026-08-03 raw
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
// Time widget. Format: "2:27pm" (or 24-hour / with seconds via settings).
// Fixed width so the bar never jitters.

function format(d, { hour24, seconds }) {
  const mm = String(d.getMinutes()).padStart(2, "0");
  const ss = seconds ? `:${String(d.getSeconds()).padStart(2, "0")}` : "";
  if (hour24) {
    return `${String(d.getHours()).padStart(2, "0")}:${mm}${ss}`;
  }
  const ampm = d.getHours() >= 12 ? "pm" : "am";
  const hour = d.getHours() % 12 || 12;
  return `${hour}:${mm}${ss}${ampm}`;
}

// Widest possible string per format, for the fixed width.
function widthCh({ hour24, seconds }) {
  if (hour24) return seconds ? 8 : 5; // "23:27:33"
  return seconds ? 10 : 7; // "12:27:33pm"
}

export default {
  name: "time",
  css: `
    .widget-time .text {
      text-align: center;
      font-family: var(--font-mono);
      font-weight: 600;
      font-variant-numeric: tabular-nums;
    }
  `,
  settings: [
    { key: "hour24", label: "24-hour clock", type: "boolean", default: false },
    { key: "seconds", label: "Show seconds", type: "boolean", default: false },
  ],
  create({ options, listen }) {
    const el = document.createElement("div");
    el.className = "chip widget-time";
    const textEl = document.createElement("span");
    textEl.className = "text";
    textEl.style.width = `${widthCh(options)}ch`;
    el.appendChild(textEl);

    // Tick on the boundary where the display actually changes: every
    // second only when seconds are shown, otherwise once a minute. The
    // small pad keeps an early-firing timer from landing just before the
    // boundary (the re-render would then be a no-op and reschedule).
    let timer;
    const render = () => {
      clearTimeout(timer);
      const now = new Date();
      textEl.textContent = format(now, options);
      const msIntoSecond = now.getMilliseconds();
      const delay = options.seconds
        ? 1000 - msIntoSecond
        : 60000 - now.getSeconds() * 1000 - msIntoSecond;
      timer = setTimeout(render, delay + 20);
    };
    render();
    // The backend detects resume from suspend and pings this so the clock
    // doesn't sit on a stale reading for the rest of its pending tick.
    const unlisten = listen("divebar://resume", render);
    return {
      el,
      destroy: () => {
        clearTimeout(timer);
        unlisten.then((f) => f());
      },
    };
  },
};