@cahyo-dimas/freeday 1.48.1 → 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 CHANGED
@@ -3,6 +3,25 @@
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
+
6
25
  ## [1.48.1] — 2026-08-20
7
26
  ### Fixed
8
27
  - **A date column sorted correctly and filtered nothing** (#025). `FdyTable`'s client-mode date
@@ -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. */
@@ -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)}>{col.label}</button>
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
- <template v-else>{{ col.label }}</template>
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"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cahyo-dimas/freeday",
3
- "version": "1.48.1",
3
+ "version": "1.49.0",
4
4
  "description": "Freeday — token-driven, framework-agnostic UI KIT (design source-of-truth).",
5
5
  "type": "module",
6
6
  "license": "MIT",