@cahyo-dimas/freeday 1.54.0 → 2.0.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 (143) hide show
  1. package/CHANGELOG.md +625 -545
  2. package/COMPONENTS.md +141 -138
  3. package/README.id.md +46 -45
  4. package/README.md +46 -43
  5. package/USAGE.md +34 -34
  6. package/adapters/blazor/FdyAppShell.razor +2 -2
  7. package/adapters/blazor/FdyAppShell.razor.cs +6 -6
  8. package/adapters/blazor/FdyCfl.razor.cs +1 -1
  9. package/adapters/blazor/FdyChart.razor +1 -1
  10. package/adapters/blazor/FdyChart.razor.cs +3 -3
  11. package/adapters/blazor/FdyChartSeries.cs +1 -1
  12. package/adapters/blazor/FdyCombo.razor.cs +2 -2
  13. package/adapters/blazor/FdyModal.razor +1 -1
  14. package/adapters/blazor/FdyTable.razor.cs +7 -7
  15. package/adapters/blazor/FdyTableFilter.razor +1 -1
  16. package/adapters/blazor/FdyTableFooter.razor +1 -1
  17. package/adapters/blazor/FdyTableFooter.razor.cs +4 -4
  18. package/adapters/blazor/Freeday.Blazor.csproj +1 -1
  19. package/adapters/blazor/FreedayComponentBase.cs +1 -1
  20. package/adapters/blazor/TableModel.cs +3 -3
  21. package/adapters/blazor/TableTypes.cs +3 -3
  22. package/adapters/blazor/freeday-blazor.js +5 -5
  23. package/adapters/core/app-shell.d.ts +1 -1
  24. package/adapters/core/app-shell.js +6 -6
  25. package/adapters/core/table-model.d.ts +2 -2
  26. package/adapters/core/table-model.js +8 -8
  27. package/adapters/react/components/FdyAppShell.tsx +5 -5
  28. package/adapters/react/components/FdyAutocomplete.tsx +1 -1
  29. package/adapters/react/components/FdyCascade.tsx +1 -1
  30. package/adapters/react/components/FdyCfl.tsx +11 -11
  31. package/adapters/react/components/FdyDateRange.tsx +2 -2
  32. package/adapters/react/components/FdyDatepicker.tsx +5 -5
  33. package/adapters/react/components/FdyDrawer.tsx +1 -1
  34. package/adapters/react/components/FdyModal.tsx +2 -2
  35. package/adapters/react/components/FdyTable.tsx +7 -7
  36. package/adapters/react/components/FdyTableFilter.tsx +1 -1
  37. package/adapters/react/components/FdyTableFooter.tsx +5 -5
  38. package/adapters/react/index.d.ts +1 -1
  39. package/adapters/react/useFreeday.js +3 -3
  40. package/adapters/vue/components/FdyAppShell.vue +5 -5
  41. package/adapters/vue/components/FdyAutocomplete.vue +1 -1
  42. package/adapters/vue/components/FdyCascade.vue +1 -1
  43. package/adapters/vue/components/FdyCfl.vue +8 -8
  44. package/adapters/vue/components/FdyChart.vue +2 -2
  45. package/adapters/vue/components/FdyCombo.vue +1 -1
  46. package/adapters/vue/components/FdyDateRange.vue +2 -2
  47. package/adapters/vue/components/FdyDatepicker.vue +5 -5
  48. package/adapters/vue/components/FdyDrawer.vue +3 -3
  49. package/adapters/vue/components/FdyModal.vue +3 -3
  50. package/adapters/vue/components/FdyTable.vue +7 -7
  51. package/adapters/vue/components/FdyTableFilter.vue +1 -1
  52. package/adapters/vue/components/FdyTableFooter.vue +5 -5
  53. package/adapters/vue/index.d.ts +2 -2
  54. package/adapters/vue/useFreeday.js +3 -3
  55. package/dist/asset.d.ts +3 -3
  56. package/dist/freeday-app-shell.js +9 -9
  57. package/dist/freeday-autocomplete.js +2 -2
  58. package/dist/freeday-breakpoint.js +1 -1
  59. package/dist/freeday-carousel.js +5 -5
  60. package/dist/freeday-cascade.js +7 -7
  61. package/dist/freeday-cfl.js +9 -9
  62. package/dist/freeday-chart.js +11 -11
  63. package/dist/freeday-chip.js +3 -3
  64. package/dist/freeday-datepicker.js +11 -7
  65. package/dist/freeday-datetime.js +5 -5
  66. package/dist/freeday-drawer.js +1 -1
  67. package/dist/freeday-form.js +14 -14
  68. package/dist/freeday-mask.js +6 -6
  69. package/dist/freeday-menu.js +1 -1
  70. package/dist/freeday-number.js +3 -3
  71. package/dist/freeday-popover.js +3 -3
  72. package/dist/freeday-rating.js +2 -2
  73. package/dist/freeday-select.js +5 -5
  74. package/dist/freeday-slider.js +2 -2
  75. package/dist/freeday-stepper.js +7 -7
  76. package/dist/freeday-table.js +15 -15
  77. package/dist/freeday-tabs.js +1 -1
  78. package/dist/freeday-timepicker.js +2 -2
  79. package/dist/freeday-toast.js +8 -8
  80. package/dist/freeday-tree.js +3 -3
  81. package/dist/freeday-upload.js +25 -25
  82. package/dist/freeday.bundle.css +176 -176
  83. package/dist/freeday.css +170 -170
  84. package/dist/freeday.d.ts +2 -2
  85. package/dist/freeday.js +162 -158
  86. package/dist/freeday.tokens.css +5 -5
  87. package/docs/agent-onboarding.md +41 -41
  88. package/docs/getting-started.md +35 -35
  89. package/docs/integrations.md +18 -18
  90. package/docs/reference-screen.html +14 -14
  91. package/package.json +4 -3
  92. package/src/base.css +6 -6
  93. package/src/components/accordion.css +4 -4
  94. package/src/components/alert.css +1 -1
  95. package/src/components/app-shell.css +10 -10
  96. package/src/components/appbar.css +3 -3
  97. package/src/components/autocomplete.css +1 -1
  98. package/src/components/avatar.css +3 -3
  99. package/src/components/badge.css +5 -5
  100. package/src/components/breadcrumb.css +1 -1
  101. package/src/components/breakpoints.css +4 -4
  102. package/src/components/button.css +13 -13
  103. package/src/components/card.css +8 -8
  104. package/src/components/carousel.css +2 -2
  105. package/src/components/cascade.css +2 -2
  106. package/src/components/cfl.css +4 -4
  107. package/src/components/chart.css +3 -3
  108. package/src/components/chip.css +2 -2
  109. package/src/components/combo.css +3 -3
  110. package/src/components/composition.css +7 -7
  111. package/src/components/datepicker.css +6 -6
  112. package/src/components/datetimepicker.css +1 -1
  113. package/src/components/description-list.css +1 -1
  114. package/src/components/divider.css +1 -1
  115. package/src/components/drawer.css +1 -1
  116. package/src/components/file-upload.css +5 -5
  117. package/src/components/filterbar.css +7 -7
  118. package/src/components/form-grid.css +2 -2
  119. package/src/components/input-group.css +6 -6
  120. package/src/components/input.css +6 -6
  121. package/src/components/kbd.css +1 -1
  122. package/src/components/list.css +6 -6
  123. package/src/components/menu.css +3 -3
  124. package/src/components/modal.css +1 -1
  125. package/src/components/pagination.css +1 -1
  126. package/src/components/progress.css +1 -1
  127. package/src/components/rating.css +2 -2
  128. package/src/components/selection.css +4 -4
  129. package/src/components/skeleton.css +1 -1
  130. package/src/components/slider.css +1 -1
  131. package/src/components/spinner.css +1 -1
  132. package/src/components/states.css +1 -1
  133. package/src/components/stepper.css +2 -2
  134. package/src/components/table.css +15 -15
  135. package/src/components/tabs.css +3 -3
  136. package/src/components/timeline.css +1 -1
  137. package/src/components/timepicker.css +1 -1
  138. package/src/components/toast.css +3 -3
  139. package/src/components/tooltip.css +1 -1
  140. package/src/components/tree.css +2 -2
  141. package/tokens/breakpoints.d.ts +2 -2
  142. package/tokens/breakpoints.mjs +3 -3
  143. package/tokens/tokens.json +1 -1
