@nomideusz/svelte-calendar 1.3.0 → 1.4.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.
@@ -53,7 +53,8 @@ const dayCells = $derived.by(() => {
53
53
  const jsDay = d.getDay();
54
54
  const isoDay = jsDay === 0 ? 7 : jsDay;
55
55
  if (hideSet.has(isoDay)) continue;
56
- const isToday = ms === todayMs;
56
+ // A dateless weekly template (showDates=false) has no today to mark.
57
+ const isToday = showDates && ms === todayMs;
57
58
  const isPast = equalDays ? false : ms < todayMs;
58
59
  const isWeekend = jsDay === 0 || jsDay === 6;
59
60
  const isDisabled = disabledSet.has(ms);
@@ -99,7 +100,9 @@ function evTimeLabel(ev) {
99
100
  /** Visible time on a day row: the start, or "until 01:00" on the day an
100
101
  * overnight event runs into. */
101
102
  function evRowTime(ev, dayMs) {
102
- if (inAllDayStrip(ev)) return L.allDay;
103
+ // A multi-day band says "All day" once, on its first row this week;
104
+ // every row's aria-label still says it.
105
+ if (inAllDayStrip(ev)) return dayCells.find((c) => c.events.includes(ev))?.ms === dayMs ? L.allDay : "";
103
106
  if (ev.start.getTime() < dayMs) return `${L.until} ${fmtTime(ev.end)}`;
104
107
  return fmtTime(ev.start);
105
108
  }
@@ -224,7 +227,7 @@ function handleDayKeydown(e, dayMs) {
224
227
  class:mw-ev--tentative={ev.status === 'tentative'}
225
228
  class:mw-ev--full={ev.status === 'full'}
226
229
  class:mw-ev--limited={ev.status === 'limited'}
227
- style:--ev-color={ev.color ?? 'var(--dt-accent)'}
230
+ style:--ev-color={ev.color ?? (inAllDayStrip(ev) ? 'var(--dt-accent)' : 'var(--dt-event, var(--dt-accent))')}
228
231
  onclick={(e) => { e.stopPropagation(); oneventclick?.(ev); }}
229
232
  onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); oneventclick?.(ev); } }}
230
233
  onpointerenter={() => oneventhover?.(ev)}
