@cahyo-dimas/freeday 1.49.0 → 1.51.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 +78 -0
- package/COMPONENTS.md +28 -2
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/dist/freeday-chart.js +62 -28
- package/dist/freeday-toast.js +31 -0
- package/dist/freeday.bundle.css +27 -8
- package/dist/freeday.css +27 -8
- package/dist/freeday.js +93 -28
- package/docs/reference-screen.html +0 -5
- package/package.json +2 -2
- package/src/components/chart.css +4 -4
- package/src/components/selection.css +10 -3
- package/src/components/toast.css +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,84 @@
|
|
|
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.51.0] — 2026-08-21
|
|
7
|
+
### Fixed
|
|
8
|
+
- **A cartesian chart's axis text and dots grew with the container** (#042). `renderCartesian` built
|
|
9
|
+
a fixed `viewBox="0 0 320 180"` and let `.fdy-chart-xy__plot` stretch it to `width:100%`, so every
|
|
10
|
+
size the renderer expressed in user units was multiplied by `plotWidth / 320`. `font-size:9px` on
|
|
11
|
+
a tick is not nine screen pixels at any width but 320px: measured on a real console it rendered
|
|
12
|
+
13px at a 350px plot and **81px at a 2232px** one, making the chart's smallest, most secondary
|
|
13
|
+
text the largest type on the page. `r="2.2"` did the same to the dots — 4.8px across at 350px,
|
|
14
|
+
30.7px at 2232px. The chart now measures `__plot` and sizes the `viewBox` to it in CSS pixels, so
|
|
15
|
+
one user unit is one pixel and every declared size means what it says at every width; a
|
|
16
|
+
`ResizeObserver` repaints it when the box changes (a collapsing sidebar moves a plot ~300px at a
|
|
17
|
+
fixed viewport, so this is the common case, not an edge case). A chart that measures 0 — detached,
|
|
18
|
+
or inside `display:none` — falls back to the old box and repaints when it is laid out.
|
|
19
|
+
- **A currency y-axis was clipped by a gutter sized for a percent one** (#042). `PL` was a constant
|
|
20
|
+
38 user units, which was ~4 characters at one scale and something else at every other. It is now
|
|
21
|
+
derived from the widest formatted tick the axis will actually draw, capped at 35% of the plot.
|
|
22
|
+
### Changed
|
|
23
|
+
- **Axis type is a real CSS size**, set by `--fdy-chart-tick-size` on `.fdy-chart-xy` (default
|
|
24
|
+
`var(--text-xs)`). `font-size` moved off `.fdy-chart-xy__tick` / `__xlabel` onto
|
|
25
|
+
`.fdy-chart-xy__plot`, which the SVG text inherits — one computed read gives the renderer the real
|
|
26
|
+
pixel size it needs for the y-gutter and for the x-label autoskip, so an override flows to the
|
|
27
|
+
text *and* to the geometry around it. Before this, chart text could not be restyled at all: any
|
|
28
|
+
value set was multiplied by a scale the consumer could not see or compute.
|
|
29
|
+
The dot radius went 2.2 → 3 and the bar corner 1.5 → 2, both retuned against a 1:1 scale.
|
|
30
|
+
### Docs
|
|
31
|
+
- **`COMPONENTS.md` claimed a `<table>` fallback that no rendered chart has** (#041). It said every
|
|
32
|
+
chart is `role="img"` + `aria-label` "with a `<table>` fallback inside the element"; the renderer
|
|
33
|
+
builds no table and wipes the element on every render path. Worse, `role="img"` makes descendants
|
|
34
|
+
presentational, so the bar values, ticks and legend the renderer *does* draw are not exposed
|
|
35
|
+
either — the author's `aria-label` is the entire text alternative, and the docs now say so and ask
|
|
36
|
+
for one that carries the numbers. The four dead fallback tables in `docs/index.html` and
|
|
37
|
+
`docs/reference-screen.html`, which no reader has ever reached, are gone with it.
|
|
38
|
+
- **The kit's own chart sizing is written down** (#041): `.fdy-chart-xy__plot` is
|
|
39
|
+
`aspect-ratio:16/9` + `min-height:8rem`, `.fdy-bars` a fixed `9rem`, `.fdy-sparkline` an
|
|
40
|
+
inline-block `8rem × 2.25rem` — with the note to override those rather than the chart root, since
|
|
41
|
+
a root `height` fights `aspect-ratio` instead of setting it.
|
|
42
|
+
### Added — guards
|
|
43
|
+
- **`browser/chart-scale.mjs`** — the axis label box and dot diameter are measured at plot widths of
|
|
44
|
+
350 / 696 / 1400 / 2232px and must not vary by more than a pixel, must stay smaller than body
|
|
45
|
+
text, and the `viewBox` must equal the measured plot. A second spec widens a container and asserts
|
|
46
|
+
the repaint. This class of bug is invisible to a source read: `font-size:9px` and `r="2.2"` look
|
|
47
|
+
like correct sizes, and the one thing in the plot that *was* protected —
|
|
48
|
+
`vector-effect:non-scaling-stroke` on the line — is what made the other three look protected too.
|
|
49
|
+
Only a real layout tells them apart.
|
|
50
|
+
|
|
51
|
+
## [1.50.0] — 2026-08-20
|
|
52
|
+
### Fixed
|
|
53
|
+
- **A toast raised from inside a modal was painted behind it** (#027). `.fdy-toast-region` carried
|
|
54
|
+
`z-index: 200`, which can never win: `.fdy-modal` is a native `<dialog>` opened with `showModal()`
|
|
55
|
+
and therefore lives in the **top layer**, above the whole z-index universe. An error raised by an
|
|
56
|
+
action taken inside a dialog was dimmed by that dialog's backdrop and mostly hidden under the
|
|
57
|
+
dialog — so the reader saw a confirmation still asking a question they had already answered, and
|
|
58
|
+
no reason why. The region is now `popover="manual"` and `freeday-toast.js` calls `showPopover()`
|
|
59
|
+
as each toast lands (per toast, not once — the top layer is ordered by when you joined, so a
|
|
60
|
+
dialog opened after the last toast would otherwise sit above it). Guarded: where `showPopover` is
|
|
61
|
+
unavailable or throws, the region stays exactly what it was.
|
|
62
|
+
- **A checkbox shrank when its label was long** (#027). `.fdy-check` is an inline-flex row and its
|
|
63
|
+
box declared a width — but a flex item with a width is still shrinkable, so a label that wrapped
|
|
64
|
+
to three lines squeezed the box from 18px to 13px while leaving it 18px tall. A group of five
|
|
65
|
+
rendered three different sizes, none of them square. `flex: none` on `.fdy-check input`,
|
|
66
|
+
`.fdy-radio input`, the `.fdy-switch` track and the standalone `.fdy-checkbox` — all four are
|
|
67
|
+
flex children somewhere in the kit.
|
|
68
|
+
### Added — guards
|
|
69
|
+
- **`pixelAt(x, y)` in `browser/harness.mjs`** — a 1×1 `Page.captureScreenshot`, decoded in-process.
|
|
70
|
+
Stacking above a modal cannot be asserted with `elementFromPoint`: a modal dialog makes the rest
|
|
71
|
+
of the document inert, so a hit test outside it returns the dialog whatever the paint order is,
|
|
72
|
+
reporting a working fix as broken. The new guard measures the colour actually composited at the
|
|
73
|
+
toast's centre. It waits for both fade-ins to finish first — a screenshot taken mid-animation
|
|
74
|
+
returns a blend of the two elements and flakes in both directions.
|
|
75
|
+
- **Selection controls are asserted square and equal** across a group whose labels run one to three
|
|
76
|
+
lines, so a box that shrank in both axes cannot pass a width-only comparison. All four controls
|
|
77
|
+
the fix touched are measured, each in the container it actually ships in: the standalone
|
|
78
|
+
`.fdy-checkbox` inside `.fdy-filter__check` — the flex row all three typed adapters render in a
|
|
79
|
+
filter popover and a CFL option list — and the switch track against a switch of its own, since it
|
|
80
|
+
is the one control that is not square. The first version of this guard held the standalone
|
|
81
|
+
checkbox in a plain block, where it cannot shrink at all, so removing `flex: none` from the one
|
|
82
|
+
box every adapter puts on screen left it passing.
|
|
83
|
+
|
|
6
84
|
## [1.49.0] — 2026-08-20
|
|
7
85
|
### Added
|
|
8
86
|
- **`FdyTableColumn.labelHidden`** (#026). A column of row CONTROLS — an edit button, a row menu —
|
package/COMPONENTS.md
CHANGED
|
@@ -858,7 +858,20 @@ Pure SVG/CSS, no dependency, re-colours with the theme. Needs `freeday-chart.js`
|
|
|
858
858
|
validated categorical palette `--chart-1`…`--chart-8`.
|
|
859
859
|
- Format: `data-fdy-format="number|percent|currency"`; legend `data-fdy-legend` (`none` to drop);
|
|
860
860
|
axes `data-fdy-axes`
|
|
861
|
-
-
|
|
861
|
+
- Sizing is already set by the kit — override **these**, never a `height` on the chart root (a
|
|
862
|
+
root height fights `aspect-ratio` instead of setting it): `.fdy-chart-xy__plot` is `width:100%`
|
|
863
|
+
+ `aspect-ratio:16/9` + `min-height:8rem`; `.fdy-bars` is a fixed `height:9rem`; `.fdy-sparkline`
|
|
864
|
+
is an inline-block `8rem × 2.25rem`.
|
|
865
|
+
- Axis type: `--fdy-chart-tick-size` (default `var(--text-xs)`) sets the y-tick and x-label size on
|
|
866
|
+
`.fdy-chart-xy`. It is a real CSS size — a cartesian chart's `viewBox` is measured to its plot, so
|
|
867
|
+
one user unit is one pixel — and the renderer reads it back to size the y-gutter and to decide how
|
|
868
|
+
many x-labels fit. Set the token rather than `font-size` on `__tick`/`__xlabel`, or the gutter and
|
|
869
|
+
the autoskip will still be computed from the old size.
|
|
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
|
|
874
|
+
render. Good: `aria-label="Monthly posting trend: Posted 62% → 88%, Draft 24% → 16%"`.
|
|
862
875
|
- Internal parts (set by JS, useful for overrides): `.fdy-chart__legend` (+`--row`) `__swatch`
|
|
863
876
|
`__tip`; `.fdy-bars__col` `__bar` `__track` `__label` `__val`; `.fdy-chart-xy__plot` `__grid`
|
|
864
877
|
`__tick` `__xlabel` `__line` `__area` `__bar` `__dot` `__band`; `.fdy-donut__ring` `__seg`
|
|
@@ -935,6 +948,13 @@ Every field is optional; it returns the toast element. Classes (rendered for you
|
|
|
935
948
|
`.fdy-toast-region`, `.fdy-toast` (+`--info` `--success` `--warning` `--danger`) · `__accent`
|
|
936
949
|
`__body` `__title` `__text` `__close`.
|
|
937
950
|
|
|
951
|
+
**Above modals, and not by z-index.** The region is a `popover` and re-enters the **top layer** as
|
|
952
|
+
each toast lands, because `.fdy-modal` is a native `<dialog>` — see the note under Modal below. If
|
|
953
|
+
a toast of yours is hidden behind something, a bigger `z-index` is not the fix; check whether that
|
|
954
|
+
something is in the top layer too. Where the Popover API is unavailable (before Chrome 114 /
|
|
955
|
+
Safari 17 / Firefox 125) the region falls back to a fixed block at `z-index: 200`, which cannot
|
|
956
|
+
clear an open modal — the message is still announced to a screen reader.
|
|
957
|
+
|
|
938
958
|
## Tooltip — `.fdy-tooltip`
|
|
939
959
|
Hover/focus only, never the sole carrier of information. Wrap in `.fdy-tooltip-wrap`; the trigger
|
|
940
960
|
gets `aria-describedby` → the `.fdy-tooltip[role="tooltip"]` `id`.
|
|
@@ -1019,7 +1039,13 @@ so the same look serves routed sub-navigation built from plain links — see the
|
|
|
1019
1039
|
> **Typed wrapper: `<FdyModal>`** — 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).
|
|
1020
1040
|
|
|
1021
1041
|
Native `<dialog>`: focus trap, Esc and backdrop come from the browser. Sizes `--sm` `--md` `--lg`
|
|
1022
|
-
`--wide`; `--cfl` for the choose-from-list dialog.
|
|
1042
|
+
`--wide`; `--cfl` for the choose-from-list dialog.
|
|
1043
|
+
|
|
1044
|
+
**It is in the TOP LAYER.** `showModal()` paints the dialog above every stacking context on the
|
|
1045
|
+
page, so no `z-index` anywhere can put anything over it — only another top-layer element (a
|
|
1046
|
+
`popover`, or another modal) can. It also makes the rest of the document inert, which means
|
|
1047
|
+
`elementFromPoint` outside the dialog returns the dialog: do not use a hit test to check what is
|
|
1048
|
+
painted on top of a modal, because it will tell you the dialog is, even when it visibly is not. Parts `__header` `__title` `__body` `__footer`
|
|
1023
1049
|
`__close`. The body scrolls; the footer never clips. `aria-labelledby` → the title's `id`;
|
|
1024
1050
|
`data-close` on any button that should close it.
|
|
1025
1051
|
|
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.51.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
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v1.51.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`.
|
package/dist/freeday-chart.js
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* (var(--color-<name>)) OR a slot "chart-1".."chart-8" (var(--chart-N)) to pin a series to the
|
|
11
11
|
* validated palette. Both data-fdy-color and data-fdy-colors accept the chart-N form.
|
|
12
12
|
* Cartesian charts (line/area, multi-series/stacked bar) draw themed axes from --chart-grid /
|
|
13
|
-
* --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency).
|
|
13
|
+
* --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency). Their
|
|
14
|
+
* viewBox is sized to the measured plot (1 user unit = 1 CSS pixel) and repainted from a
|
|
15
|
+
* ResizeObserver, so axis type stays in real pixels and is set in CSS via --fdy-chart-tick-size.
|
|
14
16
|
* Charts are an enhancement — put a table/text fallback inside the element and it will be
|
|
15
17
|
* replaced on render; give the element role="img" + aria-label.
|
|
16
18
|
*
|
|
@@ -50,6 +52,18 @@
|
|
|
50
52
|
function ensureImg(el) { if (!el.hasAttribute('role')) el.setAttribute('role', 'img'); }
|
|
51
53
|
function svgEl(name) { return document.createElementNS(NS, name); }
|
|
52
54
|
|
|
55
|
+
// Cartesian charts size their viewBox to the measured plot, so a width change needs a repaint.
|
|
56
|
+
// Guarded on width alone: a render can change the element's own height (the legend wrapping to a
|
|
57
|
+
// second row) but never its own width, so this settles instead of looping.
|
|
58
|
+
var cartesianRO = typeof ResizeObserver === 'function' ? new ResizeObserver(function (entries) {
|
|
59
|
+
entries.forEach(function (entry) {
|
|
60
|
+
var target = entry.target;
|
|
61
|
+
if (!target.isConnected) { cartesianRO.unobserve(target); return; }
|
|
62
|
+
var w = Math.round(entry.contentRect.width);
|
|
63
|
+
if (w && w !== target.__fdyChartW) renderChart(target);
|
|
64
|
+
});
|
|
65
|
+
}) : null;
|
|
66
|
+
|
|
53
67
|
// Multi-series model: data-series (JSON) wins; else the data-values single-series shortcut.
|
|
54
68
|
function getSeries(el) {
|
|
55
69
|
var raw = el.getAttribute('data-series');
|
|
@@ -194,7 +208,46 @@
|
|
|
194
208
|
var top = niceCeil(dataMax > base ? dataMax : base + 1);
|
|
195
209
|
if (top <= base) top = base + 1;
|
|
196
210
|
|
|
197
|
-
|
|
211
|
+
// Lay the chrome out first so the plot can be measured, then size the viewBox to it in CSS
|
|
212
|
+
// pixels: one user unit is one pixel, so font-size / r / stroke-width mean what they say at
|
|
213
|
+
// every container width. (A fixed 320x180 viewBox stretched by CSS multiplied every declared
|
|
214
|
+
// size by plotWidth/320 instead — a 9px axis label rendered ~39px on a 1728px viewport.)
|
|
215
|
+
var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
|
|
216
|
+
var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
|
|
217
|
+
el.classList.add('fdy-chart-xy');
|
|
218
|
+
el.innerHTML = '';
|
|
219
|
+
if (showLegend) {
|
|
220
|
+
var legend = document.createElement('ul');
|
|
221
|
+
legend.className = 'fdy-chart__legend fdy-chart__legend--row';
|
|
222
|
+
series.forEach(function (s, si) {
|
|
223
|
+
var li = document.createElement('li');
|
|
224
|
+
var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorFor(si);
|
|
225
|
+
li.appendChild(sw);
|
|
226
|
+
li.appendChild(document.createTextNode(s.label || ('Seri ' + (si + 1))));
|
|
227
|
+
legend.appendChild(li);
|
|
228
|
+
});
|
|
229
|
+
el.appendChild(legend);
|
|
230
|
+
}
|
|
231
|
+
var plot = document.createElement('div'); plot.className = 'fdy-chart-xy__plot';
|
|
232
|
+
el.appendChild(plot);
|
|
233
|
+
|
|
234
|
+
// Measured while empty: .fdy-chart-xy__plot carries its own width / aspect-ratio / min-height,
|
|
235
|
+
// so the box is already final. A detached or display:none chart measures 0 — fall back to the
|
|
236
|
+
// legacy box and let the ResizeObserver repaint it once it is actually laid out.
|
|
237
|
+
var box = plot.getBoundingClientRect();
|
|
238
|
+
var W = Math.round(box.width) || 320, H = Math.round(box.height) || 180;
|
|
239
|
+
var FS = parseFloat(getComputedStyle(plot).fontSize) || 12; // axis type size, owned by CSS
|
|
240
|
+
var charW = FS * 0.6; // mono/body advance is ~0.6em
|
|
241
|
+
|
|
242
|
+
// Tick values are known before the gutter is, so size PL to the widest one: a currency axis
|
|
243
|
+
// needs more room than a percent axis, and a fixed gutter only ever suited one of them.
|
|
244
|
+
var TICKS = 4;
|
|
245
|
+
var tickText = [];
|
|
246
|
+
for (var t0 = 0; t0 <= TICKS; t0++) tickText.push(fmt(base + (top - base) * t0 / TICKS));
|
|
247
|
+
var widestTick = 0;
|
|
248
|
+
tickText.forEach(function (t) { widestTick = Math.max(widestTick, t.length * charW); });
|
|
249
|
+
var PL = Math.min(Math.ceil(widestTick) + 10, Math.round(W * 0.35));
|
|
250
|
+
var PR = 10, PT = 10, PB = Math.round(FS * 2.2);
|
|
198
251
|
var plotW = W - PL - PR, plotH = H - PT - PB;
|
|
199
252
|
function mapY(v) { return PT + plotH * (1 - (v - base) / (top - base)); }
|
|
200
253
|
var slot = plotW / n;
|
|
@@ -206,7 +259,6 @@
|
|
|
206
259
|
svg.setAttribute('aria-hidden', 'true');
|
|
207
260
|
|
|
208
261
|
// y gridlines + formatted ticks (bottom line at `base` doubles as the zero baseline).
|
|
209
|
-
var TICKS = 4;
|
|
210
262
|
for (var k = 0; k <= TICKS; k++) {
|
|
211
263
|
var val = base + (top - base) * k / TICKS;
|
|
212
264
|
var gy = mapY(val);
|
|
@@ -216,9 +268,9 @@
|
|
|
216
268
|
gl.setAttribute('class', 'fdy-chart-xy__grid');
|
|
217
269
|
svg.appendChild(gl);
|
|
218
270
|
var tk = svgEl('text');
|
|
219
|
-
tk.setAttribute('x', PL - 5); tk.setAttribute('y', (gy +
|
|
271
|
+
tk.setAttribute('x', PL - 5); tk.setAttribute('y', (gy + FS * 0.35).toFixed(1));
|
|
220
272
|
tk.setAttribute('text-anchor', 'end'); tk.setAttribute('class', 'fdy-chart-xy__tick');
|
|
221
|
-
tk.textContent =
|
|
273
|
+
tk.textContent = tickText[k];
|
|
222
274
|
svg.appendChild(tk);
|
|
223
275
|
}
|
|
224
276
|
|
|
@@ -226,7 +278,6 @@
|
|
|
226
278
|
// of assuming a fixed 40px slot; keep every step-th plus the last, then drop any kept label that
|
|
227
279
|
// would collide with the one before it — including the forced last one (a fraction-of-a-slot
|
|
228
280
|
// overlap happens when n-1 lands next to the last kept multiple of step, e.g. n=24 at most widths).
|
|
229
|
-
var charW = 5.4; // ~9px font advance (.fdy-chart-xy__xlabel)
|
|
230
281
|
var widest = 0;
|
|
231
282
|
for (var wi = 0; wi < n; wi++) widest = Math.max(widest, String(labels[wi] || ('#' + (wi + 1))).length * charW);
|
|
232
283
|
var minGap = widest + 8;
|
|
@@ -242,7 +293,7 @@
|
|
|
242
293
|
}
|
|
243
294
|
kept.forEach(function (xk) {
|
|
244
295
|
var xt = svgEl('text');
|
|
245
|
-
xt.setAttribute('x', xPos(xk).toFixed(1)); xt.setAttribute('y', H -
|
|
296
|
+
xt.setAttribute('x', xPos(xk).toFixed(1)); xt.setAttribute('y', H - Math.round(FS * 0.7));
|
|
246
297
|
xt.setAttribute('text-anchor', 'middle'); xt.setAttribute('class', 'fdy-chart-xy__xlabel');
|
|
247
298
|
xt.textContent = labels[xk] || ('#' + (xk + 1));
|
|
248
299
|
svg.appendChild(xt);
|
|
@@ -268,7 +319,7 @@
|
|
|
268
319
|
var rect = svgEl('rect');
|
|
269
320
|
rect.setAttribute('x', x.toFixed(1)); rect.setAttribute('width', Math.max(1, barW - 1).toFixed(1));
|
|
270
321
|
rect.setAttribute('y', Math.min(y0, y1).toFixed(1)); rect.setAttribute('height', Math.max(0.5, Math.abs(y1 - y0)).toFixed(1));
|
|
271
|
-
rect.setAttribute('rx', '
|
|
322
|
+
rect.setAttribute('rx', '2'); rect.setAttribute('class', 'fdy-chart-xy__bar');
|
|
272
323
|
rect.style.fill = colorFor(si);
|
|
273
324
|
svg.appendChild(rect);
|
|
274
325
|
});
|
|
@@ -296,33 +347,14 @@
|
|
|
296
347
|
}
|
|
297
348
|
pts.forEach(function (p) {
|
|
298
349
|
var dot = svgEl('circle');
|
|
299
|
-
dot.setAttribute('cx', p[0].toFixed(1)); dot.setAttribute('cy', p[1].toFixed(1)); dot.setAttribute('r', '
|
|
350
|
+
dot.setAttribute('cx', p[0].toFixed(1)); dot.setAttribute('cy', p[1].toFixed(1)); dot.setAttribute('r', '3');
|
|
300
351
|
dot.setAttribute('class', 'fdy-chart-xy__dot'); dot.style.fill = colorFor(si);
|
|
301
352
|
svg.appendChild(dot);
|
|
302
353
|
});
|
|
303
354
|
});
|
|
304
355
|
}
|
|
305
356
|
|
|
306
|
-
// Assemble: optional legend on top, plot below, tooltip layer last.
|
|
307
|
-
var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
|
|
308
|
-
var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
|
|
309
|
-
el.classList.add('fdy-chart-xy');
|
|
310
|
-
el.innerHTML = '';
|
|
311
|
-
if (showLegend) {
|
|
312
|
-
var legend = document.createElement('ul');
|
|
313
|
-
legend.className = 'fdy-chart__legend fdy-chart__legend--row';
|
|
314
|
-
series.forEach(function (s, si) {
|
|
315
|
-
var li = document.createElement('li');
|
|
316
|
-
var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorFor(si);
|
|
317
|
-
li.appendChild(sw);
|
|
318
|
-
li.appendChild(document.createTextNode(s.label || ('Seri ' + (si + 1))));
|
|
319
|
-
legend.appendChild(li);
|
|
320
|
-
});
|
|
321
|
-
el.appendChild(legend);
|
|
322
|
-
}
|
|
323
|
-
var plot = document.createElement('div'); plot.className = 'fdy-chart-xy__plot';
|
|
324
357
|
plot.appendChild(svg);
|
|
325
|
-
el.appendChild(plot);
|
|
326
358
|
|
|
327
359
|
// Hover: one transparent band per category → readout of every series at that x.
|
|
328
360
|
var tip = makeTip(el);
|
|
@@ -342,6 +374,8 @@
|
|
|
342
374
|
band.addEventListener('mouseleave', function () { hideTip(tip); });
|
|
343
375
|
svg.appendChild(band);
|
|
344
376
|
}
|
|
377
|
+
el.__fdyChartW = W;
|
|
378
|
+
if (cartesianRO) cartesianRO.observe(el);
|
|
345
379
|
ensureImg(el);
|
|
346
380
|
}
|
|
347
381
|
|
package/dist/freeday-toast.js
CHANGED
|
@@ -28,11 +28,39 @@
|
|
|
28
28
|
region.className = 'fdy-toast-region';
|
|
29
29
|
region.setAttribute('aria-live', 'polite');
|
|
30
30
|
region.setAttribute('aria-atomic', 'false');
|
|
31
|
+
/* `manual`, not `auto`: an auto popover light-dismisses on an outside click and closes
|
|
32
|
+
itself the moment another popover opens. A toast is dismissed by its timer or its own
|
|
33
|
+
close button, by nobody else. */
|
|
34
|
+
region.setAttribute('popover', 'manual');
|
|
31
35
|
document.body.appendChild(region);
|
|
32
36
|
}
|
|
33
37
|
return region;
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
/* Put the region in the TOP LAYER, above any open <dialog>.
|
|
41
|
+
*
|
|
42
|
+
* A modal dialog lives in the top layer, where z-index does not reach — so a toast raised by an
|
|
43
|
+
* action taken inside a modal was painted behind that modal's backdrop, which is where an error
|
|
44
|
+
* message is least useful. showPopover() joins the same layer, and membership there is ordered by
|
|
45
|
+
* when you joined: hide-then-show re-joins at the top, so a toast shown while a dialog is open
|
|
46
|
+
* lands above it rather than under the dialog that was opened after the last toast.
|
|
47
|
+
*
|
|
48
|
+
* Guarded twice on purpose. `showPopover` is absent before Chrome 114 / Safari 17 / Firefox 125,
|
|
49
|
+
* and the call throws if the element is disconnected or already open — in every one of those
|
|
50
|
+
* cases the region stays an ordinary fixed block at z-index 200, which is what it has always
|
|
51
|
+
* been. A notification must never be the thing that throws.
|
|
52
|
+
*/
|
|
53
|
+
function raise(node) {
|
|
54
|
+
if (!node || typeof node.showPopover !== 'function') return;
|
|
55
|
+
try {
|
|
56
|
+
if (node.matches(':popover-open')) node.hidePopover();
|
|
57
|
+
node.showPopover();
|
|
58
|
+
} catch (e) {
|
|
59
|
+
/* Not reachable through the guards above, but a toast is on the error path itself: if
|
|
60
|
+
raising it fails the message still has to appear, one layer down. */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
36
64
|
function dismiss(toast) {
|
|
37
65
|
if (typeof toast === 'string') toast = keyed[toast]; // dismiss by key
|
|
38
66
|
if (!toast || toast.dataset.leaving === '1') return;
|
|
@@ -110,6 +138,9 @@
|
|
|
110
138
|
keyed[opts.key] = node;
|
|
111
139
|
}
|
|
112
140
|
region.appendChild(node);
|
|
141
|
+
// Raised per toast, not once at startup: a dialog opened AFTER the region joined the top layer
|
|
142
|
+
// would otherwise sit above it, which is the exact case this fixes.
|
|
143
|
+
raise(region);
|
|
113
144
|
|
|
114
145
|
var timeout = opts.timeout == null ? 4000 : opts.timeout;
|
|
115
146
|
if (timeout > 0) setTimeout(function () { dismiss(node); }, timeout);
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -972,12 +972,12 @@ a { color: var(--color-primary); }
|
|
|
972
972
|
/* Cartesian charts (line / area / multi-series & stacked bar) — SVG with themed axes.
|
|
973
973
|
* Axis colour comes from --chart-grid / --chart-tick (both theme-aware); series colours are
|
|
974
974
|
* set inline by the renderer from --chart-1..8, so the whole chart recolours on data-theme. */
|
|
975
|
-
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100
|
|
976
|
-
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;}
|
|
975
|
+
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100%;--fdy-chart-tick-size:var(--text-xs);}
|
|
976
|
+
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;font-size:var(--fdy-chart-tick-size);}
|
|
977
977
|
.fdy-chart-xy__plot svg{display:block;width:100%;height:100%;overflow:visible;}
|
|
978
978
|
.fdy-chart-xy__grid{stroke:var(--chart-grid);stroke-width:1;shape-rendering:crispEdges;}
|
|
979
|
-
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);
|
|
980
|
-
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);
|
|
979
|
+
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);}
|
|
980
|
+
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);}
|
|
981
981
|
.fdy-chart-xy__line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;vector-effect:non-scaling-stroke;}
|
|
982
982
|
.fdy-chart-xy__area{stroke:none;opacity:.14;}
|
|
983
983
|
.fdy-chart-xy__dot{stroke:var(--color-surface);stroke-width:1;}
|
|
@@ -1579,8 +1579,15 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1579
1579
|
.fdy-rating--sm .fdy-rating__star svg{width:1.1rem;height:1.1rem;}
|
|
1580
1580
|
|
|
1581
1581
|
/* Freeday — Selection controls */
|
|
1582
|
+
/* `flex:none` on every box, track and standalone checkbox is load-bearing, not tidiness.
|
|
1583
|
+
`.fdy-check` is an inline-flex row of [box, label], and a flex item with a WIDTH is still
|
|
1584
|
+
shrinkable — so as soon as the label wrapped to a second line the box gave up horizontal space
|
|
1585
|
+
to it and rendered an 18px-tall rectangle 13px wide. Squashed, off-square, and different from
|
|
1586
|
+
every sibling whose label happened to be short: a form of five checkboxes showed three sizes.
|
|
1587
|
+
Nothing in the declarations looked wrong, because the width was never overridden — it was
|
|
1588
|
+
negotiated away. */
|
|
1582
1589
|
.fdy-check,.fdy-radio,.fdy-switch{display:inline-flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;}
|
|
1583
|
-
.fdy-check input,.fdy-radio input{appearance:none;-webkit-appearance:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1590
|
+
.fdy-check input,.fdy-radio input{appearance:none;-webkit-appearance:none;flex:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1584
1591
|
.fdy-check input{border-radius:var(--radius-sm);}
|
|
1585
1592
|
.fdy-radio input{border-radius:50%;}
|
|
1586
1593
|
.fdy-check input::before{content:"";width:.62rem;height:.62rem;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);}
|
|
@@ -1590,13 +1597,13 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1590
1597
|
.fdy-check input:focus-visible,.fdy-radio input:focus-visible,.fdy-switch input:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
|
1591
1598
|
.fdy-check input:disabled,.fdy-radio input:disabled,.fdy-switch input:disabled{opacity:.5;cursor:not-allowed;}
|
|
1592
1599
|
.fdy-check:has(input:disabled),.fdy-radio:has(input:disabled),.fdy-switch:has(input:disabled){cursor:not-allowed;}
|
|
1593
|
-
.fdy-switch input{appearance:none;-webkit-appearance:none;position:relative;width:var(--control-switch-w);height:var(--control-switch-h);margin:0;border-radius:var(--radius-full);background:var(--color-control-border);cursor:pointer;transition:background var(--dur-base) var(--ease-standard);}
|
|
1600
|
+
.fdy-switch input{appearance:none;-webkit-appearance:none;flex:none;position:relative;width:var(--control-switch-w);height:var(--control-switch-h);margin:0;border-radius:var(--radius-full);background:var(--color-control-border);cursor:pointer;transition:background var(--dur-base) var(--ease-standard);}
|
|
1594
1601
|
.fdy-switch input::before{content:"";position:absolute;top:2px;left:2px;width:calc(var(--control-switch-h) - 4px);height:calc(var(--control-switch-h) - 4px);border-radius:50%;background:var(--color-switch-thumb);box-shadow:var(--shadow-1);transition:transform var(--dur-base) var(--ease-spring);}
|
|
1595
1602
|
.fdy-switch input:checked{background:var(--color-primary);}
|
|
1596
1603
|
.fdy-switch input:checked::before{transform:translateX(calc(var(--control-switch-w) - var(--control-switch-h)));}
|
|
1597
1604
|
|
|
1598
1605
|
/* Standalone checkbox (e.g. table row/select-all) — supports :indeterminate */
|
|
1599
|
-
.fdy-checkbox{appearance:none;-webkit-appearance:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);border-radius:var(--radius-sm);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1606
|
+
.fdy-checkbox{appearance:none;-webkit-appearance:none;flex:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);border-radius:var(--radius-sm);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1600
1607
|
.fdy-checkbox::before{content:"";width:.62rem;height:.62rem;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);}
|
|
1601
1608
|
.fdy-checkbox:checked,.fdy-checkbox:indeterminate{background:var(--color-primary);border-color:var(--color-primary);}
|
|
1602
1609
|
.fdy-checkbox:checked::before{transform:scale(1);}
|
|
@@ -1869,7 +1876,19 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1869
1876
|
@media (prefers-reduced-motion:no-preference){.fdy-timepicker__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
|
|
1870
1877
|
|
|
1871
1878
|
/* Freeday — Toast (transient notification) */
|
|
1872
|
-
|
|
1879
|
+
/* The region is a POPOVER, not merely a high z-index.
|
|
1880
|
+
`.fdy-modal` is a native <dialog> opened with showModal(), which puts it in the TOP LAYER — a
|
|
1881
|
+
layer above the whole z-index universe, so no number here could ever beat it. A toast raised by
|
|
1882
|
+
an action taken inside a modal was painted behind that modal's backdrop: dimmed, unreadable, and
|
|
1883
|
+
most of it under the dialog itself. `freeday-toast.js` calls showPopover() as each toast is
|
|
1884
|
+
added, which puts the region in the top layer too, and last-in wins there.
|
|
1885
|
+
Everything after `pointer-events` is undoing the UA's [popover] defaults — it arrives with
|
|
1886
|
+
inset:0, auto margins, a border, padding, a Canvas background and overflow:auto, none of which a
|
|
1887
|
+
toast stack wants. Where showPopover() is unavailable the element is still an ordinary fixed
|
|
1888
|
+
block at z-index 200, which is exactly what it was before. */
|
|
1889
|
+
.fdy-toast-region{position:fixed;z-index:200;inset:auto;bottom:var(--space-5);right:var(--space-5);display:flex;flex-direction:column;gap:var(--space-3);width:min(24rem,calc(100vw - var(--space-8)));height:auto;max-height:none;margin:0;padding:0;border:0;background:transparent;color:inherit;overflow:visible;pointer-events:none;}
|
|
1890
|
+
/* A popover gets a ::backdrop as well; a toast must not dim the page behind it. */
|
|
1891
|
+
.fdy-toast-region::backdrop{background:transparent;}
|
|
1873
1892
|
.fdy-toast{pointer-events:auto;display:flex;gap:var(--space-3);align-items:flex-start;padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);background:var(--color-inverse-surface);color:var(--color-inverse-text);box-shadow:var(--shadow-3);}
|
|
1874
1893
|
.fdy-toast__accent{flex:none;width:.25rem;align-self:stretch;border-radius:var(--radius-full);background:var(--color-primary);}
|
|
1875
1894
|
.fdy-toast--success .fdy-toast__accent{background:var(--color-success);}
|
package/dist/freeday.css
CHANGED
|
@@ -589,12 +589,12 @@ a { color: var(--color-primary); }
|
|
|
589
589
|
/* Cartesian charts (line / area / multi-series & stacked bar) — SVG with themed axes.
|
|
590
590
|
* Axis colour comes from --chart-grid / --chart-tick (both theme-aware); series colours are
|
|
591
591
|
* set inline by the renderer from --chart-1..8, so the whole chart recolours on data-theme. */
|
|
592
|
-
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100
|
|
593
|
-
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;}
|
|
592
|
+
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100%;--fdy-chart-tick-size:var(--text-xs);}
|
|
593
|
+
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;font-size:var(--fdy-chart-tick-size);}
|
|
594
594
|
.fdy-chart-xy__plot svg{display:block;width:100%;height:100%;overflow:visible;}
|
|
595
595
|
.fdy-chart-xy__grid{stroke:var(--chart-grid);stroke-width:1;shape-rendering:crispEdges;}
|
|
596
|
-
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);
|
|
597
|
-
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);
|
|
596
|
+
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);}
|
|
597
|
+
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);}
|
|
598
598
|
.fdy-chart-xy__line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;vector-effect:non-scaling-stroke;}
|
|
599
599
|
.fdy-chart-xy__area{stroke:none;opacity:.14;}
|
|
600
600
|
.fdy-chart-xy__dot{stroke:var(--color-surface);stroke-width:1;}
|
|
@@ -1196,8 +1196,15 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1196
1196
|
.fdy-rating--sm .fdy-rating__star svg{width:1.1rem;height:1.1rem;}
|
|
1197
1197
|
|
|
1198
1198
|
/* Freeday — Selection controls */
|
|
1199
|
+
/* `flex:none` on every box, track and standalone checkbox is load-bearing, not tidiness.
|
|
1200
|
+
`.fdy-check` is an inline-flex row of [box, label], and a flex item with a WIDTH is still
|
|
1201
|
+
shrinkable — so as soon as the label wrapped to a second line the box gave up horizontal space
|
|
1202
|
+
to it and rendered an 18px-tall rectangle 13px wide. Squashed, off-square, and different from
|
|
1203
|
+
every sibling whose label happened to be short: a form of five checkboxes showed three sizes.
|
|
1204
|
+
Nothing in the declarations looked wrong, because the width was never overridden — it was
|
|
1205
|
+
negotiated away. */
|
|
1199
1206
|
.fdy-check,.fdy-radio,.fdy-switch{display:inline-flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;}
|
|
1200
|
-
.fdy-check input,.fdy-radio input{appearance:none;-webkit-appearance:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1207
|
+
.fdy-check input,.fdy-radio input{appearance:none;-webkit-appearance:none;flex:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1201
1208
|
.fdy-check input{border-radius:var(--radius-sm);}
|
|
1202
1209
|
.fdy-radio input{border-radius:50%;}
|
|
1203
1210
|
.fdy-check input::before{content:"";width:.62rem;height:.62rem;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);}
|
|
@@ -1207,13 +1214,13 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1207
1214
|
.fdy-check input:focus-visible,.fdy-radio input:focus-visible,.fdy-switch input:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
|
1208
1215
|
.fdy-check input:disabled,.fdy-radio input:disabled,.fdy-switch input:disabled{opacity:.5;cursor:not-allowed;}
|
|
1209
1216
|
.fdy-check:has(input:disabled),.fdy-radio:has(input:disabled),.fdy-switch:has(input:disabled){cursor:not-allowed;}
|
|
1210
|
-
.fdy-switch input{appearance:none;-webkit-appearance:none;position:relative;width:var(--control-switch-w);height:var(--control-switch-h);margin:0;border-radius:var(--radius-full);background:var(--color-control-border);cursor:pointer;transition:background var(--dur-base) var(--ease-standard);}
|
|
1217
|
+
.fdy-switch input{appearance:none;-webkit-appearance:none;flex:none;position:relative;width:var(--control-switch-w);height:var(--control-switch-h);margin:0;border-radius:var(--radius-full);background:var(--color-control-border);cursor:pointer;transition:background var(--dur-base) var(--ease-standard);}
|
|
1211
1218
|
.fdy-switch input::before{content:"";position:absolute;top:2px;left:2px;width:calc(var(--control-switch-h) - 4px);height:calc(var(--control-switch-h) - 4px);border-radius:50%;background:var(--color-switch-thumb);box-shadow:var(--shadow-1);transition:transform var(--dur-base) var(--ease-spring);}
|
|
1212
1219
|
.fdy-switch input:checked{background:var(--color-primary);}
|
|
1213
1220
|
.fdy-switch input:checked::before{transform:translateX(calc(var(--control-switch-w) - var(--control-switch-h)));}
|
|
1214
1221
|
|
|
1215
1222
|
/* Standalone checkbox (e.g. table row/select-all) — supports :indeterminate */
|
|
1216
|
-
.fdy-checkbox{appearance:none;-webkit-appearance:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);border-radius:var(--radius-sm);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1223
|
+
.fdy-checkbox{appearance:none;-webkit-appearance:none;flex:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);border-radius:var(--radius-sm);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
1217
1224
|
.fdy-checkbox::before{content:"";width:.62rem;height:.62rem;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);}
|
|
1218
1225
|
.fdy-checkbox:checked,.fdy-checkbox:indeterminate{background:var(--color-primary);border-color:var(--color-primary);}
|
|
1219
1226
|
.fdy-checkbox:checked::before{transform:scale(1);}
|
|
@@ -1486,7 +1493,19 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1486
1493
|
@media (prefers-reduced-motion:no-preference){.fdy-timepicker__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
|
|
1487
1494
|
|
|
1488
1495
|
/* Freeday — Toast (transient notification) */
|
|
1489
|
-
|
|
1496
|
+
/* The region is a POPOVER, not merely a high z-index.
|
|
1497
|
+
`.fdy-modal` is a native <dialog> opened with showModal(), which puts it in the TOP LAYER — a
|
|
1498
|
+
layer above the whole z-index universe, so no number here could ever beat it. A toast raised by
|
|
1499
|
+
an action taken inside a modal was painted behind that modal's backdrop: dimmed, unreadable, and
|
|
1500
|
+
most of it under the dialog itself. `freeday-toast.js` calls showPopover() as each toast is
|
|
1501
|
+
added, which puts the region in the top layer too, and last-in wins there.
|
|
1502
|
+
Everything after `pointer-events` is undoing the UA's [popover] defaults — it arrives with
|
|
1503
|
+
inset:0, auto margins, a border, padding, a Canvas background and overflow:auto, none of which a
|
|
1504
|
+
toast stack wants. Where showPopover() is unavailable the element is still an ordinary fixed
|
|
1505
|
+
block at z-index 200, which is exactly what it was before. */
|
|
1506
|
+
.fdy-toast-region{position:fixed;z-index:200;inset:auto;bottom:var(--space-5);right:var(--space-5);display:flex;flex-direction:column;gap:var(--space-3);width:min(24rem,calc(100vw - var(--space-8)));height:auto;max-height:none;margin:0;padding:0;border:0;background:transparent;color:inherit;overflow:visible;pointer-events:none;}
|
|
1507
|
+
/* A popover gets a ::backdrop as well; a toast must not dim the page behind it. */
|
|
1508
|
+
.fdy-toast-region::backdrop{background:transparent;}
|
|
1490
1509
|
.fdy-toast{pointer-events:auto;display:flex;gap:var(--space-3);align-items:flex-start;padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);background:var(--color-inverse-surface);color:var(--color-inverse-text);box-shadow:var(--shadow-3);}
|
|
1491
1510
|
.fdy-toast__accent{flex:none;width:.25rem;align-self:stretch;border-radius:var(--radius-full);background:var(--color-primary);}
|
|
1492
1511
|
.fdy-toast--success .fdy-toast__accent{background:var(--color-success);}
|
package/dist/freeday.js
CHANGED
|
@@ -874,7 +874,9 @@
|
|
|
874
874
|
* (var(--color-<name>)) OR a slot "chart-1".."chart-8" (var(--chart-N)) to pin a series to the
|
|
875
875
|
* validated palette. Both data-fdy-color and data-fdy-colors accept the chart-N form.
|
|
876
876
|
* Cartesian charts (line/area, multi-series/stacked bar) draw themed axes from --chart-grid /
|
|
877
|
-
* --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency).
|
|
877
|
+
* --chart-tick and format ticks + tooltips via data-fdy-format (number|percent|currency). Their
|
|
878
|
+
* viewBox is sized to the measured plot (1 user unit = 1 CSS pixel) and repainted from a
|
|
879
|
+
* ResizeObserver, so axis type stays in real pixels and is set in CSS via --fdy-chart-tick-size.
|
|
878
880
|
* Charts are an enhancement — put a table/text fallback inside the element and it will be
|
|
879
881
|
* replaced on render; give the element role="img" + aria-label.
|
|
880
882
|
*
|
|
@@ -914,6 +916,18 @@
|
|
|
914
916
|
function ensureImg(el) { if (!el.hasAttribute('role')) el.setAttribute('role', 'img'); }
|
|
915
917
|
function svgEl(name) { return document.createElementNS(NS, name); }
|
|
916
918
|
|
|
919
|
+
// Cartesian charts size their viewBox to the measured plot, so a width change needs a repaint.
|
|
920
|
+
// Guarded on width alone: a render can change the element's own height (the legend wrapping to a
|
|
921
|
+
// second row) but never its own width, so this settles instead of looping.
|
|
922
|
+
var cartesianRO = typeof ResizeObserver === 'function' ? new ResizeObserver(function (entries) {
|
|
923
|
+
entries.forEach(function (entry) {
|
|
924
|
+
var target = entry.target;
|
|
925
|
+
if (!target.isConnected) { cartesianRO.unobserve(target); return; }
|
|
926
|
+
var w = Math.round(entry.contentRect.width);
|
|
927
|
+
if (w && w !== target.__fdyChartW) renderChart(target);
|
|
928
|
+
});
|
|
929
|
+
}) : null;
|
|
930
|
+
|
|
917
931
|
// Multi-series model: data-series (JSON) wins; else the data-values single-series shortcut.
|
|
918
932
|
function getSeries(el) {
|
|
919
933
|
var raw = el.getAttribute('data-series');
|
|
@@ -1058,7 +1072,46 @@
|
|
|
1058
1072
|
var top = niceCeil(dataMax > base ? dataMax : base + 1);
|
|
1059
1073
|
if (top <= base) top = base + 1;
|
|
1060
1074
|
|
|
1061
|
-
|
|
1075
|
+
// Lay the chrome out first so the plot can be measured, then size the viewBox to it in CSS
|
|
1076
|
+
// pixels: one user unit is one pixel, so font-size / r / stroke-width mean what they say at
|
|
1077
|
+
// every container width. (A fixed 320x180 viewBox stretched by CSS multiplied every declared
|
|
1078
|
+
// size by plotWidth/320 instead — a 9px axis label rendered ~39px on a 1728px viewport.)
|
|
1079
|
+
var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
|
|
1080
|
+
var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
|
|
1081
|
+
el.classList.add('fdy-chart-xy');
|
|
1082
|
+
el.innerHTML = '';
|
|
1083
|
+
if (showLegend) {
|
|
1084
|
+
var legend = document.createElement('ul');
|
|
1085
|
+
legend.className = 'fdy-chart__legend fdy-chart__legend--row';
|
|
1086
|
+
series.forEach(function (s, si) {
|
|
1087
|
+
var li = document.createElement('li');
|
|
1088
|
+
var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorFor(si);
|
|
1089
|
+
li.appendChild(sw);
|
|
1090
|
+
li.appendChild(document.createTextNode(s.label || ('Seri ' + (si + 1))));
|
|
1091
|
+
legend.appendChild(li);
|
|
1092
|
+
});
|
|
1093
|
+
el.appendChild(legend);
|
|
1094
|
+
}
|
|
1095
|
+
var plot = document.createElement('div'); plot.className = 'fdy-chart-xy__plot';
|
|
1096
|
+
el.appendChild(plot);
|
|
1097
|
+
|
|
1098
|
+
// Measured while empty: .fdy-chart-xy__plot carries its own width / aspect-ratio / min-height,
|
|
1099
|
+
// so the box is already final. A detached or display:none chart measures 0 — fall back to the
|
|
1100
|
+
// legacy box and let the ResizeObserver repaint it once it is actually laid out.
|
|
1101
|
+
var box = plot.getBoundingClientRect();
|
|
1102
|
+
var W = Math.round(box.width) || 320, H = Math.round(box.height) || 180;
|
|
1103
|
+
var FS = parseFloat(getComputedStyle(plot).fontSize) || 12; // axis type size, owned by CSS
|
|
1104
|
+
var charW = FS * 0.6; // mono/body advance is ~0.6em
|
|
1105
|
+
|
|
1106
|
+
// Tick values are known before the gutter is, so size PL to the widest one: a currency axis
|
|
1107
|
+
// needs more room than a percent axis, and a fixed gutter only ever suited one of them.
|
|
1108
|
+
var TICKS = 4;
|
|
1109
|
+
var tickText = [];
|
|
1110
|
+
for (var t0 = 0; t0 <= TICKS; t0++) tickText.push(fmt(base + (top - base) * t0 / TICKS));
|
|
1111
|
+
var widestTick = 0;
|
|
1112
|
+
tickText.forEach(function (t) { widestTick = Math.max(widestTick, t.length * charW); });
|
|
1113
|
+
var PL = Math.min(Math.ceil(widestTick) + 10, Math.round(W * 0.35));
|
|
1114
|
+
var PR = 10, PT = 10, PB = Math.round(FS * 2.2);
|
|
1062
1115
|
var plotW = W - PL - PR, plotH = H - PT - PB;
|
|
1063
1116
|
function mapY(v) { return PT + plotH * (1 - (v - base) / (top - base)); }
|
|
1064
1117
|
var slot = plotW / n;
|
|
@@ -1070,7 +1123,6 @@
|
|
|
1070
1123
|
svg.setAttribute('aria-hidden', 'true');
|
|
1071
1124
|
|
|
1072
1125
|
// y gridlines + formatted ticks (bottom line at `base` doubles as the zero baseline).
|
|
1073
|
-
var TICKS = 4;
|
|
1074
1126
|
for (var k = 0; k <= TICKS; k++) {
|
|
1075
1127
|
var val = base + (top - base) * k / TICKS;
|
|
1076
1128
|
var gy = mapY(val);
|
|
@@ -1080,9 +1132,9 @@
|
|
|
1080
1132
|
gl.setAttribute('class', 'fdy-chart-xy__grid');
|
|
1081
1133
|
svg.appendChild(gl);
|
|
1082
1134
|
var tk = svgEl('text');
|
|
1083
|
-
tk.setAttribute('x', PL - 5); tk.setAttribute('y', (gy +
|
|
1135
|
+
tk.setAttribute('x', PL - 5); tk.setAttribute('y', (gy + FS * 0.35).toFixed(1));
|
|
1084
1136
|
tk.setAttribute('text-anchor', 'end'); tk.setAttribute('class', 'fdy-chart-xy__tick');
|
|
1085
|
-
tk.textContent =
|
|
1137
|
+
tk.textContent = tickText[k];
|
|
1086
1138
|
svg.appendChild(tk);
|
|
1087
1139
|
}
|
|
1088
1140
|
|
|
@@ -1090,7 +1142,6 @@
|
|
|
1090
1142
|
// of assuming a fixed 40px slot; keep every step-th plus the last, then drop any kept label that
|
|
1091
1143
|
// would collide with the one before it — including the forced last one (a fraction-of-a-slot
|
|
1092
1144
|
// overlap happens when n-1 lands next to the last kept multiple of step, e.g. n=24 at most widths).
|
|
1093
|
-
var charW = 5.4; // ~9px font advance (.fdy-chart-xy__xlabel)
|
|
1094
1145
|
var widest = 0;
|
|
1095
1146
|
for (var wi = 0; wi < n; wi++) widest = Math.max(widest, String(labels[wi] || ('#' + (wi + 1))).length * charW);
|
|
1096
1147
|
var minGap = widest + 8;
|
|
@@ -1106,7 +1157,7 @@
|
|
|
1106
1157
|
}
|
|
1107
1158
|
kept.forEach(function (xk) {
|
|
1108
1159
|
var xt = svgEl('text');
|
|
1109
|
-
xt.setAttribute('x', xPos(xk).toFixed(1)); xt.setAttribute('y', H -
|
|
1160
|
+
xt.setAttribute('x', xPos(xk).toFixed(1)); xt.setAttribute('y', H - Math.round(FS * 0.7));
|
|
1110
1161
|
xt.setAttribute('text-anchor', 'middle'); xt.setAttribute('class', 'fdy-chart-xy__xlabel');
|
|
1111
1162
|
xt.textContent = labels[xk] || ('#' + (xk + 1));
|
|
1112
1163
|
svg.appendChild(xt);
|
|
@@ -1132,7 +1183,7 @@
|
|
|
1132
1183
|
var rect = svgEl('rect');
|
|
1133
1184
|
rect.setAttribute('x', x.toFixed(1)); rect.setAttribute('width', Math.max(1, barW - 1).toFixed(1));
|
|
1134
1185
|
rect.setAttribute('y', Math.min(y0, y1).toFixed(1)); rect.setAttribute('height', Math.max(0.5, Math.abs(y1 - y0)).toFixed(1));
|
|
1135
|
-
rect.setAttribute('rx', '
|
|
1186
|
+
rect.setAttribute('rx', '2'); rect.setAttribute('class', 'fdy-chart-xy__bar');
|
|
1136
1187
|
rect.style.fill = colorFor(si);
|
|
1137
1188
|
svg.appendChild(rect);
|
|
1138
1189
|
});
|
|
@@ -1160,33 +1211,14 @@
|
|
|
1160
1211
|
}
|
|
1161
1212
|
pts.forEach(function (p) {
|
|
1162
1213
|
var dot = svgEl('circle');
|
|
1163
|
-
dot.setAttribute('cx', p[0].toFixed(1)); dot.setAttribute('cy', p[1].toFixed(1)); dot.setAttribute('r', '
|
|
1214
|
+
dot.setAttribute('cx', p[0].toFixed(1)); dot.setAttribute('cy', p[1].toFixed(1)); dot.setAttribute('r', '3');
|
|
1164
1215
|
dot.setAttribute('class', 'fdy-chart-xy__dot'); dot.style.fill = colorFor(si);
|
|
1165
1216
|
svg.appendChild(dot);
|
|
1166
1217
|
});
|
|
1167
1218
|
});
|
|
1168
1219
|
}
|
|
1169
1220
|
|
|
1170
|
-
// Assemble: optional legend on top, plot below, tooltip layer last.
|
|
1171
|
-
var legendMode = el.getAttribute('data-fdy-legend') || 'auto';
|
|
1172
|
-
var showLegend = legendMode !== 'none' && (series.length >= 2 || legendMode === 'always');
|
|
1173
|
-
el.classList.add('fdy-chart-xy');
|
|
1174
|
-
el.innerHTML = '';
|
|
1175
|
-
if (showLegend) {
|
|
1176
|
-
var legend = document.createElement('ul');
|
|
1177
|
-
legend.className = 'fdy-chart__legend fdy-chart__legend--row';
|
|
1178
|
-
series.forEach(function (s, si) {
|
|
1179
|
-
var li = document.createElement('li');
|
|
1180
|
-
var sw = document.createElement('span'); sw.className = 'fdy-chart__swatch'; sw.style.background = colorFor(si);
|
|
1181
|
-
li.appendChild(sw);
|
|
1182
|
-
li.appendChild(document.createTextNode(s.label || ('Seri ' + (si + 1))));
|
|
1183
|
-
legend.appendChild(li);
|
|
1184
|
-
});
|
|
1185
|
-
el.appendChild(legend);
|
|
1186
|
-
}
|
|
1187
|
-
var plot = document.createElement('div'); plot.className = 'fdy-chart-xy__plot';
|
|
1188
1221
|
plot.appendChild(svg);
|
|
1189
|
-
el.appendChild(plot);
|
|
1190
1222
|
|
|
1191
1223
|
// Hover: one transparent band per category → readout of every series at that x.
|
|
1192
1224
|
var tip = makeTip(el);
|
|
@@ -1206,6 +1238,8 @@
|
|
|
1206
1238
|
band.addEventListener('mouseleave', function () { hideTip(tip); });
|
|
1207
1239
|
svg.appendChild(band);
|
|
1208
1240
|
}
|
|
1241
|
+
el.__fdyChartW = W;
|
|
1242
|
+
if (cartesianRO) cartesianRO.observe(el);
|
|
1209
1243
|
ensureImg(el);
|
|
1210
1244
|
}
|
|
1211
1245
|
|
|
@@ -3929,11 +3963,39 @@
|
|
|
3929
3963
|
region.className = 'fdy-toast-region';
|
|
3930
3964
|
region.setAttribute('aria-live', 'polite');
|
|
3931
3965
|
region.setAttribute('aria-atomic', 'false');
|
|
3966
|
+
/* `manual`, not `auto`: an auto popover light-dismisses on an outside click and closes
|
|
3967
|
+
itself the moment another popover opens. A toast is dismissed by its timer or its own
|
|
3968
|
+
close button, by nobody else. */
|
|
3969
|
+
region.setAttribute('popover', 'manual');
|
|
3932
3970
|
document.body.appendChild(region);
|
|
3933
3971
|
}
|
|
3934
3972
|
return region;
|
|
3935
3973
|
}
|
|
3936
3974
|
|
|
3975
|
+
/* Put the region in the TOP LAYER, above any open <dialog>.
|
|
3976
|
+
*
|
|
3977
|
+
* A modal dialog lives in the top layer, where z-index does not reach — so a toast raised by an
|
|
3978
|
+
* action taken inside a modal was painted behind that modal's backdrop, which is where an error
|
|
3979
|
+
* message is least useful. showPopover() joins the same layer, and membership there is ordered by
|
|
3980
|
+
* when you joined: hide-then-show re-joins at the top, so a toast shown while a dialog is open
|
|
3981
|
+
* lands above it rather than under the dialog that was opened after the last toast.
|
|
3982
|
+
*
|
|
3983
|
+
* Guarded twice on purpose. `showPopover` is absent before Chrome 114 / Safari 17 / Firefox 125,
|
|
3984
|
+
* and the call throws if the element is disconnected or already open — in every one of those
|
|
3985
|
+
* cases the region stays an ordinary fixed block at z-index 200, which is what it has always
|
|
3986
|
+
* been. A notification must never be the thing that throws.
|
|
3987
|
+
*/
|
|
3988
|
+
function raise(node) {
|
|
3989
|
+
if (!node || typeof node.showPopover !== 'function') return;
|
|
3990
|
+
try {
|
|
3991
|
+
if (node.matches(':popover-open')) node.hidePopover();
|
|
3992
|
+
node.showPopover();
|
|
3993
|
+
} catch (e) {
|
|
3994
|
+
/* Not reachable through the guards above, but a toast is on the error path itself: if
|
|
3995
|
+
raising it fails the message still has to appear, one layer down. */
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
|
|
3937
3999
|
function dismiss(toast) {
|
|
3938
4000
|
if (typeof toast === 'string') toast = keyed[toast]; // dismiss by key
|
|
3939
4001
|
if (!toast || toast.dataset.leaving === '1') return;
|
|
@@ -4011,6 +4073,9 @@
|
|
|
4011
4073
|
keyed[opts.key] = node;
|
|
4012
4074
|
}
|
|
4013
4075
|
region.appendChild(node);
|
|
4076
|
+
// Raised per toast, not once at startup: a dialog opened AFTER the region joined the top layer
|
|
4077
|
+
// would otherwise sit above it, which is the exact case this fixes.
|
|
4078
|
+
raise(region);
|
|
4014
4079
|
|
|
4015
4080
|
var timeout = opts.timeout == null ? 4000 : opts.timeout;
|
|
4016
4081
|
if (timeout > 0) setTimeout(function () { dismiss(node); }, timeout);
|
|
@@ -171,11 +171,6 @@
|
|
|
171
171
|
data-fdy-format="number"
|
|
172
172
|
role="img"
|
|
173
173
|
aria-label="Invoiced versus collected, in millions: invoiced rose from 62 to 88, collected from 54 to 81">
|
|
174
|
-
<table>
|
|
175
|
-
<caption>Invoiced vs collected (millions)</caption>
|
|
176
|
-
<tr><th scope="col">Month</th><th scope="col">Invoiced</th><th scope="col">Collected</th></tr>
|
|
177
|
-
<tr><td>Jul</td><td>88</td><td>81</td></tr>
|
|
178
|
-
</table>
|
|
179
174
|
</div>
|
|
180
175
|
</div>
|
|
181
176
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cahyo-dimas/freeday",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.51.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",
|
|
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",
|
|
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"
|
package/src/components/chart.css
CHANGED
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
/* Cartesian charts (line / area / multi-series & stacked bar) — SVG with themed axes.
|
|
43
43
|
* Axis colour comes from --chart-grid / --chart-tick (both theme-aware); series colours are
|
|
44
44
|
* set inline by the renderer from --chart-1..8, so the whole chart recolours on data-theme. */
|
|
45
|
-
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100
|
|
46
|
-
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;}
|
|
45
|
+
.fdy-chart-xy{position:relative;display:flex;flex-direction:column;gap:var(--space-3);width:100%;--fdy-chart-tick-size:var(--text-xs);}
|
|
46
|
+
.fdy-chart-xy__plot{width:100%;aspect-ratio:16/9;min-height:8rem;font-size:var(--fdy-chart-tick-size);}
|
|
47
47
|
.fdy-chart-xy__plot svg{display:block;width:100%;height:100%;overflow:visible;}
|
|
48
48
|
.fdy-chart-xy__grid{stroke:var(--chart-grid);stroke-width:1;shape-rendering:crispEdges;}
|
|
49
|
-
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);
|
|
50
|
-
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);
|
|
49
|
+
.fdy-chart-xy__tick{fill:var(--chart-tick);font-family:var(--font-mono);}
|
|
50
|
+
.fdy-chart-xy__xlabel{fill:var(--chart-tick);font-family:var(--font-body);}
|
|
51
51
|
.fdy-chart-xy__line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round;vector-effect:non-scaling-stroke;}
|
|
52
52
|
.fdy-chart-xy__area{stroke:none;opacity:.14;}
|
|
53
53
|
.fdy-chart-xy__dot{stroke:var(--color-surface);stroke-width:1;}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/* Freeday — Selection controls */
|
|
2
|
+
/* `flex:none` on every box, track and standalone checkbox is load-bearing, not tidiness.
|
|
3
|
+
`.fdy-check` is an inline-flex row of [box, label], and a flex item with a WIDTH is still
|
|
4
|
+
shrinkable — so as soon as the label wrapped to a second line the box gave up horizontal space
|
|
5
|
+
to it and rendered an 18px-tall rectangle 13px wide. Squashed, off-square, and different from
|
|
6
|
+
every sibling whose label happened to be short: a form of five checkboxes showed three sizes.
|
|
7
|
+
Nothing in the declarations looked wrong, because the width was never overridden — it was
|
|
8
|
+
negotiated away. */
|
|
2
9
|
.fdy-check,.fdy-radio,.fdy-switch{display:inline-flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;}
|
|
3
|
-
.fdy-check input,.fdy-radio input{appearance:none;-webkit-appearance:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
10
|
+
.fdy-check input,.fdy-radio input{appearance:none;-webkit-appearance:none;flex:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
4
11
|
.fdy-check input{border-radius:var(--radius-sm);}
|
|
5
12
|
.fdy-radio input{border-radius:50%;}
|
|
6
13
|
.fdy-check input::before{content:"";width:.62rem;height:.62rem;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);}
|
|
@@ -10,13 +17,13 @@
|
|
|
10
17
|
.fdy-check input:focus-visible,.fdy-radio input:focus-visible,.fdy-switch input:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
|
|
11
18
|
.fdy-check input:disabled,.fdy-radio input:disabled,.fdy-switch input:disabled{opacity:.5;cursor:not-allowed;}
|
|
12
19
|
.fdy-check:has(input:disabled),.fdy-radio:has(input:disabled),.fdy-switch:has(input:disabled){cursor:not-allowed;}
|
|
13
|
-
.fdy-switch input{appearance:none;-webkit-appearance:none;position:relative;width:var(--control-switch-w);height:var(--control-switch-h);margin:0;border-radius:var(--radius-full);background:var(--color-control-border);cursor:pointer;transition:background var(--dur-base) var(--ease-standard);}
|
|
20
|
+
.fdy-switch input{appearance:none;-webkit-appearance:none;flex:none;position:relative;width:var(--control-switch-w);height:var(--control-switch-h);margin:0;border-radius:var(--radius-full);background:var(--color-control-border);cursor:pointer;transition:background var(--dur-base) var(--ease-standard);}
|
|
14
21
|
.fdy-switch input::before{content:"";position:absolute;top:2px;left:2px;width:calc(var(--control-switch-h) - 4px);height:calc(var(--control-switch-h) - 4px);border-radius:50%;background:var(--color-switch-thumb);box-shadow:var(--shadow-1);transition:transform var(--dur-base) var(--ease-spring);}
|
|
15
22
|
.fdy-switch input:checked{background:var(--color-primary);}
|
|
16
23
|
.fdy-switch input:checked::before{transform:translateX(calc(var(--control-switch-w) - var(--control-switch-h)));}
|
|
17
24
|
|
|
18
25
|
/* Standalone checkbox (e.g. table row/select-all) — supports :indeterminate */
|
|
19
|
-
.fdy-checkbox{appearance:none;-webkit-appearance:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);border-radius:var(--radius-sm);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
26
|
+
.fdy-checkbox{appearance:none;-webkit-appearance:none;flex:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);border-radius:var(--radius-sm);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
|
|
20
27
|
.fdy-checkbox::before{content:"";width:.62rem;height:.62rem;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);}
|
|
21
28
|
.fdy-checkbox:checked,.fdy-checkbox:indeterminate{background:var(--color-primary);border-color:var(--color-primary);}
|
|
22
29
|
.fdy-checkbox:checked::before{transform:scale(1);}
|
package/src/components/toast.css
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
/* Freeday — Toast (transient notification) */
|
|
2
|
-
|
|
2
|
+
/* The region is a POPOVER, not merely a high z-index.
|
|
3
|
+
`.fdy-modal` is a native <dialog> opened with showModal(), which puts it in the TOP LAYER — a
|
|
4
|
+
layer above the whole z-index universe, so no number here could ever beat it. A toast raised by
|
|
5
|
+
an action taken inside a modal was painted behind that modal's backdrop: dimmed, unreadable, and
|
|
6
|
+
most of it under the dialog itself. `freeday-toast.js` calls showPopover() as each toast is
|
|
7
|
+
added, which puts the region in the top layer too, and last-in wins there.
|
|
8
|
+
Everything after `pointer-events` is undoing the UA's [popover] defaults — it arrives with
|
|
9
|
+
inset:0, auto margins, a border, padding, a Canvas background and overflow:auto, none of which a
|
|
10
|
+
toast stack wants. Where showPopover() is unavailable the element is still an ordinary fixed
|
|
11
|
+
block at z-index 200, which is exactly what it was before. */
|
|
12
|
+
.fdy-toast-region{position:fixed;z-index:200;inset:auto;bottom:var(--space-5);right:var(--space-5);display:flex;flex-direction:column;gap:var(--space-3);width:min(24rem,calc(100vw - var(--space-8)));height:auto;max-height:none;margin:0;padding:0;border:0;background:transparent;color:inherit;overflow:visible;pointer-events:none;}
|
|
13
|
+
/* A popover gets a ::backdrop as well; a toast must not dim the page behind it. */
|
|
14
|
+
.fdy-toast-region::backdrop{background:transparent;}
|
|
3
15
|
.fdy-toast{pointer-events:auto;display:flex;gap:var(--space-3);align-items:flex-start;padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);background:var(--color-inverse-surface);color:var(--color-inverse-text);box-shadow:var(--shadow-3);}
|
|
4
16
|
.fdy-toast__accent{flex:none;width:.25rem;align-self:stretch;border-radius:var(--radius-full);background:var(--color-primary);}
|
|
5
17
|
.fdy-toast--success .fdy-toast__accent{background:var(--color-success);}
|