@nomideusz/svelte-calendar 0.7.0 → 0.7.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.
@@ -948,7 +948,10 @@
948
948
  display: flex;
949
949
  align-items: center;
950
950
  justify-content: center;
951
- transition: box-shadow 120ms, background 120ms;
951
+ /* top/height only: lane reflow animates on drop; left/width stay instant so
952
+ mount-time width measurement and infinite-scroll rebases don't slide cards */
953
+ transition: box-shadow 120ms, background 120ms,
954
+ top 180ms cubic-bezier(0.2, 0.8, 0.2, 1), height 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
952
955
  }
953
956
  .fs-event:hover {
954
957
  background: color-mix(in srgb, var(--ev-color) 25%, var(--dt-surface, var(--dt-bg, #ffffff)));
@@ -970,7 +973,15 @@
970
973
  z-index: 50;
971
974
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
972
975
  cursor: grabbing;
973
- transition: none;
976
+ /* fast ease toward the snapped cursor position */
977
+ transition: left 80ms ease-out, width 80ms ease-out;
978
+ }
979
+
980
+ @media (prefers-reduced-motion: reduce) {
981
+ .fs-event,
982
+ .fs-event--dragging {
983
+ transition: box-shadow 120ms, background 120ms;
984
+ }
974
985
  }
975
986
  .fs-event--cancelled {
976
987
  opacity: 0.5;
@@ -10,6 +10,9 @@
10
10
  -->
11
11
  <script lang="ts">
12
12
  import { onMount, tick, untrack } from 'svelte';
13
+ import { flip } from 'svelte/animate';
14
+ import { crossfade } from 'svelte/transition';
15
+ import { prefersReducedMotion } from 'svelte/motion';
13
16
  import { useCalendarContext } from '../shared/context.svelte.js';
14
17
  import { createClock } from '../../core/clock.svelte.js';
15
18
  import type { TimelineEvent, BlockedSlot } from '../../core/types.js';
@@ -51,6 +54,11 @@
51
54
 
52
55
  const ctx = useCalendarContext();
53
56
  const clock = createClock();
57
+
58
+ // Drag ghost flies between day cells instead of teleporting.
59
+ // No fallback: without a counterpart (drag start/end) it appears/disappears instantly.
60
+ const ANIM = $derived(prefersReducedMotion.current ? 0 : 180);
61
+ const [previewSend, previewReceive] = crossfade({ duration: () => (prefersReducedMotion.current ? 0 : 160) });
54
62
  const drag = $derived(ctx.drag);
55
63
  const commitDragCtx = $derived(ctx.commitDrag);
56
64
  const viewState = $derived(ctx.viewState);
@@ -312,17 +320,27 @@
312
320
  return day.events.filter((ev) => ev.id !== dragPreviewEvent.id);
313
321
  }
314
322
 
323
+ // Crossfade keys for the previews, snapshotted at render time. Transition
324
+ // params are evaluated lazily at unmount — after the drag payload is
325
+ // already null — so they must never read the reactive preview directly.
326
+ // Plain Map (not $state): written during render, read only by transitions.
327
+ const previewKeySnapshot = new Map<number | 'timed', string>();
328
+
315
329
  function dragPreviewTimedForDay(dayMs: number): TimelineEvent | null {
316
330
  const ev = dragPreviewEvent;
317
331
  if (!ev || isAllDay(ev) || isMultiDay(ev)) return null;
318
332
  const dayEnd = dayMs + DAY_MS;
319
- return ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs ? ev : null;
333
+ const hit = ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs;
334
+ if (hit) previewKeySnapshot.set('timed', ev.id);
335
+ return hit ? ev : null;
320
336
  }
321
337
 
322
338
  function dragPreviewSegmentForDay(dayMs: number): DaySegment | null {
323
339
  const ev = dragPreviewEvent;
324
340
  if (!ev || (!isAllDay(ev) && !isMultiDay(ev))) return null;
325
- return segmentForDay(ev, dayMs);
341
+ const seg = segmentForDay(ev, dayMs);
342
+ if (seg) previewKeySnapshot.set(dayMs, `${ev.id}:${seg.dayIndex}`);
343
+ return seg;
326
344
  }
327
345
 
328
346
  function getCellWidth(): number {
@@ -490,6 +508,9 @@
490
508
  <div class="wg-allday">
491
509
  {#each visibleAllDaySegments as seg (seg.ev.id)}
492
510
  <div
511
+ animate:flip={{ duration: ANIM }}
512
+ in:previewReceive={{ key: `${seg.ev.id}:${seg.dayIndex}` }}
513
+ out:previewSend={{ key: `${seg.ev.id}:${seg.dayIndex}` }}
493
514
  class="wg-ad"
494
515
  class:wg-ad--start={seg.isStart}
495
516
  class:wg-ad--end={seg.isEnd}
@@ -506,6 +527,8 @@
506
527
  </div>
507
528
  {/each}
508
529
  {#if previewSegment}
530
+ <!-- key by event id + dayIndex: multi-day previews render one segment
531
+ per cell, and each pairs with its own real-card counterpart -->
509
532
  <div
510
533
  class="wg-ad wg-ad--drag-preview"
511
534
  class:wg-ad--start={previewSegment.isStart}
@@ -513,6 +536,8 @@
513
536
  class:wg-ad--mid={!previewSegment.isStart && !previewSegment.isEnd}
514
537
  style:--ev-color={previewSegment.ev.color ?? 'var(--dt-accent)'}
515
538
  aria-hidden="true"
539
+ in:previewReceive={{ key: previewKeySnapshot.get(day.ms) ?? '' }}
540
+ out:previewSend={{ key: previewKeySnapshot.get(day.ms) ?? '' }}
516
541
  >
517
542
  {@render allDaySegmentContent(previewSegment)}
518
543
  </div>
@@ -523,7 +548,12 @@
523
548
  <!-- Timed events -->
524
549
  <div class="wg-cell-events">
525
550
  {#each visibleTimedEvents.slice(0, MAX_EVENTS_SHOWN) as ev (ev.id)}
551
+ <!-- send/receive keyed by event id pair the card with the drag ghost:
552
+ drag start morphs card → ghost, drop morphs ghost → placed card -->
526
553
  <div
554
+ animate:flip={{ duration: ANIM }}
555
+ in:previewReceive={{ key: ev.id }}
556
+ out:previewSend={{ key: ev.id }}
527
557
  class="wg-ev"
528
558
  class:wg-ev--selected={selectedEventId === ev.id}
529
559
  class:wg-ev--current={ev.start.getTime() <= clock.tick && ev.end.getTime() > clock.tick}
@@ -549,6 +579,8 @@
549
579
  class="wg-ev wg-ev--drag-preview"
550
580
  style:--ev-color={previewTimedEvent.color ?? 'var(--dt-accent)'}
551
581
  aria-hidden="true"
582
+ in:previewReceive={{ key: previewKeySnapshot.get('timed') ?? '' }}
583
+ out:previewSend={{ key: previewKeySnapshot.get('timed') ?? '' }}
552
584
  >
553
585
  {@render timedEventContent(previewTimedEvent)}
554
586
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomideusz/svelte-calendar",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "A themeable Svelte 5 calendar with Day and Week views — Planner and Agenda.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -241,7 +241,10 @@
241
241
  display: flex;
242
242
  align-items: center;
243
243
  justify-content: center;
244
- transition: box-shadow 120ms, background 120ms;
244
+ /* top/height only: lane reflow animates on drop; left/width stay instant so
245
+ mount-time width measurement and infinite-scroll rebases don't slide cards */
246
+ transition: box-shadow 120ms, background 120ms,
247
+ top 180ms cubic-bezier(0.2, 0.8, 0.2, 1), height 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
245
248
  }
246
249
  .fs-event.svelte-mrwdy7:hover {
247
250
  background: color-mix(in srgb, var(--ev-color) 25%, var(--dt-surface, var(--dt-bg, #ffffff)));
@@ -263,7 +266,15 @@
263
266
  z-index: 50;
264
267
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
265
268
  cursor: grabbing;
266
- transition: none;
269
+ /* fast ease toward the snapped cursor position */
270
+ transition: left 80ms ease-out, width 80ms ease-out;
271
+ }
272
+
273
+ @media (prefers-reduced-motion: reduce) {
274
+ .fs-event.svelte-mrwdy7,
275
+ .fs-event--dragging.svelte-mrwdy7 {
276
+ transition: box-shadow 120ms, background 120ms;
277
+ }
267
278
  }
268
279
  .fs-event--cancelled.svelte-mrwdy7 {
269
280
  opacity: 0.5;
package/widget/widget.js CHANGED
@@ -158,6 +158,9 @@
158
158
  const PROPS_IS_UPDATED = 1 << 2;
159
159
  const PROPS_IS_BINDABLE = 1 << 3;
160
160
  const PROPS_IS_LAZY_INITIAL = 1 << 4;
161
+ const TRANSITION_IN = 1;
162
+ const TRANSITION_OUT = 1 << 1;
163
+ const TRANSITION_GLOBAL = 1 << 2;
161
164
  const TEMPLATE_FRAGMENT = 1;
162
165
  const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
163
166
  const HYDRATION_START = "[";
@@ -2970,8 +2973,8 @@
2970
2973
  remove_reactions(effect2, 0);
2971
2974
  var transitions = effect2.nodes && effect2.nodes.t;
2972
2975
  if (transitions !== null) {
2973
- for (const transition of transitions) {
2974
- transition.stop();
2976
+ for (const transition2 of transitions) {
2977
+ transition2.stop();
2975
2978
  }
2976
2979
  }
2977
2980
  execute_effect_teardown(effect2);
@@ -3011,8 +3014,8 @@
3011
3014
  var remaining = transitions.length;
3012
3015
  if (remaining > 0) {
3013
3016
  var check = () => --remaining || fn();
3014
- for (var transition of transitions) {
3015
- transition.out(check);
3017
+ for (var transition2 of transitions) {
3018
+ transition2.out(check);
3016
3019
  }
3017
3020
  } else {
3018
3021
  fn();
@@ -3023,9 +3026,9 @@
3023
3026
  effect2.f ^= INERT;
3024
3027
  var t = effect2.nodes && effect2.nodes.t;
3025
3028
  if (t !== null) {
3026
- for (const transition of t) {
3027
- if (transition.is_global || local) {
3028
- transitions.push(transition);
3029
+ for (const transition2 of t) {
3030
+ if (transition2.is_global || local) {
3031
+ transitions.push(transition2);
3029
3032
  }
3030
3033
  }
3031
3034
  }
@@ -3061,9 +3064,9 @@
3061
3064
  }
3062
3065
  var t = effect2.nodes && effect2.nodes.t;
3063
3066
  if (t !== null) {
3064
- for (const transition of t) {
3065
- if (transition.is_global || local) {
3066
- transition.in();
3067
+ for (const transition2 of t) {
3068
+ if (transition2.is_global || local) {
3069
+ transition2.in();
3067
3070
  }
3068
3071
  }
3069
3072
  }
@@ -3101,6 +3104,12 @@
3101
3104
  }
3102
3105
  return target_handler;
3103
3106
  }
3107
+ function on(element, type, handler, options = {}) {
3108
+ var target_handler = create_event(type, element, handler, options);
3109
+ return () => {
3110
+ element.removeEventListener(type, target_handler, options);
3111
+ };
3112
+ }
3104
3113
  function event(event_name, dom, handler, capture2, passive) {
3105
3114
  var options = { capture: capture2, passive };
3106
3115
  var target_handler = create_event(event_name, dom, handler, options);
@@ -3330,6 +3339,7 @@
3330
3339
  function is_passive_event(name) {
3331
3340
  return PASSIVE_EVENTS.includes(name);
3332
3341
  }
3342
+ let should_intro = true;
3333
3343
  function set_text(text2, value) {
3334
3344
  var str = value == null ? "" : typeof value === "object" ? `${value}` : value;
3335
3345
  if (str !== /** @type {any} */
@@ -3416,7 +3426,9 @@
3416
3426
  null
3417
3427
  );
3418
3428
  }
3429
+ should_intro = intro;
3419
3430
  component2 = Component(anchor_node2, props) || {};
3431
+ should_intro = true;
3420
3432
  if (hydrating) {
3421
3433
  active_effect.nodes.end = hydrate_node;
3422
3434
  if (hydrate_node === null || hydrate_node.nodeType !== COMMENT_NODE || /** @type {Comment} */
@@ -3641,9 +3653,9 @@
3641
3653
  * @param {TemplateNode} anchor
3642
3654
  * @param {boolean} transition
3643
3655
  */
3644
- constructor(anchor, transition = true) {
3656
+ constructor(anchor, transition2 = true) {
3645
3657
  this.anchor = anchor;
3646
- this.#transition = transition;
3658
+ this.#transition = transition2;
3647
3659
  }
3648
3660
  /**
3649
3661
  * @param {Batch} batch
@@ -4300,6 +4312,359 @@
4300
4312
  branches.ensure(component2, component2 && ((target) => render_fn(target, component2)));
4301
4313
  }, EFFECT_TRANSPARENT);
4302
4314
  }
4315
+ const now = () => performance.now();
4316
+ const raf = {
4317
+ // don't access requestAnimationFrame eagerly outside method
4318
+ // this allows basic testing of user code without JSDOM
4319
+ // bunder will eval and remove ternary when the user's app is built
4320
+ tick: (
4321
+ /** @param {any} _ */
4322
+ (_) => requestAnimationFrame(_)
4323
+ ),
4324
+ now: () => now(),
4325
+ tasks: /* @__PURE__ */ new Set()
4326
+ };
4327
+ function run_tasks() {
4328
+ const now2 = raf.now();
4329
+ raf.tasks.forEach((task) => {
4330
+ if (!task.c(now2)) {
4331
+ raf.tasks.delete(task);
4332
+ task.f();
4333
+ }
4334
+ });
4335
+ if (raf.tasks.size !== 0) {
4336
+ raf.tick(run_tasks);
4337
+ }
4338
+ }
4339
+ function loop(callback) {
4340
+ let task;
4341
+ if (raf.tasks.size === 0) {
4342
+ raf.tick(run_tasks);
4343
+ }
4344
+ return {
4345
+ promise: new Promise((fulfill) => {
4346
+ raf.tasks.add(task = { c: callback, f: fulfill });
4347
+ }),
4348
+ abort() {
4349
+ raf.tasks.delete(task);
4350
+ }
4351
+ };
4352
+ }
4353
+ function dispatch_event(element, type) {
4354
+ without_reactive_context(() => {
4355
+ element.dispatchEvent(new CustomEvent(type));
4356
+ });
4357
+ }
4358
+ function css_property_to_camelcase(style) {
4359
+ if (style === "float") return "cssFloat";
4360
+ if (style === "offset") return "cssOffset";
4361
+ if (style.startsWith("--")) return style;
4362
+ const parts = style.split("-");
4363
+ if (parts.length === 1) return parts[0];
4364
+ return parts[0] + parts.slice(1).map(
4365
+ /** @param {any} word */
4366
+ (word) => word[0].toUpperCase() + word.slice(1)
4367
+ ).join("");
4368
+ }
4369
+ function css_to_keyframe(css) {
4370
+ const keyframe = {};
4371
+ const parts = css.split(";");
4372
+ for (const part of parts) {
4373
+ const [property, value] = part.split(":");
4374
+ if (!property || value === void 0) break;
4375
+ const formatted_property = css_property_to_camelcase(property.trim());
4376
+ keyframe[formatted_property] = value.trim();
4377
+ }
4378
+ return keyframe;
4379
+ }
4380
+ const linear = (t) => t;
4381
+ function animation(element, get_fn, get_params) {
4382
+ var effect2 = (
4383
+ /** @type {Effect} */
4384
+ active_effect
4385
+ );
4386
+ var nodes = (
4387
+ /** @type {EffectNodes} */
4388
+ effect2.nodes
4389
+ );
4390
+ var from;
4391
+ var to;
4392
+ var animation2;
4393
+ var original_styles = null;
4394
+ nodes.a ??= {
4395
+ element,
4396
+ measure() {
4397
+ from = this.element.getBoundingClientRect();
4398
+ },
4399
+ apply() {
4400
+ animation2?.abort();
4401
+ to = this.element.getBoundingClientRect();
4402
+ if (from.left !== to.left || from.right !== to.right || from.top !== to.top || from.bottom !== to.bottom) {
4403
+ const options = get_fn()(this.element, { from, to }, get_params?.());
4404
+ animation2 = animate(
4405
+ this.element,
4406
+ options,
4407
+ void 0,
4408
+ 1,
4409
+ () => {
4410
+ },
4411
+ () => {
4412
+ animation2?.abort();
4413
+ animation2 = void 0;
4414
+ }
4415
+ );
4416
+ }
4417
+ },
4418
+ fix() {
4419
+ if (element.getAnimations().length) return;
4420
+ var { position, width, height } = getComputedStyle(element);
4421
+ if (position !== "absolute" && position !== "fixed") {
4422
+ var style = (
4423
+ /** @type {HTMLElement | SVGElement} */
4424
+ element.style
4425
+ );
4426
+ original_styles = {
4427
+ position: style.position,
4428
+ width: style.width,
4429
+ height: style.height,
4430
+ transform: style.transform
4431
+ };
4432
+ style.position = "absolute";
4433
+ style.width = width;
4434
+ style.height = height;
4435
+ var to2 = element.getBoundingClientRect();
4436
+ if (from.left !== to2.left || from.top !== to2.top) {
4437
+ var transform = `translate(${from.left - to2.left}px, ${from.top - to2.top}px)`;
4438
+ style.transform = style.transform ? `${style.transform} ${transform}` : transform;
4439
+ }
4440
+ }
4441
+ },
4442
+ unfix() {
4443
+ if (original_styles) {
4444
+ var style = (
4445
+ /** @type {HTMLElement | SVGElement} */
4446
+ element.style
4447
+ );
4448
+ style.position = original_styles.position;
4449
+ style.width = original_styles.width;
4450
+ style.height = original_styles.height;
4451
+ style.transform = original_styles.transform;
4452
+ }
4453
+ }
4454
+ };
4455
+ nodes.a.element = element;
4456
+ }
4457
+ function transition(flags2, element, get_fn, get_params) {
4458
+ var is_intro = (flags2 & TRANSITION_IN) !== 0;
4459
+ var is_outro = (flags2 & TRANSITION_OUT) !== 0;
4460
+ var is_both = is_intro && is_outro;
4461
+ var is_global = (flags2 & TRANSITION_GLOBAL) !== 0;
4462
+ var direction = is_both ? "both" : is_intro ? "in" : "out";
4463
+ var current_options;
4464
+ var inert = element.inert;
4465
+ var overflow = element.style.overflow;
4466
+ var intro;
4467
+ var outro;
4468
+ function get_options() {
4469
+ return without_reactive_context(() => {
4470
+ return current_options ??= get_fn()(element, get_params?.() ?? /** @type {P} */
4471
+ {}, {
4472
+ direction
4473
+ });
4474
+ });
4475
+ }
4476
+ var transition2 = {
4477
+ is_global,
4478
+ in() {
4479
+ element.inert = inert;
4480
+ if (!is_intro) {
4481
+ outro?.abort();
4482
+ outro?.reset?.();
4483
+ return;
4484
+ }
4485
+ if (!is_outro) {
4486
+ intro?.abort();
4487
+ }
4488
+ intro = animate(
4489
+ element,
4490
+ get_options(),
4491
+ outro,
4492
+ 1,
4493
+ () => {
4494
+ dispatch_event(element, "introstart");
4495
+ },
4496
+ () => {
4497
+ dispatch_event(element, "introend");
4498
+ intro?.abort();
4499
+ intro = current_options = void 0;
4500
+ element.style.overflow = overflow;
4501
+ }
4502
+ );
4503
+ },
4504
+ out(fn) {
4505
+ if (!is_outro) {
4506
+ fn?.();
4507
+ current_options = void 0;
4508
+ return;
4509
+ }
4510
+ element.inert = true;
4511
+ outro = animate(
4512
+ element,
4513
+ get_options(),
4514
+ intro,
4515
+ 0,
4516
+ () => {
4517
+ dispatch_event(element, "outrostart");
4518
+ },
4519
+ () => {
4520
+ dispatch_event(element, "outroend");
4521
+ fn?.();
4522
+ }
4523
+ );
4524
+ },
4525
+ stop: () => {
4526
+ intro?.abort();
4527
+ outro?.abort();
4528
+ }
4529
+ };
4530
+ var e = (
4531
+ /** @type {Effect & { nodes: EffectNodes }} */
4532
+ active_effect
4533
+ );
4534
+ (e.nodes.t ??= []).push(transition2);
4535
+ if (is_intro && should_intro) {
4536
+ var run = is_global;
4537
+ if (!run) {
4538
+ var block2 = (
4539
+ /** @type {Effect | null} */
4540
+ e.parent
4541
+ );
4542
+ while (block2 && (block2.f & EFFECT_TRANSPARENT) !== 0) {
4543
+ while (block2 = block2.parent) {
4544
+ if ((block2.f & BLOCK_EFFECT) !== 0) break;
4545
+ }
4546
+ }
4547
+ run = !block2 || (block2.f & REACTION_RAN) !== 0;
4548
+ }
4549
+ if (run) {
4550
+ effect(() => {
4551
+ untrack(() => transition2.in());
4552
+ });
4553
+ }
4554
+ }
4555
+ }
4556
+ function animate(element, options, counterpart, t2, on_begin, on_finish) {
4557
+ var is_intro = t2 === 1;
4558
+ if (is_function(options)) {
4559
+ var a;
4560
+ var aborted = false;
4561
+ queue_micro_task(() => {
4562
+ if (aborted) return;
4563
+ var o = options({ direction: is_intro ? "in" : "out" });
4564
+ a = animate(element, o, counterpart, t2, on_begin, on_finish);
4565
+ });
4566
+ return {
4567
+ abort: () => {
4568
+ aborted = true;
4569
+ a?.abort();
4570
+ },
4571
+ deactivate: () => a.deactivate(),
4572
+ reset: () => a.reset(),
4573
+ t: () => a.t()
4574
+ };
4575
+ }
4576
+ counterpart?.deactivate();
4577
+ if (!options?.duration && !options?.delay) {
4578
+ on_begin();
4579
+ on_finish();
4580
+ return {
4581
+ abort: noop,
4582
+ deactivate: noop,
4583
+ reset: noop,
4584
+ t: () => t2
4585
+ };
4586
+ }
4587
+ const { delay = 0, css, tick: tick2, easing = linear } = options;
4588
+ var keyframes = [];
4589
+ if (is_intro && counterpart === void 0) {
4590
+ if (tick2) {
4591
+ tick2(0, 1);
4592
+ }
4593
+ if (css) {
4594
+ var styles = css_to_keyframe(css(0, 1));
4595
+ keyframes.push(styles, styles);
4596
+ }
4597
+ }
4598
+ var get_t = () => 1 - t2;
4599
+ var animation2 = element.animate(keyframes, { duration: delay, fill: "forwards" });
4600
+ animation2.onfinish = () => {
4601
+ animation2.cancel();
4602
+ on_begin();
4603
+ var t1 = counterpart?.t() ?? 1 - t2;
4604
+ counterpart?.abort();
4605
+ var delta = t2 - t1;
4606
+ var duration2 = (
4607
+ /** @type {number} */
4608
+ options.duration * Math.abs(delta)
4609
+ );
4610
+ var keyframes2 = [];
4611
+ if (duration2 > 0) {
4612
+ var needs_overflow_hidden = false;
4613
+ if (css) {
4614
+ var n = Math.ceil(duration2 / (1e3 / 60));
4615
+ for (var i = 0; i <= n; i += 1) {
4616
+ var t = t1 + delta * easing(i / n);
4617
+ var styles2 = css_to_keyframe(css(t, 1 - t));
4618
+ keyframes2.push(styles2);
4619
+ needs_overflow_hidden ||= styles2.overflow === "hidden";
4620
+ }
4621
+ }
4622
+ if (needs_overflow_hidden) {
4623
+ element.style.overflow = "hidden";
4624
+ }
4625
+ get_t = () => {
4626
+ var time = (
4627
+ /** @type {number} */
4628
+ /** @type {globalThis.Animation} */
4629
+ animation2.currentTime
4630
+ );
4631
+ return t1 + delta * easing(time / duration2);
4632
+ };
4633
+ if (tick2) {
4634
+ loop(() => {
4635
+ if (animation2.playState !== "running") return false;
4636
+ var t3 = get_t();
4637
+ tick2(t3, 1 - t3);
4638
+ return true;
4639
+ });
4640
+ }
4641
+ }
4642
+ animation2 = element.animate(keyframes2, { duration: duration2, fill: "forwards" });
4643
+ animation2.onfinish = () => {
4644
+ get_t = () => t2;
4645
+ tick2?.(t2, 1 - t2);
4646
+ on_finish();
4647
+ };
4648
+ };
4649
+ return {
4650
+ abort: () => {
4651
+ if (animation2) {
4652
+ animation2.cancel();
4653
+ animation2.effect = null;
4654
+ animation2.onfinish = noop;
4655
+ }
4656
+ },
4657
+ deactivate: () => {
4658
+ on_finish = noop;
4659
+ },
4660
+ reset: () => {
4661
+ if (t2 === 0) {
4662
+ tick2?.(1, 0);
4663
+ }
4664
+ },
4665
+ t: () => get_t()
4666
+ };
4667
+ }
4303
4668
  const whitespace = [..." \n\r\f \v\uFEFF"];
4304
4669
  function to_class(value, hash, directives) {
4305
4670
  var classname = value == null ? "" : "" + value;
@@ -4951,6 +5316,40 @@
4951
5316
  return super.size;
4952
5317
  }
4953
5318
  }
5319
+ class ReactiveValue {
5320
+ #fn;
5321
+ #subscribe;
5322
+ /**
5323
+ *
5324
+ * @param {() => T} fn
5325
+ * @param {(update: () => void) => void} onsubscribe
5326
+ */
5327
+ constructor(fn, onsubscribe) {
5328
+ this.#fn = fn;
5329
+ this.#subscribe = createSubscriber(onsubscribe);
5330
+ }
5331
+ get current() {
5332
+ this.#subscribe();
5333
+ return this.#fn();
5334
+ }
5335
+ }
5336
+ const parenthesis_regex = /\(.+\)/;
5337
+ const non_parenthesized_keywords = /* @__PURE__ */ new Set(["all", "print", "screen", "and", "or", "not", "only"]);
5338
+ class MediaQuery extends ReactiveValue {
5339
+ /**
5340
+ * @param {string} query A media query string
5341
+ * @param {boolean} [fallback] Fallback value for the server
5342
+ */
5343
+ constructor(query, fallback) {
5344
+ let final_query = parenthesis_regex.test(query) || // we need to use `some` here because technically this `window.matchMedia('random,screen')` still returns true
5345
+ query.split(/[\s,]+/).some((keyword) => non_parenthesized_keywords.has(keyword.trim())) ? query : `(${query})`;
5346
+ const q = window.matchMedia(final_query);
5347
+ super(
5348
+ () => q.matches,
5349
+ (update) => on(q, "change", update)
5350
+ );
5351
+ }
5352
+ }
4954
5353
  const constructFromSymbol = /* @__PURE__ */ Symbol.for("constructDateFrom");
4955
5354
  function constructFrom(date, value) {
4956
5355
  if (typeof date === "function") return date(value);
@@ -5908,6 +6307,71 @@
5908
6307
  }
5909
6308
  };
5910
6309
  }
6310
+ function cubic_out(t) {
6311
+ const f = t - 1;
6312
+ return f * f * f + 1;
6313
+ }
6314
+ function assign(tar, src) {
6315
+ for (const k in src) tar[k] = src[k];
6316
+ return (
6317
+ /** @type {T & S} */
6318
+ tar
6319
+ );
6320
+ }
6321
+ function crossfade({ fallback, ...defaults }) {
6322
+ const to_receive = /* @__PURE__ */ new Map();
6323
+ const to_send = /* @__PURE__ */ new Map();
6324
+ function crossfade2(from_node, node, params) {
6325
+ const {
6326
+ delay = 0,
6327
+ duration: duration2 = (
6328
+ /** @param {number} d */
6329
+ (d2) => Math.sqrt(d2) * 30
6330
+ ),
6331
+ easing = cubic_out
6332
+ } = assign(assign({}, defaults), params);
6333
+ const from = from_node.getBoundingClientRect();
6334
+ const to = node.getBoundingClientRect();
6335
+ const dx = from.left - to.left;
6336
+ const dy = from.top - to.top;
6337
+ const dw = from.width / to.width;
6338
+ const dh = from.height / to.height;
6339
+ const d = Math.sqrt(dx * dx + dy * dy);
6340
+ const style = getComputedStyle(node);
6341
+ const transform = style.transform === "none" ? "" : style.transform;
6342
+ const opacity = +style.opacity;
6343
+ return {
6344
+ delay,
6345
+ duration: typeof duration2 === "function" ? duration2(d) : duration2,
6346
+ easing,
6347
+ css: (t, u) => `
6348
+ opacity: ${t * opacity};
6349
+ transform-origin: top left;
6350
+ transform: ${transform} translate(${u * dx}px,${u * dy}px) scale(${t + (1 - t) * dw}, ${t + (1 - t) * dh});
6351
+ `
6352
+ };
6353
+ }
6354
+ function transition2(items, counterparts, intro) {
6355
+ return (node, params) => {
6356
+ items.set(params.key, node);
6357
+ return () => {
6358
+ if (counterparts.has(params.key)) {
6359
+ const other_node = counterparts.get(params.key);
6360
+ counterparts.delete(params.key);
6361
+ return crossfade2(
6362
+ /** @type {Element} */
6363
+ other_node,
6364
+ node,
6365
+ params
6366
+ );
6367
+ }
6368
+ items.delete(params.key);
6369
+ return fallback && fallback(node, params, intro);
6370
+ };
6371
+ };
6372
+ }
6373
+ return [transition2(to_send, to_receive, false), transition2(to_receive, to_send, true)];
6374
+ }
5911
6375
  function createClock() {
5912
6376
  let tick2 = /* @__PURE__ */ state(proxy(Date.now()));
5913
6377
  let today = /* @__PURE__ */ state(proxy(sod(Date.now())));
@@ -6211,7 +6675,7 @@
6211
6675
  contentGap: 6
6212
6676
  });
6213
6677
  const positionedEvents = /* @__PURE__ */ user_derived(() => {
6214
- const now = clock.tick;
6678
+ const now2 = clock.tick;
6215
6679
  const dragP = get(drag)?.active && get(drag).mode === "move" ? get(drag).payload : null;
6216
6680
  const staticEvents = [];
6217
6681
  let draggedEv = null;
@@ -6220,13 +6684,13 @@
6220
6684
  else staticEvents.push(ev);
6221
6685
  }
6222
6686
  const sorted = [...staticEvents].sort((a, b) => a.start.getTime() - b.start.getTime());
6223
- const todayStart = sod(now);
6687
+ const todayStart = sod(now2);
6224
6688
  const todayEnd = todayStart + DAY_MS;
6225
6689
  let nextEventId = null;
6226
6690
  for (const ev of sorted) {
6227
6691
  const s = ev.start.getTime();
6228
6692
  const e = ev.end.getTime();
6229
- if (s >= todayStart && s < todayEnd && s > now && !(s <= now && e > now)) {
6693
+ if (s >= todayStart && s < todayEnd && s > now2 && !(s <= now2 && e > now2)) {
6230
6694
  nextEventId = ev.id;
6231
6695
  break;
6232
6696
  }
@@ -6242,7 +6706,7 @@
6242
6706
  width: Math.max(xEnd - x, 28),
6243
6707
  row: 0,
6244
6708
  groupMaxRow: 1,
6245
- isCurrent: s <= now && e > now,
6709
+ isCurrent: s <= now2 && e > now2,
6246
6710
  isNext: ev.id === nextEventId,
6247
6711
  isDragged: false,
6248
6712
  startMs: s,
@@ -6315,7 +6779,7 @@
6315
6779
  groupMaxRow: 1,
6316
6780
  topPx: get(contentTop),
6317
6781
  heightPx: dragH,
6318
- isCurrent: draggedEv.start.getTime() <= now && draggedEv.end.getTime() > now,
6782
+ isCurrent: draggedEv.start.getTime() <= now2 && draggedEv.end.getTime() > now2,
6319
6783
  isNext: false,
6320
6784
  isDragged: true,
6321
6785
  fit: measure.fitContent({
@@ -6889,6 +7353,60 @@
6889
7353
  pop();
6890
7354
  }
6891
7355
  delegate(["pointerdown", "click", "keydown"]);
7356
+ function cubicOut(t) {
7357
+ const f = t - 1;
7358
+ return f * f * f + 1;
7359
+ }
7360
+ function flip(node, { from, to }, params = {}) {
7361
+ var { delay = 0, duration: duration2 = (d) => Math.sqrt(d) * 120, easing = cubicOut } = params;
7362
+ var style = getComputedStyle(node);
7363
+ var transform = style.transform === "none" ? "" : style.transform;
7364
+ var [ox, oy] = style.transformOrigin.split(" ").map(parseFloat);
7365
+ ox /= node.clientWidth;
7366
+ oy /= node.clientHeight;
7367
+ var zoom = get_zoom(node);
7368
+ var sx = node.clientWidth / to.width / zoom;
7369
+ var sy = node.clientHeight / to.height / zoom;
7370
+ var fx = from.left + from.width * ox;
7371
+ var fy = from.top + from.height * oy;
7372
+ var tx = to.left + to.width * ox;
7373
+ var ty = to.top + to.height * oy;
7374
+ var dx = (fx - tx) * sx;
7375
+ var dy = (fy - ty) * sy;
7376
+ var dsx = from.width / to.width;
7377
+ var dsy = from.height / to.height;
7378
+ return {
7379
+ delay,
7380
+ duration: typeof duration2 === "function" ? duration2(Math.sqrt(dx * dx + dy * dy)) : duration2,
7381
+ easing,
7382
+ css: (t, u) => {
7383
+ var x = u * dx;
7384
+ var y = u * dy;
7385
+ var sx2 = t + u * dsx;
7386
+ var sy2 = t + u * dsy;
7387
+ return `transform: ${transform} translate(${x}px, ${y}px) scale(${sx2}, ${sy2});`;
7388
+ }
7389
+ };
7390
+ }
7391
+ function get_zoom(element) {
7392
+ if ("currentCSSZoom" in element) {
7393
+ return (
7394
+ /** @type {number} */
7395
+ element.currentCSSZoom
7396
+ );
7397
+ }
7398
+ var current = element;
7399
+ var zoom = 1;
7400
+ while (current !== null) {
7401
+ zoom *= +getComputedStyle(current).zoom;
7402
+ current = /** @type {Element | null} */
7403
+ current.parentElement;
7404
+ }
7405
+ return zoom;
7406
+ }
7407
+ const prefersReducedMotion = /* @__PURE__ */ new MediaQuery(
7408
+ "(prefers-reduced-motion: reduce)"
7409
+ );
6892
7410
  const allDaySegmentContent = ($$anchor, seg = noop) => {
6893
7411
  var fragment = root_3$5();
6894
7412
  var node = first_child(fragment);
@@ -6980,6 +7498,8 @@
6980
7498
  let mondayStart = prop($$props, "mondayStart", 3, true), height = prop($$props, "height", 3, 520), events = prop($$props, "events", 19, () => []), style = prop($$props, "style", 3, ""), selectedEventId = prop($$props, "selectedEventId", 3, null), readOnly = prop($$props, "readOnly", 3, false);
6981
7499
  const ctx = useCalendarContext();
6982
7500
  const clock = createClock();
7501
+ const ANIM = /* @__PURE__ */ user_derived(() => prefersReducedMotion.current ? 0 : 180);
7502
+ const [previewSend, previewReceive] = crossfade({ duration: () => prefersReducedMotion.current ? 0 : 160 });
6983
7503
  const drag = /* @__PURE__ */ user_derived(() => ctx.drag);
6984
7504
  const commitDragCtx = /* @__PURE__ */ user_derived(() => ctx.commitDrag);
6985
7505
  const viewState = /* @__PURE__ */ user_derived(() => ctx.viewState);
@@ -7167,16 +7687,21 @@
7167
7687
  if (!get(dragPreviewEvent)) return day.events;
7168
7688
  return day.events.filter((ev) => ev.id !== get(dragPreviewEvent).id);
7169
7689
  }
7690
+ const previewKeySnapshot = /* @__PURE__ */ new Map();
7170
7691
  function dragPreviewTimedForDay(dayMs) {
7171
7692
  const ev = get(dragPreviewEvent);
7172
7693
  if (!ev || isAllDay(ev) || isMultiDay(ev)) return null;
7173
7694
  const dayEnd = dayMs + DAY_MS;
7174
- return ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs ? ev : null;
7695
+ const hit = ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs;
7696
+ if (hit) previewKeySnapshot.set("timed", ev.id);
7697
+ return hit ? ev : null;
7175
7698
  }
7176
7699
  function dragPreviewSegmentForDay(dayMs) {
7177
7700
  const ev = get(dragPreviewEvent);
7178
7701
  if (!ev || !isAllDay(ev) && !isMultiDay(ev)) return null;
7179
- return segmentForDay(ev, dayMs);
7702
+ const seg = segmentForDay(ev, dayMs);
7703
+ if (seg) previewKeySnapshot.set(dayMs, `${ev.id}:${seg.dayIndex}`);
7704
+ return seg;
7180
7705
  }
7181
7706
  function getCellWidth() {
7182
7707
  const cell = el?.querySelector(".wg-cell");
@@ -7360,7 +7885,7 @@
7360
7885
  var consequent_10 = ($$anchor4) => {
7361
7886
  var div_9 = root_13$3();
7362
7887
  var node_12 = child(div_9);
7363
- each(node_12, 17, () => get(visibleAllDaySegments), (seg) => seg.ev.id, ($$anchor5, seg) => {
7888
+ each(node_12, 25, () => get(visibleAllDaySegments), (seg) => seg.ev.id, ($$anchor5, seg) => {
7364
7889
  var div_10 = root_11$4();
7365
7890
  let classes_5;
7366
7891
  let styles_1;
@@ -7390,6 +7915,9 @@
7390
7915
  $$props.oneventclick?.(get(seg).ev);
7391
7916
  }
7392
7917
  });
7918
+ animation(div_10, () => flip, () => ({ duration: get(ANIM) }));
7919
+ transition(1, div_10, () => previewReceive, () => ({ key: `${get(seg).ev.id}:${get(seg).dayIndex}` }));
7920
+ transition(2, div_10, () => previewSend, () => ({ key: `${get(seg).ev.id}:${get(seg).dayIndex}` }));
7393
7921
  append($$anchor5, div_10);
7394
7922
  });
7395
7923
  var node_14 = sibling(node_12, 2);
@@ -7411,6 +7939,8 @@
7411
7939
  "--ev-color": get(previewSegment).ev.color ?? "var(--dt-accent)"
7412
7940
  });
7413
7941
  });
7942
+ transition(1, div_11, () => previewReceive, () => ({ key: previewKeySnapshot.get(get(day).ms) ?? "" }));
7943
+ transition(2, div_11, () => previewSend, () => ({ key: previewKeySnapshot.get(get(day).ms) ?? "" }));
7414
7944
  append($$anchor5, div_11);
7415
7945
  };
7416
7946
  if_block(node_14, ($$render) => {
@@ -7426,7 +7956,7 @@
7426
7956
  }
7427
7957
  var div_12 = sibling(node_11, 2);
7428
7958
  var node_16 = child(div_12);
7429
- each(node_16, 17, () => get(visibleTimedEvents).slice(0, MAX_EVENTS_SHOWN), (ev) => ev.id, ($$anchor4, ev) => {
7959
+ each(node_16, 25, () => get(visibleTimedEvents).slice(0, MAX_EVENTS_SHOWN), (ev) => ev.id, ($$anchor4, ev) => {
7430
7960
  var div_13 = root_11$4();
7431
7961
  let classes_7;
7432
7962
  let styles_3;
@@ -7462,6 +7992,9 @@
7462
7992
  $$props.oneventclick?.(get(ev));
7463
7993
  }
7464
7994
  });
7995
+ animation(div_13, () => flip, () => ({ duration: get(ANIM) }));
7996
+ transition(1, div_13, () => previewReceive, () => ({ key: get(ev).id }));
7997
+ transition(2, div_13, () => previewSend, () => ({ key: get(ev).id }));
7465
7998
  append($$anchor4, div_13);
7466
7999
  });
7467
8000
  var node_18 = sibling(node_16, 2);
@@ -7475,6 +8008,8 @@
7475
8008
  template_effect(() => styles_4 = set_style(div_14, "", styles_4, {
7476
8009
  "--ev-color": get(previewTimedEvent).color ?? "var(--dt-accent)"
7477
8010
  }));
8011
+ transition(1, div_14, () => previewReceive, () => ({ key: previewKeySnapshot.get("timed") ?? "" }));
8012
+ transition(2, div_14, () => previewSend, () => ({ key: previewKeySnapshot.get("timed") ?? "" }));
7478
8013
  append($$anchor4, div_14);
7479
8014
  };
7480
8015
  if_block(node_18, ($$render) => {
@@ -7576,9 +8111,9 @@
7576
8111
  function duration(ev) {
7577
8112
  return fmtDuration(ev.start, ev.end);
7578
8113
  }
7579
- function timeUntilMs(ms, now) {
8114
+ function timeUntilMs(ms, now2) {
7580
8115
  const L = getLabels();
7581
- const diff = ms - now;
8116
+ const diff = ms - now2;
7582
8117
  if (diff <= 0) return L.now;
7583
8118
  const tMins = Math.floor(diff / 6e4);
7584
8119
  if (tMins < 60) return `in ${tMins}m`;
@@ -7588,10 +8123,10 @@
7588
8123
  const days = Math.floor(hrs / 24);
7589
8124
  return `in ${days}d`;
7590
8125
  }
7591
- function progress(ev, now) {
8126
+ function progress(ev, now2) {
7592
8127
  const s = ev.start.getTime();
7593
8128
  const e = ev.end.getTime();
7594
- return Math.min(1, Math.max(0, (now - s) / (e - s)));
8129
+ return Math.min(1, Math.max(0, (now2 - s) / (e - s)));
7595
8130
  }
7596
8131
  function groupIntoSlots(evts) {
7597
8132
  const sorted = [...evts].sort((a, b) => a.start.getTime() - b.start.getTime());
@@ -7685,15 +8220,15 @@
7685
8220
  const allDayBanner = /* @__PURE__ */ user_derived(() => get(dayEvents).filter((ev) => isAllDay(ev) || isMultiDay(ev)));
7686
8221
  const timedDayEvents = /* @__PURE__ */ user_derived(() => get(dayEvents).filter((ev) => !isAllDay(ev) && !isMultiDay(ev)));
7687
8222
  const dayCat = /* @__PURE__ */ user_derived(() => {
7688
- const now = clock.tick;
8223
+ const now2 = clock.tick;
7689
8224
  const past = [];
7690
8225
  const current = [];
7691
8226
  const upcoming = [];
7692
8227
  for (const ev of get(timedDayEvents)) {
7693
8228
  const s = ev.start.getTime();
7694
8229
  const e = ev.end.getTime();
7695
- if (e <= now) past.push(ev);
7696
- else if (s <= now && e > now) current.push(ev);
8230
+ if (e <= now2) past.push(ev);
8231
+ else if (s <= now2 && e > now2) current.push(ev);
7697
8232
  else upcoming.push(ev);
7698
8233
  }
7699
8234
  return {
@@ -8485,7 +9020,7 @@
8485
9020
  const weekStartMs = /* @__PURE__ */ user_derived(() => $$props.focusDate ? get(viewState)?.dayCount === 7 ? startOfWeek(sod($$props.focusDate.getTime()), mondayStart()) : sod($$props.focusDate.getTime()) : get(viewState)?.dayCount === 7 ? startOfWeek(clock.today, mondayStart()) : clock.today);
8486
9021
  const customDays = /* @__PURE__ */ user_derived(() => get(viewState)?.dayCount ?? 7);
8487
9022
  const weekDays = /* @__PURE__ */ user_derived(() => {
8488
- const now = clock.tick;
9023
+ const now2 = clock.tick;
8489
9024
  const todayMs = clock.today;
8490
9025
  const tomorrowMs = todayMs + DAY_MS;
8491
9026
  const out = [];
@@ -8507,8 +9042,8 @@
8507
9042
  const currentEvents = [];
8508
9043
  const upcomingEvents = [];
8509
9044
  for (const ev of timedEvts) {
8510
- if (ev.end.getTime() <= now) pastEvents.push(ev);
8511
- else if (ev.start.getTime() <= now && ev.end.getTime() > now) currentEvents.push(ev);
9045
+ if (ev.end.getTime() <= now2) pastEvents.push(ev);
9046
+ else if (ev.start.getTime() <= now2 && ev.end.getTime() > now2) currentEvents.push(ev);
8512
9047
  else upcomingEvents.push(ev);
8513
9048
  }
8514
9049
  let tier;
@@ -9085,13 +9620,13 @@
9085
9620
  return segs;
9086
9621
  });
9087
9622
  const positionedEvents = /* @__PURE__ */ user_derived(() => {
9088
- const now = clock.tick;
9623
+ const now2 = clock.tick;
9089
9624
  const sorted = [...get(timedEvents)];
9090
9625
  let nextEventId = null;
9091
9626
  if (get(isToday)) {
9092
9627
  for (const ev of [...sorted].sort((a, b) => a.start.getTime() - b.start.getTime())) {
9093
9628
  const s = ev.start.getTime();
9094
- if (s > now) {
9629
+ if (s > now2) {
9095
9630
  nextEventId = ev.id;
9096
9631
  break;
9097
9632
  }
@@ -9106,7 +9641,7 @@
9106
9641
  ev,
9107
9642
  top: topH * HOUR_HEIGHT,
9108
9643
  height: Math.max(24, (botH - topH) * HOUR_HEIGHT),
9109
- isCurrent: ev.start.getTime() <= now && ev.end.getTime() > now,
9644
+ isCurrent: ev.start.getTime() <= now2 && ev.end.getTime() > now2,
9110
9645
  isNext: ev.id === nextEventId,
9111
9646
  startMs: sMs,
9112
9647
  endMs: eMs,
@@ -10104,9 +10639,9 @@
10104
10639
  if (target) viewState.setView(target.id);
10105
10640
  }
10106
10641
  const viewIncludesToday = /* @__PURE__ */ user_derived(() => {
10107
- const now = Date.now();
10642
+ const now2 = Date.now();
10108
10643
  const { start, end } = viewState.range;
10109
- return now >= start.getTime() && now < end.getTime();
10644
+ return now2 >= start.getTime() && now2 < end.getTime();
10110
10645
  });
10111
10646
  const headerCtx = /* @__PURE__ */ user_derived(() => ({
10112
10647
  dateLabel: get(dateLabel),