@cahyo-dimas/freeday 1.18.0 → 1.20.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +162 -0
  2. package/COMPONENTS.md +748 -0
  3. package/README.id.md +9 -1
  4. package/README.md +16 -2
  5. package/USAGE.md +34 -11
  6. package/adapters/blazor/FdyTable.razor.cs +66 -7
  7. package/adapters/blazor/TableTypes.cs +6 -0
  8. package/adapters/react/components/FdyTable.tsx +32 -8
  9. package/adapters/vue/components/FdyTable.vue +30 -6
  10. package/dist/freeday.bundle.css +35 -1
  11. package/dist/freeday.css +30 -0
  12. package/dist/freeday.tokens.css +5 -1
  13. package/docs/agent-onboarding.md +151 -0
  14. package/docs/getting-started.md +454 -0
  15. package/docs/integrations.md +321 -0
  16. package/docs/reference-screen.html +461 -0
  17. package/package.json +9 -3
  18. package/src/components/list.css +29 -0
  19. package/tokens/breakpoints.d.ts +3 -0
  20. package/tokens/breakpoints.mjs +8 -1
  21. package/src/components/.gitkeep +0 -0
  22. package/src/freeday-autocomplete.js +0 -135
  23. package/src/freeday-breakpoint.js +0 -51
  24. package/src/freeday-carousel.js +0 -111
  25. package/src/freeday-cascade.js +0 -256
  26. package/src/freeday-cfl.js +0 -213
  27. package/src/freeday-chart.js +0 -429
  28. package/src/freeday-chip.js +0 -83
  29. package/src/freeday-datepicker.js +0 -321
  30. package/src/freeday-datetime.js +0 -83
  31. package/src/freeday-drawer.js +0 -43
  32. package/src/freeday-form.js +0 -181
  33. package/src/freeday-mask.js +0 -114
  34. package/src/freeday-menu.js +0 -93
  35. package/src/freeday-popover.js +0 -69
  36. package/src/freeday-rating.js +0 -50
  37. package/src/freeday-select.js +0 -218
  38. package/src/freeday-slider.js +0 -34
  39. package/src/freeday-stepper.js +0 -90
  40. package/src/freeday-table.js +0 -475
  41. package/src/freeday-tabs.js +0 -68
  42. package/src/freeday-timepicker.js +0 -180
  43. package/src/freeday-toast.js +0 -105
  44. package/src/freeday-tree.js +0 -94
  45. package/src/freeday-upload.js +0 -206
