@cahyo-dimas/freeday 3.7.0 → 3.9.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +197 -0
  2. package/COMPONENTS.md +214 -18
  3. package/README.id.md +4 -4
  4. package/README.md +4 -3
  5. package/adapters/blazor/FdyDatepicker.razor +3 -1
  6. package/adapters/blazor/FdyDatepicker.razor.cs +36 -1
  7. package/adapters/blazor/FdyMonthpicker.razor +29 -0
  8. package/adapters/blazor/FdyMonthpicker.razor.cs +102 -0
  9. package/adapters/blazor/FdyTable.razor +6 -1
  10. package/adapters/blazor/FdyTable.razor.cs +33 -0
  11. package/adapters/blazor/FdyTableFooter.razor +6 -6
  12. package/adapters/blazor/FdyTableFooter.razor.cs +36 -0
  13. package/adapters/core/month-grid.d.ts +116 -0
  14. package/adapters/core/month-grid.js +234 -0
  15. package/adapters/react/components/FdyDatepicker.tsx +164 -166
  16. package/adapters/react/components/FdyMonthpicker.tsx +400 -0
  17. package/adapters/react/components/FdyTable.tsx +38 -2
  18. package/adapters/react/components/FdyTableFilter.tsx +13 -1
  19. package/adapters/react/components/FdyTableFooter.tsx +37 -6
  20. package/adapters/react/index.d.ts +6 -2
  21. package/adapters/react/index.js +1 -0
  22. package/adapters/react/usePopover.ts +54 -9
  23. package/adapters/vue/components/FdyDatepicker.vue +158 -160
  24. package/adapters/vue/components/FdyMonthpicker.vue +387 -0
  25. package/adapters/vue/components/FdyTable.vue +33 -1
  26. package/adapters/vue/components/FdyTableFilter.vue +13 -1
  27. package/adapters/vue/components/FdyTableFooter.vue +39 -6
  28. package/adapters/vue/index.d.ts +16 -0
  29. package/adapters/vue/index.js +1 -0
  30. package/adapters/vue/usePopover.ts +53 -10
  31. package/dist/freeday-datepicker.js +68 -14
  32. package/dist/freeday-menu.js +15 -1
  33. package/dist/freeday-popover.js +33 -11
  34. package/dist/freeday-table.js +64 -5
  35. package/dist/freeday-tabs.js +21 -2
  36. package/dist/freeday.bundle.css +50 -2
  37. package/dist/freeday.css +50 -2
  38. package/dist/freeday.js +201 -33
  39. package/docs/agent-onboarding.md +3 -3
  40. package/docs/getting-started.md +6 -6
  41. package/docs/integrations.md +22 -4
  42. package/package.json +2 -2
  43. package/src/components/table.css +19 -2
  44. package/src/components/tabs.css +31 -0
@@ -5,11 +5,31 @@ namespace Freeday.Blazor;
5
5
 
6
6
  public partial class FdyDatepicker
