@heyramzi/tasks 0.3.0 → 0.5.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 +138 -18
- package/package.json +34 -1
- package/src/ambient.d.ts +3 -6
- package/src/demo.svelte +109 -19
- package/src/inbox/inbox-card.svelte +280 -0
- package/src/inbox/inbox-draft.svelte +84 -0
- package/src/inbox/inbox-logic.ts +56 -0
- package/src/inbox/inbox-statuses.ts +48 -0
- package/src/inbox/inbox-trace.svelte +45 -0
- package/src/inbox/inbox-types.ts +108 -0
- package/src/inbox/inbox.svelte +203 -0
- package/src/inbox/index.ts +23 -0
- package/src/index.ts +39 -7
- package/src/kanban-board.svelte +421 -104
- package/src/kanban-column.svelte +1 -1
- package/src/kanban-types.ts +18 -1
- package/src/side-panel.svelte +204 -0
- package/src/status-badge.svelte +37 -18
- package/src/status-dot.svelte +32 -12
- package/src/status-icon.svelte +2 -2
- package/src/status-types.ts +6 -5
- package/src/task-gantt.svelte +208 -36
- package/src/task-list.svelte +206 -101
- package/src/task-types.ts +11 -8
- package/src/time/duration.ts +69 -0
- package/src/time/time-chip.svelte +62 -0
- package/src/time/time-dot.svelte +58 -0
- package/src/time/time-ring.svelte +74 -0
- package/src/time/time-tracker.svelte +564 -0
- package/src/time/time-types.ts +74 -0
- package/src/use-kanban-dnd.svelte.ts +6 -1
- package/src/icons/board-icon.svelte +0 -9
- package/src/icons/chevron-right.svg +0 -1
- package/src/icons/gantt-icon.svelte +0 -11
- package/src/icons/list-icon.svelte +0 -16
- package/src/icons/spinner.svg +0 -1
- package/src/lib/cn.ts +0 -11
- package/src/task-view-switch.svelte +0 -118
- package/src/tone.ts +0 -51
package/src/task-gantt.svelte
CHANGED
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
<script lang="ts" generics="T">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "@heyramzi/utils";
|
|
4
4
|
import { dayNumber, isoFromDay, todayIso, weekday } from "./dates.js";
|
|
5
5
|
import type { KanbanColumn as KanbanColumnDef } from "./kanban-types.js";
|
|
6
|
-
import type { GanttChange } from "./task-types.js";
|
|
6
|
+
import type { GanttChange, GanttLabelContext } from "./task-types.js";
|
|
7
|
+
import { CaretRight, FlagPennant } from "phosphor-svelte";
|
|
7
8
|
|
|
8
9
|
type Props<T> = {
|
|
9
10
|
items: T[];
|
|
10
11
|
getId: (item: T) => string;
|
|
11
12
|
getTitle: (item: T) => string;
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
/**
|
|
14
|
+
* ISO date, inclusive. Null or empty means undated: with an end date alone the task
|
|
15
|
+
* starts the same day, and with neither it has no bar of its own.
|
|
16
|
+
*/
|
|
17
|
+
getStart: (item: T) => string | null | undefined;
|
|
18
|
+
/** ISO date, inclusive. See `getStart`. */
|
|
19
|
+
getEnd: (item: T) => string | null | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* The id of the task this one sits under. Children nest under their parent, indented
|
|
22
|
+
* and foldable. A parent with no dates of its own draws the span of what is below it.
|
|
23
|
+
*/
|
|
24
|
+
getParent?: (item: T) => string | null | undefined;
|
|
25
|
+
/** Draws a flag at the end date instead of a bar. Milestones are not draggable. */
|
|
26
|
+
isMilestone?: (item: T) => boolean;
|
|
27
|
+
/** Parent ids that start folded. */
|
|
28
|
+
collapsed?: string[];
|
|
29
|
+
/** Shown in place of a bar for an undated task. */
|
|
30
|
+
noDatesLabel?: string;
|
|
16
31
|
/** Any CSS color for the bar. Falls back to the group hue, then the primary color. */
|
|
17
32
|
getHue?: (item: T) => string | undefined;
|
|
18
33
|
/** 0 to 1. Fills the bar from the left. */
|
|
@@ -27,7 +42,7 @@
|
|
|
27
42
|
/** Called on a click or Enter that did not move the bar. */
|
|
28
43
|
onSelect?: (id: string) => void;
|
|
29
44
|
/** The left column for a row. Defaults to the title. */
|
|
30
|
-
renderLabel?: Snippet<[T]>;
|
|
45
|
+
renderLabel?: Snippet<[T, GanttLabelContext]>;
|
|
31
46
|
/** First and last day shown. Default: the tasks' span, padded, starting on a Monday. */
|
|
32
47
|
from?: string;
|
|
33
48
|
to?: string;
|
|
@@ -39,6 +54,8 @@
|
|
|
39
54
|
labelWidth?: number;
|
|
40
55
|
labelHeading?: string;
|
|
41
56
|
locale?: string;
|
|
57
|
+
/** Drops the card chrome (border, radius, fill) so the chart sits flat on the page. */
|
|
58
|
+
flat?: boolean;
|
|
42
59
|
class?: string;
|
|
43
60
|
};
|
|
44
61
|
|
|
@@ -48,6 +65,10 @@
|
|
|
48
65
|
getTitle,
|
|
49
66
|
getStart,
|
|
50
67
|
getEnd,
|
|
68
|
+
getParent,
|
|
69
|
+
isMilestone,
|
|
70
|
+
collapsed = [],
|
|
71
|
+
noDatesLabel = "No dates",
|
|
51
72
|
getHue,
|
|
52
73
|
getProgress,
|
|
53
74
|
getDependsOn,
|
|
@@ -64,6 +85,7 @@
|
|
|
64
85
|
labelWidth = 240,
|
|
65
86
|
labelHeading = "Task",
|
|
66
87
|
locale = "en-US",
|
|
88
|
+
flat = false,
|
|
67
89
|
class: className,
|
|
68
90
|
}: Props<T> = $props();
|
|
69
91
|
|
|
@@ -71,13 +93,25 @@
|
|
|
71
93
|
type Drag = { id: string; mode: "move" | "start" | "end"; originX: number; dx: number };
|
|
72
94
|
type Row =
|
|
73
95
|
| { kind: "group"; group: KanbanColumnDef; count: number; span: Span | null }
|
|
74
|
-
| {
|
|
96
|
+
| {
|
|
97
|
+
kind: "task";
|
|
98
|
+
item: T;
|
|
99
|
+
id: string;
|
|
100
|
+
order: number;
|
|
101
|
+
depth: number;
|
|
102
|
+
hasChildren: boolean;
|
|
103
|
+
open: boolean;
|
|
104
|
+
/** The task's own dates, with a live drag applied. Null when undated. */
|
|
105
|
+
own: Span | null;
|
|
106
|
+
/** What the row draws: its own span, else the span of what is below it. */
|
|
107
|
+
span: Span | null;
|
|
108
|
+
};
|
|
75
109
|
|
|
76
110
|
// Bars past this row enter together, so a long chart never waits on its stagger.
|
|
77
111
|
const STAGGER_CAP = 10;
|
|
78
112
|
// A bar narrower than this shows its title outside, to the right.
|
|
79
113
|
const INLINE_TITLE_MIN = 72;
|
|
80
|
-
const
|
|
114
|
+
const INDENT = 14;
|
|
81
115
|
|
|
82
116
|
let scroller = $state<HTMLDivElement>();
|
|
83
117
|
let corner = $state<HTMLDivElement>();
|
|
@@ -87,13 +121,26 @@
|
|
|
87
121
|
|
|
88
122
|
let todayDay = $derived(dayNumber(today));
|
|
89
123
|
let editable = $derived(!!onChange);
|
|
124
|
+
// Under 18px a day is too narrow for a gap either side of a bar.
|
|
125
|
+
let inset = $derived(dayWidth >= 18 ? 3 : 0);
|
|
126
|
+
// svelte-ignore state_referenced_locally
|
|
127
|
+
let folded = $state(new Set(collapsed));
|
|
128
|
+
|
|
129
|
+
function toggle(id: string): void {
|
|
130
|
+
const next = new Set(folded);
|
|
131
|
+
if (next.has(id)) next.delete(id);
|
|
132
|
+
else next.add(id);
|
|
133
|
+
folded = next;
|
|
134
|
+
}
|
|
90
135
|
|
|
91
136
|
let range = $derived.by(() => {
|
|
92
137
|
let lo = todayDay;
|
|
93
138
|
let hi = todayDay;
|
|
94
139
|
for (const item of items) {
|
|
95
|
-
|
|
96
|
-
|
|
140
|
+
const span = spanOf(item);
|
|
141
|
+
if (!span) continue;
|
|
142
|
+
lo = Math.min(lo, span.start);
|
|
143
|
+
hi = Math.max(hi, span.end);
|
|
97
144
|
}
|
|
98
145
|
let first = from ? dayNumber(from) : lo - 7;
|
|
99
146
|
// Day 0 was a Thursday. Step back to the Monday on or before `first`.
|
|
@@ -128,15 +175,19 @@
|
|
|
128
175
|
return out;
|
|
129
176
|
});
|
|
130
177
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
178
|
+
/** The task's own dates. A lone date stands for a one-day task. */
|
|
179
|
+
function spanOf(item: T): Span | null {
|
|
180
|
+
const first = getStart(item) || getEnd(item);
|
|
181
|
+
const last = getEnd(item) || getStart(item);
|
|
182
|
+
if (!first || !last) return null;
|
|
183
|
+
const start = dayNumber(first);
|
|
184
|
+
return { start, end: Math.max(start, dayNumber(last)) };
|
|
134
185
|
}
|
|
135
186
|
|
|
136
187
|
/** A bar's span, with the live drag applied. Resizes never cross the other edge. */
|
|
137
|
-
function liveSpan(item: T, id: string): Span {
|
|
188
|
+
function liveSpan(item: T, id: string): Span | null {
|
|
138
189
|
const span = spanOf(item);
|
|
139
|
-
if (drag?.id !== id || drag.dx === 0) return span;
|
|
190
|
+
if (!span || drag?.id !== id || drag.dx === 0) return span;
|
|
140
191
|
if (drag.mode === "move") return { start: span.start + drag.dx, end: span.end + drag.dx };
|
|
141
192
|
if (drag.mode === "start") return { start: Math.min(span.start + drag.dx, span.end), end: span.end };
|
|
142
193
|
return { start: span.start, end: Math.max(span.end + drag.dx, span.start) };
|
|
@@ -145,20 +196,55 @@
|
|
|
145
196
|
let rows = $derived.by(() => {
|
|
146
197
|
const out: Row[] = [];
|
|
147
198
|
let order = 0;
|
|
148
|
-
const
|
|
199
|
+
const join = (a: Span | null, b: Span | null): Span | null =>
|
|
200
|
+
!a ? b : !b ? a : { start: Math.min(a.start, b.start), end: Math.max(a.end, b.end) };
|
|
201
|
+
|
|
202
|
+
// Depth first, so a parent's row comes before its children. A parent's span is known
|
|
203
|
+
// only after its children are, so the row is pushed first and filled in after.
|
|
204
|
+
const emit = (members: T[]) => {
|
|
205
|
+
if (!getParent) {
|
|
206
|
+
for (const item of members) {
|
|
207
|
+
const id = getId(item);
|
|
208
|
+
const own = liveSpan(item, id);
|
|
209
|
+
out.push({ kind: "task", item, id, order: order++, depth: 0, hasChildren: false, open: true, own, span: own });
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const ids = new Set(members.map((item) => getId(item)));
|
|
214
|
+
const kids = new Map<string | null, T[]>();
|
|
215
|
+
for (const item of members) {
|
|
216
|
+
const parent = getParent(item);
|
|
217
|
+
const key = parent && ids.has(parent) ? parent : null;
|
|
218
|
+
kids.set(key, [...(kids.get(key) ?? []), item]);
|
|
219
|
+
}
|
|
220
|
+
const walk = (item: T, depth: number, visible: boolean): Span | null => {
|
|
221
|
+
const id = getId(item);
|
|
222
|
+
const children = kids.get(id) ?? [];
|
|
223
|
+
const open = !folded.has(id);
|
|
224
|
+
const own = liveSpan(item, id);
|
|
225
|
+
const row: Row = { kind: "task", item, id, order: 0, depth, hasChildren: children.length > 0, open, own, span: own };
|
|
226
|
+
if (visible) {
|
|
227
|
+
row.order = order++;
|
|
228
|
+
out.push(row);
|
|
229
|
+
}
|
|
230
|
+
let below: Span | null = null;
|
|
231
|
+
for (const child of children) below = join(below, walk(child, depth + 1, visible && open));
|
|
232
|
+
if (!own) row.span = below;
|
|
233
|
+
return join(own, below);
|
|
234
|
+
};
|
|
235
|
+
for (const item of kids.get(null) ?? []) walk(item, 0, true);
|
|
236
|
+
};
|
|
237
|
+
|
|
149
238
|
if (!groups || !getGroup) {
|
|
150
|
-
items
|
|
239
|
+
emit(items);
|
|
151
240
|
return out;
|
|
152
241
|
}
|
|
153
242
|
for (const group of groups) {
|
|
154
243
|
const members = items.filter((item) => getGroup(item) === group.id);
|
|
155
244
|
let span: Span | null = null;
|
|
156
|
-
for (const item of members)
|
|
157
|
-
const s = liveSpan(item, getId(item));
|
|
158
|
-
span = span ? { start: Math.min(span.start, s.start), end: Math.max(span.end, s.end) } : s;
|
|
159
|
-
}
|
|
245
|
+
for (const item of members) span = join(span, liveSpan(item, getId(item)));
|
|
160
246
|
out.push({ kind: "group", group, count: members.length, span });
|
|
161
|
-
members
|
|
247
|
+
emit(members);
|
|
162
248
|
}
|
|
163
249
|
return out;
|
|
164
250
|
});
|
|
@@ -196,7 +282,7 @@
|
|
|
196
282
|
if (!getDependsOn) return [];
|
|
197
283
|
const at = new Map<string, { row: number; span: Span }>();
|
|
198
284
|
rows.forEach((row, index) => {
|
|
199
|
-
if (row.kind === "task") at.set(row.id, { row: index, span:
|
|
285
|
+
if (row.kind === "task" && row.span) at.set(row.id, { row: index, span: row.span });
|
|
200
286
|
});
|
|
201
287
|
const out: { key: string; d: string; head: string; from: string; to: string }[] = [];
|
|
202
288
|
for (const row of rows) {
|
|
@@ -206,9 +292,9 @@
|
|
|
206
292
|
for (const dep of getDependsOn(row.item) ?? []) {
|
|
207
293
|
const source = at.get(dep);
|
|
208
294
|
if (!source) continue;
|
|
209
|
-
const x1 = x(source.span.end + 1) -
|
|
295
|
+
const x1 = x(source.span.end + 1) - inset;
|
|
210
296
|
const y1 = source.row * rowHeight + rowHeight / 2;
|
|
211
|
-
const x2 = x(target.span.start) +
|
|
297
|
+
const x2 = x(target.span.start) + inset;
|
|
212
298
|
const y2 = target.row * rowHeight + rowHeight / 2;
|
|
213
299
|
// Room between the bars: one bend in the gap. No room: out, round the row gap, and back in.
|
|
214
300
|
const points: [number, number][] =
|
|
@@ -267,7 +353,7 @@
|
|
|
267
353
|
const moved = drag.dx !== 0;
|
|
268
354
|
const span = liveSpan(item, id);
|
|
269
355
|
drag = null;
|
|
270
|
-
if (moved) void onChange?.(id, { start: isoFromDay(span.start), end: isoFromDay(span.end) });
|
|
356
|
+
if (moved && span) void onChange?.(id, { start: isoFromDay(span.start), end: isoFromDay(span.end) });
|
|
271
357
|
else onSelect?.(id);
|
|
272
358
|
}
|
|
273
359
|
|
|
@@ -282,6 +368,7 @@
|
|
|
282
368
|
e.preventDefault();
|
|
283
369
|
const step = e.key === "ArrowRight" ? 1 : -1;
|
|
284
370
|
const span = spanOf(item);
|
|
371
|
+
if (!span) return;
|
|
285
372
|
const next = e.shiftKey
|
|
286
373
|
? { start: span.start, end: Math.max(span.start, span.end + step) }
|
|
287
374
|
: { start: span.start + step, end: span.end + step };
|
|
@@ -298,7 +385,8 @@
|
|
|
298
385
|
<div
|
|
299
386
|
bind:this={scroller}
|
|
300
387
|
class={cn(
|
|
301
|
-
"task-gantt relative min-h-0 flex-1 overflow-auto overscroll-x-contain
|
|
388
|
+
"task-gantt relative min-h-0 flex-1 overflow-auto overscroll-x-contain",
|
|
389
|
+
flat ? "bg-background" : "rounded-xl border border-border bg-card text-card-foreground",
|
|
302
390
|
drag && "select-none",
|
|
303
391
|
className,
|
|
304
392
|
)}
|
|
@@ -412,15 +500,16 @@
|
|
|
412
500
|
class="g-summary absolute top-1/2 h-1.5 -translate-y-1/2 rounded-full"
|
|
413
501
|
style:--g-hue={hue}
|
|
414
502
|
style:--g-i={Math.min(index, STAGGER_CAP)}
|
|
415
|
-
style:left="{x(row.span.start) +
|
|
416
|
-
style:width="{(row.span.end - row.span.start + 1) * dayWidth -
|
|
503
|
+
style:left="{x(row.span.start) + inset}px"
|
|
504
|
+
style:width="{Math.max((row.span.end - row.span.start + 1) * dayWidth - inset * 2, 8)}px"
|
|
417
505
|
></div>
|
|
418
506
|
{/if}
|
|
419
507
|
</div>
|
|
420
508
|
</div>
|
|
421
509
|
{:else}
|
|
422
|
-
{@const span =
|
|
423
|
-
{@const width = (span.end - span.start + 1) * dayWidth -
|
|
510
|
+
{@const span = row.span}
|
|
511
|
+
{@const width = span ? Math.max((span.end - span.start + 1) * dayWidth - inset * 2, 8) : 0}
|
|
512
|
+
{@const ctx = { depth: row.depth, hasChildren: row.hasChildren, open: row.open }}
|
|
424
513
|
{@const progress = Math.max(0, Math.min(1, getProgress?.(row.item) ?? 0))}
|
|
425
514
|
{@const title = getTitle(row.item)}
|
|
426
515
|
{@const dragging = drag?.id === row.id}
|
|
@@ -430,14 +519,74 @@
|
|
|
430
519
|
class:g-row-hot={hoveredId === row.id || dragging}
|
|
431
520
|
style:height="{rowHeight}px"
|
|
432
521
|
>
|
|
433
|
-
<div
|
|
522
|
+
<div
|
|
523
|
+
class="sticky left-0 z-10 flex w-(--g-label) shrink-0 items-center gap-2 border-r border-border bg-card px-3 text-sm"
|
|
524
|
+
style:padding-inline-start={getParent ? `${row.depth * INDENT + 12}px` : undefined}
|
|
525
|
+
>
|
|
526
|
+
{#if getParent}
|
|
527
|
+
{#if row.hasChildren}
|
|
528
|
+
<button
|
|
529
|
+
type="button"
|
|
530
|
+
aria-expanded={row.open}
|
|
531
|
+
aria-label="{row.open ? 'Fold' : 'Unfold'} {title}"
|
|
532
|
+
onclick={() => toggle(row.id)}
|
|
533
|
+
class="-ml-1 grid size-5 shrink-0 place-items-center rounded-md text-muted-foreground outline-none transition-colors duration-[var(--duration-quick,150ms)] hover:bg-foreground/5 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/60"
|
|
534
|
+
>
|
|
535
|
+
<span class="g-chevron size-3.5 [&>svg]:size-full" class:g-chevron-open={row.open} aria-hidden="true">
|
|
536
|
+
<CaretRight weight="bold" />
|
|
537
|
+
</span>
|
|
538
|
+
</button>
|
|
539
|
+
{:else}
|
|
540
|
+
<span class="-ml-1 size-5 shrink-0" aria-hidden="true"></span>
|
|
541
|
+
{/if}
|
|
542
|
+
{/if}
|
|
434
543
|
{#if renderLabel}
|
|
435
|
-
{@render renderLabel(row.item)}
|
|
544
|
+
{@render renderLabel(row.item, ctx)}
|
|
436
545
|
{:else}
|
|
437
546
|
<span class="truncate text-foreground">{title}</span>
|
|
438
547
|
{/if}
|
|
439
548
|
</div>
|
|
440
549
|
<div class="relative shrink-0" style:width="{timelineWidth}px">
|
|
550
|
+
{#if !span}
|
|
551
|
+
<span
|
|
552
|
+
class="pointer-events-none absolute top-1/2 -translate-y-1/2 text-xs whitespace-nowrap text-muted-foreground/70"
|
|
553
|
+
style:left="{Math.max(x(todayDay) - 40, 0) + 8}px"
|
|
554
|
+
>
|
|
555
|
+
{noDatesLabel}
|
|
556
|
+
</span>
|
|
557
|
+
{:else if isMilestone?.(row.item) && row.own}
|
|
558
|
+
<button
|
|
559
|
+
type="button"
|
|
560
|
+
aria-label="{title}, {rangeLabel(span)}"
|
|
561
|
+
title="{title}, {rangeLabel(span)}"
|
|
562
|
+
class="g-flag absolute top-1/2 grid size-6 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring/60"
|
|
563
|
+
style:--g-hue={hueOf(row.item)}
|
|
564
|
+
style:left="{x(span.end) + dayWidth / 2}px"
|
|
565
|
+
onclick={() => onSelect?.(row.id)}
|
|
566
|
+
onpointerenter={() => (hoveredId = row.id)}
|
|
567
|
+
onpointerleave={() => (hoveredId = hoveredId === row.id ? null : hoveredId)}
|
|
568
|
+
>
|
|
569
|
+
<span class="size-5 [&>svg]:size-full" aria-hidden="true"><FlagPennant weight="fill" /></span>
|
|
570
|
+
</button>
|
|
571
|
+
{:else if !row.own}
|
|
572
|
+
<!-- A parent with no dates of its own: the span of what is below it. Not draggable. -->
|
|
573
|
+
<div
|
|
574
|
+
role="button"
|
|
575
|
+
tabindex="0"
|
|
576
|
+
aria-label="{title}, {rangeLabel(span)}"
|
|
577
|
+
title="{title}, {rangeLabel(span)}"
|
|
578
|
+
class="g-derived absolute top-1/2 -translate-y-1/2 rounded-md outline-none focus-visible:ring-2 focus-visible:ring-ring/60"
|
|
579
|
+
style:--g-hue={hueOf(row.item)}
|
|
580
|
+
style:--g-i={Math.min(row.order, STAGGER_CAP)}
|
|
581
|
+
style:left="{x(span.start) + inset}px"
|
|
582
|
+
style:width="{width}px"
|
|
583
|
+
style:height="{Math.max(rowHeight - 24, 10)}px"
|
|
584
|
+
onclick={() => onSelect?.(row.id)}
|
|
585
|
+
onkeydown={(e) => (e.key === "Enter" || e.key === " ") && (e.preventDefault(), onSelect?.(row.id))}
|
|
586
|
+
onpointerenter={() => (hoveredId = row.id)}
|
|
587
|
+
onpointerleave={() => (hoveredId = hoveredId === row.id ? null : hoveredId)}
|
|
588
|
+
></div>
|
|
589
|
+
{:else}
|
|
441
590
|
<div
|
|
442
591
|
data-bar
|
|
443
592
|
role="button"
|
|
@@ -451,7 +600,7 @@
|
|
|
451
600
|
)}
|
|
452
601
|
style:--g-hue={hueOf(row.item)}
|
|
453
602
|
style:--g-i={Math.min(row.order, STAGGER_CAP)}
|
|
454
|
-
style:left="{x(span.start) +
|
|
603
|
+
style:left="{x(span.start) + inset}px"
|
|
455
604
|
style:width="{width}px"
|
|
456
605
|
style:height="{rowHeight - 14}px"
|
|
457
606
|
onpointerdown={(e) => onPointerDown(e, row.id, "move")}
|
|
@@ -503,6 +652,7 @@
|
|
|
503
652
|
{title}
|
|
504
653
|
</span>
|
|
505
654
|
{/if}
|
|
655
|
+
{/if}
|
|
506
656
|
</div>
|
|
507
657
|
</div>
|
|
508
658
|
{/if}
|
|
@@ -581,6 +731,26 @@
|
|
|
581
731
|
opacity: 0.6;
|
|
582
732
|
}
|
|
583
733
|
|
|
734
|
+
.g-chevron {
|
|
735
|
+
transition: rotate var(--duration-fast, 250ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1));
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.g-chevron-open {
|
|
739
|
+
rotate: 90deg;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/* A summary of what is below: an outline, so it never reads as a task you can drag. */
|
|
743
|
+
.g-derived {
|
|
744
|
+
background: color-mix(in oklab, var(--g-hue) 10%, transparent);
|
|
745
|
+
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--g-hue) 45%, transparent);
|
|
746
|
+
animation: g-bar-in var(--duration-slow, 400ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
|
|
747
|
+
animation-delay: calc(var(--g-i, 0) * var(--duration-stagger, 40ms));
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
.g-flag {
|
|
751
|
+
color: var(--g-hue);
|
|
752
|
+
}
|
|
753
|
+
|
|
584
754
|
.g-summary {
|
|
585
755
|
background: color-mix(in oklab, var(--g-hue) 45%, transparent);
|
|
586
756
|
animation: g-bar-in var(--duration-slow, 400ms) var(--ease-smooth-out, cubic-bezier(0.22, 1, 0.36, 1)) backwards;
|
|
@@ -657,6 +827,7 @@
|
|
|
657
827
|
@media (prefers-reduced-motion: reduce) {
|
|
658
828
|
.g-bar,
|
|
659
829
|
.g-summary,
|
|
830
|
+
.g-derived,
|
|
660
831
|
.g-tip,
|
|
661
832
|
.g-today,
|
|
662
833
|
.g-arrow-line,
|
|
@@ -665,7 +836,8 @@
|
|
|
665
836
|
}
|
|
666
837
|
.g-bar,
|
|
667
838
|
.g-summary,
|
|
668
|
-
.g-progress
|
|
839
|
+
.g-progress,
|
|
840
|
+
.g-chevron {
|
|
669
841
|
transition: none;
|
|
670
842
|
}
|
|
671
843
|
}
|