@@ -0,0 +1,321 @@
1
+ # Freeday — Library Integration Map
2
+
3
+ Freeday is a **dependency-free foundation**: tokens + accessible markup + vanilla enhancers. This
4
+ document maps each area to the ecosystem library you'd normally install in a real project, **when**
5
+ Freeday's built-ins are enough, and **how to bridge**. The goal: when you start a new project, open
6
+ this one file — no more hunting around.
7
+
8
+ > Golden rule: **Freeday owns the look (tokens + markup + a11y); the library owns the engine
9
+ > (heavy logic).** Don't duplicate. Connect through the 3 mechanisms below.
10
+
11
+ ---
12
+
13
+ ## 3 bridging mechanisms
14
+
15
+ Every integration comes down to one of these:
16
+
17
+ 1. **`fdy-*` events** — listen to enhancer output, forward it to framework/library state.
18
+ All events are bubbling `CustomEvent`s; the data is in `event.detail`.
19
+ 2. **Init hook `window.Freeday<X>.initAll(el)`** — call it again after DOM is rendered
20
+ dynamically (Vue `onMounted`, React `useEffect`, Blazor `OnAfterRenderAsync`). Idempotent.
21
+ 3. **Color tokens** — match a library's colors to the active theme:
22
+ ```js
23
+ // Read a live semantic token so a 3rd-party lib matches the current theme
24
+ const token = (name) =>
25
+ getComputedStyle(document.documentElement).getPropertyValue(name).trim();
26
+ const primary = token('--color-primary'); // e.g. Chart.js borderColor
27
+ ```
28
+
29
+ ### Event & API contract (quick reference)
30
+
31
+ | Enhancer | Event `detail` | Global API |
32
+ |---|---|---|
33
+ | `freeday-select` | `fdy-change` `{value}` | `FreedayCombo` |
34
+ | `freeday-autocomplete` | `fdy-autocomplete-select` `{value}` | `FreedayAutocomplete` |
35
+ | `freeday-cascade` | `fdy-cascade-change` `{value,path,labels}` | `FreedayCascade` |
36
+ | `freeday-cfl` | `fdy-cfl-select` `{row}`/`{rows}` | `FreedayCfl` |
37
+ | `freeday-datepicker` | `fdy-datepicker-change` `{value,date}` | `FreedayDatepicker` |
38
+ | `freeday-timepicker` | `fdy-time-select` `{value}` | `FreedayTimepicker` |
39
+ | `freeday-datetime` | `fdy-datetime-change` `{date,time,value}` | `FreedayDatetime` |
40
+ | `freeday-mask` | `fdy-mask` `{value,raw}` | `FreedayMask` |
41
+ | `freeday-form` | `fdy-form-invalid` `{invalid}` / `fdy-form-valid` | `FreedayForm` |
42
+ | `freeday-table` | `fdy-table-change` · `fdy-row-select` | `FreedayTable` |
43
+ | `freeday-chip` | `fdy-chip-change` `{value,pressed,selected}` · `fdy-chip-remove` `{value}` | `FreedayChip` |
44
+ | `freeday-upload` | `fdy-upload-add` / `fdy-upload-remove` | `FreedayUpload` |
45
+ | `freeday-stepper` | `fdy-step-change` | `FreedayStepper` |
46
+ | `freeday-carousel` | `fdy-carousel-change` | `FreedayCarousel` |
47
+ | `freeday-breakpoint` | `fdy-breakpoint-change` | `FreedayBreakpoint` |
48
+ | `freeday-chart` | — (render-only) | `FreedayChart` |
49
+ | `freeday-toast` | — | `Freeday.toast({...})` |
50
+
51
+ ---
52
+
53
+ ## Map by area
54
+
55
+ Legend for **Freeday enough?**: ✅ use the built-in · ➕ built-in + library · 🔌 not in Freeday,
56
+ library only.
57
+
58
+ ### Forms, validation & input
59
+
60
+ | Area | Freeday enough? | When the built-in is enough | If you need more | Bridge |
61
+ |---|---|---|---|---|
62
+ | Validation | ✅ `freeday-form` | Native HTML rules (required, email, pattern, min/max, match) | Complex schemas, transforms, shared with the server: **Zod** / **Yup** / **Valibot** · Vue: **VeeValidate** · React: **React Hook Form** · Blazor: **DataAnnotations** / **FluentValidation** | Run the schema in your data layer → `input.setCustomValidity(msg)` (picked up by `freeday-form`), or toggle `aria-invalid` + fill `[data-fdy-error]`. See example #1. |
63
+ | Input mask | ✅ `freeday-mask` | Static patterns (card, date, phone) | Currency/locale, dynamic masks: **imask** · **Maska** (Vue) · **react-imask** | Keep `.fdy-input` for styling, attach the mask engine to the same element. Example #2. |
64
+ | Password reveal | ✅ `freeday-mask` (`[data-fdy-password]`) | All common cases | Strength meter: **zxcvbn** | Listen for `input`, render the score into `.fdy-help`. |
65
+ | Select / dropdown | ✅ `freeday-select` (APG) | Static options, single-select | Async, tags, thousands of virtualized options: **Tom Select** · **Choices.js** · React: **react-select** · Vue/Blazor: **PrimeVue/PrimeReact**, **MudBlazor** | For large remote search, use **`freeday-cfl`** (field + dialog, `fetchPage`). Example #6. |
66
+ | Autocomplete | ✅ `freeday-autocomplete` | Client-side filtering | Highlighting, heavy remote debounce: react-select/Tom Select | Listen for `fdy-autocomplete-select`. |
67
+ | Cascade / tree select | ✅ `freeday-cascade` | Drill-down hierarchy | Multi-level checkbox tree, lazy loading: **PrimeVue TreeSelect**, **MudBlazor TreeView** | `fdy-cascade-change` `{value,path}`. |
68
+ | File upload | ➕ `freeday-upload` (dropzone UI) | Pick + show per-file state | Chunked/resumable, cropping, real progress: **Uppy** · **FilePond** · **tus** (resumable) | Freeday = UI, Uppy = upload engine. Listen for `fdy-upload-add` → hand the file to Uppy. |
69
+ | Rich text editor | 🔌 | — | **TipTap** · **Quill** · **Lexical** · Blazor: **Radzen HtmlEditor** | Wrap the editor, style it via `--color-*` tokens. |
70
+
71
+ ### Data & tables
72
+
73
+ | Area | Freeday enough? | When the built-in is enough | If you need more | Bridge |
74
+ |---|---|---|---|---|
75
+ | Table (vanilla) | ✅ `freeday-table` | Static/server-rendered HTML, client-side sort/filter/pagination | — | Enhancer over a plain `<table>`; **don't** use it on a Vue/React-rendered table (it snapshots the DOM). |
76
+ | Table (Vue/React/Blazor) | ✅ `FdyTable` | `columns`/`rows` in, controlled sort/filter/page out; type-aware column filters (text/enum/number/date) | Virtualization, grouping, pinned columns: **TanStack Table** (headless) · **AG Grid** | `FdyTable` is the framework-safe primitive (reads `rows` every render). Blazor's is `FdyTable<TRow>` backed by a C# port of the shared `table-model`. TanStack is headless → still render with `.fdy-table*` classes. |
77
+ | Data fetching / cache | 🔌 | — | **TanStack Query** · **SWR** · Vue: **Pinia** | `freeday-cfl`'s `fetchPage` and `FdyTable`'s controlled `sort`/`filters`/`page` pair well with Query. |
78
+ | Excel/PDF export | 🔌 | — | **SheetJS (xlsx)** · **jsPDF** + **jspdf-autotable** | Take data from state, not from the DOM. |
79
+ | Virtual scroll | 🔌 | — | **TanStack Virtual** · Vue: **vue-virtual-scroller** | — |
80
+
81
+ > **Monospace data cells:** add `.fdy-mono` to any identifier / code / IP / timestamp cell (or an
82
+ > inline `<span>`) to render it in the data font with tabular figures. It is alignment-neutral —
83
+ > unlike `.fdy-table__num`, which is also right-aligned. `FdyTable` applies it automatically to any
84
+ > column with `mono: true`.
85
+
86
+ ### Charts & visualization
87
+
88
+ | Area | Freeday enough? | When the built-in is enough | If you need more | Bridge |
89
+ |---|---|---|---|---|
90
+ | Chart | ➕ `freeday-chart` | Compact sparkline/bar/donut in cards & dashboards | Interactive (zoom, hover tooltips, multi-series, real-time, many types): **Chart.js** · **ApexCharts** · **ECharts** · React: **Recharts** / **visx** · Vue: **vue-chartjs** · Blazor: **ApexCharts.Blazor**, **MudBlazor Chart** | Mount the library into a container; pull colors from tokens so it follows the theme. Example #3. |
91
+ | Maps | 🔌 | — | **Leaflet** · **MapLibre** | Style the controls with tokens. |
92
+ | Diagram/flow | 🔌 | — | **Mermaid** · **React Flow** | — |
93
+
94
+ ### Dates, numbers & i18n
95
+
96
+ | Area | Freeday enough? | When the built-in is enough | If you need more | Bridge |
97
+ |---|---|---|---|---|
98
+ | Date/time picking | ✅ `freeday-datepicker`/`timepicker`/`datetime` | Standard calendar & time list | Range presets, multi-month, complex locales: **flatpickr** · React: **react-day-picker** | Freeday = the picking UI; parsing/formatting → date-fns. Example #4. |
99
+ | Date math/formatting | 🔌 (reach for native `Intl` first) | Simple formatting → `Intl.DateTimeFormat` | Time zones, arithmetic, parsing: **date-fns** (+ `date-fns-tz`) · **Luxon** · **Day.js** | `format(parseISO(detail.value), 'dd MMM yyyy', { locale: enUS })`. |
100
+ | Number/currency formatting | 🔌 (reach for native `Intl` first) | `Intl.NumberFormat('en-US', {style:'currency',currency:'USD'})` | Special cases: **dinero.js** (precise money) | Combine with `freeday-mask` for input. |
101
+ | Text i18n | 🔌 | — | **i18next** · Vue: **vue-i18n** · Blazor: **IStringLocalizer** | — |
102
+
103
+ ### Overlays, positioning & motion
104
+
105
+ | Area | Freeday enough? | When the built-in is enough | If you need more | Bridge |
106
+ |---|---|---|---|---|
107
+ | Modal / dialog | ✅ modal (native `<dialog>`) · Vue/React/Blazor: `FdyModal` | Focus trap + Esc + top layer come for free; `FdyModal` adds the controlled `open`/`onClose` (`@bind-Open` in Blazor) glue | Headless primitives: **Radix** · **Headless UI** · **Ark UI** | Freeday uses native — rarely worth replacing. `FdyModal` writes the `showModal()`/`close()` + Esc + backdrop reconciliation once. |
108
+ | Drawer | ✅ `freeday-drawer` · Vue/React/Blazor: `FdyDrawer` | Left/right overlay; `FdyDrawer` adds controlled `open`/`onClose` (`@bind-Open` in Blazor) | — | Same controlled contract as `FdyModal`, `side="left"\|"right"`. |
109
+ | Tooltip / popover | ➕ tooltip (CSS) | Simple static tooltips | Collision-aware positioning (flip/shift), interactive popovers: **Floating UI** (`@floating-ui/dom`) | Use Floating UI to compute position; styling stays token-based. Example #5. |
110
+ | Toast | ✅ `freeday-toast` | Common notifications | Advanced queue/stack: **Sonner** · **react-hot-toast** · **vue-toastification** | `Freeday.toast({variant,title,message})`. |
111
+ | Animation | ✅ (CSS + respect `prefers-reduced-motion`) | Standard UI transitions | Complex orchestration: **Motion One** · React: **Framer Motion** · **GSAP** | Always check reduced-motion. |
112
+ | Carousel | ✅ `freeday-carousel` | Scroll-snap + arrows + dots | Infinite loop, parallax: **Embla** · **Swiper** | — |
113
+
114
+ ### Navigation, icons & interaction
115
+
116
+ | Area | Freeday enough? | When the built-in is enough | If you need more | Bridge |
117
+ |---|---|---|---|---|
118
+ | Icons | ➕ (inline SVG) | A small built-in set | A full set: **Lucide** (best fit — Freeday uses this stroke style), **Heroicons**, **Tabler**, **Phosphor** | Drop the SVG into a slot: `[data-fdy-icon]`, `.fdy-input-group__addon--icon`, `.fdy-combo__icon`. |
119
+ | Drag & drop / sortable | 🔌 | — | **SortableJS** (vanilla) · React: **dnd-kit** · Vue: **vuedraggable** | Lay it out with Freeday markup, DnD from the library. |
120
+ | Command palette | 🔌 | — | **cmdk** (React) · **kbar** | Style with tokens + `.fdy-kbd`. |
121
+ | Routing | 🔌 | — | Vue Router · React Router · Blazor Router | After a route change → `window.FreedayTable.initAll()`, etc. |
122
+
123
+ ---
124
+
125
+ ## Per-framework binding
126
+
127
+ > **Recommended path:** use the ready-made adapters — the `useFreeday` composable (Vue), the
128
+ > `useFreeday` hook (React), or `window.FreedayBlazor` (Blazor) — which wrap hydration + event
129
+ > bridging. Full steps per stack are in **[`getting-started.md`](getting-started.md)**. The raw
130
+ > `initAll()` pattern below is the underlying mechanism (and a fallback if you don't use an adapter).
131
+
132
+ Enhancers auto-init once on `DOMContentLoaded`. For dynamically rendered DOM:
133
+
134
+ **Vue 3**
135
+ ```vue
136
+ <script setup lang="ts">
137
+ import { onMounted, onUpdated } from 'vue';
138
+ // Re-run Freeday enhancers over freshly rendered DOM (idempotent).
139
+ const rehydrate = (): void => { window.FreedayTable?.initAll(); window.FreedayForm?.initAll(); };
140
+ onMounted(rehydrate);
141
+ onUpdated(rehydrate);
142
+ </script>
143
+ ```
144
+
145
+ **React**
146
+ ```tsx
147
+ import { useEffect, useRef } from 'react';
148
+ function Panel(): JSX.Element {
149
+ const ref = useRef<HTMLDivElement>(null);
150
+ // Scope init to this subtree so it does not re-scan the whole document.
151
+ useEffect(() => { window.FreedayCascade?.initAll(ref.current ?? undefined); }, []);
152
+ return <div ref={ref} data-fdy-cascade />;
153
+ }
154
+ ```
155
+ > **Controlled alternative (parity with Vue's `v-model`):** `@cahyo-dimas/freeday/react` also exports
156
+ > the typed components `FdyCombo` / `FdyDatepicker` / `FdyDateRange` / `FdyAutocomplete` /
157
+ > `FdyCascade` / `FdyCfl` / `FdyChart` / `FdyTable` / `FdyModal` / `FdyDrawer` — plain
158
+ > `value`/`onChange` (props in, events out), no `data-fdy-*` + manual event listener:
159
+ > ```tsx
160
+ > import { FdyCombo } from '@cahyo-dimas/freeday/react';
161
+ > <FdyCombo value={status} options={statusOptions} onChange={setStatus} ariaLabelledby="lbl-status" />
162
+ > ```
163
+ > `FdyDatepicker`/`FdyAutocomplete`/`FdyCascade`/`FdyCfl` use the same `value`/`onChange`;
164
+ > `FdyDateRange` uses `{start, end}`; `FdyChart` uses `series`/`values`; `FdyTable` takes
165
+ > `columns`/`rows` (client-side sort/filter/pagination, or controlled `sort`/`filters`/`page` for a
166
+ > server-paged table; `interface`-typed rows infer through; opt-in `rowActivatable` +
167
+ > `row-activate`/`onRowActivate` for click-to-detail rows; expandable detail rows via
168
+ > `row-detail`/`renderRowDetail` + controlled `expandedKeys`); `FdyModal`/`FdyDrawer` take `open` +
169
+ > `onClose`.
170
+ >
171
+ > **Responsive list screens — drive the page from outside the table.** `FdyTable`'s pager renders
172
+ > inside `.fdy-datatable`, so a screen that hides the table below `md` and shows a card list loses it.
173
+ > Pass **`pageIndex`** (+ `update:pageIndex` / `onPageIndexChange` / `PageIndexChanged`) to own the
174
+ > client-side index while the table still does filter/sort/paginate: render **one** pager outside for
175
+ > both breakpoints, feed the card list from the **`process`** event (`{ rows, total }`), and build the
176
+ > pager's page window with `pageWindow` from `@cahyo-dimas/freeday/table-model` so it is literally the
177
+ > same computation the table uses. One page index can then span several tables — which is what a
178
+ > grouped list needs. The same set is available
179
+ > in `@cahyo-dimas/freeday/vue` via `v-model` / props — both adapters are fully symmetric. See
180
+ > [`getting-started.md` §React](getting-started.md#react-vite) and
181
+ > `examples/react-faktur/src/App.tsx`. **Vite** transpiles the `.tsx` source with no extra config;
182
+ > **Next.js** may need `transpilePackages: ['@cahyo-dimas/freeday']`.
183
+
184
+ **Blazor**
185
+ ```csharp
186
+ // Component.razor.cs — call the enhancer after Blazor renders the markup.
187
+ protected override async Task OnAfterRenderAsync(bool firstRender)
188
+ {
189
+ if (firstRender)
190
+ await JS.InvokeVoidAsync("FreedayForm.initAll");
191
+ }
192
+ ```
193
+ ```js
194
+ // wwwroot: expose a typed helper if you prefer, or listen for events and
195
+ // forward them to .NET via DotNetObjectReference.
196
+ document.addEventListener('fdy-form-invalid', (e) =>
197
+ dotNetRef.invokeMethodAsync('OnFormInvalid', e.detail.invalid.length));
198
+ ```
199
+
200
+ > **Native components (parity with Vue's `v-model` / React's `value`/`onChange`):** the
201
+ > **`Freeday.Blazor`** RCL (net8.0, `adapters/blazor/`) ships the typed components `FdyCombo<TValue>` /
202
+ > `FdyDatepicker` / `FdyDateRange` / `FdyAutocomplete` / `FdyCascade` / `FdyCfl<TRow>` / `FdyChart` /
203
+ > `FdyTable<TRow>` / `FdyModal` / `FdyDrawer` — `@bind`, no `@ref` / manual `initAll` / `[JSInvokable]`:
204
+ > ```razor
205
+ > @using Freeday.Blazor
206
+ > <FdyCombo TValue="string" @bind-Value="_status" Options="_statusOptions" AriaLabelledby="lbl-status" />
207
+ > <FdyTable TRow="Invoice" Columns="_cols" Rows="_rows" RowKey="@(i => i.Code)" PageSize="10" />
208
+ > ```
209
+ > Add a `<ProjectReference>` to `Freeday.Blazor.csproj`; the host still loads `freeday.js` +
210
+ > `freeday-blazor.js`. `FdyTable<TRow>` does client-side sort/filter/pagination, or controlled
211
+ > `Sort`/`Filters`/`Page` for a server-paged table (`RowActivatable`, `RowDetail`); `FdyModal`/`FdyDrawer`
212
+ > take `@bind-Open`. See [`getting-started.md` §Blazor](getting-started.md#blazor-wasm) and
213
+ > `examples/blazor-faktur/Pages/ComponentsDemo.razor`. All three stacks are now symmetric.
214
+
215
+ > **Alternative:** re-implement a component as a native framework one (Vue composable / React hook /
216
+ > Blazor component), but **keep the markup + ARIA contract + `fdy-*` classes**. The enhancer is the
217
+ > reference implementation, not a requirement.
218
+
219
+ ---
220
+
221
+ ## Bridging examples
222
+
223
+ ### 1. Zod / Yup → `freeday-form`
224
+ The library owns the logic; Freeday owns the accessible error UI.
225
+ ```js
226
+ import { z } from 'zod';
227
+
228
+ const schema = z.object({
229
+ email: z.string().email('Invalid email format.'),
230
+ age: z.coerce.number().min(17, 'Must be at least 17.'),
231
+ });
232
+
233
+ const form = document.querySelector('[data-fdy-validate]');
234
+ form.addEventListener('submit', (e) => {
235
+ const data = Object.fromEntries(new FormData(form));
236
+ const result = schema.safeParse(data);
237
+ if (!result.success) {
238
+ e.preventDefault();
239
+ // Map each Zod issue onto its control; freeday-form renders the message.
240
+ for (const issue of result.error.issues) {
241
+ const field = form.elements.namedItem(String(issue.path[0]));
242
+ if (field) field.setCustomValidity(issue.message);
243
+ }
244
+ form.reportValidity(); // triggers freeday-form's paint via the invalid event
245
+ }
246
+ });
247
+ // Clear the custom error as the user edits, so native + schema rules coexist.
248
+ form.addEventListener('input', (e) => e.target.setCustomValidity?.(''));
249
+ ```
250
+ > Yup equivalent: `schema.validate(data, { abortEarly: false })` → catch `err.inner`
251
+ > (`{ path, message }`) in `catch`, then `setCustomValidity` per field.
252
+
253
+ ### 2. imask → `.fdy-input` (advanced masking)
254
+ Use this when you need currency/locale beyond `data-fdy-mask`.
255
+ ```js
256
+ import IMask from 'imask';
257
+ // Freeday keeps the input styling; imask owns the formatting engine.
258
+ IMask(document.querySelector('#amount'), {
259
+ mask: '$ num',
260
+ blocks: { num: { mask: Number, thousandsSeparator: ',', scale: 0 } },
261
+ });
262
+ ```
263
+
264
+ ### 3. Chart.js with token colors
265
+ ```js
266
+ import { Chart } from 'chart.js/auto';
267
+ const token = (n) => getComputedStyle(document.documentElement).getPropertyValue(n).trim();
268
+
269
+ new Chart(document.querySelector('#sales'), {
270
+ type: 'line',
271
+ data: { labels, datasets: [{
272
+ data,
273
+ borderColor: token('--color-primary'),
274
+ backgroundColor: token('--color-primary-soft'),
275
+ }]},
276
+ });
277
+ // Re-read tokens and update() when data-theme flips (watch with a MutationObserver).
278
+ ```
279
+
280
+ ### 4. `freeday-datepicker` + date-fns
281
+ ```js
282
+ import { parseISO, format } from 'date-fns';
283
+ import { enUS } from 'date-fns/locale';
284
+ document.querySelector('[data-fdy-datepicker]')
285
+ .addEventListener('fdy-datepicker-change', (e) => {
286
+ // e.detail.value is an ISO string; date-fns handles locale formatting.
287
+ label.textContent = format(parseISO(e.detail.value), 'EEEE, dd MMMM yyyy', { locale: enUS });
288
+ });
289
+ ```
290
+
291
+ ### 5. Floating UI for tooltip/popover positioning
292
+ ```js
293
+ import { computePosition, offset, flip, shift } from '@floating-ui/dom';
294
+ // Freeday styles the .fdy-tooltip; Floating UI keeps it inside the viewport.
295
+ computePosition(trigger, tip, { placement: 'top', middleware: [offset(8), flip(), shift({ padding: 8 })] })
296
+ .then(({ x, y }) => Object.assign(tip.style, { left: `${x}px`, top: `${y}px` }));
297
+ ```
298
+
299
+ ### 6. Large async select → `freeday-cfl`
300
+ For thousands of server-side rows, don't force a `<select>` — use choose-from-list:
301
+ ```js
302
+ window.FreedayCfl.init(el, {
303
+ // Server owns paging/search; Freeday owns the accessible dialog UI.
304
+ fetchPage: ({ query, page }) => api.get('/customers', { params: { query, page } }),
305
+ });
306
+ el.addEventListener('fdy-cfl-select', (e) => store.setCustomer(e.detail.row));
307
+ ```
308
+
309
+ ---
310
+
311
+ ## SAP B1 note
312
+
313
+ Freeday is purely for the **web** (companion apps, .NET web add-ons, portals). A **UI API
314
+ (SAPBouiCOM)** add-on is WinForms/COM — a different world where Freeday doesn't apply. For
315
+ web-based add-ons (Service Layer + .NET), Freeday + enhancers over JS interop work normally.
316
+
317
+ ---
318
+
319
+ *Library recommendations reflect what's common and stable as of 2026; choose based on your
320
+ project's license and bundle-size budget. Freeday locks you into none of them — everything is
321
+ optional and replaceable.*