7
7
  {
8
- /// <summary>Selected date as an ISO string (<c>yyyy-MM-dd</c>), or "" when unset.
8
+ /// <summary>Selected date as an ISO string (<c>yyyy-MM-dd</c>), or <c>yyyy-MM</c> when
9
+ /// <see cref="Mode"/> is <see cref="FdyDatepickerMode.Month"/>, or "" when unset.
9
10
  /// Two-way bindable via <c>@bind-Value</c>.</summary>
10
11
  [Parameter] public string? Value { get; set; }
11
12
  [Parameter] public EventCallback<string?> ValueChanged { get; set; }
12
13
 
14
+ /// <summary>What the picker answers with. <see cref="FdyDatepickerMode.Day"/> (default) is the
15
+ /// calendar as it always was. <see cref="FdyDatepickerMode.Month"/> opens on the month grid,
16
+ /// stops there, and reports <c>yyyy-MM</c>.</summary>
17
+ [Obsolete("Use FdyMonthpicker for a month. This parameter makes Value, Min and Max carry " +
18
+ "yyyy-MM-dd or yyyy-MM while a different parameter decides which, so the binding " +
19
+ "cannot state what to store in it. Still works, unchanged, and removed in 4.0.")]
20
+ [Parameter] public FdyDatepickerMode Mode { get; set; } = FdyDatepickerMode.Day;
21
+
22
+ /* null rather than "day" for the default: an attribute the enhancer does not need is an
23
+ attribute no reader of the seed has to interpret, and the enhancer's own check is
24
+ `=== 'month'`, so the day path is the absence of this.
25
+
26
+ The pragma is the kit reading its OWN deprecated member, which is the one place that has to
27
+ keep doing so: the promise is that `Mode` keeps working until 4.0, and it cannot work if the
28
+ seed stops looking at it. Scoped to these two lines so every other use still warns. */
29
+ #pragma warning disable CS0618
30
+ private string? ModeAttribute => Mode == FdyDatepickerMode.Month ? "month" : null;
31
+ #pragma warning restore CS0618
32
+
13
33
  /// <summary>Accessible label for the trigger.</summary>
14
34
  [Parameter] public string? Label { get; set; }
15
35
  [Parameter] public string? Placeholder { get; set; }
@@ -83,3 +103,18 @@ public partial class FdyDatepicker
83
103
  /// <summary>The detail shape of the enhancer's <c>fdy-datepicker-change</c> event.</summary>
84
104
  public sealed record ChangeDetail(string? Value, string? Date);
85
105
  }
