@adelalawdi/fnon2 2.0.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/README.md ADDED
@@ -0,0 +1,1167 @@
1
+ # Fnon.js v2.0 🚀
2
+
3
+ **Lightweight modal, toast, spinner, date picker, and context menu library.**
4
+ No dependencies. 42 KB minified (12 KB gzip). Works in all modern browsers.
5
+
6
+ ---
7
+ **Fnon v2.0** has been completely rebuilt from scratch! Here is what you need to know before updating:
8
+
9
+ ### 🔄 Upgrading to v2.0
10
+ * **Familiar Syntax:** I have done my absolute best to keep the code and API as close to the old version as possible, ensuring a smoother transition for everyone.
11
+ * **Check Your Code:** Because this is a major rewrite, please **review your existing implementation** before upgrading to version 2.0 to ensure everything continues to run smoothly in your project.
12
+
13
+ ### 📌 About the Project & Support
14
+ * **Personal Use:** I originally built this library for myself to use across my own projects, but decided to share it openly so others could benefit from it too.
15
+ * **Contributions & Support:** Because this is a personal passion project, **I am not accepting feature requests.** However, if you run into any legitimate bugs, feel free to report them and I will do my best to get them fixed when I can!
16
+
17
+ ---
18
+
19
+ > ### [Live Demo](https://superawdi.github.io/Fnon2/)
20
+
21
+ ## Table of Contents
22
+
23
+ - [Installation](#installation)
24
+ - [Fnon Global API](#fnon-global-api)
25
+ - [Modal System](#modal-system)
26
+ - [Alert](#alert)
27
+ - [Ask](#ask)
28
+ - [Dialogue](#dialogue)
29
+ - [Breaking Bar](#breaking-bar)
30
+ - [Modal Options](#modal-options)
31
+ - [Button Definition](#button-definition)
32
+ - [Lifecycle Hooks](#lifecycle-hooks)
33
+ - [Layout System](#layout-system)
34
+ - [Width Variants](#width-variants)
35
+ - [Hint / Toast System](#hint--toast-system)
36
+ - [Hint Options](#hint-options)
37
+ - [Hint Themes](#hint-themes)
38
+ - [Spinner System](#spinner-system)
39
+ - [Wait (Full-page)](#wait-full-page)
40
+ - [Box (Container)](#box-container)
41
+ - [Spinner Options](#spinner-options)
42
+ - [Available Spinners](#available-spinners)
43
+ - [DatePicker](#datepicker)
44
+ - [DatePicker Options](#datepicker-options)
45
+ - [Return Value](#return-value)
46
+ - [CSS Custom Properties](#datepicker-css-properties)
47
+ - [Context Menu](#context-menu)
48
+ - [ContextMenuItem](#contextmenuitem)
49
+ - [Keyboard Navigation](#context-menu-keyboard)
50
+ - [Dark Mode](#dark-mode)
51
+ - [Theme System](#theme-system)
52
+ - [Built-in Themes](#built-in-themes)
53
+ - [Custom Themes](#custom-themes)
54
+ - [Theme Colors](#theme-colors)
55
+ - [CSS Custom Properties](#css-custom-properties)
56
+ - [CSS Classes Reference](#css-classes-reference)
57
+ - [React Integration](#react-integration)
58
+ - [enableReactRendering](#enablereactrendering)
59
+ - [useFnon Hook](#usefnon-hook)
60
+ - [Renderer Hook System](#renderer-hook-system)
61
+ - [Browser Support](#browser-support)
62
+
63
+ ---
64
+
65
+ ### Default theme names:
66
+ ![Primary](https://placehold.co/80x30/029eff/ffffff?text=Primary)
67
+ ![Success](https://placehold.co/80x30/39DA8A/ffffff?text=Success)
68
+ ![Warning](https://placehold.co/80x30/fdd347/000000?text=Warning)
69
+ ![Info](https://placehold.co/80x30/48dbfb/000000?text=Info)
70
+ ![Light](https://placehold.co/80x30/f8f9fa/000000?text=Light)
71
+ ![Danger](https://placehold.co/80x30/ff6b6b/000000?text=Danger)
72
+ ![Dark](https://placehold.co/80x30/222f3e/ffffff?text=Dark)
73
+
74
+ ---
75
+
76
+ # ✪ Installation
77
+
78
+ Copy `fnon.min.js` and `fnon.min.css` to your project.
79
+
80
+ ```html
81
+ <link rel="stylesheet" href="path/to/fnon.min.css">
82
+ <script src="path/to/fnon.min.js"></script>
83
+ <script>
84
+ // Alert
85
+ Fnon.Alert.Primary('Hello World!', 'Welcome');
86
+
87
+ // Ask (returns Promise)
88
+ const confirmed = await Fnon.Ask.Primary.Async('Delete?', 'Confirm');
89
+
90
+ // Toast
91
+ Fnon.Hint.Success('File saved successfully!');
92
+
93
+ // Spinner
94
+ Fnon.Wait.Spin('Loading...');
95
+ setTimeout(() => Fnon.Wait.Remove(), 2000);
96
+
97
+ // DatePicker
98
+ const date = await Fnon.DatePicker({ initDate: new Date() });
99
+
100
+ // Context Menu
101
+ Fnon.ContextMenu(event, [
102
+ { text: 'Open', onClick: () => open() },
103
+ { text: 'More', items: [
104
+ { text: 'Sub item', onClick: () => sub() }
105
+ ]}
106
+ ]);
107
+
108
+ // Set global defaults
109
+ Fnon.Init({ isDark: true, dir: 'rtl', fontFamily: 'Arial, sans-serif' });
110
+ </script>
111
+ ```
112
+
113
+ ---
114
+
115
+ # Fnon Global API
116
+
117
+ The `Fnon` object is the single entry point. It exposes:
118
+
119
+ | Property | Type | Description |
120
+ |----------|------|-------------|
121
+ | `Alert` | object | Pre-themed alert factories (7 built-in themes + custom) |
122
+ | `Ask` | object | Pre-themed ask/confirm factories |
123
+ | `Dialogue` | object | Pre-themed dialogue factories |
124
+ | `Breaking` | object | Pre-themed breaking bar factories |
125
+ | `Hint` | object | Pre-themed hint/toast factories |
126
+ | `Wait` | object | Full-page spinner methods |
127
+ | `Box` | object | Container-targeted spinner methods |
128
+ | `DatePicker(options?)` | function | Date picker modal (alias: `DP`) |
129
+ | `ContextMenu(event, items, onClick?)` | function | Context menu (alias: `CM`) |
130
+ | `AddLogo(name, html)` | function | Register custom spinner |
131
+ | `AddTheme(name, config)` | function | Register custom theme |
132
+ | `Init(config)` | function | Set shared defaults across all subsystems |
133
+ | `setRenderer(fn)` | function | Register custom content renderer (for React etc.) |
134
+ | `registerCleanup(id, fn)` | function | Register modal cleanup callback |
135
+ | `registerReactRoot(id, root)` | function | Store React root for unmounting |
136
+
137
+ ---
138
+
139
+ # Modal System
140
+
141
+ # ⏹️ Alert
142
+
143
+
144
+ A single-OK-button modal.
145
+
146
+ ```ts
147
+ Fnon.Alert.ThemeName(
148
+ first: FnonContent | ModalOptions,
149
+ title?: string,
150
+ btnOkText?: string,
151
+ callback?: () => void,
152
+ closedCallback?: () => void
153
+ ): void
154
+
155
+ Fnon.Alert.Primary("message","title","ok text",()=> console.log('closed'))
156
+ Fnon.Alert.Warning("message","title")
157
+ Fnon.Alert.Info("message")
158
+ Fnon.Alert.Success('Operation completed.', 'Success');
159
+ Fnon.Alert.Danger('Something went wrong.', 'Error', 'Retry', () => retry());
160
+ ```
161
+
162
+ **Async variant** (returns a Promise that resolves when the user clicks OK):
163
+
164
+ ```ts
165
+ Fnon.Alert.ThemeName.Async(
166
+ first: FnonContent | ModalOptions,
167
+ title?: string,
168
+ btnOkText?: string
169
+ ): Promise<void>
170
+
171
+ Fnon.Alert.Info.Async("message","title").then(()=> console.log('A'))
172
+
173
+ // Async
174
+ await Fnon.Alert.Primary.Async('Continue?', 'Confirm');
175
+ console.log('User clicked OK');
176
+ ```
177
+
178
+ # ⏹️ Ask
179
+
180
+ A two-button (OK / Cancel) confirmation modal.
181
+
182
+ ```ts
183
+ Fnon.Ask.ThemeName(
184
+ first: FnonContent | ModalOptions,
185
+ title?: string,
186
+ btnOkText?: string,
187
+ btnCancelText?: string,
188
+ callback?: (result: boolean) => void,
189
+ cancelCallback?: (result: boolean) => void,
190
+ closedCallback?: () => void
191
+ ): void
192
+ ```
193
+
194
+ **Async variant** (resolves `true` on OK, `false` on Cancel):
195
+
196
+ ```ts
197
+ Fnon.Ask.ThemeName.Async(
198
+ first: FnonContent | ModalOptions,
199
+ title?: string,
200
+ btnOkText?: string,
201
+ btnCancelText?: string
202
+ ): Promise<boolean>
203
+ ```
204
+
205
+ **Example:**
206
+
207
+ ```ts
208
+ Fnon.Ask.Warning('Are you sure?', 'Confirm Delete', 'Delete', 'Cancel',
209
+ (ok) => { if (ok) deleteItem(); }
210
+ );
211
+
212
+ // Async
213
+ if (await Fnon.Ask.Primary.Async('Proceed?', 'Confirm')) {
214
+ proceed();
215
+ }
216
+ ```
217
+
218
+ ### Dialogue
219
+
220
+ A fully customizable modal with direct access to the modal element and close function.
221
+
222
+ ```ts
223
+ Fnon.Dialogue.ThemeName(
224
+ first: FnonContent | ModalOptions,
225
+ title?: string,
226
+ btnOkText?: string,
227
+ btnCancelText?: string,
228
+ callback?: (close: () => void, html: HTMLElement, data: Record<string, unknown>) => boolean | void,
229
+ cancelCallback?: (close: () => void, html: HTMLElement, data: Record<string, unknown>) => boolean | void,
230
+ closedCallback?: () => void
231
+ ): { close: () => void; modalEl: HTMLElement; id: string }
232
+ ```
233
+
234
+ Return `false` from `callback` or `cancelCallback` to **prevent** the modal from closing.
235
+
236
+ **Async variant** (resolves `true` on OK, `false` on Cancel, `null` if closed via X):
237
+
238
+ ```ts
239
+ Fnon.Dialogue.ThemeName.Async(
240
+ first: FnonContent | ModalOptions,
241
+ title?: string,
242
+ btnOkText?: string,
243
+ btnCancelText?: string
244
+ ): Promise<boolean | null>
245
+ ```
246
+
247
+ **Direct call** (without theme prefix):
248
+
249
+ ```ts
250
+ Fnon.Dialogue(options: ModalOptions): { close: () => void; modalEl: HTMLElement; id: string }
251
+ ```
252
+
253
+ **Example:**
254
+
255
+ ```ts
256
+ Fnon.Dialogue.Primary('Choose an option:', 'Custom Dialog',
257
+ 'Save', 'Discard',
258
+ (close, el, data) => {
259
+ save();
260
+ close(); // must call close() explicitly
261
+ },
262
+ (close) => {
263
+ close();
264
+ }
265
+ );
266
+ ```
267
+
268
+ ### Breaking Bar
269
+
270
+ A fixed-position breaking news bar anchored to top or bottom.
271
+
272
+ ```ts
273
+ Fnon.Breaking.ThemeName(
274
+ message?: FnonContent,
275
+ titleOrOptions?: string | BreakingOptions
276
+ ): { close: () => void; el: HTMLElement; id: string }
277
+ ```
278
+
279
+ **Direct call:**
280
+
281
+ ```ts
282
+ Fnon.Breaking(options: BreakingOptions): { close: () => void; el: HTMLElement; id: string }
283
+ ```
284
+
285
+ **Async variant** (resolves when bar closes):
286
+
287
+ ```ts
288
+ Fnon.Breaking.ThemeName.Async(
289
+ message?: FnonContent,
290
+ titleOrOptions?: string | BreakingOptions
291
+ ): Promise<void>
292
+ ```
293
+
294
+ **BreakingOptions:**
295
+
296
+ | Option | Type | Default | Description |
297
+ |--------|------|---------|-------------|
298
+ | `message` | `FnonContent` | — | Body text |
299
+ | `headline` | `string` | `'BREAKING'` | Headline badge text |
300
+ | `theme` | `ThemeName` | `'Danger'` | Theme to apply |
301
+ | `anchor` | `'top' \| 'bottom'` | `'top'` | Bar position |
302
+ | `buttons` | `ButtonDef[]` | — | Custom action buttons |
303
+ | `timeout` | `number` | `5000` | Auto-dismiss (ms), 0 = no dismiss |
304
+ | `closable` | `boolean` | `true` | Show close button |
305
+ | `onClose` | `() => void` | — | Close callback |
306
+ | `appendTarget` | `string \| HTMLElement` | `'body'` | Target container |
307
+ | `fontFamily` | `string` | `'Quicksand', sans-serif` | Font family |
308
+
309
+ ### Modal Options
310
+
311
+ All options accepted by `createModal()` (the core modal builder used by Alert, Ask, Dialogue):
312
+
313
+ | Option | Type | Default | Description |
314
+ |--------|------|---------|-------------|
315
+ | `message` | `FnonContent` | — | Modal body content |
316
+ | `title` | `FnonContent` | — | Title bar content (string, HTML string, or HTMLElement) |
317
+ | `buttons` | `ButtonDef[]` | — | Custom button definitions |
318
+ | `btnOkText` | `string` | — | Text for OK button |
319
+ | `btnCancelText` | `string` | — | Text for Cancel button |
320
+ | `btnOkStyle` | `string` | — | CSS class suffix for OK button (e.g. `'Primary'`, `'Danger'`) |
321
+ | `btnCancelStyle` | `string` | — | CSS class suffix for Cancel button |
322
+ | `showClose` | `boolean` | `true` | Show X close button in title bar |
323
+ | `closeButton` | `boolean` | `true` | Legacy alias for `showClose` |
324
+ | `showMin` | `boolean` | `false` | Show minimize button |
325
+ | `showMaxMin` | `boolean` | `false` | Show maximize/restore buttons |
326
+ | `data` | `Record<string, unknown>` | — | Shared mutable data passed to all callbacks |
327
+ | `userData` | `Record<string, unknown>` | — | Legacy alias for `data` |
328
+ | `autoFocus` | `boolean` | `false` | Auto-focus first focusable element |
329
+ | `autoFocusTag` | `string` | `'input:not([disabled])'` | Selector for auto-focus target |
330
+ | `autoFocusDelay` | `number` | `0` | Delay before auto-focus (ms) |
331
+ | `appendTarget` | `string \| HTMLElement` | `'body'` | Where to append the modal |
332
+ | `layout` | `string` | `'hbf'` | Section order: `h`=title, `b`=body, `f`=footer |
333
+ | `width` | `string` | — | Width variant (`'sm'`, `'nl'`, `'lg'`, `'xl'`, `'fl'`) |
334
+ | `anchor` | `'top' \| 'bottom'` | — | Vertical anchoring |
335
+ | `fontFamily` | `string` | `'Quicksand', sans-serif` | Font family |
336
+ | `animation` | `string` | `'slide-top'` | Entrance animation |
337
+ | `icon` | `string` | — | Icon HTML (shown in body) |
338
+ | `iconColor` | `string` | — | Icon color |
339
+ | `showIcon` | `boolean` | `false` | Show icon section |
340
+ | `titleColor` | `string` | — | Title text color |
341
+ | `titleBackground` | `string` | — | Title bar background |
342
+ | `color` | `string` | `'#2b2b2b'` | Body text color |
343
+ | `background` | `string` | `'rgba(0,0,0,0.4)'` | Overlay background |
344
+ | `zIndex` | `number` | `4000` | CSS z-index for overlay |
345
+ | `btnOkColor` | `string` | — | OK button text color |
346
+ | `btnOkBackground` | `string` | — | OK button background |
347
+ | `btnOkShadow` | `string` | — | OK button box-shadow |
348
+ | `btnOkBorderColor` | `string` | — | OK button border color |
349
+ | `btnCancelColor` | `string` | — | Cancel button text color |
350
+ | `btnCancelBackground` | `string` | — | Cancel button background |
351
+ | `btnCancelShadow` | `string` | — | Cancel button box-shadow |
352
+ | `btnCancelBorderColor` | `string` | — | Cancel button border color |
353
+ | `delButtons` | `boolean` | `false` | Remove all default buttons |
354
+ | `isDark` | `boolean \| (() => boolean)` | — | Enable dark mode |
355
+ | `darkBgColor` | `string` | `'#1a202c'` | Dark mode background color |
356
+ | `dir` | `'ltr' \| 'rtl'` | `'ltr'` | Text direction |
357
+
358
+ ### Button Definition
359
+
360
+ | Option | Type | Default | Description |
361
+ |--------|------|---------|-------------|
362
+ | `text` | `string` | — | Button label |
363
+ | `style` | `string` | `'Primary'` | CSS class: `'Primary'`, `'Danger'`, `'Success'`, etc. |
364
+ | `callback` | `(close, html, data) => boolean \| void` | — | Click handler. Return `false` to prevent modal from closing |
365
+ | `color` | `string` | — | Custom text color |
366
+ | `background` | `string` | — | Custom background |
367
+ | `boxShadow` | `string` | — | Custom box-shadow |
368
+ | `shadow` | `string` | — | Alias for `boxShadow` |
369
+ | `borderColor` | `string` | — | Custom border color |
370
+ | `css` | `string` | — | Additional CSS classes |
371
+
372
+ **Custom buttons example:**
373
+
374
+ ```ts
375
+ Fnon.Dialogue({
376
+ message: 'What would you like to do?',
377
+ buttons: [
378
+ { text: 'Save', style: 'Success', callback: (close) => { save(); close(); } },
379
+ { text: 'Delete', style: 'Danger', callback: (close) => { deleteItem(); close(); } },
380
+ { text: 'Cancel', style: 'Light' }, // calls close() automatically
381
+ ]
382
+ });
383
+ ```
384
+
385
+ When `delButtons: true` is set in modal options, only custom buttons are shown (default OK/Cancel are removed).
386
+
387
+ ### Modal Data (Shared State)
388
+
389
+ The `data` option lets you pass a mutable object into the modal that is accessible across all lifecycle hooks and button callbacks. This is useful for passing state between your code and the modal's event cycle.
390
+
391
+ ```ts
392
+ const myData = { userId: 42, mode: 'edit' };
393
+
394
+ Fnon.Ask.Primary('Update this record?', 'Confirm', 'Yes', 'No',
395
+ (result) => {
396
+ // result is true/false (Ask behavior)
397
+ },
398
+ null,
399
+ null,
400
+ { data: myData } // <-- last argument is ModalOptions
401
+ );
402
+
403
+ // Or via Dialogue for full access:
404
+ Fnon.Dialogue({
405
+ message: 'Edit user?',
406
+ data: { userId: 42, timestamp: Date.now() },
407
+ buttons: [
408
+ {
409
+ text: 'Save',
410
+ callback: (close, html, data) => {
411
+ // data = { userId: 42, timestamp: ... }
412
+ console.log('Saving user', data.userId);
413
+ close();
414
+ }
415
+ }
416
+ ],
417
+ onOpen: (el, data) => {
418
+ console.log('Modal opened for user', data.userId);
419
+ },
420
+ onClosed: (data) => {
421
+ console.log('Modal closed for user', data.userId);
422
+ }
423
+ });
424
+ ```
425
+
426
+ The `data` object is shared — mutations made in one callback are visible in all subsequent callbacks.
427
+
428
+ ### Lifecycle Hooks
429
+
430
+ | Hook | Signature | Timing |
431
+ |------|-----------|--------|
432
+ | `onInit` | `(modalEl, data) => void` | After element creation, before DOM append |
433
+ | `onOpen` | `(modalEl, data) => void` | After DOM append and animation start |
434
+ | `onClose` | `(data) => void` | When close is triggered |
435
+ | `onClosing` | `(modalEl, data) => void` | During close animation |
436
+ | `onClosed` | `(data) => void` | After element removed from DOM |
437
+ | `beforeShow` | `(modalEl, data) => void` | Before animation starts |
438
+ | `afterShow` | `(modalEl, data) => void` | After animation completes |
439
+ | `defaultBefore` | `(modalEl, data) => void` | Universal pre-show hook |
440
+ | `defaultAfter` | `(modalEl, data) => void` | Universal post-show hook |
441
+ | `callback` | varies | Theme-specific (OK button click) |
442
+ | `cancelCallback` | varies | Theme-specific (Cancel button click) |
443
+
444
+ ### Layout System
445
+
446
+ Control the order of title bar (`h`), body (`b`), and footer (`f`) via the `layout` string.
447
+ Default: `'hbf'`. Example: `'fbh'` puts footer first.
448
+
449
+ ### Width Variants
450
+
451
+ | Class | max-width |
452
+ |-------|-----------|
453
+ | `.fnon-modal-sm` | 360px |
454
+ | `.fnon-modal-nl` | 480px (default) |
455
+ | `.fnon-modal-lg` | 640px |
456
+ | `.fnon-modal-xl` | 800px |
457
+ | `.fnon-modal-fl` | Full-width (fixed) |
458
+
459
+ ---
460
+
461
+ ## Hint / Toast System
462
+
463
+ A notification toast that auto-dismisses. Positioned via named positions.
464
+
465
+ ```ts
466
+ Fnon.Hint.ThemeName(
467
+ message: string,
468
+ titleOrOptions?: string | HintOptions
469
+ ): { close: () => void }
470
+ ```
471
+
472
+ **Async variant:**
473
+
474
+ ```ts
475
+ Fnon.Hint.ThemeName.Async(
476
+ message: string,
477
+ titleOrOptions?: string | HintOptions
478
+ ): Promise<void>
479
+ ```
480
+
481
+ **Built-in positions:**
482
+
483
+ `right-top` (default), `right-center`, `right-bottom`, `left-top`, `left-center`, `left-bottom`, `center-top`, `center-center`, `center-bottom`
484
+
485
+ **Example:**
486
+
487
+ ```ts
488
+ Fnon.Hint.Success('Saved!', 'Success');
489
+ Fnon.Hint.Danger('Connection lost.', { position: 'center-center', timeOut: 0 });
490
+ ```
491
+
492
+ ### Hint Options
493
+
494
+ | Option | Type | Default | Description |
495
+ |--------|------|---------|-------------|
496
+ | `type` | `string` | `'Primary'` | Theme type |
497
+ | `title` | `FnonContent` | — | Title content |
498
+ | `position` | `string` | `'right-top'` | Screen position |
499
+ | `animation` | `string` | `'slide-left'` | Entrance animation |
500
+ | `closable` | `boolean` | `true` | Show close button |
501
+ | `timeOut` | `number` | `4000` | Auto-dismiss (ms), 0 = no dismiss |
502
+ | `appendTo` | `string` | — | Container selector |
503
+ | `onClose` | `() => void` | — | Dismiss callback |
504
+ | `onclosed` | `() => void` | — | Post-removal callback |
505
+ | `fontFamily` | `string` | `'Quicksand', sans-serif` | Font |
506
+ | `spacing` | `string` | `'8px'` | Margin between hints |
507
+ | `svgSize` | `{ w: number, h: number }` | `{ w: 20, h: 20 }` | Icon dimensions |
508
+ | `fontSize` | `string` | `'0.88rem'` | Text font size |
509
+ | `width` | `string` | `'360px'` | Hint width |
510
+ | `animationDuration` | `string` | `'0.3s'` | Animation speed |
511
+ | `progressColor` | `string` | — | Progress bar color |
512
+ | `isDark` | `boolean \| (() => boolean)` | — | Dark mode |
513
+ | `darkBgColor` | `string` | — | Dark background |
514
+ | `dir` | `'ltr' \| 'rtl'` | `'ltr'` | Text direction |
515
+
516
+ ### Hint Themes
517
+
518
+ Built-in hint themes and their colors:
519
+
520
+ | Theme | Title Color | Background | Border |
521
+ |-------|------------|------------|--------|
522
+ | Primary | `#029eff` | `#ebf8ff` | `#029eff` |
523
+ | Success | `#39DA8A` | `#f0fff4` | `#39DA8A` |
524
+ | Warning | `#fdd347` | `#fffbe5` | `#fdd347` |
525
+ | Danger | `#ff6b6b` | `#fff5f5` | `#ff6b6b` |
526
+ | Info | `#48dbfb` | `#e5faff` | `#48dbfb` |
527
+ | Light | `#2d3748` | `#f8f9fa` | `#ced4da` |
528
+ | Dark | `#fff` | `#222f3e` | `#222f3e` |
529
+
530
+ Add custom hint themes via `Fnon.AddTheme(name, config)` — hint theming is derived automatically from the core theme.
531
+
532
+ ---
533
+
534
+ ## Spinner System
535
+
536
+ ### Wait (Full-page)
537
+
538
+ A centered overlay spinner covering the entire viewport. Only one active at a time.
539
+
540
+ ```ts
541
+ Fnon.Wait.SpinnerName(
542
+ text?: string,
543
+ options?: SpinnerOptions
544
+ ): void
545
+ ```
546
+
547
+ **Async variant** (resolves when `Wait.Remove()` is called):
548
+
549
+ ```ts
550
+ Fnon.Wait.SpinnerName.Async(
551
+ text?: string,
552
+ options?: SpinnerOptions
553
+ ): Promise<boolean>
554
+ ```
555
+
556
+ **Control methods:**
557
+
558
+ | Method | Description |
559
+ |--------|-------------|
560
+ | `Fnon.Wait.Remove(delay?: number)` | Remove overlay (with optional delay in ms) |
561
+ | `Fnon.Wait.Change(text: string)` | Update the displayed text |
562
+
563
+ **Example:**
564
+
565
+ ```ts
566
+ Fnon.Wait.Orbit('Connecting...');
567
+ await fetch(url);
568
+ Fnon.Wait.Remove();
569
+
570
+ // Async
571
+ const result = await Fnon.Wait.Spin.Async('Working...');
572
+ // result = true when Remove() is called
573
+ ```
574
+
575
+ ### Box (Container)
576
+
577
+ A spinner overlay inside a specific container element.
578
+
579
+ ```ts
580
+ Fnon.Box.SpinnerName(
581
+ selector: string | HTMLElement,
582
+ text?: string,
583
+ options?: BoxOptions
584
+ ): void
585
+ ```
586
+
587
+ **Async variant:**
588
+
589
+ ```ts
590
+ Fnon.Box.SpinnerName.Async(
591
+ selector: string | HTMLElement,
592
+ text?: string,
593
+ options?: BoxOptions
594
+ ): Promise<boolean>
595
+ ```
596
+
597
+ **Control methods:**
598
+
599
+ | Method | Description |
600
+ |--------|-------------|
601
+ | `Fnon.Box.Remove(selector: string \| HTMLElement)` | Remove overlay from container |
602
+
603
+ **Example:**
604
+
605
+ ```ts
606
+ Fnon.Box.Spin('#my-div', 'Loading...');
607
+
608
+ // Remove later
609
+ Fnon.Box.Remove('#my-div');
610
+ ```
611
+
612
+ ### Spinner Options
613
+
614
+ | Option | Type | Wait Default | Box Default | Description |
615
+ |--------|------|-------------|-------------|-------------|
616
+ | `text` | `string` | `''` | `''` | Display text below spinner |
617
+ | `clickToClose` | `boolean` | `false` | — | Click overlay to close |
618
+ | `svgColor` | `string` | `'#029eff'` | `'#029eff'` | Spinner color |
619
+ | `appendTarget` | `string` | — | — | Where to append (Wait only) |
620
+ | `fontFamily` | `string` | `'Quicksand', sans-serif` | `'Quicksand', sans-serif` | Font |
621
+ | `containerSize` | `string` | — | — | Spinner container dimensions |
622
+ | `svgSize` | `{ w, h }` | — | — | SVG dimensions (Box only) |
623
+ | `isDark` | `boolean \| (() => boolean)` | — | — | Dark mode |
624
+ | `darkBgColor` | `string` | — | — | Dark background |
625
+ | `dir` | `'ltr' \| 'rtl'` | — | — | Text direction |
626
+
627
+ ### Available Spinners
628
+
629
+ | Name | Type | Description |
630
+ |------|------|-------------|
631
+ | `Orbit` | CSS-only | Orbiting dot with ring animation |
632
+ | `Spin` | CSS-only | Rotating ring |
633
+ | `Infinity` | SVG | Figure-eight infinite loop path |
634
+ | `Ripple` | SVG | Expanding concentric circles |
635
+ | `Typing` | SVG | Bouncing typing indicator dots |
636
+
637
+ **Custom spinners** via `Fnon.AddLogo(name, htmlString)`:
638
+
639
+ ```ts
640
+ Fnon.AddLogo('MyLogo', '<svg>...</svg>');
641
+ Fnon.Wait.MyLogo('Custom spinner');
642
+ Fnon.Box.MyLogo('#target', 'Custom');
643
+ ```
644
+
645
+ ---
646
+
647
+ ## DatePicker
648
+
649
+ A calendar modal that returns a Promise with the selected date(s).
650
+
651
+ ```ts
652
+ Fnon.DatePicker(options?: DatePickerOptions): Promise<Date | { start: Date; end: Date } | null>
653
+ ```
654
+
655
+ **Alias:** `Fnon.DP(options?)`
656
+
657
+ **Usage:**
658
+
659
+ ```ts
660
+ // Single date
661
+ const date = await Fnon.DatePicker({ initDate: new Date() });
662
+ if (date) console.log('Selected:', date);
663
+
664
+ // Date range (dateRange: true)
665
+ const range = await Fnon.DatePicker({ dateRange: true });
666
+ if (range) console.log('From:', range.start, 'To:', range.end);
667
+
668
+ // With time picker
669
+ const dt = await Fnon.DatePicker({ withTime: true });
670
+
671
+ // With constraints
672
+ const d = await Fnon.DatePicker({
673
+ minDate: new Date(2024, 0, 1),
674
+ maxDate: new Date(2025, 11, 31)
675
+ });
676
+ ```
677
+
678
+ The Promise resolves `null` if the user closes the modal without selecting (X button / Escape).
679
+
680
+ ### DatePicker Options
681
+
682
+ | Option | Type | Default | Description |
683
+ |--------|------|---------|-------------|
684
+ | `initDate` | `Date` | `new Date()` | Initial focused date |
685
+ | `dateRange` | `boolean` | `false` | Enable date range selection (returns `{ start, end }`) |
686
+ | `withTime` | `boolean` | `false` | Show time input (ignored if `dateRange` is `true`) |
687
+ | `minDate` | `Date` | — | Earliest selectable date |
688
+ | `maxDate` | `Date` | — | Latest selectable date |
689
+ | `title` | `FnonContent` | — | Modal title |
690
+ | `isDark` | `boolean \| (() => boolean)` | — | Dark mode |
691
+ | `darkBgColor` | `string` | — | Dark background |
692
+ | `dir` | `'ltr' \| 'rtl'` | `'ltr'` | Text direction |
693
+
694
+ ### Return Value
695
+
696
+ | Mode | Resolves to | Rejects when |
697
+ |------|-------------|--------------|
698
+ | Single date | `Date \| null` | Modal closed without selection |
699
+ | Date range | `{ start: Date, end: Date } \| null` | Modal closed without selection |
700
+
701
+ ### DatePicker CSS Properties
702
+
703
+ All `--dp-*` variables are scoped to `.fnon-dp-popup` (light and dark variants).
704
+
705
+ | Property | Light Default | Dark Default |
706
+ |----------|--------------|--------------|
707
+ | `--dp-accent` | `#029eff` | `#0284c7` |
708
+ | `--dp-bg` | `#fff` | `#0f172a` |
709
+ | `--dp-text` | `#2d3748` | `#e2e8f0` |
710
+ | `--dp-border` | `#e2e8f0` | `#334155` |
711
+ | `--dp-hover` | `#ebf8ff` | `#1e293b` |
712
+ | `--dp-other` | `#a0aec0` | `#64748b` |
713
+ | `--dp-disabled` | `#e2e8f0` | `#334155` |
714
+ | `--dp-selected` | `#029eff` | `#0284c7` |
715
+ | `--dp-selected-text` | `#fff` | `#fff` |
716
+ | `--dp-range` | `#ebf8ff` | `#1e3a5f` |
717
+ | `--dp-popup-bg` | `#fff` | `#1e293b` |
718
+ | `--dp-popup-shadow` | `0 8px 25px rgba(0,0,0,.12)` | `0 8px 25px rgba(0,0,0,.4)` |
719
+ | `--dp-today-ring` | `#029eff` | `#38bdf8` |
720
+
721
+ ---
722
+
723
+ ## Context Menu
724
+
725
+ A right-click (or click) context menu with nested submenus, keyboard navigation, and Chrome-like styling.
726
+
727
+ ```ts
728
+ Fnon.ContextMenu(
729
+ event: MouseEvent | { x: number; y: number },
730
+ items: ContextMenuItem[],
731
+ globalOnClick?: (item: ContextMenuItem, event: MouseEvent) => void
732
+ ): { close: () => void }
733
+ ```
734
+
735
+ **Alias:** `Fnon.CM(event, items, onClick?)`
736
+
737
+ ### ContextMenuItem
738
+
739
+ | Option | Type | Default | Description |
740
+ |--------|------|---------|-------------|
741
+ | `text` | `FnonContent` | — | Display content (string, HTML string, or HTMLElement) |
742
+ | `onClick` | `(item, event) => void` | — | Click handler (required if no submenu `items`) |
743
+ | `items` | `ContextMenuItem[]` | — | Submenu items (takes priority over `onClick`) |
744
+ | `className` | `string` | — | Additional CSS class |
745
+ | `disabled` | `boolean` | `false` | Disabled state (dimmed, no click) |
746
+
747
+ **`globalOnClick`** fires only for items that do **not** have their own `onClick`.
748
+
749
+ **Examples:**
750
+
751
+ ```ts
752
+ document.addEventListener('contextmenu', (e) => {
753
+ Fnon.ContextMenu(e, [
754
+ { text: 'Open', onClick: () => open() },
755
+ { text: 'Edit', onClick: () => edit() },
756
+ { text: 'Share', items: [
757
+ { text: 'Email', onClick: () => share('email') },
758
+ { text: 'Copy Link', onClick: () => share('link') },
759
+ ]},
760
+ { text: 'Delete', disabled: true },
761
+ ]);
762
+ });
763
+
764
+ // Left-click trigger (use an anchor point)
765
+ Fnon.ContextMenu({ x: 100, y: 200 }, [
766
+ { text: 'Option 1', onClick: () => fn1() },
767
+ { text: 'Option 2', onClick: () => fn2() },
768
+ ], (item) => console.log('Global click:', item.text));
769
+ ```
770
+
771
+ ### Context Menu Keyboard Navigation
772
+
773
+ | Key | Action |
774
+ |-----|--------|
775
+ | `ArrowDown` | Next item |
776
+ | `ArrowUp` | Previous item |
777
+ | `ArrowRight` | Open submenu |
778
+ | `ArrowLeft` | Close submenu |
779
+ | `Enter` / `Space` | Select item (or open submenu) |
780
+ | `Escape` | Close submenu (or whole menu) |
781
+
782
+ Click outside, right-click outside, scroll, and resize all dismiss the menu.
783
+
784
+ ---
785
+
786
+ ## Dark Mode
787
+
788
+ Every subsystem supports dark mode via the `isDark` option and `Fnon.Init()`.
789
+
790
+ ### Enabling dark mode
791
+
792
+ ```ts
793
+ // Globally via Init()
794
+ Fnon.Init({ isDark: true });
795
+
796
+ // Per-instance
797
+ Fnon.Alert.Primary('Message', 'Title', { isDark: true });
798
+ Fnon.Hint.Success('Done', { isDark: true });
799
+ Fnon.DatePicker({ isDark: true });
800
+ ```
801
+
802
+ The `isDark` can also be a **function** `() => boolean` — evaluated once at creation time:
803
+
804
+ ```ts
805
+ Fnon.Init({ isDark: () => localStorage.getItem('theme') === 'dark' });
806
+ ```
807
+
808
+ ### Dark behavior by subsystem
809
+
810
+ | Subsystem | Dark effect |
811
+ |-----------|-------------|
812
+ | Modals | Background set to `darkBgColor` (#1a202c), text to `#e2e8f0`, footer border to `#2d3748` |
813
+ | Hint | Background set to `darkBgColor`, text color adapted |
814
+ | Wait/Box | Overlay gets darker opacity, inner container uses `darkBgColor` |
815
+ | DatePicker | All `--dp-*` vars switch to dark palette |
816
+ | ContextMenu | `.fnon-cm-dark` class applied with dark colors |
817
+ | Breaking | Excluded from dark mode (theme-driven) |
818
+
819
+ **Important:** Only wrapper backgrounds are changed — user content inside modals is not force-colored.
820
+
821
+ ---
822
+
823
+ ## RTL Support
824
+
825
+ Every subsystem supports right-to-left text direction via the `dir` option. Set it globally via `Fnon.Init()` or per-instance.
826
+
827
+ ```ts
828
+ // Globally via Init()
829
+ Fnon.Init({ dir: 'rtl' });
830
+
831
+ // Per-instance
832
+ Fnon.Alert.Primary('Message', 'Title', { dir: 'rtl' });
833
+ Fnon.Hint.Success('Done', { dir: 'rtl' });
834
+ Fnon.DatePicker({ dir: 'rtl' });
835
+ ```
836
+
837
+ ### What gets flipped
838
+
839
+ | Subsystem | RTL effect |
840
+ |-----------|------------|
841
+ | Modals | Controls (min/max/close) order reversed, drag uses `right` instead of `left` |
842
+ | Hints | Positions `right-*` ↔ `left-*`, border accent on right side, progress bar anchors right, slide animations reverse |
843
+ | ContextMenu | Submenus open to the left, arrow icons point left, keyboard arrows swap (`ArrowRight` closes, `ArrowLeft` opens) |
844
+ | DatePicker | Navigation chevrons swap direction |
845
+
846
+ `Fnon.Init({ dir: 'rtl' })` sets `document.documentElement.dir = "rtl"`, which cascades to all components.
847
+
848
+ ---
849
+
850
+ ## Theme System
851
+
852
+ Seven built-in themes, each with defined colors for title bar, buttons, and backgrounds.
853
+
854
+ ### Built-in Themes
855
+
856
+ | Theme | titleBackground | titleColor | btnOkBackground | btnOkColor | backgroundColor |
857
+ |-------|---------------|-----------|----------------|-----------|----------------|
858
+ | Primary | `#029eff` | `#fff` | `#029eff` | `#fff` | `#fff` |
859
+ | Success | `#39DA8A` | `#fff` | `#39DA8A` | `#fff` | `#fff` |
860
+ | Warning | `#fdd347` | `#2d3748` | `#fdd347` | `#2d3748` | `#fff` |
861
+ | Danger | `#ff6b6b` | `#fff` | `#ff6b6b` | `#fff` | `#fff` |
862
+ | Info | `#48dbfb` | `#2d3748` | `#48dbfb` | `#2d3748` | `#fff` |
863
+ | Light | `#f8f9fa` | `#2d3748` | `#f8f9fa` | `#2d3748` | `#fff` |
864
+ | Dark | `#222f3e` | `#fff` | `#222f3e` | `#fff` | `#fff` |
865
+
866
+ ### Custom Themes
867
+
868
+ ```ts
869
+ Fnon.AddTheme('MyTheme', {
870
+ titleBackground: '#7c3aed',
871
+ titleColor: '#fff',
872
+ btnOkBackground: '#7c3aed',
873
+ btnOkColor: '#fff',
874
+ btnOkBorderColor: '#7c3aed',
875
+ backgroundColor: '#faf5ff',
876
+ });
877
+
878
+ // Then use it:
879
+ Fnon.Alert.MyTheme('Custom themed modal!');
880
+ Fnon.Ask.MyTheme.Async('Confirm?');
881
+ ```
882
+
883
+ This automatically registers the theme for: Alert, Ask, Dialogue, Breaking, and Hint.
884
+
885
+ ### Theme Colors
886
+
887
+ | Option | Type | Description |
888
+ |--------|------|-------------|
889
+ | `titleBackground` | `string` | Title bar background color |
890
+ | `titleColor` | `string` | Title bar text color |
891
+ | `btnOkBackground` | `string` | OK button background color |
892
+ | `btnOkColor` | `string` | OK button text color |
893
+ | `btnOkBorderColor` | `string` | OK button border color |
894
+ | `backgroundColor` | `string` | Modal body background color |
895
+ | `[key: string]` | `string` | Any additional custom values |
896
+
897
+ ---
898
+
899
+ ## CSS Custom Properties
900
+
901
+ ### Root-level
902
+
903
+ Set these on `:root` to customize globally:
904
+
905
+ | Property | Default | Description |
906
+ |----------|---------|-------------|
907
+ | `--fnon-font` | `'Quicksand', sans-serif` | Base font |
908
+ | `--fnon-z-overlay` | `4000` | Overlay z-index |
909
+ | `--fnon-z-modal` | `4001` | Modal z-index |
910
+ | `--fnon-z-hint` | `5000` | Hint z-index |
911
+ | `--fnon-z-wait` | `6000` | Wait overlay z-index |
912
+ | `--fnon-radius` | `10px` | Border radius |
913
+ | `--fnon-transition` | `0.25s ease` | Default transition |
914
+
915
+ ### Theme-injected (per instance via CSS vars on modal element)
916
+
917
+ | Property | Source |
918
+ |----------|--------|
919
+ | `--fnon-title-background` | `theme.titleBackground` |
920
+ | `--fnon-title-color` | `theme.titleColor` |
921
+ | `--fnon-btn-ok-background` | `theme.btnOkBackground` |
922
+ | `--fnon-btn-ok-color` | `theme.btnOkColor` |
923
+ | `--fnon-btn-ok-border-color` | `theme.btnOkBorderColor` |
924
+ | `--fnon-background-color` | `theme.backgroundColor` |
925
+ | `--fnon-body-color` | `opts.color` override |
926
+ | `--fnon-bg` | Dark mode `darkBgColor` |
927
+
928
+ ### Spinner
929
+
930
+ | Property | Description |
931
+ |----------|-------------|
932
+ | `--fnon-sp-color` | Spinner color |
933
+ | `--fnon-sp-size` | Spinner container size (default 48px) |
934
+
935
+ ### Hint
936
+
937
+ | Property | Description |
938
+ |----------|-------------|
939
+ | `--fnon-icon-w` | Hint icon width |
940
+ | `--fnon-icon-h` | Hint icon height |
941
+ | `--display-dur` | Progress bar animation duration |
942
+
943
+ ### Box
944
+
945
+ | Property | Description |
946
+ |----------|-------------|
947
+ | `--fnon-box-svg-w` | Box SVG width |
948
+ | `--fnon-box-svg-h` | Box SVG height |
949
+
950
+ ---
951
+
952
+ ## CSS Classes Reference
953
+
954
+ ### Overlay & Modal
955
+
956
+ | Class | Purpose |
957
+ |-------|---------|
958
+ | `.fnon-overlay` | Modal backdrop |
959
+ | `.fnon-overlay-hidden` | Hidden overlay (minimized) |
960
+ | `.fnon-modal` | Modal dialog |
961
+ | `.fnon-modal.fnon-active` | Visible modal |
962
+ | `.fnon-modal.fnon-maximized` | Fullscreen |
963
+ | `.fnon-modal.fnon-minimized` | Hidden (minimized) |
964
+ | `.fnon-modal-title` | Title bar |
965
+ | `.fnon-modal-title-text` | Title text span |
966
+ | `.fnon-modal-controls` | Window control buttons |
967
+ | `.fnon-modal-body` | Body content |
968
+ | `.fnon-modal-icon` | Icon div |
969
+ | `.fnon-modal-footer` | Footer (buttons) |
970
+ | `.fnon-dragging` | Applied during drag |
971
+ | `.fnon-theme-{name}` | Theme class |
972
+ | `.fnon-task-bar` | Minimized modal task bar |
973
+ | `.fnon-task-bar-btn` | Task bar restore button |
974
+
975
+ ### Buttons
976
+
977
+ | Class | Purpose |
978
+ |-------|---------|
979
+ | `.fnon-btn` | Base button |
980
+ | `.fnon-btn-light` | Light theme |
981
+ | `.fnon-btn-danger` | Danger theme |
982
+ | `.fnon-btn-success` | Success theme |
983
+ | `.fnon-btn-warning` | Warning theme |
984
+ | `.fnon-btn-info` | Info theme |
985
+ | `.fnon-btn-dark` | Dark theme |
986
+
987
+ ### Breaking Bar
988
+
989
+ | Class | Purpose |
990
+ |-------|---------|
991
+ | `.fnon-breaking` | Breaking bar |
992
+ | `.fnon-breaking-top` | Anchored to top |
993
+ | `.fnon-breaking-bottom` | Anchored to bottom |
994
+ | `.fnon-breaking-active` | Visible state |
995
+ | `.fnon-breaking-inner` | Flex container |
996
+ | `.fnon-breaking-headline` | Headline badge |
997
+ | `.fnon-breaking-body` | Message body |
998
+ | `.fnon-breaking-buttons` | Buttons container |
999
+ | `.fnon-breaking-close` | Close button |
1000
+
1001
+ ### Hint / Toast
1002
+
1003
+ | Class | Purpose |
1004
+ |-------|---------|
1005
+ | `.fnon-hint-container` | Positioned container |
1006
+ | `.fnon-hint-container.{position}` | e.g. `right-top` |
1007
+ | `.fnon-hint` | Hint element |
1008
+ | `.fnon-hint-active` | Visible state |
1009
+ | `.fnon-hint-icon` | Icon container |
1010
+ | `.fnon-hint-content` | Text content |
1011
+ | `.fnon-hint-title` | Title text |
1012
+ | `.fnon-hint-message` | Message text |
1013
+ | `.fnon-hint-close` | Close button |
1014
+ | `.fnon-hint-progress` | Progress bar |
1015
+
1016
+ ### Wait / Box
1017
+
1018
+ | Class | Purpose |
1019
+ |-------|---------|
1020
+ | `.fnon-wait-overlay` | Full-page overlay |
1021
+ | `.fnon-wait-active` | Visible overlay |
1022
+ | `.fnon-wait-box` | Spinner container |
1023
+ | `.fnon-wait-text` | Text below spinner |
1024
+ | `.fnon-box-overlay` | Container overlay |
1025
+ | `.fnon-box-inner` | Box spinner container |
1026
+ | `.fnon-box-label` | Box text label |
1027
+
1028
+ ### CSS-only Spinners
1029
+
1030
+ | Class | Purpose |
1031
+ |-------|---------|
1032
+ | `.fnon-sp-orbit` | Orbit spinner |
1033
+ | `.fnon-sp-orbit-dot` | Orbit dot |
1034
+ | `.fnon-sp-spin` | Spin spinner |
1035
+ | `.fnon-sp-spin-ring` | Spin ring |
1036
+
1037
+ ### DatePicker
1038
+
1039
+ | Class | Purpose |
1040
+ |-------|---------|
1041
+ | `.fnon-dp` | DatePicker container |
1042
+ | `.fnon-dp-dark` | Dark mode |
1043
+ | `.fnon-dp-header` | Header row |
1044
+ | `.fnon-dp-nav` | Nav button |
1045
+ | `.fnon-dp-nav-disabled` | Disabled nav |
1046
+ | `.fnon-dp-label` | Month/year label |
1047
+ | `.fnon-dp-time` | Time row |
1048
+ | `.fnon-dp-time-input` | Time input |
1049
+ | `.fnon-dp-grid` | Day grid |
1050
+ | `.fnon-dp-dow` | Day-of-week header |
1051
+ | `.fnon-dp-day` | Day cell |
1052
+ | `.fnon-dp-other` | Other month |
1053
+ | `.fnon-dp-today` | Today |
1054
+ | `.fnon-dp-disabled` | Disabled |
1055
+ | `.fnon-dp-selected` | Selected |
1056
+ | `.fnon-dp-range` | Range |
1057
+ | `.fnon-dp-range-start` | Range start |
1058
+ | `.fnon-dp-range-end` | Range end |
1059
+ | `.fnon-dp-range-preview` | Hover preview |
1060
+ | `.fnon-dp-actions` | Action buttons |
1061
+ | `.fnon-dp-btn` | Action button |
1062
+ | `.fnon-dp-btn-primary` | Primary action |
1063
+ | `.fnon-dp-popup` | Year/month popup |
1064
+ | `.fnon-dp-popup-nav` | Popup nav bar |
1065
+ | `.fnon-dp-popup-nav-btn` | Popup nav button |
1066
+ | `.fnon-dp-popup-nav-label` | Popup nav label |
1067
+ | `.fnon-dp-popup-row` | Popup grid row |
1068
+ | `.fnon-dp-popup-cell` | Popup cell |
1069
+ | `.fnon-dp-popup-active` | Active cell |
1070
+
1071
+ ### Context Menu
1072
+
1073
+ | Class | Purpose |
1074
+ |-------|---------|
1075
+ | `.fnon-cm` | Context menu |
1076
+ | `.fnon-cm-dark` | Dark mode |
1077
+ | `.fnon-cm-item` | Menu item |
1078
+ | `.fnon-cm-item-disabled` | Disabled |
1079
+ | `.fnon-cm-item-parent` | Has submenu |
1080
+ | `.fnon-cm-item-text` | Text span |
1081
+ | `.fnon-cm-arrow` | Submenu arrow |
1082
+ | `.fnon-cm-submenu` | Submenu |
1083
+
1084
+ ### Utility
1085
+
1086
+ | Class | Purpose |
1087
+ |-------|---------|
1088
+ | `.fnon-hidden` | `display: none !important` |
1089
+
1090
+ ---
1091
+
1092
+ ## React Integration
1093
+
1094
+ Fnon provides a React adapter for rendering React components inside modals and a hook for portal-aware modal factories.
1095
+
1096
+ ### enableReactRendering
1097
+
1098
+ ```ts
1099
+ import { enableReactRendering } from 'fnon';
1100
+ // or via CDN: Fnon.setRenderer(...)
1101
+
1102
+ enableReactRendering(ReactDOM);
1103
+ // ReactDOM = { createRoot: ReactDOM.createRoot }
1104
+ ```
1105
+
1106
+ After calling this, any `FnonContent` that is a React VNode (object with `$$typeof` or `type`) will be rendered via `createRoot()` instead of `innerHTML`. The root is automatically unmounted when the modal closes.
1107
+
1108
+ ### useFnon Hook
1109
+
1110
+ ```ts
1111
+ import { useFnon } from 'fnon';
1112
+
1113
+ function App() {
1114
+ const { Alert, Ask, Dialogue } = useFnon({ mountRoot: '#fnon-root' });
1115
+
1116
+ return (
1117
+ <div id="fnon-root">
1118
+ <button onClick={() => Alert.Primary('Hello from React!')}>
1119
+ Show Alert
1120
+ </button>
1121
+ </div>
1122
+ );
1123
+ }
1124
+ ```
1125
+
1126
+ Returns modal factories with `appendTarget` automatically set to the portal root, so modals render into a controlled container rather than `document.body`.
1127
+
1128
+ ---
1129
+
1130
+ ## Renderer Hook System
1131
+
1132
+ Low-level hooks for custom content rendering (used internally by the React adapter).
1133
+
1134
+ ```ts
1135
+ Fnon.setRenderer((content, container, modal) => {
1136
+ // Return true to indicate you handled rendering
1137
+ return false; // fall back to innerHTML
1138
+ });
1139
+
1140
+ Fnon.registerCleanup(modalId, () => {
1141
+ // Cleanup logic when modal closes
1142
+ });
1143
+
1144
+ Fnon.registerReactRoot(modalId, root);
1145
+ ```
1146
+
1147
+ | Function | Description |
1148
+ |----------|-------------|
1149
+ | `setRenderer(fn)` | Register renderer (or `null` to clear) |
1150
+ | `registerCleanup(id, fn)` | Register per-modal cleanup |
1151
+ | `registerReactRoot(id, root)` | Store React root for unmounting |
1152
+
1153
+ **`RendererFn` signature:**
1154
+
1155
+ ```ts
1156
+ (content: FnonContent, container: HTMLElement, modal: Record<string, unknown>) => boolean
1157
+ ```
1158
+
1159
+ ## Browser Support
1160
+
1161
+ Modern browsers (Chrome, Firefox, Safari, Edge). Requires ES2015+.
1162
+
1163
+ ---
1164
+
1165
+ ## License
1166
+
1167
+ MIT