@tensor_1/react 2.29.0 → 2.30.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/README.md +23 -0
- package/dist/components/SchedulingTimeline.d.ts +109 -0
- package/dist/components/SchedulingTimeline.js +245 -0
- package/dist/index.cjs +664 -303
- package/dist/index.d.cts +174 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +23 -0
- package/dist/labels-context.d.ts +3 -0
- package/dist/scheduling-timeline-model.d.ts +71 -0
- package/dist/scheduling-timeline-model.js +108 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -354,3 +354,26 @@ const icons: Record<EntityType, UixIcon> = { incident: SirenIcon, change: GitBra
|
|
|
354
354
|
- **Accessibility:** decorative unless `label` is set. Never let an icon carry meaning alone; pair it with text.
|
|
355
355
|
- **Bundle size:** each `<Name>Icon` is tree-shaken; one icon adds well under 3 KB minified. `<Icon name>` and `ICON_GLYPHS` load all of them.
|
|
356
356
|
- **A glyph is missing?** Add its Lucide name to `scripts/icon-names.txt`, run `node scripts/generate-icons.mjs <lucide-react dir>` and release. Do not paste an SVG into a product.
|
|
357
|
+
### SchedulingTimeline
|
|
358
|
+
|
|
359
|
+
Lanes of bars on a time axis (HAR-1364; TENSOR's change day/week timeline, rollout Gantt rows and licence-renewal
|
|
360
|
+
markers). It pairs with `SchedulingCalendar`: same entry states, the same explicit time zone.
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
<SchedulingTimeline
|
|
364
|
+
lanes={[{ id: 'net', label: 'Network', meta: '3 changes' }]}
|
|
365
|
+
items={changes.map((c) => ({ id: c.id, laneId: c.team, title: c.title, start: c.start, end: c.end, state: c.state }))}
|
|
366
|
+
range={{ start: weekStart, end: weekEnd }} scale="day" timeZone={tz} now={nowIso}
|
|
367
|
+
overlays={[{ id: 'q4', kind: 'freeze', label: 'Q4 freeze', start, end }]}
|
|
368
|
+
markers={[{ id: 'r1', label: 'Renewal: Fortinet', at, laneId: 'net' }]}
|
|
369
|
+
onSelectItem={(item) => openChange(item.id)}
|
|
370
|
+
onMoveItem={(id, { start, end }) => reschedule(id, start, end)}
|
|
371
|
+
/>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
- **Axis:** `scale` is `hour`, `day` (default), `week` or `month`. Ticks fall on wall-clock boundaries in `timeZone` (DST and half-hour zones included). `tickWidth` sets the width of one unit; the axis scrolls inside the component and the lane column stays put.
|
|
375
|
+
- **Bars:** overlapping bars in a lane stack and are hatched (`data-conflict`), so an overlap reads without colour. A bar cut off by the range shows a dashed edge. `renderItem` replaces the bar text. The full title is in the name and the `title` tooltip.
|
|
376
|
+
- **Moving:** with `onMoveItem`, bars move by drag or Shift+←/→ and their end changes with Alt+Shift+←/→. Moves snap to `step` (15 min on an hour axis, 1 h on a day axis, 1 day otherwise), and each move is announced. `movable: false` pins a bar. The component never moves a bar itself; it reports the new start and end and renders what you pass back.
|
|
377
|
+
- **Keyboard:** one tab stop. ←/→ go to the previous or next bar in a lane, ↑/↓ to the nearest bar in the next lane, Home/End to the lane's ends, and Enter selects.
|
|
378
|
+
- **Windows and markers:** `overlays` (`freeze`, `maintenance`, `blackout`; all lanes, or one with `laneId`), `markers` (a point in time) and `now` are drawn behind the bars and listed for screen readers.
|
|
379
|
+
- Pure helpers are exported for tests and server code: `timelineTicks`, `placeSpan`, `layoutLane`, `shiftSpan`, `snapToStep`, `pixelsToMs`, `defaultTimelineStep`.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { TimelineRange, TimelineScale } from '../scheduling-timeline-model.js';
|
|
4
|
+
import { SchedulingEntryState } from './SchedulingCalendar.js';
|
|
5
|
+
import '../calendar-model.js';
|
|
6
|
+
|
|
7
|
+
interface SchedulingTimelineLane {
|
|
8
|
+
id: string;
|
|
9
|
+
label: ReactNode;
|
|
10
|
+
/** A quieter second line under the lane name (an owner, a count). */
|
|
11
|
+
meta?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
interface SchedulingTimelineItem {
|
|
14
|
+
id: string;
|
|
15
|
+
laneId: string;
|
|
16
|
+
title: string;
|
|
17
|
+
/** ISO 8601 instants. */
|
|
18
|
+
start: string;
|
|
19
|
+
end: string;
|
|
20
|
+
state?: SchedulingEntryState;
|
|
21
|
+
meta?: string;
|
|
22
|
+
/** `false` keeps this bar fixed even when `onMoveItem` is set. */
|
|
23
|
+
movable?: boolean;
|
|
24
|
+
}
|
|
25
|
+
type SchedulingTimelineOverlayKind = 'freeze' | 'maintenance' | 'blackout';
|
|
26
|
+
interface SchedulingTimelineOverlay {
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
start: string;
|
|
30
|
+
end: string;
|
|
31
|
+
kind: SchedulingTimelineOverlayKind;
|
|
32
|
+
/** Only this lane; every lane when unset. */
|
|
33
|
+
laneId?: string;
|
|
34
|
+
}
|
|
35
|
+
interface SchedulingTimelineMarker {
|
|
36
|
+
id: string;
|
|
37
|
+
label: string;
|
|
38
|
+
/** ISO 8601 instant. */
|
|
39
|
+
at: string;
|
|
40
|
+
laneId?: string;
|
|
41
|
+
}
|
|
42
|
+
interface SchedulingTimelineLabels {
|
|
43
|
+
region: string;
|
|
44
|
+
lanes: string;
|
|
45
|
+
/** `{title}`, `{state}`, `{start}`, `{end}`, `{conflict}`. */
|
|
46
|
+
item: string;
|
|
47
|
+
conflict: string;
|
|
48
|
+
moveHint: string;
|
|
49
|
+
/** Announced after a move. `{title}`, `{start}`, `{end}`. */
|
|
50
|
+
moved: string;
|
|
51
|
+
/** Announced after a resize. `{title}`, `{end}`. */
|
|
52
|
+
resized: string;
|
|
53
|
+
loading: string;
|
|
54
|
+
retry: string;
|
|
55
|
+
empty: string;
|
|
56
|
+
/** Heading of the screen-reader list of windows and markers. */
|
|
57
|
+
windows: string;
|
|
58
|
+
now: string;
|
|
59
|
+
states: Record<SchedulingEntryState, string>;
|
|
60
|
+
overlays: Record<SchedulingTimelineOverlayKind, string>;
|
|
61
|
+
}
|
|
62
|
+
declare const DEFAULT_SCHEDULING_TIMELINE_LABELS: SchedulingTimelineLabels;
|
|
63
|
+
interface SchedulingTimelineProps {
|
|
64
|
+
lanes: SchedulingTimelineLane[];
|
|
65
|
+
items: SchedulingTimelineItem[];
|
|
66
|
+
/** The visible window (ISO 8601 instants). */
|
|
67
|
+
range: TimelineRange;
|
|
68
|
+
/** Tick unit of the axis. Default `day`. */
|
|
69
|
+
scale?: TimelineScale;
|
|
70
|
+
/** IANA zone for tick boundaries and every printed time. */
|
|
71
|
+
timeZone: string;
|
|
72
|
+
locale?: string;
|
|
73
|
+
overlays?: SchedulingTimelineOverlay[];
|
|
74
|
+
markers?: SchedulingTimelineMarker[];
|
|
75
|
+
/** Draws a "now" line at this instant (pass the current time; the component keeps no clock). */
|
|
76
|
+
now?: string;
|
|
77
|
+
/** First day of a week tick (0 = Sunday). Default 1. */
|
|
78
|
+
weekStartsOn?: number;
|
|
79
|
+
/** Width of one tick, as a CSS length. Defaults: hour 3.5rem, day 7rem, week 6rem, month 8rem. */
|
|
80
|
+
tickWidth?: string;
|
|
81
|
+
/** Move/resize granularity in ms. Defaults: 15 min (hour), 1 h (day), 1 day (week, month). */
|
|
82
|
+
step?: number;
|
|
83
|
+
/** Tick label. Default: `Intl` in `locale` and `timeZone`. */
|
|
84
|
+
formatTick?: (at: Date, scale: TimelineScale) => string;
|
|
85
|
+
/** Start/end text in names and announcements. Default: `Intl` medium date + short time in `timeZone`. */
|
|
86
|
+
formatInstant?: (instant: string) => string;
|
|
87
|
+
/** Bar content. Default: the title. */
|
|
88
|
+
renderItem?: (item: SchedulingTimelineItem) => ReactNode;
|
|
89
|
+
onSelectItem?: (item: SchedulingTimelineItem) => void;
|
|
90
|
+
/** Makes bars draggable and keyboard-movable. Receives the snapped new start and end. */
|
|
91
|
+
onMoveItem?: (id: string, next: {
|
|
92
|
+
start: string;
|
|
93
|
+
end: string;
|
|
94
|
+
}) => void;
|
|
95
|
+
loading?: boolean;
|
|
96
|
+
error?: string;
|
|
97
|
+
onRetry?: () => void;
|
|
98
|
+
labels?: Partial<SchedulingTimelineLabels>;
|
|
99
|
+
className?: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Lanes of bars on a time axis (HAR-1364; TENSOR C8): change schedules, rollout Gantt rows,
|
|
103
|
+
* renewal markers. Overlapping bars in a lane stack and are hatched; freeze, maintenance and
|
|
104
|
+
* blackout windows are bands; with `onMoveItem`, bars move by drag or Shift+arrows and their
|
|
105
|
+
* end changes with Alt+Shift+arrows, snapped to `step`.
|
|
106
|
+
*/
|
|
107
|
+
declare function SchedulingTimeline({ lanes, items, range, scale, timeZone, locale, overlays, markers, now, weekStartsOn, tickWidth, step: stepProp, formatTick, formatInstant: formatInstantProp, renderItem, onSelectItem, onMoveItem, loading, error, onRetry, labels: labelOverrides, className, }: SchedulingTimelineProps): react.JSX.Element;
|
|
108
|
+
|
|
109
|
+
export { DEFAULT_SCHEDULING_TIMELINE_LABELS, SchedulingTimeline, type SchedulingTimelineItem, type SchedulingTimelineLabels, type SchedulingTimelineLane, type SchedulingTimelineMarker, type SchedulingTimelineOverlay, type SchedulingTimelineOverlayKind, type SchedulingTimelineProps };
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
5
|
+
import { fillLabel } from "../fill-label.js";
|
|
6
|
+
import { useUixLabels } from "../labels-context.js";
|
|
7
|
+
import { defaultTimelineStep, layoutLane, pixelsToMs, placeSpan, shiftSpan, timelineTicks } from "../scheduling-timeline-model.js";
|
|
8
|
+
const DEFAULT_SCHEDULING_TIMELINE_LABELS = {
|
|
9
|
+
region: "Schedule timeline in {timeZone}",
|
|
10
|
+
lanes: "Lanes",
|
|
11
|
+
item: "{title}, {state}, {start} to {end}{conflict}",
|
|
12
|
+
conflict: ", overlaps another entry",
|
|
13
|
+
moveHint: "Shift and an arrow key moves it; Alt, Shift and an arrow key changes when it ends.",
|
|
14
|
+
moved: "Moved {title} to {start} \u2013 {end}",
|
|
15
|
+
resized: "{title} now ends {end}",
|
|
16
|
+
loading: "Loading schedule\u2026",
|
|
17
|
+
retry: "Try again",
|
|
18
|
+
empty: "Nothing is scheduled in this range.",
|
|
19
|
+
windows: "Windows and markers in this range",
|
|
20
|
+
now: "Now",
|
|
21
|
+
states: { scheduled: "Scheduled", conflicted: "Conflicted", "in-progress": "In progress", "blackout-violation": "Blackout violation" },
|
|
22
|
+
overlays: { freeze: "Change freeze", maintenance: "Maintenance window", blackout: "Blackout" }
|
|
23
|
+
};
|
|
24
|
+
const TICK_WIDTH = { hour: "3.5rem", day: "7rem", week: "6rem", month: "8rem" };
|
|
25
|
+
const TICK_FORMAT = {
|
|
26
|
+
hour: { hour: "2-digit", minute: "2-digit" },
|
|
27
|
+
day: { weekday: "short", day: "numeric" },
|
|
28
|
+
week: { day: "numeric", month: "short" },
|
|
29
|
+
month: { month: "short", year: "numeric" }
|
|
30
|
+
};
|
|
31
|
+
function SchedulingTimeline({
|
|
32
|
+
lanes,
|
|
33
|
+
items,
|
|
34
|
+
range,
|
|
35
|
+
scale = "day",
|
|
36
|
+
timeZone,
|
|
37
|
+
locale,
|
|
38
|
+
overlays = [],
|
|
39
|
+
markers = [],
|
|
40
|
+
now,
|
|
41
|
+
weekStartsOn = 1,
|
|
42
|
+
tickWidth,
|
|
43
|
+
step: stepProp,
|
|
44
|
+
formatTick,
|
|
45
|
+
formatInstant: formatInstantProp,
|
|
46
|
+
renderItem,
|
|
47
|
+
onSelectItem,
|
|
48
|
+
onMoveItem,
|
|
49
|
+
loading,
|
|
50
|
+
error,
|
|
51
|
+
onRetry,
|
|
52
|
+
labels: labelOverrides,
|
|
53
|
+
className
|
|
54
|
+
}) {
|
|
55
|
+
const uixLabels = useUixLabels();
|
|
56
|
+
const labels = {
|
|
57
|
+
...DEFAULT_SCHEDULING_TIMELINE_LABELS,
|
|
58
|
+
...uixLabels.schedulingTimeline,
|
|
59
|
+
...labelOverrides,
|
|
60
|
+
states: { ...DEFAULT_SCHEDULING_TIMELINE_LABELS.states, ...uixLabels.schedulingTimeline?.states, ...labelOverrides?.states },
|
|
61
|
+
overlays: { ...DEFAULT_SCHEDULING_TIMELINE_LABELS.overlays, ...uixLabels.schedulingTimeline?.overlays, ...labelOverrides?.overlays }
|
|
62
|
+
};
|
|
63
|
+
const id = useId();
|
|
64
|
+
const step = stepProp ?? defaultTimelineStep(scale);
|
|
65
|
+
const ticks = useMemo(() => timelineTicks(range, scale, timeZone, weekStartsOn), [range.start, range.end, scale, timeZone, weekStartsOn]);
|
|
66
|
+
const laneLayouts = useMemo(
|
|
67
|
+
() => lanes.map((lane) => ({ lane, placed: layoutLane(items.filter((item) => item.laneId === lane.id), range) })),
|
|
68
|
+
[lanes, items, range.start, range.end]
|
|
69
|
+
// eslint-disable-line react-hooks/exhaustive-deps
|
|
70
|
+
);
|
|
71
|
+
const ordered = useMemo(() => laneLayouts.flatMap((l) => l.placed.map((p) => p.item.id)), [laneLayouts]);
|
|
72
|
+
const [activeId, setActiveId] = useState();
|
|
73
|
+
const focusId = activeId && ordered.includes(activeId) ? activeId : ordered[0];
|
|
74
|
+
const [drag, setDrag] = useState(null);
|
|
75
|
+
const [announcement, setAnnouncement] = useState("");
|
|
76
|
+
const rootRef = useRef(null);
|
|
77
|
+
const fmtInstant = (instant) => formatInstantProp?.(instant) ?? new Intl.DateTimeFormat(locale, { timeZone, dateStyle: "medium", timeStyle: "short" }).format(new Date(instant));
|
|
78
|
+
const fmtTick = (at) => formatTick?.(new Date(at), scale) ?? new Intl.DateTimeFormat(locale, { timeZone, ...TICK_FORMAT[scale] }).format(new Date(at));
|
|
79
|
+
const movable = (item) => !!onMoveItem && item.movable !== false;
|
|
80
|
+
const focusItem = (itemId) => {
|
|
81
|
+
if (!itemId) return;
|
|
82
|
+
setActiveId(itemId);
|
|
83
|
+
requestAnimationFrame(() => rootRef.current?.querySelector(`[data-timeline-item="${CSS.escape(itemId)}"]`)?.focus());
|
|
84
|
+
};
|
|
85
|
+
const commit = (item, delta, mode) => {
|
|
86
|
+
if (!onMoveItem || delta === 0) return;
|
|
87
|
+
const next = shiftSpan(item, delta, mode, step);
|
|
88
|
+
onMoveItem(item.id, next);
|
|
89
|
+
setAnnouncement(mode === "move" ? fillLabel(labels.moved, { title: item.title, start: fmtInstant(next.start), end: fmtInstant(next.end) }) : fillLabel(labels.resized, { title: item.title, end: fmtInstant(next.end) }));
|
|
90
|
+
};
|
|
91
|
+
const onItemKeyDown = (event, laneIndex, placed, index) => {
|
|
92
|
+
const item = placed[index].item;
|
|
93
|
+
const horizontal = event.key === "ArrowLeft" ? -1 : event.key === "ArrowRight" ? 1 : 0;
|
|
94
|
+
if (horizontal && event.shiftKey && movable(item)) {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
commit(item, horizontal * step, event.altKey ? "resize" : "move");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (horizontal) {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
focusItem(placed[index + horizontal]?.item.id);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (event.key === "Home" || event.key === "End") {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
focusItem((event.key === "Home" ? placed[0] : placed[placed.length - 1])?.item.id);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const vertical = event.key === "ArrowUp" ? -1 : event.key === "ArrowDown" ? 1 : 0;
|
|
110
|
+
if (vertical) {
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
const start = Date.parse(item.start);
|
|
113
|
+
for (let l = laneIndex + vertical; l >= 0 && l < laneLayouts.length; l += vertical) {
|
|
114
|
+
const candidates = laneLayouts[l].placed;
|
|
115
|
+
if (candidates.length === 0) continue;
|
|
116
|
+
const nearest = candidates.reduce((best, p) => Math.abs(Date.parse(p.item.start) - start) < Math.abs(Date.parse(best.item.start) - start) ? p : best);
|
|
117
|
+
focusItem(nearest.item.id);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const onPointerDown = (event, item) => {
|
|
123
|
+
if (!movable(item) || event.button !== 0) return;
|
|
124
|
+
const track = event.currentTarget.closest(".uix-scheduling-timeline__track");
|
|
125
|
+
if (!track) return;
|
|
126
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
127
|
+
setDrag({ id: item.id, x: event.clientX, width: track.getBoundingClientRect().width, delta: 0, moved: false });
|
|
128
|
+
};
|
|
129
|
+
const onPointerMove = (event) => {
|
|
130
|
+
if (!drag) return;
|
|
131
|
+
const dx = event.clientX - drag.x;
|
|
132
|
+
const delta = pixelsToMs(dx, drag.width, range, step);
|
|
133
|
+
if (delta !== drag.delta || !drag.moved && Math.abs(dx) > 3) setDrag({ ...drag, delta, moved: drag.moved || Math.abs(dx) > 3 });
|
|
134
|
+
};
|
|
135
|
+
const onPointerUp = (item) => {
|
|
136
|
+
if (!drag || drag.id !== item.id) return;
|
|
137
|
+
if (drag.moved) commit(item, drag.delta, "move");
|
|
138
|
+
setDrag(drag.moved ? { ...drag, delta: 0 } : null);
|
|
139
|
+
if (drag.moved) window.setTimeout(() => setDrag(null), 0);
|
|
140
|
+
};
|
|
141
|
+
const nowPos = now ? placeSpan({ start: now, end: now }, range) : null;
|
|
142
|
+
const overlaysFor = (laneId) => overlays.filter((o) => !o.laneId || o.laneId === laneId);
|
|
143
|
+
const markersFor = (laneId) => markers.filter((m) => !m.laneId || m.laneId === laneId);
|
|
144
|
+
const trackStyle = { minWidth: `calc(${Math.max(ticks.length - 1, 1)} * ${tickWidth ?? TICK_WIDTH[scale]})` };
|
|
145
|
+
const band = (o, showLabel) => {
|
|
146
|
+
const pos = placeSpan(o, range);
|
|
147
|
+
return pos && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__overlay", "data-kind": o.kind, style: { left: `${pos.left}%`, width: `${pos.width}%` }, children: showLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__overlay-label", children: o.label }) }, o.id);
|
|
148
|
+
};
|
|
149
|
+
return /* @__PURE__ */ jsxs("section", { ref: rootRef, className: cx("uix-scheduling-timeline", className), "aria-label": fillLabel(labels.region, { timeZone }), children: [
|
|
150
|
+
loading ? /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__state", role: "status", children: labels.loading }) : error ? /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__state", role: "alert", children: [
|
|
151
|
+
/* @__PURE__ */ jsx("p", { children: error }),
|
|
152
|
+
onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: labels.retry })
|
|
153
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
154
|
+
(overlays.length > 0 || markers.length > 0) && /* @__PURE__ */ jsxs("div", { className: "uix-visually-hidden", children: [
|
|
155
|
+
/* @__PURE__ */ jsx("p", { id: `${id}-windows`, children: labels.windows }),
|
|
156
|
+
/* @__PURE__ */ jsxs("ul", { "aria-labelledby": `${id}-windows`, children: [
|
|
157
|
+
overlays.map((o) => /* @__PURE__ */ jsxs("li", { children: [
|
|
158
|
+
labels.overlays[o.kind],
|
|
159
|
+
": ",
|
|
160
|
+
o.label,
|
|
161
|
+
", ",
|
|
162
|
+
fmtInstant(o.start),
|
|
163
|
+
" \u2013 ",
|
|
164
|
+
fmtInstant(o.end)
|
|
165
|
+
] }, o.id)),
|
|
166
|
+
markers.map((m) => /* @__PURE__ */ jsxs("li", { children: [
|
|
167
|
+
m.label,
|
|
168
|
+
", ",
|
|
169
|
+
fmtInstant(m.at)
|
|
170
|
+
] }, m.id))
|
|
171
|
+
] })
|
|
172
|
+
] }),
|
|
173
|
+
/* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__scroller", children: /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__grid", children: [
|
|
174
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__row uix-scheduling-timeline__row--axis", "aria-hidden": "true", children: [
|
|
175
|
+
/* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__lane-label", children: labels.lanes }),
|
|
176
|
+
/* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__track uix-scheduling-timeline__axis", style: trackStyle, children: ticks.map((tick) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__tick", "data-major": tick.major || void 0, style: { left: `${tick.offset}%` }, children: tick.offset < 100 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__tick-label", children: fmtTick(tick.at) }) }, tick.at)) })
|
|
177
|
+
] }),
|
|
178
|
+
laneLayouts.map(({ lane, placed }, laneIndex) => {
|
|
179
|
+
const rows = placed.reduce((max, p) => Math.max(max, p.row + 1), 1);
|
|
180
|
+
const laneLabelId = `${id}-lane-${laneIndex}`;
|
|
181
|
+
return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__row", style: { "--uix-timeline-rows": rows }, children: [
|
|
182
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__lane-label", id: laneLabelId, children: [
|
|
183
|
+
/* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__lane-name", children: lane.label }),
|
|
184
|
+
lane.meta != null && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__lane-meta", children: lane.meta })
|
|
185
|
+
] }),
|
|
186
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__track", style: trackStyle, children: [
|
|
187
|
+
ticks.map((tick) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__gridline", "data-major": tick.major || void 0, style: { left: `${tick.offset}%` }, "aria-hidden": "true" }, tick.at)),
|
|
188
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: overlaysFor(lane.id).map((o) => band(o, laneIndex === 0 || !!o.laneId)) }),
|
|
189
|
+
nowPos && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__now", style: { left: `${nowPos.left}%` }, "aria-hidden": "true", children: laneIndex === 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__now-label", children: labels.now }) }),
|
|
190
|
+
markersFor(lane.id).map((m) => {
|
|
191
|
+
const pos = placeSpan({ start: m.at, end: m.at }, range);
|
|
192
|
+
return pos && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__marker", style: { left: `${pos.left}%` }, title: m.label, "aria-hidden": "true" }, m.id);
|
|
193
|
+
}),
|
|
194
|
+
/* @__PURE__ */ jsx("ul", { className: "uix-scheduling-timeline__items", "aria-labelledby": laneLabelId, children: placed.map((p, index) => {
|
|
195
|
+
const item = p.item;
|
|
196
|
+
const state = item.state ?? "scheduled";
|
|
197
|
+
const dragging = drag?.id === item.id && drag.moved;
|
|
198
|
+
const shown = dragging && drag ? placeSpan(shiftSpan(item, drag.delta, "move", step), range) ?? p : p;
|
|
199
|
+
return /* @__PURE__ */ jsx("li", { className: "uix-scheduling-timeline__slot", style: { left: `${shown.left}%`, width: `${shown.width}%`, "--uix-timeline-row": p.row }, children: /* @__PURE__ */ jsxs(
|
|
200
|
+
"button",
|
|
201
|
+
{
|
|
202
|
+
type: "button",
|
|
203
|
+
className: "uix-scheduling-timeline__item",
|
|
204
|
+
"data-timeline-item": item.id,
|
|
205
|
+
title: item.title,
|
|
206
|
+
"data-state": state,
|
|
207
|
+
"data-conflict": p.conflict || void 0,
|
|
208
|
+
"data-clipped-start": p.clippedStart || void 0,
|
|
209
|
+
"data-clipped-end": p.clippedEnd || void 0,
|
|
210
|
+
"data-movable": movable(item) || void 0,
|
|
211
|
+
"data-dragging": dragging || void 0,
|
|
212
|
+
tabIndex: focusId === item.id ? 0 : -1,
|
|
213
|
+
"aria-label": fillLabel(labels.item, { title: item.title, state: labels.states[state], start: fmtInstant(item.start), end: fmtInstant(item.end), conflict: p.conflict ? labels.conflict : "" }),
|
|
214
|
+
"aria-describedby": movable(item) ? `${id}-hint` : void 0,
|
|
215
|
+
onFocus: () => setActiveId(item.id),
|
|
216
|
+
onKeyDown: (event) => onItemKeyDown(event, laneIndex, placed, index),
|
|
217
|
+
onPointerDown: (event) => onPointerDown(event, item),
|
|
218
|
+
onPointerMove,
|
|
219
|
+
onPointerUp: () => onPointerUp(item),
|
|
220
|
+
onPointerCancel: () => setDrag(null),
|
|
221
|
+
onClick: () => {
|
|
222
|
+
if (drag?.moved) return;
|
|
223
|
+
onSelectItem?.(item);
|
|
224
|
+
},
|
|
225
|
+
children: [
|
|
226
|
+
/* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__item-title", children: renderItem?.(item) ?? item.title }),
|
|
227
|
+
item.meta && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__item-meta", children: item.meta })
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
) }, item.id);
|
|
231
|
+
}) })
|
|
232
|
+
] })
|
|
233
|
+
] }, lane.id);
|
|
234
|
+
})
|
|
235
|
+
] }) }),
|
|
236
|
+
ordered.length === 0 && /* @__PURE__ */ jsx("p", { className: "uix-scheduling-timeline__empty", children: labels.empty })
|
|
237
|
+
] }),
|
|
238
|
+
onMoveItem && /* @__PURE__ */ jsx("span", { id: `${id}-hint`, className: "uix-visually-hidden", children: labels.moveHint }),
|
|
239
|
+
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announcement })
|
|
240
|
+
] });
|
|
241
|
+
}
|
|
242
|
+
export {
|
|
243
|
+
DEFAULT_SCHEDULING_TIMELINE_LABELS,
|
|
244
|
+
SchedulingTimeline
|
|
245
|
+
};
|