106
+
107
+ /// <summary>What <see cref="FdyDatepicker"/> answers with.</summary>
108
+ /// <remarks>The enum goes with <see cref="FdyDatepicker.Mode"/> in 4.0, so only
109
+ /// <see cref="Day"/> is left unmarked: it names the behaviour a picker with no
110
+ /// <c>Mode</c> already has, and writing it changes nothing.</remarks>
111
+ public enum FdyDatepickerMode
112
+ {
113
+ /// <summary>A single day, reported as <c>yyyy-MM-dd</c>.</summary>
114
+ Day,
115
+
116
+ /// <summary>A whole month, reported as <c>yyyy-MM</c>.</summary>
117
+ [Obsolete("Use FdyMonthpicker, whose Value is yyyy-MM and has no Mode to read. " +
118
+ "Still works, unchanged, and removed in 4.0.")]
119
+ Month,
120
+ }
@@ -0,0 +1,29 @@
1
+ @namespace Freeday.Blazor
2
+ @inherits FreedayComponentBase<FdyMonthpicker>
3
+
4
+ @* Seed-based wrapper over freeday-datepicker.js in its month mode: the enhancer builds the
5
+ input-styled trigger + calendar popover from this empty div, opens on the month grid, and emits
6
+ fdy-datepicker-change. The enhancer owns that DOM, so we render the seed once
7
+ (ShouldRender => false) and read the picked month back through @bind-Value.
8
+
9
+ data-mode="month" is written here rather than taken as a parameter. That is the difference
10
+ between this component and FdyDatepicker: Value has one format, yyyy-MM, because nothing the
11
+ caller passes can change it. *@
12
+ <div @ref="Root" data-fdy-datepicker
13
+ data-mode="month"
14
+ data-value="@Value"
15
+ data-label="@Label"
16
+ data-placeholder="@Placeholder"
17
+ data-min="@Min"
18
+ data-max="@Max"
19
+ data-id="@Id"
20
+ data-describedby="@Describedby"
21
+ data-disabled="@(Disabled ? "true" : null)"
22
+ data-readonly="@(Readonly ? "true" : null)"
23
+ data-invalid="@(Invalid ? "true" : null)"
24
+ data-fdy-text-prev-year="@PrevYearLabel"
25
+ data-fdy-text-next-year="@NextYearLabel"
26
+ data-fdy-text-prev-years="@PrevYearsLabel"
27
+ data-fdy-text-next-years="@NextYearsLabel"
28
+ data-fdy-text-choose-year="@ChooseYearLabel"
29
+ data-fdy-text-back-to-months="@BackToMonthsLabel"></div>
@@ -0,0 +1,102 @@
1
+ using Microsoft.AspNetCore.Components;
2
+ using Microsoft.JSInterop;
3
+
4
+ namespace Freeday.Blazor;
5
+
6
+ /// <summary>
7
+ /// A picker that answers with a whole month (<c>yyyy-MM</c>).
8
+ /// </summary>
9
+ /// <remarks>
10
+ /// <para>This is the replacement for <c>FdyDatepicker</c> with <c>Mode="FdyDatepickerMode.Month"</c>,
11
+ /// which shipped in 3.8.0 and is deprecated. There, <c>Value</c> is one <c>string?</c> holding
12
+ /// <c>yyyy-MM-dd</c> or <c>yyyy-MM</c> and a different parameter decides which, so nothing about
13
+ /// the binding tells a caller what to store in it. Here it has one format and there is no
14
+ /// <c>Mode</c>. The deprecated spelling keeps working until 4.0.</para>
15
+ /// <para>It is a separate seed rather than a wrapper around <c>FdyDatepicker</c>: that component
16
+ /// renders once and then hands its DOM to the enhancer (<c>ShouldRender =&gt; !Hydrated</c>), so
17
+ /// nesting it would mean forwarding an <c>ElementReference</c> and an event subscription through a
18
+ /// component that owns neither. The logic the two share is the enhancer itself, which both reach
19
+ /// through <c>FreedayDatepicker.init</c>, and that is already one copy.</para>
20
+ /// <para>Month names come from the page's <c>&lt;html lang&gt;</c> through <c>Intl</c>, which is
21
+ /// what <c>&lt;input type="month"&gt;</c> cannot do: it names the month in the browser's language.
22
+ /// So an Indonesian page reads <c>Oktober 2026</c> with nothing configured.</para>
23
+ /// </remarks>
24
+ public partial class FdyMonthpicker
25
+ {
26
+ /// <summary>The chosen month as an ISO string (<c>yyyy-MM</c>), or "" when unset. One format,
27
+ /// always. Two-way bindable via <c>@bind-Value</c>.</summary>
28
+ [Parameter] public string? Value { get; set; }
29
+ [Parameter] public EventCallback<string?> ValueChanged { get; set; }
30
+
31
+ /// <summary>Accessible label for the trigger. Defaults to the enhancer's <c>Month</c>.</summary>
32
+ [Parameter] public string? Label { get; set; }
33
+
34
+ /// <summary>Shown while nothing is picked. Defaults to the enhancer's <c>Choose a month</c>.</summary>
35
+ [Parameter] public string? Placeholder { get; set; }
36
+
37
+ /// <summary>Earliest selectable month (ISO <c>yyyy-MM</c>; a full <c>yyyy-MM-dd</c> is read too,
38
+ /// as its own month). A bound that falls inside a month leaves that month pickable.</summary>
39
+ [Parameter] public string? Min { get; set; }
40
+
41
+ /// <summary>Latest selectable month.</summary>
42
+ [Parameter] public string? Max { get; set; }
43
+
44
+ /// <summary>Id for the trigger the enhancer builds, so a form's own label can point at it.</summary>
45
+ [Parameter] public string? Id { get; set; }
46
+
47
+ /// <summary>Id of the help or error text the trigger describes itself with.</summary>
48
+ [Parameter] public string? Describedby { get; set; }
49
+
50
+ /// <summary>Greyed and out of the tab order.</summary>
51
+ [Parameter] public bool Disabled { get; set; }
52
+
53
+ /// <summary>Locked/view mode: focusable and showing its month, but the calendar will not open.
54
+ /// Unlike <see cref="Disabled"/> it keeps tab order and is not greyed.</summary>
55
+ [Parameter] public bool Readonly { get; set; }
56
+
57
+ /// <summary>Marks the field invalid (<c>aria-invalid</c> + the error styling).</summary>
58
+ [Parameter] public bool Invalid { get; set; }
59
+
60
+ /* The calendar's navigation labels, and only the ones this picker shows. Month NAMES follow the
61
+ page's `lang` through Intl and need nothing here. The day grid's own arrows are absent along
62
+ with the day grid, so there is no PrevMonthLabel to pass: the arrows beside a month grid step
63
+ a YEAR. Leave one null and the enhancer's English default stands. */
64
+ [Parameter] public string? PrevYearLabel { get; set; }
65
+ [Parameter] public string? NextYearLabel { get; set; }
66
+ [Parameter] public string? PrevYearsLabel { get; set; }
67
+ [Parameter] public string? NextYearsLabel { get; set; }
68
+
69
+ /// <summary>The title button that drills into the year grid. <c>{label}</c> is the year shown.</summary>
70
+ [Parameter] public string? ChooseYearLabel { get; set; }
71
+
72
+ /// <summary>The year grid's title, back to months. <c>{start}</c> and <c>{end}</c> are the page range.</summary>
73
+ [Parameter] public string? BackToMonthsLabel { get; set; }
74
+
75
+ protected override ValueTask HydrateAsync() => JS.InvokeVoidAsync("FreedayDatepicker.init", Root);
76
+
77
+ protected override async ValueTask OnHydratedAsync()
78
+ => await SubscribeAsync("fdy-datepicker-change", nameof(OnChange));
79
+
80
+ // The enhancer owns the built trigger + calendar; never let Blazor re-render the seed.
81
+ protected override bool ShouldRender() => !Hydrated;
82
+
83
+ /* Which is why the three states are pushed rather than re-rendered: the seed is written once,
84
+ so a page that disables this field later would otherwise be ignored in silence. */
85
+ protected override async Task OnParametersSetAsync()
86
+ {
87
+ if (!Hydrated) return;
88
+ await JS.InvokeVoidAsync("FreedayDatepicker.setState", Root,
89
+ new { disabled = Disabled, @readonly = Readonly, invalid = Invalid });
90
+ }
91
+
92
+ /// <summary>Invoked by the bridge when the user picks (or clears) a month.</summary>
93
+ [JSInvokable]
94
+ public async Task OnChange(ChangeDetail detail)
95
+ {
96
+ Value = detail.Value;
97
+ await ValueChanged.InvokeAsync(detail.Value);
98
+ }
99
+
100
+ /// <summary>The detail shape of the enhancer's <c>fdy-datepicker-change</c> event.</summary>
101
+ public sealed record ChangeDetail(string? Value, string? Date);
102
+ }
@@ -146,6 +146,11 @@
146
146
 
