native-document 1.0.165 → 1.0.168

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 (488) hide show
  1. package/.vitepress/config.js +166 -0
  2. package/CHANGELOG.md +153 -0
  3. package/components.d.ts +2 -0
  4. package/components.js +2 -1
  5. package/devtools/widget.js +1 -1
  6. package/dist/native-document.components.min.js +11589 -2983
  7. package/dist/native-document.dev.js +2280 -396
  8. package/dist/native-document.dev.js.map +1 -1
  9. package/dist/native-document.min.js +1 -1
  10. package/docs/advanced-components.md +213 -608
  11. package/docs/anchor.md +173 -312
  12. package/docs/cache.md +95 -803
  13. package/docs/cli.md +179 -0
  14. package/docs/components/accordion.md +172 -0
  15. package/docs/components/alert.md +99 -0
  16. package/docs/components/avatar.md +160 -0
  17. package/docs/components/badge.md +102 -0
  18. package/docs/components/breadcrumb.md +89 -0
  19. package/docs/components/button.md +183 -0
  20. package/docs/components/card.md +69 -0
  21. package/docs/components/context-menu.md +118 -0
  22. package/docs/components/data-table.md +345 -0
  23. package/docs/components/dropdown.md +214 -0
  24. package/docs/components/form/autocomplete-field.md +81 -0
  25. package/docs/components/form/checkbox-field.md +41 -0
  26. package/docs/components/form/checkbox-group-field.md +54 -0
  27. package/docs/components/form/color-field.md +64 -0
  28. package/docs/components/form/date-field.md +92 -0
  29. package/docs/components/form/field-collection.md +63 -0
  30. package/docs/components/form/file-field.md +203 -0
  31. package/docs/components/form/form-control.md +87 -0
  32. package/docs/components/form/image-field.md +90 -0
  33. package/docs/components/form/index.md +115 -0
  34. package/docs/components/form/number-field.md +65 -0
  35. package/docs/components/form/radio-field.md +51 -0
  36. package/docs/components/form/select-field.md +123 -0
  37. package/docs/components/form/slider.md +136 -0
  38. package/docs/components/form/string-field.md +134 -0
  39. package/docs/components/form/textarea-field.md +65 -0
  40. package/docs/components/form-fields.md +372 -0
  41. package/docs/components/getting-started.md +264 -0
  42. package/docs/components/index.md +337 -0
  43. package/docs/components/layout.md +279 -0
  44. package/docs/components/list.md +73 -0
  45. package/docs/components/menu.md +215 -0
  46. package/docs/components/modal.md +156 -0
  47. package/docs/components/pagination.md +95 -0
  48. package/docs/components/popover.md +131 -0
  49. package/docs/components/progress.md +111 -0
  50. package/docs/components/shortcut-manager.md +221 -0
  51. package/docs/components/simple-table.md +107 -0
  52. package/docs/components/skeleton.md +155 -0
  53. package/docs/components/spinner.md +100 -0
  54. package/docs/components/splitter.md +133 -0
  55. package/docs/components/stepper.md +163 -0
  56. package/docs/components/switch.md +113 -0
  57. package/docs/components/tabs.md +153 -0
  58. package/docs/components/toast.md +119 -0
  59. package/docs/components/tooltip.md +151 -0
  60. package/docs/components/traits.md +261 -0
  61. package/docs/conditional-rendering.md +170 -588
  62. package/docs/contributing.md +300 -25
  63. package/docs/core-concepts.md +205 -374
  64. package/docs/elements.md +251 -367
  65. package/docs/extending-native-document-element.md +192 -207
  66. package/docs/filters.md +153 -1122
  67. package/docs/getting-started.md +193 -267
  68. package/docs/i18n.md +241 -0
  69. package/docs/index.md +76 -0
  70. package/docs/lifecycle-events.md +143 -75
  71. package/docs/list-rendering.md +227 -852
  72. package/docs/memory-management.md +134 -47
  73. package/docs/native-document-element.md +337 -186
  74. package/docs/native-fetch.md +99 -630
  75. package/docs/observable-resource.md +364 -0
  76. package/docs/observables.md +592 -526
  77. package/docs/routing.md +244 -653
  78. package/docs/state-management.md +134 -241
  79. package/docs/svg-elements.md +231 -0
  80. package/docs/theming.md +409 -0
  81. package/docs/validation.md +95 -97
  82. package/docs/vitepress-conventions.md +219 -0
  83. package/eslint.config.js +28 -33
  84. package/i18n.js +1 -1
  85. package/i18n.ts +2 -0
  86. package/index.js +3 -0
  87. package/package.json +36 -14
  88. package/readme.md +269 -89
  89. package/src/components/$traits/has-draggable/HasDraggable.d.ts +4 -0
  90. package/src/components/$traits/has-draggable/HasDraggable.js +13 -0
  91. package/src/components/$traits/has-items/HasItems.d.ts +9 -0
  92. package/src/components/$traits/has-items/HasItems.js +6 -6
  93. package/src/components/$traits/has-position/HasFullPosition.d.ts +14 -0
  94. package/src/components/$traits/has-position/HasFullPosition.js +44 -0
  95. package/src/components/$traits/has-position/HasPosition.d.ts +7 -0
  96. package/src/components/$traits/has-position/HasPosition.js +23 -1
  97. package/src/components/$traits/has-resizable/HasResizable.d.ts +13 -0
  98. package/src/components/$traits/has-resizable/HasResizable.js +9 -0
  99. package/src/components/$traits/has-validation/HasValidation.d.ts +17 -0
  100. package/src/components/$traits/has-validation/HasValidation.js +54 -7
  101. package/src/components/BaseComponent.d.ts +32 -0
  102. package/src/components/BaseComponent.js +65 -9
  103. package/src/components/accordion/Accordion.js +39 -14
  104. package/src/components/accordion/AccordionItem.js +45 -14
  105. package/src/components/accordion/index.js +2 -2
  106. package/src/components/accordion/types/Accordion.d.ts +47 -0
  107. package/src/components/accordion/types/AccordionItem.d.ts +48 -0
  108. package/src/components/alert/Alert.js +70 -38
  109. package/src/components/alert/index.js +2 -2
  110. package/src/components/alert/types/Alert.d.ts +62 -0
  111. package/src/components/avatar/Avatar.js +49 -12
  112. package/src/components/avatar/AvatarGroup.js +50 -2
  113. package/src/components/avatar/index.js +2 -2
  114. package/src/components/avatar/types/Avatar.d.ts +74 -0
  115. package/src/components/avatar/types/AvatarGroup.d.ts +32 -0
  116. package/src/components/badge/Badge.js +125 -5
  117. package/src/components/badge/index.js +2 -2
  118. package/src/components/badge/types/Badge.d.ts +51 -0
  119. package/src/components/breadcrumb/BreadCrumb.js +61 -5
  120. package/src/components/breadcrumb/index.js +2 -2
  121. package/src/components/breadcrumb/types/BreadCrumb.d.ts +42 -0
  122. package/src/components/button/Button.js +164 -9
  123. package/src/components/button/index.js +1 -1
  124. package/src/components/button/types/Button.d.ts +62 -0
  125. package/src/components/card/Card.js +204 -32
  126. package/src/components/card/index.js +4 -4
  127. package/src/components/card/types/Card.d.ts +42 -0
  128. package/src/components/context-menu/ContextMenu.js +49 -5
  129. package/src/components/context-menu/ContextMenuGroup.js +15 -2
  130. package/src/components/context-menu/ContextMenuItem.js +14 -2
  131. package/src/components/context-menu/index.js +5 -5
  132. package/src/components/context-menu/types/ContextMenu.d.ts +30 -0
  133. package/src/components/context-menu/types/ContextMenuGroup.d.ts +18 -0
  134. package/src/components/context-menu/types/ContextMenuItem.d.ts +18 -0
  135. package/src/components/divider/Divider.js +120 -4
  136. package/src/components/divider/index.js +3 -3
  137. package/src/components/divider/types/Divider.d.ts +55 -0
  138. package/src/components/dropdown/Dropdown.js +239 -16
  139. package/src/components/dropdown/DropdownDivider.js +22 -2
  140. package/src/components/dropdown/DropdownGroup.js +44 -5
  141. package/src/components/dropdown/DropdownItem.js +76 -3
  142. package/src/components/dropdown/DropdownTrigger.js +49 -20
  143. package/src/components/dropdown/helpers.js +1 -1
  144. package/src/components/dropdown/index.js +6 -6
  145. package/src/components/dropdown/types/Dropdown.d.ts +88 -0
  146. package/src/components/dropdown/types/DropdownDivider.d.ts +20 -0
  147. package/src/components/dropdown/types/DropdownGroup.d.ts +25 -0
  148. package/src/components/dropdown/types/DropdownItem.d.ts +41 -0
  149. package/src/components/dropdown/types/DropdownTrigger.d.ts +32 -0
  150. package/src/components/form/FormControl.js +156 -13
  151. package/src/components/form/field/Field.js +172 -9
  152. package/src/components/form/field/FieldCollection.js +116 -12
  153. package/src/components/form/field/types/AutocompleteField.js +92 -2
  154. package/src/components/form/field/types/CheckboxField.js +43 -2
  155. package/src/components/form/field/types/CheckboxGroupField.js +83 -6
  156. package/src/components/form/field/types/ColorField.js +56 -3
  157. package/src/components/form/field/types/DateField.js +155 -4
  158. package/src/components/form/field/types/EmailField.js +54 -4
  159. package/src/components/form/field/types/FileField.js +140 -6
  160. package/src/components/form/field/types/HiddenField.js +27 -1
  161. package/src/components/form/field/types/ImageField.js +82 -3
  162. package/src/components/form/field/types/NumberField.js +97 -4
  163. package/src/components/form/field/types/PasswordField.js +103 -7
  164. package/src/components/form/field/types/RadioField.js +75 -4
  165. package/src/components/form/field/types/RangeField.js +67 -1
  166. package/src/components/form/field/types/SearchField.js +41 -2
  167. package/src/components/form/field/types/SelectField.js +133 -4
  168. package/src/components/form/field/types/StringField.js +91 -2
  169. package/src/components/form/field/types/TelField.js +55 -4
  170. package/src/components/form/field/types/TextAreaField.js +76 -2
  171. package/src/components/form/field/types/TimeField.js +120 -5
  172. package/src/components/form/field/types/UrlField.js +59 -4
  173. package/src/components/form/field/types/file-field-mode/FileAvatarMode.js +83 -4
  174. package/src/components/form/field/types/file-field-mode/FileDropzoneMode.js +61 -3
  175. package/src/components/form/field/types/file-field-mode/FileItemPreview.js +79 -3
  176. package/src/components/form/field/types/file-field-mode/FileNativeMode.js +24 -2
  177. package/src/components/form/field/types/file-field-mode/FileUploadButtonMode.js +64 -3
  178. package/src/components/form/field/types/file-field-mode/FileWallMode.js +56 -3
  179. package/src/components/form/index.js +28 -28
  180. package/src/components/form/types/Field.d.ts +73 -0
  181. package/src/components/form/types/FieldCollection.d.ts +53 -0
  182. package/src/components/form/types/FormControl.d.ts +64 -0
  183. package/src/components/form/types/fields/AutocompleteField.d.ts +48 -0
  184. package/src/components/form/types/fields/CheckboxField.d.ts +33 -0
  185. package/src/components/form/types/fields/CheckboxGroupField.d.ts +49 -0
  186. package/src/components/form/types/fields/ColorField.d.ts +37 -0
  187. package/src/components/form/types/fields/DateField.d.ts +70 -0
  188. package/src/components/form/types/fields/EmailField.d.ts +35 -0
  189. package/src/components/form/types/fields/FileAvatarMode.d.ts +46 -0
  190. package/src/components/form/types/fields/FileDropzoneMode.d.ts +28 -0
  191. package/src/components/form/types/fields/FileField.d.ts +56 -0
  192. package/src/components/form/types/fields/FileItemPreview.d.ts +35 -0
  193. package/src/components/form/types/fields/FileNativeMode.d.ts +21 -0
  194. package/src/components/form/types/fields/FileUploadButtonMode.d.ts +34 -0
  195. package/src/components/form/types/fields/FileWallMode.d.ts +32 -0
  196. package/src/components/form/types/fields/HiddenField.d.ts +26 -0
  197. package/src/components/form/types/fields/ImageField.d.ts +45 -0
  198. package/src/components/form/types/fields/NumberField.d.ts +48 -0
  199. package/src/components/form/types/fields/PasswordField.d.ts +46 -0
  200. package/src/components/form/types/fields/RadioField.d.ts +48 -0
  201. package/src/components/form/types/fields/RangeField.d.ts +44 -0
  202. package/src/components/form/types/fields/SearchField.d.ts +34 -0
  203. package/src/components/form/types/fields/SelectField.d.ts +71 -0
  204. package/src/components/form/types/fields/StringField.d.ts +48 -0
  205. package/src/components/form/types/fields/TelField.d.ts +37 -0
  206. package/src/components/form/types/fields/TextAreaField.d.ts +44 -0
  207. package/src/components/form/types/fields/TimeField.d.ts +51 -0
  208. package/src/components/form/types/fields/UrlField.d.ts +35 -0
  209. package/src/components/form/validation/Validation.js +54 -54
  210. package/src/components/index.d.ts +160 -0
  211. package/src/components/list/HasListItem.js +171 -0
  212. package/src/components/list/List.js +85 -67
  213. package/src/components/list/ListDivider.js +39 -0
  214. package/src/components/list/ListGroup.js +105 -38
  215. package/src/components/list/ListItem.js +158 -49
  216. package/src/components/list/index.js +8 -6
  217. package/src/components/list/types/List.d.ts +43 -0
  218. package/src/components/list/types/ListGroup.d.ts +37 -0
  219. package/src/components/list/types/ListItem.d.ts +53 -0
  220. package/src/components/menu/HasMenuItem.js +55 -6
  221. package/src/components/menu/Menu.js +113 -22
  222. package/src/components/menu/MenuDivider.js +18 -2
  223. package/src/components/menu/MenuGroup.js +61 -6
  224. package/src/components/menu/MenuItem.js +95 -11
  225. package/src/components/menu/MenuLink.js +27 -2
  226. package/src/components/menu/index.js +6 -6
  227. package/src/components/menu/types/Menu.d.ts +60 -0
  228. package/src/components/menu/types/MenuDivider.d.ts +19 -0
  229. package/src/components/menu/types/MenuGroup.d.ts +44 -0
  230. package/src/components/menu/types/MenuItem.d.ts +46 -0
  231. package/src/components/menu/types/MenuLink.d.ts +16 -0
  232. package/src/components/modal/Modal.js +258 -17
  233. package/src/components/modal/index.js +3 -3
  234. package/src/components/modal/types/Modal.d.ts +94 -0
  235. package/src/components/pagination/Pagination.js +155 -7
  236. package/src/components/pagination/index.js +3 -3
  237. package/src/components/pagination/types/Pagination.d.ts +68 -0
  238. package/src/components/popover/Popover.js +198 -11
  239. package/src/components/popover/PopoverFooter.js +33 -9
  240. package/src/components/popover/PopoverHeader.js +33 -8
  241. package/src/components/popover/index.js +4 -4
  242. package/src/components/popover/types/Popover.d.ts +83 -0
  243. package/src/components/popover/types/PopoverFooter.d.ts +24 -0
  244. package/src/components/popover/types/PopoverHeader.d.ts +26 -0
  245. package/src/components/progress/Progress.js +182 -13
  246. package/src/components/progress/index.js +3 -3
  247. package/src/components/progress/types/Progress.d.ts +77 -0
  248. package/src/components/skeleton/Skeleton.js +117 -49
  249. package/src/components/skeleton/index.js +3 -3
  250. package/src/components/skeleton/types/Skeleton.d.ts +55 -0
  251. package/src/components/slider/Slider.js +207 -10
  252. package/src/components/slider/index.js +2 -2
  253. package/src/components/slider/types/Slider.d.ts +82 -0
  254. package/src/components/spacer/Spacer.js +12 -3
  255. package/src/components/spacer/index.js +2 -2
  256. package/src/components/spacer/types/Spacer.d.ts +19 -0
  257. package/src/components/spinner/Spinner.js +180 -9
  258. package/src/components/spinner/index.js +3 -3
  259. package/src/components/spinner/types/Spinner.d.ts +71 -0
  260. package/src/components/splitter/Splitter.js +76 -13
  261. package/src/components/splitter/SplitterGutter.js +67 -5
  262. package/src/components/splitter/SplitterPanel.js +69 -2
  263. package/src/components/splitter/index.js +5 -5
  264. package/src/components/splitter/types/Splitter.d.ts +38 -0
  265. package/src/components/splitter/types/SplitterGutter.d.ts +38 -0
  266. package/src/components/splitter/types/SplitterPanel.d.ts +41 -0
  267. package/src/components/stacks/AbsoluteStack.js +23 -3
  268. package/src/components/stacks/FixedStack.js +23 -3
  269. package/src/components/stacks/HStack.js +24 -3
  270. package/src/components/stacks/PositionStack.js +111 -3
  271. package/src/components/stacks/RelativeStack.js +23 -3
  272. package/src/components/stacks/Stack.js +73 -2
  273. package/src/components/stacks/VStack.js +24 -4
  274. package/src/components/stacks/index.js +7 -7
  275. package/src/components/stacks/types/AbsoluteStack.d.ts +16 -0
  276. package/src/components/stacks/types/FixedStack.d.ts +16 -0
  277. package/src/components/stacks/types/HStack.d.ts +16 -0
  278. package/src/components/stacks/types/PositionStack.d.ts +54 -0
  279. package/src/components/stacks/types/RelativeStack.d.ts +17 -0
  280. package/src/components/stacks/types/Stack.d.ts +39 -0
  281. package/src/components/stacks/types/VStack.d.ts +16 -0
  282. package/src/components/stepper/Stepper.js +152 -12
  283. package/src/components/stepper/StepperStep.js +104 -3
  284. package/src/components/stepper/index.js +4 -4
  285. package/src/components/stepper/types/Stepper.d.ts +68 -0
  286. package/src/components/stepper/types/StepperStep.d.ts +54 -0
  287. package/src/components/switch/Switch.js +143 -6
  288. package/src/components/switch/index.js +1 -1
  289. package/src/components/switch/types/Switch.d.ts +55 -0
  290. package/src/components/table/Column.js +105 -6
  291. package/src/components/table/ColumnGroup.js +48 -3
  292. package/src/components/table/DataTable.js +256 -19
  293. package/src/components/table/SimpleTable.js +58 -4
  294. package/src/components/table/index.js +2 -2
  295. package/src/components/table/types/Column.d.ts +49 -0
  296. package/src/components/table/types/ColumnGroup.d.ts +28 -0
  297. package/src/components/table/types/DataTable.d.ts +97 -0
  298. package/src/components/table/types/SimpleTable.d.ts +40 -0
  299. package/src/components/tabs/Tabs.js +192 -5
  300. package/src/components/tabs/index.js +3 -3
  301. package/src/components/tabs/types/Tabs.d.ts +78 -0
  302. package/src/components/toast/Toast.js +133 -5
  303. package/src/components/toast/index.js +3 -3
  304. package/src/components/toast/types/Toast.d.ts +57 -0
  305. package/src/components/toast/types/ToastError.d.ts +7 -0
  306. package/src/components/toast/types/ToastInfo.d.ts +7 -0
  307. package/src/components/toast/types/ToastSuccess.d.ts +7 -0
  308. package/src/components/toast/types/ToastWarning.d.ts +7 -0
  309. package/src/components/tooltip/Tooltip.js +157 -13
  310. package/src/components/tooltip/index.js +2 -2
  311. package/src/components/tooltip/prototypes.js +1 -1
  312. package/src/components/tooltip/types/Tooltip.d.ts +65 -0
  313. package/src/core/data/MemoryManager.js +2 -2
  314. package/src/core/data/Observable.js +15 -18
  315. package/src/core/data/ObservableArray.js +118 -46
  316. package/src/core/data/ObservableChecker.js +2 -2
  317. package/src/core/data/ObservableItem.js +135 -21
  318. package/src/core/data/ObservableObject.js +126 -35
  319. package/src/core/data/ObservableResource.js +118 -3
  320. package/src/core/data/Store.js +142 -26
  321. package/src/core/data/observable-helpers/observable.is-to.js +196 -1
  322. package/src/core/data/observable-helpers/observable.prototypes.js +35 -8
  323. package/src/core/elements/anchor/anchor-with-sentinel.js +23 -2
  324. package/src/core/elements/anchor/anchor.js +16 -7
  325. package/src/core/elements/anchor/one-child-anchor-overwriting.js +2 -2
  326. package/src/core/elements/content-formatter.js +1 -1
  327. package/src/core/elements/control/for-each-array.js +9 -9
  328. package/src/core/elements/control/for-each.js +14 -14
  329. package/src/core/elements/control/show-if.js +11 -11
  330. package/src/core/elements/control/show-when.js +5 -5
  331. package/src/core/elements/control/switch.js +14 -14
  332. package/src/core/elements/description-list.js +1 -1
  333. package/src/core/elements/form.js +2 -2
  334. package/src/core/elements/fragment.js +1 -1
  335. package/src/core/elements/html5-semantics.js +1 -1
  336. package/src/core/elements/img.js +3 -3
  337. package/src/core/elements/interactive.js +1 -1
  338. package/src/core/elements/list.js +1 -1
  339. package/src/core/elements/medias.js +1 -1
  340. package/src/core/elements/meta-data.js +1 -1
  341. package/src/core/elements/svg.js +1 -1
  342. package/src/core/elements/table.js +1 -1
  343. package/src/core/errors/ArgTypesError.js +1 -1
  344. package/src/core/utils/HasEventEmitter.js +36 -2
  345. package/src/core/utils/args-types.js +9 -9
  346. package/src/core/utils/cache.js +1 -1
  347. package/src/core/utils/callback-handler.js +29 -0
  348. package/src/core/utils/debug-manager.js +6 -6
  349. package/src/core/utils/events.js +139 -139
  350. package/src/core/utils/filters/date.js +84 -3
  351. package/src/core/utils/filters/standard.js +136 -11
  352. package/src/core/utils/filters/strings.js +34 -2
  353. package/src/core/utils/filters/utils.js +40 -4
  354. package/src/core/utils/formatters.js +4 -4
  355. package/src/core/utils/helpers.js +39 -7
  356. package/src/core/utils/localstorage.js +11 -11
  357. package/src/core/utils/memoize.js +56 -3
  358. package/src/core/utils/plugins-manager.js +3 -3
  359. package/src/core/utils/property-accumulator.js +6 -6
  360. package/src/core/utils/prototypes.js +26 -1
  361. package/src/core/utils/shortcut-manager.js +2 -2
  362. package/src/core/utils/validator.js +8 -8
  363. package/src/core/wrappers/AttributesWrapper.js +32 -22
  364. package/src/core/wrappers/DocumentObserver.js +3 -3
  365. package/src/core/wrappers/ElementCreator.js +5 -5
  366. package/src/core/wrappers/HtmlElementWrapper.js +38 -12
  367. package/src/core/wrappers/NDElement.js +328 -22
  368. package/src/core/wrappers/NdPrototype.js +60 -16
  369. package/src/core/wrappers/SingletonView.js +50 -2
  370. package/src/core/wrappers/SvgElementWrapper.js +1 -1
  371. package/src/core/wrappers/constants.js +35 -2
  372. package/src/core/wrappers/prototypes/attributes-extensions.js +7 -7
  373. package/src/core/wrappers/prototypes/nd-element-extensions.js +72 -6
  374. package/src/core/wrappers/prototypes/nd-element.transition.extensions.js +42 -2
  375. package/src/core/wrappers/template-cloner/NodeCloner.js +53 -8
  376. package/src/core/wrappers/template-cloner/TemplateCloner.js +75 -6
  377. package/src/core/wrappers/template-cloner/attributes-hydrator.js +58 -2
  378. package/src/core/wrappers/template-cloner/utils.js +42 -6
  379. package/src/fetch/NativeFetch.js +3 -3
  380. package/src/i18n/bin/scan.js +6 -6
  381. package/src/i18n/index.d.ts +2 -0
  382. package/src/i18n/service/I18nService.d.ts +27 -0
  383. package/src/i18n/service/I18nService.js +5 -5
  384. package/src/i18n/service/functions.d.ts +22 -0
  385. package/src/i18n/service/functions.js +2 -2
  386. package/src/router/Route.js +3 -3
  387. package/src/router/RouteGroupHelper.js +2 -2
  388. package/src/router/Router.js +15 -15
  389. package/src/router/RouterComponent.js +33 -7
  390. package/src/router/link.js +4 -4
  391. package/src/router/modes/HashRouter.js +2 -2
  392. package/src/router/modes/HistoryRouter.js +2 -2
  393. package/src/router/modes/MemoryRouter.js +1 -1
  394. package/src/ui/components/accordion/AccordionItemRender.js +3 -3
  395. package/src/ui/components/accordion/AccordionRender.js +1 -1
  396. package/src/ui/components/alert/AlertRender.js +10 -10
  397. package/src/ui/components/avatar/avata-group/AvatarGroupRender.js +1 -1
  398. package/src/ui/components/avatar/avatar/AvatarRender.js +1 -1
  399. package/src/ui/components/breadcrumb/BreadcrumbRender.js +2 -2
  400. package/src/ui/components/button/ButtonRender.js +1 -1
  401. package/src/ui/components/card/CardRender.js +133 -0
  402. package/src/ui/components/card/card.css +169 -0
  403. package/src/ui/components/contextmenu/ContextmenuRender.js +6 -6
  404. package/src/ui/components/dropdown/DropdownRender.js +8 -8
  405. package/src/ui/components/dropdown/group/DropdownGroupRender.js +2 -2
  406. package/src/ui/components/dropdown/item/DropdownItemRender.js +1 -1
  407. package/src/ui/components/form/FieldCollectionRender.js +2 -2
  408. package/src/ui/components/form/FormControlRender.js +5 -5
  409. package/src/ui/components/form/fields/AutocompleteFieldRender.js +3 -3
  410. package/src/ui/components/form/fields/CheckboxFieldRender.js +1 -1
  411. package/src/ui/components/form/fields/CheckboxGroupFieldRender.js +1 -1
  412. package/src/ui/components/form/fields/DateFieldRender.js +7 -7
  413. package/src/ui/components/form/fields/EmailFieldRender.js +1 -1
  414. package/src/ui/components/form/fields/FieldRender.js +4 -4
  415. package/src/ui/components/form/fields/FileFieldRender.js +1 -1
  416. package/src/ui/components/form/fields/PasswordFieldRender.js +2 -2
  417. package/src/ui/components/form/fields/RadioFieldRender.js +1 -1
  418. package/src/ui/components/form/fields/RangeFieldRender.js +1 -1
  419. package/src/ui/components/form/fields/SelectFieldRender.js +2 -2
  420. package/src/ui/components/form/fields/SliderFieldRender.js +6 -6
  421. package/src/ui/components/form/fields/StringFieldRender.js +1 -1
  422. package/src/ui/components/form/fields/TelFieldRender.js +1 -1
  423. package/src/ui/components/form/fields/TextAreaFieldRender.js +1 -1
  424. package/src/ui/components/form/fields/TimeFieldRender.js +3 -3
  425. package/src/ui/components/form/fields/UrlFieldRender.js +1 -1
  426. package/src/ui/components/form/file-upload-mode/FileAvatarModeRender.js +1 -1
  427. package/src/ui/components/form/file-upload-mode/FileDropzoneModeRender.js +2 -2
  428. package/src/ui/components/form/file-upload-mode/FileUploadButtonModeRender.js +2 -2
  429. package/src/ui/components/form/file-upload-mode/FileWallModeRender.js +1 -1
  430. package/src/ui/components/form/helpers.js +8 -8
  431. package/src/ui/components/form/index.js +27 -27
  432. package/src/ui/components/list/ListRender.js +18 -0
  433. package/src/ui/components/list/divider/ListDividerRender.js +10 -0
  434. package/src/ui/components/list/divider/list-divider.css +12 -0
  435. package/src/ui/components/list/group/ListGroupRender.js +61 -0
  436. package/src/ui/components/list/group/list-group.css +62 -0
  437. package/src/ui/components/list/item/ListItemRender.js +238 -0
  438. package/src/ui/components/list/item/list-item.css +191 -0
  439. package/src/ui/components/list/list.css +24 -0
  440. package/src/ui/components/menu/MenuDividerRender.js +1 -1
  441. package/src/ui/components/menu/MenuGroupRender.js +3 -3
  442. package/src/ui/components/menu/MenuItemRender.js +2 -2
  443. package/src/ui/components/menu/MenuLinkRender.js +3 -3
  444. package/src/ui/components/menu/helpers.js +4 -4
  445. package/src/ui/components/modal/ModalRender.js +4 -4
  446. package/src/ui/components/pagination/PaginationRender.js +9 -9
  447. package/src/ui/components/popover/PopoverRender.js +7 -7
  448. package/src/ui/components/progress/ProgressRender.js +12 -12
  449. package/src/ui/components/skeleton/SkeletonRender.js +56 -0
  450. package/src/ui/components/spacer/SpacerRender.js +10 -0
  451. package/src/ui/components/splitter/SplitterGutterRender.js +1 -1
  452. package/src/ui/components/splitter/SplitterPanelRender.js +2 -2
  453. package/src/ui/components/stacks/PositionStackRender.js +1 -1
  454. package/src/ui/components/stacks/StackRender.js +1 -1
  455. package/src/ui/components/stacks/absolute-stack/AbsoluteStackRender.js +1 -1
  456. package/src/ui/components/stacks/fixed-stack/FixedStackRender.js +1 -1
  457. package/src/ui/components/stacks/h-stack/HStackRender.js +1 -1
  458. package/src/ui/components/stacks/index.js +5 -5
  459. package/src/ui/components/stacks/relative-stack/RelativeStackRender.js +1 -1
  460. package/src/ui/components/stacks/v-stack/VStackRender.js +1 -1
  461. package/src/ui/components/stepper/StepperRender.js +2 -2
  462. package/src/ui/components/stepper/StepperStepRender.js +4 -4
  463. package/src/ui/components/switch/SwitchRender.js +4 -4
  464. package/src/ui/components/table/data-table/DataTableRender.js +5 -5
  465. package/src/ui/components/table/data-table/bulk-actions.js +7 -7
  466. package/src/ui/components/table/data-table/pagination.js +6 -6
  467. package/src/ui/components/table/data-table/tables.js +25 -25
  468. package/src/ui/components/table/data-table/toolbar.js +3 -3
  469. package/src/ui/components/table/simple-table/SimpleTableRender.js +8 -8
  470. package/src/ui/components/tabs/TabsRender.js +11 -11
  471. package/src/ui/components/toast/ToastRender.js +3 -3
  472. package/src/ui/components/tooltip/TooltipRender.js +1 -1
  473. package/src/ui/index.js +44 -36
  474. package/types/elements.d.ts +163 -1037
  475. package/types/forms.d.ts +16 -20
  476. package/types/globals.d.ts +543 -0
  477. package/types/images.d.ts +2 -2
  478. package/types/observable-resource.d.ts +3 -0
  479. package/types/property-accumulator.d.ts +4 -4
  480. package/types/store.d.ts +26 -2
  481. package/types/validator.ts +3 -3
  482. package/ui.js +1 -0
  483. package/src/components/form/field/DefaultRender.js +0 -77
  484. package/src/components/form/field/FieldFactory.js +0 -107
  485. package/src/components/skeleton/SkeletonList.js +0 -0
  486. package/src/components/skeleton/SkeletonParagraph.js +0 -0
  487. package/src/components/skeleton/SkeletonTable.js +0 -0
  488. /package/{src/components/skeleton/SkeletonCard.js → docs/tutorials/.gitkeep} +0 -0
