native-document 1.0.15 → 1.0.16-8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (649) hide show
  1. package/.npmrc.example +1 -0
  2. package/.vitepress/config.js +166 -0
  3. package/CHANGELOG.md +153 -0
  4. package/cdn.js +19 -0
  5. package/components.d.ts +2 -0
  6. package/components.js +30 -0
  7. package/devtools/ComponentRegistry.js +113 -0
  8. package/devtools/index.js +8 -0
  9. package/devtools/plugin/dev-tools-plugin.js +15 -0
  10. package/devtools/transformers/nd-vite-devtools.js +55 -0
  11. package/devtools/transformers/src/transformComponentForHrm.js +73 -0
  12. package/devtools/transformers/src/transformJsFile.js +9 -0
  13. package/devtools/transformers/src/utils.js +79 -0
  14. package/devtools/transformers/templates/hrm.hook.template.js +46 -0
  15. package/devtools/transformers/templates/hrm.orbservable.hook.template.js +76 -0
  16. package/devtools/widget/Widget.js +49 -0
  17. package/devtools/widget/widget.css +81 -0
  18. package/devtools/widget.js +23 -0
  19. package/dist/native-document.components.min.css +1 -0
  20. package/dist/native-document.components.min.js +23847 -0
  21. package/dist/native-document.dev.js +8421 -1492
  22. package/dist/native-document.dev.js.map +1 -0
  23. package/dist/native-document.devtools.min.js +1 -0
  24. package/dist/native-document.min.js +1 -1
  25. package/docs/advanced-components.md +419 -0
  26. package/docs/anchor.md +181 -257
  27. package/docs/cache.md +180 -0
  28. package/docs/cli.md +179 -0
  29. package/docs/components/accordion.md +172 -0
  30. package/docs/components/alert.md +99 -0
  31. package/docs/components/avatar.md +160 -0
  32. package/docs/components/badge.md +102 -0
  33. package/docs/components/breadcrumb.md +89 -0
  34. package/docs/components/button.md +183 -0
  35. package/docs/components/card.md +69 -0
  36. package/docs/components/context-menu.md +118 -0
  37. package/docs/components/data-table.md +345 -0
  38. package/docs/components/dropdown.md +214 -0
  39. package/docs/components/form/autocomplete-field.md +81 -0
  40. package/docs/components/form/checkbox-field.md +41 -0
  41. package/docs/components/form/checkbox-group-field.md +54 -0
  42. package/docs/components/form/color-field.md +64 -0
  43. package/docs/components/form/date-field.md +92 -0
  44. package/docs/components/form/field-collection.md +63 -0
  45. package/docs/components/form/file-field.md +203 -0
  46. package/docs/components/form/form-control.md +87 -0
  47. package/docs/components/form/image-field.md +90 -0
  48. package/docs/components/form/index.md +115 -0
  49. package/docs/components/form/number-field.md +65 -0
  50. package/docs/components/form/radio-field.md +51 -0
  51. package/docs/components/form/select-field.md +123 -0
  52. package/docs/components/form/slider.md +136 -0
  53. package/docs/components/form/string-field.md +134 -0
  54. package/docs/components/form/textarea-field.md +65 -0
  55. package/docs/components/form-fields.md +372 -0
  56. package/docs/components/getting-started.md +264 -0
  57. package/docs/components/index.md +337 -0
  58. package/docs/components/layout.md +279 -0
  59. package/docs/components/list.md +73 -0
  60. package/docs/components/menu.md +215 -0
  61. package/docs/components/modal.md +156 -0
  62. package/docs/components/pagination.md +95 -0
  63. package/docs/components/popover.md +131 -0
  64. package/docs/components/progress.md +111 -0
  65. package/docs/components/shortcut-manager.md +221 -0
  66. package/docs/components/simple-table.md +107 -0
  67. package/docs/components/skeleton.md +155 -0
  68. package/docs/components/spinner.md +100 -0
  69. package/docs/components/splitter.md +133 -0
  70. package/docs/components/stepper.md +163 -0
  71. package/docs/components/switch.md +113 -0
  72. package/docs/components/tabs.md +153 -0
  73. package/docs/components/toast.md +119 -0
  74. package/docs/components/tooltip.md +151 -0
  75. package/docs/components/traits.md +261 -0
  76. package/docs/conditional-rendering.md +177 -502
  77. package/docs/contributing.md +300 -25
  78. package/docs/core-concepts.md +207 -366
  79. package/docs/elements.md +266 -254
  80. package/docs/extending-native-document-element.md +259 -0
  81. package/docs/filters.md +247 -0
  82. package/docs/getting-started.md +195 -257
  83. package/docs/i18n.md +241 -0
  84. package/docs/index.md +76 -0
  85. package/docs/lifecycle-events.md +146 -67
  86. package/docs/list-rendering.md +240 -460
  87. package/docs/memory-management.md +135 -46
  88. package/docs/native-document-element.md +487 -0
  89. package/docs/native-fetch.md +213 -0
  90. package/docs/observable-resource.md +364 -0
  91. package/docs/observables.md +690 -357
  92. package/docs/routing.md +246 -646
  93. package/docs/state-management.md +213 -306
  94. package/docs/svg-elements.md +231 -0
  95. package/docs/theming.md +409 -0
  96. package/docs/tutorials/.gitkeep +0 -0
  97. package/docs/validation.md +98 -91
  98. package/docs/vitepress-conventions.md +219 -0
  99. package/elements.d.ts +7 -0
  100. package/elements.js +3 -4
  101. package/eslint.config.js +35 -0
  102. package/i18n.js +1 -0
  103. package/i18n.ts +2 -0
  104. package/index.d.ts +21 -0
  105. package/index.def.js +1086 -0
  106. package/index.js +19 -13
  107. package/package.json +59 -9
  108. package/readme.md +296 -93
  109. package/rollup.config.js +52 -3
  110. package/router.d.ts +7 -0
  111. package/router.js +0 -0
  112. package/src/components/$traits/has-draggable/HasDraggable.d.ts +4 -0
  113. package/src/components/$traits/has-draggable/HasDraggable.js +82 -0
  114. package/src/components/$traits/has-draggable/has-draggable.css +8 -0
  115. package/src/components/$traits/has-items/HasItems.d.ts +9 -0
  116. package/src/components/$traits/has-items/HasItems.js +64 -0
  117. package/src/components/$traits/has-position/HasFullPosition.d.ts +14 -0
  118. package/src/components/$traits/has-position/HasFullPosition.js +95 -0
  119. package/src/components/$traits/has-position/HasPosition.d.ts +7 -0
  120. package/src/components/$traits/has-position/HasPosition.js +45 -0
  121. package/src/components/$traits/has-resizable/HasResizable.d.ts +13 -0
  122. package/src/components/$traits/has-resizable/HasResizable.js +122 -0
  123. package/src/components/$traits/has-resizable/has-resizable.css +121 -0
  124. package/src/components/$traits/has-validation/HasValidation.d.ts +17 -0
  125. package/src/components/$traits/has-validation/HasValidation.js +133 -0
  126. package/src/components/BaseComponent.d.ts +32 -0
  127. package/src/components/BaseComponent.js +247 -0
  128. package/src/components/accordion/Accordion.js +268 -0
  129. package/src/components/accordion/AccordionItem.js +233 -0
  130. package/src/components/accordion/index.js +7 -0
  131. package/src/components/accordion/types/Accordion.d.ts +47 -0
  132. package/src/components/accordion/types/AccordionItem.d.ts +48 -0
  133. package/src/components/alert/Alert.js +350 -0
  134. package/src/components/alert/index.js +6 -0
  135. package/src/components/alert/types/Alert.d.ts +62 -0
  136. package/src/components/avatar/Avatar.js +430 -0
  137. package/src/components/avatar/AvatarGroup.js +97 -0
  138. package/src/components/avatar/index.js +7 -0
  139. package/src/components/avatar/types/Avatar.d.ts +74 -0
  140. package/src/components/avatar/types/AvatarGroup.d.ts +32 -0
  141. package/src/components/badge/Badge.js +245 -0
  142. package/src/components/badge/index.js +6 -0
  143. package/src/components/badge/types/Badge.d.ts +51 -0
  144. package/src/components/base-component.css +0 -0
  145. package/src/components/breadcrumb/BreadCrumb.js +138 -0
  146. package/src/components/breadcrumb/index.js +5 -0
  147. package/src/components/breadcrumb/types/BreadCrumb.d.ts +42 -0
  148. package/src/components/button/Button.js +320 -0
  149. package/src/components/button/index.js +5 -0
  150. package/src/components/button/types/Button.d.ts +62 -0
  151. package/src/components/card/Card.js +282 -0
  152. package/src/components/card/index.js +5 -0
  153. package/src/components/card/types/Card.d.ts +42 -0
  154. package/src/components/context-menu/ContextMenu.js +127 -0
  155. package/src/components/context-menu/ContextMenuGroup.js +29 -0
  156. package/src/components/context-menu/ContextMenuItem.js +28 -0
  157. package/src/components/context-menu/index.js +10 -0
  158. package/src/components/context-menu/types/ContextMenu.d.ts +30 -0
  159. package/src/components/context-menu/types/ContextMenuGroup.d.ts +18 -0
  160. package/src/components/context-menu/types/ContextMenuItem.d.ts +18 -0
  161. package/src/components/divider/Divider.js +256 -0
  162. package/src/components/divider/index.js +6 -0
  163. package/src/components/divider/types/Divider.d.ts +55 -0
  164. package/src/components/dropdown/Dropdown.js +531 -0
  165. package/src/components/dropdown/DropdownDivider.js +45 -0
  166. package/src/components/dropdown/DropdownGroup.js +83 -0
  167. package/src/components/dropdown/DropdownItem.js +150 -0
  168. package/src/components/dropdown/DropdownTrigger.js +93 -0
  169. package/src/components/dropdown/helpers.js +53 -0
  170. package/src/components/dropdown/index.js +13 -0
  171. package/src/components/dropdown/types/Dropdown.d.ts +88 -0
  172. package/src/components/dropdown/types/DropdownDivider.d.ts +20 -0
  173. package/src/components/dropdown/types/DropdownGroup.d.ts +25 -0
  174. package/src/components/dropdown/types/DropdownItem.d.ts +41 -0
  175. package/src/components/dropdown/types/DropdownTrigger.d.ts +32 -0
  176. package/src/components/form/FormControl.js +498 -0
  177. package/src/components/form/field/Field.js +419 -0
  178. package/src/components/form/field/FieldCollection.js +292 -0
  179. package/src/components/form/field/types/AutocompleteField.js +168 -0
  180. package/src/components/form/field/types/CheckboxField.js +77 -0
  181. package/src/components/form/field/types/CheckboxGroupField.js +171 -0
  182. package/src/components/form/field/types/ColorField.js +102 -0
  183. package/src/components/form/field/types/DateField.js +315 -0
  184. package/src/components/form/field/types/EmailField.js +104 -0
  185. package/src/components/form/field/types/FileField.js +276 -0
  186. package/src/components/form/field/types/HiddenField.js +44 -0
  187. package/src/components/form/field/types/ImageField.js +138 -0
  188. package/src/components/form/field/types/NumberField.js +177 -0
  189. package/src/components/form/field/types/PasswordField.js +200 -0
  190. package/src/components/form/field/types/RadioField.js +145 -0
  191. package/src/components/form/field/types/RangeField.js +117 -0
  192. package/src/components/form/field/types/SearchField.js +66 -0
  193. package/src/components/form/field/types/SelectField.js +247 -0
  194. package/src/components/form/field/types/StringField.js +148 -0
  195. package/src/components/form/field/types/TelField.js +98 -0
  196. package/src/components/form/field/types/TextAreaField.js +142 -0
  197. package/src/components/form/field/types/TimeField.js +215 -0
  198. package/src/components/form/field/types/UrlField.js +115 -0
  199. package/src/components/form/field/types/file-field-mode/FileAvatarMode.js +183 -0
  200. package/src/components/form/field/types/file-field-mode/FileDropzoneMode.js +117 -0
  201. package/src/components/form/field/types/file-field-mode/FileItemPreview.js +150 -0
  202. package/src/components/form/field/types/file-field-mode/FileNativeMode.js +43 -0
  203. package/src/components/form/field/types/file-field-mode/FileUploadButtonMode.js +120 -0
  204. package/src/components/form/field/types/file-field-mode/FileWallMode.js +106 -0
  205. package/src/components/form/index.js +61 -0
  206. package/src/components/form/merge +0 -0
  207. package/src/components/form/types/Field.d.ts +73 -0
  208. package/src/components/form/types/FieldCollection.d.ts +53 -0
  209. package/src/components/form/types/FormControl.d.ts +64 -0
  210. package/src/components/form/types/fields/AutocompleteField.d.ts +48 -0
  211. package/src/components/form/types/fields/CheckboxField.d.ts +33 -0
  212. package/src/components/form/types/fields/CheckboxGroupField.d.ts +49 -0
  213. package/src/components/form/types/fields/ColorField.d.ts +37 -0
  214. package/src/components/form/types/fields/DateField.d.ts +70 -0
  215. package/src/components/form/types/fields/EmailField.d.ts +35 -0
  216. package/src/components/form/types/fields/FileAvatarMode.d.ts +46 -0
  217. package/src/components/form/types/fields/FileDropzoneMode.d.ts +28 -0
  218. package/src/components/form/types/fields/FileField.d.ts +56 -0
  219. package/src/components/form/types/fields/FileItemPreview.d.ts +35 -0
  220. package/src/components/form/types/fields/FileNativeMode.d.ts +21 -0
  221. package/src/components/form/types/fields/FileUploadButtonMode.d.ts +34 -0
  222. package/src/components/form/types/fields/FileWallMode.d.ts +32 -0
  223. package/src/components/form/types/fields/HiddenField.d.ts +26 -0
  224. package/src/components/form/types/fields/ImageField.d.ts +45 -0
  225. package/src/components/form/types/fields/NumberField.d.ts +48 -0
  226. package/src/components/form/types/fields/PasswordField.d.ts +46 -0
  227. package/src/components/form/types/fields/RadioField.d.ts +48 -0
  228. package/src/components/form/types/fields/RangeField.d.ts +44 -0
  229. package/src/components/form/types/fields/SearchField.d.ts +34 -0
  230. package/src/components/form/types/fields/SelectField.d.ts +71 -0
  231. package/src/components/form/types/fields/StringField.d.ts +48 -0
  232. package/src/components/form/types/fields/TelField.d.ts +37 -0
  233. package/src/components/form/types/fields/TextAreaField.d.ts +44 -0
  234. package/src/components/form/types/fields/TimeField.d.ts +51 -0
  235. package/src/components/form/types/fields/UrlField.d.ts +35 -0
  236. package/src/components/form/utils.js +17 -0
  237. package/src/components/form/validation/Validation.js +565 -0
  238. package/src/components/index.d.ts +160 -0
  239. package/src/components/list/HasListItem.js +171 -0
  240. package/src/components/list/List.js +125 -0
  241. package/src/components/list/ListDivider.js +39 -0
  242. package/src/components/list/ListGroup.js +135 -0
  243. package/src/components/list/ListItem.js +212 -0
  244. package/src/components/list/index.js +12 -0
  245. package/src/components/list/types/List.d.ts +43 -0
  246. package/src/components/list/types/ListGroup.d.ts +37 -0
  247. package/src/components/list/types/ListItem.d.ts +53 -0
  248. package/src/components/menu/HasMenuItem.js +182 -0
  249. package/src/components/menu/Menu.js +227 -0
  250. package/src/components/menu/MenuDivider.js +37 -0
  251. package/src/components/menu/MenuGroup.js +126 -0
  252. package/src/components/menu/MenuItem.js +190 -0
  253. package/src/components/menu/MenuLink.js +51 -0
  254. package/src/components/menu/index.js +14 -0
  255. package/src/components/menu/types/Menu.d.ts +60 -0
  256. package/src/components/menu/types/MenuDivider.d.ts +19 -0
  257. package/src/components/menu/types/MenuGroup.d.ts +44 -0
  258. package/src/components/menu/types/MenuItem.d.ts +46 -0
  259. package/src/components/menu/types/MenuLink.d.ts +16 -0
  260. package/src/components/modal/Modal.js +524 -0
  261. package/src/components/modal/index.js +5 -0
  262. package/src/components/modal/types/Modal.d.ts +94 -0
  263. package/src/components/pagination/Pagination.js +411 -0
  264. package/src/components/pagination/index.js +5 -0
  265. package/src/components/pagination/types/Pagination.d.ts +68 -0
  266. package/src/components/popover/Popover.js +459 -0
  267. package/src/components/popover/PopoverFooter.js +61 -0
  268. package/src/components/popover/PopoverHeader.js +68 -0
  269. package/src/components/popover/index.js +10 -0
  270. package/src/components/popover/types/Popover.d.ts +83 -0
  271. package/src/components/popover/types/PopoverFooter.d.ts +24 -0
  272. package/src/components/popover/types/PopoverHeader.d.ts +26 -0
  273. package/src/components/progress/Progress.js +401 -0
  274. package/src/components/progress/index.js +6 -0
  275. package/src/components/progress/types/Progress.d.ts +77 -0
  276. package/src/components/skeleton/Skeleton.js +228 -0
  277. package/src/components/skeleton/index.js +6 -0
  278. package/src/components/skeleton/types/Skeleton.d.ts +55 -0
  279. package/src/components/slider/Slider.js +406 -0
  280. package/src/components/slider/index.js +5 -0
  281. package/src/components/slider/types/Slider.d.ts +82 -0
  282. package/src/components/spacer/Spacer.js +27 -0
  283. package/src/components/spacer/index.js +5 -0
  284. package/src/components/spacer/types/Spacer.d.ts +19 -0
  285. package/src/components/spinner/Spinner.js +350 -0
  286. package/src/components/spinner/index.js +5 -0
  287. package/src/components/spinner/types/Spinner.d.ts +71 -0
  288. package/src/components/splitter/Splitter.js +164 -0
  289. package/src/components/splitter/SplitterGutter.js +140 -0
  290. package/src/components/splitter/SplitterPanel.js +143 -0
  291. package/src/components/splitter/index.js +10 -0
  292. package/src/components/splitter/types/Splitter.d.ts +38 -0
  293. package/src/components/splitter/types/SplitterGutter.d.ts +38 -0
  294. package/src/components/splitter/types/SplitterPanel.d.ts +41 -0
  295. package/src/components/stacks/AbsoluteStack.js +53 -0
  296. package/src/components/stacks/FixedStack.js +53 -0
  297. package/src/components/stacks/HStack.js +54 -0
  298. package/src/components/stacks/PositionStack.js +254 -0
  299. package/src/components/stacks/RelativeStack.js +53 -0
  300. package/src/components/stacks/Stack.js +166 -0
  301. package/src/components/stacks/VStack.js +55 -0
  302. package/src/components/stacks/index.js +21 -0
  303. package/src/components/stacks/types/AbsoluteStack.d.ts +16 -0
  304. package/src/components/stacks/types/FixedStack.d.ts +16 -0
  305. package/src/components/stacks/types/HStack.d.ts +16 -0
  306. package/src/components/stacks/types/PositionStack.d.ts +54 -0
  307. package/src/components/stacks/types/RelativeStack.d.ts +17 -0
  308. package/src/components/stacks/types/Stack.d.ts +39 -0
  309. package/src/components/stacks/types/VStack.d.ts +16 -0
  310. package/src/components/stepper/Stepper.js +461 -0
  311. package/src/components/stepper/StepperStep.js +241 -0
  312. package/src/components/stepper/index.js +8 -0
  313. package/src/components/stepper/types/Stepper.d.ts +68 -0
  314. package/src/components/stepper/types/StepperStep.d.ts +54 -0
  315. package/src/components/switch/Switch.js +266 -0
  316. package/src/components/switch/index.js +6 -0
  317. package/src/components/switch/types/Switch.d.ts +55 -0
  318. package/src/components/table/Column.js +212 -0
  319. package/src/components/table/ColumnGroup.js +90 -0
  320. package/src/components/table/DataTable.js +720 -0
  321. package/src/components/table/SimpleTable.js +139 -0
  322. package/src/components/table/index.js +7 -0
  323. package/src/components/table/types/Column.d.ts +49 -0
  324. package/src/components/table/types/ColumnGroup.d.ts +28 -0
  325. package/src/components/table/types/DataTable.d.ts +97 -0
  326. package/src/components/table/types/SimpleTable.d.ts +40 -0
  327. package/src/components/tabs/Tabs.js +395 -0
  328. package/src/components/tabs/index.js +6 -0
  329. package/src/components/tabs/types/Tabs.d.ts +78 -0
  330. package/src/components/toast/Toast.js +262 -0
  331. package/src/components/toast/ToastError.js +0 -0
  332. package/src/components/toast/ToastInfo.js +0 -0
  333. package/src/components/toast/ToastSuccess.js +0 -0
  334. package/src/components/toast/ToastWarning.js +0 -0
  335. package/src/components/toast/index.js +5 -0
  336. package/src/components/toast/types/Toast.d.ts +57 -0
  337. package/src/components/toast/types/ToastError.d.ts +7 -0
  338. package/src/components/toast/types/ToastInfo.d.ts +7 -0
  339. package/src/components/toast/types/ToastSuccess.d.ts +7 -0
  340. package/src/components/toast/types/ToastWarning.d.ts +7 -0
  341. package/src/components/tooltip/Tooltip.js +359 -0
  342. package/src/components/tooltip/index.js +5 -0
  343. package/src/components/tooltip/prototypes.js +6 -0
  344. package/src/components/tooltip/types/Tooltip.d.ts +65 -0
  345. package/src/{data → core/data}/MemoryManager.js +2 -3
  346. package/src/core/data/Observable.js +227 -0
  347. package/src/core/data/ObservableArray.js +522 -0
  348. package/src/core/data/ObservableChecker.js +39 -0
  349. package/src/core/data/ObservableItem.js +611 -0
  350. package/src/core/data/ObservableObject.js +274 -0
  351. package/src/core/data/ObservableResource.js +315 -0
  352. package/src/core/data/ObservableWhen.js +54 -0
  353. package/src/core/data/Store.js +520 -0
  354. package/src/core/data/observable-helpers/observable.is-to.js +390 -0
  355. package/src/core/data/observable-helpers/observable.prototypes.js +145 -0
  356. package/src/core/elements/anchor/anchor-with-sentinel.js +66 -0
  357. package/src/core/elements/anchor/anchor.js +210 -0
  358. package/src/core/elements/anchor/one-child-anchor-overwriting.js +66 -0
  359. package/src/core/elements/content-formatter.js +169 -0
  360. package/src/core/elements/control/for-each-array.js +292 -0
  361. package/src/{elements → core/elements}/control/for-each.js +42 -23
  362. package/src/core/elements/control/show-if.js +94 -0
  363. package/src/core/elements/control/show-when.js +54 -0
  364. package/src/core/elements/control/switch.js +141 -0
  365. package/src/core/elements/description-list.js +19 -0
  366. package/src/core/elements/form.js +255 -0
  367. package/src/core/elements/fragment.js +8 -0
  368. package/src/core/elements/html5-semantics.js +55 -0
  369. package/src/core/elements/img.js +59 -0
  370. package/src/{elements → core/elements}/index.js +4 -4
  371. package/src/core/elements/interactive.js +25 -0
  372. package/src/core/elements/list.js +37 -0
  373. package/src/core/elements/medias.js +37 -0
  374. package/src/core/elements/meta-data.js +43 -0
  375. package/src/core/elements/svg.js +61 -0
  376. package/src/core/elements/table.js +73 -0
  377. package/src/{errors → core/errors}/ArgTypesError.js +1 -1
  378. package/src/{errors → core/errors}/NativeDocumentError.js +0 -0
  379. package/src/core/utils/HasEventEmitter.js +85 -0
  380. package/src/core/utils/args-types.js +140 -0
  381. package/src/core/utils/cache.js +5 -0
  382. package/src/core/utils/callback-handler.js +50 -0
  383. package/src/core/utils/debug-manager.js +40 -0
  384. package/src/core/utils/events.js +148 -0
  385. package/src/core/utils/filters/date.js +178 -0
  386. package/src/core/utils/filters/index.js +4 -0
  387. package/src/core/utils/filters/standard.js +263 -0
  388. package/src/core/utils/filters/strings.js +67 -0
  389. package/src/core/utils/filters/utils.js +77 -0
  390. package/src/core/utils/formatters.js +90 -0
  391. package/src/core/utils/helpers.js +144 -0
  392. package/src/core/utils/localstorage.js +57 -0
  393. package/src/core/utils/memoize.js +115 -0
  394. package/src/core/utils/plugins-manager.js +81 -0
  395. package/src/core/utils/property-accumulator.js +72 -0
  396. package/src/core/utils/prototypes.js +44 -0
  397. package/src/core/utils/shortcut-manager.js +242 -0
  398. package/src/{utils → core/utils}/validator.js +58 -22
  399. package/src/core/wrappers/AttributesWrapper.js +181 -0
  400. package/src/core/wrappers/DocumentObserver.js +182 -0
  401. package/src/core/wrappers/ElementCreator.js +110 -0
  402. package/src/core/wrappers/HtmlElementWrapper.js +98 -0
  403. package/src/core/wrappers/NDElement.js +613 -0
  404. package/src/core/wrappers/NdPrototype.js +233 -0
  405. package/src/core/wrappers/SingletonView.js +99 -0
  406. package/src/core/wrappers/SvgElementWrapper.js +15 -0
  407. package/src/core/wrappers/TemplateBinding.js +7 -0
  408. package/src/core/wrappers/constants.js +66 -0
  409. package/src/core/wrappers/prototypes/attributes-extensions.js +24 -0
  410. package/src/core/wrappers/prototypes/bind-class-extensions.js +0 -0
  411. package/src/core/wrappers/prototypes/nd-element-extensions.js +149 -0
  412. package/src/core/wrappers/prototypes/nd-element.transition.extensions.js +127 -0
  413. package/src/core/wrappers/template-cloner/NodeCloner.js +209 -0
  414. package/src/core/wrappers/template-cloner/TemplateCloner.js +192 -0
  415. package/src/core/wrappers/template-cloner/attributes-hydrator.js +142 -0
  416. package/src/core/wrappers/template-cloner/utils.js +173 -0
  417. package/src/fetch/NativeFetch.js +89 -0
  418. package/src/i18n/bin/scan.js +132 -0
  419. package/src/i18n/index.d.ts +2 -0
  420. package/src/i18n/service/I18nService.d.ts +27 -0
  421. package/src/i18n/service/I18nService.js +46 -0
  422. package/src/i18n/service/functions.d.ts +22 -0
  423. package/src/i18n/service/functions.js +29 -0
  424. package/src/router/Route.js +33 -8
  425. package/src/router/RouteGroupHelper.js +10 -2
  426. package/src/router/Router.js +63 -22
  427. package/src/router/RouterComponent.js +114 -6
  428. package/src/{errors → router/errors}/RouterError.js +0 -1
  429. package/src/router/link.js +9 -10
  430. package/src/router/modes/HashRouter.js +2 -2
  431. package/src/router/modes/HistoryRouter.js +2 -3
  432. package/src/router/modes/MemoryRouter.js +1 -1
  433. package/src/ui/components/accordion/AccordionItemRender.js +63 -0
  434. package/src/ui/components/accordion/AccordionRender.js +35 -0
  435. package/src/ui/components/accordion/accordion.css +121 -0
  436. package/src/ui/components/alert/AlertRender.js +81 -0
  437. package/src/ui/components/alert/alert.css +163 -0
  438. package/src/ui/components/avatar/avata-group/AvatarGroupRender.js +50 -0
  439. package/src/ui/components/avatar/avata-group/avatar-group.css +38 -0
  440. package/src/ui/components/avatar/avatar/AvatarRender.js +87 -0
  441. package/src/ui/components/avatar/avatar/avatar.css +189 -0
  442. package/src/ui/components/badge/BadgeRender.js +25 -0
  443. package/src/ui/components/badge/badge.css +168 -0
  444. package/src/ui/components/breadcrumb/BreadcrumbRender.js +44 -0
  445. package/src/ui/components/breadcrumb/breadcrumb.css +55 -0
  446. package/src/ui/components/button/ButtonRender.js +65 -0
  447. package/src/ui/components/button/button.css +296 -0
  448. package/src/ui/components/card/CardRender.js +133 -0
  449. package/src/ui/components/card/card.css +169 -0
  450. package/src/ui/components/contextmenu/ContextmenuRender.js +68 -0
  451. package/src/ui/components/contextmenu/contextmenu.css +36 -0
  452. package/src/ui/components/divider/DividerRender.js +70 -0
  453. package/src/ui/components/divider/divider.css +70 -0
  454. package/src/ui/components/dropdown/DropdownRender.js +92 -0
  455. package/src/ui/components/dropdown/divider/DropdownDividerRender.js +9 -0
  456. package/src/ui/components/dropdown/divider/dropdown-divider.css +0 -0
  457. package/src/ui/components/dropdown/dropdown.css +179 -0
  458. package/src/ui/components/dropdown/group/DropdownGroupRender.js +23 -0
  459. package/src/ui/components/dropdown/group/dropdown-group.css +0 -0
  460. package/src/ui/components/dropdown/item/DropdownItemRender.js +29 -0
  461. package/src/ui/components/dropdown/item/dropdown-item.css +0 -0
  462. package/src/ui/components/form/FieldCollectionRender.js +110 -0
  463. package/src/ui/components/form/FormControlRender.js +85 -0
  464. package/src/ui/components/form/field-collection.css +55 -0
  465. package/src/ui/components/form/fields/AutocompleteFieldRender.js +143 -0
  466. package/src/ui/components/form/fields/CheckboxFieldRender.js +59 -0
  467. package/src/ui/components/form/fields/CheckboxGroupFieldRender.js +92 -0
  468. package/src/ui/components/form/fields/ColorFieldRender.js +30 -0
  469. package/src/ui/components/form/fields/DateFieldRender.js +155 -0
  470. package/src/ui/components/form/fields/EmailFieldRender.js +5 -0
  471. package/src/ui/components/form/fields/FieldRender.js +118 -0
  472. package/src/ui/components/form/fields/FileFieldRender.js +41 -0
  473. package/src/ui/components/form/fields/HiddenFieldRender.js +13 -0
  474. package/src/ui/components/form/fields/ImageFieldRender.js +0 -0
  475. package/src/ui/components/form/fields/NumberFieldRender.js +52 -0
  476. package/src/ui/components/form/fields/PasswordFieldRender.js +65 -0
  477. package/src/ui/components/form/fields/RadioFieldRender.js +77 -0
  478. package/src/ui/components/form/fields/RangeFieldRender.js +122 -0
  479. package/src/ui/components/form/fields/SelectFieldRender.js +248 -0
  480. package/src/ui/components/form/fields/SliderFieldRender.js +359 -0
  481. package/src/ui/components/form/fields/StringFieldRender.js +6 -0
  482. package/src/ui/components/form/fields/TelFieldRender.js +6 -0
  483. package/src/ui/components/form/fields/TextAreaFieldRender.js +96 -0
  484. package/src/ui/components/form/fields/TimeFieldRender.js +142 -0
  485. package/src/ui/components/form/fields/UrlFieldRender.js +6 -0
  486. package/src/ui/components/form/fields/date-field.css +32 -0
  487. package/src/ui/components/form/fields/field.css +402 -0
  488. package/src/ui/components/form/fields/file-field.css +79 -0
  489. package/src/ui/components/form/fields/password-field.css +50 -0
  490. package/src/ui/components/form/fields/range-field.css +120 -0
  491. package/src/ui/components/form/fields/slider.css +195 -0
  492. package/src/ui/components/form/file-upload-mode/FileAvatarModeRender.js +143 -0
  493. package/src/ui/components/form/file-upload-mode/FileDropzoneModeRender.js +108 -0
  494. package/src/ui/components/form/file-upload-mode/FileNativeModeRender.js +22 -0
  495. package/src/ui/components/form/file-upload-mode/FileUploadButtonModeRender.js +89 -0
  496. package/src/ui/components/form/file-upload-mode/FileWallModeRender.js +90 -0
  497. package/src/ui/components/form/file-upload-mode/file-avatar-mode.css +139 -0
  498. package/src/ui/components/form/file-upload-mode/file-dropzone-mode.css +88 -0
  499. package/src/ui/components/form/file-upload-mode/file-upload-button-mode.css +44 -0
  500. package/src/ui/components/form/file-upload-mode/file-wall-mode.css +88 -0
  501. package/src/ui/components/form/form-control.css +40 -0
  502. package/src/ui/components/form/helpers.js +111 -0
  503. package/src/ui/components/form/index.js +27 -0
  504. package/src/ui/components/list/ListRender.js +18 -0
  505. package/src/ui/components/list/divider/ListDividerRender.js +10 -0
  506. package/src/ui/components/list/divider/list-divider.css +12 -0
  507. package/src/ui/components/list/group/ListGroupRender.js +61 -0
  508. package/src/ui/components/list/group/list-group.css +62 -0
  509. package/src/ui/components/list/item/ListItemRender.js +238 -0
  510. package/src/ui/components/list/item/list-item.css +191 -0
  511. package/src/ui/components/list/list.css +24 -0
  512. package/src/ui/components/menu/MenuDividerRender.js +12 -0
  513. package/src/ui/components/menu/MenuGroupRender.js +59 -0
  514. package/src/ui/components/menu/MenuItemRender.js +57 -0
  515. package/src/ui/components/menu/MenuLinkRender.js +55 -0
  516. package/src/ui/components/menu/MenuRender.js +22 -0
  517. package/src/ui/components/menu/helpers.js +121 -0
  518. package/src/ui/components/menu/menu.css +308 -0
  519. package/src/ui/components/modal/ModalRender.js +118 -0
  520. package/src/ui/components/modal/modal.css +156 -0
  521. package/src/ui/components/pagination/PaginationRender.js +112 -0
  522. package/src/ui/components/pagination/pagination.css +63 -0
  523. package/src/ui/components/popover/PopoverRender.js +233 -0
  524. package/src/ui/components/popover/popover.css +139 -0
  525. package/src/ui/components/progress/ProgressRender.js +168 -0
  526. package/src/ui/components/progress/progress.css +197 -0
  527. package/src/ui/components/skeleton/SkeletonRender.js +136 -0
  528. package/src/ui/components/skeleton/skeleton.css +154 -0
  529. package/src/ui/components/spacer/SpacerRender.js +10 -0
  530. package/src/ui/components/spinner/SpinnerRender.js +47 -0
  531. package/src/ui/components/spinner/spinner.css +152 -0
  532. package/src/ui/components/splitter/SplitterGutterRender.js +94 -0
  533. package/src/ui/components/splitter/SplitterPanelRender.js +38 -0
  534. package/src/ui/components/splitter/SplitterRender.js +75 -0
  535. package/src/ui/components/splitter/splitter.css +128 -0
  536. package/src/ui/components/stacks/PositionStackRender.js +39 -0
  537. package/src/ui/components/stacks/StackRender.js +41 -0
  538. package/src/ui/components/stacks/absolute-stack/AbsoluteStackRender.js +5 -0
  539. package/src/ui/components/stacks/fixed-stack/FixedStackRender.js +5 -0
  540. package/src/ui/components/stacks/h-stack/HStackRender.js +7 -0
  541. package/src/ui/components/stacks/h-stack/h-stack.css +4 -0
  542. package/src/ui/components/stacks/index.js +5 -0
  543. package/src/ui/components/stacks/position-stack.css +62 -0
  544. package/src/ui/components/stacks/relative-stack/RelativeStackRender.js +7 -0
  545. package/src/ui/components/stacks/relative-stack/relative-stack.css +3 -0
  546. package/src/ui/components/stacks/stack.css +78 -0
  547. package/src/ui/components/stacks/v-stack/VStackRender.js +6 -0
  548. package/src/ui/components/stacks/v-stack/v-stack.css +4 -0
  549. package/src/ui/components/stepper/StepperRender.js +71 -0
  550. package/src/ui/components/stepper/StepperStepRender.js +67 -0
  551. package/src/ui/components/stepper/stepper.css +359 -0
  552. package/src/ui/components/switch/SwitchRender.js +83 -0
  553. package/src/ui/components/switch/switch.css +143 -0
  554. package/src/ui/components/table/data-table/DataTableRender.js +50 -0
  555. package/src/ui/components/table/data-table/bulk-actions.js +34 -0
  556. package/src/ui/components/table/data-table/data-table.css +246 -0
  557. package/src/ui/components/table/data-table/pagination.js +56 -0
  558. package/src/ui/components/table/data-table/tables.js +368 -0
  559. package/src/ui/components/table/data-table/toolbar.js +67 -0
  560. package/src/ui/components/table/simple-table/SimpleTableRender.js +203 -0
  561. package/src/ui/components/table/simple-table/simple-table.css +50 -0
  562. package/src/ui/components/tabs/TabsRender.js +226 -0
  563. package/src/ui/components/tabs/tabs.css +253 -0
  564. package/src/ui/components/toast/ToastRender.js +99 -0
  565. package/src/ui/components/toast/toast.css +201 -0
  566. package/src/ui/components/tooltip/TooltipRender.js +8 -0
  567. package/src/ui/components/tooltip/tooltip.css +113 -0
  568. package/src/ui/index.js +47 -0
  569. package/src/ui/theme.js +0 -0
  570. package/src/ui/theme.scss +1 -0
  571. package/src/ui/tokens/animation.scss +36 -0
  572. package/src/ui/tokens/colors-dark.scss +58 -0
  573. package/src/ui/tokens/colors.scss +54 -0
  574. package/src/ui/tokens/components.scss +32 -0
  575. package/src/ui/tokens/fonts.scss +57 -0
  576. package/src/ui/tokens/glass.scss +10 -0
  577. package/src/ui/tokens/index.scss +38 -0
  578. package/src/ui/tokens/layouts.scss +228 -0
  579. package/src/ui/tokens/opacity.scss +21 -0
  580. package/src/ui/tokens/others.scss +11 -0
  581. package/src/ui/tokens/radius.scss +6 -0
  582. package/src/ui/tokens/reset.scss +51 -0
  583. package/src/ui/tokens/shadows.scss +29 -0
  584. package/src/ui/tokens/spacings.scss +13 -0
  585. package/src/ui/tokens/vars.scss +35 -0
  586. package/src/ui/tokens/viewports.scss +30 -0
  587. package/types/args-types.d.ts +58 -0
  588. package/types/control-flow.d.ts +62 -0
  589. package/types/elements.d.ts +231 -0
  590. package/types/filters/dates.d.ts +43 -0
  591. package/types/filters/index.d.ts +4 -0
  592. package/types/filters/standard.d.ts +70 -0
  593. package/types/filters/strings.d.ts +21 -0
  594. package/types/filters/types.d.ts +20 -0
  595. package/types/forms.d.ts +84 -0
  596. package/types/globals.d.ts +543 -0
  597. package/types/images.d.ts +23 -0
  598. package/types/localStorage.ts +102 -0
  599. package/types/memoize.d.ts +26 -0
  600. package/types/native-fetch.d.ts +72 -0
  601. package/types/nd-element.d.ts +407 -0
  602. package/types/observable-resource.d.ts +3 -0
  603. package/types/observable.d.ts +227 -0
  604. package/types/plugins-manager.d.ts +65 -0
  605. package/types/polyfill.d.ts +18 -0
  606. package/types/property-accumulator.d.ts +33 -0
  607. package/types/router.d.ts +85 -0
  608. package/types/service.d.ts +23 -0
  609. package/types/singleton.d.ts +19 -0
  610. package/types/store.d.ts +63 -0
  611. package/types/template-cloner.ts +43 -0
  612. package/types/validator.ts +66 -0
  613. package/ui.js +1 -0
  614. package/utils.d.ts +4 -0
  615. package/utils.js +12 -0
  616. package/src/data/Observable.js +0 -55
  617. package/src/data/ObservableChecker.js +0 -39
  618. package/src/data/ObservableItem.js +0 -195
  619. package/src/data/Store.js +0 -74
  620. package/src/data/observable-helpers/array.js +0 -74
  621. package/src/data/observable-helpers/batch.js +0 -22
  622. package/src/data/observable-helpers/computed.js +0 -28
  623. package/src/data/observable-helpers/object.js +0 -111
  624. package/src/elements/anchor.js +0 -129
  625. package/src/elements/content-formatter.js +0 -32
  626. package/src/elements/control/for-each-array.js +0 -280
  627. package/src/elements/control/show-if.js +0 -79
  628. package/src/elements/control/switch.js +0 -98
  629. package/src/elements/description-list.js +0 -5
  630. package/src/elements/form.js +0 -71
  631. package/src/elements/html5-semantics.js +0 -12
  632. package/src/elements/img.js +0 -45
  633. package/src/elements/interactive.js +0 -7
  634. package/src/elements/list.js +0 -10
  635. package/src/elements/medias.js +0 -8
  636. package/src/elements/meta-data.js +0 -9
  637. package/src/elements/table.js +0 -14
  638. package/src/utils/args-types.js +0 -100
  639. package/src/utils/debug-manager.js +0 -31
  640. package/src/utils/helpers.js +0 -60
  641. package/src/utils/plugins-manager.js +0 -12
  642. package/src/utils/prototypes.js +0 -45
  643. package/src/wrappers/AttributesWrapper.js +0 -144
  644. package/src/wrappers/DocumentObserver.js +0 -80
  645. package/src/wrappers/ElementCreator.js +0 -114
  646. package/src/wrappers/HtmlElementEventsWrapper.js +0 -64
  647. package/src/wrappers/HtmlElementWrapper.js +0 -50
  648. package/src/wrappers/NdPrototype.js +0 -109
  649. package/src/wrappers/constants.js +0 -2
