@cahyo-dimas/freeday 3.10.0 → 3.12.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 +105 -0
- package/COMPONENTS.md +72 -3
- package/README.id.md +4 -4
- package/README.md +3 -3
- package/adapters/blazor/FdyCascade.razor +2 -1
- package/adapters/blazor/FdyCascade.razor.cs +7 -0
- package/adapters/blazor/FdyCombo.razor +2 -1
- package/adapters/blazor/FdyCombo.razor.cs +7 -0
- package/adapters/blazor/FdyDateRange.razor +20 -2
- package/adapters/blazor/FdyDateRange.razor.cs +20 -0
- package/adapters/blazor/FdyDatepicker.razor +1 -0
- package/adapters/blazor/FdyDatepicker.razor.cs +7 -0
- package/adapters/blazor/FdyMonthpicker.razor +1 -0
- package/adapters/blazor/FdyMonthpicker.razor.cs +7 -0
- package/adapters/blazor/FdyTimepicker.razor +24 -0
- package/adapters/blazor/FdyTimepicker.razor.cs +86 -0
- package/adapters/react/components/FdyCascade.tsx +10 -1
- package/adapters/react/components/FdyCombo.tsx +9 -0
- package/adapters/react/components/FdyDateRange.tsx +29 -0
- package/adapters/react/components/FdyDatepicker.tsx +10 -1
- package/adapters/react/components/FdyMonthpicker.tsx +10 -1
- package/adapters/react/components/FdyTimepicker.tsx +221 -0
- package/adapters/react/index.d.ts +1 -0
- package/adapters/react/index.js +1 -0
- package/adapters/vue/components/FdyCascade.vue +10 -1
- package/adapters/vue/components/FdyCombo.vue +9 -0
- package/adapters/vue/components/FdyDateRange.vue +29 -0
- package/adapters/vue/components/FdyDatepicker.vue +10 -1
- package/adapters/vue/components/FdyMonthpicker.vue +10 -1
- package/adapters/vue/components/FdyTimepicker.vue +285 -0
- package/adapters/vue/index.d.ts +1 -0
- package/adapters/vue/index.js +1 -0
- package/dist/freeday-cascade.js +5 -0
- package/dist/freeday-datepicker.js +5 -0
- package/dist/freeday-form.js +60 -0
- package/dist/freeday-timepicker.js +49 -1
- package/dist/freeday.js +119 -1
- package/docs/agent-onboarding.md +3 -3
- package/docs/getting-started.md +5 -5
- package/docs/integrations.md +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,111 @@ 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.12.0] - 2026-10-27
|
|
13
|
+
|
|
14
|
+
**Sebuah form kini dapat menuntut kontrol kit terisi.** `data-fdy-validate` mewirekan Constraint
|
|
15
|
+
Validation **native**: `isCandidate` menuntut `willValidate` dan menolak `type === 'button'`, dan
|
|
16
|
+
pemicu kontrol kit memang `<button>`. Jadi `form.elements` tak pernah menawarkannya, `required` di
|
|
17
|
+
atasnya tak berarti apa pun, dan form tak bisa menuntut combo, datepicker, monthpicker, timepicker
|
|
18
|
+
atau cascade diisi.
|
|
19
|
+
|
|
20
|
+
Ini yang menahan audit 3.11.0 di 3 dari 7 pemilih. Empat `<input type="time">` di formulir shift
|
|
21
|
+
keamanan-cluster tetap native **hanya** karena menukarnya ke `FdyTimepicker` akan memperbaiki
|
|
22
|
+
penampilannya dan diam-diam membuang penegakan wajib-isinya.
|
|
23
|
+
|
|
24
|
+
Koreksi atas catatan rilis sebelumnya, yang layak dibawa: ia menulis "tiga belas wrapper bertipe
|
|
25
|
+
merender `<button>`". Salah. Hanya **lima** yang berpemicu tombol; `FdyAutocomplete` dan `FdyCfl`
|
|
26
|
+
merender `<input>` asli, dan tujuh sisanya bukan kontrol bernilai. Angka yang dibesarkan membuat
|
|
27
|
+
perbaikan ini terdengar jauh lebih mahal daripada yang ternyata.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- **`data-fdy-required` pada akar kontrol** (`#096`), dan prop `required` / `Required` di
|
|
31
|
+
`FdyCombo`, `FdyDatepicker`, `FdyMonthpicker`, `FdyTimepicker` dan `FdyCascade` di ketiga stack.
|
|
32
|
+
Submit ditolak, pesannya dilukis di slot `.fdy-field` yang sudah ada, dan fokusnya ke pemicu.
|
|
33
|
+
Aturannya **tidak menyebut satu pun nama komponen**: nilainya dibaca dari `data-value`, elemen
|
|
34
|
+
yang menerima `aria-invalid` dan fokus ditemukan lewat `aria-haspopup` yang sudah dipasang setiap
|
|
35
|
+
pemicu, dan re-validasinya memakai `MutationObserver` pada atribut itu — supaya enhancer tak
|
|
36
|
+
perlu mengenal lima nama event (`fdy-change`, `fdy-datepicker-change`, `fdy-time-select`,
|
|
37
|
+
`fdy-cascade-change`).
|
|
38
|
+
- **Kontrol yang ditolak diurut menurut urutan DOM** (`#096`). Mengumpulkan kontrol native lebih
|
|
39
|
+
dulu lalu akar kit akan memfokuskan field KEDUA sementara yang pertama juga merah, dan pembaca
|
|
40
|
+
yang dikirim melewati field merah pertamanya belajar hal yang salah.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
- **`data-value` di akar kontrol kini cermin hidup, bukan seed.** Di jalur mentah, datepicker,
|
|
44
|
+
timepicker dan cascade hanya MEMBACANYA saat init; `freeday-select.js` sudah menulisnya kembali
|
|
45
|
+
sejak lama, jadi ini membuat keluarganya sepakat alih-alih memperkenalkan atribut kedua. Aditif:
|
|
46
|
+
tak ada yang membaca atribut itu sesudah init, sebab sebelum ini nilainya basi. Cerminnya
|
|
47
|
+
dipasang di `updateDisplay()`, satu titik sempit yang dilalui pick, clear, pilih-bulan dan
|
|
48
|
+
setRange, bukan di tiap tempat event dikirim.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
- **`FdyDatepicker`, `FdyMonthpicker` dan `FdyCascade` tak pernah mencerminkan nilainya ke akar**
|
|
52
|
+
di adapter Vue dan React (`#096`), padahal `FdyCombo` dan `FdyTimepicker` melakukannya. Tanpa itu
|
|
53
|
+
aturan di atas mustahil, dan atributnya juga pegangan yang dapat dibaca uji atau aturan CSS
|
|
54
|
+
keadaan-kosong.
|
|
55
|
+
|
|
56
|
+
### Catatan
|
|
57
|
+
- `FdyAutocomplete` dan `FdyCfl` **di luar** aturan ini: keduanya merender `<input>` asli, jadi
|
|
58
|
+
`required` native yang cocok. Belum ada prop yang mengeksposnya, dan atribut tak dikenal jatuh ke
|
|
59
|
+
AKAR wrapper bukan ke input di dalamnya, jadi hari ini keduanya tak bisa dijadikan wajib di stack
|
|
60
|
+
mana pun. Dicatat terbuka di `NEXT-UP.md` alih-alih ditutup separuh: mengalirkannya lewat jalur
|
|
61
|
+
kit akan memberi satu field dua sumber kebenaran.
|
|
62
|
+
- Penimpaan pesan per-kontrol (`data-fdy-msg-required` di akar) bekerja di mana atribut mencapai
|
|
63
|
+
akar: jalur mentah, dan Vue, yang meneruskan atribut tak dikenal. React dan Blazor tidak, jadi
|
|
64
|
+
jalur yang terbaca sama di semua stack adalah `data-fdy-text-required` di elemen `<form>`.
|
|
65
|
+
|
|
66
|
+
## [3.11.0] - 2026-10-26
|
|
67
|
+
|
|
68
|
+
**Audit kontrol native di seluruh layar sebuah aplikasi konsumen, bukan di satu layar yang
|
|
69
|
+
mengeluh.** Pemiliknya melaporkan tampilan HP aslinya tak cocok dengan peramban di lebar yang sama,
|
|
70
|
+
dan dugaan pertama -- termasuk dugaan saya -- adalah CSS. Pengukuran menyangkalnya: dengan
|
|
71
|
+
stylesheet aplikasi yang sebenarnya, pada 375x667, Chromium dan WebKit menata `date`, `time`,
|
|
72
|
+
`number`, `search` dan `text` **identik**, 293x44 keenamnya, selisih satu piksel pada `y` yang
|
|
73
|
+
cuma pembulatan. Jadi yang berbeda bukan tata letaknya, melainkan **widget native-nya**: `date` dan
|
|
74
|
+
`time` digambar sistem operasi, sehingga iOS memberi roda gulir, Android piringan jam dan desktop
|
|
75
|
+
sebuah stepper, masing-masing dengan lebar dan format nilainya sendiri, dan tak ada CSS yang
|
|
76
|
+
menjangkaunya. Itu sebabnya satu HP dan satu peramban di lebar yang sama bisa tidak sepakat soal
|
|
77
|
+
layar yang selebihnya sama.
|
|
78
|
+
|
|
79
|
+
Kit sudah mengatakan ini di tabelnya sendiri ("Use `.fdy-datepicker` / `.fdy-timepicker` /
|
|
80
|
+
`.fdy-datetimepicker` instead") dan sudah mengirim CSS plus enhancer-nya. Yang belum ada adalah
|
|
81
|
+
wrapper bertipe untuk timepicker, jadi aplikasi Vue/React/Blazor harus merakit sendiri
|
|
82
|
+
plumbing custom-event-nya -- dan merakit sendiri adalah yang membuat empat `<input type="time">`
|
|
83
|
+
bertahan di satu formulir shift.
|
|
84
|
+
|
|
85
|
+
Catatan kejujuran: perbedaan iOS itu **tidak** direproduksi di sini. WebKit desktop bukan Mobile
|
|
86
|
+
Safari, dan penampilan kontrol native memang bersifat platform; buktinya adalah tangkapan layar
|
|
87
|
+
perangkat nyata plus fakta terukur bahwa tata letak per lebar sudah identik. Yang dapat diuji
|
|
88
|
+
lokal, dan kini diuji, adalah perilaku penggantinya.
|
|
89
|
+
|
|
90
|
+
### Added
|
|
91
|
+
- **`FdyTimepicker`** (`#094`), wrapper bertipe di **ketiga** stack, menjadikannya tiga belas.
|
|
92
|
+
Vue dan React mengimplementasikan listbox-nya secara native sebab ia kontrol form -- alasan yang
|
|
93
|
+
sama dengan `FdyCombo`, dan alasan yang `FdyChart` nyatakan dari sisi sebaliknya: enhancer yang
|
|
94
|
+
menguasai DOM berkelahi dengan render framework dan tak menyisakan `v-model`. Blazor
|
|
95
|
+
menjembatani enhancer lewat seed, seperti `FdyDatepicker`. Sorotannya **dijepit, bukan
|
|
96
|
+
berputar**: pada daftar 48 opsi, ArrowUp di `00:00` yang melompat ke `23:30` terbaca sebagai
|
|
97
|
+
kerusakan. Sorotan juga digulirkan ke dalam pandangan, sebab panelnya `max-height:15rem` dan
|
|
98
|
+
sorotan yang tak terlihat bukan sorotan.
|
|
99
|
+
- **`data-id` dan `data-describedby` pada seed timepicker** (`#094`). Keduanya tak terbaca
|
|
100
|
+
sebelumnya, jadi host yang menampilkan teks galat tak punya cara menautkannya. Pemicunya
|
|
101
|
+
`<button>`, yang **bukan** elemen labelable, sehingga `<label for>` memang tak akan pernah
|
|
102
|
+
mengaktifkannya -- itu didokumentasikan sekarang, bukan dibiarkan jadi kejutan.
|
|
103
|
+
- **`FreedayTimepicker.setState(root, { disabled, readonly, invalid })`** (`#094`), bentuknya sama
|
|
104
|
+
dengan `FreedayDatepicker.setState`. Tanpa ini, host yang merender seed-nya sekali lalu tidak
|
|
105
|
+
pernah lagi (Blazor, `ShouldRender => false`) memiliki tiga parameter keadaan yang tak ada yang
|
|
106
|
+
membacanya setelah render pertama.
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
- **`FdyDateRange` membuang kedelapan prop label navigasi kalender** (`#095`), di ketiga stack.
|
|
110
|
+
`FdyDatepicker` menerimanya sejak 2.1.1; rentangnya tidak meneruskannya, sehingga rentang tanggal
|
|
111
|
+
adalah **satu-satunya** kontrol tanggal di kit yang navigasinya tak bisa dinamai dalam bahasa
|
|
112
|
+
halamannya sendiri -- aplikasi berbahasa Indonesia mendapat "Previous month" tepat di tempat yang
|
|
113
|
+
tak dapat ia jangkau. Diteruskan apa adanya ke kedua pemilih. Prop datar `xxxLabel`, bukan objek
|
|
114
|
+
`labels`, sebab bentuk itulah yang sudah dipakai setiap komponen lain di kit. Blazor menambah
|
|
115
|
+
`BackToMonthsLabel`, karena seed yang kedua pemilihnya dibangun dari situ punya tombol itu.
|
|
116
|
+
|
|
12
117
|
## [3.10.0] - 2026-10-26
|
|
13
118
|
|
|
14
119
|
**Lima rilis dalam satu hari, empat di antaranya menambal satu kelas yang sama, dan pemiliknya
|
package/COMPONENTS.md
CHANGED
|
@@ -34,8 +34,9 @@ live docs.
|
|
|
34
34
|
invalid state does not belong to the control itself.
|
|
35
35
|
6. **Interactive components need their enhancer script.** Static ones (button, card, badge, table,
|
|
36
36
|
alert, breadcrumb, timeline, accordion, tree-without-cascade…) are CSS-only.
|
|
37
|
-
7. **On Vue, React or Blazor,
|
|
38
|
-
`FdyDatepicker` · `FdyMonthpicker` · `FdyDateRange` · `
|
|
37
|
+
7. **On Vue, React or Blazor, thirteen components have a typed wrapper. Use it.** `FdyCombo` ·
|
|
38
|
+
`FdyDatepicker` · `FdyMonthpicker` · `FdyDateRange` · `FdyTimepicker` · `FdyAutocomplete` ·
|
|
39
|
+
`FdyCascade` · `FdyCfl` ·
|
|
39
40
|
`FdyChart` · `FdyTable` · `FdyModal` · `FdyDrawer` · `FdyAppShell`, from `@cahyo-dimas/freeday/vue`, `/react`, or the
|
|
40
41
|
`Freeday.Blazor` RCL. Each is flagged at its own section below. Hand-writing their raw markup in
|
|
41
42
|
those stacks *looks* right at first, because the enhancer initialises once and the first render is
|
|
@@ -708,6 +709,7 @@ while each line referenced directly would be read even when hidden. Zero JS; wor
|
|
|
708
709
|
| `ariaLabelledby?` | `string` | Id of the element that labels the combobox. |
|
|
709
710
|
| `placeholder?` | `string` | Shown when no option matches the current value. |
|
|
710
711
|
| `disabled?` | `boolean` | Greyed and out of the tab order, the native `disabled` semantics. |
|
|
712
|
+
| `required?` | `boolean` | Lets a [`data-fdy-validate`](#form-validation--data-fdy-validate) form demand this control be filled. Renders `data-fdy-required` on the root. Needed because the trigger is a `<button>`, whose `willValidate` is false, so a plain `required` on it has never meant anything. Blazor: `Required`. |
|
|
711
713
|
| `readonly?` | `boolean` | Locked/view mode: keeps focus and tab order and shows its value, but cannot be opened or changed. Unlike `disabled` it is not greyed. |
|
|
712
714
|
| `invalid?` | `boolean` | Sets `aria-invalid`; pair it with `describedby` pointing at the error text. |
|
|
713
715
|
| `describedby?` | `string` | Id of the help or error text (`aria-describedby`). |
|
|
@@ -785,6 +787,7 @@ Editable combobox that filters as you type. Needs `freeday-autocomplete.js`.
|
|
|
785
787
|
| `placeholder?` | `string` | |
|
|
786
788
|
| `id?` · `ariaLabelledby?` · `describedby?` | `string` | Trigger id; the element that labels it; the help/error text it points at. |
|
|
787
789
|
| `disabled?` · `readonly?` · `invalid?` | `boolean` | As on `<FdyCombo>`. |
|
|
790
|
+
| `required?` | `boolean` | Lets a [`data-fdy-validate`](#form-validation--data-fdy-validate) form demand this control be filled. Renders `data-fdy-required` on the root. Needed because the trigger is a `<button>`, whose `willValidate` is false, so a plain `required` on it has never meant anything. Blazor: `Required`. |
|
|
788
791
|
|
|
789
792
|
Raw path: `data-disabled` / `data-readonly` / `data-invalid`, `data-id`, `data-describedby`, and
|
|
790
793
|
`FreedayCascade.setState(root, { disabled, readonly, invalid })` for a host that rendered once.
|
|
@@ -961,6 +964,7 @@ a new class.
|
|
|
961
964
|
| `placeholder?` | `string` | |
|
|
962
965
|
| `id?` · `ariaLabelledby?` · `describedby?` | `string` | Trigger id; the element that labels it; the help/error text it points at. |
|
|
963
966
|
| `disabled?` · `readonly?` · `invalid?` | `boolean` | `readonly` keeps focus and tab order and shows its date, but it cannot be opened, cleared or changed. |
|
|
967
|
+
| `required?` | `boolean` | Lets a [`data-fdy-validate`](#form-validation--data-fdy-validate) form demand this control be filled. Renders `data-fdy-required` on the root. Needed because the trigger is a `<button>`, whose `willValidate` is false, so a plain `required` on it has never meant anything. Blazor: `Required`. |
|
|
964
968
|
| `clearable?` | `boolean` | Show a × in the trigger once a date is set, so an optional date can be unset. Sends `''`. Off by default. |
|
|
965
969
|
| `clearLabel?` | `string` | Accessible name for that ×. Default `Clear date`. |
|
|
966
970
|
| `prevMonthLabel?` · `nextMonthLabel?` | `string` | The month arrows. Defaults `Previous month` / `Next month`. |
|
|
@@ -995,6 +999,7 @@ page's `<html lang>`), `clearable` and `clearLabel` (this path builds no clear b
|
|
|
995
999
|
| `placeholder?` | `string` | Shown while nothing is picked. Default `Select month`. |
|
|
996
1000
|
| `id?` · `ariaLabelledby?` · `describedby?` | `string` | Trigger id; the element that labels it; the help/error text it points at. |
|
|
997
1001
|
| `disabled?` · `readonly?` · `invalid?` | `boolean` | `readonly` keeps focus and tab order and shows its month, but it cannot be opened, cleared or changed. |
|
|
1002
|
+
| `required?` | `boolean` | Lets a [`data-fdy-validate`](#form-validation--data-fdy-validate) form demand this control be filled. Renders `data-fdy-required` on the root. Needed because the trigger is a `<button>`, whose `willValidate` is false, so a plain `required` on it has never meant anything. Blazor: `Required`. |
|
|
998
1003
|
| `clearable?` | `boolean` | Show a × in the trigger once a month is set, so an optional month can be unset. Sends `''`. Off by default. |
|
|
999
1004
|
| `clearLabel?` | `string` | Accessible name for that ×. Default `Clear month`. |
|
|
1000
1005
|
| `prevYearLabel?` · `nextYearLabel?` | `string` | The arrows beside the month grid, which step a year. Defaults `Previous year` / `Next year`. |
|
|
@@ -1020,6 +1025,7 @@ grid's own title.
|
|
|
1020
1025
|
| `startPlaceholder?` · `endPlaceholder?` | `string` | One per field. |
|
|
1021
1026
|
| `ariaLabel?` · `ariaLabelledby?` · `describedby?` | `string` | Names the group (`role="group"`), or points at the text that does. |
|
|
1022
1027
|
| `disabled?` · `readonly?` · `invalid?` | `boolean` | Applied to both pickers together. |
|
|
1028
|
+
| `prevMonthLabel?` · `nextMonthLabel?` · `prevYearLabel?` · `nextYearLabel?` · `prevYearsLabel?` · `nextYearsLabel?` · `chooseMonthLabel?` · `chooseYearLabel?` | `string` | Forwarded verbatim to **both** pickers. Until 3.11.0 this was the one picker in the kit whose calendar nav could not be named in the page's own language: the single picker took these, the range dropped them. Blazor adds `BackToMonthsLabel`, since the raw seed both pickers are built from has that button. |
|
|
1023
1029
|
|
|
1024
1030
|
Blazor splits the value into two scalars, `From` / `FromChanged` and `To` / `ToChanged`, because a
|
|
1025
1031
|
C# binding cannot express one object bound in two places, and it adds `Separator`.
|
|
@@ -1106,10 +1112,53 @@ it has always been, down to the `YYYY-MM-DD` it reports.
|
|
|
1106
1112
|
```
|
|
1107
1113
|
|
|
1108
1114
|
## Time picker — `data-fdy-timepicker`
|
|
1109
|
-
|
|
1115
|
+
> **Typed wrapper: `<FdyTimepicker>`**. Vue (`v-model`) · React (`value`/`onChange`) · Blazor (`@bind-Value`). In those stacks use the wrapper; the markup below is for stacks without an adapter (and is what the wrapper renders).
|
|
1116
|
+
|
|
1117
|
+
24-hour `HH:MM` listbox. **Author an empty `<div>`** — the enhancer builds everything. Needs
|
|
1110
1118
|
`freeday-timepicker.js`. Classes: `.fdy-timepicker` (+`--error`) · `__trigger` `__value`
|
|
1111
1119
|
(+`--placeholder`) `__icon` `__panel` `__opt`.
|
|
1112
1120
|
|
|
1121
|
+
Seed attributes: `data-step` (minutes, default 30), `data-min` (default `00:00`), `data-max`
|
|
1122
|
+
(default `23:59`), `data-value`, `data-label`, `data-placeholder` (default `--:--`), `data-id`,
|
|
1123
|
+
`data-describedby`, `data-disabled`, `data-readonly`, `data-invalid`. Emits a bubbling
|
|
1124
|
+
`fdy-time-select` (`detail {value}`). `FreedayTimepicker.setState(root, { disabled, readonly,
|
|
1125
|
+
invalid })` changes the three states afterwards, which is what a host that renders its seed once
|
|
1126
|
+
and never again (Blazor) needs.
|
|
1127
|
+
|
|
1128
|
+
**Use this instead of `<input type="time">`**, and not for theming alone: the native picker is
|
|
1129
|
+
drawn by the *operating system*, so iOS shows a scroll wheel, Android a clock dial and desktop a
|
|
1130
|
+
stepper, each with its own width and value format, and no CSS reaches any of them. A phone and a
|
|
1131
|
+
browser at the same width therefore disagree about a screen that is otherwise identical.
|
|
1132
|
+
|
|
1133
|
+
```html
|
|
1134
|
+
<div data-fdy-timepicker data-step="15" data-min="06:00" data-max="22:00"
|
|
1135
|
+
data-value="07:00" data-label="Jam mulai"></div>
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
### Props — `<FdyTimepicker>`
|
|
1139
|
+
|
|
1140
|
+
| Prop | Type | What it does |
|
|
1141
|
+
|---|---|---|
|
|
1142
|
+
| `modelValue` · `value` | `string \| null` | `HH:MM`, 24-hour. `''`/`null` for empty. A loose `H:MM` is normalised for matching. Blazor: `Value` / `@bind-Value`. |
|
|
1143
|
+
| `onChange` | `(value: string) => void` | React. Vue emits `update:modelValue` and `change`, Blazor `ValueChanged`. |
|
|
1144
|
+
| `step?` | `number` | Minutes between options. Default 30. |
|
|
1145
|
+
| `min?` · `max?` | `string` | `HH:MM` bounds, inclusive. Default `00:00` to `23:59`. |
|
|
1146
|
+
| `placeholder?` | `string` | Shown when there is no value. Default `--:--`. |
|
|
1147
|
+
| `ariaLabel?` · `ariaLabelledby?` · `describedby?` | `string` | Names the trigger, or points at the text that does. Blazor: `Label` / `Describedby`. |
|
|
1148
|
+
| `id?` | `string` | Id for the trigger. It is a `<button>`, which is **not** a labelable element, so a `<label for>` will not activate it — name it with `ariaLabel`/`ariaLabelledby`. |
|
|
1149
|
+
| `disabled?` · `readonly?` · `invalid?` | `boolean` | `readonly` keeps tab order and shows the time but will not open. |
|
|
1150
|
+
| `required?` | `boolean` | Lets a [`data-fdy-validate`](#form-validation--data-fdy-validate) form demand this control be filled. Renders `data-fdy-required` on the root. Needed because the trigger is a `<button>`, whose `willValidate` is false, so a plain `required` on it has never meant anything. Blazor: `Required`. |
|
|
1151
|
+
|
|
1152
|
+
Keyboard: `ArrowDown`/`ArrowUp` move one option, `Home`/`End` jump to the ends, `Enter`/`Space`
|
|
1153
|
+
pick, `Escape` closes. The highlight is **clamped, not cyclic** — `ArrowUp` at `00:00` stays there,
|
|
1154
|
+
because wrapping to `23:30` reads as a glitch in a list this long — and it is scrolled into view,
|
|
1155
|
+
since the panel is `max-height:15rem` and a highlight nobody can see is no highlight.
|
|
1156
|
+
|
|
1157
|
+
**Deliberate difference between the two paths.** The wrappers keep focus on the **trigger** with
|
|
1158
|
+
`aria-activedescendant` on it, which is what `FdyCombo`, `FdyDatepicker` and `FdyAutocomplete` do;
|
|
1159
|
+
the raw enhancer moves focus **into the panel**. Both are valid APG listbox patterns, and the Vue
|
|
1160
|
+
and React adapters follow their own family rather than the enhancer.
|
|
1161
|
+
|
|
1113
1162
|
## Datetime picker — `data-fdy-datetimepicker`
|
|
1114
1163
|
A **composition** of the two above, not a monolith: nest one datepicker and one timepicker inside
|
|
1115
1164
|
the wrapper. `freeday-datetime.js` joins them and emits one `fdy-datetime-change` with
|
|
@@ -1179,6 +1228,26 @@ blur/input. Needs `freeday-form.js`.
|
|
|
1179
1228
|
- Per-control messages: `data-fdy-msg-required`, `-type`, `-minlength`, `-min`, `-mismatch`, …
|
|
1180
1229
|
- Cross-field: `data-fdy-match="#otherFieldId"`
|
|
1181
1230
|
- Emits `fdy-form-valid` / `fdy-form-invalid`.
|
|
1231
|
+
- **A kit control can be required too** — `data-fdy-required` on its root. Combo, datepicker,
|
|
1232
|
+
monthpicker, timepicker and cascade build a `<button>` trigger, and a button's `willValidate` is
|
|
1233
|
+
false, so `form.elements` never offered one and `required` on it meant nothing. The enhancer now
|
|
1234
|
+
collects these roots alongside the native controls: empty `data-value` refuses the submit, the
|
|
1235
|
+
message lands in the same `.fdy-field` slot, and the focus goes to the trigger. It names no
|
|
1236
|
+
component — the value is read from `data-value` and the trigger is found by `aria-haspopup`. In
|
|
1237
|
+
the typed stacks pass `required` / `Required` instead of writing the attribute.
|
|
1238
|
+
**`FdyAutocomplete` and `FdyCfl` are outside this rule**: they render a real `<input>`, so the
|
|
1239
|
+
native `required` is the mechanism that fits them — but no prop exposes it yet, and an unknown
|
|
1240
|
+
attribute falls through to a wrapper's ROOT rather than to the input inside it, so today an app
|
|
1241
|
+
cannot require either one. Tracked in `NEXT-UP.md`; the rule above deliberately does not cover
|
|
1242
|
+
them, because painting a native input through the kit path would give it two sources of truth.
|
|
1243
|
+
Re-validation watches the attribute with a `MutationObserver`, so no control has to announce
|
|
1244
|
+
itself. The message override is per-control (`data-fdy-msg-required` on the root) wherever
|
|
1245
|
+
attributes reach the root — the raw path, and Vue, where an unknown attribute falls through. In
|
|
1246
|
+
React and Blazor they do not, so use the form-level `data-fdy-text-required`, which reads the
|
|
1247
|
+
same in every stack.
|
|
1248
|
+
- **`data-value` on a control's root is the live value**, not just the seed it used to be on the
|
|
1249
|
+
datepicker, timepicker and cascade (the combo always kept it current). That is what makes the
|
|
1250
|
+
rule above possible, and it is also the handle a test or a CSS empty-state rule can read.
|
|
1182
1251
|
- **A refused field is revealed before it is focused**, because `focus()` on a control that is not
|
|
1183
1252
|
rendered does nothing. A closed `<details>` around it (accordion, tree, nav group) is opened, an
|
|
1184
1253
|
unselected tab is selected by clicking it, and a `.fdy-step-panel` left behind is returned to by
|
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.12.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`.
|
|
@@ -44,9 +44,9 @@ enhancer:
|
|
|
44
44
|
| **Blazor (net8.0)** | RCL `Freeday.Blazor` | `@bind-Value` |
|
|
45
45
|
| HTML statis · Svelte · template server-rendered | *(tanpa wrapper)* markup mentah + enhancer, di bawah | event `fdy-*` |
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
`FdyAutocomplete` · `FdyCascade` · `FdyCfl` · `FdyChart` · `FdyTable` · `FdyModal` · `FdyDrawer` ·
|
|
49
|
-
`FdyAppShell`. Menulis markup mentah untuk
|
|
47
|
+
Ketiga belasnya: `FdyCombo` · `FdyDatepicker` · `FdyMonthpicker` · `FdyDateRange` ·
|
|
48
|
+
`FdyTimepicker` · `FdyAutocomplete` · `FdyCascade` · `FdyCfl` · `FdyChart` · `FdyTable` · `FdyModal` · `FdyDrawer` ·
|
|
49
|
+
`FdyAppShell`. Menulis markup mentah untuk ketiga belas ini di dalam Vue/React/Blazor *kelihatan* baik-baik saja pada mulanya, karena render
|
|
50
50
|
pertamanya memang benar. Setelah itu ia rusak diam-diam: DOM yang di-render framework belakangan tak pernah ter-hydrate, dan state widget
|
|
51
51
|
berakhir di DOM alih-alih di state framework-mu. Selebihnya (button, card, badge, alert, markup
|
|
52
52
|
tabel, layout, dan komponen interaktif yang tak punya wrapper) sama persis di semua stack. Untuk
|
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.12.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`.
|
|
@@ -44,8 +44,8 @@ enhancer:
|
|
|
44
44
|
| **Blazor (net8.0)** | `Freeday.Blazor` RCL | `@bind-Value` |
|
|
45
45
|
| Static HTML · Svelte · server-rendered templates | *(no wrapper)* raw markup + enhancers, below | `fdy-*` events |
|
|
46
46
|
|
|
47
|
-
The
|
|
48
|
-
`FdyCascade` · `FdyCfl` · `FdyChart` · `FdyTable` · `FdyModal` · `FdyDrawer` · `FdyAppShell`.
|
|
47
|
+
The thirteen: `FdyCombo` · `FdyDatepicker` · `FdyMonthpicker` · `FdyDateRange` · `FdyTimepicker` ·
|
|
48
|
+
`FdyAutocomplete` · `FdyCascade` · `FdyCfl` · `FdyChart` · `FdyTable` · `FdyModal` · `FdyDrawer` · `FdyAppShell`.
|
|
49
49
|
Hand-writing the raw
|
|
50
50
|
markup for these inside Vue/React/Blazor *looks* fine at first, because the first render is correct.
|
|
51
51
|
Then it breaks quietly:
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
data-describedby="@Describedby"
|
|
17
17
|
data-disabled="@(Disabled ? "true" : null)"
|
|
18
18
|
data-readonly="@(Readonly ? "true" : null)"
|
|
19
|
-
data-invalid="@(Invalid ? "true" : null)"
|
|
19
|
+
data-invalid="@(Invalid ? "true" : null)"
|
|
20
|
+
data-fdy-required="@(Required ? "" : null)">
|
|
20
21
|
<ul>
|
|
21
22
|
@foreach (FdyCascadeNode node in Nodes)
|
|
22
23
|
{
|
|
@@ -46,6 +46,13 @@ public partial class FdyCascade
|
|
|
46
46
|
/// <summary>Marks the field invalid (<c>aria-invalid</c> + the error styling).</summary>
|
|
47
47
|
[Parameter] public bool Invalid { get; set; }
|
|
48
48
|
|
|
49
|
+
/// <summary>Let a <c>data-fdy-validate</c> form demand this control be filled. Renders
|
|
50
|
+
/// <c>data-fdy-required</c> on the root, and <c>freeday-form.js</c> refuses the submit, paints
|
|
51
|
+
/// the message in the field and focuses the trigger. Needed because the trigger is a
|
|
52
|
+
/// <c><button></c>: <c>willValidate</c> is false, so a plain <c>required</c> on it has
|
|
53
|
+
/// never meant anything. Override the message with <c>data-fdy-msg-required</c> on the root.</summary>
|
|
54
|
+
[Parameter] public bool Required { get; set; }
|
|
55
|
+
|
|
49
56
|
protected override bool ShouldRender() => !Hydrated;
|
|
50
57
|
|
|
51
58
|
/* Pushed, not re-rendered: the seed's <ul> is consumed and removed by the enhancer, so this
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
markup the freeday-select.js enhancer expects; the enhancer owns the open/keyboard/focus
|
|
7
7
|
behaviour and emits fdy-change, which flows back to @bind-Value. An externally-changed Value
|
|
8
8
|
is pushed onto the enhancer silently (no event echo). *@
|
|
9
|
-
<div @ref="Root" class="@RootClass" data-fdy-combo data-value="@SelectedKey"
|
|
9
|
+
<div @ref="Root" class="@RootClass" data-fdy-combo data-value="@SelectedKey"
|
|
10
|
+
data-fdy-required="@(Required ? "" : null)">
|
|
10
11
|
<button id="@ButtonId" type="button" class="fdy-combo__button" role="combobox"
|
|
11
12
|
aria-haspopup="listbox" aria-expanded="false"
|
|
12
13
|
aria-labelledby="@AriaLabelledby"
|
|
@@ -29,6 +29,13 @@ public partial class FdyCombo<TValue>
|
|
|
29
29
|
|
|
30
30
|
[Parameter] public bool Invalid { get; set; }
|
|
31
31
|
|
|
32
|
+
/// <summary>Let a <c>data-fdy-validate</c> form demand this control be filled. Renders
|
|
33
|
+
/// <c>data-fdy-required</c> on the root, and <c>freeday-form.js</c> refuses the submit, paints
|
|
34
|
+
/// the message in the field and focuses the trigger. Needed because the trigger is a
|
|
35
|
+
/// <c><button></c>: <c>willValidate</c> is false, so a plain <c>required</c> on it has
|
|
36
|
+
/// never meant anything. Override the message with <c>data-fdy-msg-required</c> on the root.</summary>
|
|
37
|
+
[Parameter] public bool Required { get; set; }
|
|
38
|
+
|
|
32
39
|
/// <summary>Id of the help or error text the combobox describes itself with. Vue and React
|
|
33
40
|
/// have always had this one; Blazor was the odd stack out.</summary>
|
|
34
41
|
[Parameter] public string? Describedby { get; set; }
|
|
@@ -8,9 +8,27 @@
|
|
|
8
8
|
<div @ref="Root">
|
|
9
9
|
<div data-fdy-daterange role="group" aria-label="@AriaLabel">
|
|
10
10
|
<div data-fdy-datepicker data-role="from"
|
|
11
|
-
data-value="@From" data-placeholder="@FromPlaceholder" data-min="@Min" data-max="@Max"
|
|
11
|
+
data-value="@From" data-placeholder="@FromPlaceholder" data-min="@Min" data-max="@Max"
|
|
12
|
+
data-fdy-text-prev-month="@PrevMonthLabel"
|
|
13
|
+
data-fdy-text-next-month="@NextMonthLabel"
|
|
14
|
+
data-fdy-text-prev-year="@PrevYearLabel"
|
|
15
|
+
data-fdy-text-next-year="@NextYearLabel"
|
|
16
|
+
data-fdy-text-prev-years="@PrevYearsLabel"
|
|
17
|
+
data-fdy-text-next-years="@NextYearsLabel"
|
|
18
|
+
data-fdy-text-choose-month="@ChooseMonthLabel"
|
|
19
|
+
data-fdy-text-choose-year="@ChooseYearLabel"
|
|
20
|
+
data-fdy-text-back-to-months="@BackToMonthsLabel"></div>
|
|
12
21
|
<span class="fdy-daterange__sep">@Separator</span>
|
|
13
22
|
<div data-fdy-datepicker data-role="to"
|
|
14
|
-
data-value="@To" data-placeholder="@ToPlaceholder" data-min="@Min" data-max="@Max"
|
|
23
|
+
data-value="@To" data-placeholder="@ToPlaceholder" data-min="@Min" data-max="@Max"
|
|
24
|
+
data-fdy-text-prev-month="@PrevMonthLabel"
|
|
25
|
+
data-fdy-text-next-month="@NextMonthLabel"
|
|
26
|
+
data-fdy-text-prev-year="@PrevYearLabel"
|
|
27
|
+
data-fdy-text-next-year="@NextYearLabel"
|
|
28
|
+
data-fdy-text-prev-years="@PrevYearsLabel"
|
|
29
|
+
data-fdy-text-next-years="@NextYearsLabel"
|
|
30
|
+
data-fdy-text-choose-month="@ChooseMonthLabel"
|
|
31
|
+
data-fdy-text-choose-year="@ChooseYearLabel"
|
|
32
|
+
data-fdy-text-back-to-months="@BackToMonthsLabel"></div>
|
|
15
33
|
</div>
|
|
16
34
|
</div>
|
|
@@ -23,6 +23,26 @@ public partial class FdyDateRange
|
|
|
23
23
|
[Parameter] public string? Max { get; set; }
|
|
24
24
|
[Parameter] public string Separator { get; set; } = "–";
|
|
25
25
|
|
|
26
|
+
/* The calendars' own navigation labels, forwarded verbatim to BOTH pickers. Month and weekday
|
|
27
|
+
NAMES follow the page's `lang` through Intl and need nothing here; these are the buttons
|
|
28
|
+
around them. Until now the range was the one picker in the kit whose nav could not be named
|
|
29
|
+
in the page's own language -- FdyDatepicker took these, FdyDateRange dropped them, so an
|
|
30
|
+
Indonesian app got "Previous month" on the only date control it could not reach. Raised
|
|
31
|
+
from keamanan-cluster (#095). */
|
|
32
|
+
[Parameter] public string? PrevMonthLabel { get; set; }
|
|
33
|
+
[Parameter] public string? NextMonthLabel { get; set; }
|
|
34
|
+
[Parameter] public string? PrevYearLabel { get; set; }
|
|
35
|
+
[Parameter] public string? NextYearLabel { get; set; }
|
|
36
|
+
[Parameter] public string? PrevYearsLabel { get; set; }
|
|
37
|
+
[Parameter] public string? NextYearsLabel { get; set; }
|
|
38
|
+
[Parameter] public string? ChooseMonthLabel { get; set; }
|
|
39
|
+
[Parameter] public string? ChooseYearLabel { get; set; }
|
|
40
|
+
|
|
41
|
+
/// <summary>The year grid's title, back to months. Blazor only, because the raw seed both
|
|
42
|
+
/// pickers are built from supports it and <see cref="FdyDatepicker"/> already exposes it;
|
|
43
|
+
/// the Vue and React adapters build no such button.</summary>
|
|
44
|
+
[Parameter] public string? BackToMonthsLabel { get; set; }
|
|
45
|
+
|
|
26
46
|
private int _token;
|
|
27
47
|
|
|
28
48
|
// initAll(Root) links the daterange (descendant) and inits both child pickers.
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
data-disabled="@(Disabled ? "true" : null)"
|
|
19
19
|
data-readonly="@(Readonly ? "true" : null)"
|
|
20
20
|
data-invalid="@(Invalid ? "true" : null)"
|
|
21
|
+
data-fdy-required="@(Required ? "" : null)"
|
|
21
22
|
data-fdy-text-prev-month="@PrevMonthLabel"
|
|
22
23
|
data-fdy-text-next-month="@NextMonthLabel"
|
|
23
24
|
data-fdy-text-prev-year="@PrevYearLabel"
|
|
@@ -54,6 +54,13 @@ public partial class FdyDatepicker
|
|
|
54
54
|
/// <summary>Marks the field invalid (<c>aria-invalid</c> + the error styling).</summary>
|
|
55
55
|
[Parameter] public bool Invalid { get; set; }
|
|
56
56
|
|
|
57
|
+
/// <summary>Let a <c>data-fdy-validate</c> form demand this control be filled. Renders
|
|
58
|
+
/// <c>data-fdy-required</c> on the root, and <c>freeday-form.js</c> refuses the submit, paints
|
|
59
|
+
/// the message in the field and focuses the trigger. Needed because the trigger is a
|
|
60
|
+
/// <c><button></c>: <c>willValidate</c> is false, so a plain <c>required</c> on it has
|
|
61
|
+
/// never meant anything. Override the message with <c>data-fdy-msg-required</c> on the root.</summary>
|
|
62
|
+
[Parameter] public bool Required { get; set; }
|
|
63
|
+
|
|
57
64
|
/* The calendar's own navigation labels. Month and weekday NAMES follow the page's `lang`
|
|
58
65
|
through Intl and need nothing here; these are the buttons around them, which until 2.1.1
|
|
59
66
|
were literals inside the enhancer that no host could reach. Leave one null and the
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
data-disabled="@(Disabled ? "true" : null)"
|
|
22
22
|
data-readonly="@(Readonly ? "true" : null)"
|
|
23
23
|
data-invalid="@(Invalid ? "true" : null)"
|
|
24
|
+
data-fdy-required="@(Required ? "" : null)"
|
|
24
25
|
data-fdy-text-prev-year="@PrevYearLabel"
|
|
25
26
|
data-fdy-text-next-year="@NextYearLabel"
|
|
26
27
|
data-fdy-text-prev-years="@PrevYearsLabel"
|
|
@@ -57,6 +57,13 @@ public partial class FdyMonthpicker
|
|
|
57
57
|
/// <summary>Marks the field invalid (<c>aria-invalid</c> + the error styling).</summary>
|
|
58
58
|
[Parameter] public bool Invalid { get; set; }
|
|
59
59
|
|
|
60
|
+
/// <summary>Let a <c>data-fdy-validate</c> form demand this control be filled. Renders
|
|
61
|
+
/// <c>data-fdy-required</c> on the root, and <c>freeday-form.js</c> refuses the submit, paints
|
|
62
|
+
/// the message in the field and focuses the trigger. Needed because the trigger is a
|
|
63
|
+
/// <c><button></c>: <c>willValidate</c> is false, so a plain <c>required</c> on it has
|
|
64
|
+
/// never meant anything. Override the message with <c>data-fdy-msg-required</c> on the root.</summary>
|
|
65
|
+
[Parameter] public bool Required { get; set; }
|
|
66
|
+
|
|
60
67
|
/* The calendar's navigation labels, and only the ones this picker shows. Month NAMES follow the
|
|
61
68
|
page's `lang` through Intl and need nothing here. The day grid's own arrows are absent along
|
|
62
69
|
with the day grid, so there is no PrevMonthLabel to pass: the arrows beside a month grid step
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@namespace Freeday.Blazor
|
|
2
|
+
@inherits FreedayComponentBase<FdyTimepicker>
|
|
3
|
+
|
|
4
|
+
@* Seed-based wrapper over freeday-timepicker.js: the enhancer builds the input-styled trigger
|
|
5
|
+
+ time listbox from this empty div and emits fdy-time-select. The enhancer owns that DOM, so
|
|
6
|
+
the seed renders once (ShouldRender => false) and the picked time is read back through
|
|
7
|
+
@bind-Value. Value is "HH:MM" (24-hour) or "".
|
|
8
|
+
|
|
9
|
+
This is what replaces <input type="time">, and not for theming alone: the native picker is
|
|
10
|
+
drawn by the OPERATING SYSTEM, so iOS shows a scroll wheel, Android a clock dial and desktop a
|
|
11
|
+
stepper, each with its own width and value format, and no CSS reaches any of them (#094). *@
|
|
12
|
+
<div @ref="Root" data-fdy-timepicker
|
|
13
|
+
data-value="@Value"
|
|
14
|
+
data-label="@Label"
|
|
15
|
+
data-placeholder="@Placeholder"
|
|
16
|
+
data-step="@StepAttribute"
|
|
17
|
+
data-min="@Min"
|
|
18
|
+
data-max="@Max"
|
|
19
|
+
data-id="@Id"
|
|
20
|
+
data-describedby="@Describedby"
|
|
21
|
+
data-disabled="@(Disabled ? "true" : null)"
|
|
22
|
+
data-readonly="@(Readonly ? "true" : null)"
|
|
23
|
+
data-invalid="@(Invalid ? "true" : null)"
|
|
24
|
+
data-fdy-required="@(Required ? "" : null)"></div>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
using Microsoft.AspNetCore.Components;
|
|
2
|
+
using Microsoft.JSInterop;
|
|
3
|
+
|
|
4
|
+
namespace Freeday.Blazor;
|
|
5
|
+
|
|
6
|
+
public partial class FdyTimepicker
|
|
7
|
+
{
|
|
8
|
+
/// <summary>Selected time as <c>HH:MM</c> (24-hour), or "" when unset. Two-way bindable
|
|
9
|
+
/// via <c>@bind-Value</c>.</summary>
|
|
10
|
+
[Parameter] public string? Value { get; set; }
|
|
11
|
+
[Parameter] public EventCallback<string?> ValueChanged { get; set; }
|
|
12
|
+
|
|
13
|
+
/// <summary>Accessible label for the trigger.</summary>
|
|
14
|
+
[Parameter] public string? Label { get; set; }
|
|
15
|
+
|
|
16
|
+
/// <summary>Shown when there is no value. The enhancer's own default is <c>--:--</c>.</summary>
|
|
17
|
+
[Parameter] public string? Placeholder { get; set; }
|
|
18
|
+
|
|
19
|
+
/// <summary>Minutes between options. The enhancer's own default is 30.</summary>
|
|
20
|
+
[Parameter] public int? Step { get; set; }
|
|
21
|
+
|
|
22
|
+
/* null, not "30", for the default: an attribute the enhancer does not need is one no reader
|
|
23
|
+
of the seed has to interpret, and the enhancer already falls back to 30. */
|
|
24
|
+
private string? StepAttribute => Step is null ? null : Step.Value.ToString();
|
|
25
|
+
|
|
26
|
+
/// <summary>Earliest selectable time (<c>HH:MM</c>). Default <c>00:00</c>.</summary>
|
|
27
|
+
[Parameter] public string? Min { get; set; }
|
|
28
|
+
|
|
29
|
+
/// <summary>Latest selectable time (<c>HH:MM</c>). Default <c>23:59</c>.</summary>
|
|
30
|
+
[Parameter] public string? Max { get; set; }
|
|
31
|
+
|
|
32
|
+
/// <summary>Id for the trigger the enhancer builds, so <c>aria-describedby</c> targets and
|
|
33
|
+
/// other references can point at it. Note the trigger is a <c><button></c>, which is not
|
|
34
|
+
/// a labelable element, so a <c><label for></c> will not activate it — use
|
|
35
|
+
/// <see cref="Label"/>.</summary>
|
|
36
|
+
[Parameter] public string? Id { get; set; }
|
|
37
|
+
|
|
38
|
+
/// <summary>Id of the help or error text the trigger describes itself with.</summary>
|
|
39
|
+
[Parameter] public string? Describedby { get; set; }
|
|
40
|
+
|
|
41
|
+
/// <summary>Greyed and out of the tab order.</summary>
|
|
42
|
+
[Parameter] public bool Disabled { get; set; }
|
|
43
|
+
|
|
44
|
+
/// <summary>Locked/view mode: focusable and showing its time, but the list will not open.
|
|
45
|
+
/// Unlike <see cref="Disabled"/> it keeps tab order and is not greyed.</summary>
|
|
46
|
+
[Parameter] public bool Readonly { get; set; }
|
|
47
|
+
|
|
48
|
+
/// <summary>Marks the field invalid (<c>aria-invalid</c> + the error styling).</summary>
|
|
49
|
+
[Parameter] public bool Invalid { get; set; }
|
|
50
|
+
|
|
51
|
+
/// <summary>Let a <c>data-fdy-validate</c> form demand this control be filled. Renders
|
|
52
|
+
/// <c>data-fdy-required</c> on the root, and <c>freeday-form.js</c> refuses the submit, paints
|
|
53
|
+
/// the message in the field and focuses the trigger. Needed because the trigger is a
|
|
54
|
+
/// <c><button></c>: <c>willValidate</c> is false, so a plain <c>required</c> on it has
|
|
55
|
+
/// never meant anything. Override the message with <c>data-fdy-msg-required</c> on the root.</summary>
|
|
56
|
+
[Parameter] public bool Required { get; set; }
|
|
57
|
+
|
|
58
|
+
// The enhancer owns this control's DOM; init it directly (initAll would skip Root itself).
|
|
59
|
+
protected override ValueTask HydrateAsync() => JS.InvokeVoidAsync("FreedayTimepicker.init", Root);
|
|
60
|
+
|
|
61
|
+
protected override async ValueTask OnHydratedAsync()
|
|
62
|
+
=> await SubscribeAsync("fdy-time-select", nameof(OnSelect));
|
|
63
|
+
|
|
64
|
+
// The enhancer owns the built trigger + listbox; never let Blazor re-render the seed.
|
|
65
|
+
protected override bool ShouldRender() => !Hydrated;
|
|
66
|
+
|
|
67
|
+
/* Which is exactly why the three states are pushed rather than re-rendered: the seed above is
|
|
68
|
+
written once, so a page that disables this field later would otherwise be ignored. */
|
|
69
|
+
protected override async Task OnParametersSetAsync()
|
|
70
|
+
{
|
|
71
|
+
if (!Hydrated) return;
|
|
72
|
+
await JS.InvokeVoidAsync("FreedayTimepicker.setState", Root,
|
|
73
|
+
new { disabled = Disabled, @readonly = Readonly, invalid = Invalid });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// <summary>Invoked by the bridge when the reader picks a time (enhancer's fdy-time-select).</summary>
|
|
77
|
+
[JSInvokable]
|
|
78
|
+
public async Task OnSelect(SelectDetail detail)
|
|
79
|
+
{
|
|
80
|
+
Value = detail.Value;
|
|
81
|
+
await ValueChanged.InvokeAsync(detail.Value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// <summary>The detail shape of the enhancer's <c>fdy-time-select</c> event.</summary>
|
|
85
|
+
public sealed record SelectDetail(string Value);
|
|
86
|
+
}
|
|
@@ -33,6 +33,14 @@ export interface FdyCascadeProps {
|
|
|
33
33
|
disabled?: boolean;
|
|
34
34
|
/** Locked/view mode: stays focusable and shows its value, but can't be opened or changed. Unlike `disabled`, it keeps tab order and isn't greyed. */
|
|
35
35
|
readonly?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Let a `data-fdy-validate` form demand this control be filled. It renders `data-fdy-required`
|
|
38
|
+
* on the root, and `freeday-form.js` refuses the submit, paints the message in the field and
|
|
39
|
+
* focuses the trigger. Needed because the trigger is a `<button>`: `willValidate` is false, so
|
|
40
|
+
* a plain `required` on it has never meant anything (#096). Override the message per control
|
|
41
|
+
* with `data-fdy-msg-required` on the root.
|
|
42
|
+
*/
|
|
43
|
+
required?: boolean;
|
|
36
44
|
invalid?: boolean;
|
|
37
45
|
describedby?: string;
|
|
38
46
|
}
|
|
@@ -168,7 +176,8 @@ export function FdyCascade(props: FdyCascadeProps): JSX.Element {
|
|
|
168
176
|
}, [active, open, stack]);
|
|
169
177
|
|
|
170
178
|
return (
|
|
171
|
-
<div ref={rootRef} className={isInvalid ? 'fdy-cascade fdy-cascade--error' : 'fdy-cascade'}
|
|
179
|
+
<div ref={rootRef} className={isInvalid ? 'fdy-cascade fdy-cascade--error' : 'fdy-cascade'}
|
|
180
|
+
data-value={props.value} data-fdy-required={props.required === true ? '' : undefined}>
|
|
172
181
|
<button
|
|
173
182
|
ref={triggerRef}
|
|
174
183
|
id={triggerId}
|
|
@@ -17,6 +17,14 @@ export interface FdyComboProps<T extends string> {
|
|
|
17
17
|
disabled?: boolean;
|
|
18
18
|
/** Locked/view mode: stays focusable and shows its value, but can't be opened or changed. Unlike `disabled`, it keeps tab order and isn't greyed. */
|
|
19
19
|
readonly?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Let a `data-fdy-validate` form demand this control be filled. It renders `data-fdy-required`
|
|
22
|
+
* on the root, and `freeday-form.js` refuses the submit, paints the message in the field and
|
|
23
|
+
* focuses the trigger. Needed because the trigger is a `<button>`: `willValidate` is false, so
|
|
24
|
+
* a plain `required` on it has never meant anything (#096). Override the message per control
|
|
25
|
+
* with `data-fdy-msg-required` on the root.
|
|
26
|
+
*/
|
|
27
|
+
required?: boolean;
|
|
20
28
|
invalid?: boolean;
|
|
21
29
|
describedby?: string;
|
|
22
30
|
}
|
|
@@ -128,6 +136,7 @@ export function FdyCombo<T extends string>(props: FdyComboProps<T>): JSX.Element
|
|
|
128
136
|
ref={rootRef}
|
|
129
137
|
className={isInvalid ? 'fdy-combo fdy-combo--error' : 'fdy-combo'}
|
|
130
138
|
data-value={props.value}
|
|
139
|
+
data-fdy-required={props.required === true ? '' : undefined}
|
|
131
140
|
onKeyDown={onKeyDown}
|
|
132
141
|
onBlur={onFocusout}
|
|
133
142
|
>
|