@cahyo-dimas/freeday 3.13.0 → 3.14.1

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/COMPONENTS.md +45 -0
  3. package/README.id.md +1 -1
  4. package/README.md +1 -1
  5. package/adapters/react/components/FdyDrawer.tsx +4 -0
  6. package/adapters/react/index.d.ts +2 -0
  7. package/adapters/vue/components/FdyDrawer.vue +4 -0
  8. package/adapters/vue/index.d.ts +2 -0
  9. package/dist/freeday-app-shell.js +1 -0
  10. package/dist/freeday-autocomplete.js +1 -0
  11. package/dist/freeday-carousel.js +1 -0
  12. package/dist/freeday-cascade.js +1 -0
  13. package/dist/freeday-cfl.js +2 -0
  14. package/dist/freeday-chart.js +1 -0
  15. package/dist/freeday-chip.js +2 -0
  16. package/dist/freeday-datepicker.js +2 -0
  17. package/dist/freeday-datetime.js +1 -0
  18. package/dist/freeday-drawer.js +2 -0
  19. package/dist/freeday-form.js +1 -0
  20. package/dist/freeday-mask.js +2 -0
  21. package/dist/freeday-menu.js +1 -0
  22. package/dist/freeday-number.js +1 -0
  23. package/dist/freeday-rating.js +1 -0
  24. package/dist/freeday-search.js +1 -0
  25. package/dist/freeday-select.js +1 -0
  26. package/dist/freeday-slider.js +1 -0
  27. package/dist/freeday-sortable.js +222 -0
  28. package/dist/freeday-stepper.js +1 -0
  29. package/dist/freeday-table.js +1 -0
  30. package/dist/freeday-tabs.js +1 -0
  31. package/dist/freeday-timepicker.js +1 -0
  32. package/dist/freeday-tree.js +1 -0
  33. package/dist/freeday-unhydrated.js +9 -2
  34. package/dist/freeday-upload.js +1 -0
  35. package/dist/freeday.bundle.css +33 -4
  36. package/dist/freeday.css +33 -4
  37. package/dist/freeday.js +261 -2
  38. package/docs/agent-onboarding.md +2 -0
  39. package/docs/getting-started.md +1 -1
  40. package/docs/integrations.md +1 -0
  41. package/package.json +2 -2
  42. package/src/components/app-shell.css +2 -1
  43. package/src/components/drawer.css +2 -1
  44. package/src/components/modal.css +11 -2
  45. package/src/components/sortable.css +17 -0