@@ -1,546 +1,879 @@
1
+ ---
2
+ title: Observables
3
+ description: Reactive state management at the core of NativeDocument - create values that automatically update the UI when they change
4
+ ---
5
+
1
6
  # Observables
2
7
 
3
- Observables are the reactive core of NativeDocument. They allow you to create values that automatically update in the user interface when they change.
8
+ Observables are the reactive core of NativeDocument. They wrap values and automatically update the UI when those values change.
4
9
 
5
- ## Creating Simple Observables
10
+ ## Creating Observables
6
11
 
7
12
  ```javascript
8
- // Create an observable with an initial value
9
- const count = Observable(0);
10
- const message = Observable("Hello World");
13
+ const count = Observable(0);
14
+ const message = Observable('Hello World');
11
15
  const isVisible = Observable(true);
12
16
  ```
13
17
 
14
18
  ## Reading and Modifying Values
15
19
 
16
20
  ```javascript
17
- const name = Observable("John");
21
+ const name = Observable('John');
18
22
 
19
23
  // Read the current value
20
- console.log(name.val()); // "John"
21
-
22
- // Using the proxy syntax (shorthand)
23
- console.log(name.$value); // "John"
24
+ console.log(name.val()); // "John"
25
+ console.log(name.$value); // "John" - proxy shorthand
24
26
 
25
27
  // Update the value
26
- name.set("Jane");
27
- console.log(name.val()); // "Jane"
28
+ name.set('Jane');
29
+ console.log(name.val()); // "Jane"
28
30
 
29
- // Update using proxy syntax
30
- name.$value = "Bob";
31
- console.log(name.val()); // "Bob"
31
+ // Update with proxy syntax
32
+ name.$value = 'Bob';
33
+ console.log(name.val()); // "Bob"
32
34
 
33
35
  // Update with a function
34
- name.set(currentName => currentName.toUpperCase());
35
- console.log(name.val()); // "BOB"
36
+ name.set(current => current.toUpperCase());
37
+ console.log(name.val()); // "BOB"
36
38
  ```
