@nomideusz/svelte-calendar 1.2.0 → 1.3.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.
@@ -21,7 +21,8 @@ import { createClock } from "../../core/clock.svelte.js";
21
21
  import { fitParts } from "../../text-fit.js";
22
22
  import { HOUR_MS, sod, addDaysMs, diffDays } from "../../core/time.js";
23
23
  import { startOfWeek as sowFn, isAllDay, segmentForDay } from "../../core/time.js";
24
- import { fmtHParts, fmtTime, fmtDuration, weekdayShort, weekdayLong } from "../../core/locale.js";
24
+ import { fmtHParts, fmtTime, fmtDuration, fmtWeekRange, weekdayShort, weekdayLong } from "../../core/locale.js";
25
+ import { dateLong } from "../shared/format.js";
25
26
  let { mode = "week", mondayStart = true, locale, height = 520, events = [], style = "", focusDate, oneventclick, oneventcreate, onexternaldrop, selectedEventId = null, readOnly = false, visibleHours } = $props();
26
27
  // ── Context ────────────────────────────────────────
27
28
  const ctx = useCalendarContext();
@@ -899,7 +900,9 @@ function onWindowKeydown(e) {
899
900
  style:--tw-col-min="{MIN_COL_W}px"
900
901
  style:height={autoHeight ? undefined : (height ? `${height}px` : '100%')}
901
902
  role="region"
902
- aria-label={L.weekAhead}
903
+ aria-label="{singleDay ? L.dayPlanner : L.weekAhead}{dayCols.length
904
+ ? `, ${singleDay ? dateLong(dayCols[0].ms, locale) : fmtWeekRange(dayCols[0].ms, locale, dayCols[dayCols.length - 1].ms)}`
905
+ : ''}"
903
906
  >
904
907
  <div class="tw-scroll" bind:this={scrollEl}>
905
908
  <div class="tw-inner" style:min-width="{innerMinWidth}px">
