@nomideusz/svelte-calendar 1.2.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.
@@ -376,6 +376,7 @@ setContext("calendar", {
376
376
  get labels() {
377
377
  return mergedLabels;
378
378
  },
379
+ announce,
379
380
  // Load range (read/write)
380
381
  get loadRange() {
381
382
  return viewLoadRange;
@@ -479,6 +480,45 @@ const dateLabel = $derived.by(() => {
479
480
  year: "numeric"
480
481
  });
481
482
  });
483
+ // ── Polite announcements (views say why an action did nothing) ──
484
+ // Shares the header's status line; a note lasts until the range changes.
485
+ let note = $state(null);
486
+ function announce(text) {
487
+ // A repeated message must change the text node to be read again.
488
+ note = {
489
+ text: note?.text === text ? `${text}\u00a0` : text,
490
+ label: dateLabel
491
+ };
492
+ }
493
+ const noteText = $derived(note && note.label === dateLabel ? note.text : "");
494
+ // ── Period title (the built-in header's big line) ──
495
+ // Names the month the user is looking at: "October 2026"; a week across two
496
+ // months reads "Oct – Nov 2026"; a day gives its date. `dateLabel` keeps the
497
+ // exact range for the header snippet and the screen-reader status line.
498
+ const periodTitle = $derived.by(() => {
499
+ if (!showDates) return "";
500
+ const loc = locale ?? getDefaultLocale();
501
+ if (viewState.mode === "day") {
502
+ return viewState.focusDate.toLocaleDateString(loc, {
503
+ weekday: useMobile ? "short" : "long",
504
+ month: "long",
505
+ day: "numeric",
506
+ year: "numeric"
507
+ });
508
+ }
509
+ const s = viewState.mode === "week" ? viewState.range.start : viewState.focusDate;
510
+ const e = viewState.mode === "week" ? new Date(viewState.range.end.getTime() - 1) : s;
511
+ if (s.getMonth() === e.getMonth() && s.getFullYear() === e.getFullYear()) {
512
+ return s.toLocaleDateString(loc, {
513
+ month: "long",
514
+ year: "numeric"
515
+ });
516
+ }
517
+ return new Intl.DateTimeFormat(loc, {
518
+ month: "short",
519
+ year: "numeric"
520
+ }).formatRange(s, e).replace(/[  ]/g, " ");
521
+ });
482
522
  // Which modes are available?
483
523
  const modes = $derived.by(() => {
484
524
  const g = new Set(desktopViews.map((v) => v.mode));
@@ -623,70 +663,50 @@ const navCtx = $derived({
623
663
  {#if headerSnippet}
624
664
  {@render headerSnippet(headerCtx)}
625
665
 
626
- <!-- ─── Mobile header (flow layout, no absolute) ─── -->
627
- {:else if useMobile && (showNavigation || (showModePills && modes.length > 1) || dateLabel)}
628
- {@const titleBelow = stackHeader && !!dateLabel}
629
- <div class="cal-m-hd" class:cal-m-hd--stack={stackHeader} class:cal-m-hd--titled={titleBelow}>
630
- <div class="cal-m-left">
631
- {#if showModePills && modes.length > 1}
632
- <div class="cal-m-pills" role="radiogroup" aria-label={L.viewMode}>
633
- {#each modes as g (g)}
634
- <button
635
- type="button"
636
- class="cal-m-pill"
637
- class:cal-m-pill--active={viewState.mode === g}
638
- role="radio"
639
- aria-checked={viewState.mode === g}
640
- onclick={() => switchMode(g)}
641
- >
642
- {g === 'day' ? L.day : g === 'week' ? L.week : L.month}
643
- </button>
644
- {/each}
645
- </div>
646
- {/if}
647
- </div>
648
-
649
- {#if !titleBelow}
650
- <span class="cal-m-title" role="status" aria-live="polite" aria-atomic="true">{dateLabel}</span>
666
+ <!-- ─── Built-in header: the period as the title, then view switch, then Today ‹ › ─── -->
667
+ {:else if showNavigation || (showModePills && modes.length > 1) || dateLabel}
668
+ <div class="cal-hd" class:cal-hd--mobile={useMobile} class:cal-hd--stack={stackHeader}>
669
+ <!-- The visible title names the month; the status line announces the exact range -->
670
+ <span class="cal-hd-title" aria-hidden="true">{periodTitle}</span>
671
+ <span class="cal-sr" role="status" aria-live="polite" aria-atomic="true">{noteText || dateLabel}</span>
672
+ {#if showModePills && !useMobile && labelsForMode.length > 1}
673
+ <!-- View-type switcher (e.g. Planner ↔ Agenda) for the current mode -->
674
+ <div class="cal-pills cal-pills--labels" role="radiogroup" aria-label={L.viewMode}>
675
+ {#each labelsForMode as label (label)}
676
+ <button
677
+ type="button"
678
+ class="cal-pill"
679
+ class:cal-pill--active={activeView?.label === label}
680
+ role="radio"
681
+ aria-checked={activeView?.label === label}
682
+ onclick={() => switchLabel(label)}
683
+ >
684
+ {viewLabel(label)}
685
+ </button>
686
+ {/each}
687
+ </div>
651
688
  {/if}
652
-
653
- <div class="cal-m-right">
689
+ {#if showModePills && modes.length > 1}
690
+ <div class="cal-pills" role="radiogroup" aria-label={L.viewMode}>
691
+ {#each modes as g (g)}
692
+ <button
693
+ type="button"
694
+ class="cal-pill"
695
+ class:cal-pill--active={viewState.mode === g}
696
+ role="radio"
697
+ aria-checked={viewState.mode === g}
698
+ onclick={() => switchMode(g)}
699
+ >
700
+ {g === 'day' ? L.day : g === 'week' ? L.week : L.month}
701
+ </button>
702
+ {/each}
703
+ </div>
704
+ {/if}
705
+ <div class="cal-hd-nav">
654
706
  {#if navigationSnippet}
655
707
  {@render navigationSnippet(navCtx)}
656
708
  {:else if showNavigation}
657
709
  <!-- Always rendered (disabled on today) so navigating never shifts layout -->
658
- <button
659
- type="button"
660
- class="cal-m-today"
661
- onclick={() => viewState.goToday()}
662
- disabled={viewIncludesToday}
663
- title={L.goToToday}
664
- >
665
- {L.today}
666
- </button>
667
- <button type="button" class="cal-m-nav" onclick={() => viewState.prev()} aria-label={viewState.mode === 'day' ? L.previousDay : viewState.mode === 'month' ? L.previousMonth : L.previousWeek}>
668
- <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" width="16" height="16" aria-hidden="true"><path d="M10 3 5 8l5 5"/></svg>
669
- </button>
670
- <button type="button" class="cal-m-nav" onclick={() => viewState.next()} aria-label={viewState.mode === 'day' ? L.nextDay : viewState.mode === 'month' ? L.nextMonth : L.nextWeek}>
671
- <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" width="16" height="16" aria-hidden="true"><path d="M6 3l5 5-5 5"/></svg>
672
- </button>
673
- {/if}
674
- </div>
675
- </div>
676
- {#if titleBelow}
677
- <div class="cal-m-titlebar">
678
- <span class="cal-m-title" role="status" aria-live="polite" aria-atomic="true">{dateLabel}</span>
679
- </div>
680
- {/if}
681
-
682
- <!-- ─── Desktop header ─── -->
683
- {:else if showNavigation || (showModePills && modes.length > 1) || dateLabel}
684
- <div class="cal-hd">
685
- <div class="cal-hd-side">
686
- {#if navigationSnippet}
687
- {@render navigationSnippet(navCtx)}
688
- {:else if showNavigation}
689
- <!-- Always rendered (disabled on today) so navigating never shifts the centered title -->
690
710
  <button
691
711
  type="button"
692
712
  class="cal-hd-today"
@@ -704,45 +724,13 @@ const navCtx = $derived({
704
724
  </button>
705
725
  {/if}
706
726
  </div>
707
- <span class="cal-hd-title" role="status" aria-live="polite" aria-atomic="true">{dateLabel}</span>
708
- <div class="cal-hd-side cal-hd-side--end">
709
- {#if showModePills && labelsForMode.length > 1}
710
- <!-- View-type switcher (e.g. Planner ↔ Agenda) for the current mode -->
711
- <div class="cal-pills cal-pills--labels" role="radiogroup" aria-label={L.viewMode}>
712
- {#each labelsForMode as label (label)}
713
- <button
714
- type="button"
715
- class="cal-pill"
716
- class:cal-pill--active={activeView?.label === label}
717
- role="radio"
718
- aria-checked={activeView?.label === label}
719
- onclick={() => switchLabel(label)}
720
- >
721
- {viewLabel(label)}
722
- </button>
723
- {/each}
724
- </div>
725
- {/if}
726
- {#if showModePills && modes.length > 1}
727
- <div class="cal-pills" role="radiogroup" aria-label={L.viewMode}>
728
- {#each modes as g (g)}
729
- <button
730
- type="button"
731
- class="cal-pill"
732
- class:cal-pill--active={viewState.mode === g}
733
- role="radio"
734
- aria-checked={viewState.mode === g}
735
- onclick={() => switchMode(g)}
736
- >
737
- {g === 'day' ? L.day : g === 'week' ? L.week : L.month}
738
- </button>
739
- {/each}
740
- </div>
741
- {/if}
742
- </div>
743
727
  </div>
744
728
  {/if}
745
729
 
730
+ {#if headerSnippet || !(showNavigation || (showModePills && modes.length > 1) || dateLabel)}
731
+ <span class="cal-sr" role="status" aria-live="polite" aria-atomic="true">{noteText}</span>
732
+ {/if}
733
+
746
734
  <div class="cal-body">
747
735
  {#if activeView}
748
736
  {@const Comp = activeView.component}
@@ -783,7 +771,7 @@ const navCtx = $derived({
783
771
  overflow: clip;
784
772
  display: flex;
785
773
  flex-direction: column;
786
- border: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
774
+ border: 1px solid var(--dt-border, rgba(15, 15, 15, 0.08));
787
775
  box-sizing: border-box;
788
776
  }
789
777
  .cal--auto {
@@ -792,118 +780,186 @@ const navCtx = $derived({
792
780
  }
793
781
 
794
782
 
795
- /* ── Desktop header ── */
783
+ /* ── Header: the period set large as the page's title, the view switch as
784
+ plain text, Today ‹ › in 2px ink frames, one rule to the grid. ── */
796
785
  .cal-hd {
786
+ /* Controls take a small corner that follows the calendar's own radius */
787
+ --cal-ctl-r: clamp(4px, calc(var(--cal-r, 0px) / 2), 8px);
788
+ --cal-ctl-h: 36px;
797
789
  display: flex;
798
790
  flex-wrap: wrap;
799
791
  align-items: center;
800
- gap: 8px;
801
- padding: 6px 10px;
802
- min-height: 44px;
803
- box-sizing: border-box;
804
- border-bottom: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
792
+ gap: 10px 24px;
793
+ padding: 14px 16px 12px;
794
+ border-bottom: 1px solid var(--dt-border, rgba(15, 15, 15, 0.08));
805
795
  flex-shrink: 0;
806
- }
807
-
808
- .cal-hd-side {
809
- display: flex;
810
- align-items: center;
811
- gap: 4px;
812
- flex: 1;
813
- min-width: 0;
814
- }
815
-
816
- .cal-hd-side--end {
817
- justify-content: flex-end;
796
+ box-sizing: border-box;
797
+ font-family: var(--dt-sans, system-ui, sans-serif);
798
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
818
799
  }
819
800
 
820
801
  .cal-hd-title {
821
- font: 500 16px/1.2 var(--dt-sans, system-ui, sans-serif);
802
+ flex: 1 1 auto;
803
+ min-width: 0;
804
+ font: 400 32px/1.1 var(--dt-sans, system-ui, sans-serif);
805
+ font-stretch: 90%;
822
806
  font-variant-numeric: tabular-nums;
823
- letter-spacing: -0.01em;
824
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
807
+ letter-spacing: -0.02em;
825
808
  white-space: nowrap;
826
809
  overflow: hidden;
827
810
  text-overflow: ellipsis;
828
811
  }
829
812
 
830
- .cal-hd-btn {
813
+ .cal-sr {
814
+ position: absolute;
815
+ width: 1px;
816
+ height: 1px;
817
+ margin: -1px;
818
+ padding: 0;
819
+ overflow: hidden;
820
+ clip-path: inset(50%);
821
+ white-space: nowrap;
822
+ border: 0;
823
+ }
824
+
825
+ .cal-hd-nav {
831
826
  display: flex;
832
827
  align-items: center;
833
- justify-content: center;
834
- width: 28px;
835
- height: 28px;
836
- border: none;
837
- background: transparent;
838
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
839
- border-radius: 0;
840
- cursor: pointer;
841
- transition: background 120ms, color 120ms;
828
+ gap: 8px;
842
829
  }
843
-
844
- .cal-hd-btn:hover {
845
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
846
- background: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 8%, transparent);
830
+ .cal-hd-nav:empty {
831
+ display: none;
847
832
  }
848
833
 
849
- .cal-hd-btn:focus-visible,
850
- .cal-hd-today:focus-visible,
851
- .cal-pill:focus-visible {
852
- outline: 2px solid color-mix(in srgb, var(--dt-accent, #2563eb) 55%, transparent);
853
- outline-offset: 2px;
854
- }
855
-
856
- .cal-hd-today {
857
- font: 500 12px/1 var(--dt-sans, system-ui, sans-serif);
858
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
834
+ .cal-hd-today,
835
+ .cal-hd-btn {
836
+ box-sizing: border-box;
837
+ height: var(--cal-ctl-h);
838
+ border: 2px solid var(--dt-text, rgba(15, 15, 15, 0.87));
839
+ border-radius: var(--cal-ctl-r);
859
840
  background: transparent;
860
- border: 1px solid var(--dt-border-day, var(--dt-border, rgba(0, 0, 0, 0.14)));
861
- padding: 7px 10px;
862
- border-radius: 0;
841
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
863
842
  cursor: pointer;
843
+ transition: background-color 120ms, opacity 120ms;
844
+ -webkit-tap-highlight-color: transparent;
845
+ }
846
+ .cal-hd-today {
847
+ padding: 0 14px;
848
+ font: 400 15px/1 var(--dt-sans, system-ui, sans-serif);
864
849
  white-space: nowrap;
865
- margin-right: 2px;
866
- transition: background 120ms, color 120ms, border-color 120ms;
867
850
  }
868
-
869
- .cal-hd-today:hover:not(:disabled) {
870
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
871
- border-color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
851
+ .cal-hd-btn {
852
+ display: grid;
853
+ place-items: center;
854
+ width: var(--cal-ctl-h);
855
+ padding: 0;
856
+ }
857
+ /* The chevrons point along the reading direction */
858
+ .cal-hd-btn:dir(rtl) svg {
859
+ transform: scaleX(-1);
860
+ }
861
+ .cal-hd-today:hover:not(:disabled),
862
+ .cal-hd-btn:hover {
863
+ background: var(--dt-hover, color-mix(in srgb, currentColor 6%, transparent));
864
+ }
865
+ .cal-hd-today:active:not(:disabled),
866
+ .cal-hd-btn:active {
867
+ background: color-mix(in srgb, var(--dt-text, #0f0f0f) 12%, transparent);
872
868
  }
869
+ /* Today is already in view: the frame stays, quietly */
873
870
  .cal-hd-today:disabled {
874
- opacity: 0.45;
871
+ opacity: 0.4;
875
872
  cursor: default;
876
873
  }
877
874
 
878
- /* View switch: plain text tabs; the chosen one carries an ink rule. */
875
+ .cal-hd-today:focus-visible,
876
+ .cal-hd-btn:focus-visible,
877
+ .cal-pill:focus-visible {
878
+ outline: 2px solid var(--dt-text, rgba(15, 15, 15, 0.87));
879
+ outline-offset: 2px;
880
+ }
881
+
882
+ /* View switch: plain text; the chosen one is ink over a 2px ink rule.
883
+ The accent keeps its three jobs. */
879
884
  .cal-pills {
880
885
  display: flex;
881
- gap: 2px;
886
+ gap: 16px;
882
887
  flex-shrink: 0;
883
888
  }
884
-
889
+ /* A hairline between the view-type and the day/week/month rows */
890
+ .cal-pills--labels + .cal-pills {
891
+ align-self: stretch;
892
+ align-items: center;
893
+ padding-inline-start: 24px;
894
+ border-inline-start: 1px solid var(--dt-border, rgba(15, 15, 15, 0.12));
895
+ }
885
896
  .cal-pill {
886
897
  border: none;
887
898
  background: transparent;
888
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
899
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
889
900
  cursor: pointer;
890
- font: 500 13px/1 var(--dt-sans, system-ui, sans-serif);
891
- padding: 8px 8px 7px;
901
+ font: 400 15px/1 var(--dt-sans, system-ui, sans-serif);
902
+ min-height: var(--cal-ctl-h);
903
+ /* 4px of air for the focus ring, given back so the row keeps its rhythm */
904
+ padding: 0 4px;
905
+ margin: 0 -4px;
892
906
  border-radius: 0;
893
- box-shadow: inset 0 -2px 0 transparent;
894
- transition: box-shadow 100ms, color 100ms;
907
+ white-space: nowrap;
908
+ text-decoration: underline 2px transparent;
909
+ text-underline-offset: 7px;
910
+ transition: color 120ms, text-decoration-color 120ms;
911
+ -webkit-tap-highlight-color: transparent;
895
912
  }
896
-
897
- /* :not(--active) — the hover rule otherwise outranks the active color,
898
- and iOS keeps :hover stuck after a tap (dark text on the accent). */
913
+ /* :not(--active) — iOS keeps :hover stuck after a tap */
899
914
  .cal-pill:hover:not(.cal-pill--active) {
900
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
915
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
916
+ text-decoration-color: var(--dt-border-day, var(--dt-border, rgba(15, 15, 15, 0.3)));
901
917
  }
902
-
903
- /* Selected = ink text over an ink rule; the accent keeps its three jobs. */
904
918
  .cal-pill--active {
905
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
906
- box-shadow: inset 0 -2px 0 var(--dt-text, rgba(0, 0, 0, 0.87));
919
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
920
+ text-decoration-color: currentColor;
921
+ }
922
+
923
+ /* Compact (mobile) header: same drawing, smaller title, tighter gaps */
924
+ .cal-hd--mobile {
925
+ --cal-ctl-h: 40px;
926
+ gap: 8px 12px;
927
+ padding: 10px 8px 8px;
928
+ }
929
+ .cal-hd--mobile .cal-hd-title {
930
+ font-size: 22px;
931
+ }
932
+ .cal-hd--mobile .cal-pills {
933
+ gap: 12px;
934
+ }
935
+ .cal-hd--mobile .cal-pill {
936
+ font-size: 14px;
937
+ }
938
+ .cal-hd--mobile .cal-hd-today {
939
+ padding: 0 10px;
940
+ font-size: 14px;
941
+ }
942
+ .cal-hd--mobile .cal-hd-nav {
943
+ gap: 6px;
944
+ margin-inline-start: auto;
945
+ }
946
+ /* Narrow containers: the title takes its own row above the controls */
947
+ .cal-hd--stack .cal-hd-title:not(:empty) {
948
+ flex-basis: 100%;
949
+ }
950
+
951
+ @media (pointer: coarse) {
952
+ .cal-hd {
953
+ --cal-ctl-h: 44px;
954
+ }
955
+ }
956
+
957
+ @media (prefers-reduced-motion: reduce) {
958
+ .cal-hd-today,
959
+ .cal-hd-btn,
960
+ .cal-pill {
961
+ transition: none;
962
+ }
907
963
  }
908
964
 
909
965
  .cal-body {
@@ -922,7 +978,7 @@ const navCtx = $derived({
922
978
  justify-content: center;
923
979
  height: 100%;
924
980
  font: 400 13px / 1 var(--dt-sans, system-ui, sans-serif);
925
- color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
981
+ color: var(--dt-text-3, rgba(15, 15, 15, 0.38));
926
982
  }
927
983
 
928
984
  .cal-loading {
@@ -934,7 +990,7 @@ const navCtx = $derived({
934
990
  background: linear-gradient(
935
991
  90deg,
936
992
  transparent 0%,
937
- var(--dt-accent, #2563eb) 50%,
993
+ var(--dt-accent, #1d4fd7) 50%,
938
994
  transparent 100%
939
995
  );
940
996
  animation: cal-slide 1.2s ease-in-out infinite;
@@ -948,142 +1004,8 @@ const navCtx = $derived({
948
1004
  @media (prefers-reduced-motion: reduce) {
949
1005
  .cal-loading {
950
1006
  animation: none;
951
- background: var(--dt-accent-dim, rgba(37, 99, 235, 0.12));
1007
+ background: var(--dt-accent-dim, rgba(29, 79, 215, 0.12));
952
1008
  }
953
1009
  }
954
1010
 
955
- /* ── Mobile header (flow layout) ── */
956
- .cal-m-hd {
957
- display: flex;
958
- align-items: center;
959
- gap: 4px;
960
- padding: 8px 8px 6px;
961
- border-bottom: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
962
- flex-shrink: 0;
963
- min-height: 44px;
964
- }
965
-
966
- /* Narrow containers: the date label moves to its own row (.cal-m-titlebar),
967
- so the controls row spreads pills and nav to the edges. */
968
- .cal-m-hd--stack {
969
- justify-content: space-between;
970
- }
971
- .cal-m-hd--titled {
972
- border-bottom: none;
973
- padding-bottom: 2px;
974
- }
975
- .cal-m-titlebar {
976
- display: flex;
977
- justify-content: center;
978
- padding: 0 8px 8px;
979
- border-bottom: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
980
- flex-shrink: 0;
981
- }
982
- .cal-m-titlebar .cal-m-title {
983
- flex: 0 1 auto;
984
- }
985
-
986
- .cal-m-left,
987
- .cal-m-right {
988
- display: flex;
989
- align-items: center;
990
- gap: 2px;
991
- flex-shrink: 0;
992
- }
993
-
994
- .cal-m-right {
995
- justify-content: flex-end;
996
- }
997
-
998
- .cal-m-nav {
999
- display: flex;
1000
- align-items: center;
1001
- justify-content: center;
1002
- width: 40px;
1003
- height: 40px;
1004
- border: none;
1005
- background: transparent;
1006
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1007
- border-radius: 50%;
1008
- cursor: pointer;
1009
- transition: background 120ms, color 120ms;
1010
- -webkit-tap-highlight-color: transparent;
1011
- flex-shrink: 0;
1012
- }
1013
- .cal-m-nav:hover {
1014
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
1015
- background: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 8%, transparent);
1016
- }
1017
- .cal-m-nav:active {
1018
- background: var(--dt-accent-dim, rgba(37, 99, 235, 0.12));
1019
- }
1020
- .cal-m-nav:focus-visible {
1021
- outline: 2px solid color-mix(in srgb, var(--dt-accent, #2563eb) 55%, transparent);
1022
- outline-offset: 2px;
1023
- }
1024
-
1025
- .cal-m-pills {
1026
- display: flex;
1027
- gap: 2px;
1028
- flex-shrink: 0;
1029
- }
1030
- .cal-m-pill {
1031
- border: none;
1032
- background: transparent;
1033
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1034
- cursor: pointer;
1035
- font: 500 13px / 1 var(--dt-sans, system-ui, sans-serif);
1036
- padding: 11px 10px 9px;
1037
- border-radius: 0;
1038
- box-shadow: inset 0 -2px 0 transparent;
1039
- transition: box-shadow 100ms, color 100ms;
1040
- -webkit-tap-highlight-color: transparent;
1041
- }
1042
- .cal-m-pill:hover:not(.cal-m-pill--active) {
1043
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
1044
- }
1045
- .cal-m-pill--active {
1046
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
1047
- box-shadow: inset 0 -2px 0 var(--dt-text, rgba(0, 0, 0, 0.87));
1048
- }
1049
-
1050
- .cal-m-title {
1051
- flex: 1;
1052
- text-align: center;
1053
- font: 600 14px / 1.2 var(--dt-sans, system-ui, sans-serif);
1054
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
1055
- white-space: nowrap;
1056
- overflow: hidden;
1057
- text-overflow: ellipsis;
1058
- min-width: 0;
1059
- }
1060
-
1061
- .cal-m-today {
1062
- font: 600 13px / 1 var(--dt-sans, system-ui, sans-serif);
1063
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
1064
- background: transparent;
1065
- border: 1px solid var(--dt-border-day, rgba(0, 0, 0, 0.12));
1066
- min-height: 40px;
1067
- padding: 5px 12px;
1068
- border-radius: 0;
1069
- cursor: pointer;
1070
- white-space: nowrap;
1071
- transition: background 120ms, color 120ms;
1072
- -webkit-tap-highlight-color: transparent;
1073
- flex-shrink: 0;
1074
- }
1075
- .cal-m-today:hover:not(:disabled) {
1076
- border-color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1077
- }
1078
- .cal-m-today:active:not(:disabled) {
1079
- background: color-mix(in srgb, var(--dt-text, #000) 8%, transparent);
1080
- }
1081
- .cal-m-today:disabled {
1082
- opacity: 0.45;
1083
- cursor: default;
1084
- }
1085
- .cal-m-today:focus-visible {
1086
- outline: 2px solid var(--dt-text, rgba(0, 0, 0, 0.87));
1087
- outline-offset: 2px;
1088
- }
1089
1011
  </style>
@@ -69,7 +69,7 @@ export interface CalendarProps {
69
69
  mondayStart?: boolean;
70
70
  /** Total height. Use `'auto'` to let content determine height (ideal for Agenda views). */
71
71
  height?: number | 'auto';
72
- /** Border radius in px (default: 12). Set to 0 for no rounding. */
72
+ /** Border radius in px (default: 0, square-cut). */
73
73
  borderRadius?: number;
74
74
  /** Text direction: 'ltr' (default), 'rtl', or 'auto' */
75
75
  dir?: 'ltr' | 'rtl' | 'auto';
@@ -43,18 +43,18 @@ const dayNum = $derived(new Date(dayMs).getDate());
43
43
  font: 500 10px / 1 var(--dt-sans, system-ui, sans-serif);
44
44
  letter-spacing: 0.04em;
45
45
  text-transform: uppercase;
46
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
46
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
47
47
  }
48
48
  .dh-name-long {
49
49
  font-size: 12px;
50
50
  text-transform: none;
51
51
  letter-spacing: 0.01em;
52
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
52
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
53
53
  }
54
54
 
55
55
  .dh-num {
56
56
  font: 600 14px / 1 var(--dt-sans, system-ui, sans-serif);
57
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
57
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
58
58
  width: 28px;
59
59
  height: 28px;
60
60
  display: flex;
@@ -63,20 +63,20 @@ const dayNum = $derived(new Date(dayMs).getDate());
63
63
  border-radius: 50%;
64
64
  }
65
65
  .dh-num-today {
66
- background: var(--dt-accent, #2563eb);
66
+ background: var(--dt-accent, #1d4fd7);
67
67
  color: var(--dt-btn-text, #fff);
68
68
  }
69
69
 
70
70
  .dh-date {
71
71
  font: 400 10px / 1 var(--dt-sans, system-ui, sans-serif);
72
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
72
+ color: var(--dt-text-2, rgba(15, 15, 15, 0.54));
73
73
  }
74
74
 
75
75
  .dh-rel {
76
76
  font: 500 12px / 1.3 var(--dt-sans, system-ui, sans-serif);
77
- color: var(--dt-text, rgba(0, 0, 0, 0.87));
77
+ color: var(--dt-text, rgba(15, 15, 15, 0.87));
78
78
  }
79
79
  .dh-today .dh-rel {
80
- color: var(--dt-accent, #2563eb);
80
+ color: var(--dt-accent, #1d4fd7);
81
81
  }
82
82
  </style>