37
39
 
38
40
  ## Listening to Changes
39
41
 
42
+ `.subscribe()` runs on every value change, receiving the new and old values:
43
+
40
44
  ```javascript
41
45
  const counter = Observable(0);
42
46
 
43
- // Subscribe to changes
44
- counter.subscribe(newValue => {
45
- console.log("Counter is now:", newValue);
47
+ counter.subscribe((newValue, oldValue) => {
48
+ console.log(`Counter: ${oldValue} -> ${newValue}`);
46
49
  });
47
50
 
48
- // Function will be called on every change
49
- counter.set(1); // Logs: "Counter is now: 1"
50
- counter.set(2); // Logs: "Counter is now: 2"
51
+ counter.set(1); // "Counter: 0 -> 1"
52
+ counter.set(2); // "Counter: 1 -> 2"
51
53
  ```
52
54
 
53
- ## Observable Objects vs Simple Objects
55
+ ## Value-Specific Watchers - `.on()`
54
56
 
55
- **Important distinction:**
57
+ `.on()` fires only when a specific value is entered (passes `true`) or left (passes `false`). More efficient than `.subscribe()` when you only care about specific states:
56
58
 
57
59
  ```javascript
58
- // Observable.object() creates a PROXY with reactive properties
59
- const userProxy = Observable.object({
60
- name: "Alice",
61
- age: 25
60
+ const status = Observable('idle');
61
+
62
+ status.on('loading', (isActive) => {
63
+ console.log(`Loading state: ${isActive}`);
64
+ });
65
+
66
+ status.on('success', (isActive) => {
67
+ console.log(`Success state: ${isActive}`);
62
68
  });
63
69
 
64
- // Each property is an individual observable
65
- console.log(userProxy.name.val()); // "Alice"
66
- userProxy.name.set("Bob");
70
+ status.set('loading'); // "Loading state: true"
71
+ status.set('success'); // "Loading state: false", "Success state: true"
72
+ status.set('idle'); // "Success state: false"
73
+ ```
67
74
 
