gridsmith-ui 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
- import { useAppShell, ControlSizeContext, Button, useDirtyGuard, OverlayCloseCtx, DiscardConfirm, Spinner, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-2U2X6E2X.js';
2
- export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell, useUnsavedChanges } from './chunk-2U2X6E2X.js';
1
+ import { Button, useAppShell, ControlSizeContext, useDirtyGuard, OverlayCloseCtx, DiscardConfirm, Spinner, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-J43HCK7Q.js';
2
+ export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell, useUnsavedChanges } from './chunk-J43HCK7Q.js';
3
3
  import { Skeleton } from './chunk-Z63OSN64.js';
4
4
  export { Skeleton } from './chunk-Z63OSN64.js';
5
- import { FOCUSABLE, usePopupFocus, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberFocus, focusInitial, OVERLAY_CLOSE_ATTR, focusAfterRemoval, rovingTabStop, useGridKeyboard, useNonModalDialog, usePressMarks, useReturnFocus, useCloseOnFocusOut } from './chunk-5XOJZQIP.js';
5
+ import { FOCUSABLE, usePopupFocus, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberFocus, focusInitial, OVERLAY_CLOSE_ATTR, focusAfterRemoval, rovingTabStop, useGridKeyboard, useNonModalDialog, usePressMarks, useReturnFocus, useCloseOnFocusOut } from './chunk-YQABRUWD.js';
6
+ export { useGridKeyboard } from './chunk-YQABRUWD.js';
6
7
  import { Link } from './chunk-XGSQOMEY.js';
7
8
  export { CodeBlock, Divider, Link } from './chunk-XGSQOMEY.js';
8
9
  import { Badge } from './chunk-PVTGMCVR.js';
@@ -18,7 +19,7 @@ import { mergeRefs, Slot } from './chunk-7Q46JXSK.js';
18
19
  import { useLatest } from './chunk-WH2FUFAC.js';
19
20
  import { Icon } from './chunk-4LDYR7QP.js';
20
21
  import { cn, GLASS_SURFACE, ACTIVE_RING, GLASS_BACKDROP, GLASS_DROPDOWN, lockBodyScroll, unlockBodyScroll, GLASS_OVERLAY, ERROR_ACTIVE_RING, ERROR_FOCUS_RING, FOCUS_RING, ERROR_HAS_FOCUS_RING, HAS_FOCUS_RING, ERROR_FOCUS_WITHIN_RING, FOCUS_WITHIN_RING } from './chunk-4KVFH5KC.js';
21
- import { createContext, forwardRef, useState, useRef, useCallback, useLayoutEffect, useMemo, useContext, useId, memo, useEffect, Children, isValidElement, cloneElement, Fragment as Fragment$1, useSyncExternalStore } from 'react';
22
+ import { createContext, forwardRef, useState, useRef, useCallback, useLayoutEffect, useMemo, useContext, useId, memo, useEffect, useImperativeHandle, Children, isValidElement, cloneElement, Fragment as Fragment$1, useSyncExternalStore } from 'react';
22
23
  import { createPortal, flushSync } from 'react-dom';
23
24
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
24
25
 
@@ -1311,12 +1312,12 @@ function parseTypedDate(text, dayFirst = localeDayFirst()) {
1311
1312
  }
1312
1313
  return null;
1313
1314
  }
