@cahyo-dimas/freeday 1.51.1 → 1.52.1
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 +60 -0
- package/COMPONENTS.md +21 -7
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/dist/freeday-chart.js +15 -1
- package/dist/freeday.js +15 -1
- package/package.json +2 -2
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.1] — 2026-08-24
|
|
7
|
+
### Docs
|
|
8
|
+
- **1.51.1's close-watcher caveat named the wrong condition** (#048). It said a `showModal()` with
|
|
9
|
+
no transient activation — "from a timer, or after an `await` that outlived the click" — has its
|
|
10
|
+
close watcher grouped with the dialog below, so one Escape closes both. A consumer audited three
|
|
11
|
+
call sites of exactly that shape and could not reproduce it. They were right, and the reason is
|
|
12
|
+
that the line is **sticky** activation, not transient: grouping needs a page that has received no
|
|
13
|
+
user input *at all*. Six seconds after a single click — transient activation long expired,
|
|
14
|
+
`navigator.userActivation.isActive === false` — two stacked overlays are still independent. So the
|
|
15
|
+
grouping is reachable in a test harness and nowhere else, which is exactly where the kit met it.
|
|
16
|
+
- **What the advice was protecting was real, but it is a different failure.** Open either overlay
|
|
17
|
+
from script and the second one's `cancel` event arrives **non-cancelable**: Escape still closes
|
|
18
|
+
only the topmost, but `preventDefault()` on it is ignored, so an "unsaved changes, stay open" guard
|
|
19
|
+
on that dialog silently stops working. Only when both overlays are opened by a real gesture is that
|
|
20
|
+
veto available. The Modal entry now says this instead.
|
|
21
|
+
- The note also proposed documenting that the typed wrappers' `@cancel.prevent` makes them immune to
|
|
22
|
+
the grouping. **Measured, they are not** — in a page with no activation the `cancel` event is not
|
|
23
|
+
cancelable at all, so preventing it does nothing and both dialogs close regardless. That claim is
|
|
24
|
+
not in the docs, and this is why.
|
|
25
|
+
### Added — guards
|
|
26
|
+
- `browser/overlay-stack.mjs` gains the whole matrix as one assertion — four ways to open two
|
|
27
|
+
overlays × what one Escape leaves open × whether it could be refused — so a row changing is
|
|
28
|
+
reported as news about the browser rather than a regression in the kit. Verified identical on
|
|
29
|
+
Chromium 133 (the suite's engine) and Chrome 151.
|
|
30
|
+
- The fixture records each `cancel` event's `cancelable` flag; nothing prevents it, so the older
|
|
31
|
+
tests measure what they always did.
|
|
32
|
+
|
|
33
|
+
## [1.52.0] — 2026-08-24
|
|
34
|
+
### Fixed
|
|
35
|
+
- **A chart's legend, bar values and donut centre were exposed to assistive tech after all** (#047).
|
|
36
|
+
`COMPONENTS.md` explained the a11y contract with the ARIA spec — `role="img"` is *Children
|
|
37
|
+
Presentational*, therefore "rendered bar values, axis ticks and legends are not exposed". Measured
|
|
38
|
+
against Chrome's accessibility tree, that is not what happens: a donut kept `list` → three
|
|
39
|
+
`listitem` → `StaticText "posted — 76%"` and its centre total `1687`, the legacy `.fdy-bars` kept
|
|
40
|
+
every value and label (`12`, `Jan`, `30`, `Feb`…), and a two-series cartesian kept its legend — all
|
|
41
|
+
live, none ignored. What actually protected the advice was only that AT treats a *named*
|
|
42
|
+
`role="img"` as a leaf and does not descend; the docs presented a habit as a guarantee. Axis ticks
|
|
43
|
+
were the one true claim, and for an unstated reason: the renderer already sets `aria-hidden` on the
|
|
44
|
+
`<svg>` itself.
|
|
45
|
+
`ensureImg()` now marks the rendered children `aria-hidden="true"`, so the author's `aria-label`
|
|
46
|
+
really is the entire text alternative and cannot be double-announced by an AT that does descend.
|
|
47
|
+
Nothing is removed and nothing moves — the legend and the centre are still in the DOM and still
|
|
48
|
+
painted.
|
|
49
|
+
- **The hiding requires a name.** A chart with no `aria-label`/`aria-labelledby` keeps its contents
|
|
50
|
+
exposed: pruning the subtree of an unlabelled chart would leave an image with no name *and* no
|
|
51
|
+
text, which is worse than the leak and harder for the author to notice. An element whose author
|
|
52
|
+
set some other `role` is left alone entirely.
|
|
53
|
+
### Docs
|
|
54
|
+
- The Charts a11y bullet now describes the kit instead of the spec: what is hidden, that `role="img"`
|
|
55
|
+
alone would not have hidden it, and what happens when the label is missing.
|
|
56
|
+
### Added — guards
|
|
57
|
+
- `browser/chart-a11y.mjs` (5 tests) over donut, legacy bars and cartesian: the accessible subtree
|
|
58
|
+
exposes no text of its own, the author label survives as the name, the legend is still rendered and
|
|
59
|
+
visible (hidden, not deleted), and an unlabelled chart still reads. The first three were verified to
|
|
60
|
+
fail against 1.51.1.
|
|
61
|
+
- `axSubtree()` in the browser harness — every AX node under an element, ignored ones included. The
|
|
62
|
+
DOM cannot answer this class of question: `aria-hidden` changes no markup, so a `querySelector`
|
|
63
|
+
assert passes identically before and after the fix. Same reason the 1.51.0 scaling bug needed a
|
|
64
|
+
real measurement rather than a source read.
|
|
65
|
+
|
|
6
66
|
## [1.51.1] — 2026-08-24
|
|
7
67
|
### Docs
|
|
8
68
|
- **`COMPONENTS.md` never said whether two overlays may be open at once** (#046). Modal and Drawer
|
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.
|
|
872
|
-
|
|
873
|
-
|
|
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`
|
|
@@ -1056,10 +1061,19 @@ that. Escape closes the topmost first, so cancelling returns to the still-open d
|
|
|
1056
1061
|
Escape then closes the drawer. Closing them out of order is fine too: closing the drawer while the
|
|
1057
1062
|
modal is up leaves the modal open, painted and dismissible.
|
|
1058
1063
|
|
|
1059
|
-
One caveat, and it is the browser's rather than the kit's: **
|
|
1060
|
-
|
|
1061
|
-
`await` that outlived the click —
|
|
1062
|
-
|
|
1064
|
+
One caveat, and it is the browser's rather than the kit's: **the second overlay's Escape can only
|
|
1065
|
+
be refused if both were opened by a real gesture.** A `showModal()` that runs from script — a timer,
|
|
1066
|
+
or after an `await` that outlived the click — still closes only the topmost, but its `cancel` event
|
|
1067
|
+
arrives **non-cancelable**, so `preventDefault()` on it is ignored and an "unsaved changes, stay
|
|
1068
|
+
open" guard silently stops working. Routing Escape through app state instead, which is what the
|
|
1069
|
+
typed wrappers do, is unaffected: the dialog closes and your state follows it.
|
|
1070
|
+
|
|
1071
|
+
*Correction to 1.51.1, which said such a dialog has its close watcher grouped with the one below so
|
|
1072
|
+
that a single Escape closes both.* That happens only on a page that has received **no user input at
|
|
1073
|
+
all** — a test harness, never an app; six seconds after one click the overlays are still
|
|
1074
|
+
independent. Open overlays with real clicks in a test, or a stacking assertion fails for a reason
|
|
1075
|
+
that is not the kit. Both rules measured on Chromium 133 and 151, and pinned as a matrix in
|
|
1076
|
+
`browser/overlay-stack.mjs`.
|
|
1063
1077
|
|
|
1064
1078
|
## Drawer — `.fdy-drawer`
|
|
1065
1079
|
> **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).
|
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
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.52.1)
|
|
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
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.52.1)
|
|
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`.
|
package/dist/freeday-chart.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "1.52.1",
|
|
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 browser/overlay-stack.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"
|