@@ -1,7 +1,7 @@
1
- // Freeday — framework-agnostic app-shell behaviour (DOM helpers, zero dependencies).
1
+ // Freeday app-shell behaviour, framework-agnostic (DOM helpers, zero dependencies).
2
2
  //
3
3
  // The focus trap, `inert` bookkeeping and focus restore shared by FdyAppShell in the Vue, React and
4
- // Blazor adapters. Kept here plain ESM with a .d.ts sidecar for the same reason as
4
+ // Blazor adapters. Kept here, plain ESM with a .d.ts sidecar, for the same reason as
5
5
  // table-model.js: three copies of a focus trap is three chances to write it differently, and the
6
6
  // one that gets it wrong strands a keyboard user with no way out of a nav panel.
7
7
  //
@@ -14,7 +14,7 @@
14
14
 
15
15
  import { breakpoints } from '../../tokens/breakpoints.mjs';
16
16
 
17
- /** The media query the shell switches at the same 721px app-shell.css uses. */
17
+ /** The media query the shell switches at, the same 721px app-shell.css uses. */
18
18
  export const NAV_QUERY = `(min-width: ${breakpoints.nav}px)`;
19
19
 
20
20
  const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]),'
@@ -36,7 +36,7 @@ export function shellParts(root) {
36
36
  /**
37
37
  * Focusable descendants, in tab order, that are actually rendered.
38
38
  * getClientRects() rather than offsetParent: an overlay sidebar is position:fixed, and a fixed
39
- * element reports no offsetParent at all filtering on that calls every nav item invisible.
39
+ * element reports no offsetParent at all, filtering on that calls every nav item invisible.
40
40
  * @param {HTMLElement} el
41
41
  * @returns {HTMLElement[]}
42
42
  */
@@ -47,7 +47,7 @@ export function focusablesIn(el) {
47
47
  /**
48
48
  * Reconcile the parts of the shell that are not classes: `inert` and `aria-expanded`.
49
49
  *
50
- * One rule, both modes the sidebar is inert whenever the nav is not visible. `width:0` (collapsed)
50
+ * One rule for both modes: the sidebar is inert whenever the nav is not visible. `width:0` (collapsed)
51
51
  * and `translateX(-100%)` (off-canvas) hide a panel from the eye and neither hides it from the
52
52
  * keyboard, so without this a nav nobody can see still swallows Tab on the way into the page.
53
53
  * The content is inert only while the nav is an open overlay.
@@ -63,7 +63,7 @@ export function applyShellState(root, state) {
63
63
  }
64
64
 
65
65
  /**
66
- * Move focus into the panel, and hand back whatever had it the caller keeps that until close.
66
+ * Move focus into the panel, and hand back whatever had it. The caller keeps that until close.
67
67
  * @param {HTMLElement} root
68
68
  * @returns {Element|null} the element that was focused before
69
69
  */
@@ -9,7 +9,7 @@ export interface FdySortState {
9
9
  dir: FdySortDir;
10
10
  }
11
11
 
12
- /** A column's comparator type how its values are ordered when sorting. */
12
+ /** A column's comparator type, how its values are ordered when sorting. */
13
13
  export type FdyColumnType = 'text' | 'number' | 'date';
14
14
 
15
15
  /** Cell alignment. Maps to `text-align` on the header and body cells. */
@@ -25,7 +25,7 @@ export interface FdyTableColumn<T> {
25
25
  /** Header label. */
26
26
  label: string;
27
27
  /**
28
- * Render the label for assistive tech only the header cell looks empty.
28
+ * Render the label for assistive tech only, the header cell looks empty.
29
29
  *
30
30
  * For a column of row CONTROLS (an edit button, a row menu), where a visible
31
31
  * heading is noise above a column of icons but the column still has to be
@@ -1,7 +1,7 @@
1
- // Freeday framework-agnostic table model (pure functions, zero dependencies).
1
+ // Freeday, framework-agnostic table model (pure functions, zero dependencies).
2
2
  //
3
3
  // The sort / filter / paginate logic shared by adapters/vue/components/FdyTable.vue and
4
- // adapters/react/components/FdyTable.tsx. Kept here plain ESM with a .d.ts sidecar so the
4
+ // adapters/react/components/FdyTable.tsx. Kept here, plain ESM with a .d.ts sidecar, so the
5
5
  // two adapters can't drift and so the logic is unit-testable under `node --test`
6
6
  // (test/table-model.test.mjs) without a framework runtime. Every function is pure: inputs are
7
7
  // never mutated (rows are sliced before sorting), so it is safe to call on each render.
@@ -34,15 +34,15 @@ function localDay(d) {
34
34
  }
35
35
 
36
36
  /**
37
- * @param {unknown} v @returns {string} ISO calendar day (yyyy-mm-dd) lexicographically comparable
37
+ * @param {unknown} v @returns {string} ISO calendar day (yyyy-mm-dd), lexicographically comparable
38
38
  *
39
39
  * Two bugs lived here, both of which let a column SORT by date correctly and
40
- * FILTER by date wrongly the worst pairing, because a working sort is what
40
+ * FILTER by date wrongly, the worst pairing, because a working sort is what
41
41
  * persuades you the header understands dates.
42
42
  *
43
43
  * 1. It sliced instead of parsing, while `toTime` (which the date SORT uses)
44
- * parses. A column whose `value` returns a formatted date the normal way
45
- * to render one gave `"18 Mar 2024"`, sliced to `"18 Mar 202"`, and
44
+ * parses. A column whose `value` returns a formatted date, the normal way
45
+ * to render one, gave `"18 Mar 2024"`, sliced to `"18 Mar 202"`, and
46
46
  * compared as text against `"2024-03-18"`: every row failed, silently.
47
47
  * 2. A `Date` went through `toISOString`, which is UTC. At UTC+7 a date picked
48
48
  * as the 18th is `2024-03-17T17:00Z`, so filtering from the 18th dropped it.
@@ -155,7 +155,7 @@ export function paginate(rows, pageIndex, pageSize) {
155
155
  return rows.slice(start, start + pageSize);
156
156
  }
157
157
 
158
- /** Distinct non-empty cell texts for a column, naturally sorted the source for an enum filter. */
158
+ /** Distinct non-empty cell texts for a column, naturally sorted, the source for an enum filter. */
159
159
  export function distinctValues(rows, column) {
160
160
  const seen = new Set();
161
161
  const out = [];
@@ -173,7 +173,7 @@ export function distinctValues(rows, column) {
173
173
  * The page to land on when the page SIZE changes: whichever page still holds the first row you were
174
174
  * already looking at.
175
175
  *
176
- * The two obvious answers are both wrong. Jumping to page 1 throws away your place on a long list
176
+ * The two obvious answers are both wrong. Jumping to page 1 throws away your place on a long list,
177
177
  * you asked to see more rows, not to start over. Keeping the same INDEX can land past the end: page
178
178
  * 5 of 5 at twenty rows is page 2 of 2 at fifty, and index 4 is nowhere. Anchoring on the first
179
179
  * visible row is the only one that always resolves, and it is what the reader expects: the row they
@@ -13,11 +13,11 @@ import {
13
13
  // drift into two different focus traps.
14
14
  //
15
15
  // ONE model: `navOpen` means "the nav is visible to the reader". Above the nav breakpoint a hidden
16
- // nav is `--nav-collapsed`, below it a visible nav is `--nav-open` the kit owns that mapping so an
16
+ // nav is `--nav-collapsed`, below it a visible nav is `--nav-open`, the kit owns that mapping so an
17
17
  // app never reasons about the viewport to answer a question about its own UI.
18
18
  //
19
19
  // Omit `navOpen` and the component keeps its own, defaulting BY VIEWPORT: a column on a wide screen,
20
- // hidden on a narrow one. That default is why the prop is optional a parent cannot express it as a
20
+ // hidden on a narrow one. That default is why the prop is optional, a parent cannot express it as a
21
21
  // single initial value before it knows the viewport.
22
22
 
23
23
  export interface FdyAppShellProps {
@@ -52,7 +52,7 @@ export function FdyAppShell(props: FdyAppShellProps): JSX.Element {
52
52
 
53
53
  /* The media listener reads these through refs and subscribes ONCE. Depending on navVisible would
54
54
  resubscribe on every toggle and re-run the handler, and its "narrowing with the nav visible
55
- hides it" rule would then fire on an ordinary open closing the panel in the same tick the
55
+ hides it" rule would then fire on an ordinary open, closing the panel in the same tick the
56
56
  reader opened it. */
57
57
  const navVisibleRef = useRef<boolean>(navVisible);
58
58
  navVisibleRef.current = navVisible;
@@ -72,7 +72,7 @@ export function FdyAppShell(props: FdyAppShellProps): JSX.Element {
72
72
  });
73
73
  if (nowOverlay && navVisibleRef.current) setVisibleRef.current(false);
74
74
  };
75
- // Mount: adopt the viewport without the hide-on-narrow side effect nothing is open yet.
75
+ // Mount: adopt the viewport without the hide-on-narrow side effect, nothing is open yet.
76
76
  setOverlay(!media.matches);
77
77
  if (!controlled) setUncontrolled(media.matches);
78
78
  media.addEventListener('change', onChange);
@@ -120,7 +120,7 @@ export function FdyAppShell(props: FdyAppShellProps): JSX.Element {
120
120
  <aside
121
121
  className="fdy-app__sidebar"
122
122
  onClick={(e): void => {
123
- // Following a link in an overlay nav means "take me there" the panel must not stay over
123
+ // Following a link in an overlay nav means "take me there", the panel must not stay over
124
124
  // the page it was just asked for. On a wide viewport the nav is a column: nothing to close.
125
125
  if (!overlay || !navVisible) return;
126
126
  const target: HTMLElement | null = e.target as HTMLElement | null;
@@ -3,7 +3,7 @@ import { useEffect, useId, useMemo, useRef, useState } from 'react';
3
3
  import { usePopover } from '../usePopover';
4
4
 
5
5
  // A controlled React port of freeday's autocomplete (src/freeday-autocomplete.js +
6
- // autocomplete.css): a WAI-ARIA APG *editable* combobox a text input filters a listbox of
6
+ // autocomplete.css): a WAI-ARIA APG *editable* combobox, a text input filters a listbox of
7
7
  // options as you type; picking one fills the input. `value` + `onChange` in place of the
8
8
  // enhancer's DOM mutation, so React owns the input. Filtering mirrors the enhancer exactly
9
9
  // (case-insensitive substring on the trimmed query); pass server-filtered `options` and it
@@ -3,7 +3,7 @@ import { useEffect, useId, useMemo, useRef, useState } from 'react';
3
3
  import { usePopover } from '../usePopover';
4
4
 
5
5
  // A controlled React port of freeday's cascade select (src/freeday-cascade.js + cascade.css):
6
- // a hierarchical drill-down picker showing one level at a time branches drill in, a back
6
+ // a hierarchical drill-down picker showing one level at a time, branches drill in, a back
7
7
  // control ascends, a leaf selects and the value is the leaf's value (the display is the full
8
8
  // path). The enhancer's data model is a hidden nested <ul>; here it is a typed tree, which is
9
9
  // what a framework app actually has. `value` + `onChange` in place of the DOM mutation.
@@ -21,7 +21,7 @@ export interface CflPage<Row> {
21
21
  }
22
22
 
23
23
  export interface FdyCflProps<Row extends Record<string, unknown>> {
24
- /** Single: `Row | null`. With `multiple`, an array `Row[] | null`, where null and [] both mean
24
+ /** Single: `Row | null`. With `multiple`, an array, `Row[] | null`, where null and [] both mean
25
25
  * nothing picked. The enhancer has had `data-fdy-cfl-multiple` all along; this is the typed
26
26
  * wrappers catching up (#019). */
27
27
  value: Row | Row[] | null;
@@ -30,7 +30,7 @@ export interface FdyCflProps<Row extends Record<string, unknown>> {
30
30
  columns: ReadonlyArray<CflColumn<Row>>;
31
31
  display: (row: Row) => string;
32
32
  rowKey: (row: Row) => string;
33
- /** Advisory only the caller's `fetchPage` owns paging; kept for API documentation. */
33
+ /** Advisory only, the caller's `fetchPage` owns paging; kept for API documentation. */
34
34
  pageSize?: number;
35
35
  /** The dialog's heading. Default 'Choose data', matching the Blazor adapter's `Title`. */
36
36
  title?: string;
@@ -81,7 +81,7 @@ export function FdyCfl<Row extends Record<string, unknown>>(props: FdyCflProps<R
81
81
  const rowId = (index: number): string => `${baseId}-row-${index}`;
82
82
 
83
83
  /* Unsetting is not "picking nothing": it must not touch the dialog, and focus must land on a
84
- control that still exists the trigger beside it, since this button disappears with the value. */
84
+ control that still exists, the trigger beside it, since this button disappears with the value. */
85
85
  const clearLabelText: string = props.clearLabel ?? 'Clear selection';
86
86
  const clearValue = (): void => {
87
87
  setPicked([]);
@@ -106,7 +106,7 @@ export function FdyCfl<Row extends Record<string, unknown>>(props: FdyCflProps<R
106
106
 
107
107
  // Out-of-order guard: every fetch takes a monotonically increasing token; a resolved or
108
108
  // rejected page is applied only if it still owns the latest token. A newer search, a
109
- // load-more, a dialog close, or an unmount all bump it this keeps a slow stale response
109
+ // load-more, a dialog close, or an unmount all bump it, this keeps a slow stale response
110
110
  // from overwriting fresh results (or a closed/unmounted component's state).
111
111
  const reqIdRef = useRef<number>(0);
112
112
  // In-memory cache keyed by `${query}::${page}`, cleared on open and on close.
@@ -117,7 +117,7 @@ export function FdyCfl<Row extends Record<string, unknown>>(props: FdyCflProps<R
117
117
 
118
118
  const isDisabled: boolean = props.disabled === true;
119
119
  const isReadonly: boolean = props.readonly === true;
120
- /* `display()` takes one row, so in multi the field states HOW MANY naming one of six would be a
120
+ /* `display()` takes one row, so in multi the field states HOW MANY, naming one of six would be a
121
121
  lie, and naming all six does not fit a control that is 22rem wide. */
122
122
  const currentRows: Row[] = Array.isArray(props.value)
123
123
  ? props.value
@@ -137,7 +137,7 @@ export function FdyCfl<Row extends Record<string, unknown>>(props: FdyCflProps<R
137
137
  ? props.display(props.value as Row)
138
138
  : '';
139
139
  // The results <table> (owner of `resultsId`) only renders in the rows branch, so gate the
140
- // search input's aria refs on rows existing otherwise they'd dangle during loading/empty/error.
140
+ // search input's aria refs on rows existing, otherwise they'd dangle during loading/empty/error.
141
141
  const hasRows: boolean = rows.length > 0;
142
142
  const controlsId: string | undefined = hasRows ? resultsId : undefined;
143
143
  const activeDescendant: string | undefined = hasRows && activeIndex >= 0 ? rowId(activeIndex) : undefined;
@@ -156,7 +156,7 @@ export function FdyCfl<Row extends Record<string, unknown>>(props: FdyCflProps<R
156
156
  try {
157
157
  const cached: CflPage<Row> | undefined = cacheRef.current.get(key);
158
158
  const res: CflPage<Row> = cached ?? (await props.fetchPage(targetQuery, targetPage));
159
- if (token !== reqIdRef.current) return; // stale a newer request has started
159
+ if (token !== reqIdRef.current) return; // stale, a newer request has started
160
160
  if (cached === undefined) cacheRef.current.set(key, res);
161
161
  const copy: Row[] = res.rows.slice(); // never mutate the caller's array
162
162
  setRows((prev: Row[]): Row[] => (append ? prev.concat(copy) : copy));
@@ -171,7 +171,7 @@ export function FdyCfl<Row extends Record<string, unknown>>(props: FdyCflProps<R
171
171
  }
172
172
  }
173
173
 
174
- // Mirrors Vue's `retry()`: only `lastPage`/`lastAppend` are frozen from the failed request
174
+ // Mirrors Vue's `retry()`: only `lastPage`/`lastAppend` are frozen from the failed request,
175
175
  // the query is re-read live so a retry after editing the search box (within the debounce
176
176
  // window) uses what's currently typed, not a stale snapshot.
177
177
  function retry(): void {
@@ -203,7 +203,7 @@ export function FdyCfl<Row extends Record<string, unknown>>(props: FdyCflProps<R
203
203
  setActiveIndex(clamped);
204
204
  }
205
205
 
206
- // Scroll the active row into view once it (re)renders React's analogue of Vue's
206
+ // Scroll the active row into view once it (re)renders. React's analogue of Vue's
207
207
  // `nextTick(() => scrollIntoView(...))`; fires for both keyboard nav and row hover, same as
208
208
  // the Vue source's `setActive`.
209
209
  useEffect((): void => {
@@ -225,7 +225,7 @@ export function FdyCfl<Row extends Record<string, unknown>>(props: FdyCflProps<R
225
225
  );
226
226
  }
227
227
 
228
- /* A click means "tick this" in multi and "this is my answer" in single the whole difference. */
228
+ /* A click means "tick this" in multi and "this is my answer" in single, the whole difference. */
229
229
  function onRowClick(row: Row): void {
230
230
  if (isMultiple) togglePick(row);
231
231
  else commit(row);
@@ -321,7 +321,7 @@ export function FdyCfl<Row extends Record<string, unknown>>(props: FdyCflProps<R
321
321
  }
322
322
 
323
323
  // Unmount safety: bump the token so any in-flight fetch's setState is dropped, and cancel a
324
- // pending debounce mirrors Vue's onBeforeUnmount plus the same token trick onClose uses.
324
+ // pending debounce, mirrors Vue's onBeforeUnmount plus the same token trick onClose uses.
325
325
  useEffect((): (() => void) => {
326
326
  return (): void => {
327
327
  reqIdRef.current++;
@@ -5,7 +5,7 @@ import { FdyDatepicker } from './FdyDatepicker';
5
5
  // two linked FdyDatepickers where the end can't precede the start (start.max = end, end.min = start).
6
6
  // `value` + `onChange` in place of Vue's `v-model`; this composes the single-date picker rather than
7
7
  // re-implementing the calendar. Note: the vanilla enhancer's cross-calendar `.in-range` day shading
8
- // is not reproduced (each picker is independent) the min/max linkage is what keeps the range valid.
8
+ // is not reproduced (each picker is independent), the min/max linkage is what keeps the range valid.
9
9
 
10
10
  export interface DateRangeValue {
11
11
  start: string | null;
@@ -19,7 +19,7 @@ export interface FdyDateRangeProps {
19
19
  max?: string;
20
20
  locale?: string;
21
21
  disabled?: boolean;
22
- /** Locked/view mode for both pickers focusable, values shown, but can't be opened or changed. */
22
+ /** Locked/view mode for both pickers, focusable, values shown, but can't be opened or changed. */
23
23
  readonly?: boolean;
24
24
  invalid?: boolean;
25
25
  describedby?: string;
@@ -55,7 +55,7 @@ export interface FdyDatepickerProps {
55
55
  ariaLabelledby?: string;
56
56
  /** Show a clear (×) button in the trigger when a date is set, so an optional date can be unset. Calls onChange('') to reset. Off by default. */
57
57
  clearable?: boolean;
58
- /** aria-label for the previous-month nav button. Default 'Previous month' override for non-English UIs (month/weekday names already follow `locale`). */
58
+ /** aria-label for the previous-month nav button. Default 'Previous month', override for non-English UIs (month/weekday names already follow `locale`). */
59
59
  /** aria-label for the title button that drills to the month grid. Default 'Choose month'. */
60
60
  chooseMonthLabel?: string;
61
61
  /** aria-labels for the year arrows shown in the month grid. Defaults 'Previous year' / 'Next year'. */
@@ -132,7 +132,7 @@ export function FdyDatepicker(props: FdyDatepickerProps): JSX.Element {
132
132
  const pendingFocusRef = useRef<string | null>(null);
133
133
 
134
134
  const [open, setOpen] = useState<boolean>(false);
135
- /* 'days' | 'months' the calendar drills one level up instead of growing furniture beside the
135
+ /* 'days' | 'months', the calendar drills one level up instead of growing furniture beside the
136
136
  title. Before this the only pointer route to another month was one click per month. */
137
137
  const [mode, setMode] = useState<'days' | 'months' | 'years'>('days');
138
138
  const [focusMonth, setFocusMonth] = useState<number>(0);
@@ -224,7 +224,7 @@ export function FdyDatepicker(props: FdyDatepickerProps): JSX.Element {
224
224
  return false;
225
225
  }
226
226
 
227
- /** A month is only unreachable when the WHOLE month falls outside min/max a range that sits
227
+ /** A month is only unreachable when the WHOLE month falls outside min/max, a range that sits
228
228
  * inside one month disables both ends while the middle is perfectly selectable. */
229
229
  function isMonthDisabled(year: number, month: number): boolean {
230
230
  const first: Date = new Date(year, month, 1);
@@ -429,7 +429,7 @@ export function FdyDatepicker(props: FdyDatepickerProps): JSX.Element {
429
429
  closePanel(true);
430
430
  }
431
431
 
432
- // Clear (reset to empty). Calls onChange('') parseISO('') is null, so the placeholder shows again.
432
+ // Clear (reset to empty). Calls onChange(''), parseISO('') is null, so the placeholder shows again.
433
433
  function clearValue(): void {
434
434
  props.onChange('');
435
435
  setOpen(false);
@@ -506,7 +506,7 @@ export function FdyDatepicker(props: FdyDatepickerProps): JSX.Element {
506
506
  }
507
507
 
508
508
  // Close when focus leaves the control entirely (e.g. Shift+Tab off the trigger).
509
- /* A null relatedTarget means focus fell to <body> which is what happens when the element the
509
+ /* A null relatedTarget means focus fell to <body>, which is what happens when the element the
510
510
  user just pressed is REMOVED by the click it triggered (drilling into the month grid replaces
511
511
  the grid, and with it the cell that had focus). That is not focus leaving the control, and
512
512
  closing on it made the panel vanish mid-navigation. A pointer that really lands outside is
@@ -2,7 +2,7 @@ import type { JSX, ReactNode } from 'react';
2
2
  import { useEffect, useId, useRef } from 'react';
3
3
 
4
4
  // A controlled React wrapper over freeday's `.fdy-drawer` native <dialog> side panel
5
- // (src/components/drawer.css). React port of adapters/vue/components/FdyDrawer.vue the same
5
+ // (src/components/drawer.css). React port of adapters/vue/components/FdyDrawer.vue, the same
6
6
  // controlled glue as FdyModal (guarded showModal()/close(), onCancel + preventDefault so Esc routes
7
7
  // through app state, backdrop-click via `event.target === dialogEl`) applied to a drawer that anchors
8
8
  // left (default) or right. Native <dialog> supplies the focus trap, focus restore, top-layer stacking
@@ -3,7 +3,7 @@ import { useEffect, useId, useRef } from 'react';
3
3
 
4
4
  // A controlled React wrapper over freeday's `.fdy-modal` native <dialog> (src/components/modal.css).
5
5
  // React port of adapters/vue/components/FdyModal.vue: reconciles the reactive `open` boolean with a
6
- // DOM element whose open/close is a method call showModal()/close() guarded against the
6
+ // DOM element whose open/close is a method call, showModal()/close() guarded against the
7
7
  // already-open/closed cases (showModal() on an open dialog throws), onCancel + preventDefault so Esc
8
8
  // routes through app state instead of closing the DOM behind its back, and backdrop-click detection
9
9
  // via `event.target === dialogEl`. Native <dialog> already gives the focus trap, focus restore,
@@ -44,7 +44,7 @@ export function FdyModal(props: FdyModalProps): JSX.Element {
44
44
  if (dismissible) props.onClose();
45
45
  }}
46
46
  onClick={(e): void => {
47
- // The ::backdrop is not a separate element target === the dialog box means a backdrop click.
47
+ // The ::backdrop is not a separate element, target === the dialog box means a backdrop click.
48
48
  if (dismissible && e.target === dialogRef.current) props.onClose();
49
49
  }}
50
50
  >
@@ -45,14 +45,14 @@ export interface FdyTableProps<Row extends object> {
45
45
  /** Client-side page size when `page` is absent; 0/undefined = render all rows (no pager). */
46
46
  pageSize?: number;
47
47
  /**
48
- * Controlled client-side page index (0-based). Provide it with `pageSize`, without `page` to
48
+ * Controlled client-side page index (0-based). Provide it, with `pageSize`, without `page`, to
49
49
  * own the page while the table keeps doing filter/sort/paginate. This is what lets an EXTERNAL
50
50
  * pager drive the table: a responsive screen that hides `.fdy-datatable` below `md` and renders a
51
51
  * card list from `onProcess` can render one pager for both breakpoints and point it here.
52
52
  * Omit for the internal index (unchanged default).
53
53
  */
54
54
  /** Withhold the table's own footer (pager + range) so the screen can render one. Server mode had
55
- * no way to do this: the app owns the page there ANYWAY, and was still handed a second control
55
+ * no way to do this: the app owns the page there ANYWAY, and was still handed a second control,
56
56
  * a responsive list that shows a table at one breakpoint and cards at another ended up with the
57
57
  * kit's pager stacked under its own. Client mode's counterpart is `pageIndex`. Default true. */
58
58
  pager?: boolean;
@@ -61,13 +61,13 @@ export interface FdyTableProps<Row extends object> {
61
61
  * (unchanged default). Every back office has one, and a table that renders two thirds of its own
62
62
  * footer forces the app to rebuild all three to add the last (#008).
63
63
  *
64
- * Server mode reports the pick through `onPageChange` same callback as a page click, with a new
64
+ * Server mode reports the pick through `onPageChange`, same callback as a page click, with a new
65
65
  * `size`. Client mode keeps it internally and also calls `onPageSizeChange`, so the control works
66
66
  * with nothing wired and can still be persisted.
67
67
  */
68
68
  pageSizes?: readonly number[];
69
69
  /** Client mode with `pageSizes`: the reader picked a new rows-per-page. The table has already
70
- * applied it this is for a caller that wants to persist the choice. */
70
+ * applied it, this is for a caller that wants to persist the choice. */
71
71
  onPageSizeChange?: (size: number) => void;
72
72
  pageIndex?: number;
73
73
  /** Client mode with `pageIndex` provided: the table asks for a new 0-based index (pager click, a
@@ -86,7 +86,7 @@ export interface FdyTableProps<Row extends object> {
86
86
  rowClass?: (row: Row) => string | undefined;
87
87
  /** A row was activated (click, or Enter/Space while the row itself is focused). */
88
88
  onRowActivate?: (row: Row) => void;
89
- /** Called with the processed page of rows (after filter/sort/paginate) plus the total row count
89
+ /** Called with the processed page of rows (after filter/sort/paginate) plus the total row count,
90
90
  * in BOTH modes, whenever they change. Lets a consumer render the SAME processed set elsewhere
91
91
  * (a `< md` card list, a "selected" summary, export-to-CSV) without re-deriving the pipeline. */
92
92
  onProcess?: (result: { rows: Row[]; total: number }) => void;
@@ -105,7 +105,7 @@ export function FdyTable<Row extends object>(props: FdyTableProps<Row>): JSX.Ele
105
105
  const [internalFilters, setInternalFilters] = useState<FdyFilterMap>({});
106
106
  const [internalPageIndex, setInternalPageIndex] = useState<number>(0);
107
107
  /* Client-mode rows-per-page. `pageSize` is a plain prop with no callback, so a footer control that
108
- * only reported would do nothing in the app that wired nothing the table applies the pick itself
108
+ * only reported would do nothing in the app that wired nothing, the table applies the pick itself
109
109
  * and reports it. `prevPageSize` is React's adjust-state-during-render idiom: an explicit change to
110
110
  * the prop wins back, because the parent said something newer. */
111
111
  const [internalPageSize, setInternalPageSize] = useState<number | null>(null);
@@ -238,7 +238,7 @@ export function FdyTable<Row extends object>(props: FdyTableProps<Row>): JSX.Ele
238
238
  .join(' ');
239
239
  return cls === '' ? undefined : cls;
240
240
  }
241
- // Enter/Space activate only when the row itself is focused a control inside a cell keeps its own
241
+ // Enter/Space activate only when the row itself is focused, a control inside a cell keeps its own
242
242
  // event (the `event.target !== event.currentTarget` guard). Click relies on inner controls calling
243
243
  // stopPropagation, matching the pattern consumers hand-roll today.
244
244
  function onRowKeydown(e: React.KeyboardEvent<HTMLTableRowElement>, row: Row): void {
@@ -7,7 +7,7 @@ import type { FdyColumnFilter, FdyColumnFilterType } from '../../core/table-mode
7
7
  // Internal to FdyTable: one column's header funnel button + its type-aware filter popover
8
8
  // (text / enum / number / date) over freeday's `.fdy-table__filterbtn` + `.fdy-filter*` classes.
9
9
  // React port of adapters/vue/components/FdyTableFilter.vue. Reuses usePopover so the panel escapes
10
- // the table's `overflow:hidden` via the top layer. Purely controlled renders the current
10
+ // the table's `overflow:hidden` via the top layer. Purely controlled, renders the current
11
11
  // `filter`, emits the next one (or null to clear); the parent owns where it goes. Not exported.
12
12
 
13
13
  export interface FdyTableFilterProps {
@@ -8,19 +8,19 @@ import { FdyCombo } from './FdyCombo';
8
8
  * The band under a table: what you are looking at, how much of it you see, where you are.
9
9
  *
10
10
  * `FdyTable` renders this itself, so most screens never import it. It is exported for the one shape
11
- * that cannot use the table's own a RESPONSIVE list, where a `.fdy-datatable` at `lg` and a
11
+ * that cannot use the table's own, a RESPONSIVE list, where a `.fdy-datatable` at `lg` and a
12
12
  * `.fdy-list` below it are two renderings of ONE page of rows. A footer inside the table is inside
13
13
  * the half that is hidden on a phone, so those screens need to render it once, outside both
14
14
  * (improvement notes #005 and #008, from IDU_EMATE_APPL_WEB).
15
15
  *
16
- * It owns nothing. `page` in, `onPageChange` out the same contract as the table's server mode.
16
+ * It owns nothing. `page` in, `onPageChange` out, the same contract as the table's server mode.
17
17
  */
18
18
  export interface FdyTableFooterProps {
19
19
  /** The page being shown. `size` drives the range AND the rows-per-page control's value. */
20
20
  page: FdyPageState;
21
21
  onPageChange?: (page: FdyPageState) => void;
22
22
  /**
23
- * Offer a rows-per-page control. Omit for none the footer is then range + pager, exactly as
23
+ * Offer a rows-per-page control. Omit for none; the footer is then range + pager, exactly as
24
24
  * before. Picking a size calls `onPageChange` with the new `size` and the index that still holds
25
25
  * the row you were looking at.
26
26
  */
@@ -29,7 +29,7 @@ export interface FdyTableFooterProps {
29
29
 
30
30
  export function FdyTableFooter(props: FdyTableFooterProps): JSX.Element | null {
31
31
  const { index, size, total } = props.page;
32
- /* The combo is labelled by the visible word beside it a <label htmlFor> cannot reach inside a
32
+ /* The combo is labelled by the visible word beside it, a <label htmlFor> cannot reach inside a
33
33
  * component, and an aria-label would leave that word attached to nothing. */
34
34
  const sizeLabelId: string = useId();
35
35
 
@@ -41,7 +41,7 @@ export function FdyTableFooter(props: FdyTableFooterProps): JSX.Element | null {
41
41
 
42
42
  const hasPager: boolean = size > 0 && totalPages > 1;
43
43
  const sizes: readonly number[] = props.pageSizes ?? [];
44
- /* One page and no size control means there is nothing here to say the table has always withheld
44
+ /* One page and no size control means there is nothing here to say, the table has always withheld
45
45
  * the whole band in that case, and this is where that decision now lives. */
46
46
  if (!hasPager && sizes.length === 0) return null;
47
47
 
@@ -36,7 +36,7 @@ export { FdyCascade, type FdyCascadeProps, type CascadeNode } from './components
36
36
  export { FdyCfl, type FdyCflProps, type CflColumn, type CflPage } from './components/FdyCfl';
37
37
  export { FdyChart, type FdyChartProps, type FdyChartSeries } from './components/FdyChart';
38
38
  export { FdyTable, type FdyTableProps } from './components/FdyTable';
39
- /** The table's own footer, standalone for a responsive list whose table is hidden at some
39
+ /** The table's own footer, standalone, for a responsive list whose table is hidden at some
40
40
  * breakpoints and so cannot host it. */
41
41
  export { FdyTableFooter, type FdyTableFooterProps } from './components/FdyTableFooter';
42
42
  export { FdyModal, type FdyModalProps } from './components/FdyModal';
@@ -1,4 +1,4 @@
1
- /* Freeday React adapter (thin). The enhancers stay the source of truth; this
1
+ /* Freeday. React adapter (thin). The enhancers stay the source of truth; this
2
2
  * hook re-runs every registered window.Freeday*.initAll() over a subtree after
3
3
  * React commits, so [data-fdy-*] markup rendered by React gets enhanced (and
4
4
  * re-enhanced after updates). Enhancers are idempotent, so the repeated calls
@@ -9,12 +9,12 @@
9
9
  * useFreeday(root); // scopes init to this component's subtree
10
10
  * // <div ref={root}> ...[data-fdy-*] markup... </div>
11
11
  *
12
- * The ref may also sit ON the widget itself <div ref={root} data-fdy-menu> which is what you
12
+ * The ref may also sit ON the widget itself, <div ref={root} data-fdy-menu>, which is what you
13
13
  * write when the component's root element IS the widget. initAll() matches its own root as
14
14
  * well as descendants, so both shapes work. (Before 1.23.0 only the wrapping shape did, and
15
15
  * the other failed silently: querySelectorAll never matches its root.)
16
16
  *
17
- * Events are native bubbling CustomEvents add listeners on the root (they
17
+ * Events are native bubbling CustomEvents, add listeners on the root (they
18
18
  * bubble) and read event.detail. See index.d.ts for the detail types.
19
19
  */
20
20
  import { useEffect, useCallback } from 'react';
@@ -14,13 +14,13 @@ import {
14
14
  // Blazor bridge cannot drift into three different focus traps.
15
15
  //
16
16
  // ONE model: `navOpen` means "the nav is visible to the reader". The two state classes are the
17
- // kit's business above the nav breakpoint a hidden nav is `--nav-collapsed`, below it a visible
18
- // nav is `--nav-open` so an app never reasons about the viewport to answer a question about its
17
+ // kit's business, above the nav breakpoint a hidden nav is `--nav-collapsed`, below it a visible
18
+ // nav is `--nav-open`, so an app never reasons about the viewport to answer a question about its
19
19
  // own UI.
20
20
  //
21
21
  // Leave `navOpen` unbound and the component keeps its own: visible on a wide viewport, hidden on a
22
22
  // narrow one, which is the right default and one an app cannot express in a single initial value.
23
- // Bind it (`v-model:navOpen`) when the app wants to drive it a menu item, a persisted preference.
23
+ // Bind it (`v-model:navOpen`) when the app wants to drive it, a menu item, a persisted preference.
24
24
 
25
25
  const props = withDefaults(defineProps<{
26
26
  navOpen?: boolean;
@@ -78,7 +78,7 @@ function onBackdrop(): void {
78
78
  if (overlay.value && navVisible.value) setVisible(false);
79
79
  }
80
80
 
81
- /* Following a link in an overlay nav means "take me there" the panel must not stay over the page
81
+ /* Following a link in an overlay nav means "take me there", the panel must not stay over the page
82
82
  it was just asked for. On a wide viewport the nav is a column and clicking it changes nothing. */
83
83
  function onSidebarClick(e: MouseEvent): void {
84
84
  if (!overlay.value || !navVisible.value) return;
@@ -99,7 +99,7 @@ function onKeydown(e: KeyboardEvent): void {
99
99
 
100
100
  /* Both directions matter, and they are not symmetrical.
101
101
  Narrowing with the nav visible would drop an overlay panel over a page nobody asked to leave, so
102
- the nav is hidden. Widening is harmless a visible nav simply becomes the column again and the
102
+ the nav is hidden. Widening is harmless, a visible nav simply becomes the column again, and the
103
103
  watcher clears the `inert` the overlay had put on the content. */
104
104
  function onMediaChange(): void {
105
105
  const nowOverlay: boolean = media !== null && !media.matches;
@@ -3,7 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch, type
3
3
  import { usePopover } from '../usePopover';
4
4
 
5
5
  // A controlled Vue port of freeday's autocomplete (src/freeday-autocomplete.js +
6
- // autocomplete.css): a WAI-ARIA APG *editable* combobox a text input filters a listbox of
6
+ // autocomplete.css): a WAI-ARIA APG *editable* combobox, a text input filters a listbox of
7
7
  // options as you type; picking one fills the input. A real `v-model` in place of the enhancer's
8
8
  // DOM mutation, so Vue owns the input. Filtering mirrors the enhancer exactly (case-insensitive
9
9
  // substring on the trimmed query); pass server-filtered `options` and it stays a no-op re-filter.
@@ -25,7 +25,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch, type
25
25
  import { usePopover } from '../usePopover';
26
26
 
27
27
  // A controlled Vue port of freeday's cascade select (src/freeday-cascade.js + cascade.css):
28
- // a hierarchical drill-down picker showing one level at a time branches drill in, a back
28
+ // a hierarchical drill-down picker showing one level at a time, branches drill in, a back
29
29
  // control ascends, a leaf selects and the value is the leaf's value (the display is the full
30
30
  // path). The enhancer's data model is a hidden nested <ul>; here it is a typed tree, which is
31
31
  // what a framework app actually has. A real `v-model` in place of the DOM mutation.