147
147
  @if (Pager)
148
148
  {
149
- <FdyTableFooter Page="FooterPage" PageSizes="PageSizes" PageChanged="OnFooterPage" />
149
+ @* The six strings go through as given, null included: every default lives in the footer, so
150
+ a parameter left unset here is the footer's English and not a second copy of it. *@
151
+ <FdyTableFooter Page="FooterPage" PageSizes="PageSizes" PageChanged="OnFooterPage"
152
+ InfoText="@InfoText" PageSizeLabel="@PageSizeLabel"
153
+ PaginationLabel="@PaginationLabel" PrevLabel="@PrevLabel"
154
+ NextLabel="@NextLabel" PageLabel="@PageLabel" />
150
155
  }
151
156
  </div>
@@ -116,6 +116,39 @@ public partial class FdyTable<TRow>
116
116
  /// <summary>Bulk-bar actions (Blazor equivalent of Vue's <c>bulk-actions</c> slot).</summary>
117
117
  [Parameter] public RenderFragment? BulkContent { get; set; }
118
118
 
119
+ // The footer's six strings, forwarded to the FdyTableFooter this table draws itself. 3.8.0 gave
120
+ // the footer those parameters and this table passed none of them on, so the hatch reached every
121
+ // footer except the one almost every page uses, and the documented way out was to switch the
122
+ // footer off and render it yourself: a door that opens only if you stop using the table. Flat
123
+ // parameters carrying the footer's own names, so moving between the two routes moves a
124
+ // parameter rather than reshaping it.
125
+ //
126
+ // Nullable, unlike SelectedText and its four neighbours above, which this component renders
127
+ // itself and therefore defaults itself. These six are rendered by FdyTableFooter, which already
128
+ // holds their English; a default repeated here would be a second copy to keep in step, and the
129
+ // two copies would disagree the first time one of them was edited.
130
+
131
+ /// <summary>Footer range line, <c>{from}</c> <c>{to}</c> <c>{total}</c> substituted.
132
+ /// Null leaves <c>FdyTableFooter</c>'s default, <c>Showing {from}–{to} of {total}</c>.</summary>
133
+ [Parameter] public string? InfoText { get; set; }
134
+
135
+ /// <summary>Footer: the visible word beside the rows-per-page control, which also names it.
136
+ /// Null leaves the default, <c>Rows</c>.</summary>
137
+ [Parameter] public string? PageSizeLabel { get; set; }
138
+
139
+ /// <summary>Footer: accessible name of the pager landmark. Null leaves <c>Pagination</c>.</summary>
140
+ [Parameter] public string? PaginationLabel { get; set; }
141
+
142
+ /// <summary>Footer: accessible name of the previous-page arrow. Null leaves <c>Previous page</c>.</summary>
143
+ [Parameter] public string? PrevLabel { get; set; }
144
+
145
+ /// <summary>Footer: accessible name of the next-page arrow. Null leaves <c>Next page</c>.</summary>
146
+ [Parameter] public string? NextLabel { get; set; }
147
+
148
+ /// <summary>Footer: accessible name of a numbered page button, <c>{n}</c> substituted. Null
149
+ /// leaves <c>Go to page {n}</c>.</summary>
150
+ [Parameter] public string? PageLabel { get; set; }
151
+
119
152
  private FdySortState? _internalSort;
