@cahyo-dimas/freeday 1.51.0 → 1.52.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,66 @@
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.52.0] — 2026-08-24
7
+ ### Fixed
8
+ - **A chart's legend, bar values and donut centre were exposed to assistive tech after all** (#047).
9
+ `COMPONENTS.md` explained the a11y contract with the ARIA spec — `role="img"` is *Children
10
+ Presentational*, therefore "rendered bar values, axis ticks and legends are not exposed". Measured
11
+ against Chrome's accessibility tree, that is not what happens: a donut kept `list` → three
12
+ `listitem` → `StaticText "posted — 76%"` and its centre total `1687`, the legacy `.fdy-bars` kept
13
+ every value and label (`12`, `Jan`, `30`, `Feb`…), and a two-series cartesian kept its legend — all
14
+ live, none ignored. What actually protected the advice was only that AT treats a *named*
15
+ `role="img"` as a leaf and does not descend; the docs presented a habit as a guarantee. Axis ticks
16
+ were the one true claim, and for an unstated reason: the renderer already sets `aria-hidden` on the
17
+ `<svg>` itself.
18
+ `ensureImg()` now marks the rendered children `aria-hidden="true"`, so the author's `aria-label`
19
+ really is the entire text alternative and cannot be double-announced by an AT that does descend.
20
+ Nothing is removed and nothing moves — the legend and the centre are still in the DOM and still
21
+ painted.
22
+ - **The hiding requires a name.** A chart with no `aria-label`/`aria-labelledby` keeps its contents
23
+ exposed: pruning the subtree of an unlabelled chart would leave an image with no name *and* no
24
+ text, which is worse than the leak and harder for the author to notice. An element whose author
25
+ set some other `role` is left alone entirely.
26
+ ### Docs
27
+ - The Charts a11y bullet now describes the kit instead of the spec: what is hidden, that `role="img"`
28
+ alone would not have hidden it, and what happens when the label is missing.
29
+ ### Added — guards
30
+ - `browser/chart-a11y.mjs` (5 tests) over donut, legacy bars and cartesian: the accessible subtree
31
+ exposes no text of its own, the author label survives as the name, the legend is still rendered and
32
+ visible (hidden, not deleted), and an unlabelled chart still reads. The first three were verified to
33
+ fail against 1.51.1.
34
+ - `axSubtree()` in the browser harness — every AX node under an element, ignored ones included. The
35
+ DOM cannot answer this class of question: `aria-hidden` changes no markup, so a `querySelector`
36
+ assert passes identically before and after the fix. Same reason the 1.51.0 scaling bug needed a
37
+ real measurement rather than a source read.
38
+
39
+ ## [1.51.1] — 2026-08-24
40
+ ### Docs
41
+ - **`COMPONENTS.md` never said whether two overlays may be open at once** (#046). Modal and Drawer
42
+ were documented in isolation, so "can a confirm modal open over an open drawer?" had no answer in
43
+ the file — and the built CSS does not answer it either: the only overlay `z-index` in the bundle
44
+ belongs to the toast region. Both entries now state the rule, because both components are
45
+ `<dialog>` opened with `showModal()` and therefore enter the **top layer**, where paint order is
46
+ the order they were opened: the later modal is above the drawer with no `z-index` from either,
47
+ Escape closes the topmost first (so cancelling returns to the still-open drawer), and closing them
48
+ out of order leaves the survivor painted and dismissible.
49
+ - **The one caveat is the browser's, and it is now written down**: open each overlay from the
50
+ gesture that asked for it. A `showModal()` with no user activation — from a timer, or after an
51
+ `await` that outlived the click — has its close watcher GROUPED with the dialog below it, and a
52
+ single Escape then closes both. Measured, not assumed: two untrusted `.click()` opens, one Escape,
53
+ both dialogs shut.
54
+ ### Added — guards
55
+ - `browser/overlay-stack.mjs` (3 tests) proves that paragraph instead of restating it — the #041
56
+ lesson applied to a documentation change. Paint order is asserted in **pixels** at the overlap of
57
+ the two, because a modal dialog makes the rest of the document inert and `elementFromPoint` will
58
+ name the dialog whatever is really on top; the overlays are opened with **trusted** clicks, or the
59
+ close-watcher grouping above would fail the Escape assertion for a reason that is not the kit's.
60
+ - **`pressKey` in the browser harness sent every key but Tab with virtual-key code 0** — delivered
61
+ to the page, but not acted on by the browser: Escape reached a `keydown` listener yet never closed
62
+ a native `<dialog>`. A dismissal guard written with it would have passed for the wrong reason and
63
+ reported a broken kit as fine. It now carries the real `windowsVirtualKeyCode` /
64
+ `nativeVirtualKeyCode` for the keys the suite presses.
65
+
6
66
  ## [1.51.0] — 2026-08-21
7
67
  ### Fixed
8
68
  - **A cartesian chart's axis text and dots grew with the container** (#042). `renderCartesian` built
package/COMPONENTS.md CHANGED
@@ -868,9 +868,14 @@ Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`
868
868
  many x-labels fit. Set the token rather than `font-size` on `__tick`/`__xlabel`, or the gutter and
869
869
  the autoskip will still be computed from the old size.
870
870
  - A11y: every chart is `role="img"` + an **author-supplied** `aria-label`, and that label is the
871
- *entire* text alternative — write one that carries the numbers, not just the shape. `role="img"`
872
- makes descendants presentational, so rendered bar values, axis ticks and legends are not exposed
873
- either; any `<table>`/text placed inside the element is **pre-render only** and is replaced on
871
+ *entire* text alternative — write one that carries the numbers, not just the shape. The renderer
872
+ marks everything it draws `aria-hidden="true"` legend, bar values and labels, donut centre, the
873
+ SVG so nothing inside the chart competes with your label or repeats it. `role="img"` alone would
874
+ **not** have done that: it is Children Presentational per ARIA, but browsers keep the subtree in
875
+ the accessibility tree anyway, so the kit hides it rather than relying on the spec. That hiding
876
+ needs a name to work from — a chart with **no** `aria-label`/`aria-labelledby` keeps its contents
877
+ exposed, because an image with neither a name nor any text is worse than one that leaks its
878
+ legend. Any `<table>`/text placed inside the element is **pre-render only** and is replaced on
874
879
  render. Good: `aria-label="Monthly posting trend: Posted 62% → 88%, Draft 24% → 16%"`.
875
880
  - Internal parts (set by JS, useful for overrides): `.fdy-chart__legend` (+`--row`) `__swatch`
876
881
  `__tip`; `.fdy-bars__col` `__bar` `__track` `__label` `__val`; `.fdy-chart-xy__plot` `__grid`
@@ -1049,6 +1054,18 @@ painted on top of a modal, because it will tell you the dialog is, even when it
1049
1054
  `__close`. The body scrolls; the footer never clips. `aria-labelledby` → the title's `id`;
1050
1055
  `data-close` on any button that should close it.
1051
1056
 
1057
+ **Stacking two overlays is supported.** Modal and drawer are both `<dialog>` opened with
1058
+ `showModal()`, so both enter the top layer and paint in the order they were opened — a confirm
1059
+ modal opened from inside an open drawer paints **above** it, and neither needs a `z-index` for
1060
+ that. Escape closes the topmost first, so cancelling returns to the still-open drawer; a second
1061
+ Escape then closes the drawer. Closing them out of order is fine too: closing the drawer while the
1062
+ modal is up leaves the modal open, painted and dismissible.
1063
+
1064
+ One caveat, and it is the browser's rather than the kit's: **open each overlay from the gesture
1065
+ that asked for it.** A `showModal()` that runs with no user activation — from a timer, or after an
1066
+ `await` that outlived the click — has its close watcher grouped with the dialog below it, and then
1067
+ a single Escape closes both at once instead of the topmost only.
1068
+
1052
1069
  ## Drawer — `.fdy-drawer`
1053
1070
  > **Typed wrapper: `<FdyDrawer>`** — Vue (`:open` + `@close`) · React (`open` + `onClose`) · Blazor (`@bind-Open`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
1054
1071
 
@@ -1056,6 +1073,11 @@ Temporary side panel on native `<dialog>` — left by default, `--right` to flip
1056
1073
  `__title` `__body` `__footer` `__close`. Open it from any
1057
1074
  `<button data-fdy-drawer="<dialog id>">`. Needs `freeday-drawer.js`.
1058
1075
 
1076
+ Opened with `showModal()`, so it is in the **top layer** on the same terms as the modal — including
1077
+ opening a modal over an open drawer, which is supported and described under
1078
+ [Modal → *Stacking two overlays*](#modal--fdy-modal). It slides in on `transform` and never animates
1079
+ opacity: wait on its position, not on `opacity`, if you need to know when it has arrived.
1080
+
1059
1081
  ## Accordion — `.fdy-accordion`
1060
1082
  Native `<details>`, zero JS: `.fdy-accordion__item` on each `<details>`, content in
1061
1083
  `.fdy-accordion__panel`, the trigger is the `<summary>`.
package/README.id.md CHANGED
@@ -5,7 +5,7 @@
5
5
  > **Lebih banyak _free day_ buat dev — UI kit-nya sudah siap pakai.**
6
6
 
7
7
  [![Live docs](https://img.shields.io/badge/docs-live-2050d8?style=flat-square)](https://cahyo-dimas.github.io/freeday-ui-kit/)
8
- [![Release](https://img.shields.io/badge/release-v1.51.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.51.0)
8
+ [![Release](https://img.shields.io/badge/release-v1.52.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.52.0)
9
9
 
10
10
  UI KIT yang token-driven & framework-agnostic — satu sumber kebenaran untuk warna, tipografi,
11
11
  spasi, dan komponen. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  > **More free days for devs — the UI kit is ready to use.**
6
6
 
7
7
  [![Live docs](https://img.shields.io/badge/docs-live-2050d8?style=flat-square)](https://cahyo-dimas.github.io/freeday-ui-kit/)
8
- [![Release](https://img.shields.io/badge/release-v1.51.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.51.0)
8
+ [![Release](https://img.shields.io/badge/release-v1.52.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.52.0)
9
9
 
10
10
  A token-driven, framework-agnostic UI kit — one source of truth for color, typography,
11
11
  spacing, and components. Blueprint: `docs/superpowers/specs/2026-07-21-freeday-ui-kit-design.md`.
@@ -49,7 +49,21 @@
49
49
  var v = el.getAttribute(attr);
50
50
  return v ? v.split(',').map(function (s) { return s.trim(); }) : [];
51
51
  }
52
- function ensureImg(el) { if (!el.hasAttribute('role')) el.setAttribute('role', 'img'); }
52
+ // role="img" is Children Presentational per ARIA, so the docs used to say a chart's rendered
53
+ // legend, bar values and donut centre are not exposed. No browser actually prunes that subtree —
54
+ // measured in Chrome's AX tree, every one of those nodes is live and unignored; what saves the
55
+ // advice is only that AT treats a NAMED role="img" as a leaf and does not descend. So the kit
56
+ // hides them itself, and the author's aria-label really is the whole text alternative.
57
+ //
58
+ // Only for a chart that HAS a name: hiding the contents of an unlabelled one would leave an image
59
+ // with no text at all, which is worse than the leak. An author who sets some other role owns the
60
+ // subtree and gets no help from here.
61
+ function ensureImg(el) {
62
+ if (!el.hasAttribute('role')) el.setAttribute('role', 'img');
63
+ if (el.getAttribute('role') !== 'img') return;
64
+ if (!el.hasAttribute('aria-label') && !el.hasAttribute('aria-labelledby')) return;
65
+ Array.prototype.forEach.call(el.children, function (child) { child.setAttribute('aria-hidden', 'true'); });
66
+ }
53
67
  function svgEl(name) { return document.createElementNS(NS, name); }
54
68
 
55
69
  // Cartesian charts size their viewBox to the measured plot, so a width change needs a repaint.
package/dist/freeday.js CHANGED
@@ -913,7 +913,21 @@
913
913
  var v = el.getAttribute(attr);
914
914
  return v ? v.split(',').map(function (s) { return s.trim(); }) : [];
915
915
  }
916
- function ensureImg(el) { if (!el.hasAttribute('role')) el.setAttribute('role', 'img'); }
916
+ // role="img" is Children Presentational per ARIA, so the docs used to say a chart's rendered
917
+ // legend, bar values and donut centre are not exposed. No browser actually prunes that subtree —
918
+ // measured in Chrome's AX tree, every one of those nodes is live and unignored; what saves the
919
+ // advice is only that AT treats a NAMED role="img" as a leaf and does not descend. So the kit
920
+ // hides them itself, and the author's aria-label really is the whole text alternative.
921
+ //
922
+ // Only for a chart that HAS a name: hiding the contents of an unlabelled one would leave an image
923
+ // with no text at all, which is worse than the leak. An author who sets some other role owns the
924
+ // subtree and gets no help from here.
925
+ function ensureImg(el) {
926
+ if (!el.hasAttribute('role')) el.setAttribute('role', 'img');
927
+ if (el.getAttribute('role') !== 'img') return;
928
+ if (!el.hasAttribute('aria-label') && !el.hasAttribute('aria-labelledby')) return;
929
+ Array.prototype.forEach.call(el.children, function (child) { child.setAttribute('aria-hidden', 'true'); });
930
+ }
917
931
  function svgEl(name) { return document.createElementNS(NS, name); }
918
932
 
919
933
  // Cartesian charts size their viewBox to the measured plot, so a width change needs a repaint.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cahyo-dimas/freeday",
3
- "version": "1.51.0",
3
+ "version": "1.52.0",
4
4
  "description": "Freeday — token-driven, framework-agnostic UI KIT (design source-of-truth).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -92,7 +92,7 @@
92
92
  "scripts": {
93
93
  "build": "node tokens/build.mjs",
94
94
  "test": "node --test",
95
- "test:browser": "node --test browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/cfl-multi.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs",
95
+ "test:browser": "node --test browser/vanilla.mjs browser/adapter.mjs browser/layout.mjs browser/theme.mjs browser/state.mjs browser/root-init.mjs browser/upload-states.mjs browser/number.mjs browser/card-stretch.mjs browser/text-override.mjs browser/control-heights.mjs browser/cfl-multi.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs",
96
96
  "prepack": "node tokens/build.mjs",
97
97
  "version": "node tokens/build.mjs && git add dist",
98
98
  "typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit"