@nomideusz/svelte-calendar 0.9.0 → 0.10.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 +15 -7
- package/dist/calendar/Calendar.svelte +167 -59
- package/dist/calendar/Calendar.svelte.d.ts +7 -0
- package/dist/core/locale.d.ts +15 -1
- package/dist/core/locale.js +9 -2
- package/dist/theme/auto.js +27 -6
- package/dist/theme/presets.d.ts +4 -4
- package/dist/theme/presets.js +11 -7
- package/dist/views/agenda/AgendaDay.svelte +259 -110
- package/dist/views/agenda/AgendaWeek.svelte +190 -65
- package/dist/views/mobile/MobileDay.svelte +408 -213
- package/dist/views/mobile/MobileWeek.svelte +136 -74
- package/dist/views/mobile/swipe.d.ts +28 -0
- package/dist/views/mobile/swipe.js +64 -0
- package/dist/views/month/MonthGrid.svelte +166 -30
- package/dist/views/planner/PlannerDay.svelte +155 -80
- package/dist/views/planner/PlannerWeek.svelte +1155 -630
- package/dist/views/planner/PlannerWeek.svelte.d.ts +2 -0
- package/dist/views/shared/context.svelte.d.ts +3 -0
- package/dist/views/shared/context.svelte.js +13 -9
- package/dist/views/shared/format.d.ts +2 -1
- package/dist/views/shared/format.js +8 -5
- package/dist/widget/CalendarWidget.svelte +33 -5
- package/dist/widget/CalendarWidget.svelte.d.ts +16 -2
- package/dist/widget/widget.d.ts +9 -0
- package/dist/widget/widget.js +59 -13
- package/package.json +1 -1
- package/widget/widget.js +3263 -2727
- package/widget/svelte-calendar.css +0 -3054
|
@@ -8,14 +8,11 @@
|
|
|
8
8
|
<script lang="ts">import { onMount, tick, untrack } from "svelte";
|
|
9
9
|
import { useCalendarContext } from "../shared/context.svelte.js";
|
|
10
10
|
import EventContent from "../shared/EventContent.svelte";
|
|
11
|
-
import { fly } from "svelte/transition";
|
|
12
11
|
import { createClock } from "../../core/clock.svelte.js";
|
|
13
12
|
import { createTextMeasure } from "../../core/measure.js";
|
|
14
13
|
import { DAY_MS, HOUR_MS, sod } from "../../core/time.js";
|
|
15
14
|
import { isAllDay, isMultiDay, segmentForDay } from "../../core/time.js";
|
|
16
|
-
import { fmtH, fmtTime, weekdayShort } from "../../core/locale.js";
|
|
17
|
-
import { getLabels } from "../../core/locale.js";
|
|
18
|
-
const L = $derived(getLabels());
|
|
15
|
+
import { fmtDuration, fmtH, fmtTime, weekdayShort } from "../../core/locale.js";
|
|
19
16
|
let {
|
|
20
17
|
height = 520,
|
|
21
18
|
events = [],
|
|
@@ -29,6 +26,7 @@ let {
|
|
|
29
26
|
visibleHours
|
|
30
27
|
} = $props();
|
|
31
28
|
const ctx = useCalendarContext();
|
|
29
|
+
const L = $derived(ctx.labels);
|
|
32
30
|
const clock = createClock(ctx.timezone);
|
|
33
31
|
const drag = $derived(ctx.drag);
|
|
34
32
|
const commitDragCtx = $derived(ctx.commitDrag);
|
|
@@ -92,8 +90,8 @@ function timeToPx(ms) {
|
|
|
92
90
|
const elapsed = ms - origin;
|
|
93
91
|
const dayIndex = Math.floor(elapsed / DAY_MS);
|
|
94
92
|
const hourInDay = (elapsed - dayIndex * DAY_MS) / HOUR_MS;
|
|
95
|
-
const
|
|
96
|
-
return dayIndex * (dayWidth + DAY_GAP) +
|
|
93
|
+
const clamped = Math.min(Math.max(hourInDay, startHour), endHour);
|
|
94
|
+
return dayIndex * (dayWidth + DAY_GAP) + (clamped - startHour) * hourWidth;
|
|
97
95
|
}
|
|
98
96
|
function pxToTime(px) {
|
|
99
97
|
const dayStride = dayWidth + DAY_GAP;
|
|
@@ -103,6 +101,8 @@ function pxToTime(px) {
|
|
|
103
101
|
return origin + dayIndex * DAY_MS + hour * HOUR_MS;
|
|
104
102
|
}
|
|
105
103
|
const nowPx = $derived(timeToPx(clock.tick));
|
|
104
|
+
const nowHour = $derived((clock.tick - clock.today) / HOUR_MS);
|
|
105
|
+
const nowInBand = $derived(nowHour >= startHour && nowHour < endHour);
|
|
106
106
|
const timedEvents = $derived(events.filter((ev) => !isAllDay(ev) && !isMultiDay(ev)));
|
|
107
107
|
const allDayEvents = $derived.by(() => {
|
|
108
108
|
const segs = [];
|
|
@@ -126,8 +126,25 @@ const measure = createTextMeasure({
|
|
|
126
126
|
secondaryLineHeight: 13,
|
|
127
127
|
contentGap: 6
|
|
128
128
|
});
|
|
129
|
-
const
|
|
129
|
+
const nowInfo = $derived.by(() => {
|
|
130
130
|
const now = clock.tick;
|
|
131
|
+
const current = /* @__PURE__ */ new Set();
|
|
132
|
+
const todayStart = clock.today;
|
|
133
|
+
const todayEnd = todayStart + DAY_MS;
|
|
134
|
+
let nextId = null;
|
|
135
|
+
let nextStart = Infinity;
|
|
136
|
+
for (const ev of timedEvents) {
|
|
137
|
+
const s = ev.start.getTime();
|
|
138
|
+
const e = ev.end.getTime();
|
|
139
|
+
if (s <= now && e > now) current.add(ev.id);
|
|
140
|
+
else if (s >= todayStart && s < todayEnd && s > now && s < nextStart) {
|
|
141
|
+
nextStart = s;
|
|
142
|
+
nextId = ev.id;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { current, nextId };
|
|
146
|
+
});
|
|
147
|
+
const positionedEvents = $derived.by(() => {
|
|
131
148
|
const dragP = drag?.active && (drag.mode === "move" || drag.mode === "resize-start" || drag.mode === "resize-end") ? drag.payload : null;
|
|
132
149
|
const staticEvents = [];
|
|
133
150
|
let draggedEv = null;
|
|
@@ -136,17 +153,6 @@ const positionedEvents = $derived.by(() => {
|
|
|
136
153
|
else staticEvents.push(ev);
|
|
137
154
|
}
|
|
138
155
|
const sorted = [...staticEvents].sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
139
|
-
const todayStart = sod(now);
|
|
140
|
-
const todayEnd = todayStart + DAY_MS;
|
|
141
|
-
let nextEventId = null;
|
|
142
|
-
for (const ev of sorted) {
|
|
143
|
-
const s = ev.start.getTime();
|
|
144
|
-
const e = ev.end.getTime();
|
|
145
|
-
if (s >= todayStart && s < todayEnd && s > now && !(s <= now && e > now)) {
|
|
146
|
-
nextEventId = ev.id;
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
156
|
const infos = sorted.map((ev) => {
|
|
151
157
|
const s = ev.start.getTime();
|
|
152
158
|
const e = ev.end.getTime();
|
|
@@ -158,13 +164,12 @@ const positionedEvents = $derived.by(() => {
|
|
|
158
164
|
width: Math.max(xEnd - x, 28),
|
|
159
165
|
row: 0,
|
|
160
166
|
groupMaxRow: 1,
|
|
161
|
-
isCurrent: s <= now && e > now,
|
|
162
|
-
isNext: ev.id === nextEventId,
|
|
163
167
|
isDragged: false,
|
|
164
168
|
startMs: s,
|
|
165
|
-
endMs: e
|
|
169
|
+
endMs: e,
|
|
170
|
+
clippedWidth: xEnd - x
|
|
166
171
|
};
|
|
167
|
-
});
|
|
172
|
+
}).filter((info) => info.clippedWidth > 0);
|
|
168
173
|
const par = infos.map((_, i) => i);
|
|
169
174
|
function find(i) {
|
|
170
175
|
while (par[i] !== i) {
|
|
@@ -204,7 +209,7 @@ const positionedEvents = $derived.by(() => {
|
|
|
204
209
|
for (const idx of indices) infos[idx].groupMaxRow = rows.length;
|
|
205
210
|
}
|
|
206
211
|
const availH = containerH - contentTop - 8;
|
|
207
|
-
const result = infos.map(({ startMs: _s, endMs: _e, ...info }) => {
|
|
212
|
+
const result = infos.map(({ startMs: _s, endMs: _e, clippedWidth: _c, ...info }) => {
|
|
208
213
|
const laneH = Math.max(MIN_EVENT_H, availH / info.groupMaxRow - EVENT_GAP);
|
|
209
214
|
const topPx = contentTop + info.row * (availH / info.groupMaxRow);
|
|
210
215
|
const fit = measure.fitContent({
|
|
@@ -216,7 +221,7 @@ const positionedEvents = $derived.by(() => {
|
|
|
216
221
|
maxWidth: laneH - 16,
|
|
217
222
|
maxHeight: info.width - 16
|
|
218
223
|
});
|
|
219
|
-
return { ...info, topPx, heightPx: laneH,
|
|
224
|
+
return { ...info, topPx, heightPx: laneH, fit };
|
|
220
225
|
});
|
|
221
226
|
if (draggedEv && dragP) {
|
|
222
227
|
const x = timeToPx(dragP.start.getTime());
|
|
@@ -231,8 +236,6 @@ const positionedEvents = $derived.by(() => {
|
|
|
231
236
|
groupMaxRow: 1,
|
|
232
237
|
topPx: contentTop,
|
|
233
238
|
heightPx: dragH,
|
|
234
|
-
isCurrent: draggedEv.start.getTime() <= now && draggedEv.end.getTime() > now,
|
|
235
|
-
isNext: false,
|
|
236
239
|
isDragged: true,
|
|
237
240
|
fit: measure.fitContent({
|
|
238
241
|
title: draggedEv.title,
|
|
@@ -297,16 +300,20 @@ function syncFocusFromScroll() {
|
|
|
297
300
|
viewState.setFocusDate(new Date(centerDayMs));
|
|
298
301
|
}
|
|
299
302
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
303
|
+
let scrollRaf = 0;
|
|
304
|
+
function handleScroll() {
|
|
305
|
+
if (following || rebasing || scrollRaf) return;
|
|
306
|
+
scrollRaf = requestAnimationFrame(() => {
|
|
307
|
+
scrollRaf = 0;
|
|
308
|
+
if (!el || following || rebasing) return;
|
|
309
|
+
checkEdges();
|
|
310
|
+
syncFocusFromScroll();
|
|
306
311
|
});
|
|
307
|
-
|
|
312
|
+
}
|
|
313
|
+
$effect(() => {
|
|
314
|
+
if (!following) return;
|
|
308
315
|
function frame() {
|
|
309
|
-
if (
|
|
316
|
+
if (el && !scrollDragging) {
|
|
310
317
|
if (internalCenterMs !== clock.today) {
|
|
311
318
|
internalCenterMs = clock.today;
|
|
312
319
|
lastExternalMs = clock.today;
|
|
@@ -317,15 +324,22 @@ onMount(() => {
|
|
|
317
324
|
if (todayD) {
|
|
318
325
|
el.scrollLeft = todayD.x + dayWidth / 2 - el.clientWidth / 2;
|
|
319
326
|
}
|
|
320
|
-
} else if (el && !rebasing) {
|
|
321
|
-
checkEdges();
|
|
322
|
-
syncFocusFromScroll();
|
|
323
327
|
}
|
|
324
328
|
rafId = requestAnimationFrame(frame);
|
|
325
329
|
}
|
|
326
330
|
rafId = requestAnimationFrame(frame);
|
|
331
|
+
return () => cancelAnimationFrame(rafId);
|
|
332
|
+
});
|
|
333
|
+
onMount(() => {
|
|
334
|
+
const ro = new ResizeObserver((entries) => {
|
|
335
|
+
for (const entry of entries) {
|
|
336
|
+
containerW = entry.contentRect.width;
|
|
337
|
+
containerH = entry.contentRect.height;
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
ro.observe(el);
|
|
327
341
|
return () => {
|
|
328
|
-
cancelAnimationFrame(
|
|
342
|
+
cancelAnimationFrame(scrollRaf);
|
|
329
343
|
ro.disconnect();
|
|
330
344
|
};
|
|
331
345
|
});
|
|
@@ -558,20 +572,52 @@ function onResizeCancel() {
|
|
|
558
572
|
if (drag && rsStarted) drag.cancel();
|
|
559
573
|
cleanupResize();
|
|
560
574
|
}
|
|
575
|
+
function onWindowKeydown(e) {
|
|
576
|
+
if (e.key !== "Escape" || !drag?.active) return;
|
|
577
|
+
drag.cancel();
|
|
578
|
+
cleanupCreateDrag();
|
|
579
|
+
cleanupEvDrag();
|
|
580
|
+
cleanupResize();
|
|
581
|
+
wasDragging = true;
|
|
582
|
+
window.addEventListener(
|
|
583
|
+
"pointerup",
|
|
584
|
+
() => setTimeout(() => {
|
|
585
|
+
wasDragging = false;
|
|
586
|
+
}, 0),
|
|
587
|
+
{ once: true }
|
|
588
|
+
);
|
|
589
|
+
}
|
|
561
590
|
</script>
|
|
562
591
|
|
|
592
|
+
<svelte:window onkeydown={onWindowKeydown} />
|
|
593
|
+
|
|
563
594
|
<div class="fs" class:fs--auto={autoHeight} style={style || undefined} style:height={autoHeight ? undefined : (height ? `${height}px` : '100%')} role="region" aria-label={L.dayPlanner}>
|
|
564
595
|
<div
|
|
565
596
|
class="fs-scroll"
|
|
566
597
|
class:fs-grabbing={scrollDragging}
|
|
567
598
|
class:fs-readonly={readOnly}
|
|
568
599
|
bind:this={el}
|
|
569
|
-
onwheel={(e) => {
|
|
600
|
+
onwheel={(e) => {
|
|
601
|
+
const delta = e.deltaY || e.deltaX;
|
|
602
|
+
if (delta === 0) return;
|
|
603
|
+
// Only capture the wheel when the track can consume the delta in
|
|
604
|
+
// that direction — otherwise let the page scroll normally.
|
|
605
|
+
const maxScroll = el.scrollWidth - el.clientWidth;
|
|
606
|
+
const canConsume = delta < 0 ? el.scrollLeft > 0 : el.scrollLeft < maxScroll - 1;
|
|
607
|
+
if (!canConsume) return;
|
|
608
|
+
e.preventDefault();
|
|
609
|
+
el.scrollLeft += delta;
|
|
610
|
+
following = false;
|
|
611
|
+
}}
|
|
612
|
+
onscroll={handleScroll}
|
|
570
613
|
onpointerdown={onPointerDown}
|
|
571
|
-
role="
|
|
614
|
+
role="region"
|
|
572
615
|
aria-label={L.scrollableDayPlanner}
|
|
573
616
|
>
|
|
574
|
-
|
|
617
|
+
<!-- Presentational layer: the click handler is a convenience delegate for
|
|
618
|
+
click-to-create on empty canvas; keyboard users create events via the
|
|
619
|
+
host UI, and events themselves are focusable buttons. -->
|
|
620
|
+
<div class="fs-track" style:width="{totalWidth}px" onclick={handleTrackClick} role="presentation">
|
|
575
621
|
{#each days as d (d.ms)}
|
|
576
622
|
<div
|
|
577
623
|
class="fs-day"
|
|
@@ -623,19 +669,23 @@ function onResizeCancel() {
|
|
|
623
669
|
</div>
|
|
624
670
|
{/each}
|
|
625
671
|
|
|
626
|
-
<!-- Now line -->
|
|
627
|
-
|
|
628
|
-
<
|
|
629
|
-
|
|
630
|
-
|
|
672
|
+
<!-- Now line (hidden when the clock is outside the visible hour band) -->
|
|
673
|
+
{#if nowInBand}
|
|
674
|
+
<div class="fs-now" style:left="{nowPx}px">
|
|
675
|
+
<span class="fs-now-tag">{clock.hm}</span>
|
|
676
|
+
<div class="fs-now-line"></div>
|
|
677
|
+
</div>
|
|
678
|
+
{/if}
|
|
631
679
|
|
|
632
680
|
<!-- Events -->
|
|
633
681
|
{#each positionedEvents as p (p.ev.id)}
|
|
682
|
+
{@const isCurrent = nowInfo.current.has(p.ev.id)}
|
|
683
|
+
{@const isNext = !p.isDragged && p.ev.id === nowInfo.nextId}
|
|
634
684
|
<div
|
|
635
685
|
class="fs-event"
|
|
636
686
|
class:fs-event--selected={selectedEventId === p.ev.id}
|
|
637
|
-
class:fs-event--current={
|
|
638
|
-
class:fs-event--next={
|
|
687
|
+
class:fs-event--current={isCurrent}
|
|
688
|
+
class:fs-event--next={isNext}
|
|
639
689
|
class:fs-event--dragging={p.isDragged}
|
|
640
690
|
class:fs-event--resizing={p.isDragged && drag?.mode !== 'move'}
|
|
641
691
|
class:fs-event--readonly={p.ev.data?.readOnly}
|
|
@@ -651,15 +701,15 @@ function onResizeCancel() {
|
|
|
651
701
|
role="button"
|
|
652
702
|
tabindex="0"
|
|
653
703
|
title={p.ev.title}
|
|
654
|
-
aria-label="{p.ev.title}{p.ev.status === 'cancelled' ? ' (cancelled)' : ''}{p.ev.status === 'tentative' ? ' (tentative)' : ''}{p.ev.status === 'full' ? ' (full)' : ''}{p.ev.status === 'limited' ? ' (limited)' : ''}{
|
|
704
|
+
aria-label="{p.ev.title}, {fmtTime(p.ev.start, locale)} – {fmtTime(p.ev.end, locale)}, {fmtDuration(p.ev.start, p.ev.end)}{p.ev.status === 'cancelled' ? ' (cancelled)' : ''}{p.ev.status === 'tentative' ? ' (tentative)' : ''}{p.ev.status === 'full' ? ' (full)' : ''}{p.ev.status === 'limited' ? ' (limited)' : ''}{isCurrent ? ` (${L.inProgress})` : ''}{isNext ? ` (${L.upNext})` : ''}"
|
|
655
705
|
onpointerdown={(e) => onEventPointerDown(e, p.ev)}
|
|
656
706
|
onpointerenter={() => oneventhover?.(p.ev)}
|
|
657
707
|
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); oneventclick?.(p.ev); } }}
|
|
658
708
|
>
|
|
659
709
|
<div class="fs-ev-inner">
|
|
660
|
-
{#if
|
|
710
|
+
{#if isCurrent}
|
|
661
711
|
<span class="fs-ev-live" aria-hidden="true"></span>
|
|
662
|
-
{:else if
|
|
712
|
+
{:else if isNext}
|
|
663
713
|
<span class="fs-ev-next-badge" aria-hidden="true">{L.upNext}</span>
|
|
664
714
|
{/if}
|
|
665
715
|
<EventContent event={p.ev}>
|
|
@@ -797,9 +847,10 @@ function onResizeCancel() {
|
|
|
797
847
|
border-left: 1px solid var(--dt-border-day, rgba(0, 0, 0, 0.14));
|
|
798
848
|
box-sizing: border-box;
|
|
799
849
|
}
|
|
800
|
-
.fs-
|
|
801
|
-
|
|
802
|
-
|
|
850
|
+
.fs-today { background: var(--dt-today-bg, color-mix(in srgb, var(--dt-accent, #2563eb) 8%, transparent)); }
|
|
851
|
+
/* Past days: dim via a background wash instead of a subtree opacity so
|
|
852
|
+
event text keeps full contrast. */
|
|
853
|
+
.fs-past { background: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 3%, transparent); }
|
|
803
854
|
|
|
804
855
|
/* ─── Disabled day ───────────────────────────────── */
|
|
805
856
|
.fs-disabled {
|
|
@@ -877,12 +928,11 @@ function onResizeCancel() {
|
|
|
877
928
|
white-space: nowrap;
|
|
878
929
|
pointer-events: none;
|
|
879
930
|
}
|
|
880
|
-
|
|
931
|
+
/* Half-hour guide: full-height line at low opacity through the event area */
|
|
881
932
|
.fs-tick--half::before {
|
|
882
933
|
top: 18px;
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
opacity: 0.4;
|
|
934
|
+
bottom: 0;
|
|
935
|
+
opacity: 0.35;
|
|
886
936
|
}
|
|
887
937
|
|
|
888
938
|
/* ─── Now-line ────────────────────────────────────── */
|
|
@@ -905,7 +955,9 @@ function onResizeCancel() {
|
|
|
905
955
|
}
|
|
906
956
|
.fs-now-tag {
|
|
907
957
|
position: absolute;
|
|
908
|
-
top:
|
|
958
|
+
/* Below the hour-label row (labels sit at top: 2px) so the tag never
|
|
959
|
+
collides with an hour label near hour boundaries. */
|
|
960
|
+
top: 20px;
|
|
909
961
|
left: 8px;
|
|
910
962
|
font: 700 11px/1 var(--dt-mono, ui-monospace, monospace);
|
|
911
963
|
color: var(--dt-accent, #2563eb);
|
|
@@ -916,13 +968,9 @@ function onResizeCancel() {
|
|
|
916
968
|
white-space: nowrap;
|
|
917
969
|
z-index: 1;
|
|
918
970
|
}
|
|
919
|
-
.fs-now-sec {
|
|
920
|
-
font-weight: 400;
|
|
921
|
-
opacity: 0.5;
|
|
922
|
-
font-size: 10px;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
971
|
/* ─── All-day strip ─────────────────────────────── */
|
|
972
|
+
/* The container is a full-width overlay — let clicks pass through it and
|
|
973
|
+
only the chips themselves capture pointer events. */
|
|
926
974
|
.fs-allday {
|
|
927
975
|
position: absolute;
|
|
928
976
|
left: 0;
|
|
@@ -933,7 +981,7 @@ function onResizeCancel() {
|
|
|
933
981
|
z-index: 7;
|
|
934
982
|
overflow-x: auto;
|
|
935
983
|
scrollbar-width: none;
|
|
936
|
-
pointer-events:
|
|
984
|
+
pointer-events: none;
|
|
937
985
|
}
|
|
938
986
|
.fs-allday::-webkit-scrollbar { display: none; }
|
|
939
987
|
|
|
@@ -947,15 +995,18 @@ function onResizeCancel() {
|
|
|
947
995
|
border-left: 3px solid var(--ev-color);
|
|
948
996
|
white-space: nowrap;
|
|
949
997
|
flex-shrink: 0;
|
|
998
|
+
min-width: 0;
|
|
999
|
+
max-width: 320px;
|
|
950
1000
|
cursor: pointer;
|
|
951
1001
|
transition: background 0.15s;
|
|
1002
|
+
pointer-events: auto;
|
|
952
1003
|
}
|
|
953
1004
|
.fs-ad:hover {
|
|
954
1005
|
background: color-mix(in srgb, var(--ev-color) 28%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
955
1006
|
}
|
|
956
1007
|
.fs-ad:focus-visible {
|
|
957
|
-
outline:
|
|
958
|
-
|
|
1008
|
+
outline: none;
|
|
1009
|
+
box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
|
|
959
1010
|
}
|
|
960
1011
|
.fs-ad--selected {
|
|
961
1012
|
background: color-mix(in srgb, var(--ev-color) 30%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
@@ -974,7 +1025,8 @@ function onResizeCancel() {
|
|
|
974
1025
|
font-size: 0.7rem;
|
|
975
1026
|
font-weight: 500;
|
|
976
1027
|
color: var(--dt-text, rgba(0, 0, 0, 0.87));
|
|
977
|
-
|
|
1028
|
+
flex: 0 1 auto;
|
|
1029
|
+
min-width: 0;
|
|
978
1030
|
overflow: hidden;
|
|
979
1031
|
text-overflow: ellipsis;
|
|
980
1032
|
}
|
|
@@ -990,7 +1042,10 @@ function onResizeCancel() {
|
|
|
990
1042
|
position: absolute;
|
|
991
1043
|
z-index: 6;
|
|
992
1044
|
border-radius: 6px;
|
|
993
|
-
|
|
1045
|
+
/* Editable events are grabbable; touch drags move the event instead of
|
|
1046
|
+
scrolling the strip. */
|
|
1047
|
+
cursor: grab;
|
|
1048
|
+
touch-action: none;
|
|
994
1049
|
background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, var(--dt-bg, #ffffff)));
|
|
995
1050
|
border: 1px solid color-mix(in srgb, var(--ev-color) 40%, transparent);
|
|
996
1051
|
/* Solid stripe at the start edge — matches the week view, keeps the
|
|
@@ -1041,6 +1096,15 @@ function onResizeCancel() {
|
|
|
1041
1096
|
cursor: ew-resize;
|
|
1042
1097
|
touch-action: none;
|
|
1043
1098
|
}
|
|
1099
|
+
/* Hit-slop: ~20px effective grab zone while the visual stays 6px */
|
|
1100
|
+
.fs-ev-handle::before {
|
|
1101
|
+
content: '';
|
|
1102
|
+
position: absolute;
|
|
1103
|
+
top: 0;
|
|
1104
|
+
bottom: 0;
|
|
1105
|
+
left: -7px;
|
|
1106
|
+
right: -7px;
|
|
1107
|
+
}
|
|
1044
1108
|
.fs-ev-handle--start { left: 0; }
|
|
1045
1109
|
.fs-ev-handle--end { right: 0; }
|
|
1046
1110
|
.fs-ev-handle::after {
|
|
@@ -1055,7 +1119,12 @@ function onResizeCancel() {
|
|
|
1055
1119
|
opacity: 0;
|
|
1056
1120
|
transition: opacity 120ms;
|
|
1057
1121
|
}
|
|
1058
|
-
.fs-event:hover .fs-ev-handle::after
|
|
1122
|
+
.fs-event:hover .fs-ev-handle::after,
|
|
1123
|
+
.fs-event:focus-within .fs-ev-handle::after { opacity: 0.55; }
|
|
1124
|
+
/* Coarse pointers can't hover — show the grips persistently */
|
|
1125
|
+
@media (hover: none) {
|
|
1126
|
+
.fs-ev-handle::after { opacity: 0.55; }
|
|
1127
|
+
}
|
|
1059
1128
|
|
|
1060
1129
|
/* ─── Drag-to-create ghost ───────────────────────── */
|
|
1061
1130
|
.fs-create-ghost {
|
|
@@ -1082,12 +1151,16 @@ function onResizeCancel() {
|
|
|
1082
1151
|
.fs-event--dragging {
|
|
1083
1152
|
transition: box-shadow 120ms, background 120ms;
|
|
1084
1153
|
}
|
|
1154
|
+
.fs-create-ghost,
|
|
1155
|
+
.fs-ad,
|
|
1156
|
+
.fs-ev-handle::after {
|
|
1157
|
+
transition: none;
|
|
1158
|
+
}
|
|
1085
1159
|
}
|
|
1086
|
-
|
|
1087
|
-
opacity: 0.5;
|
|
1088
|
-
}
|
|
1160
|
+
/* Cancelled: strikethrough + secondary text, not a subtree opacity dim */
|
|
1089
1161
|
.fs-event--cancelled .fs-ev-title {
|
|
1090
1162
|
text-decoration: line-through;
|
|
1163
|
+
color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
|
|
1091
1164
|
}
|
|
1092
1165
|
.fs-event--tentative {
|
|
1093
1166
|
opacity: 0.65;
|
|
@@ -1100,7 +1173,8 @@ function onResizeCancel() {
|
|
|
1100
1173
|
opacity: 0.65;
|
|
1101
1174
|
border: 1px dashed color-mix(in srgb, var(--ev-color) 40%, transparent);
|
|
1102
1175
|
}
|
|
1103
|
-
.fs-event--readonly
|
|
1176
|
+
.fs-event--readonly,
|
|
1177
|
+
.fs-readonly .fs-event {
|
|
1104
1178
|
cursor: default;
|
|
1105
1179
|
}
|
|
1106
1180
|
|
|
@@ -1149,6 +1223,7 @@ function onResizeCancel() {
|
|
|
1149
1223
|
display: -webkit-box;
|
|
1150
1224
|
-webkit-box-orient: vertical;
|
|
1151
1225
|
-webkit-line-clamp: 2;
|
|
1226
|
+
line-clamp: 2;
|
|
1152
1227
|
white-space: normal;
|
|
1153
1228
|
max-height: 100%;
|
|
1154
1229
|
flex-shrink: 0;
|
|
@@ -1156,14 +1231,12 @@ function onResizeCancel() {
|
|
|
1156
1231
|
.fs-ev-time {
|
|
1157
1232
|
font: 400 10px/1 var(--dt-mono, ui-monospace, monospace);
|
|
1158
1233
|
color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
|
|
1159
|
-
opacity: 0.7;
|
|
1160
1234
|
white-space: nowrap;
|
|
1161
1235
|
flex-shrink: 0;
|
|
1162
1236
|
}
|
|
1163
1237
|
.fs-ev-sub {
|
|
1164
1238
|
font: 400 11px/1 var(--dt-sans, system-ui, sans-serif);
|
|
1165
1239
|
color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
|
|
1166
|
-
opacity: 0.6;
|
|
1167
1240
|
white-space: nowrap;
|
|
1168
1241
|
overflow: hidden;
|
|
1169
1242
|
text-overflow: ellipsis;
|
|
@@ -1172,7 +1245,7 @@ function onResizeCancel() {
|
|
|
1172
1245
|
}
|
|
1173
1246
|
.fs-ev-loc {
|
|
1174
1247
|
font: 400 10px/1 var(--dt-sans, system-ui, sans-serif);
|
|
1175
|
-
color: var(--dt-text-
|
|
1248
|
+
color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
|
|
1176
1249
|
white-space: nowrap;
|
|
1177
1250
|
overflow: hidden;
|
|
1178
1251
|
text-overflow: ellipsis;
|
|
@@ -1195,9 +1268,11 @@ function onResizeCancel() {
|
|
|
1195
1268
|
}
|
|
1196
1269
|
|
|
1197
1270
|
/* ─── Focus-visible ──────────────────────────────── */
|
|
1271
|
+
/* box-shadow instead of outline: outlines get clipped by the
|
|
1272
|
+
overflow: hidden scroll container. */
|
|
1198
1273
|
.fs-event:focus-visible {
|
|
1199
|
-
outline:
|
|
1200
|
-
|
|
1274
|
+
outline: none;
|
|
1275
|
+
box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
|
|
1201
1276
|
}
|
|
1202
1277
|
</style>
|
|
1203
1278
|
|