120
153
  private Dictionary<string, FdyColumnFilter> _internalFilters = new();
121
154
  private int _internalPageIndex;
@@ -9,12 +9,12 @@
9
9
  @if (Visible)
10
10
  {
11
11
  <div class="fdy-table-footer">
12
- <span class="fdy-table-footer__info">Showing @RangeFrom–@RangeTo of @Page.Total</span>
12
+ <span class="fdy-table-footer__info">@InfoLine</span>
13
13
 
14
14
  @if (Sizes.Count > 0)
15
15
  {
16
16
  <div class="fdy-table-footer__size">
17
- <span id="@SizeLabelId">Rows</span>
17
+ <span id="@SizeLabelId">@(PageSizeLabel ?? "Rows")</span>
18
18
  @* The kit's own listbox, not a native <select>: an OS menu is unthemeable, and on
19
19
  macOS it drops a dark panel into a light page. FdyCombo's own header says so. *@
20
20
  <FdyCombo TValue="string" Value="@Page.Size.ToString()" Options="SizeOptions"
@@ -24,10 +24,10 @@
24
24
 
25
25
  @if (HasPager)
26
26
  {
27
- <nav class="fdy-pagination" aria-label="Pagination">
27
+ <nav class="fdy-pagination" aria-label="@(PaginationLabel ?? "Pagination")">
28
28
  <ul class="fdy-pagination__list">
29
29
  <li>
30
- <button type="button" class="fdy-pagination__link" aria-label="Previous page"
30
+ <button type="button" class="fdy-pagination__link" aria-label="@(PrevLabel ?? "Previous page")"
31
31
  disabled="@(CurrentPage1 == 1)" @onclick="() => GoTo(CurrentPage1 - 1)">‹</button>
32
32
  </li>
33
33
  @foreach (int? p in Pages)
@@ -44,13 +44,13 @@
44
44
  else
45
45
  {
46
46
  int page = p.Value;
47
- <button type="button" class="fdy-pagination__link" aria-label="@($"Go to page {page}")"
47
+ <button type="button" class="fdy-pagination__link" aria-label="@PageAriaLabel(page)"
48
48
  @onclick="() => GoTo(page)">@(page)</button>
49
49
  }
50
50
  </li>
51
51
  }
52
52
  <li>
53
- <button type="button" class="fdy-pagination__link" aria-label="Next page"
53
+ <button type="button" class="fdy-pagination__link" aria-label="@(NextLabel ?? "Next page")"
54
54
  disabled="@(CurrentPage1 == TotalPages)" @onclick="() => GoTo(CurrentPage1 + 1)">›</button>
55
55
  </li>
56
56
  </ul>
@@ -21,6 +21,35 @@ public partial class FdyTableFooter
21
21
  /// </summary>
22
22
  [Parameter] public IReadOnlyList<int>? PageSizes { get; set; }
23
23
 
24
+ // The strings, six of them, one per thing this band says. The raw path has had a hatch since
25
+ // 1.39.0 (data-fdy-text-<key> read through textOf()) and this path had none, so an app that
26
+ // does not speak English could only rewrite the whole band by hand. A Blazor app mixes both
27
+ // paths by construction, since FreedayBlazor.initAll runs the enhancers, so these names follow
28
+ // the enhancer's TEXT keys where a key exists. Defaults stay English on every path, which
29
+ // COMPONENTS.md promises and the suite enforces.
30
+
31
+ /// <summary>
32
+ /// The range line. <c>{from}</c>, <c>{to}</c> and <c>{total}</c> are substituted, every
33
+ /// occurrence, as the enhancer's <c>textOf()</c> does. Default <c>Showing {from}–{to} of
34
+ /// {total}</c>, the enhancer's <c>info</c> key.
35
+ /// </summary>
36
+ [Parameter] public string? InfoText { get; set; }
37
+
38
+ /// <summary>The visible word beside the rows-per-page control, which also names it. Default <c>Rows</c>.</summary>
39
+ [Parameter] public string? PageSizeLabel { get; set; }
40
+
41
+ /// <summary>Accessible name of the pager landmark. Default <c>Pagination</c>.</summary>
42
+ [Parameter] public string? PaginationLabel { get; set; }
43
+
44
+ /// <summary>Accessible name of the previous-page arrow. Default <c>Previous page</c>.</summary>
45
+ [Parameter] public string? PrevLabel { get; set; }
46
+
47
+ /// <summary>Accessible name of the next-page arrow. Default <c>Next page</c>.</summary>
48
+ [Parameter] public string? NextLabel { get; set; }
49
+
50
+ /// <summary>Accessible name of a numbered page button. <c>{n}</c> substituted. Default <c>Go to page {n}</c>.</summary>
51
+ [Parameter] public string? PageLabel { get; set; }
52
+
24
53
  private IReadOnlyList<int> Sizes => PageSizes ?? Array.Empty<int>();
25
54
 
26
55
  // Just the number: the control sits beside a label reading "Rows", so "20 per page" states the
@@ -40,6 +69,13 @@ public partial class FdyTableFooter
40
69
 
41
70
  private bool HasPager => Page.Size > 0 && TotalPages > 1;
42
71
 
72
+ private string InfoLine => (InfoText ?? "Showing {from}–{to} of {total}")
73
+ .Replace("{from}", RangeFrom.ToString())
74
+ .Replace("{to}", RangeTo.ToString())
75
+ .Replace("{total}", Page.Total.ToString());
76
+
77
+ private string PageAriaLabel(int page1) => (PageLabel ?? "Go to page {n}").Replace("{n}", page1.ToString());
78
+
43
79
  // One page and no size control means there is nothing here to say, the table has always
44
80
  // withheld the whole band in that case, and this is where that decision now lives.
45
81
  private bool Visible => HasPager || Sizes.Count > 0;
@@ -0,0 +1,116 @@
1
+ // Types for the framework-agnostic month and year grid logic (adapters/core/month-grid.js).
2
+ // Shared by FdyDatepicker and FdyMonthpicker in the Vue and React adapters.
3
+
4
+ /** A whole month as ISO `YYYY-MM`, e.g. `'2026-10'`. The format a month picker answers with. */
5
+ export type FdyMonth = string;
6
+
7
+ /** One cell of the twelve-cell month grid, ready to render. */
8
+ export interface FdyMonthCell {
9
+ /** 0-11, the month's index in its year. */
10
+ index: number;
11
+ /** What the cell shows, a short month name in the active locale. */
12
+ label: string;
13
+ /** The cell's accessible name: the month and its year, spelled out in the active locale. */
14
+ ariaLabel: string;
15
+ selected: boolean;
16
+ today: boolean;
17
+ /** The whole month falls outside min/max, so it cannot be picked or navigated into. */
18
+ disabled: boolean;
19
+ /** Holds the grid's single `tabindex="0"`. */
20
+ focusable: boolean;
21
+ }
22
+
23
+ /** One cell of the twelve-cell year grid. */
24
+ export interface FdyYearCell {
25
+ year: number;
26
+ selected: boolean;
27
+ today: boolean;
28
+ disabled: boolean;
29
+ focusable: boolean;
30
+ }
31
+
32
+ /** The two Intl formatters a month grid renders through. */
33
+ export interface FdyMonthFormatters {
34
+ /** Short month name, for the cell's text. */
35
+ cell: Intl.DateTimeFormat;
36
+ /** Month and year spelled out, for the cell's accessible name and the trigger's value. */
37
+ name: Intl.DateTimeFormat;
38
+ }
39
+
40
+ export interface FdyMonthGridView {
41
+ year: number;
42
+ selected: Date | null;
43
+ min: Date | null;
44
+ max: Date | null;
45
+ /** Which month index holds the roving tabindex. */
46
+ focusMonth: number;
47
+ formatters: FdyMonthFormatters;
48
+ /** Overridable only so a test can pin "today"; defaults to now. */
49
+ today?: Date;
50
+ }
51
+
52
+ export interface FdyYearGridView {
53
+ pageStart: number;
54
+ selected: Date | null;
55
+ min: Date | null;
56
+ max: Date | null;
57
+ focusYear: number;
58
+ today?: Date;
59
+ }
60
+
61
+ /**
62
+ * What a key press in a grid means. `choose` is the one the two components answer differently:
63
+ * `FdyMonthpicker` commits the month, `FdyDatepicker` walks down into its days.
64
+ */
65
+ export type FdyGridAction = 'focus' | 'choose' | 'close' | 'none';
66
+
67
+ export interface FdyMonthKeyIntent {
68
+ action: FdyGridAction;
69
+ /** 0-11, already wrapped inside the year on screen. */
70
+ index: number;
71
+ /** Years to step the view by, which only PageUp and PageDown set. */
72
+ yearDelta: number;
73
+ }
74
+
75
+ export interface FdyYearKeyIntent {
76
+ action: FdyGridAction;
77
+ year: number;
78
+ }
79
+
80
+ /** A year page holds twelve years, so the grid matches the month grid's shape. */
81
+ export declare const YEARS_PER_PAGE: number;
82
+
83
+ /** A Date as `YYYY-MM`. The day is dropped. */
84
+ export declare function toISOMonth(d: Date): FdyMonth;
85
+
86
+ /** Read `YYYY-MM` or `YYYY-MM-DD` as the first day of the month named; null when unparseable. */
87
+ export declare function parseISOMonth(s: string | null | undefined): Date | null;
88
+
89
+ /** The `lang` the page declares, then `en`. Never the browser's language, which is the whole point. */
90
+ export declare function pageLocale(): string;
91
+
92
+ export declare function startOfDay(d: Date): Date;
93
+ export declare function startOfMonth(d: Date): Date;
94
+ export declare function addMonths(d: Date, n: number): Date;
95
+
96
+ /** True only when the WHOLE month falls outside the bounds. */
97
+ export declare function monthDisabled(year: number, month: number, min: Date | null, max: Date | null): boolean;
98
+
99
+ /** True only when every month in the year falls outside the bounds. */
100
+ export declare function yearDisabled(year: number, min: Date | null, max: Date | null): boolean;
101
+
102
+ /** Where the aligned page holding this year starts (2016 for 2026). */
103
+ export declare function yearPageStart(year: number): number;
104
+
105
+ /** The year grid's title, naming the page it shows. */
106
+ export declare function yearRangeLabel(pageStart: number): string;
107
+
108
+ export declare function monthFormatters(locale: string): FdyMonthFormatters;
109
+
110
+ export declare function monthCells(view: FdyMonthGridView): FdyMonthCell[];
111
+
112
+ export declare function yearCells(view: FdyYearGridView): FdyYearCell[];
113
+
114
+ export declare function monthKeyIntent(key: string, focusMonth: number): FdyMonthKeyIntent;
115
+
116
+ export declare function yearKeyIntent(key: string, focusYear: number): FdyYearKeyIntent;