@@ -268,7 +271,7 @@ function handleDayKeydown(e, dayMs) {
268
271
  user-select: none;
269
272
  font-variant-numeric: tabular-nums;
270
273
  overflow: hidden;
271
- background: var(--dt-bg, #fff);
274
+ background: var(--dt-bg, #efebe9);
272
275
  -webkit-tap-highlight-color: transparent;
273
276
  touch-action: pan-y pinch-zoom;
274
277
  }
@@ -282,7 +285,7 @@ function handleDayKeydown(e, dayMs) {
282
285
  overscroll-behavior: contain;
283
286
  -webkit-overflow-scrolling: touch;
284
287
  scrollbar-width: thin;
285
- scrollbar-color: var(--dt-scrollbar, rgba(0, 0, 0, 0.1)) transparent;
288
+ scrollbar-color: var(--dt-scrollbar, rgba(15, 15, 15, 0.1)) transparent;
286
289
  }
287
290
  .mw--auto .mw-list { overflow-y: visible; }
288
291
  .mw-list--animate {
@@ -308,34 +311,34 @@ function handleDayKeydown(e, dayMs) {
308
311
  text-align: left;
309
312
  width: 100%;
310
313
  -webkit-tap-highlight-color: transparent;
311
- border-bottom: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
314
+ border-bottom: 1px solid var(--dt-border, rgba(15, 15, 15, 0.08));
312
315
  min-height: 56px;
313
316
  }
314
317
  .mw-row:last-child {
315
318
  border-bottom: none;
316
319
  }
317
320
  .mw-row:has(.mw-row-target:active) {
318
- background: var(--dt-hover, rgba(0, 0, 0, 0.04));
321
+ background: var(--dt-hover, rgba(15, 15, 15, 0.04));
319
322
  }
320
323
  .mw-row--today {
321
- background: color-mix(in srgb, var(--dt-accent, #2563eb) 4%, transparent);
324
+ background: color-mix(in srgb, var(--dt-accent, #1d4fd7) 4%, transparent);
322
325
  }
323
326
  /* Token-based dim (not subtree opacity) so past rows stay legible/tappable */
324
327
  .mw-row--past {
325
- background: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 3%, transparent);
328
+ background: color-mix(in srgb, var(--dt-text, rgba(15, 15, 15, 0.87)) 3%, transparent);
326
329
  }
327
330
  .mw-row--past :global(.mw-ev-title),
328
331
  .mw-row--past :global(.mw-ev-time),
329
332
  .mw-row--past .mw-day-num {
330
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
333
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
331
334
  }
332
335
  .mw-row--disabled {
333
336
  background-image: repeating-linear-gradient(
334
337
  135deg,
335
338
  transparent,
336
339
  transparent 6px,
337
- color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 4%, transparent) 6px,
338
- color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 4%, transparent) 12px
340
+ color-mix(in srgb, var(--dt-text, rgba(15, 15, 15, 0.87)) 4%, transparent) 6px,
341
+ color-mix(in srgb, var(--dt-text, rgba(15, 15, 15, 0.87)) 4%, transparent) 12px
339
342
  );
340
343
  }
341
344
  .mw-row-target {
@@ -353,7 +356,7 @@ function handleDayKeydown(e, dayMs) {
353
356
  }
354
357
  .mw-row-target:focus-visible {
355
358
  outline: none;
356
- box-shadow: inset 0 0 0 2px var(--dt-accent, #2563eb);
359
+ box-shadow: inset 0 0 0 2px var(--dt-accent, #1d4fd7);
357
360
  }
358
361
 
359
362
  /* ─── Date column ────────────────────────────────── */
@@ -373,10 +376,10 @@ function handleDayKeydown(e, dayMs) {
373
376
  font: 500 12px/1 var(--dt-sans, system-ui, sans-serif);
374
377
  letter-spacing: 0.08em;
375
378
  text-transform: uppercase;
376
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
379
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
377
380
  }
378
381
  .mw-day-name--today {
379
- color: var(--dt-accent, #2563eb);
382
+ color: var(--dt-accent, #1d4fd7);
380
383
  }
381
384
 
382
385
  /* The date is the typography: large and regular, narrowed like the planner's */
@@ -385,10 +388,10 @@ function handleDayKeydown(e, dayMs) {
385
388
  font-stretch: 90%;
386
389
  font-variant-numeric: tabular-nums;
387
390
  letter-spacing: -0.02em;
388
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
391
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
389
392
  }
390
393
  .mw-day-num--today {
391
- color: var(--dt-accent, #2563eb);
394
+ color: var(--dt-accent, #1d4fd7);
392
395
  }
393
396
 
394
397
  /* ─── Events column ──────────────────────────────── */
@@ -407,7 +410,7 @@ function handleDayKeydown(e, dayMs) {
407
410
 
408
411
  .mw-empty {
409
412
  font: 400 13px/1 var(--dt-sans, system-ui, sans-serif);
410
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
413
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
411
414
  }
412
415
 
413
416
  /* ─── Event chip ─────────────────────────────────── */
@@ -423,20 +426,20 @@ function handleDayKeydown(e, dayMs) {
423
426
  transition: background 120ms;
424
427
  -webkit-tap-highlight-color: transparent;
425
428
  border: none;
426
- border-bottom: 1px solid var(--dt-border, rgba(0, 0, 0, 0.3));
429
+ border-bottom: 1px solid var(--dt-border, rgba(15, 15, 15, 0.3));
427
430
  text-align: left;
428
431
  padding: 0;
429
432
  pointer-events: auto;
430
433
  }
431
434
  .mw-ev:hover,
432
435
  .mw-ev:active {
433
- background: var(--dt-hover, rgba(0, 0, 0, 0.04));
436
+ background: var(--dt-hover, rgba(15, 15, 15, 0.04));
434
437
  }
435
438
  .mw-ev--selected {
436
439
  box-shadow: inset 0 0 0 1px var(--ev-color);
437
440
  }
438
441
  .mw-ev--current {
439
- background: var(--dt-accent-dim, rgba(37, 99, 235, 0.12));
442
+ background: var(--dt-accent-dim, rgba(29, 79, 215, 0.12));
440
443
  }
441
444
  /* All day: a solid band on the event colour, no tint. */
442
445
  .mw-ev--allday {
@@ -460,14 +463,14 @@ function handleDayKeydown(e, dayMs) {
460
463
  limited one is set in grey with a dashed bar — never a bare opacity. */
461
464
  .mw-ev--cancelled .mw-ev-title {
462
465
  text-decoration: line-through;
463
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
466
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
464
467
  }
465
468
  .mw-ev--cancelled .mw-ev-stripe {
466
469
  opacity: 0.45; /* decorative bar only */
467
470
  }
468
471
  .mw-ev--tentative .mw-ev-title,
469
472
  .mw-ev--full .mw-ev-title {
470
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
473
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
471
474
  }
472
475
  .mw-ev--tentative .mw-ev-stripe,
473
476
  .mw-ev--limited .mw-ev-stripe {
@@ -500,7 +503,7 @@ function handleDayKeydown(e, dayMs) {
500
503
 
501
504
  .mw-ev-title {
502
505
  font: 500 15px/1.25 var(--dt-sans, system-ui, sans-serif);
503
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
506
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
504
507
  white-space: nowrap;
505
508
  overflow: hidden;
506
509
  text-overflow: ellipsis;
@@ -511,14 +514,14 @@ function handleDayKeydown(e, dayMs) {
511
514
  .mw-ev-time {
512
515
  font: 400 14px/1 var(--dt-sans, system-ui, sans-serif);
513
516
  font-variant-numeric: tabular-nums;
514
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
517
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
515
518
  white-space: nowrap;
516
519
  flex-shrink: 0;
517
520
  }
518
521
 
519
522
  .mw-ev-more {
520
523
  font: 400 12.5px/1 var(--dt-sans, system-ui, sans-serif);
521
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
524
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
522
525
  padding: 2px 4px;
523
526
  min-height: 32px;
524
527
  border: none;
@@ -542,13 +545,13 @@ function handleDayKeydown(e, dayMs) {
542
545
  }
543
546
  .mw-ev-more:focus-visible {
544
547
  outline: none;
545
- box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
548
+ box-shadow: 0 0 0 2px var(--dt-accent, #1d4fd7);
546
549
  }
547
550
 
548
551
  /* ─── Chevron ────────────────────────────────────── */
549
552
  .mw-chevron {
550
553
  flex-shrink: 0;
551
- color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
554
+ color: var(--dt-text-3, rgba(15, 15, 15, 0.38));
552
555
  position: relative;
553
556
  z-index: 1;
554
557
  pointer-events: none;
@@ -557,6 +560,6 @@ function handleDayKeydown(e, dayMs) {
557
560
  /* ─── Focus ──────────────────────────────────────── */
558
561
  .mw-ev:focus-visible {
559
562
  outline: none;
560
- box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
563
+ box-shadow: 0 0 0 2px var(--dt-accent, #1d4fd7);
561
564
  }
562
565
  </style>
@@ -139,11 +139,18 @@ function cellKeydown(e, cell) {
139
139
  case " ":
140
140
  if (!cell.isDisabled) {
141
141
  e.preventDefault();
142
- ondayclick?.(cell.date);
142
+ activateCell(cell);
143
143
  }
144
144
  break;
145
145
  }
146
146
  }
147
+ /** The cell's own action: drill into the day; on phones (dots, no
148
+ * ondayclick) the whole cell opens its overflow instead of a tiny "+N". */
149
+ function activateCell(cell) {
150
+ if (cell.isDisabled) return;
151
+ if (ondayclick) ondayclick(cell.date);
152
+ else if (dotsMode && cell.overflow > 0) expandedMs = expandedMs === cell.ms ? null : cell.ms;
153
+ }
147
154
  function overflowClick(e, cell) {
148
155
  e.stopPropagation();
149
156
  if (ondayclick) ondayclick(cell.date);
@@ -236,18 +243,25 @@ function chipTime(e) {
236
243
  aria-current={cell.isToday ? 'date' : undefined}
237
244
  data-ms={cell.ms}
238
245
  tabindex={isFocusable(cell) ? (cell.ms === rovingMs ? 0 : -1) : undefined}
239
- onclick={() => {
240
- if (!cell.isDisabled) ondayclick?.(cell.date);
241
- }}
246
+ onclick={() => activateCell(cell)}
242
247
  onfocusin={() => (focusMs = cell.ms)}
243
248
  onkeydown={(e) => cellKeydown(e, cell)}
244
249
  >
245
250
  <span class="mg-daynum" class:mg-daynum--today={cell.isToday}>
246
251
  {cell.dayNum}
247
252
  </span>
248
- <div class="mg-chips">
253
+ <!-- Phones: dots are a picture of the day; the cell (its label counts
254
+ the events) is the one tap and tab target. -->
255
+ <div class="mg-chips" aria-hidden={dotsMode || undefined}>
249
256
  {#each expandedMs === cell.ms ? cell.all : cell.chips as ev (ev.id + cell.ms)}
250
- {#if eventSnippet}
257
+ {#if dotsMode}
258
+ <span
259
+ class="mg-chip"
260
+ class:mg-chip--selected={ev.id === selectedEventId}
261
+ class:mg-chip--cancelled={ev.status === 'cancelled'}
262
+ style:--mg-chip-color={ev.color ?? (isAllDay(ev) ? 'var(--dt-accent)' : 'var(--dt-event, var(--dt-accent))')}
263
+ ><span class="mg-chip-dot"></span></span>
264
+ {:else if eventSnippet}
251
265
  <button
252
266
  type="button"
253
267
  class="mg-chip mg-chip--custom"
@@ -266,7 +280,7 @@ function chipTime(e) {
266
280
  class="mg-chip"
267
281
  class:mg-chip--selected={ev.id === selectedEventId}
268
282
  class:mg-chip--cancelled={ev.status === 'cancelled'}
269
- style:--mg-chip-color={ev.color ?? 'var(--dt-accent)'}
283
+ style:--mg-chip-color={ev.color ?? (isAllDay(ev) ? 'var(--dt-accent)' : 'var(--dt-event, var(--dt-accent))')}
270
284
  title={ev.title}
271
285
  aria-label="{ev.title}{chipTime(ev) ? `, ${chipTime(ev)}` : ''}"
272
286
  onclick={(e) => {
@@ -281,7 +295,11 @@ function chipTime(e) {
281
295
  </button>
282
296
  {/if}
283
297
  {/each}
284
- {#if cell.overflow > 0 && expandedMs !== cell.ms}
298
+ {#if dotsMode}
299
+ {#if cell.overflow > 0 && expandedMs !== cell.ms}
300
+ <span class="mg-more">+{cell.overflow}</span>
301
+ {/if}
302
+ {:else if cell.overflow > 0 && expandedMs !== cell.ms}
285
303
  <button
286
304
  type="button"
287
305
  class="mg-more"
@@ -289,7 +307,7 @@ function chipTime(e) {
289
307
  aria-label={L.nMore(cell.overflow)}
290
308
  onclick={(e) => overflowClick(e, cell)}
291
309
  >
292
- {dotsMode ? `+${cell.overflow}` : L.nMore(cell.overflow)}
310
+ {L.nMore(cell.overflow)}
293
311
  </button>
294
312
  {:else if expandedMs === cell.ms}
295
313
  <button
@@ -338,7 +356,7 @@ function chipTime(e) {
338
356
  font-variant-numeric: tabular-nums;
339
357
  text-transform: uppercase;
340
358
  letter-spacing: 0.08em;
341
- color: var(--dt-text-2, rgba(0, 0, 0, 0.66));
359
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.66));
342
360
  }
343
361
 
344
362
  .mg-body {
@@ -372,7 +390,7 @@ function chipTime(e) {
372
390
  border-right: none;
373
391
  }
374
392
  .mg-cell--weekend {
375
- background: var(--dt-weekend-bg, rgba(0, 0, 0, 0.02));
393
+ background: var(--dt-weekend-bg, rgba(15, 15, 15, 0.02));
376
394
  }
377
395
  /* Dim only the day number for adjacent-month cells — their events stay
378
396
  legible. text-2, not text-3: a 22px numeral still needs 4.5:1. */
@@ -380,7 +398,7 @@ function chipTime(e) {
380
398
  background: var(--dt-surface, transparent);
381
399
  }
382
400
  .mg-cell--out .mg-daynum {
383
- color: var(--dt-text-2, rgba(0, 0, 0, 0.66));
401
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.66));
384
402
  }
385
403
  .mg-cell--today {
386
404
  background: var(--dt-today-bg);
@@ -396,10 +414,10 @@ function chipTime(e) {
396
414
  cursor: pointer;
397
415
  }
398
416
  .mg-cell--clickable:hover {
399
- background: var(--dt-hover, rgba(0, 0, 0, 0.04));
417
+ background: var(--dt-hover, rgba(15, 15, 15, 0.04));
400
418
  }
401
419
  .mg-cell:focus-visible {
402
- outline: 2px solid var(--dt-accent, #2563eb);
420
+ outline: 2px solid var(--dt-accent, #1d4fd7);
403
421
  outline-offset: -2px;
404
422
  }
405
423
 
@@ -446,10 +464,10 @@ function chipTime(e) {
446
464
  text-align: left;
447
465
  }
448
466
  .mg-chip:hover {
449
- background: var(--dt-hover, rgba(0, 0, 0, 0.04));
467
+ background: var(--dt-hover, rgba(15, 15, 15, 0.04));
450
468
  }
451
469
  .mg-chip:focus-visible {
452
- box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
470
+ box-shadow: 0 0 0 2px var(--dt-accent, #1d4fd7);
453
471
  outline: none;
454
472
  }
455
473
  .mg-chip--selected {
@@ -493,11 +511,11 @@ function chipTime(e) {
493
511
  cursor: pointer;
494
512
  }
495
513
  .mg-more:hover {
496
- background: var(--dt-hover, rgba(0, 0, 0, 0.04));
514
+ background: var(--dt-hover, rgba(15, 15, 15, 0.04));
497
515
  color: var(--dt-text);
498
516
  }
499
517
  .mg-more:focus-visible {
500
- box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
518
+ box-shadow: 0 0 0 2px var(--dt-accent, #1d4fd7);
501
519
  outline: none;
502
520
  }
503
521
 
@@ -518,13 +536,20 @@ function chipTime(e) {
518
536
  /* ── Dots mode (mobile) ─────────────────────────────
519
537
  Cells are too narrow for text chips, so events render as colored
520
538
  dots in a wrapping row. The cell itself stays the tap target
521
- (day drill-down); dots keep their title/aria-label for a11y. */
539
+ (day drill-down) and its label counts the events; dots are aria-hidden. */
522
540
  .mg--dots .mg-chips {
523
541
  flex-direction: row;
524
542
  flex-wrap: wrap;
525
543
  align-items: center;
526
544
  gap: 3px;
527
545
  }
546
+ /* The cell is the target: at least a 44px tap, dots never take the hit. */
547
+ .mg--dots .mg-cell {
548
+ min-height: 44px;
549
+ }
550
+ .mg--dots .mg-chips {
551
+ pointer-events: none;
552
+ }
528
553
  .mg--dots .mg-chip {
529
554
  padding: 3px;
530
555
  min-height: 0;