@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.
@@ -123,6 +123,7 @@ const dayCols = $derived.by(() => {
123
123
  cols.push({
124
124
  ms,
125
125
  isToday: ms === todayMs,
126
+ markToday: showDates && ms === todayMs,
126
127
  isPast: equalDays ? false : ms < todayMs,
127
128
  isWeekend: dow === 0 || dow === 6,
128
129
  isDisabled: disabledSet.has(ms),
@@ -302,8 +303,12 @@ const weekIsEmpty = $derived(!events.some((ev) => ev.start.getTime() < weekEndMs
302
303
  // Week with today → current time minus ~1h of context; otherwise the
303
304
  // visibleHours start or 08:00, whichever is later. Lands on a whole hour,
304
305
  // with room for its label (centred on the line) below the sticky top.
306
+ // An event running across that hour line would be sliced under the sticky
307
+ // top, so the scroll starts at that event's hour instead.
305
308
  $effect(() => {
306
309
  void weekStartMs;
310
+ void startHour;
311
+ void endHour;
307
312
  const el = scrollEl;
308
313
  if (!el) return;
309
314
  untrack(() => {
@@ -314,7 +319,26 @@ $effect(() => {
314
319
  } else {
315
320
  targetHour = Math.max(startHour, Math.min(8, endHour - 1));
316
321
  }
317
- el.scrollTop = Math.max(0, (Math.floor(targetHour) - startHour) * HOUR_H - LABEL_ROOM);
322
+ let hour = Math.floor(targetHour);
323
+ // Moving up can slice an earlier event on another day, so repeat
324
+ // until no block crosses the line.
325
+ // ponytail: looks back at most 2 h, so a long block can't drag the view to dawn.
326
+ const floorY = (hour - 2 - startHour) * HOUR_H;
327
+ for (let moved = true; moved;) {
328
+ moved = false;
329
+ const line = (hour - startHour) * HOUR_H;
330
+ for (const day of layoutByDay.values()) {
331
+ for (const p of day) {
332
+ if (p.top < line && p.top + p.height > line && p.top >= floorY) {
333
+ hour = Math.floor(startHour + p.top / HOUR_H);
334
+ moved = true;
335
+ break;
336
+ }
337
+ }
338
+ if (moved) break;
339
+ }
340
+ }
341
+ el.scrollTop = Math.max(0, (hour - startHour) * HOUR_H - LABEL_ROOM);
318
342
  });
319
343
  });
320
344
  // ── Pointer coalescing ─────────────────────────────
@@ -390,11 +414,12 @@ function clampToDayBand(ms, dayMs) {
390
414
  return Math.max(atHour(dayMs, startHour), Math.min(atHour(dayMs, endHour), ms));
391
415
  }
392
416
  // ── Blocked slot helpers ───────────────────────────
417
+ /** The blocked slot covering `hour` on the day, if any (truthy = blocked). */
393
418
  function isBlockedAt(dayMs, hour) {
394
- if (!blockedSlots?.length) return false;
419
+ if (!blockedSlots?.length) return undefined;
395
420
  const jsDay = new Date(dayMs).getDay();
396
421
  const isoDay = jsDay === 0 ? 7 : jsDay;
397
- return blockedSlots.some((slot) => {
422
+ return blockedSlots.find((slot) => {
398
423
  if (slot.day && slot.day !== isoDay) return false;
399
424
  return hour >= slot.start && hour < slot.end;
400
425
  });
@@ -641,11 +666,14 @@ function onColKeydown(e, i) {
641
666
  const day = dayCols[i];
642
667
  if (e.key === "Enter" || e.key === " ") {
643
668
  e.preventDefault();
644
- if (!oneventcreate || readOnly || day.isDisabled) return;
669
+ if (!oneventcreate || readOnly) return;
670
+ // Say why nothing was created, through the calendar's status line.
671
+ if (day.isDisabled) return ctx.announce(`${dateLong(day.ms, locale)}, ${L.unavailable}`);
645
672
  const step = Math.max(1, ctx.snapInterval) / 60;
646
673
  const want = day.isToday ? snapHour(nowFracHour, "floor") + step : 9;
647
674
  const hour = Math.min(Math.max(want, startHour), endHour - step);
648
- if (isBlockedAt(day.ms, hour)) return;
675
+ const slot = isBlockedAt(day.ms, hour);
676
+ if (slot) return ctx.announce(`${slot.label || L.unavailable}, ${blockedRangeLabel(day.ms, slot.start, slot.end)}`);
649
677
  const startMs = atHour(day.ms, hour);
650
678
  oneventcreate({
651
679
  start: new Date(startMs),
@@ -915,12 +943,12 @@ function onWindowKeydown(e) {
915
943
  {#each dayCols as day (day.ms)}
916
944
  <div
917
945
  class="tw-hd"
918
- class:tw-hd--today={day.isToday}
919
- aria-current={day.isToday ? 'date' : undefined}
946
+ class:tw-hd--today={day.markToday}
947
+ aria-current={day.markToday ? 'date' : undefined}
920
948
  >
921
949
  <span class="tw-hd-wd">{singleDay ? weekdayLong(day.ms, locale) : weekdayShort(day.ms, locale)}</span>
922
950
  {#if showDates}
923
- <span class="tw-hd-num" class:tw-hd-num--today={day.isToday}>{day.dayNum}</span>
951
+ <span class="tw-hd-num" class:tw-hd-num--today={day.markToday}>{day.dayNum}</span>
924
952
  {/if}
925
953
  {#if dayHeaderSnippet}
926
954
  <div class="tw-hd-custom">
@@ -941,7 +969,7 @@ function onWindowKeydown(e) {
941
969
  {@const segs = allDayByDay.get(day.ms) ?? []}
942
970
  {@const isExpanded = adExpanded[day.ms] ?? false}
943
971
  {@const shown = isExpanded ? segs : segs.slice(0, ALLDAY_MAX)}
944
- <div class="tw-ad-cell" class:tw-ad-cell--today={day.isToday}>
972
+ <div class="tw-ad-cell" class:tw-ad-cell--today={day.markToday}>
945
973
  {#each shown as seg (seg.ev.id)}
946
974
  {@render allDayChip(seg)}
947
975
  {/each}
@@ -1014,14 +1042,14 @@ function onWindowKeydown(e) {
1014
1042
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
1015
1043
  <div
1016
1044
  class="tw-col"
1017
- class:tw-col--today={day.isToday}
1045
+ class:tw-col--today={day.markToday}
1018
1046
  class:tw-col--past={day.isPast}
1019
1047
  class:tw-col--weekend={day.isWeekend}
1020
1048
  class:tw-col--disabled={day.isDisabled}
1021
1049
  data-day={day.ms}
1022
1050
  role={colsCreate ? 'group' : undefined}
1023
- aria-label={colsCreate ? `${dateLong(day.ms, locale)}, ${L.createEvent}` : undefined}
1024
- aria-keyshortcuts={colsCreate ? 'Enter' : undefined}
1051
+ aria-label={colsCreate ? `${dateLong(day.ms, locale)}, ${day.isDisabled ? L.unavailable : L.createEvent}` : undefined}
1052
+ aria-keyshortcuts={colsCreate && !day.isDisabled ? 'Enter' : undefined}
1025
1053
  tabindex={colsCreate ? (day.ms === tabbableColMs ? 0 : -1) : undefined}
1026
1054
  onfocusin={() => (focusedColMs = day.ms)}
1027
1055
  onkeydown={colsCreate ? (e) => onColKeydown(e, di) : undefined}
@@ -1125,8 +1153,8 @@ function onWindowKeydown(e) {
1125
1153
  style:top="{ghost.top}px"
1126
1154
  style:height="{ghost.height}px"
1127
1155
  style:--ev-color={ghost.create
1128
- ? 'var(--dt-accent, #2563eb)'
1129
- : (movingEvent?.color ?? 'var(--dt-accent, #2563eb)')}
1156
+ ? 'var(--dt-accent, #1d4fd7)'
1157
+ : (movingEvent?.color ?? 'var(--dt-event, var(--dt-accent))')}
1130
1158
  aria-hidden="true"
1131
1159
  >
1132
1160
  {#if ghost.showTime}
@@ -1169,7 +1197,7 @@ function onWindowKeydown(e) {
1169
1197
  overflow: hidden;
1170
1198
  user-select: none;
1171
1199
  font-variant-numeric: tabular-nums;
1172
- background: var(--dt-paper, none), var(--dt-bg, #ffffff);
1200
+ background: var(--dt-paper, none), var(--dt-bg, #efebe9);
1173
1201
  -webkit-tap-highlight-color: transparent;
1174
1202
  }
1175
1203
  .tw--auto { overflow: visible; }
@@ -1186,12 +1214,12 @@ function onWindowKeydown(e) {
1186
1214
  overscroll-behavior-x: contain;
1187
1215
  overscroll-behavior-y: auto;
1188
1216
  scrollbar-width: thin;
1189
- scrollbar-color: var(--dt-scrollbar, rgba(0, 0, 0, 0.1)) transparent;
1217
+ scrollbar-color: var(--dt-scrollbar, rgba(15, 15, 15, 0.1)) transparent;
1190
1218
  }
1191
1219
  .tw--auto .tw-scroll { overflow-y: visible; }
1192
1220
  .tw-scroll::-webkit-scrollbar { width: 5px; height: 5px; }
1193
1221
  .tw-scroll::-webkit-scrollbar-thumb {
1194
- background: var(--dt-scrollbar, rgba(0, 0, 0, 0.1));
1222
+ background: var(--dt-scrollbar, rgba(15, 15, 15, 0.1));
1195
1223
  border-radius: 4px;
1196
1224
  }
1197
1225
  .tw-scroll::-webkit-scrollbar-track { background: transparent; }
@@ -1207,8 +1235,8 @@ function onWindowKeydown(e) {
1207
1235
  position: sticky;
1208
1236
  top: 0;
1209
1237
  z-index: 30;
1210
- background: var(--dt-paper, none), var(--dt-bg, #ffffff);
1211
- border-bottom: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
1238
+ background: var(--dt-paper, none), var(--dt-bg, #efebe9);
1239
+ border-bottom: 1px solid var(--dt-border, rgba(15, 15, 15, 0.08));
1212
1240
  }
1213
1241
 
1214
1242
  /* ─── Day header row ─────────────────────────────── */
@@ -1221,7 +1249,7 @@ function onWindowKeydown(e) {
1221
1249
  position: sticky;
1222
1250
  left: 0;
1223
1251
  z-index: 2;
1224
- background: var(--dt-paper, none), var(--dt-bg, #ffffff);
1252
+ background: var(--dt-paper, none), var(--dt-bg, #efebe9);
1225
1253
  }
1226
1254
 
1227
1255
  .tw-hd {
@@ -1232,17 +1260,17 @@ function onWindowKeydown(e) {
1232
1260
  align-items: center;
1233
1261
  gap: 4px;
1234
1262
  padding: 14px 4px 12px;
1235
- border-left: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
1263
+ border-left: 1px solid var(--dt-border, rgba(15, 15, 15, 0.08));
1236
1264
  }
1237
1265
 
1238
1266
  .tw-hd-wd {
1239
1267
  font: 500 12px/1 var(--dt-sans, system-ui, sans-serif);
1240
1268
  letter-spacing: 0.08em;
1241
1269
  text-transform: uppercase;
1242
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1270
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
1243
1271
  }
1244
1272
  .tw-hd--today .tw-hd-wd {
1245
- color: var(--dt-accent, #2563eb);
1273
+ color: var(--dt-accent, #1d4fd7);
1246
1274
  }
1247
1275
 
1248
1276
  /* The day number is the typography of the grid: large, regular weight. */
@@ -1253,11 +1281,11 @@ function onWindowKeydown(e) {
1253
1281
  font: 400 40px/1 var(--dt-sans, system-ui, sans-serif);
1254
1282
  font-stretch: 90%;
1255
1283
  letter-spacing: -0.02em;
1256
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
1284
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
1257
1285
  }
1258
1286
  /* Today: the numeral in the accent, nothing else. */
1259
1287
  .tw-hd-num--today {
1260
- color: var(--dt-accent, #2563eb);
1288
+ color: var(--dt-accent, #1d4fd7);
1261
1289
  }
1262
1290
 
1263
1291
  .tw-hd-custom {
@@ -1268,7 +1296,7 @@ function onWindowKeydown(e) {
1268
1296
  /* ─── All-day strip ──────────────────────────────── */
1269
1297
  .tw-allday {
1270
1298
  display: flex;
1271
- border-top: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
1299
+ border-top: 1px solid var(--dt-border, rgba(15, 15, 15, 0.08));
1272
1300
  }
1273
1301
 
1274
1302
  .tw-ad-gutter {
@@ -1279,7 +1307,7 @@ function onWindowKeydown(e) {
1279
1307
  position: sticky;
1280
1308
  left: 0;
1281
1309
  z-index: 2;
1282
- background: var(--dt-paper, none), var(--dt-bg, #ffffff);
1310
+ background: var(--dt-paper, none), var(--dt-bg, #efebe9);
1283
1311
  display: flex;
1284
1312
  align-items: flex-start;
1285
1313
  justify-content: flex-end;
@@ -1288,7 +1316,7 @@ function onWindowKeydown(e) {
1288
1316
  .tw-ad-gutter-lb {
1289
1317
  font: 500 12px/1.2 var(--dt-sans, system-ui, sans-serif);
1290
1318
  letter-spacing: 0.08em;
1291
- color: var(--dt-text-2, rgba(0, 0, 0, 0.66));
1319
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.66));
1292
1320
  text-align: right;
1293
1321
  }
1294
1322
 
@@ -1299,9 +1327,9 @@ function onWindowKeydown(e) {
1299
1327
  flex-direction: column;
1300
1328
  gap: 2px;
1301
1329
  padding: 4px 0 5px;
1302
- border-left: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
1330
+ border-left: 1px solid var(--dt-border, rgba(15, 15, 15, 0.08));
1303
1331
  }
1304
- .tw-ad-cell--today { background: var(--dt-today-bg, rgba(37, 99, 235, 0.04)); }
1332
+ .tw-ad-cell--today { background: var(--dt-today-bg, rgba(29, 79, 215, 0.04)); }
1305
1333
 
1306
1334
  .tw-ad {
1307
1335
  appearance: none;
@@ -1337,11 +1365,11 @@ function onWindowKeydown(e) {
1337
1365
  text-decoration: line-through;
1338
1366
  }
1339
1367
  .tw-ad--cancelled {
1340
- background: color-mix(in srgb, var(--ev-color) 55%, var(--dt-bg, #fff));
1368
+ background: color-mix(in srgb, var(--ev-color) 55%, var(--dt-bg, #efebe9));
1341
1369
  }
1342
1370
  .tw-ad:focus-visible {
1343
1371
  outline: none;
1344
- box-shadow: inset 0 0 0 2px var(--dt-btn-text, #fff), 0 0 0 2px var(--dt-accent, #2563eb);
1372
+ box-shadow: inset 0 0 0 2px var(--dt-btn-text, #fff), 0 0 0 2px var(--dt-accent, #1d4fd7);
1345
1373
  }
1346
1374
 
1347
1375
  .tw-ad-title {
@@ -1384,19 +1412,19 @@ function onWindowKeydown(e) {
1384
1412
  appearance: none;
1385
1413
  background: none;
1386
1414
  border: none;
1387
- border-radius: 3px;
1415
+ border-radius: 0;
1388
1416
  text-align: left;
1389
1417
  align-self: flex-start;
1390
1418
  font: 400 12.5px/1 var(--dt-sans, system-ui, sans-serif);
1391
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1419
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
1392
1420
  padding: 2px 6px;
1393
1421
  cursor: pointer;
1394
1422
  -webkit-tap-highlight-color: transparent;
1395
1423
  }
1396
- .tw-ad-more:hover { color: var(--dt-text, rgba(0, 0, 0, 0.87)); }
1424
+ .tw-ad-more:hover { color: var(--dt-text, rgba(15, 15, 15, 0.87)); }
1397
1425
  .tw-ad-more:focus-visible {
1398
1426
  outline: none;
1399
- box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
1427
+ box-shadow: 0 0 0 2px var(--dt-accent, #1d4fd7);
1400
1428
  }
1401
1429
 
1402
1430
  /* ─── Grid body ──────────────────────────────────── */
@@ -1411,8 +1439,8 @@ function onWindowKeydown(e) {
1411
1439
  position: sticky;
1412
1440
  left: 0;
1413
1441
  z-index: 20;
1414
- background: var(--dt-paper, none), var(--dt-bg, #ffffff);
1415
- border-right: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
1442
+ background: var(--dt-paper, none), var(--dt-bg, #efebe9);
1443
+ border-right: 1px solid var(--dt-border, rgba(15, 15, 15, 0.08));
1416
1444
  }
1417
1445
 
1418
1446
  /* Timetable hour: the numeral carries the rank, the suffix sits raised
@@ -1429,7 +1457,7 @@ function onWindowKeydown(e) {
1429
1457
  font-stretch: 85%;
1430
1458
  font-variant-numeric: tabular-nums;
1431
1459
  letter-spacing: -0.02em;
1432
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
1460
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
1433
1461
  white-space: nowrap;
1434
1462
  }
1435
1463
  .tw-gutter-lb::after {
@@ -1439,7 +1467,7 @@ function onWindowKeydown(e) {
1439
1467
  height: 1px;
1440
1468
  margin-left: 5px;
1441
1469
  align-self: center;
1442
- background: var(--dt-border-day, rgba(0, 0, 0, 0.3));
1470
+ background: var(--dt-border-day, rgba(15, 15, 15, 0.3));
1443
1471
  }
1444
1472
  .tw-gutter-lb--under {
1445
1473
  visibility: hidden;
@@ -1449,7 +1477,7 @@ function onWindowKeydown(e) {
1449
1477
  font-weight: 500;
1450
1478
  line-height: 1;
1451
1479
  letter-spacing: 0;
1452
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1480
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
1453
1481
  }
1454
1482
 
1455
1483
  /* The clock beside the now line, and the drag's edges while it lasts —
@@ -1461,16 +1489,16 @@ function onWindowKeydown(e) {
1461
1489
  transform: translateY(-50%);
1462
1490
  padding: 2px 4px;
1463
1491
  border-radius: 0;
1464
- background: var(--dt-surface, var(--dt-bg, #fff));
1492
+ background: var(--dt-surface, var(--dt-bg, #efebe9));
1465
1493
  font: 500 11px/1.2 var(--dt-sans, system-ui, sans-serif);
1466
1494
  font-variant-numeric: tabular-nums;
1467
- color: var(--dt-accent, #2563eb);
1495
+ color: var(--dt-accent, #1d4fd7);
1468
1496
  white-space: nowrap;
1469
1497
  z-index: 2;
1470
1498
  }
1471
1499
  /* The clock is a reversed plate on the accent, like a board's live time. */
1472
1500
  .tw-gutter-now {
1473
- background: var(--dt-accent, #2563eb);
1501
+ background: var(--dt-accent, #1d4fd7);
1474
1502
  color: var(--dt-btn-text, #ffffff);
1475
1503
  }
1476
1504
 
@@ -1495,7 +1523,7 @@ function onWindowKeydown(e) {
1495
1523
  left: 0;
1496
1524
  right: 0;
1497
1525
  height: 1px;
1498
- background: var(--dt-border, rgba(0, 0, 0, 0.08));
1526
+ background: var(--dt-border, rgba(15, 15, 15, 0.08));
1499
1527
  }
1500
1528
  .tw-line--half { opacity: 0.4; }
1501
1529
  .tw-now-week {
@@ -1503,7 +1531,7 @@ function onWindowKeydown(e) {
1503
1531
  left: 0;
1504
1532
  right: 0;
1505
1533
  height: 0;
1506
- border-top: 2px solid var(--dt-accent, #2563eb);
1534
+ border-top: 2px solid var(--dt-accent, #1d4fd7);
1507
1535
  opacity: 0.85;
1508
1536
  transform: translateY(-1px);
1509
1537
  }
@@ -1513,11 +1541,11 @@ function onWindowKeydown(e) {
1513
1541
  flex: 1 1 0;
1514
1542
  min-width: var(--tw-col-min, 110px);
1515
1543
  position: relative;
1516
- border-left: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
1544
+ border-left: 1px solid var(--dt-border, rgba(15, 15, 15, 0.08));
1517
1545
  box-sizing: border-box;
1518
1546
  }
1519
1547
  .tw-col:focus-visible {
1520
- outline: 2px solid var(--dt-accent, #2563eb);
1548
+ outline: 2px solid var(--dt-accent, #1d4fd7);
1521
1549
  outline-offset: -2px;
1522
1550
  }
1523
1551
  /* The gutter's right border already bounds the first column */
@@ -1526,25 +1554,25 @@ function onWindowKeydown(e) {
1526
1554
  /* --tw-col-wash: the column's ground, repeated under an event that the
1527
1555
  now line crosses (that event is opaque, so the line runs beneath it). */
1528
1556
  .tw-col { background: var(--tw-col-wash, transparent); }
1529
- .tw-col--today { --tw-col-wash: var(--dt-today-bg, rgba(37, 99, 235, 0.04)); }
1557
+ .tw-col--today { --tw-col-wash: var(--dt-today-bg, rgba(29, 79, 215, 0.04)); }
1530
1558
  /* Dim past days with a wash, never a subtree opacity (event contrast) */
1531
1559
  .tw-col--past {
1532
- --tw-col-wash: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 4%, transparent);
1560
+ --tw-col-wash: color-mix(in srgb, var(--dt-text, rgba(15, 15, 15, 0.87)) 4%, transparent);
1533
1561
  }
1534
1562
  /* What has already happened steps back; a cancelled block keeps its own look. */
1535
1563
  .tw-ev--past:not(.tw-ev--cancelled) {
1536
1564
  opacity: 0.78;
1537
1565
  }
1538
1566
  .tw-col--weekend:not(.tw-col--today):not(.tw-col--past) {
1539
- --tw-col-wash: var(--dt-weekend-bg, rgba(0, 0, 0, 0.012));
1567
+ --tw-col-wash: var(--dt-weekend-bg, rgba(15, 15, 15, 0.012));
1540
1568
  }
1541
1569
  .tw-col--disabled {
1542
1570
  background: repeating-linear-gradient(
1543
1571
  45deg,
1544
1572
  transparent,
1545
1573
  transparent 6px,
1546
- var(--dt-border, rgba(0, 0, 0, 0.08)) 6px,
1547
- var(--dt-border, rgba(0, 0, 0, 0.08)) 7px
1574
+ var(--dt-border, rgba(15, 15, 15, 0.08)) 6px,
1575
+ var(--dt-border, rgba(15, 15, 15, 0.08)) 7px
1548
1576
  ) !important;
1549
1577
  }
1550
1578
 
@@ -1556,8 +1584,8 @@ function onWindowKeydown(e) {
1556
1584
  z-index: 2;
1557
1585
  background: repeating-linear-gradient(
1558
1586
  -45deg,
1559
- color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 4%, transparent),
1560
- color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 4%, transparent) 4px,
1587
+ color-mix(in srgb, var(--dt-text, rgba(15, 15, 15, 0.87)) 4%, transparent),
1588
+ color-mix(in srgb, var(--dt-text, rgba(15, 15, 15, 0.87)) 4%, transparent) 4px,
1561
1589
  transparent 4px,
1562
1590
  transparent 8px
1563
1591
  );
@@ -1569,7 +1597,7 @@ function onWindowKeydown(e) {
1569
1597
  }
1570
1598
  .tw-blocked-lb {
1571
1599
  font: 500 11px/1 var(--dt-sans, system-ui, sans-serif);
1572
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1600
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
1573
1601
  text-transform: uppercase;
1574
1602
  letter-spacing: 0.04em;
1575
1603
  white-space: nowrap;
@@ -1584,7 +1612,7 @@ function onWindowKeydown(e) {
1584
1612
  left: 0;
1585
1613
  right: 0;
1586
1614
  height: 0;
1587
- border-top: 2px solid var(--dt-accent, #2563eb);
1615
+ border-top: 2px solid var(--dt-accent, #1d4fd7);
1588
1616
  z-index: 5;
1589
1617
  pointer-events: none;
1590
1618
  transform: translateY(-1px);
@@ -1596,7 +1624,7 @@ function onWindowKeydown(e) {
1596
1624
  height: 8px;
1597
1625
  margin-top: -4px;
1598
1626
  border-radius: 50%;
1599
- background: var(--dt-accent, #2563eb);
1627
+ background: var(--dt-accent, #1d4fd7);
1600
1628
  z-index: 12;
1601
1629
  pointer-events: none;
1602
1630
  }
@@ -1619,7 +1647,7 @@ function onWindowKeydown(e) {
1619
1647
  -webkit-tap-highlight-color: transparent;
1620
1648
  }
1621
1649
  .tw-ev:hover {
1622
- background: var(--dt-hover, rgba(0, 0, 0, 0.04));
1650
+ background: var(--dt-hover, rgba(15, 15, 15, 0.04));
1623
1651
  z-index: 8;
1624
1652
  }
1625
1653
  /* Short blocks keep duration-proportional height, but get a 44px
@@ -1639,7 +1667,7 @@ function onWindowKeydown(e) {
1639
1667
  z-index: 9;
1640
1668
  }
1641
1669
  .tw-ev--current {
1642
- background: var(--dt-accent-dim, rgba(37, 99, 235, 0.12));
1670
+ background: var(--dt-accent-dim, rgba(29, 79, 215, 0.12));
1643
1671
  }
1644
1672
  /* An event the now line crosses lays its column's ground (and its own
1645
1673
  wash) over the paper, so the line passes beneath its text. */
@@ -1648,10 +1676,10 @@ function onWindowKeydown(e) {
1648
1676
  linear-gradient(var(--tw-ev-wash, transparent), var(--tw-ev-wash, transparent)),
1649
1677
  linear-gradient(var(--tw-col-wash, transparent), var(--tw-col-wash, transparent)),
1650
1678
  var(--dt-paper, none),
1651
- var(--dt-bg, #ffffff);
1679
+ var(--dt-bg, #efebe9);
1652
1680
  }
1653
- .tw-ev--on-now.tw-ev--current { --tw-ev-wash: var(--dt-accent-dim, rgba(37, 99, 235, 0.12)); }
1654
- .tw-ev--on-now:hover { --tw-ev-wash: var(--dt-hover, rgba(0, 0, 0, 0.04)); }
1681
+ .tw-ev--on-now.tw-ev--current { --tw-ev-wash: var(--dt-accent-dim, rgba(29, 79, 215, 0.12)); }
1682
+ .tw-ev--on-now:hover { --tw-ev-wash: var(--dt-hover, rgba(15, 15, 15, 0.04)); }
1655
1683
  .tw-ev--resizing {
1656
1684
  z-index: 50;
1657
1685
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
@@ -1669,12 +1697,12 @@ function onWindowKeydown(e) {
1669
1697
  grid; a tentative one is set in grey with a dashed bar. */
1670
1698
  .tw-ev--cancelled .tw-ev-title {
1671
1699
  text-decoration: line-through;
1672
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1700
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
1673
1701
  }
1674
1702
  .tw-ev--cancelled .tw-ev-stripe { opacity: 0.45; /* decorative bar only */ }
1675
1703
  .tw-ev--tentative .tw-ev-title,
1676
1704
  .tw-ev--full .tw-ev-title {
1677
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1705
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
1678
1706
  }
1679
1707
  .tw-ev--tentative .tw-ev-stripe,
1680
1708
  .tw-ev--limited .tw-ev-stripe {
@@ -1723,7 +1751,7 @@ function onWindowKeydown(e) {
1723
1751
  order: 1;
1724
1752
  font: 400 14px/1.2 var(--dt-sans, system-ui, sans-serif);
1725
1753
  font-variant-numeric: tabular-nums;
1726
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
1754
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
1727
1755
  white-space: nowrap;
1728
1756
  flex-shrink: 0;
1729
1757
  }
@@ -1735,7 +1763,7 @@ function onWindowKeydown(e) {
1735
1763
  .tw-ev-title {
1736
1764
  order: 0;
1737
1765
  font: 500 15px/1.25 var(--dt-sans, system-ui, sans-serif);
1738
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
1766
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
1739
1767
  white-space: nowrap;
1740
1768
  overflow: hidden;
1741
1769
  text-overflow: ellipsis;
@@ -1744,7 +1772,7 @@ function onWindowKeydown(e) {
1744
1772
  .tw-ev-loc {
1745
1773
  order: 2;
1746
1774
  font: 400 12.5px/1.2 var(--dt-sans, system-ui, sans-serif);
1747
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
1775
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
1748
1776
  white-space: nowrap;
1749
1777
  overflow: hidden;
1750
1778
  text-overflow: ellipsis;
@@ -1829,7 +1857,7 @@ function onWindowKeydown(e) {
1829
1857
  right: 3px;
1830
1858
  z-index: 40;
1831
1859
  border-radius: 0;
1832
- background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, var(--dt-bg, #ffffff)));
1860
+ background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, var(--dt-bg, #efebe9)));
1833
1861
  outline: 1px solid color-mix(in srgb, var(--ev-color) 45%, transparent);
1834
1862
  box-shadow: 0 6px 18px color-mix(in srgb, var(--ev-color) 24%, rgba(0, 0, 0, 0.22));
1835
1863
  display: flex;
@@ -1849,12 +1877,12 @@ function onWindowKeydown(e) {
1849
1877
  .tw-ghost-time {
1850
1878
  font: 500 12.5px/1.15 var(--dt-sans, system-ui, sans-serif);
1851
1879
  font-variant-numeric: tabular-nums;
1852
- color: var(--ev-color, var(--dt-accent, #2563eb));
1880
+ color: var(--ev-color, var(--dt-accent, #1d4fd7));
1853
1881
  white-space: nowrap;
1854
1882
  }
1855
1883
  .tw-ghost-title {
1856
1884
  font: 500 12.5px/1.2 var(--dt-sans, system-ui, sans-serif);
1857
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
1885
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
1858
1886
  white-space: nowrap;
1859
1887
  overflow: hidden;
1860
1888
  text-overflow: ellipsis;
@@ -1876,7 +1904,7 @@ function onWindowKeydown(e) {
1876
1904
  overflow: hidden scroll container. */
1877
1905
  .tw-ev:focus-visible {
1878
1906
  outline: none;
1879
- box-shadow: 0 0 0 2px var(--dt-accent, #2563eb);
1907
+ box-shadow: 0 0 0 2px var(--dt-accent, #1d4fd7);
1880
1908
  z-index: 9;
1881
1909
  }
1882
1910
 
@@ -48,6 +48,8 @@ export interface CalendarContext {
48
48
  readonly emptySnippet: Snippet | undefined;
49
49
  /** Per-instance labels (Calendar's `labels` prop merged over globals); global labels when headless. */
50
50
  readonly labels: CalendarLabels;
51
+ /** Say something through the calendar's polite status line (no-op when headless). */
52
+ announce(text: string): void;
51
53
  }
52
54
  /**
53
55
  * Read the calendar context.
@@ -51,5 +51,6 @@ export function useCalendarContext() {
51
51
  get eventSnippet() { return raw?.eventSnippet; },
52
52
  get emptySnippet() { return raw?.emptySnippet; },
53
53
  get labels() { return raw?.labels ?? getLabels(); },
54
+ announce: (text) => raw?.announce(text),
54
55
  };
55
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomideusz/svelte-calendar",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "A themeable Svelte 5 calendar: day, week, scrolling-week and month views, agenda lists, touch-first mobile layouts, drag-and-drop, timezones, a headless API and an embeddable web component.",
5
5
  "type": "module",
6
6
  "sideEffects": [