1314
- function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, className, ref, container, error = false, label, id, size: sizeProp, min, max, range }) {
1315
+ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, className, ref, container, error = false, label, id, size: sizeProp, min, max, range: range2 }) {
1315
1316
  const contextSize = useContext(ControlSizeContext);
1316
1317
  const size = sizeProp ?? contextSize ?? "md";
1317
1318
  const [open, setOpen] = useState(false);
1318
1319
  const [hover, setHover] = useState(null);
1319
- const [viewDate, setViewDate] = useState(() => value ?? range?.start ?? range?.end ?? /* @__PURE__ */ new Date());
1320
+ const [viewDate, setViewDate] = useState(() => value ?? range2?.start ?? range2?.end ?? /* @__PURE__ */ new Date());
1320
1321
  const triggerRef = useRef(null);
1321
1322
  const buttonRef = useRef(null);
1322
1323
  const panelRef = useRef(null);
@@ -1331,12 +1332,12 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1331
1332
  const today = useMemo(() => /* @__PURE__ */ new Date(), []);
1332
1333
  const startOf = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
1333
1334
  const isDisabledDay = (d) => !!min && d < startOf(min) || !!max && d > startOf(max);
1334
- const [focusDate, setFocusDate] = useState(() => startOf(value ?? range?.start ?? range?.end ?? /* @__PURE__ */ new Date()));
1335
+ const [focusDate, setFocusDate] = useState(() => startOf(value ?? range2?.start ?? range2?.end ?? /* @__PURE__ */ new Date()));
1335
1336
  const [prevOpen, setPrevOpen] = useState(open);
1336
1337
  if (open !== prevOpen) {
1337
1338
  setPrevOpen(open);
1338
1339
  if (open) {
1339
- const start = startOf(value ?? range?.start ?? range?.end ?? today);
1340
+ const start = startOf(value ?? range2?.start ?? range2?.end ?? today);
1340
1341
  setFocusDate(start);
1341
1342
  setViewDate(new Date(start.getFullYear(), start.getMonth(), 1));
1342
1343
  }
@@ -1493,8 +1494,8 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1493
1494
  if (isDisabledDay(d)) return;
1494
1495
  choose(d);
1495
1496
  };
1496
- const rangeA = range?.start ?? null;
1497
- const rangeB = range?.end ?? null;
1497
+ const rangeA = range2?.start ?? null;
1498
+ const rangeB = range2?.end ?? null;
1498
1499
  const otherEnd = value ? rangeA && !isSameDay(rangeA, value) ? rangeA : rangeB : rangeA ?? rangeB;
1499
1500
  const thisEnd = value ?? (otherEnd && hover ? hover : null);
1500
1501
  const [bandStart, bandEnd] = otherEnd && thisEnd ? otherEnd <= thisEnd ? [otherEnd, thisEnd] : [thisEnd, otherEnd] : [null, null];
@@ -2182,12 +2183,12 @@ function fileFormat(file) {
2182
2183
  function matches(file, tokens) {
2183
2184
  if (tokens.length === 0 || tokens.some((t) => t.kind === "any")) return true;
2184
2185
  const name = file.name.toLowerCase();
2185
- const format = fileFormat(file);
2186
+ const format2 = fileFormat(file);
2186
2187
  const type = file.type.toLowerCase();
2187
2188
  return tokens.some((t) => {
2188
2189
  if (t.kind === "family") return type === "" || type.startsWith(`${t.family}/`);
2189
2190
  if (t.kind !== "format") return false;
2190
- return t.name === format || t.suffix != null && name.endsWith(t.suffix) || t.mime != null && t.mime === type;
2191
+ return t.name === format2 || t.suffix != null && name.endsWith(t.suffix) || t.mime != null && t.mime === type;
2191
2192
  });
2192
2193
  }
2193
2194
  var joinOr = (items) => items.length < 2 ? items.join("") : `${items.slice(0, -1).join(", ")} or ${items[items.length - 1]}`;
@@ -2199,9 +2200,9 @@ function typeWords(accept) {
2199
2200
  const tokens = parseAccept(accept);
2200
2201
  if (tokens.length === 0 || tokens.some((t) => t.kind === "any" || t.kind === "family" && !FAMILIES[t.family])) return null;
2201
2202
  const families = [...new Set(tokens.flatMap((t) => t.kind === "family" ? [t.family] : []))].map((f) => FAMILIES[f]);
2202
- const formats = [...new Set(tokens.flatMap((t) => t.kind === "format" ? [t.name] : []))];
2203
- const many = `${joinOr([...families.map((f) => f.many), ...formats])}${formats.length ? " files" : ""}`;
2204
- const one = families.length === 0 ? `${article(formats[0])} ${joinOr(formats)}` : joinOr([...families.map((f) => f.one), ...formats.map((f) => `${article(f)} ${f}`)]);
2203
+ const formats2 = [...new Set(tokens.flatMap((t) => t.kind === "format" ? [t.name] : []))];
2204
+ const many = `${joinOr([...families.map((f) => f.many), ...formats2])}${formats2.length ? " files" : ""}`;
2205
+ const one = families.length === 0 ? `${article(formats2[0])} ${joinOr(formats2)}` : joinOr([...families.map((f) => f.one), ...formats2.map((f) => `${article(f)} ${f}`)]);
2205
2206
  return { one, many };
2206
2207
  }
2207
2208
  function sizeWords(bytes) {
@@ -9323,6 +9324,1231 @@ var StackedList = forwardRef(function StackedList2({
9323
9324
  }
9324
9325
  );
9325
9326
  });
9327
+ var TIME_ZOOMS = ["day", "week", "month", "quarter"];
9328
+ var DAY_MS = 864e5;
9329
+ var DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
9330
+ var utcDay = (y, m, d) => Math.round(Date.UTC(y, m, d) / DAY_MS);
9331
+ function dayOfDate(d) {
9332
+ if (Number.isNaN(d.getTime())) return NaN;
9333
+ const ms = ((d.getHours() * 60 + d.getMinutes()) * 60 + d.getSeconds()) * 1e3 + d.getMilliseconds();
9334
+ return utcDay(d.getFullYear(), d.getMonth(), d.getDate()) + ms / DAY_MS;
9335
+ }
9336
+ function dayOf(value) {
9337
+ if (value instanceof Date) return dayOfDate(value);
9338
+ const m = DATE_ONLY.exec(value.trim());
9339
+ if (!m) return dayOfDate(new Date(value));
9340
+ const [y, mo, d] = [Number(m[1]), Number(m[2]) - 1, Number(m[3])];
9341
+ const day = utcDay(y, mo, d);
9342
+ const back = new Date(day * DAY_MS);
9343
+ return back.getUTCFullYear() === y && back.getUTCMonth() === mo && back.getUTCDate() === d ? day : NaN;
9344
+ }
9345
+ function dateOfDay(day) {
9346
+ const whole = Math.floor(day);
9347
+ const u = new Date(whole * DAY_MS);
9348
+ return new Date(u.getUTCFullYear(), u.getUTCMonth(), u.getUTCDate(), 0, 0, 0, Math.round((day - whole) * DAY_MS));
9349
+ }
9350
+ function endDayOf(value) {
9351
+ const day = dayOf(value);
9352
+ const wholeDay2 = typeof value === "string" ? DATE_ONLY.test(value.trim()) : Number.isInteger(day);
9353
+ return wholeDay2 ? day + 1 : day;
9354
+ }
9355
+ var MINOR = { day: "day", week: "week", month: "month", quarter: "quarter" };
9356
+ var MAJOR = { day: "month", week: "month", month: "year", quarter: "year" };
9357
+ var weekday = (day) => (day % 7 + 7 + 4) % 7;
9358
+ function periodStart(unit, day, weekStartsOn) {
9359
+ const d = Math.floor(day);
9360
+ if (unit === "day") return d;
9361
+ if (unit === "week") return d - (weekday(d) - weekStartsOn + 7) % 7;
9362
+ const u = new Date(d * DAY_MS);
9363
+ const y = u.getUTCFullYear();
9364
+ const m = u.getUTCMonth();
9365
+ if (unit === "month") return utcDay(y, m, 1);
9366
+ if (unit === "quarter") return utcDay(y, m - m % 3, 1);
9367
+ return utcDay(y, 0, 1);
9368
+ }
9369
+ function periodAfter(unit, start) {
9370
+ if (unit === "day") return start + 1;
9371
+ if (unit === "week") return start + 7;
9372
+ const u = new Date(start * DAY_MS);
9373
+ const y = u.getUTCFullYear();
9374
+ const m = u.getUTCMonth();
9375
+ if (unit === "month") return utcDay(y, m + 1, 1);
9376
+ if (unit === "quarter") return utcDay(y, m + 3, 1);
9377
+ return utcDay(y + 1, 0, 1);
9378
+ }
9379
+ var formats = /* @__PURE__ */ new Map();
9380
+ function formatter(locale, options) {
9381
+ const key = `${locale ?? ""}|${JSON.stringify(options)}`;
9382
+ let f = formats.get(key);
9383
+ if (!f) {
9384
+ f = new Intl.DateTimeFormat(locale, { ...options, timeZone: "UTC" });
9385
+ formats.set(key, f);
9386
+ }
9387
+ return f;
9388
+ }
9389
+ function format(day, locale, options) {
9390
+ return formatter(locale, options).format(new Date(day * DAY_MS));
9391
+ }
9392
+ var pad22 = (n) => String(n).padStart(2, "0");
9393
+ function keyOf(unit, day) {
9394
+ const u = new Date(day * DAY_MS);
9395
+ const y = u.getUTCFullYear();
9396
+ const m = u.getUTCMonth();
9397
+ if (unit === "year") return String(y);
9398
+ if (unit === "quarter") return `${y}-Q${Math.floor(m / 3) + 1}`;
9399
+ if (unit === "month") return `${y}-${pad22(m + 1)}`;
9400
+ return `${y}-${pad22(m + 1)}-${pad22(u.getUTCDate())}`;
9401
+ }
9402
+ function labelsOf(unit, role, day, locale) {
9403
+ const f = (options) => format(day, locale, options);
9404
+ let forms;
9405
+ if (unit === "day") forms = [f({ weekday: "short", day: "numeric" }), f({ day: "numeric" })];
9406
+ else if (unit === "week") forms = [f({ day: "numeric", month: "short" }), f({ day: "numeric" })];
9407
+ else if (unit === "month") {
9408
+ forms = role === "major" ? [f({ month: "long", year: "numeric" }), f({ month: "short", year: "numeric" }), f({ month: "short" }), f({ month: "narrow" })] : [f({ month: "long" }), f({ month: "short" }), f({ month: "narrow" })];
9409
+ } else if (unit === "quarter") forms = [`Q${Math.floor(new Date(day * DAY_MS).getUTCMonth() / 3) + 1}`];
9410
+ else forms = [f({ year: "numeric" }), f({ year: "2-digit" })];
9411
+ return [...new Set(forms)];
9412
+ }
9413
+ var DENSITY = { day: 56, week: 16, month: 4, quarter: 1.5 };
9414
+ var todays = /* @__PURE__ */ new WeakMap();
9415
+ function calendarDay(day) {
9416
+ const d = Math.floor(day);
9417
+ return { day: d, weekday: weekday(d), key: keyOf("day", d) };
9418
+ }
9419
+ function ticks(unit, role, s) {
9420
+ const out = [];
9421
+ for (let p = periodStart(unit, s.startDay, s.weekStartsOn); p < s.endDay; p = periodAfter(unit, p)) {
9422
+ const from = Math.max(p, s.startDay);
9423
+ const to = Math.min(periodAfter(unit, p), s.endDay);
9424
+ out.push({ key: keyOf(unit, p), day: from, x: (from - s.startDay) * s.pxPerDay, width: (to - from) * s.pxPerDay, weekday: weekday(from), labels: labelsOf(unit, role, p, s.locale) });
9425
+ }
9426
+ return out;
9427
+ }
9428
+ function build(s) {
9429
+ const { startDay, pxPerDay } = s;
9430
+ const x = (value) => (startIndex(value) - startDay) * pxPerDay;
9431
+ const inRange = (d) => d >= startDay && d < s.endDay;
9432
+ const todayDay = Math.floor(s.todayValue);
9433
+ const scale = {
9434
+ zoom: s.zoom,
9435
+ startDay,
9436
+ endDay: s.endDay,
9437
+ pxPerDay,
9438
+ width: (s.endDay - startDay) * pxPerDay,
9439
+ x,
9440
+ span: (start, end = start) => {
9441
+ const x0 = x(start);
9442
+ return { x: x0, width: Math.max(0, (endIndex(end) - startDay) * pxPerDay - x0) };
9443
+ },
9444
+ dayAt: (px) => startDay + px / pxPerDay,
9445
+ major: ticks(MAJOR[s.zoom], "major", s),
9446
+ minor: ticks(MINOR[s.zoom], "minor", s),
9447
+ today: inRange(todayDay) ? todayDay : null,
9448
+ now: inRange(s.todayValue) ? s.todayValue : null,
9449
+ weekStartsOn: s.weekStartsOn,
9450
+ locale: s.locale
9451
+ };
9452
+ todays.set(scale, s.todayValue);
9453
+ return scale;
9454
+ }
9455
+ var toMinute = (day) => Math.round(day * 1440) / 1440;
9456
+ var startIndex = (value) => typeof value === "number" ? value : dayOf(value);
9457
+ var endIndex = (value) => typeof value === "number" ? value : endDayOf(value);
9458
+ function scaleOver(from, to, zoom, todayValue, weekStartsOn, locale, pxPerDay, pad3) {
9459
+ const fallback = Number.isFinite(todayValue) ? Math.floor(todayValue) : 0;
9460
+ const a = Number.isFinite(from) ? from : fallback;
9461
+ const b = Number.isFinite(to) ? to : Math.floor(a) + 1;
9462
+ const lo = Math.floor(Math.min(a, b));
9463
+ const hi = Math.max(Math.ceil(Math.max(a, b)), lo + 1);
9464
+ const unit = MINOR[zoom];
9465
+ let startDay = periodStart(unit, lo, weekStartsOn);
9466
+ let endDay = periodAfter(unit, periodStart(unit, hi - 1, weekStartsOn));
9467
+ for (let i = 0; i < pad3; i++) {
9468
+ startDay = periodStart(unit, startDay - 1, weekStartsOn);
9469
+ endDay = periodAfter(unit, endDay);
9470
+ }
9471
+ return build({ zoom, startDay, endDay, pxPerDay: pxPerDay > 0 ? pxPerDay : DENSITY[zoom], todayValue, weekStartsOn, locale });
9472
+ }
9473
+ function extendTimeScale(scale, width) {
9474
+ if (!(width > scale.width) || !(scale.pxPerDay > 0)) return scale;
9475
+ const unit = MINOR[scale.zoom];
9476
+ const last = scale.startDay + Math.ceil(width / scale.pxPerDay) - 1;
9477
+ const endDay = periodAfter(unit, periodStart(unit, last, scale.weekStartsOn));
9478
+ const todayValue = todays.get(scale) ?? scale.now ?? scale.today ?? NaN;
9479
+ return build({ zoom: scale.zoom, startDay: scale.startDay, endDay, pxPerDay: scale.pxPerDay, todayValue, weekStartsOn: scale.weekStartsOn, locale: scale.locale });
9480
+ }
9481
+ function useTimeScale({ start, end, zoom, today, weekStartsOn = 1, locale, pxPerDay, pad: pad3 = 0 }) {
9482
+ const [mounted] = useState(() => toMinute(dayOf(/* @__PURE__ */ new Date())));
9483
+ const from = startIndex(start);
9484
+ const to = endIndex(end);
9485
+ const todayValue = today === void 0 ? mounted : toMinute(dayOf(today));
9486
+ const density = pxPerDay?.[zoom] ?? DENSITY[zoom];
9487
+ return useMemo(() => scaleOver(from, to, zoom, todayValue, weekStartsOn, locale, density, pad3), [from, to, zoom, todayValue, weekStartsOn, locale, density, pad3]);
9488
+ }
9489
+ function timeExtent(ranges) {
9490
+ let start = Infinity;
9491
+ let end = -Infinity;
9492
+ for (const r of ranges) {
9493
+ const s = dayOf(r.start);
9494
+ const e = endDayOf(r.end ?? r.start);
9495
+ if (Number.isFinite(s)) start = Math.min(start, s);
9496
+ if (Number.isFinite(e)) end = Math.max(end, e);
9497
+ }
9498
+ return Number.isFinite(start) && Number.isFinite(end) ? { start, end: Math.max(start, end) } : { start: NaN, end: NaN };
9499
+ }
9500
+ var DAY_WORDS = { day: "numeric", month: "long" };
9501
+ var TIME_WORDS = { hour: "2-digit", minute: "2-digit" };
9502
+ var wholeDay = (value) => typeof value === "string" ? DATE_ONLY.test(value.trim()) : Number.isInteger(value instanceof Date ? dayOf(value) : value);
9503
+ var SEPARATOR = /\s*[\u2010-\u2015\u2212~\uff5e\u301c-]\s*/;
9504
+ var english = (f) => new Intl.Locale(f.resolvedOptions().locale).language === "en";
9505
+ function range(f, a, b, to) {
9506
+ if (typeof f.formatRangeToParts !== "function") return to === void 0 ? f.formatRange(a, b) : `${f.format(a)}${to}${f.format(b)}`;
9507
+ const parts = f.formatRangeToParts(a, b);
9508
+ const lastOfStart = parts.map((p) => p.source).lastIndexOf("startRange");
9509
+ const firstOfEnd = parts.findIndex((p) => p.source === "endRange");
9510
+ if (lastOfStart < 0 || firstOfEnd < 0) return f.format(a);
9511
+ if (to === void 0) return parts.map((p) => p.value).join("");
9512
+ let joined = false;
9513
+ const out = parts.map((p, i) => {
9514
+ if (joined || i <= lastOfStart || i >= firstOfEnd || !SEPARATOR.test(p.value)) return p.value;
9515
+ joined = true;
9516
+ return p.value.replace(SEPARATOR, to);
9517
+ });
9518
+ if (!joined) out.splice(lastOfStart + 1, 0, to);
9519
+ return out.join("");
9520
+ }
9521
+ function formatDateRange(start, end = start, { locale, to, day = DAY_WORDS, time = TIME_WORDS } = {}) {
9522
+ const from = startIndex(start);
9523
+ const stop = endIndex(end);
9524
+ if (!Number.isFinite(from) || !Number.isFinite(stop)) return "";
9525
+ const timed = !wholeDay(start) || !wholeDay(end);
9526
+ const first = timed ? from : Math.floor(from);
9527
+ const last = timed ? Math.max(from, stop) : Math.max(first, Math.ceil(stop) - 1);
9528
+ const a = new Date(first * DAY_MS);
9529
+ const b = new Date(last * DAY_MS);
9530
+ const years = a.getUTCFullYear() !== b.getUTCFullYear() && day.year === void 0;
9531
+ const f = formatter(locale, { ...day, ...years ? { year: "numeric" } : {}, ...timed ? time : {} });
9532
+ return first === last ? f.format(a) : range(f, a, b, to ?? (english(f) ? " to " : void 0));
9533
+ }
9534
+ function createScrollStore() {
9535
+ let current = { scrollLeft: 0, visibleWidth: 0 };
9536
+ const listeners = /* @__PURE__ */ new Set();
9537
+ return {
9538
+ get: () => current,
9539
+ set(next) {
9540
+ if (next.scrollLeft === current.scrollLeft && next.visibleWidth === current.visibleWidth) return;
9541
+ current = next;
9542
+ listeners.forEach((l) => l());
9543
+ },
9544
+ subscribe(listener) {
9545
+ listeners.add(listener);
9546
+ return () => {
9547
+ listeners.delete(listener);
9548
+ };
9549
+ }
9550
+ };
9551
+ }
9552
+ var TimeViewportCtx = createContext(null);
9553
+ var TimeViewportScrollCtx = createContext(null);
9554
+ var UNMEASURED = { scrollLeft: 0, visibleWidth: 0 };
9555
+ var noSubscription = () => () => {
9556
+ };
9557
+ var unmeasured = () => UNMEASURED;
9558
+ function useTimeViewportLayout() {
9559
+ const layout = useContext(TimeViewportCtx);
9560
+ if (!layout) throw new Error("A time-axis row or header must be inside a <TimeViewport>.");
9561
+ return layout;
9562
+ }
9563
+ function useTimeViewport() {
9564
+ const store = useContext(TimeViewportScrollCtx);
9565
+ const scroll = useSyncExternalStore(store?.subscribe ?? noSubscription, store?.get ?? unmeasured, unmeasured);
9566
+ return { ...useTimeViewportLayout(), ...scroll };
9567
+ }
9568
+ function fitLabels(boxes, measure, { gap = 8, align = "start", visible } = {}) {
9569
+ const out = boxes.map((b) => ({ key: b.key, x: b.x, width: 0, text: null }));
9570
+ const n = boxes.length;
9571
+ const levels = boxes.reduce((most, b) => Math.max(most, b.labels.length), 0);
9572
+ if (!n || !levels) return out;
9573
+ const widths = /* @__PURE__ */ new Map();
9574
+ const w = (text) => {
9575
+ let v = widths.get(text);
9576
+ if (v === void 0) {
9577
+ v = Math.max(0, measure(text) || 0);
9578
+ widths.set(text, v);
9579
+ }
9580
+ return v;
9581
+ };
9582
+ const order = boxes.map((_, i) => i).sort((a, b) => boxes[a].x - boxes[b].x || a - b);
9583
+ const rowEnd = order.reduce((end, i) => Math.max(end, boxes[i].x + Math.max(0, boxes[i].width)), -Infinity);
9584
+ const form = (b, level) => b.labels.length ? b.labels[Math.min(level, b.labels.length - 1)] : null;
9585
+ for (let step = 1; step <= n; step++) {
9586
+ const shown = [];
9587
+ for (let k = 0; k < n; k += step) shown.push(order[k]);
9588
+ const room = shown.map((i, s) => ({ start: boxes[i].x, end: s + 1 < shown.length ? boxes[shown[s + 1]].x : rowEnd }));
9589
+ const fits = (s, text) => text === null || w(text) + gap <= room[s].end - room[s].start;
9590
+ const decides = (s) => shown.length < 3 || s > 0 && s < shown.length - 1;
9591
+ let level = -1;
9592
+ for (let l = 0; l < levels && level < 0; l++) {
9593
+ if (shown.every((i, s) => !decides(s) || fits(s, form(boxes[i], l)))) level = l;
9594
+ }
9595
+ if (level < 0) continue;
9596
+ shown.forEach((i, s) => {
9597
+ const b = boxes[i];
9598
+ const lo = Math.max(room[s].start + gap / 2, visible ? visible.start + gap / 2 : -Infinity);
9599
+ const hi = Math.min(room[s].end - gap / 2, visible ? visible.end - gap / 2 : Infinity);
9600
+ let text = null;
9601
+ for (let l = level; l < Math.max(level + 1, b.labels.length) && text === null; l++) {
9602
+ const t = form(b, l);
9603
+ if (t !== null && fits(s, t) && w(t) <= hi - lo) text = t;
9604
+ }
9605
+ if (text === null) return;
9606
+ const tw = w(text);
9607
+ const from = visible ? Math.max(b.x, visible.start) : b.x;
9608
+ const to = Math.min(b.x + Math.max(0, b.width), visible ? visible.end : Infinity);
9609
+ const x = align === "center" ? (from + to) / 2 - tw / 2 : lo;
9610
+ out[i] = { key: b.key, x: Math.min(Math.max(x, lo), hi - tw), width: tw, text };
9611
+ });
9612
+ return out;
9613
+ }
9614
+ return out;
9615
+ }
9616
+ function fitLabel(labels, width, measure, { gap = 8 } = {}) {
9617
+ return labels.find((text) => Math.max(0, measure(text) || 0) + gap <= width) ?? null;
9618
+ }
9619
+ var context;
9620
+ function canvasContext() {
9621
+ if (context !== void 0) return context;
9622
+ context = null;
9623
+ if (typeof document === "undefined" || typeof navigator !== "undefined" && /jsdom/i.test(navigator.userAgent)) return context;
9624
+ try {
9625
+ context = document.createElement("canvas").getContext("2d");
9626
+ } catch {
9627
+ context = null;
9628
+ }
9629
+ return context;
9630
+ }
9631
+ function fontOf(el) {
9632
+ const cs = getComputedStyle(el);
9633
+ const size = parseFloat(cs.fontSize) || 12;
9634
+ return { font: `${cs.fontStyle || "normal"} ${cs.fontWeight || "400"} ${size}px ${cs.fontFamily || "sans-serif"}`, size, letterSpacing: cs.letterSpacing || "normal" };
9635
+ }
9636
+ function measurer(info) {
9637
+ const cache = /* @__PURE__ */ new Map();
9638
+ return (text) => {
9639
+ let v = cache.get(text);
9640
+ if (v !== void 0) return v;
9641
+ const ctx = info ? canvasContext() : null;
9642
+ if (ctx && info) {
9643
+ ctx.font = info.font;
9644
+ if ("letterSpacing" in ctx) ctx.letterSpacing = info.letterSpacing === "normal" ? "0px" : info.letterSpacing;
9645
+ v = ctx.measureText(text).width;
9646
+ } else {
9647
+ v = text.length * (info?.size ?? 12) * 0.6;
9648
+ }
9649
+ cache.set(text, v);
9650
+ return v;
9651
+ };
9652
+ }
9653
+ function useTextMeasure(ref) {
9654
+ const [info, setInfo] = useState(null);
9655
+ useLayoutEffect(() => {
9656
+ const el = ref.current;
9657
+ if (!el) return;
9658
+ let live = true;
9659
+ const read = (fresh) => {
9660
+ if (!live) return;
9661
+ const next = fontOf(el);
9662
+ setInfo((cur) => !fresh && cur && cur.font === next.font && cur.letterSpacing === next.letterSpacing ? cur : next);
9663
+ };
9664
+ read(false);
9665
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => read(false));
9666
+ ro?.observe(el);
9667
+ const fonts = typeof document === "undefined" ? void 0 : document.fonts;
9668
+ const loaded = () => read(true);
9669
+ fonts?.addEventListener?.("loadingdone", loaded);
9670
+ fonts?.ready?.then(loaded, () => {
9671
+ });
9672
+ return () => {
9673
+ live = false;
9674
+ ro?.disconnect();
9675
+ fonts?.removeEventListener?.("loadingdone", loaded);
9676
+ };
9677
+ }, [ref]);
9678
+ return useMemo(() => measurer(info), [info]);
9679
+ }
9680
+ var fontGeneration = 0;
9681
+ var fontListeners = /* @__PURE__ */ new Set();
9682
+ var fontsWatched = false;
9683
+ function watchFonts() {
9684
+ if (fontsWatched || typeof document === "undefined" || !document.fonts) return;
9685
+ fontsWatched = true;
9686
+ const moveOn = () => {
9687
+ fontGeneration++;
9688
+ shared.clear();
9689
+ fontListeners.forEach((l) => l());
9690
+ };
9691
+ document.fonts.addEventListener?.("loadingdone", moveOn);
9692
+ document.fonts.ready?.then(moveOn, () => {
9693
+ });
9694
+ }
9695
+ var shared = /* @__PURE__ */ new Map();
9696
+ function sharedMeasurer(info) {
9697
+ const key = `${fontGeneration}|${info.font}|${info.letterSpacing}`;
9698
+ let m = shared.get(key);
9699
+ if (!m) {
9700
+ m = measurer(info);
9701
+ shared.set(key, m);
9702
+ }
9703
+ return m;
9704
+ }
9705
+ var textWatchers = /* @__PURE__ */ new Map();
9706
+ var textObserver = null;
9707
+ function watchText(el, onResize) {
9708
+ if (typeof ResizeObserver === "undefined") return () => {
9709
+ };
9710
+ textObserver ??= new ResizeObserver((entries) => entries.forEach((e) => textWatchers.get(e.target)?.()));
9711
+ textWatchers.set(el, onResize);
9712
+ textObserver.observe(el);
9713
+ return () => {
9714
+ textWatchers.delete(el);
9715
+ textObserver?.unobserve(el);
9716
+ };
9717
+ }
9718
+ var NO_BORDERS = { start: 0, end: 0 };
9719
+ function useElementMetrics(ref, deps, text) {
9720
+ const [read, setRead] = useState({ info: null, borders: NO_BORDERS });
9721
+ const [generation, setGeneration] = useState(fontGeneration);
9722
+ useLayoutEffect(() => {
9723
+ const el = ref.current;
9724
+ if (!el) return;
9725
+ const readNow = () => {
9726
+ const cs = getComputedStyle(el);
9727
+ const next = fontOf(el);
9728
+ const start = parseFloat(cs.borderInlineStartWidth || cs.borderLeftWidth) || 0;
9729
+ const end = parseFloat(cs.borderInlineEndWidth || cs.borderRightWidth) || 0;
9730
+ setRead(
9731
+ (cur) => cur.info?.font === next.font && cur.info.letterSpacing === next.letterSpacing && cur.borders.start === start && cur.borders.end === end ? cur : { info: next, borders: { start, end } }
9732
+ );
9733
+ };
9734
+ readNow();
9735
+ const watched = text?.current;
9736
+ return watched ? watchText(watched, readNow) : void 0;
9737
+ }, deps);
9738
+ useLayoutEffect(() => {
9739
+ watchFonts();
9740
+ const listener = () => setGeneration(fontGeneration);
9741
+ fontListeners.add(listener);
9742
+ return () => {
9743
+ fontListeners.delete(listener);
9744
+ };
9745
+ }, []);
9746
+ const measure = useMemo(() => read.info ? sharedMeasurer(read.info) : measurer(null), [read.info, generation]);
9747
+ return { measure, borders: read.borders };
9748
+ }
9749
+ function useFittedLabels(boxes, ref, options = {}) {
9750
+ const measure = useTextMeasure(ref);
9751
+ const { gap, align } = options;
9752
+ const start = options.visible?.start;
9753
+ const end = options.visible?.end;
9754
+ return useMemo(
9755
+ () => fitLabels(boxes, measure, { gap, align, visible: start === void 0 || end === void 0 ? void 0 : { start, end } }),
9756
+ [boxes, measure, gap, align, start, end]
9757
+ );
9758
+ }
9759
+ var TODAY_INSET = 4;
9760
+ var TODAY_PAD_X = 6;
9761
+ function TimeScaleHeader({ todayLabel = "Today", className }) {
9762
+ const { scale, scrollLeft, visibleWidth, todayX: line } = useTimeViewport();
9763
+ const majorRef = useRef(null);
9764
+ const minorRef = useRef(null);
9765
+ const todayRef = useRef(null);
9766
+ const measured = visibleWidth > 0;
9767
+ const view = { start: scrollLeft, end: scrollLeft + visibleWidth };
9768
+ const major = useFittedLabels(scale.major, majorRef, measured ? { visible: view } : {});
9769
+ const minor = useFittedLabels(scale.minor, minorRef, measured ? { align: "center", visible: view } : { align: "center" });
9770
+ const measureToday = useTextMeasure(todayRef);
9771
+ const todayWidth = measureToday(todayLabel) + TODAY_PAD_X * 2;
9772
+ const todayShown = line !== null && (!measured || line >= view.start && line <= view.end);
9773
+ let todayX = line === null ? 0 : line - todayWidth / 2;
9774
+ if (measured) todayX = Math.max(view.start + TODAY_INSET, Math.min(todayX, view.end - TODAY_INSET - todayWidth));
9775
+ const underToday = (x, width) => todayShown && x < todayX + todayWidth + TODAY_INSET && x + width > todayX - TODAY_INSET;
9776
+ return /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", "data-time-scale": "", className: cn("relative select-none text-xs", className), style: { width: scale.width }, children: [
9777
+ /* @__PURE__ */ jsxs("div", { "data-scale-row": "major", className: "relative h-7 font-medium text-[var(--ds-text-primary)]", children: [
9778
+ /* @__PURE__ */ jsx("span", { ref: majorRef, className: "invisible absolute whitespace-nowrap leading-none", children: "0" }),
9779
+ scale.major.slice(1).map((t) => /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 border-s-[length:1px] border-[var(--ds-border-subtle)]", style: { insetInlineStart: t.x } }, `line-${t.key}`)),
9780
+ major.map((l) => l.text && /* @__PURE__ */ jsx("span", { "data-scale-label": "", className: "absolute top-1/2 -translate-y-1/2 whitespace-nowrap leading-none", style: { insetInlineStart: l.x }, children: l.text }, l.key))
9781
+ ] }),
9782
+ /* @__PURE__ */ jsxs("div", { "data-scale-row": "minor", className: "relative h-7 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] text-[var(--ds-text-secondary)]", children: [
9783
+ /* @__PURE__ */ jsx("span", { ref: minorRef, className: "invisible absolute whitespace-nowrap leading-none tabular-nums", children: "0" }),
9784
+ scale.minor.slice(1).map((t) => /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 border-s-[length:1px] border-[var(--ds-border-subtle)]", style: { insetInlineStart: t.x } }, `line-${t.key}`)),
9785
+ minor.map((l) => l.text && !underToday(l.x, l.width) && /* @__PURE__ */ jsx("span", { "data-scale-label": "", className: "absolute top-1/2 -translate-y-1/2 whitespace-nowrap leading-none tabular-nums", style: { insetInlineStart: l.x }, children: l.text }, l.key)),
9786
+ /* @__PURE__ */ jsx(
9787
+ "span",
9788
+ {
9789
+ ref: todayRef,
9790
+ "data-scale-today": "",
9791
+ className: cn(
9792
+ "absolute top-1/2 -translate-y-1/2 whitespace-nowrap leading-none font-medium",
9793
+ // the transparent border draws the pill's outline in forced-colours mode
9794
+ "rounded-[var(--ds-radius-full)] py-ds-0.5 border-[length:1px] border-transparent bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]",
9795
+ !todayShown && "invisible"
9796
+ ),
9797
+ style: { insetInlineStart: todayX, paddingInline: TODAY_PAD_X },
9798
+ children: todayLabel
9799
+ }
9800
+ )
9801
+ ] })
9802
+ ] });
9803
+ }
9804
+ var ZOOM_NAMES = { day: "Days", week: "Weeks", month: "Months", quarter: "Quarters" };
9805
+ var SHADE_MIN = 4;
9806
+ var INSET = 8;
9807
+ var MIN_INSET = 2;
9808
+ var MIN_TITLE = 2 * INSET + 12;
9809
+ var PHONE_BELOW = 640;
9810
+ var MIN_LABEL = 112;
9811
+ var ROW_HEIGHT = 40;
9812
+ var MARGIN = 16;
9813
+ var WHEEL_STEP = 50;
9814
+ var WHEEL_PAUSE = 250;
9815
+ var FIELD = "input, select, textarea, [contenteditable='true']";
9816
+ var border = "border-[var(--ds-border-subtle)]";
9817
+ function writeScroll(el, visibleWidth) {
9818
+ el.style.setProperty("--time-scroll", String(el.scrollLeft));
9819
+ el.style.setProperty("--time-view-end", String(el.scrollLeft + visibleWidth));
9820
+ }
9821
+ var TimeViewport = forwardRef(function TimeViewport2({
9822
+ scale,
9823
+ onZoomChange,
9824
+ zooms = TIME_ZOOMS,
9825
+ labelWidth: labelWidthProp = 240,
9826
+ labelHeader,
9827
+ header,
9828
+ toolbar,
9829
+ initialDate,
9830
+ showToday = true,
9831
+ todayAt = "day",
9832
+ shadeDays,
9833
+ tabStop = true,
9834
+ children,
9835
+ "aria-label": ariaLabel,
9836
+ className
9837
+ }, ref) {
9838
+ const scrollerRef = useRef(null);
9839
+ const headerRef = useRef(null);
9840
+ const [frame, setFrame] = useState({ width: 0, header: 0 });
9841
+ useLayoutEffect(() => {
9842
+ const el = scrollerRef.current;
9843
+ if (!el) return;
9844
+ const read = () => {
9845
+ const width = el.clientWidth;
9846
+ const headerHeight = headerRef.current?.offsetHeight ?? 0;
9847
+ setFrame((cur) => cur.width === width && cur.header === headerHeight ? cur : { width, header: headerHeight });
9848
+ };
9849
+ read();
9850
+ if (typeof ResizeObserver === "undefined") return;
9851
+ const ro = new ResizeObserver(read);
9852
+ ro.observe(el);
9853
+ if (headerRef.current) ro.observe(headerRef.current);
9854
+ return () => ro.disconnect();
9855
+ }, []);
9856
+ const phone = frame.width > 0 && frame.width < PHONE_BELOW;
9857
+ const labelWidth = phone ? Math.min(labelWidthProp, Math.max(MIN_LABEL, Math.round(frame.width * 0.36))) : labelWidthProp;
9858
+ const visibleWidth = Math.max(0, frame.width - labelWidth);
9859
+ const drawn = useMemo(() => extendTimeScale(scale, visibleWidth), [scale, visibleWidth]);
9860
+ const [store] = useState(createScrollStore);
9861
+ const lastLeft = useRef(0);
9862
+ const latest = useLatest({ drawn, visibleWidth, labelWidth, headerHeight: frame.header });
9863
+ const setLeft = useCallback((left) => {
9864
+ const el = scrollerRef.current;
9865
+ if (!el) return;
9866
+ el.scrollLeft = Math.max(0, left);
9867
+ lastLeft.current = el.scrollLeft;
9868
+ writeScroll(el, latest.current.visibleWidth);
9869
+ store.set({ scrollLeft: el.scrollLeft, visibleWidth: latest.current.visibleWidth });
9870
+ }, [store, latest]);
9871
+ const api = useMemo(() => {
9872
+ const ensureVisible = (x0, x1) => {
9873
+ const el = scrollerRef.current;
9874
+ if (!el) return;
9875
+ const vw = latest.current.visibleWidth;
9876
+ const left = el.scrollLeft;
9877
+ const lo = Math.min(x0, x1);
9878
+ const hi = Math.max(x0, x1);
9879
+ if (hi - lo > vw - 2 * MARGIN) {
9880
+ if (hi < left + MARGIN || lo > left + vw - MARGIN) setLeft(lo - MARGIN);
9881
+ } else if (lo < left + MARGIN) setLeft(lo - MARGIN);
9882
+ else if (hi > left + vw - MARGIN) setLeft(hi - vw + MARGIN);
9883
+ };
9884
+ return {
9885
+ scrollToDate(value, align = "start") {
9886
+ const { drawn: s, visibleWidth: vw } = latest.current;
9887
+ const x = s.x(value);
9888
+ setLeft(align === "center" ? x - vw / 2 : x - MARGIN);
9889
+ },
9890
+ page(direction) {
9891
+ const { drawn: s, visibleWidth: vw } = latest.current;
9892
+ const target = (scrollerRef.current?.scrollLeft ?? 0) + direction * vw;
9893
+ const tick = s.minor.reduce((best, t) => Math.abs(t.x - target) < Math.abs(best - target) ? t.x : best, Infinity);
9894
+ setLeft(Number.isFinite(tick) ? tick : target);
9895
+ },
9896
+ ensureVisible,
9897
+ reveal(target) {
9898
+ const el = scrollerRef.current;
9899
+ if (!el || !el.contains(target)) return;
9900
+ const { labelWidth: lw, headerHeight } = latest.current;
9901
+ const box = el.getBoundingClientRect();
9902
+ const r = target.getBoundingClientRect();
9903
+ const top = box.top + headerHeight;
9904
+ const bottom = box.top + el.clientHeight;
9905
+ if (r.top < top) el.scrollTop -= top - r.top;
9906
+ else if (r.bottom > bottom) el.scrollTop += Math.min(r.bottom - bottom, r.top - top);
9907
+ const x0 = r.left - box.left - lw + el.scrollLeft;
9908
+ ensureVisible(x0, x0 + r.width);
9909
+ }
9910
+ };
9911
+ }, [latest, setLeft]);
9912
+ useImperativeHandle(ref, () => api, [api]);
9913
+ const todayAtX = todayAt === "now" ? drawn.now === null ? null : drawn.x(drawn.now) : drawn.today === null ? null : drawn.x(drawn.today) + drawn.pxPerDay / 2;
9914
+ const todayX = showToday ? todayAtX : null;
9915
+ const layout = useMemo(() => ({ scale: drawn, labelWidth, showToday, todayX, compact: phone, ...api }), [drawn, labelWidth, showToday, todayX, phone, api]);
9916
+ const pending = useRef(null);
9917
+ const applied = useRef(null);
9918
+ useLayoutEffect(() => {
9919
+ const el = scrollerRef.current;
9920
+ if (!el || visibleWidth <= 0) return;
9921
+ writeScroll(el, visibleWidth);
9922
+ store.set({ scrollLeft: el.scrollLeft, visibleWidth });
9923
+ const prev = applied.current;
9924
+ applied.current = { startDay: drawn.startDay, pxPerDay: drawn.pxPerDay, visibleWidth };
9925
+ if (!prev) {
9926
+ const at = initialDate !== void 0 ? drawn.x(initialDate) : todayAtX;
9927
+ if (at !== null && Number.isFinite(at)) setLeft(at - visibleWidth / 2);
9928
+ return;
9929
+ }
9930
+ const rescaled = prev.startDay !== drawn.startDay || prev.pxPerDay !== drawn.pxPerDay;
9931
+ if (!rescaled && prev.visibleWidth === visibleWidth) return;
9932
+ const want = pending.current;
9933
+ pending.current = null;
9934
+ const anchor = want && want.zoom === drawn.zoom ? want : { day: prev.startDay + (lastLeft.current + prev.visibleWidth / 2) / prev.pxPerDay, at: visibleWidth / 2 };
9935
+ setLeft(drawn.x(anchor.day) - anchor.at);
9936
+ const focused = document.activeElement;
9937
+ if (focused instanceof HTMLElement && focused !== el && el.contains(focused)) api.reveal(focused);
9938
+ }, [drawn, visibleWidth, initialDate, todayAtX, setLeft, store, api]);
9939
+ const shaded = [];
9940
+ if (shadeDays && drawn.pxPerDay >= SHADE_MIN) {
9941
+ for (let d = drawn.startDay; d < drawn.endDay; d++) {
9942
+ if (!shadeDays(calendarDay(d))) continue;
9943
+ const last = shaded[shaded.length - 1];
9944
+ if (last && Math.abs(last.x + last.width - drawn.x(d)) < 0.5) last.width += drawn.pxPerDay;
9945
+ else shaded.push({ key: String(d), x: drawn.x(d), width: drawn.pxPerDay });
9946
+ }
9947
+ }
9948
+ const levels = TIME_ZOOMS.filter((z) => zooms.includes(z));
9949
+ const zoomTo = (direction) => {
9950
+ if (!onZoomChange) return null;
9951
+ const at = TIME_ZOOMS.indexOf(scale.zoom);
9952
+ const side = levels.filter((z) => direction < 0 ? TIME_ZOOMS.indexOf(z) < at : TIME_ZOOMS.indexOf(z) > at);
9953
+ return (direction < 0 ? side[side.length - 1] : side[0]) ?? null;
9954
+ };
9955
+ const focusedAt = (el) => {
9956
+ const f = document.activeElement;
9957
+ if (!(f instanceof HTMLElement) || f === el || !el.contains(f)) return void 0;
9958
+ const r = f.getBoundingClientRect();
9959
+ const x = (r.left + r.right) / 2 - el.getBoundingClientRect().left - labelWidth;
9960
+ return x >= 0 && x <= visibleWidth ? x : void 0;
9961
+ };
9962
+ const zoom = (direction, at) => {
9963
+ const next = zoomTo(direction);
9964
+ if (!next || !onZoomChange) return false;
9965
+ const el = scrollerRef.current;
9966
+ if (el && visibleWidth > 0) {
9967
+ const x = at ?? focusedAt(el) ?? visibleWidth / 2;
9968
+ pending.current = { zoom: next, day: drawn.dayAt(el.scrollLeft + x), at: x };
9969
+ }
9970
+ onZoomChange(next);
9971
+ return true;
9972
+ };
9973
+ const canZoomIn = zoomTo(-1) !== null;
9974
+ const canZoomOut = zoomTo(1) !== null;
9975
+ const panByPeriod = (direction) => {
9976
+ const left = scrollerRef.current?.scrollLeft ?? 0;
9977
+ const xs = drawn.minor.map((t) => t.x);
9978
+ const target = direction > 0 ? xs.find((x) => x > left + 1) ?? drawn.width : [...xs].reverse().find((x) => x < left - 1) ?? 0;
9979
+ setLeft(target);
9980
+ };
9981
+ const onKeyDown = (e) => {
9982
+ if (e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey) return;
9983
+ if (e.target instanceof HTMLElement && e.target.closest(FIELD)) return;
9984
+ if (e.key === "+" || e.key === "=") {
9985
+ if (zoom(-1)) e.preventDefault();
9986
+ } else if (e.key === "-" || e.key === "\u2212") {
9987
+ if (zoom(1)) e.preventDefault();
9988
+ } else if (e.shiftKey && (e.key === "ArrowLeft" || e.key === "ArrowRight")) {
9989
+ e.preventDefault();
9990
+ panByPeriod(e.key === "ArrowRight" ? 1 : -1);
9991
+ }
9992
+ };
9993
+ const wheel = useRef({ sum: 0, at: -Infinity });
9994
+ const onWheel = useLatest((e) => {
9995
+ if (!(e.ctrlKey || e.metaKey) || !onZoomChange) return;
9996
+ e.preventDefault();
9997
+ const w = wheel.current;
9998
+ if (e.timeStamp - w.at < WHEEL_PAUSE) return;
9999
+ w.sum += e.deltaY * (e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? 400 : 1);
10000
+ if (Math.abs(w.sum) < WHEEL_STEP) return;
10001
+ const el = scrollerRef.current;
10002
+ const x = el ? e.clientX - el.getBoundingClientRect().left - labelWidth : NaN;
10003
+ zoom(w.sum < 0 ? -1 : 1, x >= 0 && x <= visibleWidth ? x : void 0);
10004
+ wheel.current = { sum: 0, at: e.timeStamp };
10005
+ });
10006
+ useEffect(() => {
10007
+ const el = scrollerRef.current;
10008
+ if (!el) return;
10009
+ const listener = (e) => onWheel.current(e);
10010
+ el.addEventListener("wheel", listener, { passive: false });
10011
+ return () => el.removeEventListener("wheel", listener);
10012
+ }, [onWheel]);
10013
+ const drag = useRef(null);
10014
+ const [dragging, setDragging] = useState(false);
10015
+ const onScaleDown = (e) => {
10016
+ if (e.button !== 0 || e.pointerType === "touch" || !scrollerRef.current) return;
10017
+ drag.current = { id: e.pointerId, x: e.clientX, left: scrollerRef.current.scrollLeft };
10018
+ e.currentTarget.setPointerCapture?.(e.pointerId);
10019
+ setDragging(true);
10020
+ };
10021
+ const onScaleMove = (e) => {
10022
+ const d = drag.current;
10023
+ if (d && d.id === e.pointerId) setLeft(d.left - (e.clientX - d.x));
10024
+ };
10025
+ const onScaleUp = (e) => {
10026
+ if (drag.current?.id !== e.pointerId) return;
10027
+ drag.current = null;
10028
+ setDragging(false);
10029
+ };
10030
+ const onScroll = () => {
10031
+ const el = scrollerRef.current;
10032
+ if (!el) return;
10033
+ lastLeft.current = el.scrollLeft;
10034
+ writeScroll(el, visibleWidth);
10035
+ store.set({ scrollLeft: el.scrollLeft, visibleWidth });
10036
+ };
10037
+ const headerNode = header === void 0 ? /* @__PURE__ */ jsx(TimeScaleHeader, {}) : header;
10038
+ const hasHeader = headerNode != null || labelHeader != null;
10039
+ const bar = toolbar === false ? null : /* @__PURE__ */ jsxs("div", { className: cn("flex shrink-0 flex-wrap items-center gap-ds-1 px-ds-2 py-ds-1.5 border-b-[length:var(--ds-border-width)]", border), children: [
10040
+ onZoomChange && levels.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
10041
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Zoom out", "aria-disabled": !canZoomOut || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(1), children: /* @__PURE__ */ jsx(Icon, { name: "minus", size: 14 }) }),
10042
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "min-w-[4.5rem] text-center text-xs text-[var(--ds-text-secondary)]", children: ZOOM_NAMES[scale.zoom] }),
10043
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Zoom in", "aria-disabled": !canZoomIn || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(-1), children: /* @__PURE__ */ jsx(Icon, { name: "plus", size: 14 }) })
10044
+ ] }),
10045
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Previous period", onClick: () => api.page(-1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-left", size: 14 }) }),
10046
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", disabled: todayAtX === null, onClick: () => {
10047
+ if (todayAtX !== null) setLeft(todayAtX - visibleWidth / 2);
10048
+ }, children: "Today" }),
10049
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Next period", onClick: () => api.page(1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) }),
10050
+ toolbar
10051
+ ] });
10052
+ return (
10053
+ // Inline-size containment: the rows' width never widens the frame (in a grid or flex parent it would, and the
10054
+ // wider frame would extend the scale again). The viewport takes the width it is given and scrolls inside it.
10055
+ /* @__PURE__ */ jsx("div", { "data-time-viewport": "", "data-compact": phone || void 0, className: cn("flex h-full min-h-0 w-full min-w-0 flex-col [contain:inline-size] bg-[var(--ds-bg-surface)]", className), onKeyDown, children: /* @__PURE__ */ jsx(TimeViewportCtx, { value: layout, children: /* @__PURE__ */ jsxs(TimeViewportScrollCtx, { value: store, children: [
10056
+ bar,
10057
+ /* @__PURE__ */ jsxs("div", { className: "relative min-h-0 flex-1", children: [
10058
+ /* @__PURE__ */ jsx(
10059
+ "div",
10060
+ {
10061
+ ref: scrollerRef,
10062
+ role: "region",
10063
+ "aria-label": ariaLabel,
10064
+ tabIndex: tabStop ? 0 : void 0,
10065
+ onScroll,
10066
+ className: "peer h-full overflow-auto isolate outline-none",
10067
+ style: { scrollPaddingInlineStart: labelWidth, scrollPaddingTop: frame.header },
10068
+ children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col", style: { width: labelWidth + drawn.width }, children: [
10069
+ hasHeader && /* @__PURE__ */ jsxs("div", { ref: headerRef, className: cn("sticky top-0 z-[3] flex shrink-0 bg-[var(--ds-bg-surface)] border-b-[length:var(--ds-border-width)]", border), children: [
10070
+ /* @__PURE__ */ jsx(
10071
+ "div",
10072
+ {
10073
+ className: cn("sticky start-0 z-[1] flex shrink-0 items-end px-ds-3 py-ds-1.5 bg-[var(--ds-bg-surface)] border-e-[length:var(--ds-border-width)] text-xs font-medium text-[var(--ds-text-secondary)]", border),
10074
+ style: { width: labelWidth },
10075
+ children: labelHeader
10076
+ }
10077
+ ),
10078
+ /* @__PURE__ */ jsx(
10079
+ "div",
10080
+ {
10081
+ "data-time-scale-area": "",
10082
+ className: cn("shrink-0 select-none", dragging ? "cursor-grabbing" : "cursor-grab"),
10083
+ style: { width: drawn.width },
10084
+ onPointerDown: onScaleDown,
10085
+ onPointerMove: onScaleMove,
10086
+ onPointerUp: onScaleUp,
10087
+ onPointerCancel: onScaleUp,
10088
+ children: headerNode
10089
+ }
10090
+ )
10091
+ ] }),
10092
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col", children: [
10093
+ /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-y-0", style: { insetInlineStart: labelWidth, width: drawn.width }, children: [
10094
+ shaded.map((b) => /* @__PURE__ */ jsx("span", { "data-shaded-days": "", className: "absolute inset-y-0 bg-[var(--ds-bg-muted)] opacity-70", style: { insetInlineStart: b.x, width: b.width } }, b.key)),
10095
+ drawn.minor.slice(1).map((t) => /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 border-s-[length:1px] border-[var(--ds-border-subtle)]", style: { insetInlineStart: t.x } }, t.key)),
10096
+ todayX !== null && /* @__PURE__ */ jsx("span", { "data-today-line": "", className: "absolute inset-y-0 -translate-x-1/2 border-s-2 border-[var(--ds-accent-primary)]", style: { insetInlineStart: todayX } })
10097
+ ] }),
10098
+ children,
10099
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: cn("sticky start-0 z-[2] min-h-0 flex-1 bg-[var(--ds-bg-surface)] border-e-[length:var(--ds-border-width)]", border), style: { width: labelWidth } })
10100
+ ] })
10101
+ ] })
10102
+ }
10103
+ ),
10104
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 peer-focus-visible:shadow-[inset_0_0_0_2px_var(--ds-border-focus)]" })
10105
+ ] })
10106
+ ] }) }) })
10107
+ );
10108
+ });
10109
+ function TimeRow({ label, children, height = ROW_HEIGHT, labelClassName, className, style, ...props }) {
10110
+ const { scale, labelWidth } = useTimeViewportLayout();
10111
+ return /* @__PURE__ */ jsxs("div", { ...props, className: cn("flex shrink-0", className), style: { height, ...style }, children: [
10112
+ /* @__PURE__ */ jsx(
10113
+ "div",
10114
+ {
10115
+ className: cn("sticky start-0 z-[2] flex shrink-0 items-center px-ds-3 bg-[var(--ds-bg-surface)] border-e-[length:var(--ds-border-width)] border-b-[length:var(--ds-border-width)] text-sm text-[var(--ds-text-primary)]", border, labelClassName),
10116
+ style: { width: labelWidth },
10117
+ children: /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 truncate", children: label })
10118
+ }
10119
+ ),
10120
+ /* @__PURE__ */ jsx("div", { className: cn("relative shrink-0 border-b-[length:var(--ds-border-width)]", border), style: { width: scale.width }, children })
10121
+ ] });
10122
+ }
10123
+ function TimeBar({ start, end, minWidth = 0, labels, children, className, style, ...props }) {
10124
+ const { scale, labelWidth } = useTimeViewportLayout();
10125
+ const barRef = useRef(null);
10126
+ const textRef = useRef(null);
10127
+ const hasLabels = !!labels?.length;
10128
+ const { measure, borders } = useElementMetrics(barRef, [className, hasLabels], textRef);
10129
+ const { x, width } = scale.span(start, end ?? start);
10130
+ const drawnWidth = Math.max(width, minWidth);
10131
+ const inView = `(min(${x + drawnWidth - borders.end}, var(--time-view-end, 1e9)) - max(${x + borders.start}, var(--time-scroll, 0)))`;
10132
+ const sticky = { insetInlineStart: labelWidth, maxWidth: `min(100%, calc(${inView} * 1px))` };
10133
+ let label = null;
10134
+ if (labels && hasLabels) {
10135
+ const widths = labels.map(measure);
10136
+ const inset = Math.min(INSET, Math.max(MIN_INSET, (drawnWidth - borders.start - borders.end - Math.min(...widths)) / 2 - 0.5));
10137
+ const fits = (i) => `clamp(0, calc((${inView} - ${widths[i] + 2 * inset} + 0.25) * 1000), 1)`;
10138
+ const shows = (i) => i === 0 ? fits(0) : `calc(${[fits(i), ...labels.slice(0, i).map((_, j) => `(1 - ${fits(j)})`)].join(" * ")})`;
10139
+ const named = props["aria-label"] !== void 0 || props["aria-labelledby"] !== void 0;
10140
+ label = /* @__PURE__ */ jsxs(Fragment, { children: [
10141
+ /* @__PURE__ */ jsx("span", { "data-time-bar-label": "", "aria-hidden": "true", className: "sticky grid overflow-hidden whitespace-nowrap", style: { ...sticky, paddingInline: inset }, children: labels.map((text, i) => /* @__PURE__ */ jsx("span", { ref: i === 0 ? textRef : void 0, "data-time-bar-form": "", className: "justify-self-start [grid-area:1/1]", style: { opacity: shows(i) }, children: text }, i)) }),
10142
+ !named && /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels[0] })
10143
+ ] });
10144
+ } else if (children != null && children !== false) {
10145
+ label = /* @__PURE__ */ jsx(
10146
+ "span",
10147
+ {
10148
+ "data-time-bar-label": "",
10149
+ className: "sticky flex min-w-0 items-center gap-ds-1 overflow-hidden whitespace-nowrap",
10150
+ style: { ...sticky, paddingInline: INSET, opacity: `clamp(0, calc((${inView} - ${MIN_TITLE}) * 1000), 1)` },
10151
+ children: typeof children === "string" ? /* @__PURE__ */ jsx("span", { className: "truncate", children }) : children
10152
+ }
10153
+ );
10154
+ }
10155
+ return /* @__PURE__ */ jsx(
10156
+ "div",
10157
+ {
10158
+ ...props,
10159
+ ref: barRef,
10160
+ className: cn("absolute top-ds-2 bottom-ds-2 flex items-center overflow-clip rounded-[var(--ds-radius-sm)] outline-none focus-visible:shadow-[var(--ds-shadow-focus)]", className),
10161
+ style: { insetInlineStart: x, width: drawnWidth, ...style },
10162
+ children: label
10163
+ }
10164
+ );
10165
+ }
10166
+
10167
+ // ../../showcase/src/lib/gantt-layout.ts
10168
+ var ROW_HEIGHT2 = 40;
10169
+ var BAR_HEIGHT = 24;
10170
+ var MIN_BAR = 6;
10171
+ var DIAMOND = 12;
10172
+ var TIP = DIAMOND * Math.SQRT2 / 2;
10173
+ var STUB = 8;
10174
+ var HEAD = 6;
10175
+ var BESIDE = STUB + 6;
10176
+ var groupKey = (id) => `group:${id}`;
10177
+ var itemKey = (id) => `item:${id}`;
10178
+ function ganttRows(items, groups, collapsed) {
10179
+ const known = new Set(groups.map((g) => g.id));
10180
+ const byGroup = /* @__PURE__ */ new Map();
10181
+ const loose = [];
10182
+ for (const item of items) {
10183
+ if (item.group === void 0 || !known.has(item.group)) loose.push(item);
10184
+ else byGroup.set(item.group, [...byGroup.get(item.group) ?? [], item]);
10185
+ }
10186
+ const shown = groups.filter((g) => byGroup.has(g.id));
10187
+ const topLevel = shown.length + loose.length;
10188
+ const rows = [];
10189
+ const push = (row) => rows.push({ ...row, index: rows.length, top: rows.length * ROW_HEIGHT2 });
10190
+ let pos = 0;
10191
+ for (const group of shown) {
10192
+ const children = byGroup.get(group.id);
10193
+ const key = groupKey(group.id);
10194
+ push({ key, kind: "group", level: 1, setSize: topLevel, posInSet: ++pos, group, children });
10195
+ if (collapsed.has(group.id)) continue;
10196
+ children.forEach((item, i) => push({ key: itemKey(item.id), kind: "item", level: 2, setSize: children.length, posInSet: i + 1, parentKey: key, item }));
10197
+ }
10198
+ for (const item of loose) push({ key: itemKey(item.id), kind: "item", level: 1, setSize: topLevel, posInSet: ++pos, item });
10199
+ return rows;
10200
+ }
10201
+ function itemShape(item, scale) {
10202
+ if (item.milestone) {
10203
+ const centre = scale.x((dayOf(item.start) + endDayOf(item.start)) / 2);
10204
+ return { kind: "milestone", x0: centre - TIP, x1: centre + TIP, in: centre - TIP, out: centre + TIP };
10205
+ }
10206
+ const { x, width } = scale.span(item.start, item.end ?? item.start);
10207
+ const x1 = x + Math.max(width, MIN_BAR);
10208
+ return { kind: "bar", x0: x, x1, in: x, out: x1 };
10209
+ }
10210
+ function groupExtent(items) {
10211
+ return timeExtent(items.map((i) => ({ start: i.start, end: i.milestone ? void 0 : i.end })));
10212
+ }
10213
+ var drawable = (shape) => !!shape && Number.isFinite(shape.x0) && Number.isFinite(shape.x1);
10214
+ function ganttArrows(items, rows, shapeOf, collapsed) {
10215
+ const rowOf = /* @__PURE__ */ new Map();
10216
+ const groupRow = /* @__PURE__ */ new Map();
10217
+ for (const row of rows) {
10218
+ if (row.item) rowOf.set(row.item.id, row);
10219
+ if (row.group) groupRow.set(row.group.id, row);
10220
+ }
10221
+ const drawnRow = (item) => rowOf.get(item.id) ?? (item.group !== void 0 && collapsed.has(item.group) ? groupRow.get(item.group) : void 0);
10222
+ const byId = new Map(items.map((i) => [i.id, i]));
10223
+ const arrows = [];
10224
+ for (const succ of items) {
10225
+ for (const id of new Set(succ.dependsOn ?? [])) {
10226
+ const pred = byId.get(id);
10227
+ if (!pred || pred === succ) continue;
10228
+ const from = drawnRow(pred);
10229
+ const to = drawnRow(succ);
10230
+ const a = shapeOf(pred);
10231
+ const b = shapeOf(succ);
10232
+ if (!from || !to || from === to || !drawable(a) || !drawable(b)) continue;
10233
+ const x1 = a.out;
10234
+ const y1 = from.top + ROW_HEIGHT2 / 2;
10235
+ const x2 = b.in;
10236
+ const y2 = to.top + ROW_HEIGHT2 / 2;
10237
+ const routed = x2 < x1 + STUB + HEAD;
10238
+ const edge = y2 > y1 ? from.top + ROW_HEIGHT2 : from.top;
10239
+ const d = routed ? `M${x1} ${y1}H${x1 + STUB}V${edge}H${x2 - STUB - HEAD}V${y2}H${x2}` : `M${x1} ${y1}H${x1 + STUB}V${y2}H${x2}`;
10240
+ const head = `M${x2} ${y2}L${x2 - HEAD} ${y2 - HEAD / 2}L${x2 - HEAD} ${y2 + HEAD / 2}Z`;
10241
+ arrows.push({ key: `${pred.id}>${succ.id}`, from: pred.id, to: succ.id, d, head, routed, x1, y1, x2, y2 });
10242
+ }
10243
+ }
10244
+ return arrows;
10245
+ }
10246
+ function labelPlace(shape, textWidth, scaleWidth, { pad: pad3 = 8, gap = BESIDE } = {}) {
10247
+ if (shape.kind === "bar" && textWidth + 2 * pad3 <= shape.x1 - shape.x0) return "inside";
10248
+ if (shape.x1 + gap + textWidth <= scaleWidth) return "after";
10249
+ return shape.x0 - gap - textWidth >= 0 ? "before" : "after";
10250
+ }
10251
+ var TONES = {
10252
+ neutral: { fill: "bg-[var(--ds-status-neutral)]", on: "text-[var(--ds-text-on-neutral)]" },
10253
+ info: { fill: "bg-[var(--ds-status-info)]", on: "text-[var(--ds-text-on-info)]" },
10254
+ success: { fill: "bg-[var(--ds-status-success)]", on: "text-[var(--ds-text-on-success)]", icon: "check" },
10255
+ warning: { fill: "bg-[var(--ds-status-warning)]", on: "text-[var(--ds-text-on-warning)]", icon: "warning" },
10256
+ error: { fill: "bg-[var(--ds-status-error)]", on: "text-[var(--ds-text-on-error)]", icon: "error" },
10257
+ accent: { fill: "bg-[var(--ds-accent-primary)]", on: "text-[var(--ds-text-on-primary)]" }
10258
+ };
10259
+ var ICON = 12;
10260
+ var ICON_GAP = 4;
10261
+ var LEVEL_INDENT = 16;
10262
+ var TARGET = 24;
10263
+ var KNOCKOUT = 2;
10264
+ var SELECTED = "outline-solid outline-2 outline-offset-2 outline-[var(--ds-text-primary)]";
10265
+ function GanttArrows({ arrows, width, height }) {
10266
+ const { labelWidth } = useTimeViewportLayout();
10267
+ return /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", "data-gantt-arrows": "", className: "pointer-events-none absolute top-0 overflow-visible", style: { insetInlineStart: labelWidth, width, height }, children: arrows.map((a) => /* @__PURE__ */ jsxs("g", { "data-from": a.from, "data-to": a.to, "data-routed": a.routed || void 0, className: "text-[var(--ds-text-muted)]", children: [
10268
+ /* @__PURE__ */ jsx("path", { d: a.d, fill: "none", stroke: "currentColor", strokeWidth: 1.5 }),
10269
+ /* @__PURE__ */ jsx("path", { d: a.head, fill: "currentColor" })
10270
+ ] }, a.key)) });
10271
+ }
10272
+ function Legend({ statuses }) {
10273
+ const entries = Object.entries(statuses);
10274
+ if (!entries.length) return null;
10275
+ return /* @__PURE__ */ jsx("ul", { "aria-label": "Status key", className: "ms-auto flex flex-wrap items-center gap-x-ds-3 gap-y-ds-1 ps-ds-2 text-xs text-[var(--ds-text-secondary)]", children: entries.map(([key, s]) => {
10276
+ const tone = TONES[s.tone] ?? TONES.neutral;
10277
+ return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-ds-1.5", children: [
10278
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("grid size-4 shrink-0 place-items-center rounded-[var(--ds-radius-sm)]", tone.fill, tone.on), children: tone.icon && /* @__PURE__ */ jsx(Icon, { name: tone.icon, size: 10 }) }),
10279
+ s.label
10280
+ ] }, key);
10281
+ }) });
10282
+ }
10283
+ function Gantt({
10284
+ items,
10285
+ groups = [],
10286
+ selectedId,
10287
+ onSelect,
10288
+ statuses = {},
10289
+ zoom: zoomProp,
10290
+ defaultZoom = "week",
10291
+ onZoomChange,
10292
+ today,
10293
+ weekStartsOn,
10294
+ locale,
10295
+ renderLabel,
10296
+ labelHeader = "Task",
10297
+ toolbar,
10298
+ "aria-label": ariaLabel,
10299
+ className
10300
+ }) {
10301
+ const [zoomState, setZoomState] = useState(defaultZoom);
10302
+ const zoom = zoomProp ?? zoomState;
10303
+ const changeZoom = (next) => {
10304
+ if (zoomProp === void 0) setZoomState(next);
10305
+ onZoomChange?.(next);
10306
+ };
10307
+ const [selectedState, setSelectedState] = useState(null);
10308
+ const selected = selectedId !== void 0 ? selectedId : selectedState;
10309
+ const select = (item) => {
10310
+ if (selectedId === void 0) setSelectedState(item?.id ?? null);
10311
+ onSelect?.(item);
10312
+ };
10313
+ const [collapsed, setCollapsed] = useState(() => new Set(groups.filter((g) => g.defaultCollapsed).map((g) => g.id)));
10314
+ const extent = useMemo(() => timeExtent(items.map((i) => ({ start: i.start, end: i.milestone ? void 0 : i.end }))), [items]);
10315
+ const scale = useTimeScale({ start: extent.start, end: extent.end, zoom, today, weekStartsOn, locale, pad: 1 });
10316
+ const rows = useMemo(() => ganttRows(items, groups, collapsed), [items, groups, collapsed]);
10317
+ const shapes = useMemo(() => new Map(items.map((i) => [i.id, itemShape(i, scale)])), [items, scale]);
10318
+ const extents = useMemo(() => new Map(rows.filter((r) => r.children).map((r) => [r.group.id, groupExtent(r.children)])), [rows]);
10319
+ const arrows = useMemo(() => ganttArrows(items, rows, (i) => shapes.get(i.id), collapsed), [items, rows, shapes, collapsed]);
10320
+ const byKey = useMemo(() => new Map(rows.map((r) => [r.key, r])), [rows]);
10321
+ const names = useMemo(() => {
10322
+ const byId = new Map(items.map((i) => [i.id, i]));
10323
+ const list = new Intl.ListFormat(locale, { type: "conjunction" });
10324
+ const out = /* @__PURE__ */ new Map();
10325
+ for (const item of items) {
10326
+ const after = (item.dependsOn ?? []).flatMap((id) => byId.has(id) && id !== item.id ? [byId.get(id).title] : []);
10327
+ const status = item.status === void 0 ? void 0 : statuses[item.status];
10328
+ out.set(itemKey(item.id), [
10329
+ item.title,
10330
+ item.milestone ? "milestone" : null,
10331
+ formatDateRange(item.start, item.milestone ? item.start : item.end ?? item.start, { locale }),
10332
+ status?.label ?? null,
10333
+ after.length ? `after ${list.format(after)}` : null
10334
+ ].filter((part) => part).join(", "));
10335
+ }
10336
+ for (const row of rows) {
10337
+ if (!row.group || !row.children) continue;
10338
+ const e = extents.get(row.group.id);
10339
+ const count = row.children.length === 1 ? "1 item" : `${row.children.length} items`;
10340
+ out.set(row.key, [row.group.title, formatDateRange(e.start, e.end, { locale }), count].filter((part) => part).join(", "));
10341
+ }
10342
+ return out;
10343
+ }, [items, rows, extents, statuses, locale]);
10344
+ const probeRef = useRef(null);
10345
+ const measure = useTextMeasure(probeRef);
10346
+ const treeRef = useRef(null);
10347
+ const viewportRef = useRef(null);
10348
+ const [activeState, setActive] = useState(null);
10349
+ const selectedKey = selected === null ? null : itemKey(selected);
10350
+ const active = activeState !== null && byKey.has(activeState) ? activeState : selectedKey !== null && byKey.has(selectedKey) ? selectedKey : rows[0]?.key ?? "";
10351
+ const toggle = (id) => {
10352
+ const key = groupKey(id);
10353
+ const closing = !collapsed.has(id);
10354
+ setCollapsed((cur) => {
10355
+ const next = new Set(cur);
10356
+ if (next.has(id)) next.delete(id);
10357
+ else next.add(id);
10358
+ return next;
10359
+ });
10360
+ if (closing && byKey.get(active)?.parentKey === key) {
10361
+ setActive(key);
10362
+ if (treeRef.current?.contains(document.activeElement)) grid.focus(key);
10363
+ }
10364
+ };
10365
+ const activate = (row) => {
10366
+ if (row.group) toggle(row.group.id);
10367
+ else if (row.item) select(row.item);
10368
+ };
10369
+ const grid = useGridKeyboard({
10370
+ container: treeRef,
10371
+ active,
10372
+ setActive,
10373
+ keyOf: (key) => key,
10374
+ move: (from, e) => {
10375
+ if (e.shiftKey) return null;
10376
+ const row = byKey.get(from);
10377
+ if (!row) return null;
10378
+ const next = rows[row.index + 1];
10379
+ switch (e.key) {
10380
+ case "ArrowDown":
10381
+ return next?.key ?? from;
10382
+ case "ArrowUp":
10383
+ return rows[row.index - 1]?.key ?? from;
10384
+ case "Home":
10385
+ return rows[0].key;
10386
+ case "End":
10387
+ return rows[rows.length - 1].key;
10388
+ case "ArrowRight":
10389
+ if (!row.group) return from;
10390
+ if (collapsed.has(row.group.id)) {
10391
+ toggle(row.group.id);
10392
+ return from;
10393
+ }
10394
+ return next?.parentKey === row.key ? next.key : from;
10395
+ case "ArrowLeft":
10396
+ if (row.group) {
10397
+ if (!collapsed.has(row.group.id)) toggle(row.group.id);
10398
+ return from;
10399
+ }
10400
+ return row.parentKey ?? from;
10401
+ default:
10402
+ return null;
10403
+ }
10404
+ },
10405
+ onActivate: (key) => {
10406
+ const row = byKey.get(key);
10407
+ if (row) activate(row);
10408
+ },
10409
+ typeahead: { items: rows.map((r) => r.key), labelOf: (key) => byKey.get(key)?.item?.title ?? byKey.get(key)?.group?.title ?? "" },
10410
+ scrollIntoView: (_key, el) => viewportRef.current?.reveal(el)
10411
+ });
10412
+ const onKeyDown = (e) => {
10413
+ if (e.key === "Escape" && selected !== null && !e.defaultPrevented && !e.altKey && !e.ctrlKey && !e.metaKey) {
10414
+ e.preventDefault();
10415
+ select(null);
10416
+ return;
10417
+ }
10418
+ grid.onKeyDown(e);
10419
+ };
10420
+ const onRowClick = (row) => {
10421
+ if (row.item) select(row.item.id === selected ? null : row.item);
10422
+ else if (row.group) toggle(row.group.id);
10423
+ setActive(row.key);
10424
+ grid.focus(row.key);
10425
+ };
10426
+ const treeitem = (row) => ({
10427
+ role: "treeitem",
10428
+ "aria-level": row.level,
10429
+ "aria-setsize": row.setSize,
10430
+ "aria-posinset": row.posInSet,
10431
+ "aria-label": names.get(row.key),
10432
+ "data-grid-key": row.key,
10433
+ tabIndex: grid.tabIndexOf(row.key),
10434
+ onFocus: () => setActive(row.key)
10435
+ });
10436
+ const renderItem = (row, item) => {
10437
+ const shape = shapes.get(item.id);
10438
+ if (!drawable(shape)) return null;
10439
+ const tone = TONES[item.status !== void 0 && statuses[item.status]?.tone || "neutral"] ?? TONES.neutral;
10440
+ const isSelected = item.id === selected;
10441
+ const textWidth = measure(item.title) + (tone.icon ? ICON + ICON_GAP : 0) + 2 * KNOCKOUT;
10442
+ const place = labelPlace(shape, textWidth, scale.width);
10443
+ const beside = place === "after" ? shape.x1 + BESIDE : shape.x0 - BESIDE - textWidth;
10444
+ const icon = tone.icon && /* @__PURE__ */ jsx(Icon, { name: tone.icon, size: ICON, className: "shrink-0" });
10445
+ const title = /* @__PURE__ */ jsxs(Fragment, { children: [
10446
+ icon,
10447
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: item.title })
10448
+ ] });
10449
+ const shapeBox = shape.kind === "milestone" ? /* @__PURE__ */ jsx("div", { ...treeitem(row), "aria-selected": isSelected, className: cn("group absolute grid cursor-pointer place-items-center", "outline-none"), style: { insetInlineStart: (shape.x0 + shape.x1) / 2 - TARGET / 2, top: (ROW_HEIGHT2 - TARGET) / 2, width: TARGET, height: TARGET }, children: /* @__PURE__ */ jsx("span", { "data-gantt-shape": "milestone", "data-item": item.id, className: cn("block rotate-45 rounded-[2px] group-focus-visible:shadow-[var(--ds-shadow-focus)]", tone.fill, isSelected && SELECTED), style: { width: DIAMOND, height: DIAMOND } }) }) : (
10450
+ // TimeBar keeps the title in view beside the pinned column while the bar's start is scrolled away
10451
+ /* @__PURE__ */ jsx(
10452
+ TimeBar,
10453
+ {
10454
+ ...treeitem(row),
10455
+ "aria-selected": isSelected,
10456
+ "data-gantt-shape": "bar",
10457
+ "data-item": item.id,
10458
+ start: item.start,
10459
+ end: item.end ?? item.start,
10460
+ minWidth: MIN_BAR,
10461
+ className: cn("cursor-pointer text-xs font-medium", tone.fill, tone.on, isSelected && SELECTED),
10462
+ style: { top: (ROW_HEIGHT2 - BAR_HEIGHT) / 2, bottom: "auto", height: BAR_HEIGHT },
10463
+ children: place === "inside" && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "flex min-w-0 items-center gap-ds-1 overflow-hidden", children: title })
10464
+ }
10465
+ )
10466
+ );
10467
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
10468
+ shapeBox,
10469
+ place !== "inside" && // Beside the bar, past the turn an arrow makes; its own background keeps lines that cross the row off the
10470
+ // text. Once it is partly under the pinned column or past the view's end it is hidden, never a fragment.
10471
+ /* @__PURE__ */ jsx(
10472
+ "span",
10473
+ {
10474
+ "aria-hidden": "true",
10475
+ className: "pointer-events-none absolute top-1/2 flex -translate-y-1/2 items-center gap-ds-1 whitespace-nowrap rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-surface)] text-xs font-medium leading-4 text-[var(--ds-text-primary)]",
10476
+ style: { insetInlineStart: beside, paddingInline: KNOCKOUT, opacity: `clamp(0, calc(min(${beside} - var(--time-scroll, 0), var(--time-view-end, 1e9) - ${beside + textWidth}) * 1000), 1)` },
10477
+ children: title
10478
+ }
10479
+ )
10480
+ ] });
10481
+ };
10482
+ const renderGroup = (row, extent2) => /* @__PURE__ */ jsxs(Fragment, { children: [
10483
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "absolute inset-0 bg-[var(--ds-bg-muted)] opacity-60" }),
10484
+ extent2 && Number.isFinite(extent2.start) && // a 24 px target around the thin summary bar it draws
10485
+ /* @__PURE__ */ jsx(
10486
+ TimeBar,
10487
+ {
10488
+ ...treeitem(row),
10489
+ "aria-expanded": !collapsed.has(row.group.id),
10490
+ "data-gantt-shape": "summary",
10491
+ start: extent2.start,
10492
+ end: extent2.end,
10493
+ minWidth: MIN_BAR,
10494
+ className: "cursor-pointer before:absolute before:inset-x-0 before:top-1/2 before:h-ds-2 before:-translate-y-1/2 before:rounded-[var(--ds-radius-full)] before:bg-[var(--ds-text-secondary)]",
10495
+ style: { top: (ROW_HEIGHT2 - TARGET) / 2, bottom: "auto", height: TARGET }
10496
+ }
10497
+ )
10498
+ ] });
10499
+ const label = (row) => {
10500
+ if (row.group) {
10501
+ return /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "flex min-w-0 items-center gap-ds-1.5 font-medium", children: [
10502
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14, className: cn("shrink-0 transition-transform", !collapsed.has(row.group.id) && "rotate-90") }),
10503
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: row.group.title })
10504
+ ] });
10505
+ }
10506
+ const item = row.item;
10507
+ return (
10508
+ // a container, so what renderLabel adds gives way to the title when the column is narrow (a phone)
10509
+ /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "@container flex min-w-0 items-center gap-ds-2", style: { paddingInlineStart: row.level === 2 ? LEVEL_INDENT : 0 }, children: [
10510
+ item.milestone && /* @__PURE__ */ jsx("span", { className: "size-2 shrink-0 rotate-45 rounded-[1px] bg-[var(--ds-text-secondary)]" }),
10511
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: item.title }),
10512
+ renderLabel && /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center @max-[10rem]:hidden", children: renderLabel(item) })
10513
+ ] })
10514
+ );
10515
+ };
10516
+ return /* @__PURE__ */ jsx(
10517
+ TimeViewport,
10518
+ {
10519
+ ref: viewportRef,
10520
+ scale,
10521
+ onZoomChange: changeZoom,
10522
+ labelHeader,
10523
+ toolbar: toolbar === false ? false : /* @__PURE__ */ jsxs(Fragment, { children: [
10524
+ toolbar,
10525
+ /* @__PURE__ */ jsx(Legend, { statuses })
10526
+ ] }),
10527
+ tabStop: false,
10528
+ "aria-label": ariaLabel,
10529
+ className,
10530
+ children: /* @__PURE__ */ jsxs("div", { ref: treeRef, role: "tree", "aria-label": ariaLabel, className: "relative", onKeyDown, children: [
10531
+ /* @__PURE__ */ jsx("span", { ref: probeRef, "aria-hidden": "true", className: "pointer-events-none invisible absolute whitespace-nowrap text-xs font-medium", children: "0" }),
10532
+ /* @__PURE__ */ jsx(GanttArrows, { arrows, width: scale.width, height: rows.length * ROW_HEIGHT2 }),
10533
+ rows.map((row) => /* @__PURE__ */ jsx(
10534
+ TimeRow,
10535
+ {
10536
+ "data-row-key": row.key,
10537
+ label: label(row),
10538
+ className: "cursor-pointer",
10539
+ labelClassName: cn(
10540
+ row.group && "bg-[image:linear-gradient(var(--ds-bg-muted),var(--ds-bg-muted))]",
10541
+ row.item && row.item.id === selected && "bg-[image:linear-gradient(var(--ds-accent-subtle),var(--ds-accent-subtle))] text-[var(--ds-accent-subtle-text)]"
10542
+ ),
10543
+ onClick: () => onRowClick(row),
10544
+ children: row.group ? renderGroup(row, extents.get(row.group.id)) : renderItem(row, row.item)
10545
+ },
10546
+ row.key
10547
+ ))
10548
+ ] })
10549
+ }
10550
+ );
10551
+ }
9326
10552
  var DEFAULT_OFFSET2 = "var(--ds-space-4)";
