@cahyo-dimas/freeday 3.14.0 → 3.14.2
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 +66 -0
- package/COMPONENTS.md +5 -0
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/adapters/react/index.d.ts +2 -1
- package/adapters/react/useFreeday.js +20 -2
- package/adapters/vue/index.d.ts +2 -1
- package/adapters/vue/useFreeday.js +15 -1
- package/dist/freeday.bundle.css +15 -4
- package/dist/freeday.css +15 -4
- package/docs/getting-started.md +1 -1
- package/package.json +2 -2
- package/src/components/app-shell.css +2 -1
- package/src/components/drawer.css +2 -1
- package/src/components/modal.css +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,72 @@ benar. Perubahan seperti itu ditulis di bawah `### Changed: BREAKING (types)`
|
|
|
9
9
|
lama → tipe baru, dan cara menyempitkannya — bukan di bawah `### Added`, betapapun aditifnya dari
|
|
10
10
|
sisi kit.
|
|
11
11
|
|
|
12
|
+
## [3.14.2] - 2026-10-30
|
|
13
|
+
|
|
14
|
+
**`useFreeday(ref)` dengan ref yang masih kosong kini bersuara.** Ref yang lupa dipasang di template
|
|
15
|
+
membuat adapter memindai seluruh dokumen tanpa tanda apa pun. Hasilnya bekerja, hanya lebih luas
|
|
16
|
+
dari yang diminta, dan markup lain di halaman diam-diam ikut bergantung padanya sampai hari ref itu
|
|
17
|
+
dipasang (`CATATAN-FREEDAY.md` §17). Kebalikan dari cakupan yang terlalu sempit di 3.13.0 `#099`.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **Vue dan React: satu `console.warn` per komponen ketika ref yang diberikan ke `useFreeday` masih
|
|
21
|
+
`null` saat mount** (`#105`). Di Vue diperiksa di `onMounted`, di React sesudah commit pertama;
|
|
22
|
+
pembaruan sesudahnya tidak mengulangnya. Pesannya menyebut dua penyebab (ref tak terpasang, atau
|
|
23
|
+
terpasang di elemen di balik `v-if`/kondisi) dan dua jalan keluarnya (pasang di elemen yang selalu
|
|
24
|
+
dirender, atau `useFreeday()` tanpa argumen bila seluruh dokumen memang yang dimaksud).
|
|
25
|
+
|
|
26
|
+
### Catatan
|
|
27
|
+
- **Yang dipindai tidak berubah.** Ref kosong tetap jatuh ke seluruh dokumen. Adapter tidak dapat
|
|
28
|
+
membedakan ref yang lupa dari ref yang sekadar terlambat, dan mempersempitnya sekarang akan
|
|
29
|
+
mematikan dengan diam markup yang hari ini hanya hidup karena pemindaian itu, persis kelas cacat
|
|
30
|
+
`#099`. Peringatan ini memberi aplikasi waktu untuk membereskannya lebih dulu.
|
|
31
|
+
- Peringatan dinyalakan di setiap build, bukan hanya mode dev, sama seperti
|
|
32
|
+
`freeday-unhydrated.js`: kit tidak punya bendera build, dan peringatan ini hanya muncul pada kode
|
|
33
|
+
yang memang keliru.
|
|
34
|
+
- Blazor tidak dicakup: `ElementReference` adalah struct, bukan ref yang dapat `null`, dan apa yang
|
|
35
|
+
sampai ke `FreedayBlazor.initAll` dari `@ref` yang lupa ditangkap belum diukur.
|
|
36
|
+
|
|
37
|
+
## [3.14.1] - 2026-10-30
|
|
38
|
+
|
|
39
|
+
**Badan `FdyModal` kembali setinggi isinya di iPhone.** Di iPhone 7 sungguhan (iOS 15), setiap modal
|
|
40
|
+
menampilkan kepala dan kaki dengan badan tinggal selarik, tergulir sampai dasar, dan formulirnya
|
|
41
|
+
tak terlihat. "Tambah security" dan "Tulis info" di app ronda tak dapat dipakai sama sekali dari HP
|
|
42
|
+
(`CATATAN-FREEDAY.md` §16).
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- **`.fdy-modal__body` memakai `flex:1 1 auto`, bukan `flex:1`** (`#104`). Basis shorthand itu 0%,
|
|
46
|
+
dan `.fdy-modal` hanya punya `max-height`, sehingga persentasenya diresolusikan terhadap tinggi
|
|
47
|
+
yang tak tentu. Mesin terkini membacanya sebagai `content`, sedangkan WebKit lama membacanya
|
|
48
|
+
sebagai 0: dialog setinggi kepala ditambah kaki, dan badan tinggal padding 40px dengan
|
|
49
|
+
formulirnya meluap di dalamnya. Basis `0px` mereproduksinya di Chrome sampai ke pikselnya (badan
|
|
50
|
+
39,7px untuk isi 358px), dan `auto` menghasilkan tata letak yang identik dengan sebelumnya di mesin
|
|
51
|
+
terkini (dialog 498,7px, badan 355,7px, di kedua bacaan). Mekanismenya terukur. Bahwa Safari 15
|
|
52
|
+
memang membaca 0% sebagai 0 disimpulkan dari gejalanya dan belum diukur di perangkat, sebab tak
|
|
53
|
+
ada WebKit setua itu yang dapat dijalankan di sini.
|
|
54
|
+
- **`100vh` diikuti `100dvh`** di `max-height` modal, tinggi drawer, dan tinggi sidebar sticky
|
|
55
|
+
(`#104`). Di Safari iOS `100vh` adalah viewport besar, seolah bilah alat tak ada, jadi kotak yang
|
|
56
|
+
diukur dengannya berjalan ke bawah bilah itu. Sidebar overlay sudah memakai `100dvh` sejak lama;
|
|
57
|
+
tiga tempat ini tertinggal. Mesin tanpa `dvh` memakai nilai pertama.
|
|
58
|
+
|
|
59
|
+
### Catatan
|
|
60
|
+
- Dugaan di catatan app, bahwa `100vh` penyebab utamanya, tidak cocok dengan gejalanya: `100vh` yang
|
|
61
|
+
kebesaran membuat dialog terlalu tinggi, bukan badannya terlalu pendek. Tetap diperbaiki karena
|
|
62
|
+
ia cacat tersendiri.
|
|
63
|
+
- Drawer dan nav sidebar memakai pola `flex:1` yang sama tetapi tidak terkena, sebab kontainernya
|
|
64
|
+
punya tinggi pasti.
|
|
65
|
+
|
|
66
|
+
### Gerbang
|
|
67
|
+
- `css.test.mjs`: item flex yang menjadi wilayah gulir (`min-height:0` + `overflow`) tak boleh
|
|
68
|
+
memakai basis nol atau persen, kecuali kontainernya bertinggi pasti dan tercatat. Setiap `100vh`
|
|
69
|
+
yang mengukur kotak wajib diikuti `100dvh`, kecuali tinggi minimum halaman.
|
|
70
|
+
- `browser/modal-phone.mjs` di 375×667: badan modal pendek setinggi isinya, modal panjang berhenti
|
|
71
|
+
di `max-height` dengan kaki tetap di layar, dan basis terkomputasi `auto`. Dengan basis `0px`
|
|
72
|
+
uji ini merah dengan "40px shown of 358px", angka yang sama dengan di iPhone.
|
|
73
|
+
- `webkit/modal-phone.spec.mjs`: tata letak yang sama di WebKit dengan deskriptor iPhone 7 di CI. WebKit
|
|
74
|
+
Playwright adalah mesin terkini yang membaca 0% dengan benar, jadi berkas ini menjaga hasilnya,
|
|
75
|
+
bukan penyebabnya; penyebabnya dijaga dua gerbang di atas.
|
|
76
|
+
- Empat mutasi diverifikasi merah.
|
|
77
|
+
|
|
12
78
|
## [3.14.0] - 2026-10-29
|
|
13
79
|
|
|
14
80
|
**Daftar terurut lewat seret dan papan ketik, dan satu atribut untuk berkata "ini bukan urusan
|
package/COMPONENTS.md
CHANGED
|
@@ -52,6 +52,11 @@ live docs.
|
|
|
52
52
|
never enhanced logs one `console.warn` naming it, with the element attached. The usual cause is
|
|
53
53
|
a `useFreeday()` ref on a wrapper that does not contain it. `freeday-unhydrated.js` does this,
|
|
54
54
|
and it ships in the root import.
|
|
55
|
+
**A `useFreeday(ref)` whose ref is still null at mount says so too.** That is a ref left off the
|
|
56
|
+
template, or one on an element behind `v-if` / a condition, and the call falls back to the whole
|
|
57
|
+
document: everything works, wider than asked, and other markup on the page can come to rely on
|
|
58
|
+
that scan unnoticed. It logs one `console.warn` per component. Put the ref on an element that is
|
|
59
|
+
always rendered, or write `useFreeday()` with no argument when the document is what you mean.
|
|
55
60
|
**`data-fdy-ignore` says "not yours".** On an element or any ancestor, every enhancer skips that
|
|
56
61
|
subtree, even under an explicit `initAll(document)`, and the warning above stays quiet. Use it
|
|
57
62
|
where the app drives kit-styled markup itself, such as a chip matrix with its own removal flow.
|
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/v3.14.2)
|
|
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/v3.14.2)
|
|
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`.
|
|
@@ -8,7 +8,8 @@ export interface UseFreedayReturn {
|
|
|
8
8
|
/**
|
|
9
9
|
* Auto-initialise Freeday enhancers over a component's subtree on mount and
|
|
10
10
|
* after every commit. Pass a ref to scope the work; omit it to scan the whole
|
|
11
|
-
* document.
|
|
11
|
+
* document. A ref that is still null after the first commit also scans the
|
|
12
|
+
* whole document, and warns once: bind it to an always-rendered element, or omit it.
|
|
12
13
|
*/
|
|
13
14
|
export declare function useFreeday(rootRef?: RefObject<HTMLElement | null>): UseFreedayReturn;
|
|
14
15
|
|
|
@@ -14,10 +14,16 @@
|
|
|
14
14
|
* well as descendants, so both shapes work. (Before 1.23.0 only the wrapping shape did, and
|
|
15
15
|
* the other failed silently: querySelectorAll never matches its root.)
|
|
16
16
|
*
|
|
17
|
+
* A ref that is still null after the first commit is a mistake, and an unusually quiet one: the
|
|
18
|
+
* call falls back to the whole document, so everything works, only wider than asked, and other code
|
|
19
|
+
* on the page can come to depend on that scan without knowing it. It warns once, after the first
|
|
20
|
+
* commit. Pass no argument, useFreeday(), when the whole document is what you mean; and put the ref
|
|
21
|
+
* on an element that is always rendered, not one behind a condition.
|
|
22
|
+
*
|
|
17
23
|
* Events are native bubbling CustomEvents, add listeners on the root (they
|
|
18
24
|
* bubble) and read event.detail. See index.d.ts for the detail types.
|
|
19
25
|
*/
|
|
20
|
-
import { useEffect, useCallback } from 'react';
|
|
26
|
+
import { useEffect, useCallback, useRef } from 'react';
|
|
21
27
|
|
|
22
28
|
// Discover Freeday enhancer namespaces once (all registered by the time an app
|
|
23
29
|
// mounts, since `import 'freeday'` runs first). Each exposes initAll(el?).
|
|
@@ -36,7 +42,13 @@ function freedayApis() {
|
|
|
36
42
|
return _apis;
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
var EMPTY_REF = 'Freeday: useFreeday(ref) got a ref that is still null after the first commit, so it hydrated '
|
|
46
|
+
+ 'the whole document instead of one subtree. Either ref={...} is missing from the JSX, or it sits on an '
|
|
47
|
+
+ 'element that is not rendered yet (a condition). Put the ref on an element that is always rendered, or '
|
|
48
|
+
+ 'call useFreeday() with no argument if the whole document is what you mean.';
|
|
49
|
+
|
|
39
50
|
export function useFreeday(rootRef) {
|
|
51
|
+
var checked = useRef(false);
|
|
40
52
|
var rehydrate = useCallback(function () {
|
|
41
53
|
var el = rootRef && rootRef.current ? rootRef.current : undefined;
|
|
42
54
|
var apis = freedayApis();
|
|
@@ -44,6 +56,12 @@ export function useFreeday(rootRef) {
|
|
|
44
56
|
}, [rootRef]);
|
|
45
57
|
// No dependency array: runs after every commit (mount + updates), mirroring
|
|
46
58
|
// the Vue adapter's onMounted + onUpdated.
|
|
47
|
-
useEffect(
|
|
59
|
+
useEffect(function () {
|
|
60
|
+
if (!checked.current) {
|
|
61
|
+
checked.current = true;
|
|
62
|
+
if (rootRef && !rootRef.current) console.warn(EMPTY_REF);
|
|
63
|
+
}
|
|
64
|
+
rehydrate();
|
|
65
|
+
});
|
|
48
66
|
return { rehydrate: rehydrate };
|
|
49
67
|
}
|
package/adapters/vue/index.d.ts
CHANGED
|
@@ -8,7 +8,8 @@ export interface UseFreedayReturn {
|
|
|
8
8
|
/**
|
|
9
9
|
* Auto-initialise Freeday enhancers over a component's subtree on mount and
|
|
10
10
|
* after every update. Pass a template ref to scope the work; omit it to scan
|
|
11
|
-
* the whole document.
|
|
11
|
+
* the whole document. A ref that is still null at mount also scans the whole
|
|
12
|
+
* document, and warns once: bind it to an always-rendered element, or omit it.
|
|
12
13
|
*/
|
|
13
14
|
export declare function useFreeday(rootRef?: Ref<HTMLElement | null>): UseFreedayReturn;
|
|
14
15
|
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
* well as descendants, so both shapes work. (Before 1.23.0 only the wrapping shape did, and
|
|
16
16
|
* the other failed silently: querySelectorAll never matches its root.)
|
|
17
17
|
*
|
|
18
|
+
* A ref that is still null once the component has mounted is a mistake, and an unusually quiet
|
|
19
|
+
* one: the call falls back to the whole document, so everything works, only wider than asked, and
|
|
20
|
+
* other code on the page can come to depend on that scan without knowing it. It warns once, at
|
|
21
|
+
* mount. Pass no argument, useFreeday(), when the whole document is what you mean; and put the ref
|
|
22
|
+
* on an element that is always rendered, not one behind v-if.
|
|
23
|
+
*
|
|
18
24
|
* Events come straight through as native bubbling CustomEvents, bind them with
|
|
19
25
|
* plain v-on (e.g. @fdy-cascade-change) and read event.detail. See index.d.ts
|
|
20
26
|
* for the detail types.
|
|
@@ -36,6 +42,11 @@ function freedayApis() {
|
|
|
36
42
|
return apis;
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
var EMPTY_REF = 'Freeday: useFreeday(ref) got a ref that is still null after mount, so it hydrated the whole '
|
|
46
|
+
+ 'document instead of one subtree. Either ref="..." is missing from the template, or it sits on an element '
|
|
47
|
+
+ 'that is not rendered yet (v-if). Put the ref on an element that is always rendered, or call useFreeday() '
|
|
48
|
+
+ 'with no argument if the whole document is what you mean.';
|
|
49
|
+
|
|
39
50
|
export function useFreeday(rootRef) {
|
|
40
51
|
var apis = null;
|
|
41
52
|
function rehydrate() {
|
|
@@ -43,7 +54,10 @@ export function useFreeday(rootRef) {
|
|
|
43
54
|
var el = rootRef && 'value' in rootRef ? (rootRef.value || undefined) : undefined;
|
|
44
55
|
for (var i = 0; i < apis.length; i++) apis[i].initAll(el);
|
|
45
56
|
}
|
|
46
|
-
onMounted(
|
|
57
|
+
onMounted(function () {
|
|
58
|
+
if (rootRef && 'value' in rootRef && !rootRef.value) console.warn(EMPTY_REF);
|
|
59
|
+
rehydrate();
|
|
60
|
+
});
|
|
47
61
|
onUpdated(function () { nextTick(rehydrate); });
|
|
48
62
|
return { rehydrate: rehydrate };
|
|
49
63
|
}
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -1285,7 +1285,8 @@ a { color: var(--color-primary-strong); }
|
|
|
1285
1285
|
* > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
|
|
1286
1286
|
* topbar a tall containing block so it can travel, a bare grid cell can't. */
|
|
1287
1287
|
.fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
|
|
1288
|
-
|
|
1288
|
+
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
|
|
1289
|
+
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;height:100dvh;overflow:hidden;flex:none;width:var(--fdy-app-sidebar-w,15.5rem);box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
1289
1290
|
/* `min-height:0` is the height-axis twin of `min-width:0`, and its absence was invisible for as long
|
|
1290
1291
|
* as the shell scrolled the PAGE (#055 §2). A consumer that pins the shell to the viewport and
|
|
1291
1292
|
* scrolls .fdy-app__main instead hits it immediately: a column flex item's automatic minimum size is
|
|
@@ -2472,7 +2473,8 @@ a { color: var(--color-primary-strong); }
|
|
|
2472
2473
|
/* display is gated behind [open] so the native <dialog> stays hidden until opened.
|
|
2473
2474
|
* An unconditional display here would beat the UA `dialog:not([open]){display:none}`
|
|
2474
2475
|
* rule (author origin wins over user-agent) and leave the drawer visible on load. */
|
|
2475
|
-
|
|
2476
|
+
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
|
|
2477
|
+
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;height:100dvh;max-height:none;width:min(20rem,82vw);flex-direction:column;border:0;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-4),var(--surface-inset);overflow:hidden;transform:translateX(-100%);transition-property:transform,overlay,display;transition-duration:var(--dur-slow);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
|
|
2476
2478
|
.fdy-drawer[open]{display:flex;transform:none;}
|
|
2477
2479
|
.fdy-drawer--right{inset:0 0 0 auto;transform:translateX(100%);}
|
|
2478
2480
|
.fdy-drawer::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition-property:opacity,overlay,display;transition-duration:var(--dur-slow);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
|
|
@@ -2779,7 +2781,8 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
2779
2781
|
.fdy-btn-split__toggle svg{display:block;width:1rem;height:1rem;}
|
|
2780
2782
|
|
|
2781
2783
|
/* Freeday. Modal (native <dialog>) */
|
|
2782
|
-
|
|
2784
|
+
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
|
|
2785
|
+
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - var(--space-16));max-height:calc(100dvh - var(--space-16));padding:0;border:var(--bw) solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-lift-hover),var(--surface-inset);overflow:hidden;flex-direction:column;opacity:0;transform:translateY(8px) scale(.98);/* Longhands, not the `transition` shorthand. A shorthand carrying one keyword the engine does not know is INVALID AT PARSE, so the whole declaration is thrown away -- and `allow-discrete` is unknown below Safari 17.4, which is under this kit's floor. The result was a modal and a drawer that opened and closed with no animation at all on an iPhone 7. Split this way only `transition-behavior` is dropped there, and the opacity/transform half survives. The lists align BY INDEX and reproduce the shorthand exactly, including `ease` for the discrete pair and `normal` for the continuous one -- verified by comparing computed styles before and after (#097). */transition-property:opacity,transform,overlay,display;transition-duration:var(--dur-base);transition-timing-function:var(--ease-spring),var(--ease-spring),ease,ease;transition-behavior:normal,normal,allow-discrete,allow-discrete;}
|
|
2783
2786
|
.fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
|
|
2784
2787
|
.fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
|
|
2785
2788
|
.fdy-modal--lg{width:min(48rem,calc(100vw - var(--space-8)));}
|
|
@@ -2789,7 +2792,15 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
2789
2792
|
.fdy-modal__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text);margin:0 auto 0 0;}
|
|
2790
2793
|
.fdy-modal__close{border:0;background:transparent;color:var(--color-text-muted);font-size:var(--text-xl);line-height:1;cursor:pointer;padding:var(--space-1);border-radius:var(--radius-sm);}
|
|
2791
2794
|
.fdy-modal__close:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
2792
|
-
|
|
2795
|
+
/* `1 1 auto`, NOT `flex:1`. The shorthand's basis is 0%, and `.fdy-modal` has only a max-height, so
|
|
2796
|
+
* the percentage resolves against an indefinite height. Current engines read that as `content`;
|
|
2797
|
+
* older WebKit read it as 0, sized the dialog to header + footer, and left the body its 40px of
|
|
2798
|
+
* padding, with the form overflowing inside it. That was every modal on an iPhone 7 (iOS 15),
|
|
2799
|
+
* reported from a real device (CATATAN-FREEDAY §16). A 0px basis reproduces it in Chrome to the
|
|
2800
|
+
* pixel. `auto` lays out identically in current engines (measured: 498.7px dialog, 355.7px body,
|
|
2801
|
+
* both readings) and leaves no indefinite percentage for any engine to read. min-height:0 is
|
|
2802
|
+
* still what lets a long body shrink and scroll once the dialog reaches its max-height. */
|
|
2803
|
+
.fdy-modal__body{flex:1 1 auto;min-height:0;padding:var(--space-5);overflow:auto;}
|
|
2793
2804
|
.fdy-modal__footer{flex:none;display:flex;gap:var(--space-2);justify-content:flex-end;padding:var(--space-4) var(--space-5);border-top:var(--bw) solid var(--color-border-muted);background:var(--color-surface-2);}
|
|
2794
2805
|
/* Symmetric enter AND exit (fade + rise + subtle scale) on the native <dialog>. @starting-style
|
|
2795
2806
|
* animates the entry; overlay/display allow-discrete keep it painted through the exit so it
|
package/dist/freeday.css
CHANGED
|
@@ -108,7 +108,8 @@ a { color: var(--color-primary-strong); }
|
|
|
108
108
|
* > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
|
|
109
109
|
* topbar a tall containing block so it can travel, a bare grid cell can't. */
|
|
110
110
|
.fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
|
|
111
|
-
|
|
111
|
+
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
|
|
112
|
+
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;height:100dvh;overflow:hidden;flex:none;width:var(--fdy-app-sidebar-w,15.5rem);box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
112
113
|
/* `min-height:0` is the height-axis twin of `min-width:0`, and its absence was invisible for as long
|
|
113
114
|
* as the shell scrolled the PAGE (#055 §2). A consumer that pins the shell to the viewport and
|
|
114
115
|
* scrolls .fdy-app__main instead hits it immediately: a column flex item's automatic minimum size is
|
|
@@ -1295,7 +1296,8 @@ a { color: var(--color-primary-strong); }
|
|
|
1295
1296
|
/* display is gated behind [open] so the native <dialog> stays hidden until opened.
|
|
1296
1297
|
* An unconditional display here would beat the UA `dialog:not([open]){display:none}`
|
|
1297
1298
|
* rule (author origin wins over user-agent) and leave the drawer visible on load. */
|
|
1298
|
-
|
|
1299
|
+
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
|
|
1300
|
+
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;height:100dvh;max-height:none;width:min(20rem,82vw);flex-direction:column;border:0;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-4),var(--surface-inset);overflow:hidden;transform:translateX(-100%);transition-property:transform,overlay,display;transition-duration:var(--dur-slow);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
|
|
1299
1301
|
.fdy-drawer[open]{display:flex;transform:none;}
|
|
1300
1302
|
.fdy-drawer--right{inset:0 0 0 auto;transform:translateX(100%);}
|
|
1301
1303
|
.fdy-drawer::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition-property:opacity,overlay,display;transition-duration:var(--dur-slow);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
|
|
@@ -1602,7 +1604,8 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1602
1604
|
.fdy-btn-split__toggle svg{display:block;width:1rem;height:1rem;}
|
|
1603
1605
|
|
|
1604
1606
|
/* Freeday. Modal (native <dialog>) */
|
|
1605
|
-
|
|
1607
|
+
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
|
|
1608
|
+
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - var(--space-16));max-height:calc(100dvh - var(--space-16));padding:0;border:var(--bw) solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-lift-hover),var(--surface-inset);overflow:hidden;flex-direction:column;opacity:0;transform:translateY(8px) scale(.98);/* Longhands, not the `transition` shorthand. A shorthand carrying one keyword the engine does not know is INVALID AT PARSE, so the whole declaration is thrown away -- and `allow-discrete` is unknown below Safari 17.4, which is under this kit's floor. The result was a modal and a drawer that opened and closed with no animation at all on an iPhone 7. Split this way only `transition-behavior` is dropped there, and the opacity/transform half survives. The lists align BY INDEX and reproduce the shorthand exactly, including `ease` for the discrete pair and `normal` for the continuous one -- verified by comparing computed styles before and after (#097). */transition-property:opacity,transform,overlay,display;transition-duration:var(--dur-base);transition-timing-function:var(--ease-spring),var(--ease-spring),ease,ease;transition-behavior:normal,normal,allow-discrete,allow-discrete;}
|
|
1606
1609
|
.fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
|
|
1607
1610
|
.fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
|
|
1608
1611
|
.fdy-modal--lg{width:min(48rem,calc(100vw - var(--space-8)));}
|
|
@@ -1612,7 +1615,15 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1612
1615
|
.fdy-modal__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text);margin:0 auto 0 0;}
|
|
1613
1616
|
.fdy-modal__close{border:0;background:transparent;color:var(--color-text-muted);font-size:var(--text-xl);line-height:1;cursor:pointer;padding:var(--space-1);border-radius:var(--radius-sm);}
|
|
1614
1617
|
.fdy-modal__close:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
1615
|
-
|
|
1618
|
+
/* `1 1 auto`, NOT `flex:1`. The shorthand's basis is 0%, and `.fdy-modal` has only a max-height, so
|
|
1619
|
+
* the percentage resolves against an indefinite height. Current engines read that as `content`;
|
|
1620
|
+
* older WebKit read it as 0, sized the dialog to header + footer, and left the body its 40px of
|
|
1621
|
+
* padding, with the form overflowing inside it. That was every modal on an iPhone 7 (iOS 15),
|
|
1622
|
+
* reported from a real device (CATATAN-FREEDAY §16). A 0px basis reproduces it in Chrome to the
|
|
1623
|
+
* pixel. `auto` lays out identically in current engines (measured: 498.7px dialog, 355.7px body,
|
|
1624
|
+
* both readings) and leaves no indefinite percentage for any engine to read. min-height:0 is
|
|
1625
|
+
* still what lets a long body shrink and scroll once the dialog reaches its max-height. */
|
|
1626
|
+
.fdy-modal__body{flex:1 1 auto;min-height:0;padding:var(--space-5);overflow:auto;}
|
|
1616
1627
|
.fdy-modal__footer{flex:none;display:flex;gap:var(--space-2);justify-content:flex-end;padding:var(--space-4) var(--space-5);border-top:var(--bw) solid var(--color-border-muted);background:var(--color-surface-2);}
|
|
1617
1628
|
/* Symmetric enter AND exit (fade + rise + subtle scale) on the native <dialog>. @starting-style
|
|
1618
1629
|
* animates the entry; overlay/display allow-discrete keep it painted through the exit so it
|
package/docs/getting-started.md
CHANGED
|
@@ -198,7 +198,7 @@ live docs also have a copy button per component.
|
|
|
198
198
|
```bash
|
|
199
199
|
npm i @cahyo-dimas/freeday
|
|
200
200
|
```
|
|
201
|
-
Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.14.
|
|
201
|
+
Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.14.2"` (public npm package). `dist/` is
|
|
202
202
|
committed and published → no build step; `npm ci` runs without auth.
|
|
203
203
|
|
|
204
204
|
### 2. Import the CSS + enhancers **once** in your entry (`src/main.ts`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cahyo-dimas/freeday",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.2",
|
|
4
4
|
"description": "Freeday: token-driven, framework-agnostic UI KIT (design source-of-truth).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"scripts": {
|
|
97
97
|
"build": "node tokens/build.mjs",
|
|
98
98
|
"test": "node --test",
|
|
99
|
-
"test:browser": "node --test --test-concurrency=3 browser/color-mix.mjs 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/field-width.mjs browser/cfl-multi.mjs browser/cfl-dialog-only.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs browser/bottom-nav.mjs browser/picker-states.mjs browser/tabs-strip.mjs browser/tabs-vertical.mjs browser/menu-align.mjs browser/cfl-narrow.mjs browser/form-reveal.mjs browser/confirm-search-copy.mjs browser/daterange-settled.mjs browser/display-parts.mjs browser/table-empty.mjs browser/table-footer-text.mjs browser/month-mode.mjs browser/monthpicker.mjs browser/page-columns.mjs browser/table-hover.mjs browser/typed-popover.mjs browser/filter-parity.mjs browser/filter-wrap.mjs browser/filter-stack.mjs browser/timepicker.mjs browser/form-kit-required.mjs browser/picker-in-modal.mjs browser/harness-contract.mjs browser/app-owned.mjs browser/sortable.mjs",
|
|
99
|
+
"test:browser": "node --test --test-concurrency=3 browser/color-mix.mjs 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/field-width.mjs browser/cfl-multi.mjs browser/cfl-dialog-only.mjs browser/crowding.mjs browser/over-dialog.mjs browser/chart-scale.mjs browser/overlay-stack.mjs browser/chart-a11y.mjs browser/app-shell.mjs browser/bottom-nav.mjs browser/picker-states.mjs browser/tabs-strip.mjs browser/tabs-vertical.mjs browser/menu-align.mjs browser/cfl-narrow.mjs browser/form-reveal.mjs browser/confirm-search-copy.mjs browser/daterange-settled.mjs browser/display-parts.mjs browser/table-empty.mjs browser/table-footer-text.mjs browser/month-mode.mjs browser/monthpicker.mjs browser/page-columns.mjs browser/table-hover.mjs browser/typed-popover.mjs browser/filter-parity.mjs browser/filter-wrap.mjs browser/filter-stack.mjs browser/timepicker.mjs browser/form-kit-required.mjs browser/picker-in-modal.mjs browser/harness-contract.mjs browser/app-owned.mjs browser/sortable.mjs browser/modal-phone.mjs browser/empty-ref.mjs",
|
|
100
100
|
"prepack": "node tokens/build.mjs",
|
|
101
101
|
"version": "node tokens/build.mjs && git add dist",
|
|
102
102
|
"typecheck:react": "tsc -p adapters/react/tsconfig.json --noEmit",
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* > (.fdy-app__topbar, .fdy-app__main)). The __content wrapper gives the sticky
|
|
7
7
|
* topbar a tall containing block so it can travel, a bare grid cell can't. */
|
|
8
8
|
.fdy-app{display:flex;align-items:stretch;min-height:100vh;background:var(--color-surface-2);}
|
|
9
|
-
|
|
9
|
+
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
|
|
10
|
+
.fdy-app__sidebar{position:sticky;top:0;align-self:flex-start;height:100vh;height:100dvh;overflow:hidden;flex:none;width:var(--fdy-app-sidebar-w,15.5rem);box-sizing:border-box;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);border-right:var(--bw) solid var(--color-border);display:flex;flex-direction:column;transition:width var(--dur-slow) var(--ease-standard),border-right-width var(--dur-slow) var(--ease-standard);}
|
|
10
11
|
/* `min-height:0` is the height-axis twin of `min-width:0`, and its absence was invisible for as long
|
|
11
12
|
* as the shell scrolled the PAGE (#055 §2). A consumer that pins the shell to the viewport and
|
|
12
13
|
* scrolls .fdy-app__main instead hits it immediately: a column flex item's automatic minimum size is
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
/* display is gated behind [open] so the native <dialog> stays hidden until opened.
|
|
5
5
|
* An unconditional display here would beat the UA `dialog:not([open]){display:none}`
|
|
6
6
|
* rule (author origin wins over user-agent) and leave the drawer visible on load. */
|
|
7
|
-
|
|
7
|
+
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
|
|
8
|
+
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;height:100dvh;max-height:none;width:min(20rem,82vw);flex-direction:column;border:0;background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-4),var(--surface-inset);overflow:hidden;transform:translateX(-100%);transition-property:transform,overlay,display;transition-duration:var(--dur-slow);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
|
|
8
9
|
.fdy-drawer[open]{display:flex;transform:none;}
|
|
9
10
|
.fdy-drawer--right{inset:0 0 0 auto;transform:translateX(100%);}
|
|
10
11
|
.fdy-drawer::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition-property:opacity,overlay,display;transition-duration:var(--dur-slow);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
|
package/src/components/modal.css
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Freeday. Modal (native <dialog>) */
|
|
2
|
-
|
|
2
|
+
/* 100vh then 100dvh: on iOS Safari 100vh is the LARGE viewport, as if the toolbar were gone, so a box sized by it runs under the toolbar. An engine without dvh keeps the first value. */
|
|
3
|
+
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - var(--space-16));max-height:calc(100dvh - var(--space-16));padding:0;border:var(--bw) solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-surface-raised);backdrop-filter:var(--surface-filter);-webkit-backdrop-filter:var(--surface-filter);color:var(--color-text);box-shadow:var(--shadow-lift-hover),var(--surface-inset);overflow:hidden;flex-direction:column;opacity:0;transform:translateY(8px) scale(.98);/* Longhands, not the `transition` shorthand. A shorthand carrying one keyword the engine does not know is INVALID AT PARSE, so the whole declaration is thrown away -- and `allow-discrete` is unknown below Safari 17.4, which is under this kit's floor. The result was a modal and a drawer that opened and closed with no animation at all on an iPhone 7. Split this way only `transition-behavior` is dropped there, and the opacity/transform half survives. The lists align BY INDEX and reproduce the shorthand exactly, including `ease` for the discrete pair and `normal` for the continuous one -- verified by comparing computed styles before and after (#097). */transition-property:opacity,transform,overlay,display;transition-duration:var(--dur-base);transition-timing-function:var(--ease-spring),var(--ease-spring),ease,ease;transition-behavior:normal,normal,allow-discrete,allow-discrete;}
|
|
3
4
|
.fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
|
|
4
5
|
.fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
|
|
5
6
|
.fdy-modal--lg{width:min(48rem,calc(100vw - var(--space-8)));}
|
|
@@ -9,7 +10,15 @@
|
|
|
9
10
|
.fdy-modal__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text);margin:0 auto 0 0;}
|
|
10
11
|
.fdy-modal__close{border:0;background:transparent;color:var(--color-text-muted);font-size:var(--text-xl);line-height:1;cursor:pointer;padding:var(--space-1);border-radius:var(--radius-sm);}
|
|
11
12
|
.fdy-modal__close:hover{background:var(--color-surface-2);color:var(--color-text);}
|
|
12
|
-
|
|
13
|
+
/* `1 1 auto`, NOT `flex:1`. The shorthand's basis is 0%, and `.fdy-modal` has only a max-height, so
|
|
14
|
+
* the percentage resolves against an indefinite height. Current engines read that as `content`;
|
|
15
|
+
* older WebKit read it as 0, sized the dialog to header + footer, and left the body its 40px of
|
|
16
|
+
* padding, with the form overflowing inside it. That was every modal on an iPhone 7 (iOS 15),
|
|
17
|
+
* reported from a real device (CATATAN-FREEDAY §16). A 0px basis reproduces it in Chrome to the
|
|
18
|
+
* pixel. `auto` lays out identically in current engines (measured: 498.7px dialog, 355.7px body,
|
|
19
|
+
* both readings) and leaves no indefinite percentage for any engine to read. min-height:0 is
|
|
20
|
+
* still what lets a long body shrink and scroll once the dialog reaches its max-height. */
|
|
21
|
+
.fdy-modal__body{flex:1 1 auto;min-height:0;padding:var(--space-5);overflow:auto;}
|
|
13
22
|
.fdy-modal__footer{flex:none;display:flex;gap:var(--space-2);justify-content:flex-end;padding:var(--space-4) var(--space-5);border-top:var(--bw) solid var(--color-border-muted);background:var(--color-surface-2);}
|
|
14
23
|
/* Symmetric enter AND exit (fade + rise + subtle scale) on the native <dialog>. @starting-style
|
|
15
24
|
* animates the entry; overlay/display allow-discrete keep it painted through the exit so it
|