@@ -8,7 +8,7 @@ var NativeDocument = (function (exports) {
8
8
  enabled: true,
9
9
 
10
10
  enable() {
11
- DebugManager$1.log('🔍 NativeDocument Debug Mode enabled');
11
+ DebugManager$1.log('NativeDocument Debug Mode enabled');
12
12
  },
13
13
 
14
14
  disable() {
@@ -16,19 +16,19 @@ var NativeDocument = (function (exports) {
16
16
  },
17
17
 
18
18
  log(category, message, data) {
19
- console.group(`🔍 [${category}] ${message}`);
19
+ console.group(`[${category}] ${message}`);
20
20
  if (data) console.log(data);
21
21
  console.trace();
22
22
  console.groupEnd();
23
23
  },
24
24
 
25
25
  warn(category, message, data) {
26
- console.warn(`⚠️ [${category}] ${message}`, data);
26
+ console.warn(`[${category}] ${message}`, data);
27
27
  },
28
28
 
29
29
  error(category, message, error) {
30
- console.error(`❌ [${category}] ${message}`, error);
31
- }
30
+ console.error(`[${category}] ${message}`, error);
31
+ },
32
32
  };
33
33
 
34
34
  }
@@ -47,7 +47,7 @@ var NativeDocument = (function (exports) {
47
47
  ELEMENT: 1,
48
48
  TEXT: 3,
49
49
  COMMENT: 8,
50
- DOCUMENT_FRAGMENT: 11
50
+ DOCUMENT_FRAGMENT: 11,
51
51
  };
52
52
 
53
53
  const VALID_TYPES = [];
@@ -70,13 +70,13 @@ var NativeDocument = (function (exports) {
70
70
  return value?.__$isObservableArray;
71
71
  },
72
72
  isProxy(value) {
73
- return value?.__isProxy__
73
+ return value?.__isProxy__;
74
74
  },
75
75
  isObservableOrProxy(value) {
76
- return Validator.isObservable(value) || Validator.isProxy(value);
76
+ return value?.__$Observable;
77
77
  },
78
78
  isAnchor(value) {
79
- return value?.__Anchor__
79
+ return value?.__Anchor__;
80
80
  },
81
81
  isObservableChecker(value) {
82
82
  return value?.__$isObservableChecker;
@@ -103,7 +103,7 @@ var NativeDocument = (function (exports) {
103
103
  return typeof value === 'object' && value !== null;
104
104
  },
105
105
  isJson(value) {
106
- return !(typeof value !== 'object' || value === null || Array.isArray(value) || value.constructor.name !== 'Object')
106
+ return !(typeof value !== 'object' || value === null || Array.isArray(value) || value.constructor.name !== 'Object');
107
107
  },
108
108
  isElement(value) {
109
109
  return value && VALID_TYPES[value.nodeType];
@@ -176,7 +176,7 @@ var NativeDocument = (function (exports) {
176
176
  if (typeof callback !== 'function') {
177
177
  throw new NativeDocumentError('Event callback must be a function');
178
178
  }
179
- }
179
+ },
180
180
  };
181
181
  {
182
182
  Validator.validateAttributes = function(attributes) {
@@ -225,9 +225,42 @@ var NativeDocument = (function (exports) {
225
225
  'itemscope',
226
226
  'allowfullscreen',
227
227
  'allowpaymentrequest',
228
- 'playsinline'
228
+ 'playsinline',
229
229
  ]);
230
230
 
231
+ const BOOL_ATTRIBUTES_NAME = {
232
+ 'allowfullscreen': 'allowFullscreen',
233
+ 'allowpaymentrequest': 'allowPaymentRequest',
234
+ 'async': 'async',
235
+ 'autocomplete': 'autocomplete',
236
+ 'autofocus': 'autofocus',
237
+ 'autoplay': 'autoplay',
238
+ 'checked': 'checked',
239
+ 'controls': 'controls',
240
+ 'default': 'default',
241
+ 'defer': 'defer',
242
+ 'disabled': 'disabled',
243
+ 'download': 'download',
244
+ 'draggable': 'draggable',
245
+ 'formnovalidate': 'formNoValidate',
246
+ 'contenteditable': 'contentEditable',
247
+ 'hidden': 'hidden',
248
+ 'itemscope': 'itemScope',
249
+ 'loop': 'loop',
250
+ 'multiple': 'multiple',
251
+ 'muted': 'muted',
252
+ 'novalidate': 'noValidate',
253
+ 'open': 'open',
254
+ 'playsinline': 'playsInline',
255
+ 'readonly': 'readOnly',
256
+ 'required': 'required',
257
+ 'reversed': 'reversed',
258
+ 'scoped': 'scoped',
259
+ 'selected': 'selected',
260
+ 'spellcheck': 'spellcheck',
261
+ 'translate': 'translate',
262
+ };
263
+
231
264
  const MemoryManager = (function() {
232
265
 
233
266
  let $nextObserverId = 0;
@@ -277,7 +310,7 @@ var NativeDocument = (function (exports) {
277
310
  if (cleanedCount > 0) {
278
311
  DebugManager$2.log('Memory Auto Clean', `🧹 Cleaned ${cleanedCount} orphaned observables`);
279
312
  }
280
- }
313
+ },
281
314
  };
282
315
  }());
283
316
 
@@ -299,7 +332,7 @@ var NativeDocument = (function (exports) {
299
332
  }
300
333
  name = name || plugin.name;
301
334
  if (!name || typeof name !== 'string') {
302
- throw new Error(`Please, provide a valid plugin name`);
335
+ throw new Error('Please, provide a valid plugin name');
303
336
  }
304
337
  if($plugins.has(name)) {
305
338
  return;
@@ -354,13 +387,21 @@ var NativeDocument = (function (exports) {
354
387
  }
355
388
  }
356
389
  }
357
- }
390
+ },
358
391
  };
359
392
  }());
360
393
  }
361
394
 
362
395
  var PluginsManager$1 = PluginsManager;
363
396
 
397
+ /**
398
+ * Calls a function with the given arguments and optional context.
399
+ *
400
+ * @internal
401
+ * @param {Function} fn - Function to invoke
402
+ * @param {Array} args - Arguments to pass
403
+ * @param {Object|null} [context] - `this` context, or null to call without binding
404
+ */
364
405
  const invoke = function(fn, args, context) {
365
406
  if(context) {
366
407
  fn.apply(context, args);
@@ -389,7 +430,7 @@ var NativeDocument = (function (exports) {
389
430
  // debounce mode: reset the timer for each call
390
431
  clearTimeout(timer);
391
432
  timer = setTimeout(() => invoke(fn, lastArgs, context), delay);
392
- }
433
+ };
393
434
  };
394
435
 
395
436
  const nextTick = function(fn) {
@@ -405,12 +446,18 @@ var NativeDocument = (function (exports) {
405
446
  };
406
447
  };
407
448
 
449
+
408
450
  /**
451
+ * Returns the unique key for a given item, used by ForEach and ForEachArray for DOM diffing.
452
+ * Resolution order:
453
+ * 1. If key is a function: calls key(item, defaultKey)
454
+ * 2. If key is a string: reads item[key] (unwrapping observables)
455
+ * 3. Otherwise: returns item value or defaultKey
409
456
  *
410
- * @param {*} item
411
- * @param {string|null} defaultKey
412
- * @param {?Function} key
413
- * @returns {*}
457
+ * @param {*} item - The item to extract a key from
458
+ * @param {string|number} defaultKey - Fallback key (usually the array index)
459
+ * @param {string|Function|null} key - Key property name or custom key function
460
+ * @returns {string|number} The resolved unique key
414
461
  */
415
462
  const getKey = (item, defaultKey, key) => {
416
463
  if (Validator.isString(key)) {
@@ -427,10 +474,28 @@ var NativeDocument = (function (exports) {
427
474
  return val ?? defaultKey;
428
475
  };
429
476
 
477
+ /**
478
+ * Trims all leading and trailing occurrences of char from str.
479
+ *
480
+ * @param {string} str - String to trim
481
+ * @param {string} char - Character to remove from both ends
482
+ * @returns {string} Trimmed string
483
+ * @example
484
+ * trim('/foo/bar/', '/'); // 'foo/bar'
485
+ */
430
486
  const trim = function(str, char) {
431
487
  return str.replace(new RegExp(`^[${char}]+|[${char}]+$`, 'g'), '');
432
488
  };
433
489
 
490
+ /**
491
+ * Deep clones a value. Uses structuredClone when available.
492
+ * Handles: primitives, Dates, Arrays, plain Objects.
493
+ * Observables are kept by reference (not cloned); onObservableFound is called for each.
494
+ *
495
+ * @param {*} value - Value to clone
496
+ * @param {((observable: ObservableItem) => void)?} [onObservableFound] - Called for each observable encountered
497
+ * @returns {*} Deep clone of the value
498
+ */
434
499
  const deepClone = (value, onObservableFound) => {
435
500
  try {
436
501
  if(window.structuredClone !== undefined) {
@@ -470,7 +535,7 @@ var NativeDocument = (function (exports) {
470
535
 
471
536
  const LocalStorage = {
472
537
  getJson(key) {
473
- let value = localStorage.getItem(key);
538
+ const value = localStorage.getItem(key);
474
539
  try {
475
540
  return JSON.parse(value);
476
541
  } catch (e) {
@@ -501,7 +566,7 @@ var NativeDocument = (function (exports) {
501
566
  },
502
567
  has(key) {
503
568
  return localStorage.getItem(key) != null;
504
- }
569
+ },
505
570
  };
506
571
 
507
572
  const $getFromStorage = (key, value) => {
@@ -509,26 +574,34 @@ var NativeDocument = (function (exports) {
509
574
  return value;
510
575
  }
511
576
  switch (typeof value) {
512
- case 'object': return LocalStorage.getJson(key) ?? value;
513
- case 'boolean': return LocalStorage.getBool(key) ?? value;
514
- case 'number': return LocalStorage.getNumber(key) ?? value;
515
- default: return LocalStorage.get(key, value) ?? value;
577
+ case 'object': return LocalStorage.getJson(key) ?? value;
578
+ case 'boolean': return LocalStorage.getBool(key) ?? value;
579
+ case 'number': return LocalStorage.getNumber(key) ?? value;
580
+ default: return LocalStorage.get(key, value) ?? value;
516
581
  }
517
582
  };
518
583
 
519
584
  const $saveToStorage = (value) => {
520
585
  switch (typeof value) {
521
- case 'object': return LocalStorage.setJson;
522
- case 'boolean': return LocalStorage.setBool;
523
- default: return LocalStorage.set;
586
+ case 'object': return LocalStorage.setJson;
587
+ case 'boolean': return LocalStorage.setBool;
588
+ default: return LocalStorage.set;
524
589
  }
525
590
  };
526
591
 
527
592
  /**
593
+ * Reactive primitive value container.
594
+ * Notifies subscribers whenever its value changes.
595
+ * The core building block of NativeDocument's reactivity system.
528
596
  *
529
- * @param {*} value
530
- * @param {{ propagation: boolean, reset: boolean} | null} configs
531
- * @class ObservableItem
597
+ * @constructor
598
+ * @param {*} value - Initial value of the observable
599
+ * @param {{ propagation?: boolean, reset?: boolean, deep?: boolean } | null} [configs=null] - Optional configuration
600
+ * @param {boolean} [configs.reset] - If true, stores the initial value for later reset via .reset()
601
+ * @param {boolean} [configs.propagation] - Controls whether changes propagate to parent observables
602
+ * @example
603
+ * const count = new ObservableItem(0);
604
+ * const name = new ObservableItem('John', { reset: true });
532
605
  */
533
606
  function ObservableItem(value, configs = null) {
534
607
  value = Validator.isObservable(value) ? value.val() : value;
@@ -587,15 +660,34 @@ var NativeDocument = (function (exports) {
587
660
  return this;
588
661
  };
589
662
 
663
+ /**
664
+ * Intercepts mutations of the array observable before they are applied.
665
+ * Allows transforming or cancelling array operations.
666
+ *
667
+ * @param {(operation: { action: string, args: any[] }) => void} callback - Called before each mutation with the operation details
668
+ * @returns {ObservableItem} this
669
+ */
590
670
  ObservableItem.prototype.interceptMutations = function(callback) {
591
671
  this.$mutationInterceptor = callback;
592
672
  return this;
593
673
  };
594
674
 
675
+ /**
676
+ * Calls the first registered subscriber directly (internal optimisation).
677
+ *
678
+ * @internal
679
+ * @param {{ action?: string, args?: any[], result?: any }} [operations] - Mutation metadata
680
+ */
595
681
  ObservableItem.prototype.triggerFirstListener = function(operations) {
596
682
  this.$firstListener(this.$currentValue, this.$previousValue, operations);
597
683
  };
598
684
 
685
+ /**
686
+ * Calls all registered subscribers (internal).
687
+ *
688
+ * @internal
689
+ * @param {{ action?: string, args?: any[], result?: any }} [operations] - Mutation metadata
690
+ */
599
691
  ObservableItem.prototype.triggerListeners = function(operations) {
600
692
  const $listeners = this.$listeners;
601
693
  const $previousValue = this.$previousValue;
@@ -606,6 +698,12 @@ var NativeDocument = (function (exports) {
606
698
  }
607
699
  };
608
700
 
701
+ /**
702
+ * Triggers callbacks registered via .on() for the current and previous values (internal).
703
+ *
704
+ * @internal
705
+ * @param {{ action?: string, args?: any[], result?: any }} [operations] - Mutation metadata
706
+ */
609
707
  ObservableItem.prototype.triggerWatchers = function(operations) {
610
708
  const $watchers = this.$watchers;
611
709
  const $previousValue = this.$previousValue;
@@ -621,16 +719,35 @@ var NativeDocument = (function (exports) {
621
719
  }
622
720
  };
623
721
 
722
+ /**
723
+ * Triggers both watchers and all subscribers (internal).
724
+ *
725
+ * @internal
726
+ * @param {{ action?: string, args?: any[], result?: any }} [operations] - Mutation metadata
727
+ */
624
728
  ObservableItem.prototype.triggerAll = function(operations) {
625
729
  this.triggerWatchers(operations);
626
730
  this.triggerListeners(operations);
627
731
  };
628
732
 
733
+ /**
734
+ * Triggers both watchers and the first subscriber only (internal optimization).
735
+ *
736
+ * @internal
737
+ * @param {{ action?: string, args?: any[], result?: any }} [operations] - Mutation metadata
738
+ */
629
739
  ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations) {
630
740
  this.triggerWatchers(operations);
631
741
  this.triggerFirstListener(operations);
632
742
  };
633
743
 
744
+ /**
745
+ * Selects and assigns the optimal trigger strategy based on the current
746
+ * combination of listeners and watchers (internal).
747
+ * Called automatically after every subscribe / unsubscribe / on / off.
748
+ *
749
+ * @internal
750
+ */
634
751
  ObservableItem.prototype.assocTrigger = function() {
635
752
  this.$firstListener = null;
636
753
  if(this.$watchers?.size && this.$listeners?.length) {
@@ -695,16 +812,30 @@ var NativeDocument = (function (exports) {
695
812
  * @param {*} data
696
813
  */
697
814
  ObservableItem.prototype.$basicSet = function(data) {
698
- let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
815
+ const newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
699
816
  this.$updateWithNewValue(newValue);
700
817
  };
701
818
 
702
819
  ObservableItem.prototype.set = ObservableItem.prototype.$basicSet;
703
820
 
821
+ /**
822
+ * Returns the current value of the observable.
823
+ *
824
+ * @returns {*} The current value
825
+ * @example
826
+ * const count = Observable(42);
827
+ * count.val(); // 42
828
+ */
704
829
  ObservableItem.prototype.val = function() {
705
830
  return this.$currentValue;
706
831
  };
707
832
 
833
+ /**
834
+ * Disconnects all listeners and watchers and nullifies internal state.
835
+ * Does not trigger cleanup callbacks. Prefer .cleanup() for full disposal.
836
+ *
837
+ * @returns {void}
838
+ */
708
839
  ObservableItem.prototype.disconnectAll = function() {
709
840
  this.$previousValue = null;
710
841
  this.$currentValue = null;
@@ -728,6 +859,16 @@ var NativeDocument = (function (exports) {
728
859
  this.$cleanupListeners.push(callback);
729
860
  };
730
861
 
862
+ /**
863
+ * Disposes the observable: runs cleanup callbacks, unregisters from MemoryManager,
864
+ * disconnects all listeners and removes the $value property.
865
+ *
866
+ * @returns {void}
867
+ * @example
868
+ * const obs = Observable(0);
869
+ * obs.onCleanup(() => console.log('disposed'));
870
+ * obs.cleanup(); // logs 'disposed', frees memory
871
+ */
731
872
  ObservableItem.prototype.cleanup = function() {
732
873
  if (this.$cleanupListeners) {
733
874
  for (let i = 0; i < this.$cleanupListeners.length; i++) {
@@ -743,10 +884,16 @@ var NativeDocument = (function (exports) {
743
884
  delete this.$value;
744
885
  };
745
886
 
887
+
746
888
  /**
889
+ * Subscribes to value changes. The callback is called every time the value changes.
890
+ * Returns nothing — use .unsubscribe(callback) to remove the listener.
747
891
  *
748
- * @param {Function} callback
749
- * @returns {(function(): void)}
892
+ * @param {(current: *, previous: *, operations?: { action?: string }) => void} callback - Called on each value change
893
+ * @example
894
+ * const count = Observable(0);
895
+ * count.subscribe((val) => console.log('New value:', val));
896
+ * count.$value++; // logs 'New value: 1'
750
897
  */
751
898
  ObservableItem.prototype.subscribe = function(callback) {
752
899
  {
@@ -862,9 +1009,16 @@ var NativeDocument = (function (exports) {
862
1009
  this.subscribe(handler);
863
1010
  };
864
1011
 
1012
+
865
1013
  /**
866
- * Unsubscribe from an observable.
867
- * @param {Function} callback
1014
+ * Removes a previously registered subscriber.
1015
+ *
1016
+ * @param {Function} callback - The exact function reference passed to .subscribe()
1017
+ * @returns {void}
1018
+ * @example
1019
+ * const handler = (val) => console.log(val);
1020
+ * count.subscribe(handler);
1021
+ * count.unsubscribe(handler);
868
1022
  */
869
1023
  ObservableItem.prototype.unsubscribe = function(callback) {
870
1024
  if(!this.$listeners) return;
@@ -983,8 +1137,22 @@ var NativeDocument = (function (exports) {
983
1137
  return this.$currentValue;
984
1138
  };
985
1139
 
986
-
987
-
1140
+ /**
1141
+ * Syncs this observable's value to localStorage and restores it on load.
1142
+ * Optionally transforms values on get and set.
1143
+ *
1144
+ * @param {string} key - localStorage key
1145
+ * @param {{ get?: (stored: any) => T, set?: (value: T) => any }} [options={}] - Transform options
1146
+ * @param {Function} [options.get] - Transform the stored value before applying it
1147
+ * @param {Function} [options.set] - Transform the value before saving it
1148
+ * @returns {ObservableItem} this — chainable
1149
+ * @example
1150
+ * const theme = Observable('light').persist('app-theme');
1151
+ * const count = Observable(0).persist('count', {
1152
+ * get: (v) => parseInt(v),
1153
+ * set: (v) => String(v),
1154
+ * });
1155
+ */
988
1156
  ObservableItem.prototype.persist = function(key, options = {}) {
989
1157
  let value = $getFromStorage(key, this.$currentValue);
990
1158
  if(options.get) {
@@ -998,6 +1166,17 @@ var NativeDocument = (function (exports) {
998
1166
  return this;
999
1167
  };
1000
1168
 
1169
+ /**
1170
+ * Creates a new ObservableItem with a deep clone of the current value.
1171
+ * For objects implementing a .clone() method, delegates to that method.
1172
+ *
1173
+ * @returns {ObservableItem} A new independent observable with the cloned value
1174
+ * @example
1175
+ * const original = Observable({ x: 1 });
1176
+ * const copy = original.clone();
1177
+ * copy.set({ x: 99 });
1178
+ * original.val(); // { x: 1 } — untouched
1179
+ */
1001
1180
  ObservableItem.prototype.clone = function() {
1002
1181
  let clonedValue = this.$currentValue;
1003
1182
 
@@ -1012,11 +1191,24 @@ var NativeDocument = (function (exports) {
1012
1191
  return new ObservableItem(clonedValue);
1013
1192
  };
1014
1193
 
1194
+ /**
1195
+ * Converts a value to a Date object. Returns the value as-is if it's already a Date.
1196
+ *
1197
+ * @param {Date|number|string} value - Value to convert
1198
+ * @returns {Date}
1199
+ */
1015
1200
  function toDate(value) {
1016
1201
  if (value instanceof Date) return value;
1017
1202
  return new Date(value);
1018
1203
  }
1019
1204
 
1205
+ /**
1206
+ * Returns true if two date values fall on the same calendar day (year, month, day).
1207
+ *
1208
+ * @param {Date|number|string} date1 - First date
1209
+ * @param {Date|number|string} date2 - Second date
1210
+ * @returns {boolean}
1211
+ */
1020
1212
  function isSameDay(date1, date2) {
1021
1213
  const d1 = toDate(date1);
1022
1214
  const d2 = toDate(date2);
@@ -1025,72 +1217,165 @@ var NativeDocument = (function (exports) {
1025
1217
  d1.getDate() === d2.getDate();
1026
1218
  }
1027
1219
 
1220
+ /**
1221
+ * Returns the total number of seconds elapsed since midnight for the given date's time component.
1222
+ * Used internally for time range comparisons.
1223
+ *
1224
+ * @param {Date|number|string} date - Date value to extract time from
1225
+ * @returns {number} Seconds since midnight (0–86399)
1226
+ */
1028
1227
  function getSecondsOfDay(date) {
1029
1228
  const d = toDate(date);
1030
1229
  return (d.getHours() * 3600) + (d.getMinutes() * 60) + d.getSeconds();
1031
1230
  }
1032
1231
 
1232
+ /**
1233
+ * Creates a FilterResult from a single observable or static value and a comparison callback.
1234
+ * If the value is an observable, it is registered as a dependency so the filter reacts to changes.
1235
+ *
1236
+ * @param {*|ObservableItem} observableOrValue - Observable or static value used as the comparison target
1237
+ * @param {(value: *, target: *) => boolean} callbackFn - Filter function receiving (item value, target value)
1238
+ * @returns {{ dependencies: ObservableItem|null, callback: (value: *) => boolean }} FilterResult
1239
+ */
1033
1240
  function createFilter(observableOrValue, callbackFn){
1034
1241
  const isObservable = Validator.isObservable(observableOrValue);
1035
1242
 
1036
1243
  return {
1037
1244
  dependencies: isObservable ? observableOrValue : null,
1038
- callback: (value) => callbackFn(value, isObservable ? observableOrValue.val() : observableOrValue)
1245
+ callback: (value) => callbackFn(value, isObservable ? observableOrValue.val() : observableOrValue),
1039
1246
  };
1040
1247
  }
1041
1248
 
1249
+ /**
1250
+ * Creates a FilterResult from multiple observable or static sources and a multi-value comparison callback.
1251
+ * All observable sources are registered as dependencies.
1252
+ *
1253
+ * @param {Array<*|ObservableItem>} sources - Array of observables or static values
1254
+ * @param {(value: *, targets: any[]) => boolean} callbackFn - Filter function receiving (item value, array of resolved source values)
1255
+ * @returns {{ dependencies: ObservableItem[]|null, callback: (value: *) => boolean }} FilterResult
1256
+ */
1042
1257
  function createMultiSourceFilter(sources, callbackFn){
1043
1258
  const observables = sources.filter(Validator.isObservable);
1044
1259
 
1045
1260
  const getValues = () => sources.map(src =>
1046
- Validator.isObservable(src) ? src.val() : src
1261
+ Validator.isObservable(src) ? src.val() : src,
1047
1262
  );
1048
1263
 
1049
1264
  return {
1050
1265
  dependencies: observables.length > 0 ? observables : null,
1051
- callback: (value) => callbackFn(value, getValues())
1266
+ callback: (value) => callbackFn(value, getValues()),
1052
1267
  };
1053
1268
  }
1054
1269
 
1270
+ /**
1271
+ * Creates a filter that passes values strictly equal to the target.
1272
+ *
1273
+ * @param {*|ObservableItem} observableOrValue - Static value or observable to compare against
1274
+ * @returns {FilterResult}
1275
+ * @example
1276
+ * const statusFilter = equals('active');
1277
+ * users.where({ status: statusFilter });
1278
+ */
1055
1279
  function equals(observableOrValue){
1056
1280
  return createFilter(observableOrValue, (value, target) => value === target);
1057
1281
  }
1058
1282
 
1283
+ /**
1284
+ * Creates a filter that passes values not strictly equal to the target.
1285
+ *
1286
+ * @param {*|ObservableItem} observableOrValue - Static value or observable
1287
+ * @returns {FilterResult}
1288
+ */
1059
1289
  function notEquals(observableOrValue){
1060
1290
  return createFilter(observableOrValue, (value, target) => value !== target);
1061
1291
  }
1062
1292
 
1293
+ /**
1294
+ * Creates a filter that passes values greater than the target.
1295
+ *
1296
+ * @param {number|ObservableItem<number>} observableOrValue - Threshold value or observable
1297
+ * @returns {FilterResult}
1298
+ */
1063
1299
  function greaterThan(observableOrValue){
1064
1300
  return createFilter(observableOrValue, (value, target) => value > target);
1065
1301
  }
1066
1302
 
1303
+ /**
1304
+ * Creates a filter that passes values greater than or equal to the target.
1305
+ *
1306
+ * @param {number|ObservableItem<number>} observableOrValue - Threshold value or observable
1307
+ * @returns {FilterResult}
1308
+ */
1067
1309
  function greaterThanOrEqual(observableOrValue){
1068
1310
  return createFilter(observableOrValue, (value, target) => value >= target);
1069
1311
  }
1070
1312
 
1313
+ /**
1314
+ * Creates a filter that passes values less than the target.
1315
+ *
1316
+ * @param {number|ObservableItem<number>} observableOrValue - Threshold value or observable
1317
+ * @returns {FilterResult}
1318
+ */
1071
1319
  function lessThan(observableOrValue){
1072
1320
  return createFilter(observableOrValue, (value, target) => value < target);
1073
1321
  }
1074
1322
 
1323
+ /**
1324
+ * Creates a filter that passes values less than or equal to the target.
1325
+ *
1326
+ * @param {number|ObservableItem<number>} observableOrValue - Threshold value or observable
1327
+ * @returns {FilterResult}
1328
+ */
1075
1329
  function lessThanOrEqual(observableOrValue){
1076
1330
  return createFilter(observableOrValue, (value, target) => value <= target);
1077
1331
  }
1078
1332
 
1333
+ /**
1334
+ * Creates a filter that passes values between min and max (inclusive).
1335
+ * Both min and max can be static values or observables.
1336
+ *
1337
+ * @param {number|ObservableItem<number>} minObservableOrValue - Lower bound
1338
+ * @param {number|ObservableItem<number>} maxObservableOrValue - Upper bound
1339
+ * @returns {FilterResult}
1340
+ * @example
1341
+ * items.where({ age: between(18, 65) });
1342
+ */
1079
1343
  function between(minObservableOrValue, maxObservableOrValue){
1080
1344
  return createMultiSourceFilter(
1081
1345
  [minObservableOrValue, maxObservableOrValue],
1082
- (value, [min, max]) => value >= min && value <= max
1346
+ (value, [min, max]) => value >= min && value <= max,
1083
1347
  );
1084
1348
  }
1085
1349
 
1350
+ /**
1351
+ * Creates a filter that passes values included in the given array.
1352
+ *
1353
+ * @param {Array|ObservableItem<Array>} observableOrArray - Array to check membership in
1354
+ * @returns {FilterResult}
1355
+ * @example
1356
+ * items.where({ role: inArray(['admin', 'editor']) });
1357
+ */
1086
1358
  function inArray(observableOrArray){
1087
1359
  return createFilter(observableOrArray, (value, arr) => arr.includes(value));
1088
1360
  }
1089
1361
 
1362
+ /**
1363
+ * Creates a filter that passes values not included in the given array.
1364
+ *
1365
+ * @param {Array|ObservableItem<Array>} observableOrArray - Array to check exclusion from
1366
+ * @returns {FilterResult}
1367
+ */
1090
1368
  function notIn(observableOrArray){
1091
1369
  return createFilter(observableOrArray, (value, arr) => !arr.includes(value));
1092
1370
  }
1093
1371
 
1372
+ /**
1373
+ * Creates a filter that passes when the value is empty (null, undefined, empty string, or empty array).
1374
+ * Pass false as the argument to filter for non-empty values instead.
1375
+ *
1376
+ * @param {boolean|ObservableItem<boolean>} [observableOrValue=true] - If true, filters empty values; if false, filters non-empty
1377
+ * @returns {FilterResult}
1378
+ */
1094
1379
  function isEmpty(observableOrValue = true){
1095
1380
  return createFilter(observableOrValue, (value, shouldBeEmpty) => {
1096
1381
  const isActuallyEmpty = !value || value === '' ||
@@ -1100,6 +1385,13 @@ var NativeDocument = (function (exports) {
1100
1385
  });
1101
1386
  }
1102
1387
 
1388
+ /**
1389
+ * Creates a filter that passes when the value is not empty.
1390
+ * Pass false as the argument to filter for empty values instead.
1391
+ *
1392
+ * @param {boolean|ObservableItem<boolean>} [observableOrValue=true] - If true, filters non-empty values
1393
+ * @returns {FilterResult}
1394
+ */
1103
1395
  function isNotEmpty(observableOrValue = true){
1104
1396
  return createFilter(observableOrValue, (value, shouldBeNotEmpty) => {
1105
1397
  const isActuallyNotEmpty = !!value && value !== '' &&
@@ -1109,6 +1401,18 @@ var NativeDocument = (function (exports) {
1109
1401
  });
1110
1402
  }
1111
1403
 
1404
+ /**
1405
+ * Creates a filter that tests the value against a pattern (string or regex).
1406
+ *
1407
+ * @param {string|RegExp|ObservableItem} patternObservableOrValue - Pattern to match against
1408
+ * @param {boolean|ObservableItem<boolean>} [asRegexObservableOrValue=true] - If true, treat pattern as a regex; if false, use case-insensitive substring match
1409
+ * @param {string|ObservableItem<string>} [flagsObservableOrValue=''] - Regex flags (e.g. 'i', 'g')
1410
+ * @returns {FilterResult}
1411
+ * @example
1412
+ * const search = Observable('john');
1413
+ * users.where({ name: match(search) }); // regex match, reactive
1414
+ * users.where({ name: match('john', false) }); // case-insensitive substring
1415
+ */
1112
1416
  function match(patternObservableOrValue, asRegexObservableOrValue = true, flagsObservableOrValue = ''){
1113
1417
  return createMultiSourceFilter(
1114
1418
  [patternObservableOrValue, asRegexObservableOrValue, flagsObservableOrValue],
@@ -1129,10 +1433,19 @@ var NativeDocument = (function (exports) {
1129
1433
  return String(value).toLowerCase().includes(String(pattern).toLowerCase());
1130
1434
  }
1131
1435
  return String(value).includes(String(pattern));
1132
- }
1436
+ },
1133
1437
  );
1134
1438
  }
1135
1439
 
1440
+ /**
1441
+ * Combines multiple filters with AND logic — all filters must pass.
1442
+ * Merges dependencies from all child filters automatically.
1443
+ *
1444
+ * @param {...FilterResult} filters - Filters to combine
1445
+ * @returns {FilterResult}
1446
+ * @example
1447
+ * items.where({ _: and(greaterThan(18), lessThan(65)) });
1448
+ */
1136
1449
  function and(...filters){
1137
1450
  const dependencies = filters
1138
1451
  .flatMap(f => f.dependencies ? (Array.isArray(f.dependencies) ? f.dependencies : [f.dependencies]) : [])
@@ -1140,10 +1453,19 @@ var NativeDocument = (function (exports) {
1140
1453
 
1141
1454
  return {
1142
1455
  dependencies: dependencies.length > 0 ? dependencies : null,
1143
- callback: (value) => filters.every(f => f.callback(value))
1456
+ callback: (value) => filters.every(f => f.callback(value)),
1144
1457
  };
1145
1458
  }
1146
1459
 
1460
+ /**
1461
+ * Combines multiple filters with OR logic — at least one filter must pass.
1462
+ * Merges dependencies from all child filters automatically.
1463
+ *
1464
+ * @param {...FilterResult} filters - Filters to combine
1465
+ * @returns {FilterResult}
1466
+ * @example
1467
+ * items.where({ _: or(equals('admin'), equals('editor')) });
1468
+ */
1147
1469
  function or(...filters){
1148
1470
  const dependencies = filters
1149
1471
  .flatMap(f => f.dependencies ? (Array.isArray(f.dependencies) ? f.dependencies : [f.dependencies]) : [])
@@ -1151,17 +1473,36 @@ var NativeDocument = (function (exports) {
1151
1473
 
1152
1474
  return {
1153
1475
  dependencies: dependencies.length > 0 ? dependencies : null,
1154
- callback: (value) => filters.some(f => f.callback(value))
1476
+ callback: (value) => filters.some(f => f.callback(value)),
1155
1477
  };
1156
1478
  }
1157
1479
 
1480
+ /**
1481
+ * Negates a filter — passes when the given filter does not pass.
1482
+ *
1483
+ * @param {FilterResult} filter - Filter to negate
1484
+ * @returns {FilterResult}
1485
+ * @example
1486
+ * items.where({ status: not(equals('deleted')) });
1487
+ */
1158
1488
  function not(filter){
1159
1489
  return {
1160
1490
  dependencies: filter.dependencies,
1161
- callback: (value) => !filter.callback(value)
1491
+ callback: (value) => !filter.callback(value),
1162
1492
  };
1163
1493
  }
1164
1494
 
1495
+ /**
1496
+ * Creates a custom filter from a callback and a list of observable dependencies.
1497
+ * The callback receives the item value followed by the current values of all observables.
1498
+ *
1499
+ * @param {(value: *, ...depValues: any[]) => boolean} callbackFn - Filter function
1500
+ * @param {...ObservableItem} observables - Observable dependencies passed as extra arguments to callback
1501
+ * @returns {FilterResult}
1502
+ * @example
1503
+ * const minAge = Observable(18);
1504
+ * items.where({ age: custom((age, min) => age >= min, minAge) });
1505
+ */
1165
1506
  function custom(callbackFn, ...observables){
1166
1507
  const dependencies = observables.filter(Validator.isObservable);
1167
1508
 
@@ -1169,10 +1510,10 @@ var NativeDocument = (function (exports) {
1169
1510
  dependencies: dependencies.length > 0 ? dependencies : null,
1170
1511
  callback: (value) => {
1171
1512
  const values = observables.map(o =>
1172
- Validator.isObservable(o) ? o.val() : o
1513
+ Validator.isObservable(o) ? o.val() : o,
1173
1514
  );
1174
1515
  return callbackFn(value, ...values);
1175
- }
1516
+ },
1176
1517
  };
1177
1518
  }
1178
1519
 
@@ -1185,6 +1526,16 @@ var NativeDocument = (function (exports) {
1185
1526
  const all = and;
1186
1527
  const any = or;
1187
1528
 
1529
+ /**
1530
+ * Creates a filter that passes when the date value is on the same day as the target date.
1531
+ * Accepts Date objects, timestamps, or ISO strings.
1532
+ *
1533
+ * @param {Date|number|string|ObservableItem} observableOrValue - Target date to compare against
1534
+ * @returns {FilterResult}
1535
+ * @example
1536
+ * const today = new Date();
1537
+ * events.where({ date: dateEquals(today) });
1538
+ */
1188
1539
  const dateEquals = (observableOrValue) => {
1189
1540
  return createFilter(observableOrValue, (value, target) => {
1190
1541
  if (!value || !target) return false;
@@ -1192,6 +1543,12 @@ var NativeDocument = (function (exports) {
1192
1543
  });
1193
1544
  };
1194
1545
 
1546
+ /**
1547
+ * Creates a filter that passes when the date value is strictly before the target date (day comparison).
1548
+ *
1549
+ * @param {Date|number|string|ObservableItem} observableOrValue - Target date
1550
+ * @returns {FilterResult}
1551
+ */
1195
1552
  const dateBefore = (observableOrValue) => {
1196
1553
  return createFilter(observableOrValue, (value, target) => {
1197
1554
  if (!value || !target) return false;
@@ -1199,6 +1556,12 @@ var NativeDocument = (function (exports) {
1199
1556
  });
1200
1557
  };
1201
1558
 
1559
+ /**
1560
+ * Creates a filter that passes when the date value is strictly after the target date (day comparison).
1561
+ *
1562
+ * @param {Date|number|string|ObservableItem} observableOrValue - Target date
1563
+ * @returns {FilterResult}
1564
+ */
1202
1565
  const dateAfter = (observableOrValue) => {
1203
1566
  return createFilter(observableOrValue, (value, target) => {
1204
1567
  if (!value || !target) return false;
@@ -1206,6 +1569,15 @@ var NativeDocument = (function (exports) {
1206
1569
  });
1207
1570
  };
1208
1571
 
1572
+ /**
1573
+ * Creates a filter that passes when the date value falls within the given date range (inclusive, day comparison).
1574
+ *
1575
+ * @param {Date|number|string|ObservableItem} startObservableOrValue - Start of the range
1576
+ * @param {Date|number|string|ObservableItem} endObservableOrValue - End of the range
1577
+ * @returns {FilterResult}
1578
+ * @example
1579
+ * events.where({ date: dateBetween(startDate, endDate) });
1580
+ */
1209
1581
  const dateBetween = (startObservableOrValue, endObservableOrValue) => {
1210
1582
  return createMultiSourceFilter(
1211
1583
  [startObservableOrValue, endObservableOrValue],
@@ -1213,10 +1585,16 @@ var NativeDocument = (function (exports) {
1213
1585
  if (!value || !start || !end) return false;
1214
1586
  const date = toDate(value);
1215
1587
  return date >= toDate(start) && date <= toDate(end);
1216
- }
1588
+ },
1217
1589
  );
1218
1590
  };
1219
1591
 
1592
+ /**
1593
+ * Creates a filter that passes when the time component (HH:MM:SS) equals the target time.
1594
+ *
1595
+ * @param {Date|number|string|ObservableItem} observableOrValue - Target time
1596
+ * @returns {FilterResult}
1597
+ */
1220
1598
  const timeEquals = (observableOrValue) => {
1221
1599
  return createFilter(observableOrValue, (value, target) => {
1222
1600
  if (!value || !target) return false;
@@ -1228,6 +1606,12 @@ var NativeDocument = (function (exports) {
1228
1606
  });
1229
1607
  };
1230
1608
 
1609
+ /**
1610
+ * Creates a filter that passes when the time component is strictly after the target time.
1611
+ *
1612
+ * @param {Date|number|string|ObservableItem} observableOrValue - Target time
1613
+ * @returns {FilterResult}
1614
+ */
1231
1615
  const timeAfter = (observableOrValue) => {
1232
1616
  return createFilter(observableOrValue, (value, target) => {
1233
1617
  if (!value || !target) return false;
@@ -1235,6 +1619,12 @@ var NativeDocument = (function (exports) {
1235
1619
  });
1236
1620
  };
1237
1621
 
1622
+ /**
1623
+ * Creates a filter that passes when the time component is strictly before the target time.
1624
+ *
1625
+ * @param {Date|number|string|ObservableItem} observableOrValue - Target time
1626
+ * @returns {FilterResult}
1627
+ */
1238
1628
  const timeBefore = (observableOrValue) => {
1239
1629
  return createFilter(observableOrValue, (value, target) => {
1240
1630
  if (!value || !target) return false;
@@ -1242,16 +1632,29 @@ var NativeDocument = (function (exports) {
1242
1632
  });
1243
1633
  };
1244
1634
 
1635
+ /**
1636
+ * Creates a filter that passes when the time component falls within the given time range (inclusive).
1637
+ *
1638
+ * @param {Date|number|string|ObservableItem} startObservableOrValue - Start time
1639
+ * @param {Date|number|string|ObservableItem} endObservableOrValue - End time
1640
+ * @returns {FilterResult}
1641
+ */
1245
1642
  const timeBetween = (startObservableOrValue, endObservableOrValue) => {
1246
1643
  return createMultiSourceFilter([startObservableOrValue, endObservableOrValue],
1247
1644
  (value, [start, end]) => {
1248
1645
  if (!value || !start || !end) return false;
1249
1646
  const date = getSecondsOfDay(value);
1250
1647
  return date >= getSecondsOfDay(start) && date <= getSecondsOfDay(end);
1251
- }
1648
+ },
1252
1649
  );
1253
1650
  };
1254
1651
 
1652
+ /**
1653
+ * Creates a filter that passes when the full datetime (date + time) equals the target exactly.
1654
+ *
1655
+ * @param {Date|number|string|ObservableItem} observableOrValue - Target datetime
1656
+ * @returns {FilterResult}
1657
+ */
1255
1658
  const dateTimeEquals = (observableOrValue) => {
1256
1659
  return createFilter(observableOrValue, (value, target) => {
1257
1660
  if (!value || !target) return false;
@@ -1259,6 +1662,12 @@ var NativeDocument = (function (exports) {
1259
1662
  });
1260
1663
  };
1261
1664
 
1665
+ /**
1666
+ * Creates a filter that passes when the full datetime is strictly after the target.
1667
+ *
1668
+ * @param {Date|number|string|ObservableItem} observableOrValue - Target datetime
1669
+ * @returns {FilterResult}
1670
+ */
1262
1671
  const dateTimeAfter = (observableOrValue) => {
1263
1672
  return createFilter(observableOrValue, (value, target) => {
1264
1673
  if (!value || !target) return false;
@@ -1266,6 +1675,12 @@ var NativeDocument = (function (exports) {
1266
1675
  });
1267
1676
  };
1268
1677
 
1678
+ /**
1679
+ * Creates a filter that passes when the full datetime is strictly before the target.
1680
+ *
1681
+ * @param {Date|number|string|ObservableItem} observableOrValue - Target datetime
1682
+ * @returns {FilterResult}
1683
+ */
1269
1684
  const dateTimeBefore = (observableOrValue) => {
1270
1685
  return createFilter(observableOrValue, (value, target) => {
1271
1686
  if (!value || !target) return false;
@@ -1273,6 +1688,13 @@ var NativeDocument = (function (exports) {
1273
1688
  });
1274
1689
  };
1275
1690
 
1691
+ /**
1692
+ * Creates a filter that passes when the full datetime falls within the given range (inclusive).
1693
+ *
1694
+ * @param {Date|number|string|ObservableItem} startObservableOrValue - Start of the range
1695
+ * @param {Date|number|string|ObservableItem} endObservableOrValue - End of the range
1696
+ * @returns {FilterResult}
1697
+ */
1276
1698
  const dateTimeBetween = (startObservableOrValue, endObservableOrValue) => {
1277
1699
  return createMultiSourceFilter([startObservableOrValue, endObservableOrValue], (value, [start, end]) => {
1278
1700
  if (!value || !start || !end) return false;
@@ -1281,6 +1703,19 @@ var NativeDocument = (function (exports) {
1281
1703
  });
1282
1704
  };
1283
1705
 
1706
+ /**
1707
+ * Creates a filter that passes when the value includes the given query string.
1708
+ * Case-insensitive by default.
1709
+ * Alias: contains
1710
+ *
1711
+ * @param {string|ObservableItem<string>} observableOrValue - Substring to search for
1712
+ * @param {boolean} [caseSensitive=false] - If true, comparison is case-sensitive
1713
+ * @returns {FilterResult}
1714
+ * @example
1715
+ * const search = Observable('john');
1716
+ * users.where({ name: includes(search) }); // reactive, case-insensitive
1717
+ * users.where({ name: includes('John', true) }); // case-sensitive
1718
+ */
1284
1719
  function includes(observableOrValue, caseSensitive = false){
1285
1720
  return createFilter(observableOrValue, (value, query) => {
1286
1721
  if (!value) return false;
@@ -1294,6 +1729,16 @@ var NativeDocument = (function (exports) {
1294
1729
 
1295
1730
  const contains = includes;
1296
1731
 
1732
+ /**
1733
+ * Creates a filter that passes when the value starts with the given query string.
1734
+ * Case-insensitive by default.
1735
+ *
1736
+ * @param {string|ObservableItem<string>} observableOrValue - Prefix to search for
1737
+ * @param {boolean} [caseSensitive=false] - If true, comparison is case-sensitive
1738
+ * @returns {FilterResult}
1739
+ * @example
1740
+ * users.where({ name: startsWith('Jo') });
1741
+ */
1297
1742
  function startsWith(observableOrValue, caseSensitive = false){
1298
1743
  return createFilter(observableOrValue, (value, query) => {
1299
1744
  if (!query) return true;
@@ -1304,6 +1749,16 @@ var NativeDocument = (function (exports) {
1304
1749
  });
1305
1750
  }
1306
1751
 
1752
+ /**
1753
+ * Creates a filter that passes when the value ends with the given query string.
1754
+ * Case-insensitive by default.
1755
+ *
1756
+ * @param {string|ObservableItem<string>} observableOrValue - Suffix to search for
1757
+ * @param {boolean} [caseSensitive=false] - If true, comparison is case-sensitive
1758
+ * @returns {FilterResult}
1759
+ * @example
1760
+ * files.where({ name: endsWith('.js') });
1761
+ */
1307
1762
  function endsWith(observableOrValue, caseSensitive = false){
1308
1763
  return createFilter(observableOrValue, (value, query) => {
1309
1764
  if (!query) return true;
@@ -1367,10 +1822,18 @@ var NativeDocument = (function (exports) {
1367
1822
  const noMutationMethods = ['map', 'forEach', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex', 'concat', 'includes', 'indexOf'];
1368
1823
 
1369
1824
  /**
1825
+ * Reactive array container extending ObservableItem.
1826
+ * Wraps a native array and triggers reactivity on mutations (push, pop, splice, etc.).
1827
+ * Use Observable.array() rather than instantiating directly.
1370
1828
  *
1371
- * @param target
1372
- * @param {{propagation: boolean, deep: boolean, reset: boolean}|null} configs
1373
1829
  * @constructor
1830
+ * @param {Array} target - Initial array value
1831
+ * @param {{ propagation?: boolean, deep?: boolean, reset?: boolean }|null} [configs=null] - Configuration
1832
+ * @param {boolean} [configs.deep] - If false, nested arrays are not wrapped in ObservableArray
1833
+ * @param {boolean} [configs.reset] - If true, stores initial value for .reset()
1834
+ * @example
1835
+ * const items = Observable.array([1, 2, 3]);
1836
+ * items.push(4); // triggers reactivity
1374
1837
  */
1375
1838
  const ObservableArray = function (target, configs = null) {
1376
1839
  if(!Array.isArray(target)) {
@@ -1391,7 +1854,7 @@ var NativeDocument = (function (exports) {
1391
1854
  Object.defineProperty(ObservableArray.prototype, 'length', {
1392
1855
  get() {
1393
1856
  return this.$currentValue.length;
1394
- }
1857
+ },
1395
1858
  });
1396
1859
 
1397
1860
 
@@ -1411,7 +1874,7 @@ var NativeDocument = (function (exports) {
1411
1874
  const result = this.$currentValue[method].apply(this.$currentValue, argsToUse);
1412
1875
  this.trigger({ action: method, args: argsToUse, result });
1413
1876
  return result;
1414
- })
1877
+ });
1415
1878
  };
1416
1879
  });
1417
1880
 
@@ -1524,6 +1987,17 @@ var NativeDocument = (function (exports) {
1524
1987
  return true;
1525
1988
  };
1526
1989
 
1990
+ /**
1991
+ * Swaps two items by reference (not by index).
1992
+ * Finds indices of both items and delegates to .swap().
1993
+ *
1994
+ * @param {*} itemA - First item (by reference)
1995
+ * @param {*} itemB - Second item (by reference)
1996
+ * @returns {boolean} True if swap was successful
1997
+ * @example
1998
+ * const items = Observable.array(['a', 'b', 'c']);
1999
+ * items.swapItems('a', 'c'); // ['c', 'b', 'a']
2000
+ */
1527
2001
  ObservableArray.prototype.swapItems = function(itemA, itemB) {
1528
2002
  const indexA = this.$currentValue.indexOf(itemA);
1529
2003
  const indexB = this.$currentValue.indexOf(itemB);
@@ -1531,6 +2005,16 @@ var NativeDocument = (function (exports) {
1531
2005
  return this.swap(indexA, indexB);
1532
2006
  };
1533
2007
 
2008
+ /**
2009
+ * Inserts an item immediately after the target item in the array.
2010
+ *
2011
+ * @param {*} data - Item to insert
2012
+ * @param {*} target - Existing item after which data is inserted
2013
+ * @returns {Array} Result of the underlying splice call
2014
+ * @example
2015
+ * const items = Observable.array(['a', 'c']);
2016
+ * items.insertAfter('b', 'a'); // ['a', 'b', 'c']
2017
+ */
1534
2018
  ObservableArray.prototype.insertAfter = function(data, target) {
1535
2019
  const targetIndex = this.$currentValue.indexOf(target);
1536
2020
  return this.splice(targetIndex + 1, 0, data);
@@ -1574,24 +2058,28 @@ var NativeDocument = (function (exports) {
1574
2058
  return this.remove(indexOfItem);
1575
2059
  };
1576
2060
 
2061
+
1577
2062
  /**
1578
- * Checks if the array is empty.
2063
+ * Checks if the array has no elements.
2064
+ * Semantic alias for length === 0. Different from .clear() which empties the array.
1579
2065
  *
1580
- * @returns {boolean} True if array has no elements
2066
+ * @returns {boolean} True if the array contains no elements
1581
2067
  * @example
1582
2068
  * const items = Observable.array([]);
1583
- * items.isEmpty(); // true
2069
+ * items.empty(); // true
1584
2070
  */
1585
2071
  ObservableArray.prototype.empty = function() {
1586
2072
  return this.$currentValue.length === 0;
1587
2073
  };
1588
2074
 
2075
+
1589
2076
  /**
1590
- * Triggers a populate operation with the current array, iteration count, and callback.
1591
- * Used internally for rendering optimizations.
2077
+ * Triggers a 'populate' operation used internally by ForEachArray for batch rendering.
2078
+ * Not intended for direct use in application code.
1592
2079
  *
1593
- * @param {number} iteration - Iteration count for rendering
1594
- * @param {Function} callback - Callback function for rendering items
2080
+ * @internal
2081
+ * @param {number} iteration - Number of items to render
2082
+ * @param {Function} callback - Render callback for each item
1595
2083
  */
1596
2084
  ObservableArray.prototype.populateAndRender = function(iteration, callback) {
1597
2085
  this.trigger({ action: 'populate', args: [this.$currentValue, iteration, callback] });
@@ -1681,8 +2169,8 @@ var NativeDocument = (function (exports) {
1681
2169
  return this.where({
1682
2170
  _: {
1683
2171
  dependencies: filter.dependencies,
1684
- callback: (item) => fields.some(field => filter.callback(item[field]))
1685
- }
2172
+ callback: (item) => fields.some(field => filter.callback(item[field])),
2173
+ },
1686
2174
  });
1687
2175
  };
1688
2176
 
@@ -1704,11 +2192,23 @@ var NativeDocument = (function (exports) {
1704
2192
  return this.where({
1705
2193
  _: {
1706
2194
  dependencies: filter.dependencies,
1707
- callback: (item) => fields.every(field => filter.callback(item[field]))
1708
- }
2195
+ callback: (item) => fields.every(field => filter.callback(item[field])),
2196
+ },
1709
2197
  });
1710
2198
  };
1711
2199
 
2200
+ /**
2201
+ * Subscribes deeply to all observable items within the array.
2202
+ * Automatically binds and unbinds listeners as items are added or removed.
2203
+ * Returns an unsubscribe function.
2204
+ *
2205
+ * @param {(value: Array) => void} callback - Called whenever any nested observable changes
2206
+ * @returns {() => void} Unsubscribe function
2207
+ * @example
2208
+ * const users = Observable.array([Observable({ name: 'John' })]);
2209
+ * const unsub = users.deepSubscribe((items) => console.log('changed', items));
2210
+ * unsub(); // stop listening
2211
+ */
1712
2212
  ObservableArray.prototype.deepSubscribe = function(callback) {
1713
2213
  const updatedValue = nextTick(() => callback(this.val()));
1714
2214
  const $listeners = new WeakMap();
@@ -1740,29 +2240,29 @@ var NativeDocument = (function (exports) {
1740
2240
 
1741
2241
  this.subscribe((items, _, operations) => {
1742
2242
  switch (operations?.action) {
1743
- case 'push':
1744
- case 'unshift':
1745
- operations.args.forEach(bindItem);
1746
- break;
2243
+ case 'push':
2244
+ case 'unshift':
2245
+ operations.args.forEach(bindItem);
2246
+ break;
1747
2247
 
1748
- case 'splice': {
1749
- const [start, deleteCount, ...newItems] = operations.args;
1750
- operations.result?.forEach(unbindItem);
1751
- newItems.forEach(bindItem);
1752
- break;
1753
- }
2248
+ case 'splice': {
2249
+ const [start, deleteCount, ...newItems] = operations.args;
2250
+ operations.result?.forEach(unbindItem);
2251
+ newItems.forEach(bindItem);
2252
+ break;
2253
+ }
1754
2254
 
1755
- case 'remove':
1756
- unbindItem(operations.result);
1757
- break;
2255
+ case 'remove':
2256
+ unbindItem(operations.result);
2257
+ break;
1758
2258
 
1759
- case 'merge':
1760
- operations.args.forEach(bindItem);
1761
- break;
2259
+ case 'merge':
2260
+ operations.args.forEach(bindItem);
2261
+ break;
1762
2262
 
1763
- case 'clear':
1764
- this.$currentValue.forEach(unbindItem);
1765
- break;
2263
+ case 'clear':
2264
+ this.$currentValue.forEach(unbindItem);
2265
+ break;
1766
2266
  }
1767
2267
  });
1768
2268
 
@@ -1771,7 +2271,19 @@ var NativeDocument = (function (exports) {
1771
2271
  };
1772
2272
  };
1773
2273
 
1774
-
2274
+ /**
2275
+ * Keeps this array in sync with another ObservableArray.
2276
+ * All mutations are mirrored to the target array in real time.
2277
+ *
2278
+ * @param {ObservableArray} targetObservable - The array to sync into
2279
+ * @returns {() => void} Unsubscribe function to stop syncing
2280
+ * @example
2281
+ * const source = Observable.array([1, 2, 3]);
2282
+ * const target = Observable.array([]);
2283
+ * const unsync = source.sync(target);
2284
+ * source.push(4); // target is now [1, 2, 3, 4]
2285
+ * unsync();
2286
+ */
1775
2287
  ObservableArray.prototype.sync = function(targetObservable) {
1776
2288
  if (!targetObservable || !targetObservable.__$isObservableArray) {
1777
2289
  throw new NativeDocumentError('ObservableArray.sync : target must be an ObservableArray');
@@ -1793,30 +2305,70 @@ var NativeDocument = (function (exports) {
1793
2305
  return () => this.unsubscribe(sync);
1794
2306
  };
1795
2307
 
2308
+ /**
2309
+ * Creates a new ObservableArray with a shallow clone of the current array.
2310
+ *
2311
+ * @returns {ObservableArray} New independent ObservableArray with same items
2312
+ */
1796
2313
  ObservableArray.prototype.clone = function() {
1797
2314
  return new ObservableArray(this.resolve());
1798
2315
  };
1799
2316
 
2317
+ /**
2318
+ * Returns a derived ObservableChecker that emits true when the array has at least one item.
2319
+ *
2320
+ * @returns {ObservableChecker<boolean>}
2321
+ * @example
2322
+ * const items = Observable.array([]);
2323
+ * items.isNotEmpty().val(); // false
2324
+ * items.push(1);
2325
+ * items.isNotEmpty().val(); // true
2326
+ */
1800
2327
  ObservableArray.prototype.isNotEmpty = function () {
1801
2328
  return this.is((x) => x.length > 0);
1802
2329
  };
1803
2330
 
2331
+ /**
2332
+ * Reactive object container extending ObservableItem.
2333
+ * Each property of the target object becomes an individual ObservableItem (or ObservableArray/ObservableObject for nested structures).
2334
+ * Use Observable.object() or Observable.init() rather than instantiating directly.
2335
+ *
2336
+ * @constructor
2337
+ * @param {Record<string, *>} target - Plain object to make reactive
2338
+ * @param {{ deep?: boolean, reset?: boolean, propagation?: boolean }|null} [configs] - Configuration
2339
+ * @param {boolean} [configs.deep] - If false, nested objects and arrays are not wrapped recursively (default: true)
2340
+ * @example
2341
+ * const user = Observable.object({ name: 'John', age: 25 });
2342
+ * user.name.$value = 'Jane'; // triggers reactivity on name only
2343
+ */
1804
2344
  const ObservableObject = function(target, configs) {
1805
2345
  ObservableItem.call(this, target);
1806
2346
  this.$observables = {};
1807
2347
  this.configs = configs;
1808
2348
 
1809
- this.$load(target);
1810
-
1811
- for(const name in target) {
1812
- if(!Object.hasOwn(this, name)) {
1813
- Object.defineProperty(this, name, {
1814
- get: () => this.$observables[name],
1815
- set: (value) => this.$observables[name].set(value)
2349
+ for(const key in target) {
2350
+ if(!Object.hasOwn(this, key)) {
2351
+ Object.defineProperty(this, key, {
2352
+ get: () => this.$observables[key],
2353
+ set: (value) => {
2354
+ this.$observables[key].set(value);
2355
+ },
2356
+ configurable: true,
2357
+ enumerable: true,
1816
2358
  });
1817
2359
  }
1818
2360
  }
1819
2361
 
2362
+ this.$load(target);
2363
+
2364
+ Object.defineProperty(this, '$currentValue', {
2365
+ get: function() {
2366
+ return this.val();
2367
+ },
2368
+ set(value) {
2369
+ this.set(value);
2370
+ }
2371
+ });
1820
2372
  };
1821
2373
 
1822
2374
  ObservableObject.prototype = Object.create(ObservableItem.prototype);
@@ -1827,12 +2379,19 @@ var NativeDocument = (function (exports) {
1827
2379
  },
1828
2380
  set(value) {
1829
2381
  this.set(value);
1830
- }
2382
+ },
1831
2383
  });
1832
2384
 
1833
2385
  ObservableObject.prototype.__$isObservableObject = true;
1834
2386
  ObservableObject.prototype.__isProxy__ = true;
1835
2387
 
2388
+ /**
2389
+ * Initialises (or reinitialize) the internal observables map from a plain object.
2390
+ * Called automatically in the constructor.
2391
+ *
2392
+ * @internal
2393
+ * @param {Record<string, *>} initialValue - Object whose properties are turned into observables
2394
+ */
1836
2395
  ObservableObject.prototype.$load = function(initialValue) {
1837
2396
  const configs = this.configs;
1838
2397
  for(const key in initialValue) {
@@ -1854,34 +2413,39 @@ var NativeDocument = (function (exports) {
1854
2413
  this.$observables[key] = new ObservableArray(itemValue, configs);
1855
2414
  continue;
1856
2415
  }
1857
- if(Validator.isObservable(itemValue) || Validator.isProxy(itemValue)) {
2416
+ if(itemValue?.__$Observable) {
1858
2417
  this.$observables[key] = itemValue;
1859
2418
  continue;
1860
2419
  }
1861
- this.$observables[key] = (typeof itemValue === 'object') ? new ObservableObject(itemValue, configs) : new ObservableItem(itemValue, configs);
2420
+ this.$observables[key] = (Validator.isJson(itemValue)) ? new ObservableObject(itemValue, configs) : new ObservableItem(itemValue, configs);
1862
2421
  }
1863
2422
  };
1864
2423
 
2424
+ /**
2425
+ * Returns a plain snapshot of all observable values.
2426
+ * Unwraps nested ObservableItem, ObservableArray, and ObservableObject recursively.
2427
+ * Alias: $val()
2428
+ *
2429
+ * @returns {Record<string, *>} Plain object with current values
2430
+ * @example
2431
+ * const user = Observable.object({ name: 'John', age: 25 });
2432
+ * user.val(); // { name: 'John', age: 25 }
2433
+ */
1865
2434
  ObservableObject.prototype.val = function() {
1866
2435
  const result = {};
1867
2436
  for(const key in this.$observables) {
1868
2437
  const dataItem = this.$observables[key];
1869
- if(Validator.isObservable(dataItem)) {
2438
+ if(dataItem?.__$Observable) {
1870
2439
  let value = dataItem.val();
1871
2440
  if(Array.isArray(value)) {
1872
2441
  value = value.map(item => {
1873
- if(Validator.isObservable(item)) {
2442
+ if(item.__$Observable) {
1874
2443
  return item.val();
1875
2444
  }
1876
- if(Validator.isProxy(item)) {
1877
- return item.$value;
1878
- }
1879
2445
  return item;
1880
2446
  });
1881
2447
  }
1882
2448
  result[key] = value;
1883
- } else if(Validator.isProxy(dataItem)) {
1884
- result[key] = dataItem.$value;
1885
2449
  } else {
1886
2450
  result[key] = dataItem;
1887
2451
  }
@@ -1890,20 +2454,37 @@ var NativeDocument = (function (exports) {
1890
2454
  };
1891
2455
  ObservableObject.prototype.$val = ObservableObject.prototype.val;
1892
2456
 
2457
+ /**
2458
+ * Returns the current value of a single property, unwrapped from its observable.
2459
+ * Alias: $get(property)
2460
+ *
2461
+ * @param {string} property - Property name
2462
+ * @returns {*} The current value of that property
2463
+ * @example
2464
+ * const user = Observable.object({ name: 'John' });
2465
+ * user.get('name'); // 'John'
2466
+ */
1893
2467
  ObservableObject.prototype.get = function(property) {
1894
2468
  const item = this.$observables[property];
1895
- if(Validator.isObservable(item)) {
2469
+ if(item?.__$Observable) {
1896
2470
  return item.val();
1897
2471
  }
1898
- if(Validator.isProxy(item)) {
1899
- return item.$value;
1900
- }
1901
2472
  return item;
1902
2473
  };
1903
2474
  ObservableObject.prototype.$get = ObservableObject.prototype.get;
1904
2475
 
2476
+ /**
2477
+ * Updates one or more properties with new values.
2478
+ * Supports partial updates — only provided keys are changed.
2479
+ * Aliases: $set(newData), $updateWith(newData), update(newData)
2480
+ *
2481
+ * @param {Partial<Record<string, *>>} newData - Object with properties to update
2482
+ * @example
2483
+ * const user = Observable.object({ name: 'John', age: 25 });
2484
+ * user.set({ name: 'Jane' }); // Only name changes, age stays 25
2485
+ */
1905
2486
  ObservableObject.prototype.set = function(newData) {
1906
- const data = Validator.isProxy(newData) ? newData.$value : newData;
2487
+ const data = newData?.__$Observable ? newData.$value : newData;
1907
2488
  const configs = this.configs;
1908
2489
 
1909
2490
  for(const key in data) {
@@ -1911,15 +2492,19 @@ var NativeDocument = (function (exports) {
1911
2492
  const newValueOrigin = newData[key];
1912
2493
  const newValue = data[key];
1913
2494
 
1914
- if(Validator.isObservable(targetItem)) {
2495
+ if(targetItem?.__$Observable) {
2496
+ if(targetItem.__$isObservableObject) {
2497
+ targetItem.update(newValue);
2498
+ continue;
2499
+ }
1915
2500
  if(!Validator.isArray(newValue)) {
1916
2501
  targetItem.set(newValue);
1917
2502
  continue;
1918
2503
  }
1919
2504
  const firstElementFromOriginalValue = newValueOrigin.at(0);
1920
- if(Validator.isObservable(firstElementFromOriginalValue) || Validator.isProxy(firstElementFromOriginalValue)) {
2505
+ if(firstElementFromOriginalValue?.__$Observable) {
1921
2506
  const newValues = newValue.map(item => {
1922
- if(Validator.isProxy(firstElementFromOriginalValue)) {
2507
+ if(firstElementFromOriginalValue.__$isObservableObject) {
1923
2508
  return new ObservableObject(item, configs);
1924
2509
  }
1925
2510
  return ObservableItem(item, configs);
@@ -1930,35 +2515,68 @@ var NativeDocument = (function (exports) {
1930
2515
  targetItem.set([...newValue]);
1931
2516
  continue;
1932
2517
  }
1933
- if(Validator.isProxy(targetItem)) {
1934
- targetItem.update(newValue);
1935
- continue;
1936
- }
1937
2518
  this[key] = newValue;
1938
2519
  }
1939
2520
  };
1940
2521
  ObservableObject.prototype.$set = ObservableObject.prototype.set;
1941
2522
  ObservableObject.prototype.$updateWith = ObservableObject.prototype.set;
1942
2523
 
2524
+ /**
2525
+ * Returns an array of all internal observable instances (one per property).
2526
+ * Alias: $observables()
2527
+ *
2528
+ * @returns {ObservableItem[]} Array of observable instances
2529
+ */
1943
2530
  ObservableObject.prototype.observables = function() {
1944
2531
  return Object.values(this.$observables);
1945
2532
  };
1946
2533
  ObservableObject.prototype.$observables = ObservableObject.prototype.observables;
1947
2534
 
2535
+ /**
2536
+ * Returns all property names of the observable object.
2537
+ * Alias: $keys()
2538
+ *
2539
+ * @returns {string[]} Array of property names
2540
+ */
1948
2541
  ObservableObject.prototype.keys = function() {
1949
2542
  return Object.keys(this.$observables);
1950
2543
  };
1951
2544
  ObservableObject.prototype.$keys = ObservableObject.prototype.keys;
2545
+
2546
+ /**
2547
+ * Creates a new ObservableObject with a snapshot of the current values.
2548
+ * Changes to the clone do not affect the original.
2549
+ * Alias: $clone()
2550
+ *
2551
+ * @returns {ObservableObject} New independent ObservableObject with the same structure and values
2552
+ */
1952
2553
  ObservableObject.prototype.clone = function() {
1953
2554
  return new ObservableObject(this.val(), this.configs);
1954
2555
  };
1955
2556
  ObservableObject.prototype.$clone = ObservableObject.prototype.clone;
2557
+
2558
+ /**
2559
+ * Resets all properties to their initial values by calling .reset() on each child observable.
2560
+ * Only works if observables were created with { reset: true }.
2561
+ */
1956
2562
  ObservableObject.prototype.reset = function() {
1957
2563
  for(const key in this.$observables) {
1958
2564
  this.$observables[key].reset();
1959
2565
  }
1960
2566
  };
1961
2567
  ObservableObject.prototype.originalSubscribe = ObservableObject.prototype.subscribe;
2568
+
2569
+ /**
2570
+ * Subscribes to changes across all nested observables.
2571
+ * The callback is called whenever any property (or nested value) changes.
2572
+ * Internally uses debouncing (nextTick) to batch multiple simultaneous changes.
2573
+ *
2574
+ * @param {(value: Record<string, *>) => void} callback - Called on any nested change
2575
+ * @example
2576
+ * const user = Observable.object({ name: 'John', age: 25 });
2577
+ * user.subscribe(() => console.log('user changed:', user.val()));
2578
+ * user.name.$value = 'Jane'; // logs 'user changed: { name: "Jane", age: 25 }'
2579
+ */
1962
2580
  ObservableObject.prototype.subscribe = function(callback) {
1963
2581
  const observables = this.observables();
1964
2582
  const updatedValue = nextTick(() => this.trigger());
@@ -1969,7 +2587,7 @@ var NativeDocument = (function (exports) {
1969
2587
  const observable = observables[i];
1970
2588
  if (observable.__$isObservableArray) {
1971
2589
  observable.deepSubscribe(updatedValue);
1972
- continue
2590
+ continue;
1973
2591
  }
1974
2592
  observable.subscribe(updatedValue);
1975
2593
  }
@@ -1987,6 +2605,17 @@ var NativeDocument = (function (exports) {
1987
2605
  // is... -> ObservableChecker<boolean>
1988
2606
  //
1989
2607
 
2608
+ /**
2609
+ * Returns a derived observable that emits true when the value strictly equals the given value.
2610
+ * Supports reactive comparison when an ObservableItem is passed.
2611
+ *
2612
+ * @param {*|ObservableItem} value - Static value or observable to compare against
2613
+ * @returns {ObservableChecker<boolean>}
2614
+ * @example
2615
+ * const age = Observable(25);
2616
+ * age.isEqualTo(25).val(); // true
2617
+ * age.isEqualTo(Observable(25)).val(); // true
2618
+ */
1990
2619
  ObservableItem.prototype.isEqualTo = function (value) {
1991
2620
  if (value?.__$Observable) {
1992
2621
  return $computed((a, b) => a === b, [this, value]);
@@ -1994,6 +2623,12 @@ var NativeDocument = (function (exports) {
1994
2623
  return $checker(this, x => x === value);
1995
2624
  };
1996
2625
 
2626
+ /**
2627
+ * Returns a derived observable that emits true when the value does not strictly equal the given value.
2628
+ *
2629
+ * @param {*|ObservableItem} value - Static value or observable to compare against
2630
+ * @returns {ObservableChecker<boolean>}
2631
+ */
1997
2632
  ObservableItem.prototype.isNotEqualTo = function (value) {
1998
2633
  if (value?.__$Observable) {
1999
2634
  return $computed((a, b) => a !== b, [this, value]);
@@ -2001,6 +2636,12 @@ var NativeDocument = (function (exports) {
2001
2636
  return $checker(this, x => x !== value);
2002
2637
  };
2003
2638
 
2639
+ /**
2640
+ * Returns a derived observable that emits true when the value is greater than the given value.
2641
+ *
2642
+ * @param {number|ObservableItem<number>} value - Threshold value or observable
2643
+ * @returns {ObservableChecker<boolean>}
2644
+ */
2004
2645
  ObservableItem.prototype.isGreaterThan = function (value) {
2005
2646
  if (value?.__$Observable) {
2006
2647
  return $computed((a, b) => a > b, [this, value]);
@@ -2008,6 +2649,12 @@ var NativeDocument = (function (exports) {
2008
2649
  return $checker(this, x => x > value);
2009
2650
  };
2010
2651
 
2652
+ /**
2653
+ * Returns a derived observable that emits true when the value is greater than or equal to the given value.
2654
+ *
2655
+ * @param {number|ObservableItem<number>} value - Threshold value or observable
2656
+ * @returns {ObservableChecker<boolean>}
2657
+ */
2011
2658
  ObservableItem.prototype.isGreaterThanOrEqualTo = function (value) {
2012
2659
  if (value?.__$Observable) {
2013
2660
  return $computed((a, b) => a >= b, [this, value]);
@@ -2015,6 +2662,12 @@ var NativeDocument = (function (exports) {
2015
2662
  return $checker(this, x => x >= value);
2016
2663
  };
2017
2664
 
2665
+ /**
2666
+ * Returns a derived observable that emits true when the value is less than the given value.
2667
+ *
2668
+ * @param {number|ObservableItem<number>} value - Threshold value or observable
2669
+ * @returns {ObservableChecker<boolean>}
2670
+ */
2018
2671
  ObservableItem.prototype.isLessThan = function (value) {
2019
2672
  if (value?.__$Observable) {
2020
2673
  return $computed((a, b) => a < b, [this, value]);
@@ -2022,6 +2675,12 @@ var NativeDocument = (function (exports) {
2022
2675
  return $checker(this, x => x < value);
2023
2676
  };
2024
2677
 
2678
+ /**
2679
+ * Returns a derived observable that emits true when the value is less than or equal to the given value.
2680
+ *
2681
+ * @param {number|ObservableItem<number>} value - Threshold value or observable
2682
+ * @returns {ObservableChecker<boolean>}
2683
+ */
2025
2684
  ObservableItem.prototype.isLessThanOrEqualTo = function (value) {
2026
2685
  if (value?.__$Observable) {
2027
2686
  return $computed((a, b) => a <= b, [this, value]);
@@ -2029,6 +2688,18 @@ var NativeDocument = (function (exports) {
2029
2688
  return $checker(this, x => x <= value);
2030
2689
  };
2031
2690
 
2691
+ /**
2692
+ * Returns a derived observable that emits true when the value is between min and max (inclusive).
2693
+ * All combinations of static and observable min/max are supported.
2694
+ *
2695
+ * @param {number|ObservableItem<number>} min - Lower bound (inclusive)
2696
+ * @param {number|ObservableItem<number>} max - Upper bound (inclusive)
2697
+ * @returns {ObservableChecker<boolean>}
2698
+ * @example
2699
+ * const age = Observable(25);
2700
+ * age.isBetween(18, 65).val(); // true
2701
+ * age.isBetween(Observable(18), Observable(65)).val(); // true
2702
+ */
2032
2703
  ObservableItem.prototype.isBetween = function (min, max) {
2033
2704
  if (min.__$Observable && max.__$Observable) {
2034
2705
  return $computed((x, a, b) => x >= a && x <= b, [this, min, max]);
@@ -2042,18 +2713,39 @@ var NativeDocument = (function (exports) {
2042
2713
  return $checker(this, x => x >= min && x <= max);
2043
2714
  };
2044
2715
 
2716
+ /**
2717
+ * Returns a derived observable that emits true when the value is null or undefined.
2718
+ *
2719
+ * @returns {ObservableChecker<boolean>}
2720
+ */
2045
2721
  ObservableItem.prototype.isNull = function () {
2046
2722
  return $checker(this, x => x == null);
2047
2723
  };
2048
2724
 
2725
+ /**
2726
+ * Returns a derived observable that emits true when the value is truthy.
2727
+ *
2728
+ * @returns {ObservableChecker<boolean>}
2729
+ */
2049
2730
  ObservableItem.prototype.isTruthy = function () {
2050
2731
  return $checker(this, x => !!x);
2051
2732
  };
2052
2733
 
2734
+ /**
2735
+ * Returns a derived observable that emits true when the value is falsy.
2736
+ *
2737
+ * @returns {ObservableChecker<boolean>}
2738
+ */
2053
2739
  ObservableItem.prototype.isFalsy = function () {
2054
2740
  return $checker(this, x => !x);
2055
2741
  };
2056
2742
 
2743
+ /**
2744
+ * Returns a derived observable that emits true when the string value starts with the given string.
2745
+ *
2746
+ * @param {string|ObservableItem<string>} str - Prefix to check for
2747
+ * @returns {ObservableChecker<boolean>}
2748
+ */
2057
2749
  ObservableItem.prototype.isStartingWith = function (str) {
2058
2750
  if (str?.__$Observable) {
2059
2751
  return $computed((a, b) => String(a).startsWith(b), [this, str]);
@@ -2061,6 +2753,12 @@ var NativeDocument = (function (exports) {
2061
2753
  return $checker(this, x => String(x).startsWith(str));
2062
2754
  };
2063
2755
 
2756
+ /**
2757
+ * Returns a derived observable that emits true when the string value ends with the given string.
2758
+ *
2759
+ * @param {string|ObservableItem<string>} str - Suffix to check for
2760
+ * @returns {ObservableChecker<boolean>}
2761
+ */
2064
2762
  ObservableItem.prototype.isEndingWith = function (str) {
2065
2763
  if (str?.__$Observable) {
2066
2764
  return $computed((a, b) => String(a).endsWith(b), [this, str]);
@@ -2068,6 +2766,12 @@ var NativeDocument = (function (exports) {
2068
2766
  return $checker(this, x => String(x).endsWith(str));
2069
2767
  };
2070
2768
 
2769
+ /**
2770
+ * Returns a derived observable that emits true when the string value matches the given regex.
2771
+ *
2772
+ * @param {RegExp|ObservableItem<RegExp>} regex - Pattern to test against
2773
+ * @returns {ObservableChecker<boolean>}
2774
+ */
2071
2775
  ObservableItem.prototype.isMatchingPattern = function (regex) {
2072
2776
  if (regex?.__$Observable) {
2073
2777
  return $computed((a, b) => new RegExp(b).test(String(a)), [this, regex]);
@@ -2075,14 +2779,36 @@ var NativeDocument = (function (exports) {
2075
2779
  return $checker(this, x => regex.test(String(x)));
2076
2780
  };
2077
2781
 
2782
+ /**
2783
+ * Returns a derived observable that emits true when the value is empty.
2784
+ * Empty means: null, undefined, empty string, or empty array.
2785
+ *
2786
+ * @returns {ObservableChecker<boolean>}
2787
+ */
2078
2788
  ObservableItem.prototype.isEmpty = function () {
2079
2789
  return $checker(this, x => x == null || x === '' || (Array.isArray(x) && x.length === 0));
2080
2790
  };
2081
2791
 
2792
+ /**
2793
+ * Returns a derived observable that emits true when the value is not empty.
2794
+ * Not empty means: not null, not undefined, not empty string, not empty array.
2795
+ *
2796
+ * @returns {ObservableChecker<boolean>}
2797
+ */
2082
2798
  ObservableItem.prototype.isNotEmpty = function () {
2083
2799
  return $checker(this, x => x != null && x !== '' && !(Array.isArray(x) && x.length === 0));
2084
2800
  };
2085
2801
 
2802
+ /**
2803
+ * Returns a derived observable that emits true when the value includes the given value.
2804
+ * Works for both arrays (includes check) and strings (substring check).
2805
+ *
2806
+ * @param {*|ObservableItem} value - Value to search for
2807
+ * @returns {ObservableChecker<boolean>}
2808
+ * @example
2809
+ * Observable([1, 2, 3]).isIncludes(2).val(); // true
2810
+ * Observable('hello world').isIncludes('world').val(); // true
2811
+ */
2086
2812
  ObservableItem.prototype.isIncludes = function (value) {
2087
2813
  if (value?.__$Observable) {
2088
2814
  return $computed((a, b) => {
@@ -2096,6 +2822,16 @@ var NativeDocument = (function (exports) {
2096
2822
  });
2097
2823
  };
2098
2824
 
2825
+ /**
2826
+ * Returns a derived observable that emits true when the value is included in the given array.
2827
+ * Alias: isOneOf
2828
+ *
2829
+ * @param {Array|ObservableItem<Array>} array - Array to check membership in
2830
+ * @returns {ObservableChecker<boolean>}
2831
+ * @example
2832
+ * const role = Observable('admin');
2833
+ * role.isIncludedIn(['admin', 'editor']).val(); // true
2834
+ */
2099
2835
  ObservableItem.prototype.isIncludedIn = function (array) {
2100
2836
  if (array?.__$Observable) {
2101
2837
  return $computed((a, b) => b.includes(a), [this, array]);
@@ -2105,6 +2841,15 @@ var NativeDocument = (function (exports) {
2105
2841
 
2106
2842
  ObservableItem.prototype.isOneOf = ObservableItem.prototype.isIncludedIn;
2107
2843
 
2844
+ /**
2845
+ * Returns a derived observable that emits true when the given key exists in the value object.
2846
+ *
2847
+ * @param {string|ObservableItem<string>} key - Property key to check for
2848
+ * @returns {ObservableChecker<boolean>}
2849
+ * @example
2850
+ * const user = Observable({ name: 'John' });
2851
+ * user.isHaving('name').val(); // true
2852
+ */
2108
2853
  ObservableItem.prototype.isHaving = function (key) {
2109
2854
  if (key?.__$Observable) {
2110
2855
  return $computed((a, b) => b in Object(a), [this, key]);
@@ -2116,28 +2861,68 @@ var NativeDocument = (function (exports) {
2116
2861
  // to... -> ObservableChecker<any>
2117
2862
  //
2118
2863
 
2864
+ /**
2865
+ * Returns a derived observable that emits the string value converted to uppercase.
2866
+ *
2867
+ * @returns {ObservableChecker<string>}
2868
+ */
2119
2869
  ObservableItem.prototype.toUpperCase = function () {
2120
2870
  return $checker(this, x => String(x).toUpperCase());
2121
2871
  };
2122
2872
 
2873
+ /**
2874
+ * Returns a derived observable that emits the string value converted to lowercase.
2875
+ *
2876
+ * @returns {ObservableChecker<string>}
2877
+ */
2123
2878
  ObservableItem.prototype.toLowerCase = function () {
2124
2879
  return $checker(this, x => String(x).toLowerCase());
2125
2880
  };
2126
2881
 
2882
+ /**
2883
+ * Returns a derived observable that emits the string value trimmed of whitespace.
2884
+ *
2885
+ * @returns {ObservableChecker<string>}
2886
+ */
2127
2887
  ObservableItem.prototype.toTrimmed = function () {
2128
2888
  return $checker(this, x => String(x).trim());
2129
2889
  };
2130
2890
 
2891
+ /**
2892
+ * Returns a derived observable that emits the value coerced to boolean.
2893
+ *
2894
+ * @returns {ObservableChecker<boolean>}
2895
+ */
2131
2896
  ObservableItem.prototype.toBoolean = function () {
2132
2897
  return $checker(this, x => !!x);
2133
2898
  };
2134
2899
 
2900
+ /**
2901
+ * Returns a derived observable that emits a string with the placeholder replaced by the current value.
2902
+ * Alias: toFormatted
2903
+ *
2904
+ * @param {string} template - Template string containing the placeholder
2905
+ * @param {string} [placeholder='${v}'] - Placeholder string to replace with the value
2906
+ * @returns {ObservableChecker<string>}
2907
+ * @example
2908
+ * const count = Observable(5);
2909
+ * count.toLiteral('You have ${v} items').val(); // 'You have 5 items'
2910
+ */
2135
2911
  ObservableItem.prototype.toLiteral = function (template, placeholder = '${v}') {
2136
2912
  return $checker(this, x => template.replace(placeholder, x));
2137
2913
  };
2138
2914
 
2139
2915
  ObservableItem.prototype.toFormatted = ObservableItem.prototype.toLiteral;
2140
2916
 
2917
+ /**
2918
+ * Returns a derived observable that emits a nested property value resolved via dot notation.
2919
+ *
2920
+ * @param {string} key - Dot-notation path to the property (e.g. 'user.address.city')
2921
+ * @returns {ObservableChecker<*>}
2922
+ * @example
2923
+ * const state = Observable({ user: { name: 'John' } });
2924
+ * state.toProperty('user.name').val(); // 'John'
2925
+ */
2141
2926
  ObservableItem.prototype.toProperty = function (key) {
2142
2927
  const keys = key.split('.');
2143
2928
  return $checker(this, x => {
@@ -2150,10 +2935,27 @@ var NativeDocument = (function (exports) {
2150
2935
  });
2151
2936
  };
2152
2937
 
2938
+ /**
2939
+ * Returns a derived observable that emits the length of the current value.
2940
+ * Returns 0 if the value is null or undefined.
2941
+ *
2942
+ * @returns {ObservableChecker<number>}
2943
+ */
2153
2944
  ObservableItem.prototype.toLength = function () {
2154
2945
  return $checker(this, x => (x == null ? 0 : x.length));
2155
2946
  };
2156
2947
 
2948
+ /**
2949
+ * Returns a derived observable that emits the value clamped between min and max.
2950
+ * All combinations of static and observable min/max are supported.
2951
+ *
2952
+ * @param {number|ObservableItem<number>} min - Minimum bound
2953
+ * @param {number|ObservableItem<number>} max - Maximum bound
2954
+ * @returns {ObservableChecker<number>}
2955
+ * @example
2956
+ * const volume = Observable(150);
2957
+ * volume.toClamped(0, 100).val(); // 100
2958
+ */
2157
2959
  ObservableItem.prototype.toClamped = function (min, max) {
2158
2960
  if (min.__$Observable && max.__$Observable) {
2159
2961
  return $computed((x, a, b) => Math.min(Math.max(x, a), b), [this, min, max]);
@@ -2167,6 +2969,17 @@ var NativeDocument = (function (exports) {
2167
2969
  return $checker(this, x => Math.min(Math.max(x, min), max));
2168
2970
  };
2169
2971
 
2972
+ /**
2973
+ * Returns a derived observable that emits the value expressed as a percentage of total.
2974
+ * Returns 0 if total is 0.
2975
+ *
2976
+ * @param {number|ObservableItem<number>} total - The total value representing 100%
2977
+ * @returns {ObservableChecker<number>}
2978
+ * @example
2979
+ * const score = Observable(75);
2980
+ * score.toPercent(100).val(); // 75
2981
+ * score.toPercent(200).val(); // 37.5
2982
+ */
2170
2983
  ObservableItem.prototype.toPercent = function (total) {
2171
2984
  if (total?.__$Observable) {
2172
2985
  return $computed((a, b) => (b === 0 ? 0 : (a / b) * 100), [this, total]);
@@ -2279,7 +3092,7 @@ var NativeDocument = (function (exports) {
2279
3092
  }).formatToParts(d).reduce((acc, { type, value }) => {
2280
3093
  acc[type] = value;
2281
3094
  return acc;
2282
- }, {})
3095
+ }, {}),
2283
3096
  };
2284
3097
  };
2285
3098
 
@@ -2305,20 +3118,20 @@ var NativeDocument = (function (exports) {
2305
3118
  currency,
2306
3119
  notation,
2307
3120
  minimumFractionDigits,
2308
- maximumFractionDigits
3121
+ maximumFractionDigits,
2309
3122
  }).format(value),
2310
3123
 
2311
3124
  number: (value, locale, { notation, minimumFractionDigits, maximumFractionDigits } = {}) =>
2312
3125
  new Intl.NumberFormat(locale, {
2313
3126
  notation,
2314
3127
  minimumFractionDigits,
2315
- maximumFractionDigits
3128
+ maximumFractionDigits,
2316
3129
  }).format(value),
2317
3130
 
2318
3131
  percent: (value, locale, { decimals = 1 } = {}) =>
2319
3132
  new Intl.NumberFormat(locale, {
2320
3133
  style: 'percent',
2321
- maximumFractionDigits: decimals
3134
+ maximumFractionDigits: decimals,
2322
3135
  }).format(value),
2323
3136
 
2324
3137
  date: (value, locale, { format, dateStyle = 'long' } = {}) => {
@@ -2380,14 +3193,39 @@ var NativeDocument = (function (exports) {
2380
3193
  * @param callback
2381
3194
  * @returns {ObservableChecker}
2382
3195
  */
2383
- ObservableItem.prototype.check = function(callback) {
2384
- return new ObservableChecker(this, callback)
2385
- };
2386
-
2387
- ObservableItem.prototype.transform = ObservableItem.prototype.check;
3196
+ ObservableItem.prototype.check = function(callback) {
3197
+ return new ObservableChecker(this, callback);
3198
+ };
3199
+
3200
+ ObservableItem.prototype.transform = ObservableItem.prototype.check;
3201
+
3202
+ /**
3203
+ * Returns a derived observable that emits the value of a nested property.
3204
+ * Alias for .check(value => value[property]).
3205
+ *
3206
+ * @param {string} property - Property name to extract
3207
+ * @returns {ObservableChecker<*>}
3208
+ * @example
3209
+ * const user = Observable({ name: 'John', age: 25 });
3210
+ * user.pluck('name').val(); // 'John'
3211
+ */
2388
3212
  ObservableItem.prototype.pluck = function(property) {
2389
3213
  return new ObservableChecker(this, (value) => value[property]);
2390
3214
  };
3215
+
3216
+ /**
3217
+ * Creates a derived observable using a callback or checks equality with a static value.
3218
+ * - If callback is a function: equivalent to .check(callback)
3219
+ * - If callback is a value: equivalent to .check(v => v === value)
3220
+ * Alias: .select()
3221
+ *
3222
+ * @param {Function|*} callbackOrValue - Transform function or value to compare
3223
+ * @returns {ObservableChecker<*>}
3224
+ * @example
3225
+ * const count = Observable(5);
3226
+ * count.is(v => v > 3).val(); // true
3227
+ * count.is(5).val(); // true
3228
+ */
2391
3229
  ObservableItem.prototype.is = function(callbackOrValue) {
2392
3230
  if(typeof callbackOrValue === 'function') {
2393
3231
  return new ObservableChecker(this, callbackOrValue);
@@ -2447,6 +3285,8 @@ var NativeDocument = (function (exports) {
2447
3285
  * // Reacts to locale changes automatically
2448
3286
  * Store.setLocale('en-US');
2449
3287
  */
3288
+
3289
+
2450
3290
  ObservableItem.prototype.format = function(type, options = {}) {
2451
3291
  const self = this;
2452
3292
 
@@ -2457,7 +3297,7 @@ var NativeDocument = (function (exports) {
2457
3297
  {
2458
3298
  if (!Formatters[type]) {
2459
3299
  throw new NativeDocumentError(
2460
- `Observable.format : unknown type '${type}'. Available : ${Object.keys(Formatters).join(', ')}.`
3300
+ `Observable.format : unknown type '${type}'. Available : ${Object.keys(Formatters).join(', ')}.`,
2461
3301
  );
2462
3302
  }
2463
3303
  }
@@ -2466,7 +3306,7 @@ var NativeDocument = (function (exports) {
2466
3306
  const localeObservable = Formatters.locale;
2467
3307
 
2468
3308
  return ObservableItem.computed(() => formatter(self.val(), localeObservable.val(), options),
2469
- [self, localeObservable]
3309
+ [self, localeObservable],
2470
3310
  );
2471
3311
  };
2472
3312
 
@@ -2478,6 +3318,28 @@ var NativeDocument = (function (exports) {
2478
3318
  ERRORED: 'errored',
2479
3319
  };
2480
3320
 
3321
+ /**
3322
+ * Reactive async data fetcher with built-in state management.
3323
+ * Tracks loading, ready, refreshing, and error states automatically.
3324
+ * Use Observable.resource() rather than instantiating directly.
3325
+ *
3326
+ * @constructor
3327
+ * @param {(...depValues: any[], signal?: AbortSignal) => Promise<*>} fn - Async function to fetch data. Receives dependency values as arguments. If its arity exceeds the number of dependencies, an AbortSignal is passed as the last argument.
3328
+ * @param {ObservableItem[]} deps - Observable dependencies — resource re-fetches when any changes
3329
+ * @param {{ auto?: boolean, lazy?: boolean, debounce?: number, into?: ObservableItem, apply?: Function }} config - Configuration
3330
+ * @param {boolean} [config.auto=false] - If true, fetch runs automatically on creation (or when deps change)
3331
+ * @param {boolean} [config.lazy=false] - If true with deps, does not fetch immediately — waits for first dep change
3332
+ * @param {number} [config.debounce=0] - Debounce delay in ms for dependency-triggered re-fetches
3333
+ * @param {ObservableItem} [config.into] - Observable to write results into instead of creating a new one
3334
+ * @param {Function} [config.apply] - Custom function to apply the result to this.data
3335
+ * @example
3336
+ * const userId = Observable(1);
3337
+ * const user = Observable.resource(
3338
+ * async (id, signal) => fetch(`/api/users/${id}`, { signal }).then(r => r.json()),
3339
+ * [userId],
3340
+ * { auto: true }
3341
+ * );
3342
+ */
2481
3343
  function ObservableResource(fn, deps, config) {
2482
3344
  this.$fn = (config.debounce > 0) ? debounce(fn, config.debounce) : fn;
2483
3345
  this.$dependencies = deps;
@@ -2485,13 +3347,13 @@ var NativeDocument = (function (exports) {
2485
3347
  this.$controller = null;
2486
3348
  this.$subscriptions = [];
2487
3349
 
2488
- this.data = config.initial ?? new ObservableItem(null);
3350
+ this.data = config.into ?? new ObservableItem(null);
2489
3351
  this.error = new ObservableItem(null);
2490
3352
  this.state = new ObservableItem(STATE.UNRESOLVED);
2491
3353
 
2492
3354
  this.loading = ObservableItem.computed(
2493
3355
  (state) => state === STATE.PENDING || state === STATE.REFRESHING,
2494
- [this.state]
3356
+ [this.state],
2495
3357
  );
2496
3358
 
2497
3359
  if (config.auto) {
@@ -2503,6 +3365,14 @@ var NativeDocument = (function (exports) {
2503
3365
  }
2504
3366
  }
2505
3367
 
3368
+ ObservableResource.prototype.$applyResult = function(result) {
3369
+ if(this.$config.apply) {
3370
+ this.$config.apply(result, this.data);
3371
+ return;
3372
+ }
3373
+ this.data.set(result);
3374
+ };
3375
+
2506
3376
  ObservableResource.prototype.$abort = function() {
2507
3377
  if (this.$controller) {
2508
3378
  this.$controller.abort();
@@ -2530,7 +3400,7 @@ var NativeDocument = (function (exports) {
2530
3400
  if (signal.aborted) {
2531
3401
  return;
2532
3402
  }
2533
- this.data.set(result);
3403
+ this.$applyResult(result);
2534
3404
  this.error.set(null);
2535
3405
  this.state.set(STATE.READY);
2536
3406
  this.$controller = null;
@@ -2555,7 +3425,7 @@ var NativeDocument = (function (exports) {
2555
3425
 
2556
3426
  Promise.resolve(this.$fn(...args))
2557
3427
  .then(result => {
2558
- this.data.set(result);
3428
+ this.$applyResult(result);
2559
3429
  this.error.set(null);
2560
3430
  this.state.set(STATE.READY);
2561
3431
  })
@@ -2565,6 +3435,11 @@ var NativeDocument = (function (exports) {
2565
3435
  });
2566
3436
  };
2567
3437
 
3438
+ ObservableResource.prototype.into = function($observable) {
3439
+ this.data = $observable;
3440
+ return this;
3441
+ };
3442
+
2568
3443
  ObservableResource.prototype.$run = function(isRefetch = false) {
2569
3444
  const needsSignal = this.$fn.length > this.$dependencies.length;
2570
3445
  if(needsSignal) {
@@ -2590,48 +3465,138 @@ var NativeDocument = (function (exports) {
2590
3465
  }
2591
3466
  };
2592
3467
 
3468
+ /**
3469
+ * Sets a custom function to apply fetched results to this.data.
3470
+ * Useful when the raw response needs transformation before storing.
3471
+ *
3472
+ * @param {(result: *, data: ObservableItem) => void} fn - Function receiving the result and the data observable
3473
+ * @returns {this}
3474
+ * @example
3475
+ * resource.apply((result, data) => data.set(result.items));
3476
+ */
3477
+ ObservableResource.prototype.apply = function(fn) {
3478
+ this.$config.apply = fn;
3479
+ return this;
3480
+ };
3481
+
3482
+ /**
3483
+ * Redirects fetched results into an existing ObservableItem instead of the default internal one.
3484
+ * Updates both this.data reference and config.into.
3485
+ *
3486
+ * @param {ObservableItem} $observable - Target observable to write results into
3487
+ * @returns {this}
3488
+ * @example
3489
+ * const items = Observable([]);
3490
+ * resource.into(items);
3491
+ */
3492
+ ObservableResource.prototype.into = function($observable) {
3493
+ this.$config.into = $observable;
3494
+ this.data = $observable;
3495
+ return this;
3496
+ };
3497
+
3498
+ /**
3499
+ * Triggers a fresh fetch (state transitions to 'pending').
3500
+ * Use when no prior data exists or when a full reload is needed.
3501
+ *
3502
+ * @returns {this}
3503
+ */
2593
3504
  ObservableResource.prototype.fetch = function() {
2594
3505
  this.$run(false);
2595
3506
  return this;
2596
3507
  };
2597
3508
 
3509
+ /**
3510
+ * Triggers a re-fetch (state transitions to 'refreshing' if data already exists).
3511
+ * Use when you want to reload while keeping the previous data visible.
3512
+ *
3513
+ * @returns {this}
3514
+ */
2598
3515
  ObservableResource.prototype.refetch = function() {
2599
3516
  this.$run(true);
2600
3517
  return this;
2601
3518
  };
2602
3519
 
3520
+ /**
3521
+ * Manually sets the data value and marks the state as 'ready'.
3522
+ * Useful for optimistic updates or seeding initial data without a network call.
3523
+ *
3524
+ * @param {*} value - New value to set on this.data
3525
+ * @returns {this}
3526
+ * @example
3527
+ * resource.mutate([...resource.data.val(), newItem]);
3528
+ */
2603
3529
  ObservableResource.prototype.mutate = function(value) {
2604
3530
  this.data.set(value);
2605
3531
  this.state.set(STATE.READY);
2606
3532
  return this;
2607
3533
  };
2608
3534
 
3535
+ /**
3536
+ * Cancels any pending request, unsubscribes from all dependencies, and clears subscriptions.
3537
+ * Call this when the component using this resource is unmounted.
3538
+ *
3539
+ * @returns {void}
3540
+ */
2609
3541
  ObservableResource.prototype.destroy = function() {
2610
3542
  this.$abort();
2611
3543
  this.$subscriptions.forEach(unsub => unsub());
2612
3544
  this.$subscriptions = [];
2613
3545
  };
2614
3546
 
3547
+ /**
3548
+ * Returns a derived observable that emits true when the state is 'ready'.
3549
+ *
3550
+ * @returns {ObservableChecker<boolean>}
3551
+ */
2615
3552
  ObservableResource.prototype.isReady = function() {
2616
3553
  return this.state.isEqualTo(STATE.READY);
2617
3554
  };
2618
3555
 
3556
+ /**
3557
+ * Returns a derived observable that emits true when the state is 'pending' (initial load).
3558
+ *
3559
+ * @returns {ObservableChecker<boolean>}
3560
+ */
2619
3561
  ObservableResource.prototype.isPending = function() {
2620
3562
  return this.state.isEqualTo(STATE.PENDING);
2621
3563
  };
2622
3564
 
3565
+ /**
3566
+ * Returns a derived observable that emits true when the state is 'refreshing' (reload with existing data).
3567
+ *
3568
+ * @returns {ObservableChecker<boolean>}
3569
+ */
2623
3570
  ObservableResource.prototype.isRefreshing = function() {
2624
3571
  return this.state.isEqualTo(STATE.REFRESHING);
2625
3572
  };
2626
3573
 
3574
+ /**
3575
+ * Returns a derived observable that emits true when the state is 'errored'.
3576
+ *
3577
+ * @returns {ObservableChecker<boolean>}
3578
+ */
2627
3579
  ObservableResource.prototype.isErrored = function() {
2628
3580
  return this.state.isEqualTo(STATE.ERRORED);
2629
3581
  };
2630
3582
 
3583
+ /**
3584
+ * Returns a derived observable that emits true when no fetch has been triggered yet.
3585
+ *
3586
+ * @returns {ObservableChecker<boolean>}
3587
+ */
2631
3588
  ObservableResource.prototype.isUnresolved = function() {
2632
3589
  return this.state.isEqualTo(STATE.UNRESOLVED);
2633
3590
  };
2634
3591
 
3592
+ /**
3593
+ * Registers a callback that is called every time a fetch completes successfully.
3594
+ *
3595
+ * @param {(value: *) => void} callback - Called with the fetched data value
3596
+ * @returns {this}
3597
+ * @example
3598
+ * resource.onSuccess((data) => console.log('Loaded:', data));
3599
+ */
2635
3600
  ObservableResource.prototype.onSuccess = function(callback) {
2636
3601
  this.data.subscribe((value) => {
2637
3602
  if (this.state.val() === STATE.READY) {
@@ -2641,6 +3606,14 @@ var NativeDocument = (function (exports) {
2641
3606
  return this;
2642
3607
  };
2643
3608
 
3609
+ /**
3610
+ * Registers a callback called every time a fetch fails.
3611
+ *
3612
+ * @param {(error: Error) => void} callback - Called with the error object
3613
+ * @returns {this}
3614
+ * @example
3615
+ * resource.onError((err) => console.error('Failed:', err.message));
3616
+ */
2644
3617
  ObservableResource.prototype.onError = function(callback) {
2645
3618
  this.error.subscribe((err) => {
2646
3619
  if (err !== null) {
@@ -2802,8 +3775,8 @@ var NativeDocument = (function (exports) {
2802
3775
  }
2803
3776
 
2804
3777
  dependencies.forEach(dependency => {
2805
- if(Validator.isProxy(dependency)) {
2806
- dependency.$observables.forEach((observable) => {
3778
+ if(dependency.__$isObservableObject) {
3779
+ dependency.observables().forEach((observable) => {
2807
3780
  observable.subscribe(updatedValue);
2808
3781
  });
2809
3782
  return;
@@ -2817,7 +3790,7 @@ var NativeDocument = (function (exports) {
2817
3790
 
2818
3791
 
2819
3792
  Observable.init = function(initialValue, configs = null) {
2820
- return new ObservableObject(initialValue, configs)
3793
+ return new ObservableObject(initialValue, configs);
2821
3794
  };
2822
3795
 
2823
3796
  /**
@@ -2846,9 +3819,6 @@ var NativeDocument = (function (exports) {
2846
3819
  if(data?.__$Observable) {
2847
3820
  return data.val();
2848
3821
  }
2849
- if(Validator.isProxy(data)) {
2850
- return data.$value;
2851
- }
2852
3822
  return data;
2853
3823
  };
2854
3824
 
@@ -2869,9 +3839,12 @@ var NativeDocument = (function (exports) {
2869
3839
  };
2870
3840
 
2871
3841
  /**
3842
+ * Applies a reactive class map to an HTMLElement.
3843
+ * Each key is a CSS class name; each value is a boolean or ObservableItem<boolean>.
3844
+ * If the value is an ObservableChecker emitting a string, toggles the class name dynamically.
2872
3845
  *
2873
- * @param {HTMLElement} element
2874
- * @param {Object} data
3846
+ * @param {HTMLElement} element - Target element
3847
+ * @param {Record<string, boolean|ObservableItem<boolean>|ObservableChecker<boolean|string>>} data - Class map
2875
3848
  */
2876
3849
  const bindClassAttribute = (element, data) => {
2877
3850
  for(const className in data) {
@@ -2879,7 +3852,7 @@ var NativeDocument = (function (exports) {
2879
3852
  if(value.__$Observable) {
2880
3853
  if(value.__$isObservableChecker) {
2881
3854
  let lastClass = value.val();
2882
- if(typeof lastClass === "string") {
3855
+ if(typeof lastClass === 'string') {
2883
3856
  element.classes.toggle(lastClass, true);
2884
3857
  value.subscribe((currentValue) => {
2885
3858
  element.classes.remove(lastClass);
@@ -2902,9 +3875,13 @@ var NativeDocument = (function (exports) {
2902
3875
  };
2903
3876
 
2904
3877
  /**
3878
+ * Applies a reactive style map to an HTMLElement.
3879
+ * Each key is a CSS property name (camelCase or CSS custom property `--var`);
3880
+ * each value is a string or ObservableItem<string>.
3881
+ * CSS custom properties are set via element.style.setProperty().
2905
3882
  *
2906
- * @param {HTMLElement} element
2907
- * @param {Object} data
3883
+ * @param {HTMLElement} element - Target element
3884
+ * @param {Record<string, string|ObservableItem<string>>} data - Style map
2908
3885
  */
2909
3886
  const bindStyleAttribute = (element, data) => {
2910
3887
  for(const styleName in data) {
@@ -2952,24 +3929,27 @@ var NativeDocument = (function (exports) {
2952
3929
  const bindBooleanAttribute = (element, attributeName, value) => {
2953
3930
  const isObservable = value.__$isObservable;
2954
3931
  const defaultValue = isObservable? value.val() : value;
3932
+
3933
+ const attributeRealName = BOOL_ATTRIBUTES_NAME[attributeName];
3934
+
2955
3935
  if(Validator.isBoolean(defaultValue)) {
2956
- element[attributeName] = defaultValue;
3936
+ element[attributeRealName] = defaultValue;
2957
3937
  }
2958
3938
  else {
2959
- element[attributeName] = defaultValue === element.value;
3939
+ element[attributeRealName] = defaultValue === element.value;
2960
3940
  }
2961
3941
  if(isObservable) {
2962
3942
  if(attributeName === 'checked') {
2963
3943
  if(typeof defaultValue === 'boolean') {
2964
- element.addEventListener('input', () => value.set(element[attributeName]));
3944
+ element.addEventListener('input', () => value.set(element[attributeRealName]));
2965
3945
  }
2966
3946
  else {
2967
3947
  element.addEventListener('input', () => value.set(element.value));
2968
3948
  }
2969
- value.subscribe((newValue) => element[attributeName] = newValue);
3949
+ value.subscribe((newValue) => element[attributeRealName] = newValue);
2970
3950
  return;
2971
3951
  }
2972
- value.subscribe((newValue) => element[attributeName] = (newValue === element.value));
3952
+ value.subscribe((newValue) => element[attributeRealName] = (newValue === element.value));
2973
3953
  }
2974
3954
  };
2975
3955
 
@@ -3005,7 +3985,7 @@ var NativeDocument = (function (exports) {
3005
3985
 
3006
3986
  for(const originalAttributeName in attributes) {
3007
3987
  const attributeName = originalAttributeName.toLowerCase();
3008
- let value = attributes[originalAttributeName];
3988
+ const value = attributes[originalAttributeName];
3009
3989
  if(value == null) {
3010
3990
  continue;
3011
3991
  }
@@ -3075,7 +4055,7 @@ var NativeDocument = (function (exports) {
3075
4055
  * @returns {Text}
3076
4056
  */
3077
4057
  createStaticTextNode: (parent, value) => {
3078
- let text = ElementCreator.createTextNode();
4058
+ const text = ElementCreator.createTextNode();
3079
4059
  text.nodeValue = value;
3080
4060
  parent && parent.appendChild(text);
3081
4061
  return text;
@@ -3107,7 +4087,7 @@ var NativeDocument = (function (exports) {
3107
4087
  {
3108
4088
  PluginsManager$1.emit('BeforeProcessChildren', parent);
3109
4089
  }
3110
- let child = ElementCreator.getChild(children);
4090
+ const child = ElementCreator.getChild(children);
3111
4091
  if(child) {
3112
4092
  parent.appendChild(child);
3113
4093
  }
@@ -3154,6 +4134,16 @@ var NativeDocument = (function (exports) {
3154
4134
  processStyleAttribute: bindStyleAttribute,
3155
4135
  };
3156
4136
 
4137
+ /**
4138
+ * Creates an augmented DocumentFragment with comment sentinel nodes and a MutationObserver
4139
+ * that fires when the fragment is inserted into the live DOM.
4140
+ * Used as the base for Anchor — not intended for direct use in application code.
4141
+ *
4142
+ * @internal
4143
+ * @constructor
4144
+ * @param {string} name - Debug label used in comment node text content
4145
+ * @returns {AnchorWithSentinel} Augmented DocumentFragment instance
4146
+ */
3157
4147
  function AnchorWithSentinel(name) {
3158
4148
  const instance = Reflect.construct(DocumentFragment, [], AnchorWithSentinel);
3159
4149
  const sentinel = document.createComment((name || '') + ' Anchor Sentinel');
@@ -3184,11 +4174,24 @@ var NativeDocument = (function (exports) {
3184
4174
  AnchorWithSentinel.prototype = Object.create(DocumentFragment.prototype);
3185
4175
  AnchorWithSentinel.prototype.constructor = AnchorWithSentinel;
3186
4176
 
4177
+ /**
4178
+ * Registers a callback to call every time the sentinel is connected to the live DOM.
4179
+ * The callback receives the parent node as its argument.
4180
+ *
4181
+ * @param {(parent: Node) => void} callback - Called each time the fragment is inserted
4182
+ * @returns {this}
4183
+ */
3187
4184
  AnchorWithSentinel.prototype.onConnected = function(callback) {
3188
4185
  this.$events.connected = callback;
3189
4186
  return this;
3190
4187
  };
3191
4188
 
4189
+ /**
4190
+ * Registers a callback to call the first time the sentinel is connected to the live DOM.
4191
+ * After the first connection, the MutationObserver is disconnected automatically.
4192
+ *
4193
+ * @param {(parent: Node) => void} callback - Called once on first insertion
4194
+ */
3192
4195
  AnchorWithSentinel.prototype.onConnectedOnce = function(callback) {
3193
4196
  this.$events.connected = (parent) => {
3194
4197
  callback(parent);
@@ -3260,6 +4263,15 @@ var NativeDocument = (function (exports) {
3260
4263
  };
3261
4264
  }
3262
4265
 
4266
+ /**
4267
+ * Creates an anchor fragment — a managed DocumentFragment delimited by comment sentinels.
4268
+ * Used internally by ForEach, ShowIf, Switch, Match and other control-flow directives
4269
+ * to manage dynamic DOM regions without a real container element.
4270
+ *
4271
+ * @param {string} name - Debug name for the anchor (visible as HTML comments in the DOM)
4272
+ * @param {boolean} [isUniqueChild=false] - If true, optimises rendering when this anchor is the only child of its parent
4273
+ * @returns {AnchorDocumentFragment} An augmented DocumentFragment with anchor management methods
4274
+ */
3263
4275
  function Anchor(name, isUniqueChild = false) {
3264
4276
  const anchorFragment = new AnchorWithSentinel(name);
3265
4277
 
@@ -3342,7 +4354,7 @@ var NativeDocument = (function (exports) {
3342
4354
  parentNode.nativeInsertBefore(child, anchorStart);
3343
4355
  return;
3344
4356
  }
3345
- parentNode.insertBefore(child, anchorStart);
4357
+ parentNode.insertBefore(child, anchorStart.nextSibling);
3346
4358
  };
3347
4359
 
3348
4360
  anchorFragment.removeChildren = function() {
@@ -3458,7 +4470,7 @@ var NativeDocument = (function (exports) {
3458
4470
 
3459
4471
  class ArgTypesError extends Error {
3460
4472
  constructor(message, errors) {
3461
- super(`${message}\n\n${errors.join("\n")}\n\n`);
4473
+ super(`${message}\n\n${errors.join('\n')}\n\n`);
3462
4474
  }
3463
4475
  }
3464
4476
 
@@ -3503,8 +4515,8 @@ var NativeDocument = (function (exports) {
3503
4515
  name,
3504
4516
  type: 'oneOf',
3505
4517
  types: argTypes,
3506
- validate: (v) => argTypes.some(type => type.validate(v))
3507
- })
4518
+ validate: (v) => argTypes.some(type => type.validate(v)),
4519
+ }),
3508
4520
  };
3509
4521
 
3510
4522
 
@@ -3544,7 +4556,7 @@ var NativeDocument = (function (exports) {
3544
4556
  });
3545
4557
 
3546
4558
  if (errors.length > 0) {
3547
- throw new ArgTypesError(`Argument validation failed`, errors);
4559
+ throw new ArgTypesError('Argument validation failed', errors);
3548
4560
  }
3549
4561
  };
3550
4562
 
@@ -3572,7 +4584,7 @@ var NativeDocument = (function (exports) {
3572
4584
  return { props, children };
3573
4585
  }
3574
4586
  if(typeof props !== 'object' || Array.isArray(props) || props === null || props.constructor.name !== 'Object' || props.$hydrate) { // IF it's not a JSON
3575
- return { props: children, children: props }
4587
+ return { props: children, children: props };
3576
4588
  }
3577
4589
  return { props, children };
3578
4590
  };
@@ -3695,7 +4707,7 @@ var NativeDocument = (function (exports) {
3695
4707
  DocumentObserver.unmountedSupposedSize--;
3696
4708
  }
3697
4709
  data = null;
3698
- }
4710
+ },
3699
4711
  };
3700
4712
 
3701
4713
  const addListener = (type, callback) => {
@@ -3753,11 +4765,18 @@ var NativeDocument = (function (exports) {
3753
4765
 
3754
4766
  off: (type, callback) => {
3755
4767
  removeListener(type, callback);
3756
- }
4768
+ },
3757
4769
  };
3758
- }
4770
+ },
3759
4771
  };
3760
4772
 
4773
+ /**
4774
+ * Wraps an HTMLElement with NativeDocument's reactivity and lifecycle API.
4775
+ * Created automatically by HtmlElementWrapper — not intended to be instantiated directly.
4776
+ *
4777
+ * @constructor
4778
+ * @param {HTMLElement} element - The underlying HTML element to wrap
4779
+ */
3761
4780
  function NDElement(element) {
3762
4781
  this.$element = element;
3763
4782
  this.$attachements = null;
@@ -3766,10 +4785,18 @@ var NativeDocument = (function (exports) {
3766
4785
  }
3767
4786
  }
3768
4787
 
4788
+
3769
4789
  NDElement.prototype.__$isNDElement = true;
3770
4790
 
3771
4791
  NDElement.$getChild = (el) => el;
3772
4792
 
4793
+ /**
4794
+ * Appends a child element to an internal DocumentFragment (ghost DOM),
4795
+ * keeping it detached from the main document until explicitly mounted.
4796
+ *
4797
+ * @param {HTMLElement|DocumentFragment|NDElement} element - Element to append
4798
+ * @returns {this}
4799
+ */
3773
4800
  NDElement.prototype.ghostDom = function(element) {
3774
4801
  if(!this.$attachements) {
3775
4802
  this.$attachements = document.createDocumentFragment();
@@ -3778,15 +4805,47 @@ var NativeDocument = (function (exports) {
3778
4805
  return this;
3779
4806
  };
3780
4807
 
4808
+ /**
4809
+ * Returns the underlying HTMLElement. Used internally for type coercion.
4810
+ *
4811
+ * @returns {HTMLElement}
4812
+ */
3781
4813
  NDElement.prototype.valueOf = function() {
3782
4814
  return this.$element;
3783
4815
  };
3784
4816
 
4817
+ /**
4818
+ * Stores the underlying HTMLElement in target[name].
4819
+ * Use this to keep a reference to the raw DOM node.
4820
+ *
4821
+ * @param {Record<string, any>} target - Object to store the reference in
4822
+ * @param {string} name - Property name to assign on the target object
4823
+ * @returns {this}
4824
+ * @example
4825
+ * const refs = {};
4826
+ * Input({ type: 'text' }).nd.ref(refs, 'emailInput');
4827
+ * refs.emailInput.focus();
4828
+ */
3785
4829
  NDElement.prototype.ref = function(target, name) {
3786
4830
  target[name] = this.$element;
3787
4831
  return this;
3788
4832
  };
3789
4833
 
4834
+ /**
4835
+ * Stores the NDElement instance itself in target[name].
4836
+ * Use this to expose a component's public API to a parent (via .with()).
4837
+ *
4838
+ * @param {Record<string, any>} target - Object to store the reference in
4839
+ * @param {string} name - Property name to assign on the target object
4840
+ * @returns {this}
4841
+ * @example
4842
+ * const refs = {};
4843
+ * Counter()
4844
+ * .nd.with({ increment() { count.$value++; return this; } })
4845
+ * .refSelf(refs, 'counter');
4846
+ *
4847
+ * refs.counter.increment();
4848
+ */
3790
4849
  NDElement.prototype.refSelf = function(target, name) {
3791
4850
  target[name] = this;
3792
4851
  // TODO: @DIM to check
@@ -3794,6 +4853,12 @@ var NativeDocument = (function (exports) {
3794
4853
  return this;
3795
4854
  };
3796
4855
 
4856
+ /**
4857
+ * Calls .nd.remove() on all child NDElements before removing this element.
4858
+ * Used to propagate lifecycle cleanup through the component tree.
4859
+ *
4860
+ * @returns {this}
4861
+ */
3797
4862
  NDElement.prototype.unmountChildren = function() {
3798
4863
  let element = this.$element;
3799
4864
  for(let i = 0, length = element.children.length; i < length; i++) {
@@ -3807,6 +4872,12 @@ var NativeDocument = (function (exports) {
3807
4872
  return this;
3808
4873
  };
3809
4874
 
4875
+ /**
4876
+ * Removes the element from the DOM and cleans up its lifecycle observers.
4877
+ * Also calls unmountChildren() recursively.
4878
+ *
4879
+ * @returns {this}
4880
+ */
3810
4881
  NDElement.prototype.remove = function() {
3811
4882
  let element = this.$element;
3812
4883
  element.nd.unmountChildren();
@@ -3819,6 +4890,19 @@ var NativeDocument = (function (exports) {
3819
4890
  };
3820
4891
 
3821
4892
  const $lifeCycleObservers = new WeakMap();
4893
+
4894
+ /**
4895
+ * Registers mounted and/or unmounted lifecycle callbacks for this element.
4896
+ * Uses MutationObserver internally to detect DOM insertion and removal.
4897
+ *
4898
+ * @param {{ mounted?: (el: HTMLElement) => void, unmounted?: (el: HTMLElement) => boolean|void }} states - Lifecycle hooks
4899
+ * @returns {this}
4900
+ * @example
4901
+ * Div({}).nd.lifecycle({
4902
+ * mounted: (el) => console.log('mounted', el),
4903
+ * unmounted: (el) => console.log('unmounted', el),
4904
+ * });
4905
+ */
3822
4906
  NDElement.prototype.lifecycle = function(states) {
3823
4907
  const el = this.$element;
3824
4908
  if (!$lifeCycleObservers.has(el)) {
@@ -3837,31 +4921,68 @@ var NativeDocument = (function (exports) {
3837
4921
  return this;
3838
4922
  };
3839
4923
 
4924
+ /**
4925
+ * Registers an unmounted callback that cleans up all beforeUnmount handlers
4926
+ * and aborts any pending async operations on this element and its children.
4927
+ *
4928
+ * @returns {this}
4929
+ */
3840
4930
  NDElement.prototype.destroyOnUnmount = function() {
3841
- this.unmounted(() => {
3842
- this.$element?.querySelectorAll('[data--nd-before-unmount]').forEach(child => {
3843
- child.remove();
3844
- child.__$controller?.abort();
3845
- child.__$controller = null;
3846
- $lifeCycleObservers.delete(child);
3847
- });
4931
+ this.unmounted(() => this.destroy());
4932
+ return this;
4933
+ };
3848
4934
 
3849
- this.$element.__$controller?.abort();
3850
- this.$element.__$controller = null;
3851
- $lifeCycleObservers.delete(this.$element);
3852
- this.$element = null;
4935
+ /**
4936
+ * aborts any pending async operations on this element and its children.
4937
+ *
4938
+ * @returns {this}
4939
+ */
4940
+ NDElement.prototype.destroy = function() {
4941
+ this.$element?.querySelectorAll('[data--nd-before-unmount]').forEach(child => {
4942
+ child.remove();
4943
+ child.__$controller?.abort();
4944
+ child.__$controller = null;
4945
+ $lifeCycleObservers.delete(child);
3853
4946
  });
3854
- return this;
4947
+
4948
+ this.$element.__$controller?.abort();
4949
+ this.$element.__$controller = null;
4950
+ $lifeCycleObservers.delete(this.$element);
4951
+ this.$element = null;
3855
4952
  };
3856
4953
 
4954
+ /**
4955
+ * Shorthand for lifecycle({ mounted: callback }).
4956
+ *
4957
+ * @param {(el: HTMLElement) => void} callback - Called when element is inserted into the DOM
4958
+ * @returns {this}
4959
+ */
3857
4960
  NDElement.prototype.mounted = function(callback) {
3858
4961
  return this.lifecycle({ mounted: callback });
3859
4962
  };
3860
4963
 
4964
+ /**
4965
+ * Shorthand for lifecycle({ unmounted: callback }).
4966
+ *
4967
+ * @param {(el: HTMLElement) => boolean|void} callback - Called when element is removed from the DOM
4968
+ * @returns {this}
4969
+ */
3861
4970
  NDElement.prototype.unmounted = function(callback) {
3862
4971
  return this.lifecycle({ unmounted: callback });
3863
4972
  };
3864
4973
 
4974
+ /**
4975
+ * Registers an async callback to run before this element is removed from the DOM.
4976
+ * The element's .remove() is delayed until all beforeUnmount callbacks resolve.
4977
+ *
4978
+ * @param {string} id - Unique identifier for this callback (allows overwriting)
4979
+ * @param {(this: NDElement, el: HTMLElement) => void|Promise<void>} callback - Async-compatible callback
4980
+ * @returns {this}
4981
+ * @example
4982
+ * Div({}).nd.beforeUnmount('fade-out', async (el) => {
4983
+ * await el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 300 }).finished;
4984
+ * });
4985
+ */
3865
4986
  NDElement.prototype.beforeUnmount = function(id, callback) {
3866
4987
  const el = this.$element;
3867
4988
 
@@ -3894,18 +5015,31 @@ var NativeDocument = (function (exports) {
3894
5015
  return this;
3895
5016
  };
3896
5017
 
5018
+ /**
5019
+ * Returns the underlying HTMLElement.
5020
+ * Alias: .node()
5021
+ *
5022
+ * @returns {HTMLElement}
5023
+ */
3897
5024
  NDElement.prototype.htmlElement = function() {
3898
5025
  return this.$element;
3899
5026
  };
3900
5027
 
3901
5028
  NDElement.prototype.node = NDElement.prototype.htmlElement;
3902
5029
 
5030
+ /**
5031
+ * Attaches a Shadow DOM to this element, redirecting all child appends to the shadow root.
5032
+ *
5033
+ * @param {'open'|'closed'} mode - Shadow DOM encapsulation mode
5034
+ * @param {string|null} [style=null] - Optional CSS string to inject into the shadow root
5035
+ * @returns {this}
5036
+ */
3903
5037
  NDElement.prototype.shadow = function(mode, style = null) {
3904
5038
  const $element = this.$element;
3905
5039
  const children = Array.from($element.childNodes);
3906
5040
  const shadowRoot = $element.attachShadow({ mode });
3907
5041
  if(style) {
3908
- const styleNode = document.createElement("style");
5042
+ const styleNode = document.createElement('style');
3909
5043
  styleNode.textContent = style;
3910
5044
  shadowRoot.appendChild(styleNode);
3911
5045
  }
@@ -3916,10 +5050,22 @@ var NativeDocument = (function (exports) {
3916
5050
  return this;
3917
5051
  };
3918
5052
 
5053
+ /**
5054
+ * Shorthand for .shadow('open', style).
5055
+ *
5056
+ * @param {string|null} [style=null] - Optional CSS string to inject into the shadow root
5057
+ * @returns {this}
5058
+ */
3919
5059
  NDElement.prototype.openShadow = function(style = null) {
3920
5060
  return this.shadow('open', style);
3921
5061
  };
3922
5062
 
5063
+ /**
5064
+ * Shorthand for .shadow('closed', style).
5065
+ *
5066
+ * @param {string|null} [style=null] - Optional CSS string to inject into the shadow root
5067
+ * @returns {this}
5068
+ */
3923
5069
  NDElement.prototype.closedShadow = function(style = null) {
3924
5070
  return this.shadow('closed', style);
3925
5071
  };
@@ -3968,7 +5114,16 @@ var NativeDocument = (function (exports) {
3968
5114
  return this;
3969
5115
  };
3970
5116
 
3971
-
5117
+ /**
5118
+ * Sets a single attribute on the element.
5119
+ * If value is an ObservableItem, the attribute is updated reactively.
5120
+ *
5121
+ * @param {string} name - Attribute name
5122
+ * @param {string|ObservableItem<string>} value - Attribute value, static or reactive
5123
+ * @returns {this}
5124
+ * @example
5125
+ * Input({}).nd.attr('placeholder', label); // reactive placeholder
5126
+ */
3972
5127
  NDElement.prototype.attr = function(name, value) {
3973
5128
  if(value?.__$Observable) {
3974
5129
  bindAttributeWithObservable(this.$element, name, value);
@@ -3978,16 +5133,37 @@ var NativeDocument = (function (exports) {
3978
5133
  return this;
3979
5134
  };
3980
5135
 
5136
+ /**
5137
+ * Applies a batch of attributes to the element via AttributesWrapper.
5138
+ * Supports reactive values, class maps, and style maps.
5139
+ *
5140
+ * @param {Object} attrs - Attributes object (same format as HtmlElementWrapper props)
5141
+ * @returns {this}
5142
+ */
3981
5143
  NDElement.prototype.attrs = function(attrs) {
3982
5144
  AttributesWrapper(this.$element, attrs);
3983
5145
  return this;
3984
5146
  };
3985
5147
 
5148
+ /**
5149
+ * Applies a reactive class map to the element.
5150
+ * Each key is a class name; each value is a boolean or ObservableItem<boolean>.
5151
+ *
5152
+ * @param {Record<string, boolean|ObservableItem<boolean>>} classes - Class map
5153
+ * @returns {this}
5154
+ */
3986
5155
  NDElement.prototype.class = function(classes) {
3987
5156
  bindClassAttribute(this.$element, classes);
3988
5157
  return this;
3989
5158
  };
3990
5159
 
5160
+ /**
5161
+ * Applies a reactive style map to the element.
5162
+ * Each key is a CSS property; each value is a string or ObservableItem<string>.
5163
+ *
5164
+ * @param {Record<string, string|ObservableItem<string>>} style - Style map
5165
+ * @returns {this}
5166
+ */
3991
5167
  NDElement.prototype.style = function(style) {
3992
5168
  bindStyleAttribute(this.$element, style);
3993
5169
  return this;
@@ -4023,7 +5199,7 @@ var NativeDocument = (function (exports) {
4023
5199
  const protectedMethods = new Set([
4024
5200
  'constructor', 'valueOf', '$element', '$observer',
4025
5201
  'ref', 'remove', 'cleanup', 'with', 'extend', 'attach',
4026
- 'lifecycle', 'mounted', 'unmounted', 'unmountChildren'
5202
+ 'lifecycle', 'mounted', 'unmounted', 'unmountChildren',
4027
5203
  ]);
4028
5204
 
4029
5205
  for (const name in methods) {
@@ -4056,160 +5232,302 @@ var NativeDocument = (function (exports) {
4056
5232
  return NDElement;
4057
5233
  };
4058
5234
 
5235
+
5236
+ /**
5237
+ * The global sanitizer function used by nd.html() when sanitize option is enabled.
5238
+ * Must be set via NDElement.setSanitizer() before using {sanitize: true}.
5239
+ *
5240
+ * @type {Function|null}
5241
+ */
5242
+ NDElement.$sanitizer = null;
5243
+
5244
+ /**
5245
+ * Configures the global sanitizer for nd.html().
5246
+ * The sanitizer function receives the HTML string and an optional config object.
5247
+ * Designed to be decoupled from any specific sanitizer library.
5248
+ *
5249
+ * @param {Function} sanitizerFn - Sanitizer function (html, config) => string
5250
+ * @returns {typeof NDElement}
5251
+ * @throws {NativeDocumentError} If sanitizerFn is not a function
5252
+ * @example
5253
+ * import DOMPurify from 'dompurify';
5254
+ * NDElement.setSanitizer((html, config) => DOMPurify.sanitize(html, config));
5255
+ */
5256
+ NDElement.setSanitizer = function(sanitizerFn) {
5257
+ if(typeof sanitizerFn !== 'function') {
5258
+ throw new NativeDocumentError('NDElement.setSanitizer() expects a function');
5259
+ }
5260
+ NDElement.$sanitizer = sanitizerFn;
5261
+ return NDElement;
5262
+ };
5263
+
5264
+ /**
5265
+ * Sets the inner HTML of the element.
5266
+ * Requires either {unsafe: true} to bypass security checks,
5267
+ * or {sanitize: true|Object|Function} to sanitize the content.
5268
+ * Supports Observable values for reactive HTML updates.
5269
+ *
5270
+ * @param {string|ObservableItem} content - HTML string or Observable<string>
5271
+ * @param {Object} [options={}]
5272
+ * @param {boolean} [options.unsafe=false] - Bypass security check. Use only with trusted content.
5273
+ * @param {boolean|Object|Function} [options.sanitize=false] - Sanitize strategy:
5274
+ * - true: use global sanitizer with default config
5275
+ * - Object: use global sanitizer with custom config
5276
+ * - Function: use this function directly as sanitizer (html) => string
5277
+ * @returns {this}
5278
+ * @throws {NativeDocumentError} If sanitize is true/Object but no global sanitizer is configured
5279
+ * @example
5280
+ * // Unsafe — trusted content only
5281
+ * el.nd.html('<strong>Hello</strong>', {unsafe: true})
5282
+ *
5283
+ * // Global sanitizer with default config
5284
+ * el.nd.html($userContent, {sanitize: true})
5285
+ *
5286
+ * // Global sanitizer with custom config
5287
+ * el.nd.html($userContent, {sanitize: {
5288
+ * ALLOWED_TAGS: ['b', 'i', 'strong', 'a'],
5289
+ * ALLOWED_ATTR: ['href'],
5290
+ * }})
5291
+ *
5292
+ * // Custom sanitizer function for this specific case
5293
+ * el.nd.html($userContent, {sanitize: (html) => myCustomSanitizer(html)})
5294
+ *
5295
+ * // Reactive with sanitize
5296
+ * el.nd.html($content, {sanitize: true})
5297
+ */
5298
+ NDElement.prototype.html = function(content, {unsafe = false, sanitize = false} = {}) {
5299
+ const $element = this.$element;
5300
+ const apply = (value) => {
5301
+ if(sanitize) {
5302
+ if(typeof sanitize === 'function') {
5303
+ $element.innerHTML = sanitize(value);
5304
+ return;
5305
+ }
5306
+
5307
+ if(!NDElement.$sanitizer) {
5308
+ throw new NativeDocumentError('nd.html() — no sanitizer configured. Call NDElement.setSanitizer() first.');
5309
+ }
5310
+ const config = sanitize === true ? {} : sanitize;
5311
+ $element.innerHTML = NDElement.$sanitizer(value, config);
5312
+ return;
5313
+ }
5314
+
5315
+ if(!unsafe) {
5316
+ console.warn('nd.html() — use {unsafe: true} or {sanitize: true|Object|Function}');
5317
+ return;
5318
+ }
5319
+
5320
+ $element.innerHTML = value;
5321
+ };
5322
+
5323
+ if(content?.__$Observable) {
5324
+ content.subscribe(apply);
5325
+ apply(content.val());
5326
+ return this;
5327
+ }
5328
+
5329
+ apply(content);
5330
+ return this;
5331
+ };
5332
+
5333
+ /**
5334
+ * Makes the element content editable and binds it to an Observable.
5335
+ * Changes in the DOM update the Observable, and changes to the Observable
5336
+ * update the DOM (only when the element is not focused to avoid cursor issues).
5337
+ *
5338
+ * @param {ObservableItem} $obs - Observable to bind to the element content
5339
+ * @param {Object} [options={}]
5340
+ * @param {string} [options.format='html'] - 'html' uses innerHTML, 'text' uses innerText
5341
+ * @returns {this}
5342
+ * @example
5343
+ * // Basic usage
5344
+ * const $content = $('<b>Hello</b>');
5345
+ * Div({}).nd.contentEditable($content)
5346
+ *
5347
+ * // Text only
5348
+ * Div({}).nd.contentEditable($content, {format: 'text'})
5349
+ */
5350
+ NDElement.prototype.contentEditable = function($obs, {format = 'html'} = {}) {
5351
+ this.$element.contentEditable = true;
5352
+
5353
+ const getValue = format === 'text'
5354
+ ? () => this.$element.innerText
5355
+ : () => this.$element.innerHTML;
5356
+
5357
+ const setValue = format === 'text'
5358
+ ? (value) => { this.$element.innerText = value; }
5359
+ : (value) => { this.$element.innerHTML = value; };
5360
+
5361
+ if($obs?.__$Observable) {
5362
+ $obs.subscribe((value) => {
5363
+ if(document.activeElement !== this.$element) {
5364
+ setValue(value);
5365
+ }
5366
+ });
5367
+ setValue($obs.val() || '');
5368
+ }
5369
+
5370
+ this.$element.addEventListener('input', () => {
5371
+ $obs?.set(getValue());
5372
+ }, { signal: this.$getSignal() });
5373
+
5374
+ return this;
5375
+ };
5376
+
4059
5377
  const EVENTS = [
4060
- "Click",
4061
- "DblClick",
4062
- "MouseDown",
4063
- "MouseEnter",
4064
- "MouseLeave",
4065
- "MouseMove",
4066
- "MouseOut",
4067
- "MouseOver",
4068
- "MouseUp",
4069
- "Wheel",
4070
- "KeyDown",
4071
- "KeyPress",
4072
- "KeyUp",
4073
- "Blur",
4074
- "Change",
4075
- "Focus",
4076
- "Input",
4077
- "Invalid",
4078
- "Reset",
4079
- "Search",
4080
- "Select",
4081
- "Submit",
4082
- "Drag",
4083
- "DragEnd",
4084
- "DragEnter",
4085
- "DragLeave",
4086
- "DragOver",
4087
- "DragStart",
4088
- "Drop",
4089
- "AfterPrint",
4090
- "BeforePrint",
4091
- "BeforeUnload",
4092
- "Error",
4093
- "HashChange",
4094
- "Load",
4095
- "Offline",
4096
- "Online",
4097
- "PageHide",
4098
- "PageShow",
4099
- "Resize",
4100
- "Scroll",
4101
- "Unload",
4102
- "Abort",
4103
- "CanPlay",
4104
- "CanPlayThrough",
4105
- "DurationChange",
4106
- "Emptied",
4107
- "Ended",
4108
- "LoadedData",
4109
- "LoadedMetadata",
4110
- "LoadStart",
4111
- "Pause",
4112
- "Play",
4113
- "Playing",
4114
- "Progress",
4115
- "RateChange",
4116
- "Seeked",
4117
- "Seeking",
4118
- "Stalled",
4119
- "Suspend",
4120
- "TimeUpdate",
4121
- "VolumeChange",
4122
- "Waiting",
4123
-
4124
- "TouchCancel",
4125
- "TouchEnd",
4126
- "TouchMove",
4127
- "TouchStart",
4128
- "AnimationEnd",
4129
- "AnimationIteration",
4130
- "AnimationStart",
4131
- "TransitionEnd",
4132
- "Copy",
4133
- "Cut",
4134
- "Paste",
4135
- "FocusIn",
4136
- "FocusOut",
4137
- "ContextMenu"
5378
+ 'Click',
5379
+ 'DblClick',
5380
+ 'MouseDown',
5381
+ 'MouseEnter',
5382
+ 'MouseLeave',
5383
+ 'MouseMove',
5384
+ 'MouseOut',
5385
+ 'MouseOver',
5386
+ 'MouseUp',
5387
+ 'Wheel',
5388
+ 'KeyDown',
5389
+ 'KeyPress',
5390
+ 'KeyUp',
5391
+ 'Blur',
5392
+ 'Change',
5393
+ 'Focus',
5394
+ 'Input',
5395
+ 'Invalid',
5396
+ 'Reset',
5397
+ 'Search',
5398
+ 'Select',
5399
+ 'Submit',
5400
+ 'Drag',
5401
+ 'DragEnd',
5402
+ 'DragEnter',
5403
+ 'DragLeave',
5404
+ 'DragOver',
5405
+ 'DragStart',
5406
+ 'Drop',
5407
+ 'AfterPrint',
5408
+ 'BeforePrint',
5409
+ 'BeforeUnload',
5410
+ 'Error',
5411
+ 'HashChange',
5412
+ 'Load',
5413
+ 'Offline',
5414
+ 'Online',
5415
+ 'PageHide',
5416
+ 'PageShow',
5417
+ 'Resize',
5418
+ 'Scroll',
5419
+ 'Unload',
5420
+ 'Abort',
5421
+ 'CanPlay',
5422
+ 'CanPlayThrough',
5423
+ 'DurationChange',
5424
+ 'Emptied',
5425
+ 'Ended',
5426
+ 'LoadedData',
5427
+ 'LoadedMetadata',
5428
+ 'LoadStart',
5429
+ 'Pause',
5430
+ 'Play',
5431
+ 'Playing',
5432
+ 'Progress',
5433
+ 'RateChange',
5434
+ 'Seeked',
5435
+ 'Seeking',
5436
+ 'Stalled',
5437
+ 'Suspend',
5438
+ 'TimeUpdate',
5439
+ 'VolumeChange',
5440
+ 'Waiting',
5441
+
5442
+ 'TouchCancel',
5443
+ 'TouchEnd',
5444
+ 'TouchMove',
5445
+ 'TouchStart',
5446
+ 'AnimationEnd',
5447
+ 'AnimationIteration',
5448
+ 'AnimationStart',
5449
+ 'TransitionEnd',
5450
+ 'Copy',
5451
+ 'Cut',
5452
+ 'Paste',
5453
+ 'FocusIn',
5454
+ 'FocusOut',
5455
+ 'ContextMenu',
4138
5456
  ];
4139
5457
 
4140
5458
  const EVENTS_WITH_PREVENT = [
4141
- "Click",
4142
- "DblClick",
4143
- "MouseDown",
4144
- "MouseUp",
4145
- "Wheel",
4146
- "KeyDown",
4147
- "KeyPress",
4148
- "Invalid",
4149
- "Reset",
4150
- "Submit",
4151
- "DragOver",
4152
- "Drop",
4153
- "BeforeUnload",
4154
- "TouchCancel",
4155
- "TouchEnd",
4156
- "TouchMove",
4157
- "TouchStart",
4158
- "Copy",
4159
- "Cut",
4160
- "Paste",
4161
- "ContextMenu"
5459
+ 'Click',
5460
+ 'DblClick',
5461
+ 'MouseDown',
5462
+ 'MouseUp',
5463
+ 'Wheel',
5464
+ 'KeyDown',
5465
+ 'KeyPress',
5466
+ 'Invalid',
5467
+ 'Reset',
5468
+ 'Submit',
5469
+ 'DragOver',
5470
+ 'Drop',
5471
+ 'BeforeUnload',
5472
+ 'TouchCancel',
5473
+ 'TouchEnd',
5474
+ 'TouchMove',
5475
+ 'TouchStart',
5476
+ 'Copy',
5477
+ 'Cut',
5478
+ 'Paste',
5479
+ 'ContextMenu',
4162
5480
  ];
4163
5481
 
4164
5482
  const EVENTS_WITH_STOP = [
4165
- "Click",
4166
- "DblClick",
4167
- "MouseDown",
4168
- "MouseMove",
4169
- "MouseOut",
4170
- "MouseOver",
4171
- "MouseUp",
4172
- "Wheel",
4173
- "KeyDown",
4174
- "KeyPress",
4175
- "KeyUp",
4176
- "Change",
4177
- "Input",
4178
- "Invalid",
4179
- "Reset",
4180
- "Search",
4181
- "Select",
4182
- "Submit",
4183
- "Drag",
4184
- "DragEnd",
4185
- "DragEnter",
4186
- "DragLeave",
4187
- "DragOver",
4188
- "DragStart",
4189
- "Drop",
4190
- "BeforeUnload",
4191
- "HashChange",
4192
- "TouchCancel",
4193
- "TouchEnd",
4194
- "TouchMove",
4195
- "TouchStart",
4196
- "AnimationEnd",
4197
- "AnimationIteration",
4198
- "AnimationStart",
4199
- "TransitionEnd",
4200
- "Copy",
4201
- "Cut",
4202
- "Paste",
4203
- "FocusIn",
4204
- "FocusOut",
4205
- "ContextMenu"
5483
+ 'Click',
5484
+ 'DblClick',
5485
+ 'MouseDown',
5486
+ 'MouseMove',
5487
+ 'MouseOut',
5488
+ 'MouseOver',
5489
+ 'MouseUp',
5490
+ 'Wheel',
5491
+ 'KeyDown',
5492
+ 'KeyPress',
5493
+ 'KeyUp',
5494
+ 'Change',
5495
+ 'Input',
5496
+ 'Invalid',
5497
+ 'Reset',
5498
+ 'Search',
5499
+ 'Select',
5500
+ 'Submit',
5501
+ 'Drag',
5502
+ 'DragEnd',
5503
+ 'DragEnter',
5504
+ 'DragLeave',
5505
+ 'DragOver',
5506
+ 'DragStart',
5507
+ 'Drop',
5508
+ 'BeforeUnload',
5509
+ 'HashChange',
5510
+ 'TouchCancel',
5511
+ 'TouchEnd',
5512
+ 'TouchMove',
5513
+ 'TouchStart',
5514
+ 'AnimationEnd',
5515
+ 'AnimationIteration',
5516
+ 'AnimationStart',
5517
+ 'TransitionEnd',
5518
+ 'Copy',
5519
+ 'Cut',
5520
+ 'Paste',
5521
+ 'FocusIn',
5522
+ 'FocusOut',
5523
+ 'ContextMenu',
4206
5524
  ];
4207
5525
 
4208
5526
  const property = {
4209
5527
  configurable: true,
4210
5528
  get() {
4211
5529
  return new NDElement(this);
4212
- }
5530
+ },
4213
5531
  };
4214
5532
 
4215
5533
  Object.defineProperty(HTMLElement.prototype, 'nd', property);
@@ -4220,7 +5538,7 @@ var NativeDocument = (function (exports) {
4220
5538
  configurable: true,
4221
5539
  get: function() {
4222
5540
  return this;
4223
- }
5541
+ },
4224
5542
  });
4225
5543
 
4226
5544
 
@@ -4233,7 +5551,7 @@ var NativeDocument = (function (exports) {
4233
5551
  NDElement.prototype['on'+eventSourceName] = function(callback = null, options = {}) {
4234
5552
  this.$element.addEventListener(eventName, callback, {
4235
5553
  signal: this.$getSignal(),
4236
- ...options
5554
+ ...options,
4237
5555
  });
4238
5556
  return this;
4239
5557
  };
@@ -4244,14 +5562,14 @@ var NativeDocument = (function (exports) {
4244
5562
  NDElement.prototype['onStop'+eventSourceName] = function(callback = null, options = {}) {
4245
5563
  _stop(this.$element, eventName, callback, {
4246
5564
  signal: this.$getSignal(),
4247
- ...options
5565
+ ...options,
4248
5566
  });
4249
5567
  return this;
4250
5568
  };
4251
5569
  NDElement.prototype['onPreventStop'+eventSourceName] = function(callback = null, options = {}) {
4252
5570
  _preventStop(this.$element, eventName, callback, {
4253
5571
  signal: this.$getSignal(),
4254
- ...options
5572
+ ...options,
4255
5573
  });
4256
5574
  return this;
4257
5575
  };
@@ -4262,12 +5580,20 @@ var NativeDocument = (function (exports) {
4262
5580
  NDElement.prototype['onPrevent'+eventSourceName] = function(callback = null, options = {}) {
4263
5581
  _prevent(this.$element, eventName, callback, {
4264
5582
  signal: this.$getSignal(),
4265
- ...options
5583
+ ...options,
4266
5584
  });
4267
5585
  return this;
4268
5586
  };
4269
5587
  });
4270
5588
 
5589
+ /**
5590
+ * Retrieves or creates an AbortController signal tied to this element's lifecycle.
5591
+ * The signal is automatically aborted when the element is removed via .nd.remove().
5592
+ * Used internally by all event listeners (onClick, onInput, etc.) to auto-cleanup on unmount.
5593
+ *
5594
+ * @internal
5595
+ * @returns {AbortSignal} The signal for this element's AbortController
5596
+ */
4271
5597
  NDElement.prototype.$getSignal = function() {
4272
5598
  if(!this.$element.__$controller) {
4273
5599
  this.$element.__$controller = new AbortController();
@@ -4275,27 +5601,63 @@ var NativeDocument = (function (exports) {
4275
5601
  return this.$element.__$controller.signal;
4276
5602
  };
4277
5603
 
5604
+
5605
+ /**
5606
+ * Adds a native event listener to the underlying HTMLElement.
5607
+ * The listener is automatically removed when the element is unmounted (via AbortSignal).
5608
+ *
5609
+ * @param {string} name - Event name (case-insensitive, e.g. 'click', 'input')
5610
+ * @param {EventListener} callback - Handler to call when the event fires
5611
+ * @param {boolean|AddEventListenerOptions} [options] - Listener options merged with the element's AbortSignal
5612
+ * @returns {this}
5613
+ */
4278
5614
  NDElement.prototype.on = function(name, callback, options) {
4279
5615
  this.$element.addEventListener(name.toLowerCase(), callback, {
4280
5616
  signal: this.$getSignal(),
4281
- ...options
5617
+ ...options,
4282
5618
  });
4283
5619
  return this;
4284
5620
  };
4285
5621
 
5622
+ /**
5623
+ * Removes a previously registered event listener from the underlying HTMLElement.
5624
+ *
5625
+ * @param {string} name - Event name (case-insensitive)
5626
+ * @param {EventListener} callback - The exact handler reference to remove
5627
+ * @returns {this}
5628
+ */
4286
5629
  NDElement.prototype.off = function(name, callback) {
4287
5630
  this.$element.removeEventListener(name.toLowerCase(), callback);
4288
5631
  return this;
4289
5632
  };
4290
5633
 
5634
+ /**
5635
+ * Adds a one-time event listener that removes itself after the first call.
5636
+ * Uses the element's AbortSignal for lifecycle-safe cleanup.
5637
+ *
5638
+ * @param {string} name - Event name (case-insensitive)
5639
+ * @param {EventListener} callback - Handler called once when the event fires
5640
+ * @returns {this}
5641
+ */
4291
5642
  NDElement.prototype.once = function(name, callback) {
4292
5643
  this.$element.addEventListener(name.toLowerCase(), callback, {
4293
5644
  signal: this.$getSignal(),
4294
- once: true
5645
+ once: true,
4295
5646
  });
4296
5647
  return this;
4297
5648
  };
4298
5649
 
5650
+ /**
5651
+ * Dispatches a custom event on the underlying HTMLElement.
5652
+ * The event bubbles and is cancelable by default.
5653
+ *
5654
+ * @param {string} name - Custom event name
5655
+ * @param {*} [detail=null] - Data passed in event.detail
5656
+ * @returns {this}
5657
+ * @example
5658
+ * Button('Save').nd.emit('saved', { id: 42 });
5659
+ * // Triggers: element.addEventListener('saved', e => console.log(e.detail.id))
5660
+ */
4299
5661
  NDElement.prototype.emit = function(name, detail = null) {
4300
5662
  const event = new CustomEvent(name, {
4301
5663
  detail,
@@ -4379,7 +5741,7 @@ var NativeDocument = (function (exports) {
4379
5741
  },
4380
5742
  contains(value) {
4381
5743
  return this.getClasses().indexOf(value) >= 0;
4382
- }
5744
+ },
4383
5745
  };
4384
5746
 
4385
5747
  Object.defineProperty(HTMLElement.prototype, 'classes', {
@@ -4387,17 +5749,42 @@ var NativeDocument = (function (exports) {
4387
5749
  get() {
4388
5750
  return {
4389
5751
  $element: this,
4390
- ...classListMethods
5752
+ ...classListMethods,
4391
5753
  };
4392
- }
5754
+ },
4393
5755
  });
4394
5756
 
4395
5757
  DocumentFragment.prototype.__IS_FRAGMENT = true;
4396
5758
 
5759
+ /**
5760
+ * Wraps a function with argument validation based on an ArgTypes schema.
5761
+ * Throws an ArgTypesError if the arguments don't match the schema.
5762
+ *
5763
+ * @param {...ArgType} args - ArgType descriptors defining expected argument types
5764
+ * @returns {Function} Wrapped function that validates its arguments before executing
5765
+ * @example
5766
+ * function greet(name, age) { ... }
5767
+ * const safeGreet = greet.args(ArgTypes.string('name'), ArgTypes.number('age'));
5768
+ * safeGreet('John', 25); // OK
5769
+ * safeGreet('John', 'old'); // throws ArgTypesError
5770
+ */
4397
5771
  Function.prototype.args = function(...args) {
4398
5772
  return exports.withValidation(this, args);
4399
5773
  };
4400
5774
 
5775
+
5776
+ /**
5777
+ * Wraps a function with a try/catch error boundary.
5778
+ * If the function throws, the callback is called with the error and context instead.
5779
+ *
5780
+ * @param {(error: Error, context: { caller: Function, args: any[] }) => *} callback - Error handler
5781
+ * @returns {Function} Wrapped function with error boundary
5782
+ * @example
5783
+ * const safeRender = render.errorBoundary((err, { args }) => {
5784
+ * console.error('Render failed:', err.message);
5785
+ * return Div({}, 'Error');
5786
+ * });
5787
+ */
4401
5788
  Function.prototype.errorBoundary = function(callback) {
4402
5789
  const handler = (...args) => {
4403
5790
  try {
@@ -4417,36 +5804,85 @@ var NativeDocument = (function (exports) {
4417
5804
 
4418
5805
  NDElement.$getChild = ElementCreator.getChild;
4419
5806
 
5807
+ /**
5808
+ * Converts a string to a reactive text node.
5809
+ *
5810
+ * @returns {Text} Static text node containing the string value
5811
+ */
4420
5812
  String.prototype.toNdElement = function () {
4421
5813
  return ElementCreator.createStaticTextNode(null, this);
4422
5814
  };
4423
5815
 
5816
+ /**
5817
+ * Converts a number to a static text node.
5818
+ *
5819
+ * @returns {Text} Static text node containing the number as a string
5820
+ */
4424
5821
  Number.prototype.toNdElement = function () {
4425
5822
  return ElementCreator.createStaticTextNode(null, this.toString());
4426
5823
  };
4427
5824
 
5825
+ /**
5826
+ * Returns the element itself (identity for DOM compatibility).
5827
+ *
5828
+ * @returns {Element} this
5829
+ */
4428
5830
  Element.prototype.toNdElement = function () {
4429
5831
  return this;
4430
5832
  };
5833
+
5834
+ /**
5835
+ * Returns the text node itself (identity for DOM compatibility).
5836
+ *
5837
+ * @returns {Text} this
5838
+ */
4431
5839
  Text.prototype.toNdElement = function () {
4432
5840
  return this;
4433
5841
  };
5842
+
5843
+ /**
5844
+ * Returns the comment node itself (identity for DOM compatibility).
5845
+ *
5846
+ * @returns {Comment} this
5847
+ */
4434
5848
  Comment.prototype.toNdElement = function () {
4435
5849
  return this;
4436
5850
  };
5851
+
5852
+ /**
5853
+ * Returns the document itself (identity for DOM compatibility).
5854
+ *
5855
+ * @returns {Document} this
5856
+ */
4437
5857
  Document.prototype.toNdElement = function () {
4438
5858
  return this;
4439
5859
  };
5860
+
5861
+ /**
5862
+ * Returns the document fragment itself (identity for DOM compatibility).
5863
+ *
5864
+ * @returns {DocumentFragment} this
5865
+ */
4440
5866
  DocumentFragment.prototype.toNdElement = function () {
4441
5867
  return this;
4442
5868
  };
4443
5869
 
5870
+ /**
5871
+ * Converts the ObservableItem to a reactive text node that updates automatically when the value changes.
5872
+ *
5873
+ * @returns {Text} Reactive text node bound to this observable
5874
+ */
4444
5875
  ObservableItem.prototype.toNdElement = function () {
4445
5876
  return ElementCreator.createObservableNode(null, this);
4446
5877
  };
4447
5878
 
4448
5879
  ObservableChecker.prototype.toNdElement = ObservableItem.prototype.toNdElement;
4449
5880
 
5881
+ /**
5882
+ * Converts the NDElement to its underlying HTMLElement (or ghost DOM fragment if ghostDom was used).
5883
+ *
5884
+ * @returns {HTMLElement|DocumentFragment} The underlying DOM node
5885
+ */
4450
5886
  NDElement.prototype.toNdElement = function () {
4451
5887
  const element = this.$element ?? this.$build?.() ?? this.build?.() ?? null;
4452
5888
  if(this.$attachements) {
@@ -4458,6 +5894,12 @@ var NativeDocument = (function (exports) {
4458
5894
  return element;
4459
5895
  };
4460
5896
 
5897
+ /**
5898
+ * Converts the array to a DocumentFragment containing all elements.
5899
+ * Each item is processed through ElementCreator.getChild().
5900
+ *
5901
+ * @returns {DocumentFragment} Fragment containing all array children
5902
+ */
4461
5903
  Array.prototype.toNdElement = function () {
4462
5904
  const fragment = document.createDocumentFragment();
4463
5905
  for(let i = 0, length = this.length; i < length; i++) {
@@ -4468,6 +5910,12 @@ var NativeDocument = (function (exports) {
4468
5910
  return fragment;
4469
5911
  };
4470
5912
 
5913
+ /**
5914
+ * Calls the function and converts its return value to a DOM node.
5915
+ * Used internally by ElementCreator to process function-based children.
5916
+ *
5917
+ * @returns {Node} The DOM node returned by the function
5918
+ */
4471
5919
  Function.prototype.toNdElement = function () {
4472
5920
  const child = this;
4473
5921
  {
@@ -4476,6 +5924,11 @@ var NativeDocument = (function (exports) {
4476
5924
  return ElementCreator.getChild(child());
4477
5925
  };
4478
5926
 
5927
+ /**
5928
+ * Converts the TemplateBinding to a hydratable DOM node for use in TemplateCloner.
5929
+ *
5930
+ * @returns {Node} Hydratable node
5931
+ */
4479
5932
  TemplateBinding.prototype.toNdElement = function () {
4480
5933
  return ElementCreator.createHydratableNode(null, this);
4481
5934
  };
@@ -4514,6 +5967,16 @@ var NativeDocument = (function (exports) {
4514
5967
  });
4515
5968
  };
4516
5969
 
5970
+ /**
5971
+ * Registers a beforeUnmount hook that plays an exit CSS transition before the element is removed.
5972
+ * Adds the class `{transitionName}-exit`, waits for the transition/animation to end, then removes it.
5973
+ *
5974
+ * @param {string} transitionName - CSS class prefix for the exit transition
5975
+ * @returns {this}
5976
+ * @example
5977
+ * Div({ class: 'modal' }).nd.transitionOut('fade');
5978
+ * // Adds 'fade-exit' before removal, waits for transitionend/animationend
5979
+ */
4517
5980
  NDElement.prototype.transitionOut = function(transitionName) {
4518
5981
  const exitClass = transitionName + '-exit';
4519
5982
  const el = this.$element;
@@ -4525,6 +5988,17 @@ var NativeDocument = (function (exports) {
4525
5988
  return this;
4526
5989
  };
4527
5990
 
5991
+ /**
5992
+ * Plays an enter CSS transition when the element is mounted into the DOM.
5993
+ * Adds `{transitionName}-enter-from` immediately, then swaps to `{transitionName}-enter-to`
5994
+ * on the next animation frame, and cleans up after the transition ends.
5995
+ *
5996
+ * @param {string} transitionName - CSS class prefix for the enter transition
5997
+ * @returns {this}
5998
+ * @example
5999
+ * Div({ class: 'modal' }).nd.transitionIn('fade');
6000
+ * // On mount: adds 'fade-enter-from', then swaps to 'fade-enter-to'
6001
+ */
4528
6002
  NDElement.prototype.transitionIn = function(transitionName) {
4529
6003
  const startClass = transitionName + '-enter-from';
4530
6004
  const endClass = transitionName + '-enter-to';
@@ -4548,13 +6022,32 @@ var NativeDocument = (function (exports) {
4548
6022
  return this;
4549
6023
  };
4550
6024
 
4551
-
6025
+ /**
6026
+ * Applies both enter and exit transitions to the element.
6027
+ * Shorthand for calling .transitionIn(name) and .transitionOut(name).
6028
+ *
6029
+ * @param {string} transitionName - CSS class prefix for both enter and exit transitions
6030
+ * @returns {this}
6031
+ * @example
6032
+ * Div({}).nd.transition('slide');
6033
+ * // On mount: enter transition; on unmount: exit transition
6034
+ */
4552
6035
  NDElement.prototype.transition = function (transitionName) {
4553
6036
  this.transitionIn(transitionName);
4554
6037
  this.transitionOut(transitionName);
4555
6038
  return this;
4556
6039
  };
4557
6040
 
6041
+ /**
6042
+ * Immediately applies a CSS animation class to the element.
6043
+ * Removes the class automatically once the animation ends.
6044
+ *
6045
+ * @param {string} animationName - CSS animation class name to add
6046
+ * @returns {this}
6047
+ * @example
6048
+ * Button('Click me').nd.animate('shake');
6049
+ * // Adds 'shake' class, removes it when animationend fires
6050
+ */
4558
6051
  NDElement.prototype.animate = function(animationName) {
4559
6052
  const el = this.$element;
4560
6053
  el.classes.add(animationName);
@@ -4577,14 +6070,17 @@ var NativeDocument = (function (exports) {
4577
6070
 
4578
6071
  ObservableChecker.prototype.handleNdAttribute = ObservableItem.prototype.handleNdAttribute;
4579
6072
 
4580
- TemplateBinding.prototype.handleNdAttribute = function(element, attributeName) {
6073
+ TemplateBinding.prototype.handleNdAttribute = function(element, attributeName) {
4581
6074
  this.$hydrate(element, attributeName);
4582
6075
  };
4583
6076
 
4584
6077
  /**
6078
+ * Creates a reactive or static text node from the given value.
6079
+ * If the value has a .toNdElement() method, delegates to it.
6080
+ * Otherwise creates an empty text node.
4585
6081
  *
4586
- * @param {*} value
4587
- * @returns {Text}
6082
+ * @param {string|number|ObservableItem|*} value - Value to convert to a text node
6083
+ * @returns {Text} Text node, reactive if value is an ObservableItem
4588
6084
  */
4589
6085
  const createTextNode = (value) => {
4590
6086
  if(value) {
@@ -4594,8 +6090,18 @@ var NativeDocument = (function (exports) {
4594
6090
  };
4595
6091
 
4596
6092
 
6093
+ /**
6094
+ * Applies attributes and children to an existing HTMLElement.
6095
+ * Used internally by HtmlElementWrapper on each cloned node.
6096
+ *
6097
+ * @internal
6098
+ * @param {HTMLElement} element - Element to configure
6099
+ * @param {Object|null} _attributes - Attributes object or children if no attrs provided
6100
+ * @param {ValidChild|null} [_children=null] - Children to append
6101
+ * @returns {HTMLElement} The configured element
6102
+ */
4597
6103
  const createHtmlElement = (element, _attributes, _children = null) => {
4598
- let { props: attributes, children = null } = normalizeComponentArgs(_attributes, _children);
6104
+ const { props: attributes, children = null } = normalizeComponentArgs(_attributes, _children);
4599
6105
 
4600
6106
  ElementCreator.processAttributes(element, attributes);
4601
6107
  ElementCreator.processChildren(children, element);
@@ -4603,10 +6109,22 @@ var NativeDocument = (function (exports) {
4603
6109
  };
4604
6110
 
4605
6111
  /**
6112
+ * Creates a reusable element factory function for the given HTML tag.
6113
+ * The factory clones a cached template node on each call for performance.
6114
+ * Optionally wraps the created element with a custom wrapper function.
4606
6115
  *
4607
- * @param {string} name
4608
- * @param {?Function=} customWrapper
4609
- * @returns {Function}
6116
+ * @param {string} name - HTML tag name (e.g. 'div', 'button', 'input'). Pass empty string to create a Fragment.
6117
+ * @param {((element: HTMLElement) => HTMLElement)|null} [customWrapper=null] - Optional function to augment the element before returning
6118
+ * @returns {(attr?: Object, children?: ValidChild) => HTMLElement} Element factory function
6119
+ * @example
6120
+ * const Div = HtmlElementWrapper('div');
6121
+ * Div({ class: 'container' }, 'Hello');
6122
+ *
6123
+ * // With custom wrapper
6124
+ * const Form = HtmlElementWrapper('form', (el) => {
6125
+ * el.submit = (action) => { ... };
6126
+ * return el;
6127
+ * });
4610
6128
  */
4611
6129
  function HtmlElementWrapper(name, customWrapper = null) {
4612
6130
  if(name) {
@@ -4619,7 +6137,7 @@ var NativeDocument = (function (exports) {
4619
6137
  };
4620
6138
  return createHtmlElement(customWrapper(node.cloneNode()), attr, children); };
4621
6139
 
4622
- return (attr, children) => createElement(attr, children)
6140
+ return (attr, children) => createElement(attr, children);
4623
6141
  }
4624
6142
 
4625
6143
  let node = null;
@@ -4631,7 +6149,7 @@ var NativeDocument = (function (exports) {
4631
6149
  return createHtmlElement(node.cloneNode(), attr, children);
4632
6150
  };
4633
6151
 
4634
- return (attr, children) => createElement(attr, children)
6152
+ return (attr, children) => createElement(attr, children);
4635
6153
  }
4636
6154
  return (children, name = '') => {
4637
6155
  const anchor = Anchor(name);
@@ -4640,6 +6158,15 @@ var NativeDocument = (function (exports) {
4640
6158
  };
4641
6159
  }
4642
6160
 
6161
+ /**
6162
+ * Stores deferred attribute, class, style, and event bindings for a cloneable element.
6163
+ * Used internally by TemplateCloner to apply per-instance data to cloned DOM nodes.
6164
+ * Not intended for direct use in application code.
6165
+ *
6166
+ * @internal
6167
+ * @constructor
6168
+ * @param {HTMLElement} $element - The template element to clone
6169
+ */
4643
6170
  function NodeCloner($element) {
4644
6171
  this.$element = $element;
4645
6172
  this.$classes = null;
@@ -4679,6 +6206,12 @@ var NativeDocument = (function (exports) {
4679
6206
  return cache;
4680
6207
  };
4681
6208
 
6209
+ /**
6210
+ * Pre-compiles all registered bindings into a sequence of optimised steps.
6211
+ * Called once before the first clone operation. Subsequent calls are no-ops.
6212
+ *
6213
+ * @internal
6214
+ */
4682
6215
  NodeCloner.prototype.resolve = function() {
4683
6216
  if(this.$content) {
4684
6217
  return;
@@ -4765,26 +6298,56 @@ var NativeDocument = (function (exports) {
4765
6298
  };
4766
6299
  };
4767
6300
 
6301
+ /**
6302
+ * Clones the template element and applies all compiled binding steps with the given data.
6303
+ *
6304
+ * @internal
6305
+ * @param {Array} data - Data array passed to each binding callback
6306
+ * @returns {HTMLElement} The cloned and hydrated element
6307
+ */
4768
6308
  NodeCloner.prototype.cloneNode = function(data) {
4769
6309
  return this.$element.cloneNode(false);
4770
6310
  };
4771
6311
 
6312
+ /**
6313
+ * Registers an NDElement method binding (e.g. onClick, onInput) to be applied on each clone.
6314
+ *
6315
+ * @internal
6316
+ * @param {string} methodName - Name of the NDElement method to call (e.g. 'onClick')
6317
+ * @param {Function} callback - Callback function to pass to the method
6318
+ * @returns {NodeCloner} this
6319
+ */
4772
6320
  NodeCloner.prototype.attach = function(methodName, callback) {
4773
6321
  this.$ndMethods = this.$ndMethods || {};
4774
6322
  this.$ndMethods[methodName] = callback;
4775
6323
  return this;
4776
6324
  };
4777
6325
 
4778
- NodeCloner.prototype.text = function(value) {
4779
- this.$content = value;
4780
- if(typeof value === 'function') {
4781
- this.cloneNode = (data) => createTextNode(value.apply(null, data));
6326
+ /**
6327
+ * Registers a reactive text content binding for the element.
6328
+ *
6329
+ * @internal
6330
+ * @param {Function} valueorProperty - Function receiving data and returning the text content
6331
+ * @returns {NodeCloner} this
6332
+ */
6333
+ NodeCloner.prototype.text = function(valueorProperty) {
6334
+ this.$content = valueorProperty;
6335
+ if(typeof valueorProperty === 'function') {
6336
+ this.cloneNode = (data) => createTextNode(valueorProperty.apply(null, data));
4782
6337
  return this;
4783
6338
  }
4784
- this.cloneNode = (data) => createTextNode(data[0][value]);
6339
+ this.cloneNode = (data) => createTextNode(data[0][valueorProperty]);
4785
6340
  return this;
4786
6341
  };
4787
6342
 
6343
+ /**
6344
+ * Registers an attribute binding to be applied on each clone.
6345
+ *
6346
+ * @internal
6347
+ * @param {string} attrName - Attribute name
6348
+ * @param {{property: string, value: *}} value - Function receiving data and returning the attribute value
6349
+ * @returns {NodeCloner} this
6350
+ */
4788
6351
  NodeCloner.prototype.attr = function(attrName, value) {
4789
6352
  if(attrName === 'class') {
4790
6353
  this.$classes = this.$classes || {};
@@ -4801,6 +6364,16 @@ var NativeDocument = (function (exports) {
4801
6364
  return this;
4802
6365
  };
4803
6366
 
6367
+ /**
6368
+ * Applies a binding to a DOM node via its NodeCloner, routing to the correct binding type.
6369
+ * Called internally by TemplateCloner's binder methods (text, style, class, attr, event).
6370
+ *
6371
+ * @internal
6372
+ * @param {Function|string} value - Binding callback or property name
6373
+ * @param {'value'|'style'|'class'|'attach'|string} targetType - Binding type determining which NodeCloner method to call
6374
+ * @param {HTMLElement} element - Target DOM element
6375
+ * @param {string} property - Attribute name or method name (used for 'attach' and 'attr' types)
6376
+ */
4804
6377
  const $hydrateFn = function(value, targetType, element, property) {
4805
6378
  element.nodeCloner = element.nodeCloner || new NodeCloner(element);
4806
6379
  if(targetType === 'value') {
@@ -4814,6 +6387,23 @@ var NativeDocument = (function (exports) {
4814
6387
  element.nodeCloner.attr(targetType, { property, value });
4815
6388
  };
4816
6389
 
6390
+ /**
6391
+ * Creates a high-performance template cloner for repeated rendering of the same structure.
6392
+ * On the first call, builds the template by calling $fn with a binder object.
6393
+ * On subsequent calls, clones the compiled template and hydrates it with new data.
6394
+ * Used internally by ForEachArray and other list renderers.
6395
+ *
6396
+ * @constructor
6397
+ * @param {(binder: TemplateCloner) => HTMLElement} $fn - Function that builds the template using binder methods
6398
+ * @example
6399
+ * const cloner = new TemplateCloner((t) =>
6400
+ * Div({},
6401
+ * Span(t.text((item) => item.name)),
6402
+ * Button({}, 'Delete').nd.onClick(t.event((item) => () => list.removeItem(item)))
6403
+ * )
6404
+ * );
6405
+ * cloner.clone([item]); // returns a hydrated clone
6406
+ */
4817
6407
  function TemplateCloner($fn) {
4818
6408
  let $node = null;
4819
6409
 
@@ -4858,6 +6448,14 @@ var NativeDocument = (function (exports) {
4858
6448
  return containDynamicNode;
4859
6449
  };
4860
6450
 
6451
+
6452
+ /**
6453
+ * Clones the compiled template and hydrates it with the given data.
6454
+ * On the first call, also compiles the template (builds and optimizes binding steps).
6455
+ *
6456
+ * @param {Array} data - Data array passed to all binding callbacks
6457
+ * @returns {HTMLElement} Cloned and hydrated DOM node
6458
+ */
4861
6459
  this.clone = (data) => {
4862
6460
  const binder = createTemplateCloner(this);
4863
6461
  $node = $fn(binder);
@@ -4876,25 +6474,69 @@ var NativeDocument = (function (exports) {
4876
6474
  });
4877
6475
  };
4878
6476
 
6477
+ /**
6478
+ * Creates a style binding — the result of fn(data) is applied as inline styles.
6479
+ *
6480
+ * @param {((...data: any[]) => Record<string, string>)} fn - Function returning a style object
6481
+ * @returns {TemplateBinding}
6482
+ */
4879
6483
  this.style = (fn) => {
4880
6484
  return createBinding(fn, 'style');
4881
6485
  };
6486
+
6487
+ /**
6488
+ * Creates a class binding — the result of fn(data) is applied as a class map.
6489
+ *
6490
+ * @param {((...data: any[]) => Record<string, boolean>)} fn - Function returning a class map
6491
+ * @returns {TemplateBinding}
6492
+ */
4882
6493
  this.class = (fn) => {
4883
6494
  return createBinding(fn, 'class');
4884
6495
  };
6496
+
4885
6497
  this.property = (propertyName) => {
4886
6498
  return this.value(propertyName);
4887
6499
  };
6500
+
6501
+ /**
6502
+ * Creates a text/value binding — the result is set as text content or input value.
6503
+ * Alias: .text()
6504
+ *
6505
+ * @param {string|(((...data: any[]) => string))} callbackOrProperty - Property name (string) or callback returning the value
6506
+ * @returns {TemplateBinding}
6507
+ */
4888
6508
  this.value = (callbackOrProperty) => {
4889
6509
  return createBinding(callbackOrProperty, 'value');
4890
6510
  };
6511
+
6512
+ /**
6513
+ * Alias for .value() — creates a text content binding.
6514
+ *
6515
+ * @param {string|(((...data: any[]) => string))} callbackOrProperty
6516
+ * @returns {TemplateBinding}
6517
+ */
4891
6518
  this.text = this.value;
6519
+
6520
+ /**
6521
+ * Creates an attribute binding — the result of fn(data) is set as an attribute value.
6522
+ *
6523
+ * @param {((...data: any[]) => string)} fn - Function returning the attribute value
6524
+ * @returns {TemplateBinding}
6525
+ */
4892
6526
  this.attr = (fn) => {
4893
6527
  return createBinding(fn, 'attributes');
4894
6528
  };
6529
+
6530
+ /**
6531
+ * Creates an event binding — fn(data) returns the event handler to attach.
6532
+ *
6533
+ * @param {((...data: any[]) => EventListener)} fn - Function returning the event callback
6534
+ * @returns {TemplateBinding}
6535
+ */
4895
6536
  this.attach = (fn) => {
4896
6537
  return createBinding(fn, 'attach');
4897
6538
  };
6539
+
4898
6540
  this.callback = this.attach;
4899
6541
  }
4900
6542
 
@@ -4907,7 +6549,7 @@ var NativeDocument = (function (exports) {
4907
6549
  }
4908
6550
  if (typeof prop === 'symbol') return target[prop];
4909
6551
  return target.value(prop);
4910
- }
6552
+ },
4911
6553
  });
4912
6554
  };
4913
6555
 
@@ -4932,10 +6574,34 @@ var NativeDocument = (function (exports) {
4932
6574
  };
4933
6575
  }
4934
6576
 
6577
+ /**
6578
+ * Creates a singleton view — a component that is instantiated only once,
6579
+ * then reused across multiple renders. Useful for performance-critical components
6580
+ * that are frequently shown/hidden or repeated in lists.
6581
+ *
6582
+ * @constructor
6583
+ * @param {(instance: SingletonView) => Node} $viewCreator - Function that builds the view once and returns the root node.
6584
+ * Receives the SingletonView instance so it can call .createSection().
6585
+ * @example
6586
+ * const Card = useSingleton((view) => {
6587
+ * const nameSection = view.createSection('name');
6588
+ * return Div({ class: 'card' }, nameSection);
6589
+ * });
6590
+ * Card([{ name: 'John' }]); // Renders once, reused on subsequent calls
6591
+ */
4935
6592
  function SingletonView($viewCreator) {
4936
6593
  let $cacheNode = null;
4937
6594
  let $components = null;
4938
6595
 
6596
+
6597
+ /**
6598
+ * Renders the singleton view with the given data.
6599
+ * On the first call, create the view by calling $viewCreator.
6600
+ * On later calls, updates registered sections via their update functions.
6601
+ *
6602
+ * @param {Array} data - Array where data[0] is an object mapping section names to new content
6603
+ * @returns {Node} The cached root node
6604
+ */
4939
6605
  this.render = (data) => {
4940
6606
  if(!$cacheNode) {
4941
6607
  $cacheNode = $viewCreator(this);
@@ -4953,6 +6619,18 @@ var NativeDocument = (function (exports) {
4953
6619
  return $cacheNode;
4954
6620
  };
4955
6621
 
6622
+
6623
+ /**
6624
+ * Creates a named anchor section inside the singleton view.
6625
+ * The section can be updated later by passing new content through .render().
6626
+ *
6627
+ * @param {string} name - Unique section name used as the update key
6628
+ * @param {((content: *) => Node)?} [fn] - Optional transform function applied to new content before inserting
6629
+ * @returns {AnchorDocumentFragment} Anchor fragment to place inside the view's DOM
6630
+ * @example
6631
+ * const nameSection = view.createSection('name');
6632
+ * // Later: Card([{ name: 'Jane' }]); // replaces content in nameSection
6633
+ */
4956
6634
  this.createSection = (name, fn) => {
4957
6635
  $components = $components || {};
4958
6636
  const anchor = Anchor('Component ' + name);
@@ -4970,6 +6648,19 @@ var NativeDocument = (function (exports) {
4970
6648
  }
4971
6649
 
4972
6650
 
6651
+ /**
6652
+ * Creates a memoized factory that returns a SingletonView instance.
6653
+ * The singleton is created on the first call and reused for all subsequent calls.
6654
+ *
6655
+ * @param {(instance: SingletonView) => Node} fn - View creator function passed to SingletonView
6656
+ * @returns {(...args: any[]) => Node} Function that renders the singleton with the given data
6657
+ * @example
6658
+ * const Card = useSingleton((view) => {
6659
+ * const title = view.createSection('title');
6660
+ * return Div({}, title);
6661
+ * });
6662
+ * Card([{ title: 'Hello' }]);
6663
+ */
4973
6664
  function useSingleton(fn) {
4974
6665
  let $cache = null;
4975
6666
 
@@ -4982,7 +6673,7 @@ var NativeDocument = (function (exports) {
4982
6673
  }
4983
6674
 
4984
6675
  const cssPropertyAccumulator = function(initialValue = {}) {
4985
- let data = Validator.isString(initialValue) ? initialValue.split(';').filter(Boolean) : initialValue;
6676
+ const data = Validator.isString(initialValue) ? initialValue.split(';').filter(Boolean) : initialValue;
4986
6677
 
4987
6678
  return {
4988
6679
  add(key, value) {
@@ -5009,7 +6700,7 @@ var NativeDocument = (function (exports) {
5009
6700
  };
5010
6701
 
5011
6702
  const classPropertyAccumulator = function(initialValue = []) {
5012
- let data = Validator.isString(initialValue) ? initialValue.split(" ").filter(Boolean) : initialValue;
6703
+ let data = Validator.isString(initialValue) ? initialValue.split(' ').filter(Boolean) : initialValue;
5013
6704
 
5014
6705
  return {
5015
6706
  add(key, value = true) {
@@ -5052,6 +6743,18 @@ var NativeDocument = (function (exports) {
5052
6743
  };
5053
6744
  };
5054
6745
 
6746
+ /**
6747
+ * Wraps a function so it executes at most once.
6748
+ * Later calls return the cached result without re-executing the function.
6749
+ *
6750
+ * @template T
6751
+ * @param {(...args: any[]) => T} fn - Function to wrap
6752
+ * @returns {(...args: any[]) => T} Memoized function
6753
+ * @example
6754
+ * const init = once(() => expensiveSetup());
6755
+ * init(); // runs setup
6756
+ * init(); // returns cached result
6757
+ */
5055
6758
  const once$1 = (fn) => {
5056
6759
  let result = null;
5057
6760
  return (...args) => {
@@ -5063,6 +6766,18 @@ var NativeDocument = (function (exports) {
5063
6766
  };
5064
6767
  };
5065
6768
 
6769
+ /**
6770
+ * Creates a lazy proxy that calls fn() once on first property access,
6771
+ * then returns properties from the cached result for all later accesses.
6772
+ *
6773
+ * @template T
6774
+ * @param {() => T} fn - Factory function to call once
6775
+ * @returns {T} Proxy to the lazily created object
6776
+ * @example
6777
+ * const store = autoOnce(() => createExpensiveStore());
6778
+ * store.count; // triggers createExpensiveStore() on first access
6779
+ * store.name; // uses a cached result
6780
+ */
5066
6781
  const autoOnce = (fn) => {
5067
6782
  let target = null;
5068
6783
  return new Proxy({}, {
@@ -5072,10 +6787,22 @@ var NativeDocument = (function (exports) {
5072
6787
  }
5073
6788
  target = fn();
5074
6789
  return target[key];
5075
- }
6790
+ },
5076
6791
  });
5077
6792
  };
5078
6793
 
6794
+ /**
6795
+ * Wraps a function with key-based memoization.
6796
+ * The first argument is used as the cache key; later arguments are passed to fn.
6797
+ *
6798
+ * @template T
6799
+ * @param {(...args: any[]) => T} fn - Function to memoize
6800
+ * @returns {(key: any, ...args: any[]) => T} Memoized function
6801
+ * @example
6802
+ * const getUser = memoize((id) => fetchUser(id));
6803
+ * getUser('user-1', 1); // fetches
6804
+ * getUser('user-1', 1); // returns cached
6805
+ */
5079
6806
  const memoize$1 = (fn) => {
5080
6807
  const cache = new Map();
5081
6808
  return (...args) => {
@@ -5090,6 +6817,23 @@ var NativeDocument = (function (exports) {
5090
6817
  };
5091
6818
  };
5092
6819
 
6820
+ /**
6821
+ * Creates a proxy where each property access memorizes the result of calling fn with the property key.
6822
+ * If fn accepts arguments (fn.length > 0), the proxy returns a memoized function instead.
6823
+ *
6824
+ * @template T
6825
+ * @param {((key: string|symbol, ...args?: any[]) => T)} fn - Factory function
6826
+ * @returns {Record<string|symbol, T>} Proxy with per-key memoized results
6827
+ * @example
6828
+ * // fn with no args — result memoized by key
6829
+ * const icons = autoMemoize((name) => loadIcon(name));
6830
+ * icons.home; // calls loadIcon('home'), caches result
6831
+ * icons.home; // returns cached
6832
+ *
6833
+ * // fn with args — returns a memoized function per key
6834
+ * const formatters = autoMemoize((locale, value) => format(value, locale));
6835
+ * formatters.fr('hello'); // calls format('hello', 'fr'), caches under 'fr'
6836
+ */
5093
6837
  const autoMemoize = (fn) => {
5094
6838
  const cache = new Map();
5095
6839
  return new Proxy({}, {
@@ -5104,15 +6848,20 @@ var NativeDocument = (function (exports) {
5104
6848
  const result = fn(...args, key);
5105
6849
  cache.set(key, result);
5106
6850
  return result;
5107
- }
6851
+ };
5108
6852
  }
5109
6853
  const result = fn(key);
5110
6854
  cache.set(key, result);
5111
6855
  return result;
5112
- }
6856
+ },
5113
6857
  });
5114
6858
  };
5115
6859
 
6860
+ const WRITE_METHODS = [
6861
+ 'use', 'get', 'create', 'createResettable', 'createComposed',
6862
+ 'createPersistent', 'createPersistentResettable', 'delete', 'reset',
6863
+ ];
6864
+
5116
6865
  const StoreFactory = function() {
5117
6866
 
5118
6867
  const $stores = new Map();
@@ -5126,7 +6875,7 @@ var NativeDocument = (function (exports) {
5126
6875
  if (!item) {
5127
6876
  DebugManager$2.error('Store', `Store.${method}('${name}') : store not found. Did you call Store.create('${name}') first?`);
5128
6877
  throw new NativeDocumentError(
5129
- `Store.${method}('${name}') : store not found.`
6878
+ `Store.${method}('${name}') : store not found.`,
5130
6879
  );
5131
6880
  }
5132
6881
  return item;
@@ -5139,7 +6888,7 @@ var NativeDocument = (function (exports) {
5139
6888
  const readOnlyError = (method) => () => {
5140
6889
  DebugManager$2.error('Store', `Store.${context}('${name}') is read-only. '${method}()' is not allowed.`);
5141
6890
  throw new NativeDocumentError(
5142
- `Store.${context}('${name}') is read-only.`
6891
+ `Store.${context}('${name}') is read-only.`,
5143
6892
  );
5144
6893
  };
5145
6894
  observer.set = readOnlyError('set');
@@ -5151,7 +6900,7 @@ var NativeDocument = (function (exports) {
5151
6900
  if(Array.isArray(value)) {
5152
6901
  return Observable.array(value, options);
5153
6902
  }
5154
- if(typeof value === 'object') {
6903
+ if(Validator.isJson(value)) {
5155
6904
  return Observable.object(value, options);
5156
6905
  }
5157
6906
  return Observable(value, options);
@@ -5170,7 +6919,7 @@ var NativeDocument = (function (exports) {
5170
6919
  if ($stores.has(name)) {
5171
6920
  DebugManager$2.warn('Store', `Store.create('${name}') : a store with this name already exists. Use Store.get('${name}') to retrieve it.`);
5172
6921
  throw new NativeDocumentError(
5173
- `Store.create('${name}') : a store with this name already exists.`
6922
+ `Store.create('${name}') : a store with this name already exists.`,
5174
6923
  );
5175
6924
  }
5176
6925
  const observer = $createObservable(value);
@@ -5191,7 +6940,7 @@ var NativeDocument = (function (exports) {
5191
6940
  if ($stores.has(name)) {
5192
6941
  DebugManager$2.warn('Store', `Store.createResettable('${name}') : a store with this name already exists.`);
5193
6942
  throw new NativeDocumentError(
5194
- `Store.createResettable('${name}') : a store with this name already exists.`
6943
+ `Store.createResettable('${name}') : a store with this name already exists.`,
5195
6944
  );
5196
6945
  }
5197
6946
  const observer = $createObservable(value, { reset: true });
@@ -5227,17 +6976,17 @@ var NativeDocument = (function (exports) {
5227
6976
  if ($stores.has(name)) {
5228
6977
  DebugManager$2.warn('Store', `Store.createComposed('${name}') : a store with this name already exists.`);
5229
6978
  throw new NativeDocumentError(
5230
- `Store.createComposed('${name}') : a store with this name already exists.`
6979
+ `Store.createComposed('${name}') : a store with this name already exists.`,
5231
6980
  );
5232
6981
  }
5233
6982
  if (typeof computation !== 'function') {
5234
6983
  throw new NativeDocumentError(
5235
- `Store.createComposed('${name}') : computation must be a function.`
6984
+ `Store.createComposed('${name}') : computation must be a function.`,
5236
6985
  );
5237
6986
  }
5238
6987
  if (!Array.isArray(dependencies) || dependencies.length === 0) {
5239
6988
  throw new NativeDocumentError(
5240
- `Store.createComposed('${name}') : dependencies must be a non-empty array of store names.`
6989
+ `Store.createComposed('${name}') : dependencies must be a non-empty array of store names.`,
5241
6990
  );
5242
6991
  }
5243
6992
 
@@ -5250,7 +6999,7 @@ var NativeDocument = (function (exports) {
5250
6999
  if (!depItem) {
5251
7000
  DebugManager$2.error('Store', `Store.createComposed('${name}') : dependency '${depName}' not found. Create it first.`);
5252
7001
  throw new NativeDocumentError(
5253
- `Store.createComposed('${name}') : dependency store '${depName}' not found.`
7002
+ `Store.createComposed('${name}') : dependency store '${depName}' not found.`,
5254
7003
  );
5255
7004
  }
5256
7005
  return depItem.observer;
@@ -5284,13 +7033,13 @@ var NativeDocument = (function (exports) {
5284
7033
  if (item.composed) {
5285
7034
  DebugManager$2.error('Store', `Store.reset('${name}') : composed stores cannot be reset. Their value is derived from dependencies.`);
5286
7035
  throw new NativeDocumentError(
5287
- `Store.reset('${name}') : composed stores cannot be reset.`
7036
+ `Store.reset('${name}') : composed stores cannot be reset.`,
5288
7037
  );
5289
7038
  }
5290
7039
  if (!item.resettable) {
5291
7040
  DebugManager$2.error('Store', `Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`);
5292
7041
  throw new NativeDocumentError(
5293
- `Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`
7042
+ `Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`,
5294
7043
  );
5295
7044
  }
5296
7045
  item.observer.reset();
@@ -5311,7 +7060,7 @@ var NativeDocument = (function (exports) {
5311
7060
  if (item.composed) {
5312
7061
  DebugManager$2.error('Store', `Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`);
5313
7062
  throw new NativeDocumentError(
5314
- `Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`
7063
+ `Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`,
5315
7064
  );
5316
7065
  }
5317
7066
 
@@ -5349,7 +7098,9 @@ var NativeDocument = (function (exports) {
5349
7098
  const { observer: originalObserver, subscribers } = $getStoreOrThrow('follow', name);
5350
7099
  const observerFollower = $createObservable(originalObserver.val());
5351
7100
 
5352
- const onStoreChange = value => observerFollower.set(value);
7101
+ const originalSet = observerFollower.set.bind(observerFollower);
7102
+ const onStoreChange = value => originalSet(value);
7103
+
5353
7104
  originalObserver.subscribe(onStoreChange);
5354
7105
 
5355
7106
  $applyReadOnly(observerFollower, name, 'follow');
@@ -5405,6 +7156,7 @@ var NativeDocument = (function (exports) {
5405
7156
  item.subscribers.clear();
5406
7157
  item.observer.cleanup();
5407
7158
  $stores.delete(name);
7159
+ $followersCache.delete(name);
5408
7160
  },
5409
7161
  /**
5410
7162
  * Creates an isolated store group with its own state namespace.
@@ -5459,6 +7211,26 @@ var NativeDocument = (function (exports) {
5459
7211
  callback && callback(store);
5460
7212
  return store;
5461
7213
  },
7214
+
7215
+ /**
7216
+ * Creates a store that is automatically persisted to localStorage.
7217
+ * On creation, the store is initialized with the value from localStorage
7218
+ * if it exists, otherwise falls back to the provided default value.
7219
+ * Every mutation is automatically saved to localStorage.
7220
+ *
7221
+ * @param {string} name - Store name
7222
+ * @param {*} value - Default value if nothing is found in localStorage
7223
+ * @param {string} [localstorage_key] - Custom localStorage key. Defaults to the store name.
7224
+ * @returns {ObservableItem}
7225
+ *
7226
+ * @example
7227
+ * const $theme = Store.createPersistent('theme', 'light');
7228
+ *
7229
+ * $theme.set('dark'); // saved to localStorage automatically
7230
+ *
7231
+ * // With a custom key
7232
+ * const $lang = Store.createPersistent('language', 'en', 'nd:lang');
7233
+ */
5462
7234
  createPersistent(name, value, localstorage_key) {
5463
7235
  localstorage_key = localstorage_key || name;
5464
7236
  const observer = this.create(name, $getFromStorage(localstorage_key, value));
@@ -5467,6 +7239,28 @@ var NativeDocument = (function (exports) {
5467
7239
  observer.subscribe((val) => saver(localstorage_key, val));
5468
7240
  return observer;
5469
7241
  },
7242
+
7243
+ /**
7244
+ * Creates a resettable store that is automatically persisted to localStorage.
7245
+ * On creation, the store is initialized with the value from localStorage
7246
+ * if it exists, otherwise falls back to the provided default value.
7247
+ * Every mutation is automatically saved to localStorage.
7248
+ * Calling reset() restores the initial value AND removes the localStorage entry.
7249
+ *
7250
+ * @param {string} name - Store name
7251
+ * @param {*} value - Default value if nothing is found in localStorage
7252
+ * @param {string} [localstorage_key] - Custom localStorage key. Defaults to the store name.
7253
+ * @returns {ObservableItem}
7254
+ *
7255
+ * @example
7256
+ * const $filters = Store.createPersistentResettable('filters', { category: null, date: null });
7257
+ *
7258
+ * $filters.set({ category: 'news', date: '2024-01-01' }); // saved to localStorage
7259
+ * $filters.reset(); // restored to { category: null, date: null } + localStorage entry removed
7260
+ *
7261
+ * // With a custom key
7262
+ * const $prefs = Store.createPersistentResettable('preferences', { lang: 'en' }, 'nd:prefs');
7263
+ */
5470
7264
  createPersistentResettable(name, value, localstorage_key) {
5471
7265
  localstorage_key = localstorage_key || name;
5472
7266
  const observer = this.createResettable(name, $getFromStorage(localstorage_key, value));
@@ -5480,7 +7274,68 @@ var NativeDocument = (function (exports) {
5480
7274
  };
5481
7275
 
5482
7276
  return observer;
5483
- }
7277
+ },
7278
+ /**
7279
+ * Returns a read-only proxy of this store.
7280
+ * All write operations (use, get, create, createResettable, createComposed,
7281
+ * createPersistent, createPersistentResettable, delete, reset) will throw.
7282
+ * Property access returns a read-only follower via follow().
7283
+ *
7284
+ * The recommended pattern is to keep the original store private
7285
+ * and export only the protected version as the public contract.
7286
+ *
7287
+ * @returns {Proxy}
7288
+ *
7289
+ * @example
7290
+ * // store/user.store.js
7291
+ *
7292
+ * const PrivateUserStore = Store.group('user', (group) => {
7293
+ * group.create('profile', null);
7294
+ * group.create('role', 'viewer');
7295
+ * group.create('token', null);
7296
+ * });
7297
+ *
7298
+ * // Only the read-only version is exported
7299
+ * export const UserStore = PrivateUserStore.protected();
7300
+ *
7301
+ * // --- In any other module ---
7302
+ *
7303
+ * import { UserStore } from './store/user.store.js';
7304
+ *
7305
+ * UserStore.profile // ✅ follower read-only
7306
+ * UserStore.follow('role') // ✅
7307
+ * UserStore.has('token') // ✅
7308
+ *
7309
+ * UserStore.use('profile') // ❌ throws — read-only store
7310
+ * UserStore.get('profile') // ❌ throws — read-only store
7311
+ * UserStore.create('x', 1) // ❌ throws — read-only store
7312
+ */
7313
+ protected() {
7314
+ return new Proxy($api, {
7315
+ get(target, prop) {
7316
+ if (typeof prop === 'symbol' || prop.startsWith('$')) {
7317
+ return target[prop];
7318
+ }
7319
+ if (WRITE_METHODS.includes(prop)) {
7320
+ return () => {
7321
+ throw new NativeDocumentError(
7322
+ `Store.${prop}() is not allowed on a read-only store. Use the original store reference instead.`
7323
+ );
7324
+ };
7325
+ }
7326
+ if (target.has(prop)) {
7327
+ return target.follow(prop);
7328
+ }
7329
+ return target[prop];
7330
+ },
7331
+ set() {
7332
+ throw new NativeDocumentError('This store is read-only.');
7333
+ },
7334
+ deleteProperty() {
7335
+ throw new NativeDocumentError('This store is read-only.');
7336
+ },
7337
+ });
7338
+ },
5484
7339
  };
5485
7340
 
5486
7341
 
@@ -5501,17 +7356,20 @@ var NativeDocument = (function (exports) {
5501
7356
  },
5502
7357
  set(target, prop, value) {
5503
7358
  DebugManager$2.error('Store', `Forbidden: You cannot overwrite the store key '${String(prop)}'. Use .use('${String(prop)}').set(value) instead.`);
5504
- throw new NativeDocumentError(`Store structure is immutable. Use .set() on the observable.`);
7359
+ throw new NativeDocumentError('Store structure is immutable. Use .set() on the observable.');
5505
7360
  },
5506
7361
  deleteProperty(target, prop) {
5507
- throw new NativeDocumentError(`Store keys cannot be deleted.`);
5508
- }
7362
+ throw new NativeDocumentError('Store keys cannot be deleted.');
7363
+ },
5509
7364
  });
5510
7365
  };
5511
7366
 
5512
7367
  const Store = StoreFactory();
5513
7368
 
5514
- Store.create('locale', navigator.language.split('-')[0] || 'en');
7369
+ Store.create(
7370
+ 'locale',
7371
+ (typeof navigator !== 'undefined' ? navigator.language.split('-')[0] : 'en') || 'en'
7372
+ );
5515
7373
 
5516
7374
  const SELF_RENDER$1 = (item) => item;
5517
7375
 
@@ -5541,7 +7399,7 @@ var NativeDocument = (function (exports) {
5541
7399
  const blockEnd = element.endElement();
5542
7400
  element.startElement();
5543
7401
 
5544
- let cache = new Map();
7402
+ const cache = new Map();
5545
7403
  let lastKeyOrder = null;
5546
7404
  const keyIds = new Set();
5547
7405
 
@@ -5585,9 +7443,9 @@ var NativeDocument = (function (exports) {
5585
7443
 
5586
7444
  try {
5587
7445
  const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
5588
- let child = ElementCreator.getChild(callback(item, indexObserver));
7446
+ const child = ElementCreator.getChild(callback(item, indexObserver));
5589
7447
  if(!child) {
5590
- throw new NativeDocumentError("ForEach child can't be null or undefined!");
7448
+ throw new NativeDocumentError('ForEach child can\'t be null or undefined!');
5591
7449
  }
5592
7450
  cache.set(keyId, { keyId, isNew: true, child: new WeakRef(child), indexObserver});
5593
7451
  } catch (e) {
@@ -5611,7 +7469,7 @@ var NativeDocument = (function (exports) {
5611
7469
  };
5612
7470
 
5613
7471
  const diffingDOMUpdates = (parent) => {
5614
- let fragment = document.createDocumentFragment();
7472
+ const fragment = document.createDocumentFragment();
5615
7473
  const newKeys = Array.from(keyIds);
5616
7474
  Array.from(lastKeyOrder);
5617
7475
 
@@ -5700,7 +7558,7 @@ var NativeDocument = (function (exports) {
5700
7558
  const element = Anchor('ForEach Array', configs.isParentUniqueChild);
5701
7559
  const blockEnd = element.endElement();
5702
7560
 
5703
- let cache = new Map();
7561
+ const cache = new Map();
5704
7562
  let lastNumberOfItems = 0;
5705
7563
  const isIndexRequired = callback.length >= 2;
5706
7564
 
@@ -5731,7 +7589,7 @@ var NativeDocument = (function (exports) {
5731
7589
  const child = ElementCreator.getChild(callback(item, null));
5732
7590
  {
5733
7591
  if(!child) {
5734
- throw new NativeDocumentError("ForEachArray child can't be null or undefined!");
7592
+ throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
5735
7593
  }
5736
7594
  }
5737
7595
  cache.set(item, { child, indexObserver: null });
@@ -5743,7 +7601,7 @@ var NativeDocument = (function (exports) {
5743
7601
  const child = ElementCreator.getChild(callback(item, indexObserver));
5744
7602
  {
5745
7603
  if(!child) {
5746
- throw new NativeDocumentError("ForEachArray child can't be null or undefined!");
7604
+ throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
5747
7605
  }
5748
7606
  }
5749
7607
  cache.set(item, { child, indexObserver });
@@ -5754,7 +7612,7 @@ var NativeDocument = (function (exports) {
5754
7612
  const child = ElementCreator.getChild(callback(item, indexKey));
5755
7613
  {
5756
7614
  if(!child) {
5757
- throw new NativeDocumentError("ForEachArray child can't be null or undefined!");
7615
+ throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
5758
7616
  }
5759
7617
  }
5760
7618
  cache.set(item, { child, indexObserver: null });
@@ -5884,7 +7742,7 @@ var NativeDocument = (function (exports) {
5884
7742
  const garbageFragment = document.createDocumentFragment();
5885
7743
 
5886
7744
  if(deleted.length > 0) {
5887
- let firstItem = deleted[0];
7745
+ const firstItem = deleted[0];
5888
7746
  if(deleted.length === 1) {
5889
7747
  removeByItem(firstItem, garbageFragment);
5890
7748
  } else if(deleted.length > 1) {
@@ -5936,7 +7794,7 @@ var NativeDocument = (function (exports) {
5936
7794
  parent.insertBefore(childA, childBNext);
5937
7795
  childA = null;
5938
7796
  childB = null;
5939
- }
7797
+ },
5940
7798
  };
5941
7799
  Actions.merge = Actions.add;
5942
7800
  Actions.push = Actions.add;
@@ -5977,11 +7835,11 @@ var NativeDocument = (function (exports) {
5977
7835
  */
5978
7836
  const ShowIf = function(condition, child, { comment = null, shouldKeepInCache = true} = {}) {
5979
7837
  if(!Validator.isObservable(condition)) {
5980
- if(typeof condition === "boolean") {
7838
+ if(typeof condition === 'boolean') {
5981
7839
  return condition ? ElementCreator.getChild(child) : null;
5982
7840
  }
5983
7841
 
5984
- return DebugManager$2.warn('ShowIf', "ShowIf : condition must be an Observable or boolean / "+comment, condition);
7842
+ return DebugManager$2.warn('ShowIf', 'ShowIf : condition must be an Observable or boolean / '+comment, condition);
5985
7843
  }
5986
7844
  const element = Anchor('Show if : '+(comment || ''));
5987
7845
 
@@ -6004,7 +7862,7 @@ var NativeDocument = (function (exports) {
6004
7862
  }
6005
7863
 
6006
7864
  condition.subscribe((value) => {
6007
- if(!!value) {
7865
+ if(value) {
6008
7866
  element.appendChild(getChildElement());
6009
7867
  return;
6010
7868
  }
@@ -6079,7 +7937,7 @@ var NativeDocument = (function (exports) {
6079
7937
  if(!Validator.isObservableWhenResult(observer)) {
6080
7938
  throw new NativeDocumentError('showWhen observer must be an ObservableWhenResult', {
6081
7939
  data: observer,
6082
- 'help': 'Use observer.when(target) to create an ObservableWhenResult'
7940
+ 'help': 'Use observer.when(target) to create an ObservableWhenResult',
6083
7941
  });
6084
7942
  }
6085
7943
  return ShowIf(observer, target);
@@ -6097,7 +7955,7 @@ var NativeDocument = (function (exports) {
6097
7955
  data: [
6098
7956
  'showWhen(observer, target, view)',
6099
7957
  'showWhen(observerWhenResult, view)',
6100
- ]
7958
+ ],
6101
7959
  });
6102
7960
  };
6103
7961
 
@@ -6124,7 +7982,7 @@ var NativeDocument = (function (exports) {
6124
7982
  const Match = function($condition, values, shouldKeepInCache = true) {
6125
7983
 
6126
7984
  if(!Validator.isObservable($condition)) {
6127
- throw new NativeDocumentError("Toggle : condition must be an Observable");
7985
+ throw new NativeDocumentError('Toggle : condition must be an Observable');
6128
7986
  }
6129
7987
 
6130
7988
  const anchor = Anchor('Match');
@@ -6171,7 +8029,7 @@ var NativeDocument = (function (exports) {
6171
8029
  shouldKeepInCache && cache.delete(key);
6172
8030
  $condition.set([...cache.keys()].at(-1) ?? '');
6173
8031
  delete values[key];
6174
- }
8032
+ },
6175
8033
  });
6176
8034
  };
6177
8035
 
@@ -6193,7 +8051,7 @@ var NativeDocument = (function (exports) {
6193
8051
  */
6194
8052
  const Switch = function ($condition, onTrue, onFalse) {
6195
8053
  if(!Validator.isObservable($condition)) {
6196
- throw new NativeDocumentError("Toggle : condition must be an Observable");
8054
+ throw new NativeDocumentError('Toggle : condition must be an Observable');
6197
8055
  }
6198
8056
 
6199
8057
  return Match($condition.toBoolean(), {
@@ -6215,7 +8073,7 @@ var NativeDocument = (function (exports) {
6215
8073
  */
6216
8074
  const When = function($condition) {
6217
8075
  if(!Validator.isObservable($condition)) {
6218
- throw new NativeDocumentError("When : condition must be an Observable");
8076
+ throw new NativeDocumentError('When : condition must be an Observable');
6219
8077
  }
6220
8078
 
6221
8079
  let $onTrue = null;
@@ -6232,8 +8090,8 @@ var NativeDocument = (function (exports) {
6232
8090
  },
6233
8091
  toNdElement() {
6234
8092
  return Switch($condition, $onTrue, $onFalse);
6235
- }
6236
- }
8093
+ },
8094
+ };
6237
8095
  };
6238
8096
 
6239
8097
  /**
@@ -7303,7 +9161,7 @@ var NativeDocument = (function (exports) {
7303
9161
  $path = '/'+trim($path, '/').replace(/\/+/, '/');
7304
9162
 
7305
9163
  let $pattern = null;
7306
- let $name = $options.name || null;
9164
+ const $name = $options.name || null;
7307
9165
 
7308
9166
  const $middlewares = $options.middlewares || [];
7309
9167
  const $shouldRebuild = $options.shouldRebuild || false;
@@ -7453,7 +9311,7 @@ var NativeDocument = (function (exports) {
7453
9311
  }
7454
9312
  }
7455
9313
  return null;
7456
- }
9314
+ },
7457
9315
  };
7458
9316
 
7459
9317
  function HashRouter() {
@@ -7687,6 +9545,9 @@ var NativeDocument = (function (exports) {
7687
9545
 
7688
9546
  let $lastNodeInserted = null;
7689
9547
 
9548
+ const $lifecycles = new Map();
9549
+ let $currentPath = null;
9550
+
7690
9551
  const getNodeAnchorForLayout = (node, path) => {
7691
9552
  const existingAnchor = $routeInstanceAnchors.get(node);
7692
9553
  if(existingAnchor) {
@@ -7724,7 +9585,7 @@ var NativeDocument = (function (exports) {
7724
9585
  };
7725
9586
 
7726
9587
  const updateContainerByLayout = (layout, node, route, path) => {
7727
- let nodeToInsert = getNodeToInsert(node);
9588
+ const nodeToInsert = getNodeToInsert(node);
7728
9589
 
7729
9590
  const cachedLayout = $layoutCache.get(nodeToInsert);
7730
9591
  if(cachedLayout) {
@@ -7758,7 +9619,7 @@ var NativeDocument = (function (exports) {
7758
9619
  updateContainerByLayout(layout, node, route, path);
7759
9620
  return;
7760
9621
  }
7761
- let nodeToInsert = getNodeToInsert(node);
9622
+ const nodeToInsert = getNodeToInsert(node);
7762
9623
 
7763
9624
  cleanContainer();
7764
9625
  container.appendChild(nodeToInsert);
@@ -7769,16 +9630,39 @@ var NativeDocument = (function (exports) {
7769
9630
  if(!state.route) {
7770
9631
  return;
7771
9632
  }
9633
+
7772
9634
  const { route, params, query, path } = state;
9635
+
9636
+ if($currentPath && $currentPath !== path) {
9637
+ $lifecycles.get($currentPath)?.onLeave?.();
9638
+ }
9639
+
7773
9640
  if($cache.has(path)) {
7774
9641
  const cacheNode = $cache.get(path);
7775
9642
  updateContainer(cacheNode, route);
9643
+
9644
+ $lifecycles.get(path)?.onEnter?.(params, query);
9645
+ $currentPath = path;
9646
+
7776
9647
  return;
7777
9648
  }
9649
+ const pathLifecycles = {};
9650
+ $lifecycles.set(path, pathLifecycles);
9651
+
9652
+
9653
+
7778
9654
  const Component = route.component();
7779
- const node = Component({ params, query });
9655
+ const node = Component({
9656
+ params,
9657
+ query,
9658
+ onEnter: (cb) => { pathLifecycles.onEnter = cb; },
9659
+ onLeave: (cb) => { pathLifecycles.onLeave = cb; },
9660
+ });
7780
9661
  $cache.set(path, node);
7781
9662
  updateContainer(node, route, path);
9663
+
9664
+ pathLifecycles.onEnter?.(params, query);
9665
+ $currentPath = path;
7782
9666
  };
7783
9667
 
7784
9668
  router.subscribe(handleCurrentRouterState);
@@ -7840,7 +9724,7 @@ var NativeDocument = (function (exports) {
7840
9724
  ...options,
7841
9725
  middlewares: RouteGroupHelper.fullMiddlewares($groupTree, options?.middlewares || []),
7842
9726
  name: options?.name ? RouteGroupHelper.fullName($groupTree, options.name) : null,
7843
- layout: options?.layout || RouteGroupHelper.layout($groupTree)
9727
+ layout: options?.layout || RouteGroupHelper.layout($groupTree),
7844
9728
  });
7845
9729
  $routes.push(route);
7846
9730
  if(route.name()) {
@@ -7903,7 +9787,7 @@ var NativeDocument = (function (exports) {
7903
9787
  route,
7904
9788
  params: [],
7905
9789
  query: [],
7906
- path: route.url({ name: target })
9790
+ path: route.url({ name: target }),
7907
9791
  };
7908
9792
  }
7909
9793
  }
@@ -7916,7 +9800,7 @@ var NativeDocument = (function (exports) {
7916
9800
  route,
7917
9801
  params: target.params,
7918
9802
  query: target.query,
7919
- path: route.url({ ...target })
9803
+ path: route.url({ ...target }),
7920
9804
  };
7921
9805
  }
7922
9806
 
@@ -8103,7 +9987,7 @@ var NativeDocument = (function (exports) {
8103
9987
 
8104
9988
  const $interceptors = {
8105
9989
  request: [],
8106
- response: []
9990
+ response: [],
8107
9991
  };
8108
9992
 
8109
9993
  this.interceptors = {
@@ -8112,7 +9996,7 @@ var NativeDocument = (function (exports) {
8112
9996
  },
8113
9997
  request: (callback) => {
8114
9998
  $interceptors.request.push(callback);
8115
- }
9999
+ },
8116
10000
  };
8117
10001
 
8118
10002
  this.fetch = async function(method, endpoint, params = {}, options = {}) {
@@ -8129,7 +10013,7 @@ var NativeDocument = (function (exports) {
8129
10013
  let configs = {
8130
10014
  method,
8131
10015
  headers: {
8132
- ...(options.headers || {})
10016
+ ...(options.headers || {}),
8133
10017
  },
8134
10018
  };
8135
10019
  if(params) {