gridsmith-ui 0.16.0 → 0.17.1
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/{chunk-2U2X6E2X.js → chunk-J43HCK7Q.js} +3 -3
- package/dist/{chunk-2U2X6E2X.js.map → chunk-J43HCK7Q.js.map} +1 -1
- package/dist/{chunk-5XOJZQIP.js → chunk-YQABRUWD.js} +53 -14
- package/dist/chunk-YQABRUWD.js.map +1 -0
- package/dist/editor.js +1 -1
- package/dist/index.d.ts +461 -2
- package/dist/index.js +1244 -18
- package/dist/index.js.map +1 -1
- package/dist/resizable.js +2 -2
- package/dist/roots.css +9 -0
- package/package.json +1 -1
- package/dist/chunk-5XOJZQIP.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { useAppShell, ControlSizeContext,
|
|
2
|
-
export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell, useUnsavedChanges } from './chunk-
|
|
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-
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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 =
|
|
1497
|
-
const rangeB =
|
|
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
|
|
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 ===
|
|
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
|
|
2203
|
-
const many = `${joinOr([...families.map((f) => f.many), ...
|
|
2204
|
-
const one = families.length === 0 ? `${article(
|
|
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
|
|
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
|