package/CHANGELOG.md CHANGED
@@ -9,6 +9,110 @@ 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.1] - 2026-10-30
13
+
14
+ **Badan `FdyModal` kembali setinggi isinya di iPhone.** Di iPhone 7 sungguhan (iOS 15), setiap modal
15
+ menampilkan kepala dan kaki dengan badan tinggal selarik, tergulir sampai dasar, dan formulirnya
16
+ tak terlihat. "Tambah security" dan "Tulis info" di app ronda tak dapat dipakai sama sekali dari HP
17
+ (`CATATAN-FREEDAY.md` §16).
18
+
19
+ ### Fixed
20
+ - **`.fdy-modal__body` memakai `flex:1 1 auto`, bukan `flex:1`** (`#104`). Basis shorthand itu 0%,
21
+ dan `.fdy-modal` hanya punya `max-height`, sehingga persentasenya diresolusikan terhadap tinggi
22
+ yang tak tentu. Mesin terkini membacanya sebagai `content`, sedangkan WebKit lama membacanya
23
+ sebagai 0: dialog setinggi kepala ditambah kaki, dan badan tinggal padding 40px dengan
24
+ formulirnya meluap di dalamnya. Basis `0px` mereproduksinya di Chrome sampai ke pikselnya (badan
25
+ 39,7px untuk isi 358px), dan `auto` menghasilkan tata letak yang identik dengan sebelumnya di mesin
26
+ terkini (dialog 498,7px, badan 355,7px, di kedua bacaan). Mekanismenya terukur. Bahwa Safari 15
27
+ memang membaca 0% sebagai 0 disimpulkan dari gejalanya dan belum diukur di perangkat, sebab tak
28
+ ada WebKit setua itu yang dapat dijalankan di sini.
29
+ - **`100vh` diikuti `100dvh`** di `max-height` modal, tinggi drawer, dan tinggi sidebar sticky
30
+ (`#104`). Di Safari iOS `100vh` adalah viewport besar, seolah bilah alat tak ada, jadi kotak yang
31
+ diukur dengannya berjalan ke bawah bilah itu. Sidebar overlay sudah memakai `100dvh` sejak lama;
32
+ tiga tempat ini tertinggal. Mesin tanpa `dvh` memakai nilai pertama.
33
+
34
+ ### Catatan
35
+ - Dugaan di catatan app, bahwa `100vh` penyebab utamanya, tidak cocok dengan gejalanya: `100vh` yang
36
+ kebesaran membuat dialog terlalu tinggi, bukan badannya terlalu pendek. Tetap diperbaiki karena
37
+ ia cacat tersendiri.
38
+ - Drawer dan nav sidebar memakai pola `flex:1` yang sama tetapi tidak terkena, sebab kontainernya
39
+ punya tinggi pasti.
40
+
41
+ ### Gerbang
42
+ - `css.test.mjs`: item flex yang menjadi wilayah gulir (`min-height:0` + `overflow`) tak boleh
43
+ memakai basis nol atau persen, kecuali kontainernya bertinggi pasti dan tercatat. Setiap `100vh`
44
+ yang mengukur kotak wajib diikuti `100dvh`, kecuali tinggi minimum halaman.
45
+ - `browser/modal-phone.mjs` di 375×667: badan modal pendek setinggi isinya, modal panjang berhenti
46
+ di `max-height` dengan kaki tetap di layar, dan basis terkomputasi `auto`. Dengan basis `0px`
47
+ uji ini merah dengan "40px shown of 358px", angka yang sama dengan di iPhone.
48
+ - `webkit/modal-phone.spec.mjs`: tata letak yang sama di WebKit dengan deskriptor iPhone 7 di CI. WebKit
49
+ Playwright adalah mesin terkini yang membaca 0% dengan benar, jadi berkas ini menjaga hasilnya,
50
+ bukan penyebabnya; penyebabnya dijaga dua gerbang di atas.
51
+ - Empat mutasi diverifikasi merah.
52
+
53
+ ## [3.14.0] - 2026-10-29
54
+
55
+ **Daftar terurut lewat seret dan papan ketik, dan satu atribut untuk berkata "ini bukan urusan
56
+ kit".** Dua butir dari `CATATAN-FREEDAY.md` app ronda (§14 dan §15). Mengaudit §14 menemukan satu
57
+ cacat yang tak dilaporkan: `FdyDrawer` Vue dan React bisa ditutup lewat backdrop walau
58
+ `dismissible=false`.
59
+
60
+ ### Added
61
+ - **`data-fdy-sortable`, enhancer ke-32** (`#103`), `src/freeday-sortable.js` plus
62
+ `.fdy-sortable__handle`. Baris diseret lewat pegangannya, atau dipindah satu langkah dengan ↑ / ↓
63
+ saat pegangannya terfokus. Lima layar di app ronda menyuruh orang mengetik nomor urut di kotak
64
+ angka, dan app itu akhirnya menulis sendiri SortableJS plus jalur papan ketiknya. Selama diseret,
65
+ garis menunjukkan tempat baris akan mendarat dan tak ada elemen yang berpindah sebelum tombol
66
+ dilepas; Escape membatalkan. `fdy-sort-change` (`{key, from, to, order}`) dapat dibatalkan,
67
+ dengan pola yang sama seperti chip di 3.13.0: halaman statis membiarkannya dan kit memindah
68
+ elemennya, sedangkan framework membatalkannya lalu mengurutkan state-nya sendiri, sebab DOM-nya
69
+ milik framework. Sesudah langkah papan ketik, fokus kembali ke pegangan baris yang sama, dicari
70
+ lewat `data-key` supaya tetap ketemu walau render ulang mengganti elemennya. Perpindahan
71
+ diumumkan di live region. `data-fdy-sort-fixed` menandai baris yang tak bisa dilewati, dan
72
+ `data-disabled` mematikan pengurutan selama daftar dicari atau disaring. Yang sengaja tidak
73
+ dibuat: gulir otomatis saat seretan mendekati tepi halaman, dan memindah baris antar-daftar.
74
+ - **`data-fdy-ignore`** (`#102`). Di elemen atau leluhur mana pun, setiap enhancer melewati
75
+ subtree itu, termasuk di bawah `initAll(document)` yang eksplisit, dan peringatan hook dari 3.13.0
76
+ ikut diam. Pesan peringatannya kini menyebut atribut ini sebagai jalan keluar yang sah. Pemicunya
77
+ matriks chip di layar Pola rotasi, yang perilakunya dikelola app sendiri, sehingga setiap klik di
78
+ situ mencetak peringatan untuk sesuatu yang memang disengaja. Nama yang diusulkan catatannya,
79
+ `data-fdy-raw`, tidak dipakai: di dokumen kit "raw" berarti jalur markup + enhancer, kebalikan
80
+ dari maksud atribut ini.
81
+
82
+ ### Fixed
83
+ - **`FdyDrawer` Vue dan React tertutup lewat backdrop walau `dismissible=false`** (`#102`), begitu
84
+ `useFreeday()` dipasang di seluruh halaman. Wrapper itu merender `.fdy-drawer`, yang merupakan hook
85
+ enhancer drawer, sehingga enhancer memasang handler backdrop kedua yang memanggil `close()`
86
+ langsung. Dialognya tertutup, `open` di state app tetap `true`, dan tak ada event `close`. Di luar
87
+ cakupan pemindai, wrapper yang sama memicu peringatan 3.13.0 setiap kali diklik. Keduanya kini
88
+ membawa `data-fdy-ignore`. `FdyChart` juga merender hook, tetapi sejak awal sudah mencap penanda
89
+ siapnya sendiri; ia kini dijaga uji supaya tetap begitu.
90
+
91
+ ### Catatan
92
+ - **Angka 9 menit di §14 bukan ongkos pemindaian.** Catatannya mengukur `useFreeday()` di seluruh
93
+ halaman pada 9 menit 6 detik untuk sepuluh uji e2e, dibanding 19,1 detik untuk cakupan modal saja,
94
+ dan mempersempit cakupannya karena itu. Pada bentuk matriks yang sama (38 baris × 4 shift, dua chip
95
+ per sel, 304 tombol), satu `rehydrate()` di seluruh halaman memakan 1,1 ms dan satu update reaktif
96
+ end-to-end 2,1 ms. Sekitar 55 detik per uji lebih mirip uji yang menunggu sampai timeout. Kalau
97
+ angka itu diambil sebelum 3.13.0, waktu itu kit masih menghapus chip yang diklik, dan itu cukup
98
+ menjelaskannya. Ini dugaan; pengukuran ulangnya ada di sisi app.
99
+
100
+ ### Gerbang
101
+ - `docs.test.mjs`: setiap fungsi init yang mencap penanda hook wajib memeriksa `data-fdy-ignore`
102
+ sebelum mencap, dan setiap wrapper Vue/React yang merender hook enhancer wajib menandainya
103
+ `data-fdy-ignore` atau mencap penanda siapnya sendiri. Snapshot string enhancer mendapat dua
104
+ string sortable.
105
+ - `browser/app-owned.mjs`: `useFreeday()` di seluruh halaman atas matriks itu, dengan ambang longgar
106
+ (25 ms per rehydrate, 50 ms per update) yang menangkap pemindaian yang meledak, bukan runner yang
107
+ lambat. Drawer `dismissible=false` bertahan dari klik backdrop, chart ter-render sekali, subtree
108
+ yang diabaikan tetap tak tersentuh, dan tak ada satu pun peringatan.
109
+ - `browser/sortable.mjs`, dengan mouse dan papan ketik asli: tanda pendaratan di tengah seretan,
110
+ Escape, klik yang bukan seretan, baris tetap, keadaan nonaktif, perubahan yang dibatalkan tak
111
+ menyentuh DOM, dan list Vue ber-key yang mengurutkan state-nya sendiri.
112
+ - `browser/harness.mjs` mendapat `dragFromTo` dan mengekspos `clickAt`.
113
+ - Dua belas mutasi diverifikasi merah. Satu mutasi yang tetap hijau membuang ambang jarak seret dari
114
+ enhancer: tekanan yang tak meninggalkan barisnya sendiri sudah ditolak `allowed()`.
115
+
12
116
  ## [3.13.0] - 2026-10-29
