@cahyo-dimas/freeday 3.12.0 → 3.13.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 +94 -6
- package/COMPONENTS.md +31 -1
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/dist/freeday-breakpoint.js +12 -2
- package/dist/freeday-cfl.js +3 -1
- package/dist/freeday-chip.js +10 -4
- package/dist/freeday-unhydrated.js +76 -0
- package/dist/freeday-upload.js +5 -3
- package/dist/freeday.bundle.css +6 -5
- package/dist/freeday.css +6 -5
- package/dist/freeday.js +107 -10
- package/docs/agent-onboarding.md +2 -1
- package/docs/getting-started.md +1 -1
- package/docs/integrations.md +2 -2
- package/package.json +2 -2
- package/src/components/drawer.css +2 -2
- package/src/components/input.css +2 -1
- package/src/components/modal.css +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,94 @@ 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.13.0] - 2026-10-29
|
|
13
|
+
|
|
14
|
+
**Kit berhenti memutuskan hal yang bukan urusannya, dan markup yang tak pernah dihidrasi kini
|
|
15
|
+
bersuara.** Lima butir dari `CATATAN-FREEDAY.md` app ronda (§9 sampai §13), dikerjakan dalam satu
|
|
16
|
+
rilis. Tiga di antaranya gagal tanpa galat apa pun: chip yang hilang sesudah Batal, tombol simpan
|
|
17
|
+
yang mati, dan kelas yang tak cocok dengan aturan mana pun.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **`fdy-chip-remove` dan `fdy-upload-remove` kini dapat dibatalkan** (`#098`). Begitu
|
|
21
|
+
`.fdy-chip__remove` ditekan, enhancer chip langsung menghapus chip-nya, padahal kit tak tahu
|
|
22
|
+
apakah penghapusan itu perlu ditanyakan dulu. Matriks pola rotasi di app ronda memakai chip
|
|
23
|
+
sebagai tampilan dan punya dialog konfirmasi sendiri. Chip-nya hilang ketika dialog masih
|
|
24
|
+
terbuka, dan tetap hilang sesudah **Batal** ditekan. Datanya utuh, layarnya yang salah. Sekarang
|
|
25
|
+
event-nya `cancelable`, dan elemennya baru dihapus kalau tak ada listener yang memanggil
|
|
26
|
+
`preventDefault()`. Perubahan ini aditif: app yang tidak membatalkan mendapat perilaku yang persis
|
|
27
|
+
sama. Baris upload ikut diubah karena bentuknya sama, dan berkas yang sudah dipegang server
|
|
28
|
+
memang layak ditanyakan dulu sebelum dibuang.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- **Peringatan konsol untuk hook yang tak pernah dihidrasi** (`#099`): `src/freeday-unhydrated.js`,
|
|
32
|
+
sudah termasuk di import akar. Klik, fokus atau submit pertama di dalam elemen ber-hook yang tak
|
|
33
|
+
punya penanda siap mencetak satu `console.warn` per elemen. Pesannya menyebut hook-nya, dan
|
|
34
|
+
elemennya ikut sebagai argumen kedua supaya devtools menautkannya. Pemicunya §10: `useFreeday()`
|
|
35
|
+
yang dipasang di pembungkus yang salah membuat `data-fdy-validate` di luar pembungkus itu diam
|
|
36
|
+
total, tanpa kalimat validasi dan tanpa `fdy-form-valid`. Yang memberi tahu cuma dua uji e2e yang
|
|
37
|
+
merah. Catatannya meminta peringatan ini untuk form, tetapi kegagalannya berlaku untuk setiap
|
|
38
|
+
hook, jadi tabelnya memuat 29 hook dari 24 enhancer. Peringatan dipicu interaksi, bukan saat
|
|
39
|
+
halaman dimuat, karena framework me-mount dan menghidrasi di antara keduanya.
|
|
40
|
+
- **`fdy-help--danger`** (`#100`), alias `fdy-help--error` di dalam aturan yang sama. Kit memakai
|
|
41
|
+
`--danger` sebagai kata nada di mana-mana (tombol, alert, lencana, toast), sehingga app menebak
|
|
42
|
+
`fdy-help--danger`. Kelas itu tak cocok dengan aturan apa pun, dan kalimat peringatannya tampil
|
|
43
|
+
abu-abu. Kontrol form sengaja tidak diberi alias. Keadaannya digerakkan `aria-invalid`, jadi
|
|
44
|
+
`--error` di sana jarang ditulis tangan. Aturan penamaannya kini tertulis di COMPONENTS.md.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
- **`FreedayCfl` dan `FreedayBreakpoint` tak punya `initAll`** (`#099`), padahal hanya nama itu
|
|
48
|
+
yang dicari ketiga helper stack (`useFreeday` di Vue dan React, `FreedayBlazor.initAll`). Markup
|
|
49
|
+
CFL mentah di dalam subtree framework dilewati ketiganya dan dialognya tak pernah terbuka. Label
|
|
50
|
+
breakpoint yang di-mount belakangan tetap kosong sampai breakpoint berganti. Tak ada yang
|
|
51
|
+
melaporkan ini; keduanya ketemu saat mengaudit kelas cacat §10.
|
|
52
|
+
|
|
53
|
+
### Docs
|
|
54
|
+
- **`<ul>` polos adalah daftar berbutir yang didukung** (`#101`). `base.css` memang tak pernah
|
|
55
|
+
mereset daftar. Sekarang COMPONENTS.md menjanjikannya, dan sebuah uji menjaga janji itu.
|
|
56
|
+
- §13 (`required` di `FdyCombo`) sudah terkirim di 3.12.0 (`#096`). Tak ada kode baru; app tinggal
|
|
57
|
+
memakainya.
|
|
58
|
+
|
|
59
|
+
### Gerbang
|
|
60
|
+
- `docs.test.mjs`: setiap penanda siap yang dicap enhancer wajib ada di tabel peringatan, dicap di
|
|
61
|
+
berkas milik namespace yang disebut tabel, dan berkas itu wajib benar-benar meng-query selektor
|
|
62
|
+
pasangannya. Setiap enhancer yang menghidrasi markup wajib mengekspos `initAll`. Penjaga string
|
|
63
|
+
kini melewati teks `console.warn` (untuk devtools, tak pernah masuk DOM) dan identifier
|
|
64
|
+
PascalCase.
|
|
65
|
+
- `css.test.mjs`: `fdy-help--danger` harus satu aturan dengan `--error`, dan tak boleh ada aturan
|
|
66
|
+
tanpa cakupan yang mereset `ul`/`ol`/`li`.
|
|
67
|
+
- `browser/app-owned.mjs`, dengan mouse asli: chip dan baris upload yang dibatalkan tetap di
|
|
68
|
+
tempatnya, form yatim memperingatkan sekali sementara form yang terhidrasi diam, dan CFL serta
|
|
69
|
+
label breakpoint yang di-mount belakangan terhidrasi lewat `FreedayBlazor.initAll`.
|
|
70
|
+
- Tiga belas mutasi diverifikasi merah sebelum dipulihkan: enam pada perilaku, lima pada tabel dan
|
|
71
|
+
ekspor, dua pada CSS.
|
|
72
|
+
|
|
73
|
+
## [3.12.1] - 2026-10-27
|
|
74
|
+
|
|
75
|
+
**Modal dan drawer kembali beranimasi di bawah Safari 17.4, termasuk di iPhone 7 pemiliknya.**
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
- **Empat shorthand `transition` dipecah jadi longhand** (`#097`), di `modal.css` dan `drawer.css`.
|
|
79
|
+
Shorthand yang membawa satu keyword yang tak dikenal mesin **tidak sah saat parse**, jadi seluruh
|
|
80
|
+
deklarasinya dibuang, dan `allow-discrete` tak dikenal di bawah Safari 17.4, yang ada di bawah
|
|
81
|
+
lantai dukungan kit. Akibatnya modal dan drawer buka-tutup tanpa animasi apa pun. Dipecah begini,
|
|
82
|
+
yang hilang di bawah lantai hanya `transition-behavior`, dan paruh opacity/transform-nya bertahan.
|
|
83
|
+
`overlay` dan `display` tetap terdaftar di `transition-property`, tempat nama yang tak cocok tetap
|
|
84
|
+
sah secara sintaks dan sekadar tak pernah cocok, jadi tak ada lagi yang ikut jatuh. Keluarnya
|
|
85
|
+
tetap seketika di sana sebab `display` memang tak dapat ditransisikan di bawah lantai, sama
|
|
86
|
+
seperti sebelumnya, jadi pemecahan ini tidak mengambil apa pun.
|
|
87
|
+
|
|
88
|
+
Ekuivalensinya di atas lantai **diukur, bukan dinalar**: `transition-property`,
|
|
89
|
+
`transition-timing-function` dan `transition-behavior` identik byte-per-byte sebelum dan sesudah,
|
|
90
|
+
dan satu-satunya beda adalah satu nilai `transition-duration` yang terserialisasi sebagai satu
|
|
91
|
+
nilai alih-alih tiga. Bahwa satu nilai itu tetap berlaku untuk SETIAP properti juga diukur: 280ms
|
|
92
|
+
masing-masing, dan easing tetap per-indeks.
|
|
93
|
+
|
|
94
|
+
Harganya empat deklarasi yang harus tetap sejalan, dan itu kini dijaga uji: `baseline.test.mjs`
|
|
95
|
+
menolak `allow-discrete` di dalam shorthand `transition` mana pun, dan menolak
|
|
96
|
+
`transition-behavior` yang berdiri tanpa ketiga longhand lainnya. Regresi yang paling mungkin
|
|
97
|
+
adalah seseorang merapikan empat longhand kembali menjadi satu shorthand, yang terbaca lebih
|
|
98
|
+
bersih dan diam-diam mengambil animasinya dari setiap peramban di bawah Safari 17.4.
|
|
99
|
+
|
|
12
100
|
## [3.12.0] - 2026-10-27
|
|
13
101
|
|
|
14
102
|
**Sebuah form kini dapat menuntut kontrol kit terisi.** `data-fdy-validate` mewirekan Constraint
|
|
@@ -67,7 +155,7 @@ perbaikan ini terdengar jauh lebih mahal daripada yang ternyata.
|
|
|
67
155
|
|
|
68
156
|
**Audit kontrol native di seluruh layar sebuah aplikasi konsumen, bukan di satu layar yang
|
|
69
157
|
mengeluh.** Pemiliknya melaporkan tampilan HP aslinya tak cocok dengan peramban di lebar yang sama,
|
|
70
|
-
dan dugaan pertama
|
|
158
|
+
dan dugaan pertama, termasuk dugaan saya, adalah CSS. Pengukuran menyangkalnya: dengan
|
|
71
159
|
stylesheet aplikasi yang sebenarnya, pada 375x667, Chromium dan WebKit menata `date`, `time`,
|
|
72
160
|
`number`, `search` dan `text` **identik**, 293x44 keenamnya, selisih satu piksel pada `y` yang
|
|
73
161
|
cuma pembulatan. Jadi yang berbeda bukan tata letaknya, melainkan **widget native-nya**: `date` dan
|
|
@@ -79,7 +167,7 @@ layar yang selebihnya sama.
|
|
|
79
167
|
Kit sudah mengatakan ini di tabelnya sendiri ("Use `.fdy-datepicker` / `.fdy-timepicker` /
|
|
80
168
|
`.fdy-datetimepicker` instead") dan sudah mengirim CSS plus enhancer-nya. Yang belum ada adalah
|
|
81
169
|
wrapper bertipe untuk timepicker, jadi aplikasi Vue/React/Blazor harus merakit sendiri
|
|
82
|
-
plumbing custom-event-nya
|
|
170
|
+
plumbing custom-event-nya, dan merakit sendiri adalah yang membuat empat `<input type="time">`
|
|
83
171
|
bertahan di satu formulir shift.
|
|
84
172
|
|
|
85
173
|
Catatan kejujuran: perbedaan iOS itu **tidak** direproduksi di sini. WebKit desktop bukan Mobile
|
|
@@ -89,8 +177,8 @@ lokal, dan kini diuji, adalah perilaku penggantinya.
|
|
|
89
177
|
|
|
90
178
|
### Added
|
|
91
179
|
- **`FdyTimepicker`** (`#094`), wrapper bertipe di **ketiga** stack, menjadikannya tiga belas.
|
|
92
|
-
Vue dan React mengimplementasikan listbox-nya secara native sebab ia kontrol form
|
|
93
|
-
|
|
180
|
+
Vue dan React mengimplementasikan listbox-nya secara native sebab ia kontrol form. Alasannya sama
|
|
181
|
+
dengan `FdyCombo`, dan `FdyChart` menyatakannya dari sisi sebaliknya: enhancer yang
|
|
94
182
|
menguasai DOM berkelahi dengan render framework dan tak menyisakan `v-model`. Blazor
|
|
95
183
|
menjembatani enhancer lewat seed, seperti `FdyDatepicker`. Sorotannya **dijepit, bukan
|
|
96
184
|
berputar**: pada daftar 48 opsi, ArrowUp di `00:00` yang melompat ke `23:30` terbaca sebagai
|
|
@@ -99,7 +187,7 @@ lokal, dan kini diuji, adalah perilaku penggantinya.
|
|
|
99
187
|
- **`data-id` dan `data-describedby` pada seed timepicker** (`#094`). Keduanya tak terbaca
|
|
100
188
|
sebelumnya, jadi host yang menampilkan teks galat tak punya cara menautkannya. Pemicunya
|
|
101
189
|
`<button>`, yang **bukan** elemen labelable, sehingga `<label for>` memang tak akan pernah
|
|
102
|
-
mengaktifkannya
|
|
190
|
+
mengaktifkannya, dan itu kini didokumentasikan.
|
|
103
191
|
- **`FreedayTimepicker.setState(root, { disabled, readonly, invalid })`** (`#094`), bentuknya sama
|
|
104
192
|
dengan `FreedayDatepicker.setState`. Tanpa ini, host yang merender seed-nya sekali lalu tidak
|
|
105
193
|
pernah lagi (Blazor, `ShouldRender => false`) memiliki tiga parameter keadaan yang tak ada yang
|
|
@@ -109,7 +197,7 @@ lokal, dan kini diuji, adalah perilaku penggantinya.
|
|
|
109
197
|
- **`FdyDateRange` membuang kedelapan prop label navigasi kalender** (`#095`), di ketiga stack.
|
|
110
198
|
`FdyDatepicker` menerimanya sejak 2.1.1; rentangnya tidak meneruskannya, sehingga rentang tanggal
|
|
111
199
|
adalah **satu-satunya** kontrol tanggal di kit yang navigasinya tak bisa dinamai dalam bahasa
|
|
112
|
-
halamannya sendiri
|
|
200
|
+
halamannya sendiri: aplikasi berbahasa Indonesia mendapat "Previous month" tepat di tempat yang
|
|
113
201
|
tak dapat ia jangkau. Diteruskan apa adanya ke kedua pemilih. Prop datar `xxxLabel`, bukan objek
|
|
114
202
|
`labels`, sebab bentuk itulah yang sudah dipakai setiap komponen lain di kit. Blazor menambah
|
|
115
203
|
`BackToMonthsLabel`, karena seed yang kedua pemilihnya dibangun dari situ punya tombol itu.
|
package/COMPONENTS.md
CHANGED
|
@@ -32,6 +32,10 @@ live docs.
|
|
|
32
32
|
state *and* the screen-reader state. Pair it with `aria-describedby` → the `id` of a
|
|
33
33
|
`.fdy-help.fdy-help--error`. The `--error` class modifiers exist only for the case where the
|
|
34
34
|
invalid state does not belong to the control itself.
|
|
35
|
+
**`--error` names a field's validation state, `--danger` names a tone** (button, alert, badge,
|
|
36
|
+
toast, menu item). `.fdy-help` is the one block that takes both, `--danger` being the same
|
|
37
|
+
colour, because help text is where either reading is natural: a sentence that warns without
|
|
38
|
+
any field being invalid.
|
|
35
39
|
6. **Interactive components need their enhancer script.** Static ones (button, card, badge, table,
|
|
36
40
|
alert, breadcrumb, timeline, accordion, tree-without-cascade…) are CSS-only.
|
|
37
41
|
7. **On Vue, React or Blazor, thirteen components have a typed wrapper. Use it.** `FdyCombo` ·
|
|
@@ -44,6 +48,10 @@ live docs.
|
|
|
44
48
|
breaks quietly: later-rendered DOM is never hydrated and the widget's state sits in the DOM
|
|
45
49
|
instead of your framework's. Everything else is the same markup everywhere; hydrate the other
|
|
46
50
|
interactive components with `useFreeday` (Vue/React) or `FreedayBlazor.initAll` (Blazor).
|
|
51
|
+
**Markup no `initAll` reached says so.** The first click, focus or submit inside a hook the kit
|
|
52
|
+
never enhanced logs one `console.warn` naming it, with the element attached. The usual cause is
|
|
53
|
+
a `useFreeday()` ref on a wrapper that does not contain it. `freeday-unhydrated.js` does this,
|
|
54
|
+
and it ships in the root import.
|
|
47
55
|
|
|
48
56
|
### Containment — why the kit's containers are `position:relative`
|
|
49
57
|
|
|
@@ -562,7 +570,7 @@ native control, otherwise a `<div>` + explicitly associated label.
|
|
|
562
570
|
`.fdy-field--full` on the fields — **not** a `max-width` override on the container. Both of this
|
|
563
571
|
kit's own consoles wrote that override, once per form, before this modifier reached outside the
|
|
564
572
|
grid.
|
|
565
|
-
- `.fdy-label` (+`--required`) · `.fdy-input` (+`--error`) · `.fdy-textarea` · `.fdy-help` (+`--error`)
|
|
573
|
+
- `.fdy-label` (+`--required`) · `.fdy-input` (+`--error`) · `.fdy-textarea` · `.fdy-help` (+`--error`, or its alias `--danger`)
|
|
566
574
|
|
|
567
575
|
**Grouped controls are a `<fieldset>`, not a new block.** Put `.fdy-field` on the fieldset and
|
|
568
576
|
`.fdy-label` on the legend. The kit already resets the UA border/padding and supplies the spacing:
|
|
@@ -1214,6 +1222,9 @@ zone.addEventListener('fdy-upload-add', (e) => {
|
|
|
1214
1222
|
zone.addEventListener('fdy-upload-remove', (e) => forget(e.detail.file)); // same element
|
|
1215
1223
|
```
|
|
1216
1224
|
|
|
1225
|
+
`fdy-upload-remove` is **cancelable** too: `preventDefault()` keeps the row, for a file the server
|
|
1226
|
+
already holds and the user should confirm losing. Remove it yourself afterwards with `row.el.remove()`.
|
|
1227
|
+
|
|
1217
1228
|
**Bring your own row:** omit the file list entirely (no `data-filelist`, and no `.fdy-filelist`
|
|
1218
1229
|
sibling) and the enhancer renders nothing while still dispatching `fdy-upload-add`, where `detail.row`
|
|
1219
1230
|
still works, its element simply isn't attached. Note the fallback when `data-filelist` is absent is
|
|
@@ -1757,6 +1768,10 @@ responsive `.fdy-datatable` should become below `md`, rather than a stack of `.f
|
|
|
1757
1768
|
control in the kit. Do not hand-roll it: an undimmed row that still lights up under the pointer
|
|
1758
1769
|
reads as clickable while it is refusing input.
|
|
1759
1770
|
- Not `.fdy-list-reset`, which only strips UA bullets/indent from a semantic list.
|
|
1771
|
+
- Not for three bullet points in a dialog either. **A plain bulleted list is a plain `<ul>`/`<ol>`,
|
|
1772
|
+
no class**, and that is supported: `base.css` deliberately leaves lists alone, so native bullets
|
|
1773
|
+
and indent survive (a test keeps it that way). The exception is a utility framework run with its
|
|
1774
|
+
preflight on, which strips them.
|
|
1760
1775
|
|
|
1761
1776
|
```html
|
|
1762
1777
|
<ul class="fdy-list">
|
|
@@ -2335,6 +2350,21 @@ Three roles inside a `.fdy-chips` row:
|
|
|
2335
2350
|
|
|
2336
2351
|
Interactive chips are managed by `freeday-chip.js`: wrap the row in `data-fdy-chips` (+
|
|
2337
2352
|
`data-single` for pick-one, `data-label` for the group name), give each `data-value`.
|
|
2353
|
+
|
|
2354
|
+
**Removal is cancelable.** Pressing `.fdy-chip__remove` fires `fdy-chip-remove` (`detail.value`) on
|
|
2355
|
+
the chip, then removes the chip only if no listener called `preventDefault()`. Cancel it whenever
|
|
2356
|
+
the removal is the app's call: a confirm step, or chips rendered from framework state, which
|
|
2357
|
+
should leave the DOM when the state changes and not before. Without that, Cancel in your own
|
|
2358
|
+
confirm dialog leaves the data intact and the chip gone.
|
|
2359
|
+
|
|
2360
|
+
```js
|
|
2361
|
+
row.addEventListener('fdy-chip-remove', async (e) => {
|
|
2362
|
+
e.preventDefault(); // the kit leaves the chip alone
|
|
2363
|
+
if (await Freeday.confirm({ title: `Remove ${e.detail.value}?`, tone: 'danger' })) {
|
|
2364
|
+
removeFromState(e.detail.value); // your render takes it out
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2367
|
+
```
|
|
2338
2368
|
`--tone-1`…`--tone-8` for non-semantic category colours.
|
|
2339
2369
|
|
|
2340
2370
|
## Timeline — `.fdy-timeline`
|
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.13.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/v3.13.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`.
|
|
@@ -26,10 +26,19 @@
|
|
|
26
26
|
if (bp === last) return;
|
|
27
27
|
last = bp;
|
|
28
28
|
root.setAttribute('data-breakpoint', bp);
|
|
29
|
-
|
|
29
|
+
fillLabels(document);
|
|
30
|
+
root.dispatchEvent(new CustomEvent('fdy-breakpoint-change', { bubbles: true, detail: { breakpoint: bp } }));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Also run over late-mounted markup by the stack helpers, which find it through initAll: a label
|
|
34
|
+
rendered after load otherwise stayed empty until the next breakpoint change. */
|
|
35
|
+
function fillLabels(context) {
|
|
36
|
+
var ctx = context || document;
|
|
37
|
+
var bp = last || current();
|
|
38
|
+
if (ctx.matches && ctx.matches('[data-fdy-breakpoint-label]')) ctx.textContent = bp;
|
|
39
|
+
Array.prototype.forEach.call(ctx.querySelectorAll('[data-fdy-breakpoint-label]'), function (el) {
|
|
30
40
|
el.textContent = bp;
|
|
31
41
|
});
|
|
32
|
-
root.dispatchEvent(new CustomEvent('fdy-breakpoint-change', { bubbles: true, detail: { breakpoint: bp } }));
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
window.addEventListener('resize', update, { passive: true });
|
|
@@ -40,6 +49,7 @@
|
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
window.FreedayBreakpoint = {
|
|
52
|
+
initAll: fillLabels,
|
|
43
53
|
get: function () { return last || current(); },
|
|
44
54
|
current: current,
|
|
45
55
|
// true if the current breakpoint is `name` or wider (e.g. is('md') → md, lg, or xl)
|
package/dist/freeday-cfl.js
CHANGED
|
@@ -240,5 +240,7 @@
|
|
|
240
240
|
initTriggers();
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
|
|
243
|
+
/* initAll is what every stack helper looks for (useFreeday in Vue and React, FreedayBlazor.initAll):
|
|
244
|
+
without it they skipped this enhancer, and raw CFL markup rendered by a framework never opened. */
|
|
245
|
+
window.FreedayCfl = { init: initTriggers, initAll: initTriggers };
|
|
244
246
|
})();
|
package/dist/freeday-chip.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/* Freeday, chip enhancer (optional, zero-dependency).
|
|
2
2
|
* Two behaviours, both progressive enhancement:
|
|
3
3
|
*
|
|
4
|
-
* 1. Removable chips, any .fdy-chip__remove button
|
|
5
|
-
*
|
|
4
|
+
* 1. Removable chips, any .fdy-chip__remove button emits a bubbling, CANCELABLE
|
|
5
|
+
* "fdy-chip-remove" (detail {value}) and then removes its chip, unless a listener called
|
|
6
|
+
* preventDefault(). Cancel it whenever the app owns the removal: a confirm step, or a chip
|
|
7
|
+
* rendered from framework state, which should leave the DOM when the state does.
|
|
6
8
|
* 2. Choice / filter groups, a [data-fdy-chips] container of interactive chips
|
|
7
9
|
* (.fdy-chip--choice / .fdy-chip--filter, each a <button>) toggles aria-pressed
|
|
8
10
|
* on click. Add data-single for one-at-a-time (radio-like) selection; filter
|
|
@@ -28,8 +30,12 @@
|
|
|
28
30
|
if (rm) rm.remove();
|
|
29
31
|
value = (clone.textContent || '').trim();
|
|
30
32
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
/* Cancelable, because the kit cannot know whether this removal needs asking first. Removing
|
|
34
|
+
unconditionally made a chip vanish while the app's own confirm dialog was still open, and
|
|
35
|
+
stay gone after Cancel, with the data intact and the screen wrong (CATATAN-FREEDAY §9). */
|
|
36
|
+
var ev = new CustomEvent('fdy-chip-remove', { bubbles: true, cancelable: true, detail: { value: value } });
|
|
37
|
+
chip.dispatchEvent(ev);
|
|
38
|
+
if (!ev.defaultPrevented) chip.remove();
|
|
33
39
|
});
|
|
34
40
|
}
|
|
35
41
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* Freeday, un-hydrated hook warning (optional, zero-dependency, no markup of its own).
|
|
2
|
+
* Every enhancer marks the element it took over (`data-fdy-*-ready`). Markup that no initAll()
|
|
3
|
+
* ever reached carries the hook and not the mark, and it fails in the worst way available: it
|
|
4
|
+
* renders, looks finished, and does nothing, with no error anywhere. A `data-fdy-validate` form
|
|
5
|
+
* outside the subtree handed to useFreeday() neither paints a message nor fires fdy-form-valid,
|
|
6
|
+
* so its save button reads as dead (CATATAN-FREEDAY §10). The enhancer knows the attribute is
|
|
7
|
+
* there; the person who wrote it does not.
|
|
8
|
+
*
|
|
9
|
+
* So on the first click, focus or submit inside such an element this logs ONE console.warn per
|
|
10
|
+
* element, naming the hook, with the element itself as the second argument so devtools links it.
|
|
11
|
+
* Interaction, not load, is the trigger on purpose: a framework mounts and hydrates in between,
|
|
12
|
+
* and only once a person is touching the widget is "not enhanced yet" the same as "never".
|
|
13
|
+
*
|
|
14
|
+
* HOOKS pairs each hook selector with the ready flag its enhancer stamps on THAT element and the
|
|
15
|
+
* namespace that owns it. A hook whose enhancer is not loaded is skipped: that is a different
|
|
16
|
+
* mistake, and this message would misname it. test/docs.test.mjs fails if an enhancer gains a
|
|
17
|
+
* ready flag this table does not list.
|
|
18
|
+
*/
|
|
19
|
+
(function () {
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
var HOOKS = [
|
|
23
|
+
['[data-fdy-app]', 'fdyAppShellReady', 'FreedayAppShell'],
|
|
24
|
+
['[data-fdy-autocomplete]', 'fdyAcReady', 'FreedayAutocomplete'],
|
|
25
|
+
['[data-fdy-carousel]', 'fdyCarouselReady', 'FreedayCarousel'],
|
|
26
|
+
['[data-fdy-cascade]', 'fdyCascadeReady', 'FreedayCascade'],
|
|
27
|
+
['[data-fdy-cfl]', 'fdyCflBound', 'FreedayCfl'],
|
|
28
|
+
['[data-fdy-cfl-open]', 'fdyCflBound', 'FreedayCfl'],
|
|
29
|
+
['[data-fdy-chart]', 'fdyChartReady', 'FreedayChart'],
|
|
30
|
+
['[data-fdy-chips]', 'fdyChipsReady', 'FreedayChip'],
|
|
31
|
+
['.fdy-chip__remove', 'fdyChipRmReady', 'FreedayChip'],
|
|
32
|
+
['[data-fdy-datepicker]', 'fdyDpReady', 'FreedayDatepicker'],
|
|
33
|
+
['[data-fdy-daterange]', 'fdyDrReady', 'FreedayDatepicker'],
|
|
34
|
+
['[data-fdy-datetimepicker]', 'fdyDtReady', 'FreedayDatetime'],
|
|
35
|
+
['[data-fdy-drawer]', 'fdyDrawerBound', 'FreedayDrawer'],
|
|
36
|
+
['.fdy-drawer', 'fdyDrawerReady', 'FreedayDrawer'],
|
|
37
|
+
['[data-fdy-validate]', 'fdyFormReady', 'FreedayForm'],
|
|
38
|
+
['[data-fdy-mask]', 'fdyMaskReady', 'FreedayMask'],
|
|
39
|
+
['[data-fdy-password]', 'fdyPwReady', 'FreedayMask'],
|
|
40
|
+
['[data-fdy-menu]', 'fdyMenuReady', 'FreedayMenu'],
|
|
41
|
+
['[data-fdy-number]', 'fdyNumberReady', 'FreedayNumber'],
|
|
42
|
+
['[data-fdy-rating]', 'fdyRatingReady', 'FreedayRating'],
|
|
43
|
+
['[data-fdy-search]', 'fdySearchReady', 'FreedaySearch'],
|
|
44
|
+
['[data-fdy-combo]', 'fdyComboReady', 'FreedayCombo'],
|
|
45
|
+
['[data-fdy-slider]', 'fdySliderReady', 'FreedaySlider'],
|
|
46
|
+
['[data-fdy-stepper]', 'fdyStepperReady', 'FreedayStepper'],
|
|
47
|
+
['[data-fdy-table]', 'fdyTableReady', 'FreedayTable'],
|
|
48
|
+
['[data-fdy-tabs]', 'fdyTabsReady', 'FreedayTabs'],
|
|
49
|
+
['[data-fdy-timepicker]', 'fdyTpReady', 'FreedayTimepicker'],
|
|
50
|
+
['[data-fdy-tree]', 'fdyTreeReady', 'FreedayTree'],
|
|
51
|
+
['[data-fdy-dropzone]', 'fdyUploadReady', 'FreedayUpload']
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
var warned = typeof WeakSet === 'function' ? new WeakSet() : null;
|
|
55
|
+
|
|
56
|
+
function check(e) {
|
|
57
|
+
if (!warned || !e.target || !e.target.closest) return;
|
|
58
|
+
for (var i = 0; i < HOOKS.length; i++) {
|
|
59
|
+
var hook = HOOKS[i];
|
|
60
|
+
if (!window[hook[2]]) continue;
|
|
61
|
+
var el = e.target.closest(hook[0]);
|
|
62
|
+
if (!el || el.dataset[hook[1]] === '1' || warned.has(el)) continue;
|
|
63
|
+
warned.add(el);
|
|
64
|
+
console.warn('Freeday: this ' + hook[0] + ' was never enhanced, so it does nothing. Either it is '
|
|
65
|
+
+ 'outside every initAll() scope (Vue/React: inside the element given to useFreeday(); Blazor: '
|
|
66
|
+
+ 'FreedayBlazor.initAll after render; anything else: ' + hook[2] + '.initAll(el) once it is in '
|
|
67
|
+
+ 'the DOM), or its markup lacks a part the enhancer needs (see COMPONENTS.md).', el);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
['click', 'focusin', 'submit'].forEach(function (type) {
|
|
72
|
+
document.addEventListener(type, check, true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
window.FreedayUnhydrated = { hooks: HOOKS };
|
|
76
|
+
})();
|
package/dist/freeday-upload.js
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* Emits bubbling CustomEvents. BOTH on the dropzone element, which is the one target a consumer
|
|
19
19
|
* needs to listen on:
|
|
20
20
|
* "fdy-upload-add" {file, rejected, reason, row}
|
|
21
|
-
* "fdy-upload-remove" {file}
|
|
21
|
+
* "fdy-upload-remove" {file}, cancelable: preventDefault() keeps the row (e.g. to confirm
|
|
22
|
+
* removing a file the server already holds, then call row.el.remove())
|
|
22
23
|
* `row` is the state machine over the rendered .fdy-file:
|
|
23
24
|
* .ready() (rest, where a dropped file starts) / .uploading() / .setProgress(pct) /
|
|
24
25
|
* .waiting(label) (sent, awaiting the server) / .done() / .fail(msg) / .el
|
|
@@ -111,8 +112,9 @@
|
|
|
111
112
|
Firing on both would look safer, but it makes the pair asymmetric, one `add` and, for
|
|
112
113
|
anyone delegating on a common ancestor, two `remove`s, which is its own silent bug. It
|
|
113
114
|
also fixes the listless case: a row that was never attached bubbles to nothing at all. */
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
var ev = new CustomEvent('fdy-upload-remove', { bubbles: true, cancelable: true, detail: { file: file } });
|
|
116
|
+
zone.dispatchEvent(ev);
|
|
117
|
+
if (!ev.defaultPrevented) el.remove();
|
|
116
118
|
});
|
|
117
119
|
el.appendChild(icon);
|
|
118
120
|
el.appendChild(meta);
|
package/dist/freeday.bundle.css
CHANGED
|
@@ -2472,10 +2472,10 @@ a { color: var(--color-primary-strong); }
|
|
|
2472
2472
|
/* display is gated behind [open] so the native <dialog> stays hidden until opened.
|
|
2473
2473
|
* An unconditional display here would beat the UA `dialog:not([open]){display:none}`
|
|
2474
2474
|
* rule (author origin wins over user-agent) and leave the drawer visible on load. */
|
|
2475
|
-
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;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:transform
|
|
2475
|
+
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;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
2476
|
.fdy-drawer[open]{display:flex;transform:none;}
|
|
2477
2477
|
.fdy-drawer--right{inset:0 0 0 auto;transform:translateX(100%);}
|
|
2478
|
-
.fdy-drawer::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition:opacity
|
|
2478
|
+
.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;}
|
|
2479
2479
|
.fdy-drawer__header{flex:none;display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
2480
2480
|
.fdy-drawer__title{font-family:var(--font-display);font-weight:var(--weight-semibold);font-size:var(--text-lg);color:var(--color-text);margin:0 auto 0 0;}
|
|
2481
2481
|
.fdy-drawer__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);}
|
|
@@ -2696,7 +2696,8 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
2696
2696
|
remember to mark aria-hidden, and it cannot be forgotten. */
|
|
2697
2697
|
.fdy-label--required::after{content:"*" / "";margin-left:var(--space-1);color:var(--color-danger);}
|
|
2698
2698
|
.fdy-help{font-size:var(--text-xs);color:var(--color-text-muted);}
|
|
2699
|
-
|
|
2699
|
+
/* --danger is the tone word everywhere else in the kit; help text takes either (CATATAN-FREEDAY §11). */
|
|
2700
|
+
.fdy-help--error,.fdy-help--danger{color:var(--color-danger);}
|
|
2700
2701
|
|
|
2701
2702
|
/* Freeday. Keyboard key */
|
|
2702
2703
|
.fdy-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:1.5rem;height:1.5rem;padding:0 var(--space-2);font-family:var(--font-mono);font-size:var(--text-xs);color:var(--color-text);background:var(--color-surface-2);border:var(--bw) solid var(--color-border-strong);border-bottom-width:2px;border-radius:var(--radius-sm);}
|
|
@@ -2778,12 +2779,12 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
2778
2779
|
.fdy-btn-split__toggle svg{display:block;width:1rem;height:1rem;}
|
|
2779
2780
|
|
|
2780
2781
|
/* Freeday. Modal (native <dialog>) */
|
|
2781
|
-
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - 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)
|
|
2782
|
+
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - 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;}
|
|
2782
2783
|
.fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
|
|
2783
2784
|
.fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
|
|
2784
2785
|
.fdy-modal--lg{width:min(48rem,calc(100vw - var(--space-8)));}
|
|
2785
2786
|
.fdy-modal--wide{width:min(60rem,calc(100vw - var(--space-8)));}
|
|
2786
|
-
.fdy-modal::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition:opacity
|
|
2787
|
+
.fdy-modal::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-base);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
|
|
2787
2788
|
.fdy-modal__header{flex:none;display:flex;align-items:center;gap:var(--space-3);padding:var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
2788
2789
|
.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;}
|
|
2789
2790
|
.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);}
|
package/dist/freeday.css
CHANGED
|
@@ -1295,10 +1295,10 @@ a { color: var(--color-primary-strong); }
|
|
|
1295
1295
|
/* display is gated behind [open] so the native <dialog> stays hidden until opened.
|
|
1296
1296
|
* An unconditional display here would beat the UA `dialog:not([open]){display:none}`
|
|
1297
1297
|
* rule (author origin wins over user-agent) and leave the drawer visible on load. */
|
|
1298
|
-
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;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:transform
|
|
1298
|
+
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;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
1299
|
.fdy-drawer[open]{display:flex;transform:none;}
|
|
1300
1300
|
.fdy-drawer--right{inset:0 0 0 auto;transform:translateX(100%);}
|
|
1301
|
-
.fdy-drawer::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition:opacity
|
|
1301
|
+
.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;}
|
|
1302
1302
|
.fdy-drawer__header{flex:none;display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
1303
1303
|
.fdy-drawer__title{font-family:var(--font-display);font-weight:var(--weight-semibold);font-size:var(--text-lg);color:var(--color-text);margin:0 auto 0 0;}
|
|
1304
1304
|
.fdy-drawer__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);}
|
|
@@ -1519,7 +1519,8 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1519
1519
|
remember to mark aria-hidden, and it cannot be forgotten. */
|
|
1520
1520
|
.fdy-label--required::after{content:"*" / "";margin-left:var(--space-1);color:var(--color-danger);}
|
|
1521
1521
|
.fdy-help{font-size:var(--text-xs);color:var(--color-text-muted);}
|
|
1522
|
-
|
|
1522
|
+
/* --danger is the tone word everywhere else in the kit; help text takes either (CATATAN-FREEDAY §11). */
|
|
1523
|
+
.fdy-help--error,.fdy-help--danger{color:var(--color-danger);}
|
|
1523
1524
|
|
|
1524
1525
|
/* Freeday. Keyboard key */
|
|
1525
1526
|
.fdy-kbd{display:inline-flex;align-items:center;justify-content:center;min-width:1.5rem;height:1.5rem;padding:0 var(--space-2);font-family:var(--font-mono);font-size:var(--text-xs);color:var(--color-text);background:var(--color-surface-2);border:var(--bw) solid var(--color-border-strong);border-bottom-width:2px;border-radius:var(--radius-sm);}
|
|
@@ -1601,12 +1602,12 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
1601
1602
|
.fdy-btn-split__toggle svg{display:block;width:1rem;height:1rem;}
|
|
1602
1603
|
|
|
1603
1604
|
/* Freeday. Modal (native <dialog>) */
|
|
1604
|
-
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - 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)
|
|
1605
|
+
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - 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;}
|
|
1605
1606
|
.fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
|
|
1606
1607
|
.fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
|
|
1607
1608
|
.fdy-modal--lg{width:min(48rem,calc(100vw - var(--space-8)));}
|
|
1608
1609
|
.fdy-modal--wide{width:min(60rem,calc(100vw - var(--space-8)));}
|
|
1609
|
-
.fdy-modal::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition:opacity
|
|
1610
|
+
.fdy-modal::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-base);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
|
|
1610
1611
|
.fdy-modal__header{flex:none;display:flex;align-items:center;gap:var(--space-3);padding:var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
1611
1612
|
.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;}
|
|
1612
1613
|
.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);}
|
package/dist/freeday.js
CHANGED
|
@@ -409,10 +409,19 @@
|
|
|
409
409
|
if (bp === last) return;
|
|
410
410
|
last = bp;
|
|
411
411
|
root.setAttribute('data-breakpoint', bp);
|
|
412
|
-
|
|
412
|
+
fillLabels(document);
|
|
413
|
+
root.dispatchEvent(new CustomEvent('fdy-breakpoint-change', { bubbles: true, detail: { breakpoint: bp } }));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* Also run over late-mounted markup by the stack helpers, which find it through initAll: a label
|
|
417
|
+
rendered after load otherwise stayed empty until the next breakpoint change. */
|
|
418
|
+
function fillLabels(context) {
|
|
419
|
+
var ctx = context || document;
|
|
420
|
+
var bp = last || current();
|
|
421
|
+
if (ctx.matches && ctx.matches('[data-fdy-breakpoint-label]')) ctx.textContent = bp;
|
|
422
|
+
Array.prototype.forEach.call(ctx.querySelectorAll('[data-fdy-breakpoint-label]'), function (el) {
|
|
413
423
|
el.textContent = bp;
|
|
414
424
|
});
|
|
415
|
-
root.dispatchEvent(new CustomEvent('fdy-breakpoint-change', { bubbles: true, detail: { breakpoint: bp } }));
|
|
416
425
|
}
|
|
417
426
|
|
|
418
427
|
window.addEventListener('resize', update, { passive: true });
|
|
@@ -423,6 +432,7 @@
|
|
|
423
432
|
}
|
|
424
433
|
|
|
425
434
|
window.FreedayBreakpoint = {
|
|
435
|
+
initAll: fillLabels,
|
|
426
436
|
get: function () { return last || current(); },
|
|
427
437
|
current: current,
|
|
428
438
|
// true if the current breakpoint is `name` or wider (e.g. is('md') → md, lg, or xl)
|
|
@@ -1328,7 +1338,9 @@
|
|
|
1328
1338
|
initTriggers();
|
|
1329
1339
|
}
|
|
1330
1340
|
|
|
1331
|
-
|
|
1341
|
+
/* initAll is what every stack helper looks for (useFreeday in Vue and React, FreedayBlazor.initAll):
|
|
1342
|
+
without it they skipped this enhancer, and raw CFL markup rendered by a framework never opened. */
|
|
1343
|
+
window.FreedayCfl = { init: initTriggers, initAll: initTriggers };
|
|
1332
1344
|
})();
|
|
1333
1345
|
|
|
1334
1346
|
/* Freeday, chart renderer (optional, zero-dependency, no external chart lib).
|
|
@@ -1845,8 +1857,10 @@
|
|
|
1845
1857
|
/* Freeday, chip enhancer (optional, zero-dependency).
|
|
1846
1858
|
* Two behaviours, both progressive enhancement:
|
|
1847
1859
|
*
|
|
1848
|
-
* 1. Removable chips, any .fdy-chip__remove button
|
|
1849
|
-
*
|
|
1860
|
+
* 1. Removable chips, any .fdy-chip__remove button emits a bubbling, CANCELABLE
|
|
1861
|
+
* "fdy-chip-remove" (detail {value}) and then removes its chip, unless a listener called
|
|
1862
|
+
* preventDefault(). Cancel it whenever the app owns the removal: a confirm step, or a chip
|
|
1863
|
+
* rendered from framework state, which should leave the DOM when the state does.
|
|
1850
1864
|
* 2. Choice / filter groups, a [data-fdy-chips] container of interactive chips
|
|
1851
1865
|
* (.fdy-chip--choice / .fdy-chip--filter, each a <button>) toggles aria-pressed
|
|
1852
1866
|
* on click. Add data-single for one-at-a-time (radio-like) selection; filter
|
|
@@ -1872,8 +1886,12 @@
|
|
|
1872
1886
|
if (rm) rm.remove();
|
|
1873
1887
|
value = (clone.textContent || '').trim();
|
|
1874
1888
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1889
|
+
/* Cancelable, because the kit cannot know whether this removal needs asking first. Removing
|
|
1890
|
+
unconditionally made a chip vanish while the app's own confirm dialog was still open, and
|
|
1891
|
+
stay gone after Cancel, with the data intact and the screen wrong (CATATAN-FREEDAY §9). */
|
|
1892
|
+
var ev = new CustomEvent('fdy-chip-remove', { bubbles: true, cancelable: true, detail: { value: value } });
|
|
1893
|
+
chip.dispatchEvent(ev);
|
|
1894
|
+
if (!ev.defaultPrevented) chip.remove();
|
|
1877
1895
|
});
|
|
1878
1896
|
}
|
|
1879
1897
|
|
|
@@ -5465,6 +5483,83 @@
|
|
|
5465
5483
|
window.FreedayTree = { init: initTree, initAll: initAll };
|
|
5466
5484
|
})();
|
|
5467
5485
|
|
|
5486
|
+
/* Freeday, un-hydrated hook warning (optional, zero-dependency, no markup of its own).
|
|
5487
|
+
* Every enhancer marks the element it took over (`data-fdy-*-ready`). Markup that no initAll()
|
|
5488
|
+
* ever reached carries the hook and not the mark, and it fails in the worst way available: it
|
|
5489
|
+
* renders, looks finished, and does nothing, with no error anywhere. A `data-fdy-validate` form
|
|
5490
|
+
* outside the subtree handed to useFreeday() neither paints a message nor fires fdy-form-valid,
|
|
5491
|
+
* so its save button reads as dead (CATATAN-FREEDAY §10). The enhancer knows the attribute is
|
|
5492
|
+
* there; the person who wrote it does not.
|
|
5493
|
+
*
|
|
5494
|
+
* So on the first click, focus or submit inside such an element this logs ONE console.warn per
|
|
5495
|
+
* element, naming the hook, with the element itself as the second argument so devtools links it.
|
|
5496
|
+
* Interaction, not load, is the trigger on purpose: a framework mounts and hydrates in between,
|
|
5497
|
+
* and only once a person is touching the widget is "not enhanced yet" the same as "never".
|
|
5498
|
+
*
|
|
5499
|
+
* HOOKS pairs each hook selector with the ready flag its enhancer stamps on THAT element and the
|
|
5500
|
+
* namespace that owns it. A hook whose enhancer is not loaded is skipped: that is a different
|
|
5501
|
+
* mistake, and this message would misname it. test/docs.test.mjs fails if an enhancer gains a
|
|
5502
|
+
* ready flag this table does not list.
|
|
5503
|
+
*/
|
|
5504
|
+
(function () {
|
|
5505
|
+
'use strict';
|
|
5506
|
+
|
|
5507
|
+
var HOOKS = [
|
|
5508
|
+
['[data-fdy-app]', 'fdyAppShellReady', 'FreedayAppShell'],
|
|
5509
|
+
['[data-fdy-autocomplete]', 'fdyAcReady', 'FreedayAutocomplete'],
|
|
5510
|
+
['[data-fdy-carousel]', 'fdyCarouselReady', 'FreedayCarousel'],
|
|
5511
|
+
['[data-fdy-cascade]', 'fdyCascadeReady', 'FreedayCascade'],
|
|
5512
|
+
['[data-fdy-cfl]', 'fdyCflBound', 'FreedayCfl'],
|
|
5513
|
+
['[data-fdy-cfl-open]', 'fdyCflBound', 'FreedayCfl'],
|
|
5514
|
+
['[data-fdy-chart]', 'fdyChartReady', 'FreedayChart'],
|
|
5515
|
+
['[data-fdy-chips]', 'fdyChipsReady', 'FreedayChip'],
|
|
5516
|
+
['.fdy-chip__remove', 'fdyChipRmReady', 'FreedayChip'],
|
|
5517
|
+
['[data-fdy-datepicker]', 'fdyDpReady', 'FreedayDatepicker'],
|
|
5518
|
+
['[data-fdy-daterange]', 'fdyDrReady', 'FreedayDatepicker'],
|
|
5519
|
+
['[data-fdy-datetimepicker]', 'fdyDtReady', 'FreedayDatetime'],
|
|
5520
|
+
['[data-fdy-drawer]', 'fdyDrawerBound', 'FreedayDrawer'],
|
|
5521
|
+
['.fdy-drawer', 'fdyDrawerReady', 'FreedayDrawer'],
|
|
5522
|
+
['[data-fdy-validate]', 'fdyFormReady', 'FreedayForm'],
|
|
5523
|
+
['[data-fdy-mask]', 'fdyMaskReady', 'FreedayMask'],
|
|
5524
|
+
['[data-fdy-password]', 'fdyPwReady', 'FreedayMask'],
|
|
5525
|
+
['[data-fdy-menu]', 'fdyMenuReady', 'FreedayMenu'],
|
|
5526
|
+
['[data-fdy-number]', 'fdyNumberReady', 'FreedayNumber'],
|
|
5527
|
+
['[data-fdy-rating]', 'fdyRatingReady', 'FreedayRating'],
|
|
5528
|
+
['[data-fdy-search]', 'fdySearchReady', 'FreedaySearch'],
|
|
5529
|
+
['[data-fdy-combo]', 'fdyComboReady', 'FreedayCombo'],
|
|
5530
|
+
['[data-fdy-slider]', 'fdySliderReady', 'FreedaySlider'],
|
|
5531
|
+
['[data-fdy-stepper]', 'fdyStepperReady', 'FreedayStepper'],
|
|
5532
|
+
['[data-fdy-table]', 'fdyTableReady', 'FreedayTable'],
|
|
5533
|
+
['[data-fdy-tabs]', 'fdyTabsReady', 'FreedayTabs'],
|
|
5534
|
+
['[data-fdy-timepicker]', 'fdyTpReady', 'FreedayTimepicker'],
|
|
5535
|
+
['[data-fdy-tree]', 'fdyTreeReady', 'FreedayTree'],
|
|
5536
|
+
['[data-fdy-dropzone]', 'fdyUploadReady', 'FreedayUpload']
|
|
5537
|
+
];
|
|
5538
|
+
|
|
5539
|
+
var warned = typeof WeakSet === 'function' ? new WeakSet() : null;
|
|
5540
|
+
|
|
5541
|
+
function check(e) {
|
|
5542
|
+
if (!warned || !e.target || !e.target.closest) return;
|
|
5543
|
+
for (var i = 0; i < HOOKS.length; i++) {
|
|
5544
|
+
var hook = HOOKS[i];
|
|
5545
|
+
if (!window[hook[2]]) continue;
|
|
5546
|
+
var el = e.target.closest(hook[0]);
|
|
5547
|
+
if (!el || el.dataset[hook[1]] === '1' || warned.has(el)) continue;
|
|
5548
|
+
warned.add(el);
|
|
5549
|
+
console.warn('Freeday: this ' + hook[0] + ' was never enhanced, so it does nothing. Either it is '
|
|
5550
|
+
+ 'outside every initAll() scope (Vue/React: inside the element given to useFreeday(); Blazor: '
|
|
5551
|
+
+ 'FreedayBlazor.initAll after render; anything else: ' + hook[2] + '.initAll(el) once it is in '
|
|
5552
|
+
+ 'the DOM), or its markup lacks a part the enhancer needs (see COMPONENTS.md).', el);
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5555
|
+
|
|
5556
|
+
['click', 'focusin', 'submit'].forEach(function (type) {
|
|
5557
|
+
document.addEventListener(type, check, true);
|
|
5558
|
+
});
|
|
5559
|
+
|
|
5560
|
+
window.FreedayUnhydrated = { hooks: HOOKS };
|
|
5561
|
+
})();
|
|
5562
|
+
|
|
5468
5563
|
/* Freeday, file-upload enhancer (optional, zero-dependency).
|
|
5469
5564
|
* A drop target that also opens the file dialog on click / Enter / Space, validates each file
|
|
5470
5565
|
* (accept + max size), and renders a per-file row with explicit state. Actual upload is the
|
|
@@ -5485,7 +5580,8 @@
|
|
|
5485
5580
|
* Emits bubbling CustomEvents. BOTH on the dropzone element, which is the one target a consumer
|
|
5486
5581
|
* needs to listen on:
|
|
5487
5582
|
* "fdy-upload-add" {file, rejected, reason, row}
|
|
5488
|
-
* "fdy-upload-remove" {file}
|
|
5583
|
+
* "fdy-upload-remove" {file}, cancelable: preventDefault() keeps the row (e.g. to confirm
|
|
5584
|
+
* removing a file the server already holds, then call row.el.remove())
|
|
5489
5585
|
* `row` is the state machine over the rendered .fdy-file:
|
|
5490
5586
|
* .ready() (rest, where a dropped file starts) / .uploading() / .setProgress(pct) /
|
|
5491
5587
|
* .waiting(label) (sent, awaiting the server) / .done() / .fail(msg) / .el
|
|
@@ -5578,8 +5674,9 @@
|
|
|
5578
5674
|
Firing on both would look safer, but it makes the pair asymmetric, one `add` and, for
|
|
5579
5675
|
anyone delegating on a common ancestor, two `remove`s, which is its own silent bug. It
|
|
5580
5676
|
also fixes the listless case: a row that was never attached bubbles to nothing at all. */
|
|
5581
|
-
|
|
5582
|
-
|
|
5677
|
+
var ev = new CustomEvent('fdy-upload-remove', { bubbles: true, cancelable: true, detail: { file: file } });
|
|
5678
|
+
zone.dispatchEvent(ev);
|
|
5679
|
+
if (!ev.defaultPrevented) el.remove();
|
|
5583
5680
|
});
|
|
5584
5681
|
el.appendChild(icon);
|
|
5585
5682
|
el.appendChild(meta);
|
package/docs/agent-onboarding.md
CHANGED
|
@@ -229,4 +229,5 @@ Order of work that avoids a half-migrated mess:
|
|
|
229
229
|
- Keyboard: Tab reaches every control, focus is always visible, Esc closes overlays.
|
|
230
230
|
- Form errors carry `aria-invalid` + a linked message; icon-only buttons have `aria-label`.
|
|
231
231
|
- The interactive components on the screen have their enhancer loaded, and SPA-rendered DOM is
|
|
232
|
-
re-hydrated.
|
|
232
|
+
re-hydrated. Click each one once with devtools open: a `Freeday: this [data-fdy-…] was never
|
|
233
|
+
enhanced` warning means it sits outside every `initAll()` scope.
|
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.
|
|
201
|
+
Lands in `package.json` as `"@cahyo-dimas/freeday": "^3.13.0"` (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/docs/integrations.md
CHANGED
|
@@ -40,8 +40,8 @@ Every integration comes down to one of these:
|
|
|
40
40
|
| `freeday-mask` | `fdy-mask` `{value,raw}` | `FreedayMask` |
|
|
41
41
|
| `freeday-form` | `fdy-form-invalid` `{invalid}` / `fdy-form-valid` | `FreedayForm` |
|
|
42
42
|
| `freeday-table` | `fdy-table-change` · `fdy-row-select` | `FreedayTable` |
|
|
43
|
-
| `freeday-chip` | `fdy-chip-change` `{value,pressed,selected}` · `fdy-chip-remove` `{value}
|
|
44
|
-
| `freeday-upload` | `fdy-upload-add` / `fdy-upload-remove` | `FreedayUpload` |
|
|
43
|
+
| `freeday-chip` | `fdy-chip-change` `{value,pressed,selected}` · `fdy-chip-remove` `{value}`, cancelable | `FreedayChip` |
|
|
44
|
+
| `freeday-upload` | `fdy-upload-add` / `fdy-upload-remove` (cancelable) | `FreedayUpload` |
|
|
45
45
|
| `freeday-stepper` | `fdy-step-change` | `FreedayStepper` |
|
|
46
46
|
| `freeday-carousel` | `fdy-carousel-change` | `FreedayCarousel` |
|
|
47
47
|
| `freeday-breakpoint` | `fdy-breakpoint-change` | `FreedayBreakpoint` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cahyo-dimas/freeday",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.0",
|
|
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/harness-contract.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",
|
|
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",
|
|
@@ -4,10 +4,10 @@
|
|
|
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
|
-
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;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:transform
|
|
7
|
+
.fdy-drawer{position:fixed;inset:0 auto 0 0;margin:0;padding:0;height:100vh;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
8
|
.fdy-drawer[open]{display:flex;transform:none;}
|
|
9
9
|
.fdy-drawer--right{inset:0 0 0 auto;transform:translateX(100%);}
|
|
10
|
-
.fdy-drawer::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition:opacity
|
|
10
|
+
.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;}
|
|
11
11
|
.fdy-drawer__header{flex:none;display:flex;align-items:center;gap:var(--space-3);padding:var(--space-4) var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
12
12
|
.fdy-drawer__title{font-family:var(--font-display);font-weight:var(--weight-semibold);font-size:var(--text-lg);color:var(--color-text);margin:0 auto 0 0;}
|
|
13
13
|
.fdy-drawer__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);}
|
package/src/components/input.css
CHANGED
|
@@ -56,4 +56,5 @@ fieldset.fdy-field>legend{padding:0;float:none;margin-bottom:var(--space-2);}
|
|
|
56
56
|
remember to mark aria-hidden, and it cannot be forgotten. */
|
|
57
57
|
.fdy-label--required::after{content:"*" / "";margin-left:var(--space-1);color:var(--color-danger);}
|
|
58
58
|
.fdy-help{font-size:var(--text-xs);color:var(--color-text-muted);}
|
|
59
|
-
|
|
59
|
+
/* --danger is the tone word everywhere else in the kit; help text takes either (CATATAN-FREEDAY §11). */
|
|
60
|
+
.fdy-help--error,.fdy-help--danger{color:var(--color-danger);}
|
package/src/components/modal.css
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/* Freeday. Modal (native <dialog>) */
|
|
2
|
-
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - 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)
|
|
2
|
+
.fdy-modal{width:min(32rem,calc(100vw - var(--space-8)));max-height:calc(100vh - 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
3
|
.fdy-modal--sm{width:min(24rem,calc(100vw - var(--space-8)));}
|
|
4
4
|
.fdy-modal--md{width:min(32rem,calc(100vw - var(--space-8)));}
|
|
5
5
|
.fdy-modal--lg{width:min(48rem,calc(100vw - var(--space-8)));}
|
|
6
6
|
.fdy-modal--wide{width:min(60rem,calc(100vw - var(--space-8)));}
|
|
7
|
-
.fdy-modal::backdrop{background:rgba(10,12,25,.5);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);opacity:0;transition:opacity
|
|
7
|
+
.fdy-modal::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-base);transition-timing-function:var(--ease-standard),ease,ease;transition-behavior:normal,allow-discrete,allow-discrete;}
|
|
8
8
|
.fdy-modal__header{flex:none;display:flex;align-items:center;gap:var(--space-3);padding:var(--space-5);border-bottom:var(--bw) solid var(--color-border-muted);}
|
|
9
9
|
.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
10
|
.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);}
|