@cahyo-dimas/freeday 3.13.0 → 3.14.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 +63 -0
- package/COMPONENTS.md +45 -0
- package/README.id.md +1 -1
- package/README.md +1 -1
- package/adapters/react/components/FdyDrawer.tsx +4 -0
- package/adapters/react/index.d.ts +2 -0
- package/adapters/vue/components/FdyDrawer.vue +4 -0
- package/adapters/vue/index.d.ts +2 -0
- package/dist/freeday-app-shell.js +1 -0
- package/dist/freeday-autocomplete.js +1 -0
- package/dist/freeday-carousel.js +1 -0
- package/dist/freeday-cascade.js +1 -0
- package/dist/freeday-cfl.js +2 -0
- package/dist/freeday-chart.js +1 -0
- package/dist/freeday-chip.js +2 -0
- package/dist/freeday-datepicker.js +2 -0
- package/dist/freeday-datetime.js +1 -0
- package/dist/freeday-drawer.js +2 -0
- package/dist/freeday-form.js +1 -0
- package/dist/freeday-mask.js +2 -0
- package/dist/freeday-menu.js +1 -0
- package/dist/freeday-number.js +1 -0
- package/dist/freeday-rating.js +1 -0
- package/dist/freeday-search.js +1 -0
- package/dist/freeday-select.js +1 -0
- package/dist/freeday-slider.js +1 -0
- package/dist/freeday-sortable.js +222 -0
- package/dist/freeday-stepper.js +1 -0
- package/dist/freeday-table.js +1 -0
- package/dist/freeday-tabs.js +1 -0
- package/dist/freeday-timepicker.js +1 -0
- package/dist/freeday-tree.js +1 -0
- package/dist/freeday-unhydrated.js +9 -2
- package/dist/freeday-upload.js +1 -0
- package/dist/freeday.bundle.css +18 -0
- package/dist/freeday.css +18 -0
- package/dist/freeday.js +261 -2
- package/docs/agent-onboarding.md +2 -0
- package/docs/getting-started.md +1 -1
- package/docs/integrations.md +1 -0
- package/package.json +2 -2
- package/src/components/sortable.css +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,69 @@ benar. Perubahan seperti itu ditulis di bawah `### Changed: BREAKING (types)`
|
|
|
9
9
|
lama → tipe baru, dan cara menyempitkannya — bukan di bawah `### Added`, betapapun aditifnya dari
|
|
10
10
|
sisi kit.
|
|
11
11
|
|
|
12
|
+
## [3.14.0] - 2026-10-29
|
|
13
|
+
|
|
14
|
+
**Daftar terurut lewat seret dan papan ketik, dan satu atribut untuk berkata "ini bukan urusan
|
|
15
|
+
kit".** Dua butir dari `CATATAN-FREEDAY.md` app ronda (§14 dan §15). Mengaudit §14 menemukan satu
|
|
16
|
+
cacat yang tak dilaporkan: `FdyDrawer` Vue dan React bisa ditutup lewat backdrop walau
|
|
17
|
+
`dismissible=false`.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **`data-fdy-sortable`, enhancer ke-32** (`#103`), `src/freeday-sortable.js` plus
|
|
21
|
+
`.fdy-sortable__handle`. Baris diseret lewat pegangannya, atau dipindah satu langkah dengan ↑ / ↓
|
|
22
|
+
saat pegangannya terfokus. Lima layar di app ronda menyuruh orang mengetik nomor urut di kotak
|
|
23
|
+
angka, dan app itu akhirnya menulis sendiri SortableJS plus jalur papan ketiknya. Selama diseret,
|
|
24
|
+
garis menunjukkan tempat baris akan mendarat dan tak ada elemen yang berpindah sebelum tombol
|
|
25
|
+
dilepas; Escape membatalkan. `fdy-sort-change` (`{key, from, to, order}`) dapat dibatalkan,
|
|
26
|
+
dengan pola yang sama seperti chip di 3.13.0: halaman statis membiarkannya dan kit memindah
|
|
27
|
+
elemennya, sedangkan framework membatalkannya lalu mengurutkan state-nya sendiri, sebab DOM-nya
|
|
28
|
+
milik framework. Sesudah langkah papan ketik, fokus kembali ke pegangan baris yang sama, dicari
|
|
29
|
+
lewat `data-key` supaya tetap ketemu walau render ulang mengganti elemennya. Perpindahan
|
|
30
|
+
diumumkan di live region. `data-fdy-sort-fixed` menandai baris yang tak bisa dilewati, dan
|
|
31
|
+
`data-disabled` mematikan pengurutan selama daftar dicari atau disaring. Yang sengaja tidak
|
|
32
|
+
dibuat: gulir otomatis saat seretan mendekati tepi halaman, dan memindah baris antar-daftar.
|
|
33
|
+
- **`data-fdy-ignore`** (`#102`). Di elemen atau leluhur mana pun, setiap enhancer melewati
|
|
34
|
+
subtree itu, termasuk di bawah `initAll(document)` yang eksplisit, dan peringatan hook dari 3.13.0
|
|
35
|
+
ikut diam. Pesan peringatannya kini menyebut atribut ini sebagai jalan keluar yang sah. Pemicunya
|
|
36
|
+
matriks chip di layar Pola rotasi, yang perilakunya dikelola app sendiri, sehingga setiap klik di
|
|
37
|
+
situ mencetak peringatan untuk sesuatu yang memang disengaja. Nama yang diusulkan catatannya,
|
|
38
|
+
`data-fdy-raw`, tidak dipakai: di dokumen kit "raw" berarti jalur markup + enhancer, kebalikan
|
|
39
|
+
dari maksud atribut ini.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
- **`FdyDrawer` Vue dan React tertutup lewat backdrop walau `dismissible=false`** (`#102`), begitu
|
|
43
|
+
`useFreeday()` dipasang di seluruh halaman. Wrapper itu merender `.fdy-drawer`, yang merupakan hook
|
|
44
|
+
enhancer drawer, sehingga enhancer memasang handler backdrop kedua yang memanggil `close()`
|
|
45
|
+
langsung. Dialognya tertutup, `open` di state app tetap `true`, dan tak ada event `close`. Di luar
|
|
46
|
+
cakupan pemindai, wrapper yang sama memicu peringatan 3.13.0 setiap kali diklik. Keduanya kini
|
|
47
|
+
membawa `data-fdy-ignore`. `FdyChart` juga merender hook, tetapi sejak awal sudah mencap penanda
|
|
48
|
+
siapnya sendiri; ia kini dijaga uji supaya tetap begitu.
|
|
49
|
+
|
|
50
|
+
### Catatan
|
|
51
|
+
- **Angka 9 menit di §14 bukan ongkos pemindaian.** Catatannya mengukur `useFreeday()` di seluruh
|
|
52
|
+
halaman pada 9 menit 6 detik untuk sepuluh uji e2e, dibanding 19,1 detik untuk cakupan modal saja,
|
|
53
|
+
dan mempersempit cakupannya karena itu. Pada bentuk matriks yang sama (38 baris × 4 shift, dua chip
|
|
54
|
+
per sel, 304 tombol), satu `rehydrate()` di seluruh halaman memakan 1,1 ms dan satu update reaktif
|
|
55
|
+
end-to-end 2,1 ms. Sekitar 55 detik per uji lebih mirip uji yang menunggu sampai timeout. Kalau
|
|
56
|
+
angka itu diambil sebelum 3.13.0, waktu itu kit masih menghapus chip yang diklik, dan itu cukup
|
|
57
|
+
menjelaskannya. Ini dugaan; pengukuran ulangnya ada di sisi app.
|
|
58
|
+
|
|
59
|
+
### Gerbang
|
|
60
|
+
- `docs.test.mjs`: setiap fungsi init yang mencap penanda hook wajib memeriksa `data-fdy-ignore`
|
|
61
|
+
sebelum mencap, dan setiap wrapper Vue/React yang merender hook enhancer wajib menandainya
|
|
62
|
+
`data-fdy-ignore` atau mencap penanda siapnya sendiri. Snapshot string enhancer mendapat dua
|
|
63
|
+
string sortable.
|
|
64
|
+
- `browser/app-owned.mjs`: `useFreeday()` di seluruh halaman atas matriks itu, dengan ambang longgar
|
|
65
|
+
(25 ms per rehydrate, 50 ms per update) yang menangkap pemindaian yang meledak, bukan runner yang
|
|
66
|
+
lambat. Drawer `dismissible=false` bertahan dari klik backdrop, chart ter-render sekali, subtree
|
|
67
|
+
yang diabaikan tetap tak tersentuh, dan tak ada satu pun peringatan.
|
|
68
|
+
- `browser/sortable.mjs`, dengan mouse dan papan ketik asli: tanda pendaratan di tengah seretan,
|
|
69
|
+
Escape, klik yang bukan seretan, baris tetap, keadaan nonaktif, perubahan yang dibatalkan tak
|
|
70
|
+
menyentuh DOM, dan list Vue ber-key yang mengurutkan state-nya sendiri.
|
|
71
|
+
- `browser/harness.mjs` mendapat `dragFromTo` dan mengekspos `clickAt`.
|
|
72
|
+
- Dua belas mutasi diverifikasi merah. Satu mutasi yang tetap hijau membuang ambang jarak seret dari
|
|
73
|
+
enhancer: tekanan yang tak meninggalkan barisnya sendiri sudah ditolak `allowed()`.
|
|
74
|
+
|
|
12
75
|
## [3.13.0] - 2026-10-29
|
|
13
76
|
|
|
14
77
|
**Kit berhenti memutuskan hal yang bukan urusannya, dan markup yang tak pernah dihidrasi kini
|
package/COMPONENTS.md
CHANGED
|
@@ -52,6 +52,11 @@ live docs.
|
|
|
52
52
|
never enhanced logs one `console.warn` naming it, with the element attached. The usual cause is
|
|
53
53
|
a `useFreeday()` ref on a wrapper that does not contain it. `freeday-unhydrated.js` does this,
|
|
54
54
|
and it ships in the root import.
|
|
55
|
+
**`data-fdy-ignore` says "not yours".** On an element or any ancestor, every enhancer skips that
|
|
56
|
+
subtree, even under an explicit `initAll(document)`, and the warning above stays quiet. Use it
|
|
57
|
+
where the app drives kit-styled markup itself, such as a chip matrix with its own removal flow.
|
|
58
|
+
The Vue and React `FdyDrawer` carry it already, so a page-wide `useFreeday()` cannot hand the
|
|
59
|
+
drawer enhancer a second backdrop handler.
|
|
55
60
|
|
|
56
61
|
### Containment — why the kit's containers are `position:relative`
|
|
57
62
|
|
|
@@ -101,6 +106,7 @@ element is fine. Events are bubbling
|
|
|
101
106
|
| `data-fdy-tree` | `freeday-tree.js` | `FreedayTree` | — |
|
|
102
107
|
| `data-fdy-rating` | `freeday-rating.js` | `FreedayRating` | — (native `change`) |
|
|
103
108
|
| `data-fdy-slider` | `freeday-slider.js` | `FreedaySlider` | — (native `input`) |
|
|
109
|
+
| `data-fdy-sortable` | `freeday-sortable.js` | `FreedaySortable` | `fdy-sort-change` |
|
|
104
110
|
| `data-fdy-number` | `freeday-number.js` | `FreedayNumber` | — (native `input`, `change`) |
|
|
105
111
|
| `data-fdy-search` | `freeday-search.js` | `FreedaySearch` | `fdy-search` (+ native `input` on clear) |
|
|
106
112
|
| `data-fdy-copy="value"` (delegated, no hydration) | `freeday-copy.js` | `FreedayCopy` | `fdy-copy` |
|
|
@@ -2367,6 +2373,45 @@ row.addEventListener('fdy-chip-remove', async (e) => {
|
|
|
2367
2373
|
```
|
|
2368
2374
|
`--tone-1`…`--tone-8` for non-semantic category colours.
|
|
2369
2375
|
|
|
2376
|
+
## Sortable list — `data-fdy-sortable`
|
|
2377
|
+
Reorder a list by dragging a handle, or by focusing the handle and pressing ↑ / ↓. Needs
|
|
2378
|
+
`freeday-sortable.js`. The kit draws the handle and the drag states; the list keeps whatever look it
|
|
2379
|
+
already had (`.fdy-list`, a table body, plain rows).
|
|
2380
|
+
|
|
2381
|
+
- Container: `.fdy-sortable` + `data-fdy-sortable` on a `<ul>`/`<ol>`, a `<tbody>` or a `<div>`.
|
|
2382
|
+
Its element children are the items.
|
|
2383
|
+
- Item: `data-key` (the id the event reports, **required** when a framework renders the list),
|
|
2384
|
+
`data-label` (the name read out, the item's text otherwise), and `data-fdy-sort-fixed` for an item
|
|
2385
|
+
that never moves and that nothing can pass, such as a system row pinned to one end.
|
|
2386
|
+
- Handle: `<button class="fdy-sortable__handle" type="button">` inside the item, icon inside. With no
|
|
2387
|
+
`aria-label` of your own it gets *Move {label}. Drag, or press the up and down arrow keys.*
|
|
2388
|
+
- `data-disabled` on the container switches reordering off, and the handles follow through
|
|
2389
|
+
`aria-disabled`. Set it while the list is searched or filtered, where a row's visible position is
|
|
2390
|
+
not its stored one.
|
|
2391
|
+
- States, set by the enhancer: `is-sorting` on the container, `is-dragging` on the item, and
|
|
2392
|
+
`is-drop-before` / `is-drop-after` on the item it will land against. Nothing moves until the
|
|
2393
|
+
pointer is released, and Escape cancels.
|
|
2394
|
+
- Event: `fdy-sort-change` on the container, `detail {key, from, to, order}`, **cancelable**. Left
|
|
2395
|
+
alone, the kit moves the element. Under Vue, React or Blazor, cancel it and reorder your state,
|
|
2396
|
+
because the DOM is the framework's. Either way the kit announces *{label} moved to position {n} of
|
|
2397
|
+
{total}.* in a polite live region, and after a keyboard step it puts focus back on the moved
|
|
2398
|
+
item's handle, found by `data-key` so a re-render that replaced the element still gets it.
|
|
2399
|
+
- Strings: `data-fdy-text-handle`, `data-fdy-text-moved` on the container.
|
|
2400
|
+
- Not in the kit: scrolling the page while a drag nears its edge, and moving items between lists.
|
|
2401
|
+
Keep a sortable list short enough to see whole, or paginate it.
|
|
2402
|
+
|
|
2403
|
+
```vue
|
|
2404
|
+
<ul ref="list" class="fdy-list fdy-sortable" data-fdy-sortable
|
|
2405
|
+
:data-disabled="filtered ? '' : undefined"
|
|
2406
|
+
@fdy-sort-change.prevent="(e) => saveOrder(e.detail.order)">
|
|
2407
|
+
<li v-for="r in rows" :key="r.id" class="fdy-list__row" :data-key="r.id" :data-label="r.name">
|
|
2408
|
+
<button class="fdy-sortable__handle" type="button"><IconGrip class="fdy-icon" aria-hidden="true" /></button>
|
|
2409
|
+
{{ r.name }}
|
|
2410
|
+
</li>
|
|
2411
|
+
</ul>
|
|
2412
|
+
<!-- useFreeday(list) in <script setup> -->
|
|
2413
|
+
```
|
|
2414
|
+
|
|
2370
2415
|
## Timeline — `.fdy-timeline`
|
|
2371
2416
|
Vertical event feed, pure CSS. `<ol class="fdy-timeline">` of `.fdy-timeline__item` (`--muted`
|
|
2372
2417
|
`--success` `--warning` `--danger`) with `__marker` `__time` `__title` `__text`. Status is carried
|
package/README.id.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **Lebih banyak _free day_ buat dev. UI kit-nya sudah siap pakai.**
|
|
6
6
|
|
|
7
7
|
[](https://cahyo-dimas.github.io/freeday-ui-kit/)
|
|
8
|
-
[](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.14.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.14.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`.
|
|
@@ -7,6 +7,9 @@ import { useEffect, useId, useRef } from 'react';
|
|
|
7
7
|
// through app state, backdrop-click via `event.target === dialogEl`) applied to a drawer that anchors
|
|
8
8
|
// left (default) or right. Native <dialog> supplies the focus trap, focus restore, top-layer stacking
|
|
9
9
|
// and inert background; `dismissible` (default true) gates Esc + backdrop dismissal.
|
|
10
|
+
// `data-fdy-ignore` on the root: this component owns the behaviour, so a page-wide useFreeday() must
|
|
11
|
+
// not hand `.fdy-drawer` to the enhancer as well. Its backdrop handler calls close() past
|
|
12
|
+
// `dismissible` and leaves `open` true on a dialog that is already closed.
|
|
10
13
|
|
|
11
14
|
export interface FdyDrawerProps {
|
|
12
15
|
open: boolean;
|
|
@@ -37,6 +40,7 @@ export function FdyDrawer(props: FdyDrawerProps): JSX.Element {
|
|
|
37
40
|
<dialog
|
|
38
41
|
ref={dialogRef}
|
|
39
42
|
className={drawerClass}
|
|
43
|
+
data-fdy-ignore=""
|
|
40
44
|
aria-labelledby={titleId}
|
|
41
45
|
onCancel={(e): void => {
|
|
42
46
|
e.preventDefault();
|
|
@@ -23,6 +23,8 @@ export interface FdyMaskDetail { value: string; raw: string }
|
|
|
23
23
|
export interface FdyFormInvalidDetail { invalid: HTMLElement[] }
|
|
24
24
|
export interface FdyChipChangeDetail { value: string; pressed: boolean; selected: string[] }
|
|
25
25
|
export interface FdyChipRemoveDetail { value: string }
|
|
26
|
+
/** `fdy-sort-change`, cancelable: `order` is every item's `data-key` in the new order. */
|
|
27
|
+
export interface FdySortChangeDetail { key: string; from: number; to: number; order: string[] }
|
|
26
28
|
export interface FdyCflSelectDetail { row?: unknown; rows?: unknown[] }
|
|
27
29
|
export interface FdyTableChangeDetail { page: number; sort: string | null; query: string }
|
|
28
30
|
|
|
@@ -7,6 +7,9 @@ import { computed, onMounted, useId, watch, type ComputedRef, type Ref, ref } fr
|
|
|
7
7
|
// dialogEl`, applied to a drawer that anchors left (default) or right. Native <dialog> supplies the
|
|
8
8
|
// focus trap, focus restore, top-layer stacking and inert background; `dismissible` (default true)
|
|
9
9
|
// gates Esc + backdrop dismissal.
|
|
10
|
+
// `data-fdy-ignore` on the root: this component owns the behaviour, so a page-wide useFreeday() must
|
|
11
|
+
// not hand `.fdy-drawer` to the enhancer as well. Its backdrop handler calls close() past
|
|
12
|
+
// `dismissible` and leaves `open` true on a dialog that is already closed.
|
|
10
13
|
|
|
11
14
|
// dismissible MUST go through withDefaults: Vue's boolean-cast gives an omitted Boolean prop
|
|
12
15
|
// `false`, not `undefined`, so a bare `props.dismissible !== false` would make an un-annotated
|
|
@@ -53,6 +56,7 @@ function onClick(e: MouseEvent): void {
|
|
|
53
56
|
<dialog
|
|
54
57
|
ref="dialogEl"
|
|
55
58
|
:class="drawerClass"
|
|
59
|
+
data-fdy-ignore
|
|
56
60
|
:aria-labelledby="titleId"
|
|
57
61
|
@cancel.prevent="onCancel"
|
|
58
62
|
@click="onClick"
|
package/adapters/vue/index.d.ts
CHANGED
|
@@ -93,5 +93,7 @@ export interface FdyMaskDetail { value: string; raw: string }
|
|
|
93
93
|
export interface FdyFormInvalidDetail { invalid: HTMLElement[] }
|
|
94
94
|
export interface FdyChipChangeDetail { value: string; pressed: boolean; selected: string[] }
|
|
95
95
|
export interface FdyChipRemoveDetail { value: string }
|
|
96
|
+
/** `fdy-sort-change`, cancelable: `order` is every item's `data-key` in the new order. */
|
|
97
|
+
export interface FdySortChangeDetail { key: string; from: number; to: number; order: string[] }
|
|
96
98
|
export interface FdyCflSelectDetail { row?: unknown; rows?: unknown[] }
|
|
97
99
|
export interface FdyTableChangeDetail { page: number; sort: string | null; query: string }
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
function initAutocomplete(root) {
|
|
20
20
|
if (root.dataset.fdyAcReady === '1') return;
|
|
21
|
+
if (root.closest('[data-fdy-ignore]')) return;
|
|
21
22
|
root.dataset.fdyAcReady = '1';
|
|
22
23
|
|
|
23
24
|
var input = root.querySelector('input[role="combobox"]') || root.querySelector('input');
|
package/dist/freeday-carousel.js
CHANGED
package/dist/freeday-cascade.js
CHANGED
package/dist/freeday-cfl.js
CHANGED
|
@@ -207,6 +207,7 @@
|
|
|
207
207
|
|
|
208
208
|
function bindGroup(group) {
|
|
209
209
|
if (group.dataset.fdyCflBound === '1') return;
|
|
210
|
+
if (group.closest('[data-fdy-ignore]')) return;
|
|
210
211
|
var dialog = document.getElementById(group.getAttribute('data-fdy-cfl'));
|
|
211
212
|
if (!dialog) return;
|
|
212
213
|
initDialog(dialog);
|
|
@@ -218,6 +219,7 @@
|
|
|
218
219
|
|
|
219
220
|
function bindOpener(opener) {
|
|
220
221
|
if (opener.dataset.fdyCflBound === '1') return;
|
|
222
|
+
if (opener.closest('[data-fdy-ignore]')) return;
|
|
221
223
|
var dialog = document.getElementById(opener.getAttribute('data-fdy-cfl-open'));
|
|
222
224
|
if (!dialog) return;
|
|
223
225
|
initDialog(dialog);
|
package/dist/freeday-chart.js
CHANGED
|
@@ -489,6 +489,7 @@
|
|
|
489
489
|
// Auto-init renders once; explicit update() re-renders after data changes.
|
|
490
490
|
function initChart(el) {
|
|
491
491
|
if (el.dataset.fdyChartReady === '1') return;
|
|
492
|
+
if (el.closest('[data-fdy-ignore]')) return;
|
|
492
493
|
el.dataset.fdyChartReady = '1';
|
|
493
494
|
renderChart(el);
|
|
494
495
|
}
|
package/dist/freeday-chip.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
function initRemove(btn) {
|
|
20
20
|
if (btn.dataset.fdyChipRmReady === '1') return;
|
|
21
|
+
if (btn.closest('[data-fdy-ignore]')) return;
|
|
21
22
|
btn.dataset.fdyChipRmReady = '1';
|
|
22
23
|
btn.addEventListener('click', function () {
|
|
23
24
|
var chip = btn.closest('.fdy-chip');
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
|
|
42
43
|
function initGroup(group) {
|
|
43
44
|
if (group.dataset.fdyChipsReady === '1') return;
|
|
45
|
+
if (group.closest('[data-fdy-ignore]')) return;
|
|
44
46
|
group.dataset.fdyChipsReady = '1';
|
|
45
47
|
var single = group.hasAttribute('data-single');
|
|
46
48
|
if (!group.getAttribute('role')) group.setAttribute('role', 'group');
|
|
@@ -118,6 +118,7 @@
|
|
|
118
118
|
|
|
119
119
|
function initPicker(wrap) {
|
|
120
120
|
if (wrap.dataset.fdyDpReady === '1') return wrap._fdyDp || null;
|
|
121
|
+
if (wrap.closest('[data-fdy-ignore]')) return null;
|
|
121
122
|
wrap.dataset.fdyDpReady = '1';
|
|
122
123
|
wrap.classList.add('fdy-datepicker');
|
|
123
124
|
|
|
@@ -647,6 +648,7 @@
|
|
|
647
648
|
|
|
648
649
|
function initRange(root) {
|
|
649
650
|
if (root.dataset.fdyDrReady === '1') return;
|
|
651
|
+
if (root.closest('[data-fdy-ignore]')) return;
|
|
650
652
|
root.dataset.fdyDrReady = '1';
|
|
651
653
|
var fromEl = root.querySelector('[data-fdy-datepicker][data-role="from"]');
|
|
652
654
|
var toEl = root.querySelector('[data-fdy-datepicker][data-role="to"]');
|
package/dist/freeday-datetime.js
CHANGED
package/dist/freeday-drawer.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
function initDrawer(dialog) {
|
|
10
10
|
if (dialog.dataset.fdyDrawerReady === '1') return;
|
|
11
|
+
if (dialog.closest('[data-fdy-ignore]')) return;
|
|
11
12
|
dialog.dataset.fdyDrawerReady = '1';
|
|
12
13
|
// Click on the backdrop (target is the dialog box itself) closes it.
|
|
13
14
|
dialog.addEventListener('click', function (e) { if (e.target === dialog) dialog.close(); });
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
|
|
19
20
|
function bindTrigger(btn) {
|
|
20
21
|
if (btn.dataset.fdyDrawerBound === '1') return;
|
|
22
|
+
if (btn.closest('[data-fdy-ignore]')) return;
|
|
21
23
|
var dialog = document.getElementById(btn.getAttribute('data-fdy-drawer'));
|
|
22
24
|
if (!dialog) return;
|
|
23
25
|
initDrawer(dialog);
|
package/dist/freeday-form.js
CHANGED
|
@@ -186,6 +186,7 @@
|
|
|
186
186
|
|
|
187
187
|
function initForm(form) {
|
|
188
188
|
if (form.dataset.fdyFormReady === '1') return;
|
|
189
|
+
if (form.closest('[data-fdy-ignore]')) return;
|
|
189
190
|
form.dataset.fdyFormReady = '1';
|
|
190
191
|
form.setAttribute('novalidate', '');
|
|
191
192
|
var eager = form.hasAttribute('data-fdy-eager');
|
package/dist/freeday-mask.js
CHANGED
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
|
|
80
80
|
function initMask(el) {
|
|
81
81
|
if (el.dataset.fdyMaskReady === '1') return;
|
|
82
|
+
if (el.closest('[data-fdy-ignore]')) return;
|
|
82
83
|
el.dataset.fdyMaskReady = '1';
|
|
83
84
|
var pattern = el.getAttribute('data-fdy-mask') || '';
|
|
84
85
|
if (!pattern) return;
|
|
@@ -101,6 +102,7 @@
|
|
|
101
102
|
|
|
102
103
|
function initReveal(input) {
|
|
103
104
|
if (input.dataset.fdyPwReady === '1') return;
|
|
105
|
+
if (input.closest('[data-fdy-ignore]')) return;
|
|
104
106
|
input.dataset.fdyPwReady = '1';
|
|
105
107
|
|
|
106
108
|
var group = document.createElement('span');
|
package/dist/freeday-menu.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
function initMenu(wrap) {
|
|
21
21
|
if (wrap.dataset.fdyMenuReady === '1') return;
|
|
22
|
+
if (wrap.closest('[data-fdy-ignore]')) return;
|
|
22
23
|
wrap.dataset.fdyMenuReady = '1';
|
|
23
24
|
var trigger = wrap.querySelector('[aria-haspopup="menu"]');
|
|
24
25
|
var menu = wrap.querySelector('.fdy-menu, [role="menu"]');
|
package/dist/freeday-number.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
function initNumber(root) {
|
|
29
29
|
if (root.dataset.fdyNumberReady === '1') return;
|
|
30
|
+
if (root.closest('[data-fdy-ignore]')) return;
|
|
30
31
|
|
|
31
32
|
var input = root.querySelector('input[type="number"]');
|
|
32
33
|
var buttons = Array.prototype.slice.call(root.querySelectorAll('[data-fdy-number-step]'));
|
package/dist/freeday-rating.js
CHANGED
package/dist/freeday-search.js
CHANGED
package/dist/freeday-select.js
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
function initCombo(root) {
|
|
34
34
|
if (root.dataset.fdyComboReady === '1') return; // idempotent
|
|
35
|
+
if (root.closest('[data-fdy-ignore]')) return;
|
|
35
36
|
var button = root.querySelector('.fdy-combo__button');
|
|
36
37
|
var listbox = root.querySelector('.fdy-combo__listbox');
|
|
37
38
|
var valueEl = root.querySelector('.fdy-combo__value');
|
package/dist/freeday-slider.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
function initSlider(input) {
|
|
14
14
|
if (input.dataset.fdySliderReady === '1') return;
|
|
15
|
+
if (input.closest('[data-fdy-ignore]')) return;
|
|
15
16
|
input.dataset.fdySliderReady = '1';
|
|
16
17
|
var out = input.id ? document.querySelector('output[for="' + input.id + '"]') : null;
|
|
17
18
|
if (!out) return;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/* Freeday, sortable list enhancer (optional, zero-dependency).
|
|
2
|
+
* Reorder the children of a list by dragging a handle, or by focusing it and pressing ↑ / ↓. The
|
|
3
|
+
* keyboard path is not an extra: drag-only reordering locks out every keyboard and screen-reader
|
|
4
|
+
* user from a settings screen, and that path is the part each app used to write for itself
|
|
5
|
+
* (CATATAN-FREEDAY §15).
|
|
6
|
+
*
|
|
7
|
+
* Markup contract:
|
|
8
|
+
* <ul class="fdy-sortable" data-fdy-sortable> (or a <tbody>, or a <div>)
|
|
9
|
+
* <li data-key="ketua" data-label="Ketua">
|
|
10
|
+
* <button class="fdy-sortable__handle" type="button">…icon…</button> Ketua
|
|
11
|
+
* </li>
|
|
12
|
+
* <li data-key="sistem" data-fdy-sort-fixed>…</li> (never moves, and nothing passes it)
|
|
13
|
+
* </ul>
|
|
14
|
+
* The items are the container's element children. `data-key` names each in the event; `data-label`
|
|
15
|
+
* is what the handle's default aria-label and the announcement call it (the item text otherwise).
|
|
16
|
+
* `data-disabled` on the container switches reordering off, e.g. while the list is filtered, and
|
|
17
|
+
* the handles follow it through aria-disabled.
|
|
18
|
+
*
|
|
19
|
+
* Emits a bubbling, CANCELABLE "fdy-sort-change" on the container, detail {key, from, to, order},
|
|
20
|
+
* `order` being every item's key in the new order. Not cancelled, the kit moves the element, which
|
|
21
|
+
* is the whole job on a static page. A framework that renders the list from state cancels it and
|
|
22
|
+
* reorders the state instead, because the DOM is the framework's and an element moved behind its
|
|
23
|
+
* back leaves the two disagreeing about where each row is. Either way the kit announces the move
|
|
24
|
+
* and, after a keyboard step, puts focus back on the moved item's handle (looked up by data-key,
|
|
25
|
+
* since a framework re-render may have replaced the element).
|
|
26
|
+
*
|
|
27
|
+
* The drag shows where the item will land (is-drop-before / is-drop-after on the item it lands
|
|
28
|
+
* against) and moves nothing until the pointer is released; Escape cancels it. Exposes
|
|
29
|
+
* window.FreedaySortable.
|
|
30
|
+
*/
|
|
31
|
+
(function () {
|
|
32
|
+
'use strict';
|
|
33
|
+
|
|
34
|
+
var TEXT = {
|
|
35
|
+
handle: 'Move {label}. Drag, or press the up and down arrow keys.',
|
|
36
|
+
moved: '{label} moved to position {position} of {total}.'
|
|
37
|
+
};
|
|
38
|
+
/* Kebab-cased for the lookup, see the note in freeday-upload.js. */
|
|
39
|
+
function textAttr(root, key) {
|
|
40
|
+
if (!root || !root.getAttribute) return null;
|
|
41
|
+
var kebab = root.getAttribute('data-fdy-text-' + key.replace(/[A-Z]/g, function (c) { return '-' + c.toLowerCase(); }));
|
|
42
|
+
return kebab != null && kebab !== '' ? kebab : root.getAttribute('data-fdy-text-' + key);
|
|
43
|
+
}
|
|
44
|
+
function textOf(root, key, vars) {
|
|
45
|
+
var custom = textAttr(root, key);
|
|
46
|
+
var s = custom != null && custom !== '' ? custom : TEXT[key];
|
|
47
|
+
for (var k in vars) if (Object.prototype.hasOwnProperty.call(vars, k)) s = s.split('{' + k + '}').join(vars[k]);
|
|
48
|
+
return s;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
var HANDLE = '.fdy-sortable__handle';
|
|
52
|
+
|
|
53
|
+
var live = null;
|
|
54
|
+
function announce(message) {
|
|
55
|
+
if (live === null) {
|
|
56
|
+
live = document.createElement('div');
|
|
57
|
+
live.className = 'fdy-visually-hidden';
|
|
58
|
+
live.setAttribute('aria-live', 'polite');
|
|
59
|
+
document.body.appendChild(live);
|
|
60
|
+
}
|
|
61
|
+
/* The same sentence twice in a row is no change, and a live region announces changes. */
|
|
62
|
+
live.textContent = live.textContent === message ? message + ' ' : message;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function items(root) {
|
|
66
|
+
return Array.prototype.slice.call(root.children);
|
|
67
|
+
}
|
|
68
|
+
function keyOf(item, index) {
|
|
69
|
+
var key = item.getAttribute('data-key');
|
|
70
|
+
return key !== null ? key : String(index);
|
|
71
|
+
}
|
|
72
|
+
function labelOf(item) {
|
|
73
|
+
var label = item.getAttribute('data-label');
|
|
74
|
+
return label !== null ? label : (item.textContent || '').replace(/\s+/g, ' ').trim();
|
|
75
|
+
}
|
|
76
|
+
function disabled(root) {
|
|
77
|
+
return root.hasAttribute('data-disabled');
|
|
78
|
+
}
|
|
79
|
+
function itemOf(root, node) {
|
|
80
|
+
for (var n = node; n && n !== root; n = n.parentElement) {
|
|
81
|
+
if (n.parentElement === root) return n;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* A move is allowed only if it shifts no fixed item, which is the same as saying the item does
|
|
87
|
+
not start fixed and does not cross one on its way. */
|
|
88
|
+
function allowed(list, from, to) {
|
|
89
|
+
if (from === to || to < 0 || to >= list.length) return false;
|
|
90
|
+
var lo = Math.min(from, to), hi = Math.max(from, to);
|
|
91
|
+
for (var i = lo; i <= hi; i++) if (list[i].hasAttribute('data-fdy-sort-fixed')) return false;
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function syncHandles(root) {
|
|
96
|
+
var off = disabled(root) ? 'true' : 'false';
|
|
97
|
+
items(root).forEach(function (item) {
|
|
98
|
+
var handle = item.querySelector(HANDLE);
|
|
99
|
+
if (!handle) return;
|
|
100
|
+
if (handle.getAttribute('aria-disabled') !== off) handle.setAttribute('aria-disabled', off);
|
|
101
|
+
if (!handle.hasAttribute('aria-label')) handle.setAttribute('aria-label', textOf(root, 'handle', { label: labelOf(item) }));
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function commit(root, from, to, refocus) {
|
|
106
|
+
var list = items(root);
|
|
107
|
+
var item = list[from];
|
|
108
|
+
var key = keyOf(item, from);
|
|
109
|
+
var keys = list.map(keyOf);
|
|
110
|
+
keys.splice(to, 0, keys.splice(from, 1)[0]);
|
|
111
|
+
var ev = new CustomEvent('fdy-sort-change', {
|
|
112
|
+
bubbles: true,
|
|
113
|
+
cancelable: true,
|
|
114
|
+
detail: { key: key, from: from, to: to, order: keys }
|
|
115
|
+
});
|
|
116
|
+
root.dispatchEvent(ev);
|
|
117
|
+
if (!ev.defaultPrevented) root.insertBefore(item, to > from ? list[to].nextSibling : list[to]);
|
|
118
|
+
announce(textOf(root, 'moved', { label: labelOf(item), position: String(to + 1), total: String(list.length) }));
|
|
119
|
+
if (!refocus) return;
|
|
120
|
+
/* After the app's own render, which a framework runs after this handler returns. Moving a focused
|
|
121
|
+
element drops its focus, and a keyed re-render may have replaced the element outright. */
|
|
122
|
+
setTimeout(function () {
|
|
123
|
+
var now = items(root);
|
|
124
|
+
var moved = item.parentElement === root ? item : now.filter(function (el, i) { return keyOf(el, i) === key; })[0];
|
|
125
|
+
var handle = moved ? moved.querySelector(HANDLE) : null;
|
|
126
|
+
if (handle && document.activeElement !== handle) handle.focus();
|
|
127
|
+
}, 0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function initSortable(root) {
|
|
131
|
+
if (root.dataset.fdySortableReady === '1') return;
|
|
132
|
+
if (root.closest('[data-fdy-ignore]')) return;
|
|
133
|
+
root.dataset.fdySortableReady = '1';
|
|
134
|
+
syncHandles(root);
|
|
135
|
+
/* Rows a framework adds later get their aria-label, and data-disabled reaches every handle. */
|
|
136
|
+
new MutationObserver(function () { syncHandles(root); })
|
|
137
|
+
.observe(root, { childList: true, attributes: true, attributeFilter: ['data-disabled'] });
|
|
138
|
+
|
|
139
|
+
root.addEventListener('keydown', function (e) {
|
|
140
|
+
var handle = e.target.closest ? e.target.closest(HANDLE) : null;
|
|
141
|
+
if (!handle || !root.contains(handle) || disabled(root)) return;
|
|
142
|
+
if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
var list = items(root);
|
|
145
|
+
var from = list.indexOf(itemOf(root, handle));
|
|
146
|
+
var to = from + (e.key === 'ArrowUp' ? -1 : 1);
|
|
147
|
+
if (from !== -1 && allowed(list, from, to)) commit(root, from, to, true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
var drag = null;
|
|
151
|
+
|
|
152
|
+
function clearMarks() {
|
|
153
|
+
items(root).forEach(function (el) { el.classList.remove('is-drop-before', 'is-drop-after'); });
|
|
154
|
+
}
|
|
155
|
+
function end(apply) {
|
|
156
|
+
if (drag === null) return;
|
|
157
|
+
var d = drag;
|
|
158
|
+
drag = null;
|
|
159
|
+
document.removeEventListener('keydown', onEscape, true);
|
|
160
|
+
clearMarks();
|
|
161
|
+
d.item.classList.remove('is-dragging');
|
|
162
|
+
root.classList.remove('is-sorting');
|
|
163
|
+
if (apply && d.started && d.to !== null) commit(root, d.from, d.to, false);
|
|
164
|
+
}
|
|
165
|
+
function onEscape(e) {
|
|
166
|
+
if (e.key === 'Escape') { e.preventDefault(); end(false); }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
root.addEventListener('pointerdown', function (e) {
|
|
170
|
+
var handle = e.target.closest ? e.target.closest(HANDLE) : null;
|
|
171
|
+
if (!handle || !root.contains(handle) || disabled(root) || e.button !== 0) return;
|
|
172
|
+
var item = itemOf(root, handle);
|
|
173
|
+
if (!item) return;
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
handle.focus();
|
|
176
|
+
handle.setPointerCapture(e.pointerId);
|
|
177
|
+
drag = { item: item, from: items(root).indexOf(item), started: false, to: null };
|
|
178
|
+
document.addEventListener('keydown', onEscape, true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
root.addEventListener('pointermove', function (e) {
|
|
182
|
+
if (drag === null) return;
|
|
183
|
+
/* No travel threshold: a press that never leaves its own row resolves to to === from, which
|
|
184
|
+
allowed() refuses, so a click on the handle cannot reorder anything. */
|
|
185
|
+
if (!drag.started) {
|
|
186
|
+
drag.started = true;
|
|
187
|
+
drag.item.classList.add('is-dragging');
|
|
188
|
+
root.classList.add('is-sorting');
|
|
189
|
+
}
|
|
190
|
+
clearMarks();
|
|
191
|
+
drag.to = null;
|
|
192
|
+
var list = items(root);
|
|
193
|
+
var over = itemOf(root, document.elementFromPoint(e.clientX, e.clientY));
|
|
194
|
+
if (!over) return;
|
|
195
|
+
var rect = over.getBoundingClientRect();
|
|
196
|
+
var after = e.clientY > rect.top + rect.height / 2;
|
|
197
|
+
var slot = list.indexOf(over) + (after ? 1 : 0);
|
|
198
|
+
var to = slot > drag.from ? slot - 1 : slot;
|
|
199
|
+
if (!allowed(list, drag.from, to)) return;
|
|
200
|
+
drag.to = to;
|
|
201
|
+
over.classList.add(after ? 'is-drop-after' : 'is-drop-before');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
root.addEventListener('pointerup', function () { end(true); });
|
|
205
|
+
root.addEventListener('pointercancel', function () { end(false); });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function initAll(context) {
|
|
209
|
+
var root = context || document;
|
|
210
|
+
/* root included: querySelectorAll never matches its own root, and a framework ref often sits ON the widget. */
|
|
211
|
+
if (root.matches && root.matches('[data-fdy-sortable]')) initSortable(root);
|
|
212
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-fdy-sortable]'), initSortable);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (document.readyState === 'loading') {
|
|
216
|
+
document.addEventListener('DOMContentLoaded', function () { initAll(); });
|
|
217
|
+
} else {
|
|
218
|
+
initAll();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
window.FreedaySortable = { init: initSortable, initAll: initAll };
|
|
222
|
+
})();
|
package/dist/freeday-stepper.js
CHANGED