9327
10553
  var LAUNCHER_SIZES = {
9328
10554
  md: "var(--ds-space-10)",
@@ -14935,7 +16161,7 @@ function usePagination(list, options = {}) {
14935
16161
  const grows = style !== "pagination";
14936
16162
  const start = grows ? 0 : (current - 1) * pageSize;
14937
16163
  const items = list.slice(start, current * pageSize);
14938
- const range = items.length === 0 ? [0, 0] : [start + 1, start + items.length];
16164
+ const range2 = items.length === 0 ? [0, 0] : [start + 1, start + items.length];
14939
16165
  const hasMore = current < totalPages;
14940
16166
  const setPage = (next) => setPageState(Math.min(Math.max(1, Math.floor(next)), totalPages));
14941
16167
  const loadMore = () => setPageState((p) => Math.min(p + 1, totalPages));
@@ -14950,7 +16176,7 @@ function usePagination(list, options = {}) {
14950
16176
  pageSize,
14951
16177
  totalItems,
14952
16178
  totalPages,
14953
- range,
16179
+ range: range2,
14954
16180
  shown: items.length,
14955
16181
  hasMore,
14956
16182
  loadMore,
@@ -15167,6 +16393,6 @@ var TextOverflow = forwardRef(function TextOverflow2({
15167
16393
  return /* @__PURE__ */ jsx(Tooltip, { content: resolvedTooltipContent, children: content });
15168
16394
  });
15169
16395
 
15170
- export { Accordion, ActionPanel, ActionPanelGroup, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, Dropdown, DropdownMenu, EmptyState, EntityCell, Field, FileUpload, FormWrapper, HoverCard, InlineFieldError, Input, Kbd, Label, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, NumberInput, PageContainer, PageHeader, Pagination, PinInput, Popover, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, Textarea, TimePicker, Timeline, Toast, ToastProvider, Toggle, Tour, TransferList, TreeView, useContentWidth, useDestructiveAction, useFormContext, usePagination, useSidebar, useToast, useToastOptional };
16396
+ export { Accordion, ActionPanel, ActionPanelGroup, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, Dropdown, DropdownMenu, EmptyState, EntityCell, Field, FileUpload, FormWrapper, Gantt, HoverCard, InlineFieldError, Input, Kbd, Label, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, NumberInput, PageContainer, PageHeader, Pagination, PinInput, Popover, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, TIME_ZOOMS, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, Textarea, TimeBar, TimePicker, TimeRow, TimeScaleHeader, TimeViewport, Timeline, Toast, ToastProvider, Toggle, Tour, TransferList, TreeView, dateOfDay, dayOf, endDayOf, fitLabel, fitLabels, formatDateRange, timeExtent, useContentWidth, useDestructiveAction, useFittedLabels, useFormContext, usePagination, useSidebar, useTextMeasure, useTimeScale, useTimeViewport, useTimeViewportLayout, useToast, useToastOptional };
15171
16397
  //# sourceMappingURL=index.js.map
15172
16398
  //# sourceMappingURL=index.js.map