68
- // Get all values as plain object
69
- console.log(userProxy.$value); // { name: "Bob", age: 25 }
70
- console.log(Observable.value(userProxy)); // { name: "Bob", age: 25 }
75
+ | | `.on(value, callback)` | `.subscribe(callback)` |
76
+ |---|---|---|
77
+ | **When called** | Entering / leaving a specific value | Every change |
78
+ | **Signature** | `(isActive: boolean) => void` | `(newValue, oldValue) => void` |
79
+ | **Performance** | Only relevant callbacks run | All callbacks run on every change |
80
+ | **Use case** | Specific states | General change detection |
71
81
 
72
- // Observable(object) creates a SINGLE observable containing the whole object
73
- const userSingle = Observable({
74
- name: "Alice",
75
- age: 25
82
+ ## `.when()` - Lightweight Conditional
83
+
84
+ `.when()` creates a lightweight transitive object (no new observable) - ideal for CSS class binding:
85
+
86
+ ```javascript
87
+ const status = Observable('loading');
88
+
89
+ const element = Div({
90
+ class: {
91
+ 'spinner': status.when('loading'),
92
+ 'success': status.when('success'),
93
+ 'error': status.when('error')
94
+ }
76
95
  });
96
+ ```
77
97
 
78
- // The entire object is the observable value
79
- console.log(userSingle.val()); // { name: "Alice", age: 25 }
80
- userSingle.set({ name: "Bob", age: 30 }); // Replace entire object
98
+ ## Observable Checkers
99
+
100
+ Observable checkers are derived observables that transform or evaluate the current value. The aliases `.check()`, `.is()`, `.select()`, `.pluck()`, and `.transform()` all create the same `ObservableChecker` - they exist purely to make code more expressive and readable:
101
+
102
+ ```javascript
103
+ const age = Observable(17);
104
+
105
+ const isAdult = age.check(value => value >= 18);
106
+ console.log(isAdult.val()); // false
107
+
108
+ age.set(20);
109
+ console.log(isAdult.val()); // true
81
110
  ```
82
111
 
83
- **Observable.object is an alias:**
112
+ | Alias | Best suited for |
113
+ |---|---|
114
+ | `.check(fn)` | General conditions |
115
+ | `.is(fn \| value)` | Boolean / state checks |
116
+ | `.select(fn)` | Extracting a field |
117
+ | `.pluck(property)` | Extracting a field (Lodash-style) |
118
+ | `.transform(fn)` | Explicit value transformation |
119
+
84
120
  ```javascript
85
- // These are identical
86
- Observable.object(data) === Observable.json(data) === Observable.init(data)
121
+ ShowIf(user.is(u => u.isAdmin), AdminPanel)
122
+
123
+ const email = user.select(u => u.email);
124
+ const label = status.transform(s => s.toUpperCase());
125
+ const name = user.pluck('name');
87
126
  ```
88
127
 
89
- ## Working with Observable Proxies
128
+ ## Utility Methods
129
+
130
+ ### `toggle()` - Boolean toggle
90
131
 
91
132
  ```javascript
92
- const user = Observable.object({
93
- name: "Alice",
94
- age: 25,
95
- email: "alice@example.com"
96
- });
133
+ const isVisible = Observable(false);
97
134
 
98
- // Access individual properties (each is an observable)
99
- console.log(user.name.val()); // "Alice"
100
- console.log(user.name.$value); // "Alice" (proxy syntax)
135
+ isVisible.toggle(); // true
136
+ isVisible.toggle(); // false
101
137
 
102
- // Update individual properties
103
- user.name.set("Bob");
104
- user.age.$value = 30; // Using proxy syntax
138
+ Button('Toggle').nd.onClick(() => isVisible.toggle());
139
+ ```
105
140
 
106
- // Get the complete object value
107
- console.log(user.$value); // { name: "Bob", age: 30, email: "alice@example.com" }
108
- console.log(Observable.value(user)); // Same as above
141
+ ### `equals()` / `toBool()`
109
142
 
110
- // Listen to individual property changes
111
- user.name.subscribe(newName => {
112
- console.log("New name:", newName);
113
- });
143
+ ```javascript
144
+ const num = Observable(5);
114
145
 
115
- // Update multiple properties
116
- Observable.update(user, {
117
- name: "Charlie",
118
- age: 35
119
- });
146
+ console.log(num.equals(5)); // true
147
+ console.log(num.equals(Observable(5))); // true
148
+
149
+ const text = Observable('');
150
+ console.log(text.toBool()); // false
151
+ text.set('Hello');
152
+ console.log(text.toBool()); // true
120
153
  ```
121
154
 
155
+ ### Convenience checkers - `is...()`
156
+
157
+ All `is...()` methods return an `ObservableChecker<boolean>` and accept an observable or a plain value where noted:
158
+
159
+ | Method | Returns `true` when... |
160
+ |---|---|
161
+ | `isTruthy()` | value is truthy |
162
+ | `isFalsy()` | value is falsy |
163
+ | `isNull()` | value is `null` or `undefined` |
164
+ | `isEmpty()` | value is `null`, `''`, or an empty array |
165
+ | `isNotEmpty()` | value is not `null`, not `''`, and not an empty array |
166
+ | `isEqualTo(value)` | value equals `value` (observable-aware) |
167
+ | `isNotEqualTo(value)` | value does not equal `value` (observable-aware) |
168
+ | `isGreaterThan(value)` | value > `value` (observable-aware) |
169
+ | `isGreaterThanOrEqualTo(value)` | value >= `value` (observable-aware) |
170
+ | `isLessThan(value)` | value < `value` (observable-aware) |
171
+ | `isLessThanOrEqualTo(value)` | value <= `value` (observable-aware) |
172
+ | `isBetween(min, max)` | min <= value <= max (observable-aware) |
173
+ | `isStartingWith(str)` | string starts with `str` (observable-aware) |
174
+ | `isEndingWith(str)` | string ends with `str` (observable-aware) |
175
+ | `isMatchingPattern(regex)` | string matches `regex` (observable-aware) |
176
+ | `isIncludes(value)` | array includes `value`, or string contains `value` (observable-aware) |
177
+ | `isIncludedIn(array)` / `isOneOf(array)` | value is in `array` (observable-aware) |
178
+ | `isHaving(key)` | object has property `key` (observable-aware) |
179
+
122
180
  ```javascript
123
- const todos = Observable.array([
124
- "Buy groceries",
125
- "Call doctor"
126
- ]);
181
+ const score = Observable(72);
182
+ const list = Observable.array([]);
183
+ const name = Observable('');
184
+ const status = Observable('active');
185
+ const user = Observable({ role: 'admin' });
186
+ const minAge = Observable(18);
187
+
188
+ ShowIf(list.isEmpty(), Div('No items yet'))
189
+ ShowIf(name.isFalsy(), Div('Name is required'))
190
+ ShowIf(name.isTruthy(), Div(['Hello, ', name]))
191
+ ShowIf(score.isGreaterThan(50), Div('Passing grade'))
192
+ ShowIf(score.isBetween(0, 100), Div('Valid score'))
193
+ ShowIf(score.isBetween(minAge, 100), Div('Reactive min'))
194
+ ShowIf(name.isStartingWith('A'), Div('Starts with A'))
195
+ ShowIf(status.isOneOf(['active', 'pending']), Div('In progress'))
196
+ ShowIf(user.isHaving('role'), Div('Has a role'))
197
+ ```
127
198
 
128
- // Add elements
129
- todos.push("Clean house");
199
+ ### Convenience transformers - `to...()`
130
200
 
131
- // Remove last element
132
- todos.pop();
201
+ All `to...()` methods return an `ObservableChecker<any>` - a derived observable with the transformed value:
202
+
203
+ | Method | Returns... |
204
+ |---|---|
205
+ | `toUpperCase()` | uppercased string |
206
+ | `toLowerCase()` | lowercased string |
207
+ | `toTrimmed()` | trimmed string |
208
+ | `toBoolean()` | `!!value` |
209
+ | `toLiteral(template, placeholder?)` / `toFormatted(...)` | string with value interpolated into template |
210
+ | `toProperty(key)` | deep property via dot-path (e.g. `'address.city'`) |
211
+ | `toLength()` | length of string or array (`0` if null) |
212
+ | `toClamped(min, max)` | value clamped between min and max (observable-aware) |
213
+ | `toPercent(total)` | `(value / total) * 100` (observable-aware) |
133
214
 
134
- // Use array methods
135
- const completed = todos.filter(todo => todo.includes("✓"));
215
+ ```javascript
216
+ const name = Observable(' Alice ');
217
+ const score = Observable(72);
218
+ const progress = Observable(350);
219
+ const user = Observable({ address: { city: 'Paris' } });
220
+
221
+ name.toTrimmed() // "Alice"
222
+ name.toUpperCase() // " ALICE "
223
+ score.toClamped(0, 100) // 72
224
+ score.toClamped(Observable(0), 100) // reactive min
225
+ progress.toPercent(500) // 70
226
+ user.toProperty('address.city') // "Paris"
227
+
228
+ name.toLiteral('Hello ${v}!') // "Hello Alice !"
229
+ name.toTrimmed().toLiteral('Hello ${v}!') // "Hello Alice!"
230
+
231
+ name.toLiteral('Hello [name]!', '[name]') // custom placeholder
136
232
  ```
137
233
 
138
- ## Computed Observables
234
+ ### `reset()` - Reset to initial value
139
235
 
140
- Computed observables automatically recalculate when their dependencies change.
236
+ ```javascript
237
+ const name = Observable('Alice', { reset: true });
238
+
239
+ name.set('Bob');
240
+ name.reset();
241
+ console.log(name.val()); // "Alice"
242
+ ```
243
+
244
+ ### `intercept(callback)` - Transform or abort before setting
245
+
246
+ The interceptor runs before every `.set()` call. Return a new value to replace it, or return `undefined` to abort the assignment entirely:
141
247
 
142
248
  ```javascript
143
- const firstName = Observable("John");
144
- const lastName = Observable("Doe");
249
+ const age = Observable(0);
145
250
 
146
- // Updates automatically
147
- const fullName = Observable.computed(() => {
148
- return `${firstName.val()} ${lastName.val()}`;
149
- }, [firstName, lastName]);
251
+ age.intercept((newValue, currentValue) => {
252
+ if (newValue < 0) return 0; // clamp minimum
253
+ if (newValue > 120) return 120; // clamp maximum
254
+ return newValue;
255
+ });
150
256
 
151
- console.log(fullName.val()); // "John Doe"
257
+ age.set(-5); // sets 0
258
+ age.set(150); // sets 120
259
+ age.set(25); // sets 25
152
260
 
153
- firstName.set("Jane");
154
- console.log(fullName.val()); // "Jane Doe"
261
+ // Abort example - reject the value without changing anything
262
+ const username = Observable('alice');
263
+
264
+ username.intercept((newValue, currentValue) => {
265
+ if (newValue.trim() === '') return undefined; // abort - keeps 'alice'
266
+ return newValue.toLowerCase().trim();
267
+ });
268
+
269
+ username.set(''); // aborted - still 'alice'
270
+ username.set(' Bob '); // sets 'bob'
155
271
  ```
156
272
 
157
- ## Practical Example: Simple Counter
273
+ ### `interceptMutations(callback)` - Intercept array/object mutations
274
+
275
+ Intercepts mutations (push, splice, etc.) on arrays and objects separately from `.intercept()`:
158
276
 
159
277
  ```javascript
160
- const count = Observable(0);
278
+ const items = Observable.array([]);
161
279
 
162
- const increment = () => count.set(count.val() + 1);
163
- const decrement = () => (count.$value--);
280
+ items.interceptMutations((operations) => {
281
+ console.log('Mutation:', operations);
282
+ });
164
283
 
165
- // Reactive interface
166
- const app = Div({ class: "counter" }, [
167
- Button("-").nd.onClick(decrement),
168
- Span({ class: "count" }, count), // Automatic display
169
- Button("+").nd.onClick(increment)
170
- ]);
284
+ items.push('apple'); // logs mutation details
171
285
  ```
172
286
 
173
- # Batching Operations
287
+ ### `once(predicate, callback)` - Single-time listener
174
288
 
175
- Batching is a performance optimization technique that delays notifications to **dependent observers** (like computed observables) until the end of a batch operation. Individual observable subscribers still receive their notifications immediately, but computed observables that depend on the batch function are only triggered once at the end.
289
+ ```javascript
290
+ const count = Observable(0);
291
+
292
+ count.once(5, () => console.log('Reached 5!')); // fires once when value === 5
293
+ count.once(val => val > 10, () => console.log('Over 10!')); // fires once on condition
294
+
295
+ count.set(5); // "Reached 5!"
296
+ count.set(5); // nothing - already fired
297
+ ```
176
298
 
177
- ### Understanding Batch Behavior
299
+ ### `off(value, callback?)` - Remove watchers
178
300
 
179
301
  ```javascript
180
- const name = Observable("John");
181
- const age = Observable(25);
302
+ const status = Observable('idle');
303
+ const handler = (isActive) => console.log('Loading:', isActive);
304
+
305
+ status.on('loading', handler);
306
+
307
+ status.off('loading', handler); // remove specific callback
308
+ status.off('loading'); // remove all watchers for this value
309
+ ```
182
310
 
183
- // Direct subscribers always get immediate notifications
184
- name.subscribe(value => console.log("Name changed to:", value));
185
- age.subscribe(value => console.log("Age changed to:", value));
311
+ ### `onCleanup(callback)` - Register cleanup callback
186
312
 
187
- const updateProfile = Observable.batch(() => {
188
- name.set("Alice"); // Logs: "Name changed to: Alice"
189
- age.set(30); // Logs: "Age changed to: 30"
313
+ Registers a callback that runs when the observable is cleaned up:
314
+
315
+ ```javascript
316
+ const data = Observable('test');
317
+
318
+ data.onCleanup(() => {
319
+ console.log('Observable cleaned up');
190
320
  });
191
321
 
192
- updateProfile(); // Individual subscribers are notified immediately
322
+ data.cleanup(); // triggers the cleanup callback
193
323
  ```
194
324
 
195
- ### Batching with Computed Dependencies
325
+ ### `persist(key, options?)` - Bind to localStorage
196
326
 
197
- The real power of batching shows when computed observables depend on the batch function:
327
+ Automatically saves on every change and restores on load:
198
328
 
199
329
  ```javascript
200
- const firstName = Observable("John");
201
- const lastName = Observable("Doe");
330
+ const theme = Observable('light').persist('theme');
331
+ theme.set('dark'); // saved to localStorage
202
332
 
203
- // Direct subscribers get immediate notifications
204
- firstName.subscribe(name => console.log("First name:", name));
205
- lastName.subscribe(name => console.log("Last name:", name));
333
+ // On next page load
334
+ const theme = Observable('light').persist('theme');
335
+ theme.val(); // "dark" - restored
336
+ ```
206
337
 
207
- // Batch function for name updates
208
- const updateName = Observable.batch((first, last) => {
209
- firstName.set(first); // Logs: "First name: Alice"
210
- lastName.set(last); // Logs: "Last name: Smith"
338
+ With transform options:
339
+
340
+ ```javascript
341
+ const selectedDate = Observable(new Date()).persist('event:date', {
342
+ get: value => new Date(value), // transform on load
343
+ set: value => value.toISOString() // transform on save
211
344
  });
345
+ ```
346
+
347
+ ### `clone()` - Create a copy
212
348
 
213
- // Computed that depends on the BATCH FUNCTION (not individual observables)
214
- const fullName = Observable.computed(() => {
215
- return `${firstName.val()} ${lastName.val()}`;
216
- }, updateName); // ← Depends on the batch function
349
+ Creates a deep clone of the observable's current value:
217
350
 
218
- fullName.subscribe(name => console.log("Full name:", name));
351
+ ```javascript
352
+ const original = Observable({ name: 'Alice', scores: [1, 2, 3] });
353
+ const copy = original.clone();
219
354
 
220
- // When we call the batch:
221
- updateName("Alice", "Smith");
222
- // Logs:
223
- // "First name: Alice" ← immediate notification
224
- // "Last name: Smith" ← immediate notification
225
- // "Full name: Alice Smith" ← single notification at the end
355
+ copy.set({ ...copy.val(), name: 'Bob' });
356
+ original.val().name; // still "Alice"
226
357
  ```
227
358
 
228
- ### Comparison: Normal vs Batch Dependencies
359
+ ### `resolve()` - Extract plain value
360
+
361
+ Recursively extracts plain values from observables, arrays, and proxies:
229
362
 
230
363
  ```javascript
231
- const score = Observable(0);
232
- const lives = Observable(3);
364
+ const name = Observable('Alice');
365
+ name.resolve(); // "Alice" - same as Observable.value(name)
233
366
 
234
- // Method 1: Computed depends on individual observables
235
- const gameStatus1 = Observable.computed(() => {
236
- return `Score: ${score.val()}, Lives: ${lives.val()}`;
237
- }, [score, lives]); // ← Depends on individual observables
367
+ const user = Observable.object({ name: Observable('Bob') });
368
+ Observable.value(user); // { name: 'Bob' } - deeply resolved
369
+ ```
238
370
 
239
- // Method 2: Computed depends on batch function
240
- const updateGame = Observable.batch(() => {
241
- score.set(score.val() + 100);
242
- lives.set(lives.val() - 1);
243
- });
371
+ ### `trigger()` - Force update without value change
244
372
 
245
- const gameStatus2 = Observable.computed(() => {
246
- return `Score: ${score.val()}, Lives: ${lives.val()}`;
247
- }, updateGame); // Depends on the batch function
373
+ ```javascript
374
+ const data = Observable('test');
375
+ data.trigger(); // notifies all subscribers without changing the value
376
+ ```
248
377
 
249
- // Without batching - gameStatus1 recalculates twice:
250
- score.set(100); // gameStatus1 recalculates
251
- lives.set(2); // gameStatus1 recalculates again
378
+ ### `cleanup()` - Remove all listeners
252
379
 
253
- // With batching - gameStatus2 recalculates only once:
254
- updateGame(); // gameStatus2 recalculates only at the end
380
+ ```javascript
381
+ const data = Observable('test');
382
+ data.cleanup(); // removes all listeners and prevents new subscriptions
255
383
  ```
256
384
 
257
- ### Practical Example: Shopping Cart
385
+ ### `deepSubscribe(callback)` - Deep change detection
386
+
387
+ Reacts to changes at any depth - array mutations and property changes on nested observables:
258
388
 
259
389
  ```javascript
260
- const items = Observable.array([]);
261
- const discount = Observable(0);
262
- const shippingCost = Observable(0);
390
+ const tags = Observable.array([{ label: Observable('admin') }]);
263
391
 
264
- // Individual subscribers for immediate UI updates
265
- items.subscribe(items => {
266
- console.log('Items count : '+items.length);
267
- });
392
+ const unsub = tags.deepSubscribe(value => console.log('changed:', value));
268
393
 
269
- discount.subscribe(discount => {
270
- console.log(`Discount: ${discount}%`);
271
- });
394
+ tags.push({ label: Observable('editor') }); // triggers
395
+ tags.at(0).label.set('superadmin'); // triggers
396
+ tags.splice(0, 1); // triggers + cleans up listener
272
397
 
273
- // Batch function for cart operations
274
- const updateCart = Observable.batch((cartData) => {
275
- items.splice(0); // Clear current items
276
- cartData.items.forEach(item => items.push(item));
277
- discount.set(cartData.discount);
278
- shippingCost.set(cartData.shipping);
279
- });
398
+ unsub(); // manual cleanup
399
+ ```
280
400
 
281
- // Expensive calculation that should only run after complete cart updates
282
- const cartTotal = Observable.computed(() => {
283
- const itemsTotal = items.val().reduce((sum, item) => sum + (item.price * item.quantity), 0);
284
- const discountAmount = itemsTotal * (discount.val() / 100);
285
- return itemsTotal - discountAmount + shippingCost.val();
286
- }, updateCart); // ← Only recalculates when updateCart() is called
287
-
288
- // Example usage
289
- updateCart({
290
- items: [
291
- { name: "Product A", price: 29.99, quantity: 2 },
292
- { name: "Product B", price: 19.99, quantity: 1 }
293
- ],
294
- discount: 10,
295
- shipping: 5.99
296
- });
297
- // Individual subscribers fire immediately, cartTotal calculates once at the end
401
+ ---
402
+
403
+ ## Observable Static Methods
404
+
405
+ ### `Observable.setLocale(locale)`
406
+
407
+ Sets the locale observable used by `.format()`. Accepts an observable or a plain string:
408
+
409
+ ```javascript
410
+ Observable.setLocale(I18nService.current); // observable (recommended)
411
+ Observable.setLocale(Observable('fr')); // plain observable
412
+ Observable.setLocale('fr'); // plain string - wrapped automatically
298
413
  ```
299
414
 
300
- ### Async Batching
415
+ ### `Observable.value(data)`
301
416
 
302
- Batch functions handle asynchronous operations, delaying dependent notifications until the promise resolves:
417
+ Recursively extracts plain values from observables, arrays, and proxies:
303
418
 
304
419
  ```javascript
305
- const isLoading = Observable(false);
306
- const userData = Observable(null);
307
- const error = Observable(null);
420
+ Observable.value(Observable(42)); // 42
421
+ Observable.value(Observable.array([Observable(1)])); // [1]
422
+ Observable.value(Observable.object({ n: Observable('x') })); // { n: 'x' }
423
+ Observable.value('plain string'); // 'plain string'
424
+ ```
308
425
 
309
- // These subscribe immediately to loading states
310
- isLoading.subscribe(loading => {
311
- console.log('Loading.....');
312
- });
426
+ ### `Observable.cleanup(observable)`
313
427
 
314
- const fetchUser = Observable.batch(async (userId) => {
315
- isLoading.set(true); // Immediate notification
316
- error.set(null); // Immediate notification
317
-
318
- try {
319
- const response = await fetch(`/api/users/${userId}`);
320
- const data = await response.json();
321
- userData.set(data); // Immediate notification
322
- } catch (err) {
323
- error.set(err.message); // Immediate notification
324
- } finally {
325
- isLoading.set(false); // Immediate notification
326
- }
327
- // Dependent computed observables are notified HERE
428
+ Static alias for `observable.cleanup()`:
429
+
430
+ ```javascript
431
+ Observable.cleanup(myObservable);
432
+ ```
433
+
434
+ ### `Observable.autoCleanup(enable, options?)`
435
+
436
+ Enables automatic cleanup on page unload and via a periodic interval:
437
+
438
+ ```javascript
439
+ Observable.autoCleanup(true, {
440
+ interval: 60000, // run cleanup every 60s (default)
441
+ threshold: 100 // clean up when > 100 unreferenced observables (default)
328
442
  });
443
+ ```
329
444
 
330
- // This computed depends on the batch function
331
- const userDisplay = Observable.computed(() => {
332
- if (isLoading.val()) return "Loading...";
333
- if (error.val()) return `Error: ${error.val()}`;
334
- if (userData.val()) return `Hello ${userData.val().name}`;
335
- return "No user";
336
- }, fetchUser); // ← Only updates when fetchUser() completes
445
+ ### `Observable.getById(id)`
337
446
 
338
- await fetchUser(123);
447
+ Retrieves a registered observable by its internal memory ID. Useful for debugging:
448
+
449
+ ```javascript
450
+ const obs = Observable(42);
451
+ const id = obs.toString(); // "{{#ObItem::(N)}}"
452
+
453
+ Observable.getById(N); // returns obs
339
454
  ```
340
455
 
341
- ### Single Batch Dependency Only
456
+ ### `Observable.useValueProperty(propertyName?)`
342
457
 
343
- **Important**: Computed observables can only depend on **one batch function**, not multiple:
458
+ Defines a property name as an alias for `$value`. Default is `'value'`, but you can pass any name:
344
459
 
345
460
  ```javascript
346
- const user = Observable.object({ name: "", email: "" });
347
- const settings = Observable.object({ theme: "light", lang: "en" });
461
+ Observable.useValueProperty(); // enables .value
462
+ Observable.useValueProperty('val'); // enables .val (overrides the method!)
463
+ Observable.useValueProperty('current'); // enables .current
348
464
 
349
- const updateProfile = Observable.batch((profileData) => {
350
- user.name.set(profileData.name);
351
- user.email.set(profileData.email);
352
- settings.theme.set(profileData.theme);
353
- settings.lang.set(profileData.lang);
354
- });
465
+ const count = Observable(0);
355
466
 
356
- // Correct: Single batch dependency
357
- const profileSummary = Observable.computed(() => {
358
- return {
359
- user: user.$value,
360
- settings: settings.$value,
361
- lastUpdated: Date.now()
362
- };
363
- }, updateProfile); // ← Single batch function
467
+ // With default
468
+ Observable.useValueProperty();
469
+ count.value; // 0 - same as count.$value
470
+ count.value = 5; // same as count.$value = 5
364
471
 
365
- // This is NOT supported:
366
- // Observable.computed(callback, [batch1, batch2])
472
+ // With custom name
473
+ Observable.useValueProperty('current');
474
+ count.current; // 0
475
+ count.current = 5; // sets to 5
367
476
  ```
368
477
 
369
- ### Performance Benefits
478
+ > Be careful not to pick a name that conflicts with an existing method - for example `'val'` would shadow the `.val()` method.
479
+
480
+ ---
481
+
482
+ ## Observable Objects
483
+
484
+ ### `Observable(object)` vs `Observable.object(object)`
370
485
 
371
486
  ```javascript
372
- const items = Observable.array([]);
487
+ // Observable(object) - single observable wrapping the whole object
488
+ const userSingle = Observable({ name: 'Alice', age: 25 });
489
+ userSingle.val();
490
+ userSingle.set({ name: 'Bob', age: 30 });
491
+
492
+ // Observable.object() - proxy with each property as its own observable
493
+ const userProxy = Observable.object({ name: 'Alice', age: 25 });
494
+ userProxy.name.val(); // "Alice"
495
+ userProxy.name.set('Bob');
496
+ userProxy.$value; // { name: 'Bob', age: 25 }
497
+ ```
498
+
499
+ `Observable.object` has aliases:
373
500
 
374
- // Expensive computed operation
375
- const expensiveCalculation = Observable.computed(() => {
376
- console.log("🔄 Recalculating..."); // This helps visualize when it runs
377
- return items.val()
378
- .filter(item => item.active)
379
- .map(item => item.price * item.quantity)
380
- .reduce((sum, total) => sum + total, 0);
381
- }, [items]); // ← Depends on individual observable
382
-
383
- const batchUpdateItems = Observable.batch(() => {
384
- items.push({ active: true, price: 10, quantity: 2 });
385
- items.push({ active: true, price: 15, quantity: 1 });
386
- items.push({ active: false, price: 20, quantity: 3 });
501
+ ```javascript
502
+ Observable.object(data) // same as:
503
+ Observable.json(data)
504
+ Observable.init(data)
505
+ ```
506
+
507
+ ### Subscribing to an object observable
508
+
509
+ Subscribing to `Observable.object()` reacts to any property change:
510
+
511
+ ```javascript
512
+ const user = Observable.object({ name: 'Alice', age: 25 });
513
+
514
+ user.subscribe(value => {
515
+ console.log('User changed:', value);
387
516
  });
388
517
 
389
- const optimizedCalculation = Observable.computed(() => {
390
- console.log("✅ Optimized recalculation");
391
- return items.val()
392
- .filter(item => item.active)
393
- .map(item => item.price * item.quantity)
394
- .reduce((sum, total) => sum + total, 0);
395
- }, batchUpdateItems); // ← Depends on batch function
518
+ user.name.set('Bob'); // triggers the parent subscribe
519
+ user.age.set(30); // triggers the parent subscribe
520
+ ```
521
+
522
+ ---
396
523
 
397
- // Without batching:
398
- items.push({ active: true, price: 10, quantity: 2 }); // 🔄 Recalculating...
399
- items.push({ active: true, price: 15, quantity: 1 }); // 🔄 Recalculating...
400
- items.push({ active: false, price: 20, quantity: 3 }); // 🔄 Recalculating...
524
+ ## Observable Arrays
401
525
 
402
- // With batching:
403
- batchUpdateItems(); // Optimized recalculation (only once!)
526
+ ```javascript
527
+ const todos = Observable.array(['Buy groceries', 'Call doctor']);
528
+
529
+ todos.push('Clean house');
530
+ todos.pop();
531
+ todos.splice(0, 1);
532
+
533
+ console.log(todos.val().length); // 1
404
534
  ```
405
535
 
406
- ### Best Practices
536
+ ### Array-specific methods
407
537
 
408
- 1. **Use batch dependencies for expensive computations**: When you have costly computed observables that shouldn't recalculate on every individual change
538
+ | Method | Description |
539
+ |---|---|
540
+ | `push(item)` | Add item at the end |
541
+ | `pop()` | Remove last item |
542
+ | `splice(index, count)` | Remove items at index |
543
+ | `at(index)` | Get observable at index |
544
+ | `merge(values)` | Merge new values |
545
+ | `clear()` | Empty the array |
546
+ | `empty()` | Alias for `clear()` |
547
+ | `count(condition?)` | Count items matching a condition |
548
+ | `swap(indexA, indexB)` | Swap two items by index |
549
+ | `swapItems(itemA, itemB)` | Swap two items by reference |
550
+ | `insertAfter(data, target)` | Insert after a target item |
551
+ | `remove(index)` | Remove item at index |
552
+ | `removeItem(item)` | Remove item by reference |
553
+ | `clone()` | Deep clone the array |
554
+ | `isEmpty()` | Returns an `ObservableChecker` |
409
555
 
410
- 2. **Keep individual subscribers for immediate feedback**: UI feedback like input validation should use direct subscriptions
556
+ ### Filtering with `.where()`
411
557
 
412
- 3. **Batch related operations**: Group logically connected updates that should trigger dependent computations together
558
+ > `.where()`, `.whereSome()`, and `.whereEvery()` are available on **`ObservableArray` only** - not on plain observables. They return a new live `ObservableArray` that re-filters automatically when the source or any reactive predicate changes.
559
+
560
+ **Function predicate** - full item passed to the callback:
561
+
562
+ ```javascript
563
+ const users = Observable.array([
564
+ { name: 'Alice', age: 17, role: 'user' },
565
+ { name: 'Bob', age: 25, role: 'admin' },
566
+ { name: 'Carol', age: 32, role: 'user' },
567
+ { name: 'Dave', age: 28, role: 'admin' },
568
+ ]);
413
569
 
414
- 4. **Don't over-batch**: Only use batching when you have computed observables that benefit from delayed updates
570
+ const adults = users.where(item => item.age >= 18);
571
+ // -> Bob, Carol, Dave
572
+ ```
415
573
 
416
- ### Common Patterns
574
+ **Object predicate** - filter per field:
417
575
 
418
- ### State Machine with Batched Transitions
419
576
  ```javascript
420
- const gameState = Observable.object({
421
- level: 1,
422
- score: 0,
423
- lives: 3
577
+ const activeAdmins = users.where({
578
+ role: 'admin', // shorthand for equals
579
+ age: val => val >= 18 // plain function on the field value
424
580
  });
581
+ // -> Bob, Dave
582
+ ```
425
583
 
426
- // Individual subscribers for immediate UI updates
427
- gameState.score.subscribe(score => updateScoreDisplay(score));
428
- gameState.lives.subscribe(lives => updateLivesDisplay(lives));
584
+ **Reactive predicate** - re-filters when an observable changes:
429
585
 
430
- // Batch function for state transitions
431
- const levelUp = Observable.batch(() => {
432
- gameState.level.set(gameState.level.val() + 1);
433
- gameState.score.set(gameState.score.val() + 1000);
434
- gameState.lives.set(gameState.lives.val() + 1);
586
+ ```javascript
587
+ import { match } from 'native-document/filters';
588
+
589
+ const search = Observable('');
590
+
591
+ const results = users.where({
592
+ name: match(search) // re-filters every time search changes
435
593
  });
436
594
 
437
- // Complex computed that should only run after complete transitions
438
- const gameStatusMessage = Observable.computed(() => {
439
- const state = gameState.$value;
440
- return `Level ${state.level}: ${state.score} points, ${state.lives} lives remaining`;
441
- }, levelUp); // ← Only updates when levelUp() is called
595
+ search.set('ali'); // -> Alice
596
+ search.set(''); // -> all users
442
597
  ```
443
598
 
444
- ### When NOT to Use Batch Dependencies
599
+ **`and` / `or` / `not`** - composable per-field filters:
600
+
601
+ > `and`, `or`, and `not` operate on filter result objects (the kind returned by `equals`, `greaterThan`, `match`, etc.) **within a single field**. For cross-field logic, use the `_` key with a plain function.
445
602
 
446
- - **Real-time updates**: When computed observables need to update immediately
447
- - **Simple computations**: When the computational cost is minimal
448
- - **Debugging**: Batching can make the flow harder to debug
449
- - **Single observable changes**: No benefit when only one observable changes
603
+ ```javascript
604
+ import { equals, greaterThan, lessThan, and, or, not } from 'native-document/filters';
450
605
 
451
- The key insight is that batching in NativeDocument is about **controlling when dependent computed observables recalculate**, not about suppressing individual observable notifications.
606
+ // and - field must pass ALL conditions
607
+ const youngAdults = users.where({
608
+ age: and(greaterThan(18), lessThan(30))
609
+ });
610
+ // -> Bob (25), Dave (28)
452
611
 
453
- ## String Templates with Observables
612
+ // or - field must pass AT LEAST ONE condition
613
+ const adminOrEditor = users.where({
614
+ role: or(equals('admin'), equals('editor'))
615
+ });
616
+ // -> Bob, Dave
454
617
 
455
- ### The .use() Method
618
+ // not - inverts a filter
619
+ const nonAdmins = users.where({
620
+ role: not(equals('admin'))
621
+ });
622
+ // -> Alice, Carol
456
623
 
457
- ```javascript
458
- const name = Observable("Alice");
459
- const age = Observable(25);
624
+ // Cross-field OR - use the _ key with a plain function
625
+ const adminsOrMinors = users.where({
626
+ _: item => item.role === 'admin' || item.age < 18
627
+ });
628
+ // -> Alice (minor), Bob (admin), Dave (admin)
629
+ ```
460
630
 
461
- const template = "Hello ${name}, you are ${age} years old";
462
- const message = template.use({ name, age });
631
+ **`whereSome(fields, filter)`** - at least one field matches (OR across fields):
463
632
 
464
- console.log(message.val()); // "Hello Alice, you are 25 years old"
633
+ ```javascript
634
+ import { match } from 'native-document/filters';
465
635
 
466
- name.set("Bob");
467
- console.log(message.val()); // "Hello Bob, you are 25 years old"
636
+ const search = Observable('car');
637
+
638
+ const results = products.whereSome(['name', 'category'], match(search));
639
+ // matches if name OR category contains 'car'
468
640
  ```
469
641
 
470
- ### Automatic Template Resolution
642
+ **`whereEvery(fields, filter)`** - all fields match (AND across fields):
471
643
 
472
644
  ```javascript
473
- const greeting = Observable("Hello");
474
- const user = Observable("Marie");
645
+ import { equals } from 'native-document/filters';
475
646
 
476
- // Observables are automatically integrated
477
- const element = Div(null, `${greeting} ${user}!`);
478
- // Updates when greeting or user changes
647
+ const verified = items.whereEvery(['status', 'verified'], equals('active'));
648
+ // matches only if both status AND verified equal 'active'
479
649
  ```
480
650
 
481
- ## Observable Checkers
651
+ ---
652
+
653
+ ## Computed Observables
482
654
 
483
- Create derived observables with conditions:
655
+ Computed observables automatically recalculate when their dependencies change. The callback receives the current value of each dependency **as arguments, in the order they are declared**:
484
656
 
485
657
  ```javascript
486
- const age = Observable(17);
487
- const isAdult = age.check(value => value >= 18);
658
+ const firstName = Observable('John');
659
+ const lastName = Observable('Doe');
488
660
 
489
- console.log(isAdult.val()); // false
661
+ const fullName = Observable.computed((first, last) => {
662
+ return `${first} ${last}`;
663
+ }, [firstName, lastName]);
490
664
 
491
- age.set(20);
492
- console.log(isAdult.val()); // true
665
+ console.log(fullName.val()); // "John Doe"
666
+
667
+ firstName.set('Jane');
668
+ console.log(fullName.val()); // "Jane Doe"
493
669
  ```
494
670
 
495
- ## Memory Management
671
+ Another example with more dependencies:
496
672
 
497
673
  ```javascript
498
- const data = Observable("test");
674
+ const price = Observable(100);
675
+ const quantity = Observable(2);
676
+ const tax = Observable(0.2);
677
+
678
+ const total = Observable.computed((p, q, t) => {
679
+ return p * q * (1 + t);
680
+ }, [price, quantity, tax]);
499
681
 
500
- // Create a subscription
501
- const unsubscribe = data.subscribe(value => console.log(value));
682
+ console.log(total.val()); // 240
502
683
 
503
- // Clean up manually if needed
504
- unsubscribe();
684
+ quantity.set(3);
685
+ console.log(total.val()); // 360
686
+ ```
687
+
688
+ ---
505
689
 
506
- // Complete observable cleanup
507
- data.cleanup(); // Removes all listeners and prevents new subscriptions
690
+ ## Batching Updates
508
691
 
509
- // Manual trigger (useful for forcing updates)
510
- data.trigger(); // Notifies all subscribers without changing the value
692
+ `Observable.batch()` delays notifications to **dependent computed observables** until all changes are done. Individual subscribers still fire immediately.
511
693
 
512
- // Get original value (useful for reset functionality)
513
- console.log(data.originalValue()); // Returns the initial value
694
+ ```javascript
695
+ const firstName = Observable('John');
696
+ const lastName = Observable('Doe');
514
697
 
515
- // Extract values from any observable structure
516
- const complexData = Observable.object({
517
- user: "John",
518
- items: [1, 2, 3]
698
+ const updateName = Observable.batch((first, last) => {
699
+ firstName.set(first);
700
+ lastName.set(last);
519
701
  });
520
- console.log(Observable.value(complexData)); // Plain object with extracted values
702
+
703
+ const fullName = Observable.computed((first, last) => {
704
+ return `${first} ${last}`;
705
+ }, updateName); // depends on the batch - recalculates once
706
+
707
+ fullName.subscribe(name => console.log('Full name:', name));
708
+
709
+ updateName('Alice', 'Smith');
710
+ // "Full name: Alice Smith" - single notification
521
711
  ```
522
712
 
713
+ Async batching is also supported:
714
+
715
+ ```javascript
716
+ const isLoading = Observable(false);
717
+ const userData = Observable(null);
718
+ const error = Observable(null);
719
+
720
+ const fetchUser = Observable.batch(async (userId) => {
721
+ isLoading.set(true);
722
+ error.set(null);
723
+ try {
724
+ const data = await fetch(`/api/users/${userId}`).then(r => r.json());
725
+ userData.set(data);
726
+ } catch (err) {
727
+ error.set(err.message);
728
+ } finally {
729
+ isLoading.set(false);
730
+ }
731
+ });
732
+
733
+ const userDisplay = Observable.computed((loading, data, err) => {
734
+ if (loading) return 'Loading...';
735
+ if (err) return `Error: ${err}`;
736
+ if (data) return `Hello ${data.name}`;
737
+ return 'No user';
738
+ }, fetchUser);
739
+
740
+ await fetchUser(123);
741
+ ```
742
+
743
+ > A computed observable can depend on **one batch function** only, not multiple.
744
+
745
+ ---
746
+
747
+ ## Observable Resources
748
+
749
+ `Observable.resource()` manages async data fetching with built-in states, `AbortController` support, and reactive dependencies. See **[Observable Resource](./observable-resource.md)** for the full guide.
750
+
751
+ ```javascript
752
+ const userId = Observable(1);
753
+
754
+ const user = Observable.resource(
755
+ async (id, signal) => {
756
+ const res = await fetch(`/api/users/${id}`, { signal });
757
+ return res.json();
758
+ },
759
+ [userId],
760
+ { auto: true }
761
+ );
762
+
763
+ Div([
764
+ ShowIf(user.loading, () => Div('Loading...')),
765
+ ShowIf(user.isReady(), () => Div(user.data.select(u => u.name))),
766
+ ShowIf(user.isErrored(), () => Div(['Error: ', user.error]))
767
+ ]);
768
+ ```
769
+
770
+ ---
771
+
772
+ ## Observable.format()
773
+
774
+ Creates a derived observable that formats the current value using `Intl`. You must set a locale observable before using `format()`. When using the CLI template, pass `I18nService.current` as the locale:
775
+
776
+ ```javascript
777
+ import { Observable } from 'native-document';
778
+ import { I18nService } from '@/core/services';
779
+
780
+ // Using the CLI template i18n service (recommended)
781
+ Observable.setLocale(I18nService.current);
782
+
783
+ // Or with a plain observable
784
+ const $locale = Observable('fr');
785
+ Observable.setLocale($locale);
786
+
787
+ const price = Observable(15000);
788
+ const date = Observable(new Date());
789
+ const count = Observable(3);
790
+
791
+ price.format('currency') // "15 000 FCFA"
792
+ price.format('currency', { currency: 'EUR' }) // "15 000,00 €"
793
+ price.format('currency', { notation: 'compact' }) // "15 K FCFA"
794
+
795
+ price.format('number') // "15 000"
796
+
797
+ Observable(0.15).format('percent') // "15,0 %"
798
+ Observable(0.15).format('percent', { decimals: 2 }) // "15,00 %"
799
+
800
+ date.format('date') // "3 mars 2026"
801
+ date.format('date', { dateStyle: 'full' }) // "mardi 3 mars 2026"
802
+ date.format('date', { format: 'DD/MM/YYYY' }) // "03/03/2026"
803
+
804
+ date.format('time') // "20:30"
805
+ date.format('time', { second: '2-digit' }) // "20:30:00"
806
+
807
+ date.format('datetime') // "3 mars 2026, 20:30"
808
+
809
+ date.format('relative') // "dans 11 jours"
810
+ date.format('relative', { unit: 'month' }) // "dans 1 mois"
811
+
812
+ count.format('plural', { singular: 'billet', plural: 'billets' }) // "3 billets"
813
+
814
+ // Custom formatter - works like transform()
815
+ price.format(value => `${value.toLocaleString()} FCFA`)
816
+ ```
817
+
818
+ Formatted observables react to locale changes automatically:
819
+
820
+ ```javascript
821
+ Observable.setLocale(I18nService.current);
822
+
823
+ const label = Observable(15000).format('currency', { currency: 'XOF' });
824
+ label.val(); // "15 000 FCFA"
825
+
826
+ I18nService.current.set('en-US');
827
+ label.val(); // "$15,000.00"
828
+ ```
829
+
830
+ ### Available format types
831
+
832
+ | Type | Input | Key options |
833
+ |---|---|---|
834
+ | `currency` | `number` | `currency`, `notation`, `minimumFractionDigits`, `maximumFractionDigits` |
835
+ | `number` | `number` | `notation`, `minimumFractionDigits`, `maximumFractionDigits` |
836
+ | `percent` | `number` | `decimals` |
837
+ | `date` | `Date \| number` | `dateStyle`, `format` |
838
+ | `time` | `Date \| number` | `hour`, `minute`, `second`, `format` |
839
+ | `datetime` | `Date \| number` | `dateStyle`, `hour`, `minute`, `second`, `format` |
840
+ | `relative` | `Date \| number` | `unit`, `numeric` |
841
+ | `plural` | `number` | `singular`, `plural` |
842
+
843
+ ### Extending Formatters
844
+
845
+ ```javascript
846
+ import { Formatters } from 'native-document';
847
+
848
+ Formatters.duration = (value, locale) => {
849
+ const hours = Math.floor(value / 3600);
850
+ const minutes = Math.floor((value % 3600) / 60);
851
+ return `${hours}h${minutes < 10 ? '0' : ''}${minutes}`;
852
+ };
853
+
854
+ const duration = Observable(3661);
855
+ duration.format('duration'); // "1h01"
856
+ ```
857
+
858
+ ---
859
+
523
860
  ## Best Practices
524
861
 
525
- 1. **Use descriptive names** for your observables
526
- 2. **Understand the difference**: `Observable(object)` vs `Observable.object(object)`
527
- 3. **Use proxies for convenience**: `obs.$value` instead of `obs.val()`
528
- 4. **Group related data** with `Observable.object()` for individual property reactivity
529
- 5. **Use `Observable.value()`** to extract plain values from complex structures
530
- 6. **Prefer computed** for derived values
531
- 7. **Clean up** unused observables to prevent memory leaks
532
- 8. **Use `trigger()`** when you need to force updates without value changes
533
- 9. **Avoid** direct modifications in subscription callbacks
862
+ 1. Use descriptive names for your observables
863
+ 2. Know the difference between `Observable(object)` (single observable) and `Observable.object(object)` (per-property observables)
864
+ 3. Use `Observable.computed()` for derived values instead of manual subscriptions
865
+ 4. Use `.persist()` for component-level localStorage binding instead of the global Store
866
+ 5. Use `.intercept()` to sanitize, clamp, or abort values at the source
867
+ 6. Use `.on()` instead of `.subscribe()` when you only care about specific values
868
+ 7. Use `Observable.autoCleanup(true)` in long-running applications
869
+ 8. Use `Observable.resource()` for async data instead of manual fetch + observable patterns
534
870
 
535
871
  ## Next Steps
536
872
 
537
- Now that you understand NativeDocument's observable, explore these advanced topics:
538
-
539
- - **[Elements](elements.md)** - Creating and composing UI
540
- - **[Conditional Rendering](conditional-rendering.md)** - Dynamic content
541
- - **[List Rendering](list-rendering.md)** - (ForEach | ForEachArray) and dynamic lists
542
- - **[Routing](routing.md)** - Navigation and URL management
543
- - **[State Management](state-management.md)** - Global state patterns
544
- - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
545
- - **[Memory Management](memory-management.md)** - Memory management
546
- - **[Anchor](anchor.md)** - Anchor
873
+ - **[Elements](./elements.md)** - Creating and composing UI
874
+ - **[Conditional Rendering](./conditional-rendering.md)** - Dynamic content
875
+ - **[List Rendering](./list-rendering.md)** - ForEach and dynamic lists
876
+ - **[State Management](./state-management.md)** - Global state with Store
877
+ - **[Observable Resource](./observable-resource.md)** - Async data fetching
878
+ - **[i18n & Formatting](./i18n.md)** - Locale-aware formatting
879
+ - **[Filters](./filters.md)** - Data filtering helpers