@@ -1017,7 +1020,7 @@ function onWindowKeydown(e) {
1017
1020
  class:tw-col--disabled={day.isDisabled}
1018
1021
  data-day={day.ms}
1019
1022
  role={colsCreate ? 'group' : undefined}
1020
- aria-label={colsCreate ? `${weekdayLong(day.ms, locale)} ${day.dayNum}, ${L.createEvent}` : undefined}
1023
+ aria-label={colsCreate ? `${dateLong(day.ms, locale)}, ${L.createEvent}` : undefined}
1021
1024
  aria-keyshortcuts={colsCreate ? 'Enter' : undefined}
1022
1025
  tabindex={colsCreate ? (day.ms === tabbableColMs ? 0 : -1) : undefined}
1023
1026
  onfocusin={() => (focusedColMs = day.ms)}
@@ -1064,6 +1067,7 @@ function onWindowKeydown(e) {
1064
1067
  class:tw-ev--past={p.ev.end.getTime() < clock.tick}
1065
1068
  class:tw-ev--short={p.height < 44}
1066
1069
  class:tw-ev--compact={p.height < 34}
1070
+ class:tw-ev--on-now={weekHasToday && nowY !== null && p.top < nowY && p.top + p.height > nowY}
1067
1071
  style:top="{p.top}px"
1068
1072
  style:height="{p.height}px"
1069
1073
  style:left="calc({(p.col / p.totalCols) * 100}% + 1px)"
@@ -1138,9 +1142,8 @@ function onWindowKeydown(e) {
1138
1142
 
1139
1143
  <!-- Now line (today's column only) -->
1140
1144
  {#if day.isToday && nowY !== null}
1141
- <div class="tw-now" style:top="{nowY}px" aria-label={L.currentTime}>
1142
- <span class="tw-now-dot" aria-hidden="true"></span>
1143
- </div>
1145
+ <div class="tw-now" style:top="{nowY}px" aria-label={L.currentTime}></div>
1146
+ <span class="tw-now-dot" style:top="{nowY}px" aria-hidden="true"></span>
1144
1147
  {/if}
1145
1148
  </div>
1146
1149
  {/each}
@@ -1283,8 +1286,9 @@ function onWindowKeydown(e) {
1283
1286
  padding: 6px 8px 6px 0;
1284
1287
  }
1285
1288
  .tw-ad-gutter-lb {
1286
- font: 500 10px/1.2 var(--dt-sans, system-ui, sans-serif);
1287
- color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
1289
+ font: 500 12px/1.2 var(--dt-sans, system-ui, sans-serif);
1290
+ letter-spacing: 0.08em;
1291
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.66));
1288
1292
  text-align: right;
1289
1293
  }
1290
1294
 
@@ -1350,7 +1354,7 @@ function onWindowKeydown(e) {
1350
1354
  min-width: 0;
1351
1355
  }
1352
1356
  .tw-ad-span {
1353
- font: 400 11px/1 var(--dt-sans, system-ui, sans-serif);
1357
+ font: 400 12.5px/1 var(--dt-sans, system-ui, sans-serif);
1354
1358
  font-variant-numeric: tabular-nums;
1355
1359
  color: inherit;
1356
1360
  opacity: 0.75;
@@ -1383,7 +1387,7 @@ function onWindowKeydown(e) {
1383
1387
  border-radius: 3px;
1384
1388
  text-align: left;
1385
1389
  align-self: flex-start;
1386
- font: 500 10px/1 var(--dt-sans, system-ui, sans-serif);
1390
+ font: 400 12.5px/1 var(--dt-sans, system-ui, sans-serif);
1387
1391
  color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1388
1392
  padding: 2px 6px;
1389
1393
  cursor: pointer;
@@ -1519,17 +1523,20 @@ function onWindowKeydown(e) {
1519
1523
  /* The gutter's right border already bounds the first column */
1520
1524
  .tw-lines + .tw-col { border-left: none; }
1521
1525
 
1522
- .tw-col--today { background: var(--dt-today-bg, rgba(37, 99, 235, 0.04)); }
1526
+ /* --tw-col-wash: the column's ground, repeated under an event that the
1527
+ now line crosses (that event is opaque, so the line runs beneath it). */
1528
+ .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)); }
1523
1530
  /* Dim past days with a wash, never a subtree opacity (event contrast) */
1524
1531
  .tw-col--past {
1525
- background: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 4%, transparent);
1532
+ --tw-col-wash: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 4%, transparent);
1526
1533
  }
1527
1534
  /* What has already happened steps back; a cancelled block keeps its own look. */
1528
1535
  .tw-ev--past:not(.tw-ev--cancelled) {
1529
1536
  opacity: 0.78;
1530
1537
  }
1531
1538
  .tw-col--weekend:not(.tw-col--today):not(.tw-col--past) {
1532
- background: var(--dt-weekend-bg, rgba(0, 0, 0, 0.012));
1539
+ --tw-col-wash: var(--dt-weekend-bg, rgba(0, 0, 0, 0.012));
1533
1540
  }
1534
1541
  .tw-col--disabled {
1535
1542
  background: repeating-linear-gradient(
@@ -1561,7 +1568,7 @@ function onWindowKeydown(e) {
1561
1568
  overflow: hidden;
1562
1569
  }
1563
1570
  .tw-blocked-lb {
1564
- font: 500 10px/1 var(--dt-sans, system-ui, sans-serif);
1571
+ font: 500 11px/1 var(--dt-sans, system-ui, sans-serif);
1565
1572
  color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1566
1573
  text-transform: uppercase;
1567
1574
  letter-spacing: 0.04em;
@@ -1570,24 +1577,28 @@ function onWindowKeydown(e) {
1570
1577
  }
1571
1578
 
1572
1579
  /* ─── Now line ───────────────────────────────────── */
1580
+ /* Under the events (z 6): the one it crosses is opaque (.tw-ev--on-now),
1581
+ so the line never strikes through a title. The dot stays on top. */
1573
1582
  .tw-now {
1574
1583
  position: absolute;
1575
1584
  left: 0;
1576
1585
  right: 0;
1577
1586
  height: 0;
1578
1587
  border-top: 2px solid var(--dt-accent, #2563eb);
1579
- z-index: 12;
1588
+ z-index: 5;
1580
1589
  pointer-events: none;
1581
1590
  transform: translateY(-1px);
1582
1591
  }
1583
1592
  .tw-now-dot {
1584
1593
  position: absolute;
1585
1594
  left: -4px;
1586
- top: -5px;
1587
1595
  width: 8px;
1588
1596
  height: 8px;
1597
+ margin-top: -4px;
1589
1598
  border-radius: 50%;
1590
1599
  background: var(--dt-accent, #2563eb);
1600
+ z-index: 12;
1601
+ pointer-events: none;
1591
1602
  }
1592
1603
 
1593
1604
  /* ─── Events ─────────────────────────────────────── */
@@ -1630,6 +1641,17 @@ function onWindowKeydown(e) {
1630
1641
  .tw-ev--current {
1631
1642
  background: var(--dt-accent-dim, rgba(37, 99, 235, 0.12));
1632
1643
  }
1644
+ /* An event the now line crosses lays its column's ground (and its own
1645
+ wash) over the paper, so the line passes beneath its text. */
1646
+ .tw-ev.tw-ev--on-now {
1647
+ background:
1648
+ linear-gradient(var(--tw-ev-wash, transparent), var(--tw-ev-wash, transparent)),
1649
+ linear-gradient(var(--tw-col-wash, transparent), var(--tw-col-wash, transparent)),
1650
+ var(--dt-paper, none),
1651
+ var(--dt-bg, #ffffff);
1652
+ }
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)); }
1633
1655
  .tw-ev--resizing {
1634
1656
  z-index: 50;
1635
1657
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
@@ -1681,14 +1703,20 @@ function onWindowKeydown(e) {
1681
1703
  gap: 1px;
1682
1704
  overflow: hidden;
1683
1705
  }
1684
- /* Compact (< ~35min at default zoom): single inline line "9:00 Title" */
1706
+ /* Compact (< ~35min at default zoom): one line, "Title 9:00". The title
1707
+ wins the room: the time wraps onto a second line, which the block
1708
+ clips, before the title is cut; a title too long alone ellipsizes. */
1685
1709
  .tw-ev--compact .tw-ev-body {
1686
1710
  flex-direction: row;
1687
- align-items: center;
1688
- gap: 4px;
1689
- padding-top: 1px;
1690
- padding-bottom: 1px;
1691
- }
1711
+ flex-wrap: wrap;
1712
+ align-content: flex-start;
1713
+ align-items: baseline;
1714
+ gap: 24px 5px;
1715
+ padding-top: 2px;
1716
+ padding-bottom: 0;
1717
+ }
1718
+ .tw-ev--compact .tw-ev-title { flex: 0 1 auto; min-width: 0; }
1719
+ .tw-ev--compact .tw-ev-end { display: none; }
1692
1720
 
1693
1721
  /* Title first, time under it; both one size, rank by weight and ink. */
1694
1722
  .tw-ev-time {
@@ -1699,7 +1727,6 @@ function onWindowKeydown(e) {
1699
1727
  white-space: nowrap;
1700
1728
  flex-shrink: 0;
1701
1729
  }
1702
- .tw-ev--compact .tw-ev-time { order: 0; }
1703
1730
  /* A narrow overlap column keeps the start time whole and drops the end. */
1704
1731
  @container tw-ev (max-width: 104px) {
1705
1732
  .tw-ev-end { display: none; }
@@ -1820,13 +1847,13 @@ function onWindowKeydown(e) {
1820
1847
  box-shadow: none;
1821
1848
  }
1822
1849
  .tw-ghost-time {
1823
- font: 700 12px/1.15 var(--dt-sans, system-ui, sans-serif);
1850
+ font: 500 12.5px/1.15 var(--dt-sans, system-ui, sans-serif);
1824
1851
  font-variant-numeric: tabular-nums;
1825
1852
  color: var(--ev-color, var(--dt-accent, #2563eb));
1826
1853
  white-space: nowrap;
1827
1854
  }
1828
1855
  .tw-ghost-title {
1829
- font: 600 12px/1.2 var(--dt-sans, system-ui, sans-serif);
1856
+ font: 500 12.5px/1.2 var(--dt-sans, system-ui, sans-serif);
1830
1857
  color: var(--dt-text, rgba(0, 0, 0, 0.87));
1831
1858
  white-space: nowrap;
1832
1859
  overflow: hidden;
@@ -18,3 +18,6 @@ export interface TimeSlot {
18
18
  events: TimelineEvent[];
19
19
  }
20
20
  export declare function groupIntoSlots(evts: TimelineEvent[]): TimeSlot[];
21
+ /** A day in full, in the locale's own order: "Monday, October 19" (en-US),
22
+ * "poniedziałek, 19 października" (pl-PL). For screen-reader names. */
23
+ export declare function dateLong(ms: number, locale?: string): string;
@@ -1,4 +1,4 @@
1
- import { fmtTime as _fmtTime, fmtDuration, getLabels } from '../../core/locale.js';
1
+ import { fmtTime as _fmtTime, fmtDuration, getLabels, getDefaultLocale } from '../../core/locale.js';
2
2
  export function fmtTime(d, locale) {
3
3
  return _fmtTime(d, locale);
4
4
  }
@@ -47,3 +47,12 @@ export function groupIntoSlots(evts) {
47
47
  }
48
48
  return slots;
49
49
  }
50
+ /** A day in full, in the locale's own order: "Monday, October 19" (en-US),
51
+ * "poniedziałek, 19 października" (pl-PL). For screen-reader names. */
52
+ export function dateLong(ms, locale) {
53
+ return new Date(ms).toLocaleDateString(locale ?? getDefaultLocale(), {
54
+ weekday: 'long',
55
+ month: 'long',
56
+ day: 'numeric',
57
+ });
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nomideusz/svelte-calendar",
3
- "version": "1.2.0",
3
+ "version": "1.3.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": [