13
117
 
14
118
  **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
  [![Live docs](https://img.shields.io/badge/docs-live-2050d8?style=flat-square)](https://cahyo-dimas.github.io/freeday-ui-kit/)
8
- [![Release](https://img.shields.io/badge/release-v3.13.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.13.0)
8
+ [![Release](https://img.shields.io/badge/release-v3.14.1-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.14.1)
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
  [![Live docs](https://img.shields.io/badge/docs-live-2050d8?style=flat-square)](https://cahyo-dimas.github.io/freeday-ui-kit/)
8
- [![Release](https://img.shields.io/badge/release-v3.13.0-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.13.0)
8
+ [![Release](https://img.shields.io/badge/release-v3.14.1-0078d4?style=flat-square)](https://github.com/cahyo-dimas/freeday-ui-kit/tree/v3.14.1)
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"
@@ -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 }
@@ -46,6 +46,7 @@
46
46
 
47
47
  function initShell(app) {
48
48
  if (app.dataset.fdyAppShellReady === '1') return;
49
+ if (app.closest('[data-fdy-ignore]')) return;
49
50
  app.dataset.fdyAppShellReady = '1';
50
51
 
51
52
  var toggle = app.querySelector('.fdy-app__navtoggle');
@@ -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');
@@ -46,6 +46,7 @@
46
46
 
47
47
  function initCarousel(root) {
48
48
  if (root.dataset.fdyCarouselReady === '1') return;
49
+ if (root.closest('[data-fdy-ignore]')) return;
49
50
  root.dataset.fdyCarouselReady = '1';
50
51
 
51
52
  var viewport = root.querySelector('.fdy-carousel__viewport');
@@ -84,6 +84,7 @@
84
84
 
85
85
  function initCascade(wrap) {
86
86
  if (wrap.dataset.fdyCascadeReady === '1') return;
87
+ if (wrap.closest('[data-fdy-ignore]')) return;
87
88
  wrap.dataset.fdyCascadeReady = '1';
88
89
  wrap.classList.add('fdy-cascade');
89
90
 
@@ -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);
@@ -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
  }
@@ -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"]');
@@ -17,6 +17,7 @@
17
17
 
18
18
  function initDatetime(wrap) {
19
19
  if (wrap.dataset.fdyDtReady === '1') return;
20
+ if (wrap.closest('[data-fdy-ignore]')) return;
20
21
  wrap.dataset.fdyDtReady = '1';
21
22
  wrap.classList.add('fdy-datetimepicker');
22
23
 
@@ -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);
@@ -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');
@@ -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');
@@ -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"]');
@@ -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]'));
@@ -8,6 +8,7 @@
8
8
 
9
9
  function initRating(root) {
10
10
  if (root.dataset.fdyRatingReady === '1') return;
11
+ if (root.closest('[data-fdy-ignore]')) return;
11
12
  root.dataset.fdyRatingReady = '1';
12
13
 
13
14
  var stars = Array.prototype.slice.call(root.querySelectorAll('.fdy-rating__star'));
@@ -24,6 +24,7 @@
24
24
 
25
25
  function initSearch(root) {
26
26
  if (root.dataset.fdySearchReady === '1') return;
27
+ if (root.closest('[data-fdy-ignore]')) return;
27
28
  var input = root.querySelector('input');
28
29
  if (!input) return;
29
30
  root.dataset.fdySearchReady = '1';
@@ -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');
@@ -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;