@cahyo-dimas/freeday 1.48.0 → 1.49.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.
- package/CHANGELOG.md +38 -0
- package/adapters/blazor/FdyTable.razor +2 -2
- package/adapters/blazor/TableTypes.cs +7 -0
- package/adapters/core/table-model.d.ts +10 -0
- package/adapters/core/table-model.js +30 -3
- package/adapters/react/components/FdyTable.tsx +4 -2
- package/adapters/vue/components/FdyTable.vue +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,44 @@
|
|
|
3
3
|
Semua perubahan penting dicatat di sini. Format longgar mengikuti
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); tiap versi = git tag.
|
|
5
5
|
|
|
6
|
+
## [1.49.0] — 2026-08-20
|
|
7
|
+
### Added
|
|
8
|
+
- **`FdyTableColumn.labelHidden`** (#026). A column of row CONTROLS — an edit button, a row menu —
|
|
9
|
+
could be named or quiet, not both: `label` renders as bare text in the `<th>`, so a designer who
|
|
10
|
+
does not want a word above a column of icons was left with an empty header, which assistive tech
|
|
11
|
+
announces as nothing. With `labelHidden: true` the label renders inside `.fdy-visually-hidden`:
|
|
12
|
+
the cell looks empty and the column is still named. The label keeps naming the column's filter
|
|
13
|
+
popover and sort button, so it stays meaningful either way.
|
|
14
|
+
Contract and ALL THREE typed adapters — Vue, React and Blazor. Each renders its own header, so
|
|
15
|
+
each spends the flag itself; Blazor takes `LabelHidden` on `FdyTableColumn<TRow>`.
|
|
16
|
+
### Added — guards
|
|
17
|
+
- **The column contract now has to reach all three adapters** (#026). Vue and React consume
|
|
18
|
+
`table-model.d.ts` and TypeScript keeps them honest, but Blazor RE-DECLARES the column in C# — a
|
|
19
|
+
hand copy, and the surface that can fall behind silently. A test asserts every contract property
|
|
20
|
+
exists on Blazor's `FdyTableColumn<TRow>`; coverage, not equality, so it needs no exemption list.
|
|
21
|
+
- **A hidden label is clipped, not dropped** — asserted in a real browser for both JS adapters
|
|
22
|
+
(`browser/adapter.mjs`), because the claim is about computed geometry: a span carrying the class
|
|
23
|
+
with no CSS behind it passes any string match and still prints the word.
|
|
24
|
+
|
|
25
|
+
## [1.48.1] — 2026-08-20
|
|
26
|
+
### Fixed
|
|
27
|
+
- **A date column sorted correctly and filtered nothing** (#025). `FdyTable`'s client-mode date
|
|
28
|
+
filter read a cell with `dateOnly`, which **sliced** the string, while its date SORT reads the same
|
|
29
|
+
cell with `toTime`, which **parses**. A date column normally renders a formatted date — that is
|
|
30
|
+
what `value` is for — so `"18 Mar 2024"` sliced to `"18 Mar 202"` and compared as text against an
|
|
31
|
+
ISO bound: every row failed, silently. The working sort is what made the broken filter look
|
|
32
|
+
trustworthy.
|
|
33
|
+
- **A `Date` cell was read as its UTC day, not the reader's** (#025). `toISOString()` east of
|
|
34
|
+
Greenwich turns local midnight into the previous day, so at UTC+7 filtering from the 18th dropped
|
|
35
|
+
a row dated the 18th.
|
|
36
|
+
- Text that is not a date now yields `''` and is EXCLUDED by an active date filter, rather than
|
|
37
|
+
slicing to something that compares as less than every bound and matching everything.
|
|
38
|
+
### Added — guards
|
|
39
|
+
- Three tests in `test/table-model.test.mjs`, each verified to fail against the old body: a
|
|
40
|
+
formatted `value`, a `Date` cell at a positive UTC offset, and unparseable text. The existing date
|
|
41
|
+
test only ever filtered a column with no accessor holding an already-ISO string, which is the one
|
|
42
|
+
shape neither bug can affect.
|
|
43
|
+
|
|
6
44
|
## [1.48.0] — 2026-08-20
|
|
7
45
|
### Reverted
|
|
8
46
|
- **1.47.0's readonly focus ring (#024) is withdrawn.** The report was measured wrong. The control in
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
<th scope="col" style="@AlignStyle(col)" aria-sort="@AriaSortOf(col)">
|
|
27
27
|
@if (col.Sortable)
|
|
28
28
|
{
|
|
29
|
-
<button type="button" class="fdy-table__sortbtn" @onclick="() => OnSortClick(col)">@col.Label</button>
|
|
29
|
+
<button type="button" class="fdy-table__sortbtn" @onclick="() => OnSortClick(col)"><span class="@(col.LabelHidden ? "fdy-visually-hidden" : null)">@col.Label</span></button>
|
|
30
30
|
}
|
|
31
31
|
else
|
|
32
32
|
{
|
|
33
|
-
@col.Label
|
|
33
|
+
<span class="@(col.LabelHidden ? "fdy-visually-hidden" : null)">@col.Label</span>
|
|
34
34
|
}
|
|
35
35
|
@if (col.Filter is { } ft)
|
|
36
36
|
{
|
|
@@ -47,6 +47,13 @@ public sealed class FdyTableColumn<TRow>
|
|
|
47
47
|
/// <summary>Header label.</summary>
|
|
48
48
|
public required string Label { get; init; }
|
|
49
49
|
|
|
50
|
+
/// <summary>Render the label for assistive tech only — the header cell looks empty.
|
|
51
|
+
/// For a column of row CONTROLS (an edit button, a row menu), where a visible heading is noise
|
|
52
|
+
/// above a column of icons but the column still has to be named: a <c>th</c> with no text is
|
|
53
|
+
/// announced as nothing, and a reader tabbing the header row cannot tell what it is. The label
|
|
54
|
+
/// still names the column's filter popover and its sort button, so it must stay meaningful.</summary>
|
|
55
|
+
public bool LabelHidden { get; init; }
|
|
56
|
+
|
|
50
57
|
/// <summary>Show a sort toggle in the header.</summary>
|
|
51
58
|
public bool Sortable { get; init; }
|
|
52
59
|
|
|
@@ -24,6 +24,16 @@ export interface FdyTableColumn<T> {
|
|
|
24
24
|
key: string;
|
|
25
25
|
/** Header label. */
|
|
26
26
|
label: string;
|
|
27
|
+
/**
|
|
28
|
+
* Render the label for assistive tech only — the header cell looks empty.
|
|
29
|
+
*
|
|
30
|
+
* For a column of row CONTROLS (an edit button, a row menu), where a visible
|
|
31
|
+
* heading is noise above a column of icons but the column still has to be
|
|
32
|
+
* named: a `<th>` with no text is announced as nothing, and a reader tabbing
|
|
33
|
+
* the header row cannot tell what it is. The label still names the column's
|
|
34
|
+
* filter popover and its sort button, so it must stay meaningful.
|
|
35
|
+
*/
|
|
36
|
+
labelHidden?: boolean;
|
|
27
37
|
/** Show a sort toggle in the header. */
|
|
28
38
|
sortable?: boolean;
|
|
29
39
|
/** Offer a column filter of this type. */
|
|
@@ -23,10 +23,37 @@ function toTime(v) {
|
|
|
23
23
|
return Number.isNaN(t) ? 0 : t;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/**
|
|
26
|
+
/** Already an ISO calendar day, so it needs no parsing. */
|
|
27
|
+
const ISO_DAY = /^\d{4}-\d{2}-\d{2}/;
|
|
28
|
+
|
|
29
|
+
/** @param {Date} d @returns {string} the LOCAL calendar day, never UTC's */
|
|
30
|
+
function localDay(d) {
|
|
31
|
+
const month = d.getMonth() + 1;
|
|
32
|
+
const day = d.getDate();
|
|
33
|
+
return `${d.getFullYear()}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {unknown} v @returns {string} ISO calendar day (yyyy-mm-dd) — lexicographically comparable
|
|
38
|
+
*
|
|
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
|
|
41
|
+
* persuades you the header understands dates.
|
|
42
|
+
*
|
|
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
|
|
46
|
+
* compared as text against `"2024-03-18"`: every row failed, silently.
|
|
47
|
+
* 2. A `Date` went through `toISOString`, which is UTC. At UTC+7 a date picked
|
|
48
|
+
* as the 18th is `2024-03-17T17:00Z`, so filtering from the 18th dropped it.
|
|
49
|
+
* The calendar day a person means is their own, not Greenwich's.
|
|
50
|
+
*/
|
|
27
51
|
function dateOnly(v) {
|
|
28
|
-
if (v instanceof Date) return Number.isNaN(v.getTime()) ? '' : v
|
|
29
|
-
|
|
52
|
+
if (v instanceof Date) return Number.isNaN(v.getTime()) ? '' : localDay(v);
|
|
53
|
+
const s = String(v == null ? '' : v);
|
|
54
|
+
if (ISO_DAY.test(s)) return s.slice(0, 10);
|
|
55
|
+
const t = Date.parse(s);
|
|
56
|
+
return Number.isNaN(t) ? '' : localDay(new Date(t));
|
|
30
57
|
}
|
|
31
58
|
|
|
32
59
|
/** Raw cell value for a column: the column accessor if present, else row[key]. */
|
|
@@ -271,9 +271,11 @@ export function FdyTable<Row extends object>(props: FdyTableProps<Row>): JSX.Ele
|
|
|
271
271
|
{props.columns.map((col: FdyTableColumn<Row>): JSX.Element => (
|
|
272
272
|
<th key={col.key} scope="col" style={alignStyle(col)} aria-sort={ariaSortOf(col)}>
|
|
273
273
|
{col.sortable ? (
|
|
274
|
-
<button type="button" className="fdy-table__sortbtn" onClick={(): void => onSort(col)}>
|
|
274
|
+
<button type="button" className="fdy-table__sortbtn" onClick={(): void => onSort(col)}>
|
|
275
|
+
<span className={col.labelHidden ? 'fdy-visually-hidden' : undefined}>{col.label}</span>
|
|
276
|
+
</button>
|
|
275
277
|
) : (
|
|
276
|
-
col.label
|
|
278
|
+
<span className={col.labelHidden ? 'fdy-visually-hidden' : undefined}>{col.label}</span>
|
|
277
279
|
)}
|
|
278
280
|
{col.filter && (
|
|
279
281
|
<FdyTableFilter
|
|
@@ -280,8 +280,8 @@ function isExpanded(row: Row): boolean {
|
|
|
280
280
|
type="button"
|
|
281
281
|
class="fdy-table__sortbtn"
|
|
282
282
|
@click="onSort(col)"
|
|
283
|
-
>{{ col.label }}</button>
|
|
284
|
-
<
|
|
283
|
+
><span :class="col.labelHidden ? 'fdy-visually-hidden' : undefined">{{ col.label }}</span></button>
|
|
284
|
+
<span v-else :class="col.labelHidden ? 'fdy-visually-hidden' : undefined">{{ col.label }}</span>
|
|
285
285
|
<FdyTableFilter
|
|
286
286
|
v-if="col.filter"
|
|
287
287
|
:label="col.label"
|