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
@@ -1,1012 +1,387 @@
1
- # List Rendering
2
-
3
- List rendering in NativeDocument provides powerful utilities for efficiently displaying dynamic collections of data. The framework offers two specialized functions: `ForEach` for generic iteration over objects and arrays, and `ForEachArray` for high-performance array-specific operations with advanced optimization features.
1
+ ---
2
+ title: List Rendering
3
+ description: Efficiently render dynamic collections with ForEach and ForEachArray - automatic DOM updates, keyed diffing, and reactive filtering
4
+ ---
4
5
 
5
- ## Understanding List Rendering
6
+ # List Rendering
6
7
 
7
- List rendering automatically manages DOM updates when your data changes. Instead of manually manipulating the DOM, you define how each item should be rendered, and NativeDocument handles creation, updates, reordering, and cleanup efficiently.
8
+ NativeDocument provides two functions for rendering dynamic collections: `ForEach` for generic iteration over arrays and objects, and `ForEachArray` for high-performance array-specific operations.
8
9
 
9
10
  ```javascript
10
- import { ForEach, Observable, Li, Ul } from 'native-document';
11
+ import { ForEach, ForEachArray } from 'native-document/elements';
12
+ ```
11
13
 
12
- const items = Observable.array(['Apple', 'Banana', 'Cherry']);
14
+ ---
13
15
 
14
- // Automatically updates when items change
15
- const itemList = Ul([
16
- ForEach(items, item => Li(item))
17
- ]);
16
+ ## `ForEach` - Generic Collection Rendering
18
17
 
19
- // Add items - DOM updates automatically
20
- items.push('Orange', 'Grape');
21
- ```
22
-
23
- ## ForEach - Generic Collection Rendering
18
+ `ForEach` works with both observable arrays and observable objects.
24
19
 
25
- `ForEach` is the versatile option that works with both arrays and objects. It's perfect when you need flexibility or are working with mixed data types.
20
+ ```javascript
21
+ ForEach(data, callback, key?, options?)
22
+ ```
26
23
 
27
- ### Basic Array Iteration
24
+ ### Array iteration
28
25
 
29
26
  ```javascript
30
27
  const fruits = Observable.array(['Apple', 'Banana', 'Cherry']);
31
28
 
32
- const FruitList = Ul([
33
- ForEach(fruits, fruit =>
34
- Li({ class: 'fruit-item' }, fruit)
35
- )
36
- ]);
29
+ Ul(
30
+ ForEach(fruits, fruit => Li(fruit))
31
+ )
37
32
 
38
- // All array operations trigger updates
39
- fruits.push('Orange'); // Adds new item
40
- fruits.splice(1, 1); // Removes 'Banana'
41
- fruits.sort(); // Reorders items
33
+ // All array operations trigger DOM updates
34
+ fruits.push('Orange');
35
+ fruits.splice(1, 1);
36
+ fruits.sort();
42
37
  ```
43
38
 
44
- ### Object Iteration
39
+ ### Object iteration
45
40
 
46
41
  ```javascript
47
- const userRoles = Observable({
48
- admin: 'Administrator',
49
- editor: 'Content Editor',
42
+ const roles = Observable({
43
+ admin: 'Administrator',
44
+ editor: 'Content Editor',
50
45
  viewer: 'Read Only'
51
46
  });
52
47
 
53
- const RolesList = Ul([
54
- ForEach(userRoles, (roleName, roleKey) =>
55
- Li([
56
- Strong(roleKey), ': ', roleName
57
- ])
48
+ Ul(
49
+ ForEach(roles, (roleName, roleKey) =>
50
+ Li([Strong(roleKey), ': ', roleName])
58
51
  )
59
- ]);
60
-
61
- // Update object - DOM reflects changes
62
- userRoles.set({
63
- ...userRoles.val(),
64
- moderator: 'Community Moderator'
65
- });
52
+ )
66
53
  ```
67
54
 
68
- ### Using Index Parameter
55
+ ### Index parameter
56
+
57
+ The second callback argument is an **observable** tracking the item's current index:
69
58
 
70
59
  ```javascript
71
- const tasks = Observable.array([
72
- 'Review pull requests',
73
- 'Update documentation',
74
- 'Fix bug reports'
75
- ]);
60
+ const tasks = Observable.array(['Review PRs', 'Update docs', 'Fix bugs']);
76
61
 
77
- const TaskList = Ol([
78
- ForEach(tasks, (task, indexObservable) =>
79
- Li([
80
- Strong(indexObservable.check(val => val + 1)),
81
- ' ',
82
- task,
83
- Button('Remove').nd.onClick(() =>
84
- tasks.remove(indexObservable.val())
85
- )
86
- ])
87
- )
88
- ]);
62
+ Ol(ForEach(tasks, (task, index) =>
63
+ Li([
64
+ Strong(index.transform(i => i + 1)), '. ',
65
+ task,
66
+ Button('Remove').nd.onClick(() => tasks.remove(index.val()))
67
+ ])
68
+ ))
89
69
  ```
90
70
 
91
- ### Custom Key Functions
71
+ ### Key function
92
72
 
93
- Use custom key functions
73
+ Use a key to help NativeDocument identify items for efficient reordering. Pass a property name string or a function:
94
74
 
95
75
  ```javascript
96
76
  const users = Observable.array([
97
77
  { id: 1, name: 'Alice', role: 'admin' },
98
- { id: 2, name: 'Bob', role: 'user' },
99
- { id: 3, name: 'Carol', role: 'editor' }
78
+ { id: 2, name: 'Bob', role: 'user' }
100
79
  ]);
101
80
 
102
- // Use 'id' field as the key for efficient updates
103
- const UserList = Div([
104
- ForEach(users,
105
- user => Div({ class: 'user-card' }, [
106
- H3(user.name),
107
- Span({ class: 'role' }, user.role)
108
- ]),
109
- 'id' // Key function - uses user.id
110
- // Or (item) => item.id
111
- )
112
- ]);
81
+ // Property name shorthand
82
+ ForEach(users, user => Div(user.name), 'id')
113
83
 
114
- // When users reorder, DOM nodes are moved, not recreated
115
- users.set((items) => items.sort((a, b) => a.name.localeCompare(b.name)));
84
+ // Function
85
+ ForEach(users, user => Div(user.name), item => item.id)
116
86
  ```
117
87
 
118
- ## ForEachArray - High-Performance Array Rendering
88
+ ### Options
119
89
 
120
- `ForEachArray` is specifically optimized for **arrays of complex objects** and provides superior performance. **Use ForEachArray for object arrays** - it's designed for array-specific operations.
90
+ ```javascript
91
+ ForEach(data, callback, key, { shouldKeepItemsInCache: true })
92
+ ```
121
93
 
122
- ### Why ForEachArray for Complex Arrays?
94
+ `shouldKeepItemsInCache` - when `true`, rendered items stay in cache even when removed. Useful when items toggle frequently:
123
95
 
124
- `ForEachArray` includes optimizations that generic `ForEach` not provide:
96
+ ```javascript
97
+ // Items are cached - re-adding won't re-render them
98
+ ForEach(items, renderItem, 'id', { shouldKeepItemsInCache: true })
99
+ ```
125
100
 
126
- - **Specialized diffing algorithm** optimized for array operations
127
- - **Batch DOM updates** for better performance
128
- - **Memory-efficient caching** with WeakMap references
129
- - **Array method detection** for targeted updates (push, splice, sort, etc.)
101
+ ---
130
102
 
131
- ### Basic Usage
103
+ ## `ForEachArray` - High-Performance Array Rendering
104
+
105
+ `ForEachArray` is specifically optimized for arrays of objects. Use it when performance matters.
106
+
107
+ ```javascript
108
+ ForEachArray(data, callback, configs?)
109
+ ```
132
110
 
133
111
  ```javascript
134
112
  const messages = Observable.array([
135
- { id: 1, text: 'Hello world!', timestamp: Date.now() },
113
+ { id: 1, text: 'Hello!', timestamp: Date.now() },
136
114
  { id: 2, text: 'How are you?', timestamp: Date.now() + 1000 }
137
115
  ]);
138
116
 
139
- const ChatMessages = Div({ class: 'chat-container' }, [
140
- ForEachArray(messages, message =>
117
+ Div({ class: 'chat' },
118
+ ForEachArray(messages, message =>
141
119
  Div({ class: 'message' }, [
142
- Div({ class: 'message-text' }, message.text),
143
- Div({ class: 'timestamp' }, new Date(message.timestamp).toLocaleTimeString())
120
+ Div(message.text),
121
+ Div(new Date(message.timestamp).toLocaleTimeString())
144
122
  ])
145
123
  )
146
- ]);
147
-
148
- // Optimized array operations
149
- messages.push({ id: 3, text: 'New message!', timestamp: Date.now() });
124
+ )
150
125
  ```
151
126
 
152
- ### Advanced Array Operations
127
+ ### Index in `ForEachArray`
153
128
 
154
- `ForEachArray` efficiently handles all array mutations:
129
+ The index is a **computed observable** derived from the array - always reactive, no need to call `.val()` to use it in the DOM:
155
130
 
156
131
  ```javascript
157
- const playlist = Observable.array([
158
- { id: 1, title: 'Song One', artist: 'Artist A' },
159
- { id: 2, title: 'Song Two', artist: 'Artist B' },
160
- { id: 3, title: 'Song Three', artist: 'Artist C' }
161
- ]);
162
-
163
- const PlaylistView = Div({ class: 'playlist' }, [
164
- ForEachArray(playlist, (song, indexObservable) => {
165
-
166
- return Div({ class: 'song-item', style: 'display: flex; align-items: center; column-gap: 10px;' }, [
167
- Div({ class: 'song-info' }, [
168
- Strong(indexObservable.get((value) => value + 1)),
169
- ' - ',
170
- Strong(song.title),
171
- Span({ class: 'artist' }, ` by ${song.artist}`)
172
- ]),
173
- Div({ class: 'song-controls' }, [
174
- Button('↑').nd.onClick(() => {
175
- const index = indexObservable.$value;
176
- if(index > 0) {
177
- playlist.swap(index, index-1);
178
- }
179
- }),
180
- Button('↓').nd.onClick(() => {
181
- const index = indexObservable.$value;
182
- if(index < playlist.length -1) {
183
- playlist.swap(index, index+1);
184
- }
185
- }),
186
- Button('Remove').nd.onClick(() =>{
187
- playlist.remove(indexObservable.$value);
188
- })
189
- ])
190
- ])
191
- }, 'id'),
192
- Br,
132
+ ForEachArray(playlist, (song, index) =>
193
133
  Div([
194
- Button('Push ').nd.onClick(() => {
195
- playlist.push({ id: 4, title: 'New Song', artist: 'New Artist' });
196
- }),
197
- Button('Unshift').nd.onClick(() => {
198
- playlist.unshift({ id: 0, title: 'First Song', artist: 'First' })
134
+ index.transform(i => i + 1), '. ',
135
+ song.title,
136
+ Button('Up').nd.onClick(() => {
137
+ const i = index.$value;
138
+ if (i > 0) {
139
+ playlist.swap(i, i - 1);
140
+ }
199
141
  }),
200
- Button('Reverse').nd.onClick(() => {
201
- playlist.reverse()
142
+ Button('Down').nd.onClick(() => {
143
+ const i = index.$value;
144
+ if (i < playlist.val().length - 1) {
145
+ playlist.swap(i, i + 1);
146
+ }
202
147
  }),
203
- Button('Sort').nd.onClick(() => {
204
- playlist.sort((a, b) => a.title.localeCompare(b.title))
205
- })
148
+ Button('Remove').nd.onClick(() => playlist.remove(index.$value))
206
149
  ])
207
- ]);
150
+ )
208
151
  ```
209
152
 
210
- ### Date and Time Filters
211
- ```javascript
212
- import {
213
- dateEquals, dateBefore, dateAfter, dateBetween,
214
- timeEquals, timeBefore, timeAfter, timeBetween,
215
- dateTimeEquals, dateTimeBefore, dateTimeAfter, dateTimeBetween
216
- } from 'native-document/utils/filters';
217
-
218
- const events = Observable.array([
219
- { name: 'Meeting', date: '2024-03-15' },
220
- { name: 'Conference', date: '2024-06-20' },
221
- { name: 'Workshop', date: '2024-09-10' }
222
- ]);
223
-
224
- // Date filters
225
- const today = Observable(new Date());
226
- const todayEvents = events.where({
227
- date: dateEquals(today)
228
- });
229
-
230
- const futureEvents = events.where({
231
- date: dateAfter(new Date())
232
- });
233
-
234
- const summerEvents = events.where({
235
- date: dateBetween('2024-06-01', '2024-08-31')
236
- });
237
-
238
- // Time filters (ignores date, only checks time)
239
- const morningEvents = events.where({
240
- date: timeBefore('2024-01-01 12:00:00')
241
- });
153
+ ### Configs
242
154
 
243
- const afternoonEvents = events.where({
244
- date: timeBetween(
245
- new Date('2024-01-01 13:00:00'),
246
- new Date('2024-01-01 17:00:00')
247
- )
248
- });
249
- ```
250
-
251
- ### Custom Filters
252
-
253
- Create custom filter logic:
254
155
  ```javascript
255
- import { custom } from 'native-document/utils/filters';
256
-
257
- const minRating = Observable(4);
258
-
259
- const highRatedProducts = products.where({
260
- _: custom((product, minRatingValue) => {
261
- return product.rating >= minRatingValue && product.reviews > 10;
262
- }, minRating) // Pass observables as dependencies
263
- });
264
-
265
- // Multiple observable dependencies
266
- const searchTerm = Observable('');
267
- const minPrice = Observable(0);
268
-
269
- const advancedFilter = products.where({
270
- _: custom((product, search, price) => {
271
- const matchesSearch = product.name.toLowerCase().includes(search.toLowerCase());
272
- const matchesPrice = product.price >= price;
273
- const hasDiscount = product.discount > 0;
274
-
275
- return matchesSearch && matchesPrice && hasDiscount;
276
- }, searchTerm, minPrice)
277
- });
278
- ```
279
-
280
- ## Observable Array Utility Methods
281
-
282
- ### swap() - Reorder Items
283
-
284
- Swap two items by their indices:
285
- ```javascript
286
- const items = Observable.array(['A', 'B', 'C', 'D']);
287
-
288
- // Swap items at index 0 and 2
289
- items.swap(0, 2); // Result: ['C', 'B', 'A', 'D']
290
-
291
- // Practical example: Move item up/down
292
- const moveUp = (index) => {
293
- if (index > 0) {
294
- items.swap(index, index - 1);
295
- }
296
- };
297
-
298
- const moveDown = (index) => {
299
- if (index < items.length - 1) {
300
- items.swap(index, index + 1);
301
- }
302
- };
303
- ```
304
-
305
- ### removeItem() - Remove by Value
306
-
307
- Remove an item by its value (not index):
308
- ```javascript
309
- const tags = Observable.array(['javascript', 'react', 'vue', 'angular']);
310
-
311
- // Remove by value
312
- tags.removeItem('react'); // Result: ['javascript', 'vue', 'angular']
313
-
314
- // Practical example: Remove tag
315
- const TagList = ForEachArray(tags, tag =>
316
- Span({ class: 'tag' }, [
317
- tag,
318
- Button('×').nd.onClick(() => tags.removeItem(tag))
319
- ]),
320
- (item) => item
321
- );
322
- ```
323
-
324
- ### isEmpty() - Check if Empty
325
-
326
- Check if array is empty:
327
- ```javascript
328
- const todos = Observable.array([]);
329
-
330
- // Check if empty
331
- console.log(todos.isEmpty()); // true
332
-
333
- todos.push({ text: 'New task' });
334
- console.log(todos.isEmpty()); // false
335
-
336
- // Practical example: Show empty state
337
- const TodoList = Div([
338
- ShowIf(todos.check(list => list.isEmpty()),
339
- Div({ class: 'empty-state' }, 'No todos yet!')
340
- ),
341
- ForEachArray(todos, renderTodo, 'id')
342
- ]);
343
- ```
344
-
345
- ### clear() - Remove All Items
346
-
347
- Clear all items from array:
348
- ```javascript
349
- const notifications = Observable.array([...]);
350
-
351
- // Clear all notifications
352
- notifications.clear();
353
-
354
- // Practical example: Clear all button
355
- Button('Clear All').nd.onClick(() => notifications.clear())
356
- ```
357
-
358
- ### at() - Access Item by Index
359
-
360
- Get item at specific index (supports negative indices):
361
- ```javascript
362
- const items = Observable.array(['A', 'B', 'C', 'D']);
363
-
364
- console.log(items.at(0)); // 'A'
365
- console.log(items.at(-1)); // 'D' (last item)
366
- console.log(items.at(-2)); // 'C' (second to last)
367
- ```
368
-
369
- ### count() - Conditional Count
370
-
371
- Count items that match a condition:
372
- ```javascript
373
- const tasks = Observable.array([
374
- { text: 'Task 1', done: true },
375
- { text: 'Task 2', done: false },
376
- { text: 'Task 3', done: true }
377
- ]);
378
-
379
- // Count completed tasks
380
- const completedCount = tasks.count(task => task.done); // 2
381
-
382
- // Practical example: Display count
383
- const Stats = Div([
384
- 'Completed: ',
385
- Observable.computed(() => tasks.count(t => t.done), [tasks]),
386
- ' / ',
387
- tasks.check(list => list.length)
388
- ]);
156
+ ForEachArray(data, callback, {
157
+ shouldKeepItemsInCache: false, // same as ForEach
158
+ pushDelay: (items) => items.length > 100 ? 50 : 0 // throttle large batch inserts
159
+ })
389
160
  ```
390
161
 
391
- ### merge() - vBatch Add Items
162
+ ---
392
163
 
393
- Add multiple items efficiently:
394
- ```javascript
395
- const items = Observable.array([1, 2, 3]);
164
+ ## Choosing Between `ForEach` and `ForEachArray`
396
165
 
397
- // Add multiple items at once
398
- items.merge([4, 5, 6]); // Result: [1, 2, 3, 4, 5, 6]
166
+ | | `ForEach` | `ForEachArray` |
167
+ |---|---|---|
168
+ | Arrays of objects | yes | yes (optimized) |
169
+ | Arrays of primitives | yes | yes |
170
+ | Object (non-array) iteration | yes | no |
171
+ | Index is observable | yes | yes (computed) |
172
+ | Key argument | third arg (string/fn) | inside configs |
173
+ | Best for | objects, primitives, small lists | large or frequently updated arrays |
399
174
 
400
- // More efficient than multiple push calls
401
- // items.push(4); items.push(5); items.push(6); // Less efficient
402
- ````
175
+ ---
403
176
 
404
- ## Advanced Filtering with where()
177
+ ## Filtering with `.where()`
405
178
 
406
- The `where()` method creates filtered observable arrays using powerful filter helpers that handle both static values and reactive observables.
179
+ `.where()` is available on `ObservableArray` only. It returns a new live `ObservableArray` that re-filters automatically. See [Observables](./observables.md) for the full `.where()` reference.
407
180
 
408
- ### Import Filter Helpers
409
181
  ```javascript
410
- // Direct import of specific helpers
411
- import { equals, greaterThan, between, includes, and, or, not } from 'native-document/utils/filters';
182
+ import { equals, greaterThan, lessThan, between, includes, match,
183
+ startsWith, endsWith, inArray, notIn, custom,
184
+ and, or, not } from 'native-document/filters';
412
185
 
413
- // Or import all filters
414
- import * as Filters from 'native-document/utils/filters';
415
- ```
416
-
417
- ### Basic Filtering with Helpers
418
- ```javascript
419
186
  const products = Observable.array([
420
- { id: 1, name: 'Phone', price: 599, inStock: true, category: 'electronics' },
187
+ { id: 1, name: 'Phone', price: 599, inStock: true, category: 'electronics' },
421
188
  { id: 2, name: 'Laptop', price: 999, inStock: false, category: 'electronics' },
422
- { id: 3, name: 'Tablet', price: 399, inStock: true, category: 'electronics' },
423
- { id: 4, name: 'Book', price: 29, inStock: true, category: 'books' }
189
+ { id: 3, name: 'Book', price: 29, inStock: true, category: 'books' }
424
190
  ]);
425
-
426
- // Filter by exact value
427
- const inStockProducts = products.where({
428
- inStock: equals(true)
429
- });
430
-
431
- // Filter by category
432
- const electronics = products.where({
433
- category: equals('electronics')
434
- });
435
191
  ```
436
192
 
437
- ### Reactive Filtering with Observables
193
+ ### Comparison
438
194
 
439
- Filter helpers work seamlessly with observables - filters update automatically when observables change:
440
195
  ```javascript
441
- const minPrice = Observable(0);
442
- const maxPrice = Observable(1000);
443
- const searchTerm = Observable('');
444
-
445
- // Reactive filters using observables
446
- const filteredProducts = products.where({
447
- price: between(minPrice, maxPrice), // Updates when minPrice or maxPrice change
448
- name: includes(searchTerm) // Updates when searchTerm changes
449
- });
450
-
451
- // UI controls
452
- const FiltersUI = Div([
453
- Input({
454
- type: 'number',
455
- placeholder: 'Min price',
456
- value: minPrice
457
- }),
458
- Input({
459
- type: 'number',
460
- placeholder: 'Max price',
461
- value: maxPrice
462
- }),
463
- Input({
464
- placeholder: 'Search products...',
465
- value: searchTerm
466
- })
467
- ]);
468
-
469
- // Product list updates automatically
470
- const ProductList = ForEachArray(filteredProducts, product =>
471
- ProductCard(product)
472
- );
196
+ products.where({ price: greaterThan(500) })
197
+ products.where({ price: lessThan(100) })
198
+ products.where({ price: between(200, 800) })
199
+ products.where({ inStock: equals(true) })
200
+ products.where({ name: notEquals('Phone') })
473
201
  ```
474
202
 
475
- ### Available Filter Helpers
203
+ ### String
476
204
 
477
- #### Comparison Filters
478
205
  ```javascript
479
- import { equals, notEquals, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual } from 'native-document/utils/filters';
480
-
481
- // Or use shortcuts
482
- import { eq, neq, gt, gte, lt, lte } from 'native-document/utils/filters';
483
-
484
- const expensiveProducts = products.where({
485
- price: gt(500) // price > 500
486
- });
487
-
488
- const affordableProducts = products.where({
489
- price: lte(100) // price <= 100
490
- });
491
-
492
- const notPhones = products.where({
493
- name: neq('Phone') // name !== 'Phone'
494
- });
495
- ```
496
-
497
- #### Range Filters
498
- ```javascript
499
- import { between } from 'native-document/utils/filters';
500
-
501
- const midRangeProducts = products.where({
502
- price: between(200, 800) // price >= 200 AND price <= 800
503
- });
504
-
505
- // With reactive observables
506
- const minPrice = Observable(100);
507
- const maxPrice = Observable(500);
508
-
509
- const rangeFiltered = products.where({
510
- price: between(minPrice, maxPrice) // Updates when either observable changes
511
- });
206
+ products.where({ name: includes('phone') }) // case-insensitive by default
207
+ products.where({ name: startsWith('P') })
208
+ products.where({ name: endsWith('book') })
209
+ products.where({ name: match(/^[A-Z]/) }) // regex
210
+ products.where({ name: match('lap', false) }) // plain string, no regex
512
211
  ```
513
212
 
514
- #### String Filters
515
- ```javascript
516
- import { includes, startsWith, endsWith, match } from 'native-document/utils/filters';
517
-
518
- // Contains (case-insensitive by default)
519
- const searchResults = products.where({
520
- name: includes('phone') // Matches 'Phone', 'PHONE', 'phone', 'Smartphone'
521
- });
522
-
523
- // Starts with
524
- const pProducts = products.where({
525
- name: startsWith('P') // Phone, Pencil, etc.
526
- });
527
-
528
- // Ends with
529
- const bookProducts = products.where({
530
- name: endsWith('book') // Textbook, Handbook, etc.
531
- });
532
-
533
- // Regex pattern matching
534
- const alphaNumeric = products.where({
535
- sku: match(/^[A-Z]{3}-\d{3}$/) // Matches 'ABC-123' pattern
536
- });
213
+ ### Array membership
537
214
 
538
- // Simple text search (no regex)
539
- const simpleSearch = products.where({
540
- name: match('lap', false) // false = not regex, just contains
541
- });
542
- ```
543
-
544
- #### Array Filters
545
215
  ```javascript
546
- import { inArray, notIn } from 'native-document/utils/filters';
216
+ const allowed = Observable.array(['electronics', 'books']);
547
217
 
548
- const allowedCategories = Observable.array(['electronics', 'books']);
549
-
550
- const filteredByCategory = products.where({
551
- category: inArray(allowedCategories) // category in ['electronics', 'books']
552
- });
553
-
554
- const excludedCategories = ['clothing', 'food'];
555
- const nonExcluded = products.where({
556
- category: notIn(excludedCategories)
557
- });
218
+ products.where({ category: inArray(allowed) }) // reactive
219
+ products.where({ category: notIn(['clothing']) })
558
220
  ```
559
221
 
560
- #### Empty/Existence Filters
561
- ```javascript
562
- import { isEmpty, isNotEmpty } from 'native-document/utils/filters';
222
+ ### Reactive filters
563
223
 
564
- const tasks = Observable.array([
565
- { title: 'Task 1', description: '' },
566
- { title: 'Task 2', description: 'Details here' }
567
- ]);
224
+ Pass an observable as the filter value - re-filters automatically when it changes:
568
225
 
569
- const tasksWithDescription = tasks.where({
570
- description: isNotEmpty() // Has a description
571
- });
572
-
573
- const tasksWithoutDescription = tasks.where({
574
- description: isEmpty() // Empty or null description
575
- });
576
- ```
577
-
578
- ### Combining Filters with Logic
579
226
  ```javascript
580
- import { and, or, not, equals, gt, lt, gte } from 'native-document/utils/filters';
581
-
582
- // AND: All conditions must be true
583
- const premiumElectronics = products.where({
584
- category: equals('electronics'),
585
- price: gt(500)
586
- });
587
-
588
- // OR: Any condition can be true
589
- const dealsOrPopular = products.where({
590
- price: or(
591
- lt(50), // Price < 50
592
- gte(1000) // OR rating >= 1000
593
- )
594
- });
595
-
596
- // NOT: Invert condition
597
- const notInStock = products.where({
598
- inStock: not(equals(true)) // NOT in stock
599
- });
227
+ const search = Observable('');
228
+ const minPrice = Observable(0);
229
+ const maxPrice = Observable(1000);
600
230
 
601
- // Complex combinations
602
- const searchQuery = Observable('');
603
- const selectedCategory = Observable('all');
604
-
605
- const complexFilter = products.where({
606
- category: and(
607
- or(
608
- equals('all'), // Show all categories
609
- equals(selectedCategory) // OR match selected category
610
- ),
611
- includes(searchQuery) // AND name includes search term
612
- )
231
+ const filtered = products.where({
232
+ name: includes(search),
233
+ price: between(minPrice, maxPrice)
613
234
  });
614
235
  ```
615
236
 
616
- ## Choosing Between ForEach and ForEachArray
617
-
618
- ### Use ForEachArray When:
619
-
620
- ✅ **Working with arrays of complex objects**
621
- ✅ **Performance is critical** - Large lists, frequent updates
622
- ✅ **Using array methods** - push, pop, splice, sort, reverse, etc.
623
-
624
- ```javascript
625
- // Perfect for ForEachArray
626
- const comments = Observable.array([...]);
627
- const CommentList = ForEachArray(comments, comment => CommentComponent(comment));
628
-
629
- // Array operations work optimally
630
- comments.push(newComment);
631
-
632
- comments.splice(index, 1);
633
-
634
- comments.sort((a, b) => b.timestamp - a.timestamp);
635
- ```
636
-
637
- ### Use ForEach When:
638
-
639
- ✅ **Working with objects** - ForEach is required for object iteration
640
- ✅ **Mixed data types** - When data might be array or object
641
- ✅ **Arrays of primitive values** - string, number, boolean
642
- ✅ **Simple use cases** - Small lists with infrequent updates
643
-
644
- ## Configuration Options
237
+ ### Custom filter
645
238
 
646
- ### ForEach Configuration
647
-
648
- `ForEach` accepts an optional configuration object:
649
239
  ```javascript
650
- ForEach(data, callback, key, { shouldKeepItemsInCache: false })
651
- ```
652
-
653
- **shouldKeepItemsInCache**: When `true`, keeps rendered items in cache even when removed from the list. Useful for frequently toggling items visibility.
654
- ```javascript
655
- const items = Observable.array(['A', 'B', 'C']);
656
-
657
- // Items stay in cache when removed
658
- const list = ForEach(items, item => Div(item), null, {
659
- shouldKeepItemsInCache: true
660
- });
661
-
662
- items.splice(1, 1); // Removes 'B' from DOM but keeps it cached
663
- items.push('B'); // Re-adds 'B' without re-rendering
664
- ```
665
-
666
- ### ForEachArray Configuration
240
+ const minRating = Observable(4);
667
241
 
668
- `ForEachArray` accepts a configuration object:
669
- ```javascript
670
- ForEachArray(data, callback, {
671
- shouldKeepItemsInCache: false
242
+ products.where({
243
+ _: custom((product, min) => {
244
+ return product.rating >= min && product.reviews > 10;
245
+ }, minRating) // observables passed as extra args
672
246
  })
673
247
  ```
674
248
 
675
- **shouldKeepItemsInCache**: Same as ForEach - keeps items in cache when removed.
676
-
677
- **pushDelay**: Function that returns delay in milliseconds for batch operations. Useful for large datasets.
678
- ```javascript
679
- const bigList = Observable.array([]);
680
-
681
- const list = ForEachArray(bigList, item => Div(item), {
682
- pushDelay: (items) => {
683
- // Add delay for large batches
684
- return items.length > 100 ? 50 : 0;
685
- }
686
- });
687
-
688
- // Adding 500 items will be throttled
689
- bigList.push(...Array(500).fill().map((_, i) => ({ id: i, text: `Item ${i}` })));
690
- ```
691
- ## Real-World Examples
692
-
693
- ### Nested Lists with Mixed Rendering
249
+ ### Combining
694
250
 
695
251
  ```javascript
696
- const categories = Observable.array([
697
- {
698
- id: 1,
699
- name: 'Electronics',
700
- items: Observable.array([
701
- { id: 101, name: 'Smartphone', price: 599 },
702
- { id: 102, name: 'Laptop', price: 999 }
703
- ])
704
- },
705
- {
706
- id: 2,
707
- name: 'Books',
708
- items: Observable.array([
709
- { id: 201, name: 'JavaScript Guide', price: 29.99 },
710
- { id: 202, name: 'Design Patterns', price: 39.99 }
711
- ])
712
- }
713
- ]);
714
-
715
- const CategorizedProducts = Div({ class: 'product-categories' }, [
716
- // Categories use ForEachArray (it's an array)
717
- ForEachArray(categories, category =>
718
- Div({ class: 'category' }, [
719
- H3({ class: 'category-title' }, category.name),
720
-
721
- // Items within each category also use ForEachArray
722
- ForEachArray(category.items,
723
- item => Div({ class: 'product-item' }, [
724
- Span({ class: 'product-name' }, item.name),
725
- Span({ class: 'product-price' }, `$${item.price}`),
726
- Button('Add to Cart').nd.onClick(() => addToCart(item))
727
- ]),
728
- ),
729
-
730
- Button('Add Item').nd.onClick(() => {
731
- const newItem = {
732
- id: Date.now(),
733
- name: `New ${category.name} Item`,
734
- price: Math.floor(Math.random() * 100) + 10
735
- };
736
- category.items.push(newItem);
737
- })
738
- ])
739
- )
740
- ]);
741
- ```
252
+ // and - field must pass ALL conditions
253
+ products.where({ price: and(greaterThan(100), lessThan(500)) })
742
254
 
743
- ## Performance Best Practices
255
+ // or - field must pass AT LEAST ONE
256
+ products.where({ category: or(equals('electronics'), equals('books')) })
744
257
 
745
- ### 1. Always Use Keys for Complex Objects
258
+ // not - invert
259
+ products.where({ inStock: not(equals(true)) })
746
260
 
747
- ```javascript
748
- // ✅ Good: Efficient updates and reordering
749
- ForEachArray(users, user => UserCard(user))
750
- ForEach(tags, tag => TagComponent(tag), 'index')
751
- ```
752
-
753
- ### 2. Choose the Right Function
754
-
755
- ```javascript
756
- // ✅ Perfect: ForEachArray for complex objects
757
- const users = Observable.array([{id: 1, name: 'Alice'}]);
758
- ForEachArray(users, renderUser, 'id')
759
-
760
- // ✅ Correct: ForEach for primitives
761
- const tags = Observable.array(['js', 'css']);
762
- ForEach(tags, renderTag)
763
-
764
- // ✅ Correct: ForEach for objects
765
- const config = Observable({theme: 'dark'});
766
- ForEach(config, renderSetting, (item, key) => key)
767
- ```
768
-
769
- ### 3. Use Computed Values for Derived Lists
770
-
771
- ```javascript
772
- // ✅ Efficient: Computed filtered list
773
- const searchTerm = Observable('');
774
- const filteredItems = Observable.computed(() =>
775
- allItems.val().filter(item =>
776
- item.name.toLowerCase().includes(searchTerm.val().toLowerCase())
777
- ),
778
- [allItems, searchTerm]
779
- );
780
-
781
- ForEachArray(filteredItems, renderItem);
782
-
783
- // ❌ Inefficient: Filtering in render
784
- ForEachArray(allItems, item => {
785
- if (item.name.includes(searchTerm.val())) {
786
- return renderItem(item);
787
- }
788
- return null;
261
+ // cross-field - use _ key with plain function
262
+ products.where({
263
+ _: item => item.inStock && item.price < 500
789
264
  })
790
265
  ```
791
266
 
792
- ## Memory Management
793
-
794
- Both `ForEach` and `ForEachArray` automatically manage memory:
267
+ ### Date and time filters
795
268
 
796
269
  ```javascript
797
- // Cleanup is automatic when observables are garbage collected
798
- let myList = Observable.array([1, 2, 3]);
799
- let listComponent = ForEachArray(myList, item => Div(item));
800
-
801
- // When references are lost, cleanup happens automatically
802
- myList = null;
803
- listComponent = null; // Memory will be freed
804
- ```
270
+ import { dateEquals, dateBefore, dateAfter, dateBetween,
271
+ timeBefore, timeBetween } from 'native-document/filters';
805
272
 
806
- For explicit cleanup:
807
-
808
- ```javascript
809
- const items = Observable.array([...]);
810
- const listComponent = ForEachArray(items, renderItem);
273
+ const events = Observable.array([
274
+ { name: 'Meeting', date: '2024-03-15' },
275
+ { name: 'Conference', date: '2024-06-20' }
276
+ ]);
811
277
 
812
- // Manual cleanup when needed
813
- items.cleanup();
278
+ events.where({ date: dateAfter(new Date()) })
279
+ events.where({ date: dateBetween('2024-06-01', '2024-08-31') })
280
+ events.where({ date: timeBetween(
281
+ new Date('2024-01-01 09:00:00'),
282
+ new Date('2024-01-01 17:00:00')
283
+ )})
814
284
  ```
815
285
 
816
- ## Common Pitfalls and Solutions
817
-
818
- ### 1. Missing Keys with Complex Objects
286
+ ---
819
287
 
820
- ```javascript
821
- // ❌ Problem: No key, inefficient updates
822
- ForEachArray(users, user => UserProfile(user))
823
-
824
- // ✅ Solution: Use unique key
825
- ForEachArray(users, user => UserProfile(user))
826
- ```
288
+ ## Common Patterns
827
289
 
828
- ### 2. Using ForEach for Complex Arrays
290
+ ### Empty state
829
291
 
830
292
  ```javascript
831
- // Suboptimal: Generic ForEach for arrays
832
- ForEach(genericObject, renderItem)
293
+ const items = Observable.array([]);
833
294
 
834
- // ✅ Optimal: Specialized ForEachArray for arrays
835
- ForEachArray(arrayData, renderItem)
295
+ Div([
296
+ ShowIf(items.isEmpty(), Div({ class: 'empty' }, 'No items yet')),
297
+ ForEachArray(items, item => ItemComponent(item))
298
+ ])
836
299
  ```
837
300
 
838
- ### 3. Modifying Arrays Directly
301
+ ### Search + filter
839
302
 
840
303
  ```javascript
841
- // Wrong: Direct mutation doesn't trigger updates
842
- items.val().push(newItem);
843
-
844
- // ✅ Correct: Use Observable array methods
845
- items.push(newItem);
846
-
847
- // ✅ Also correct: Set new array
848
- items.set([...items.val(), newItem]);
849
- ```
304
+ const search = Observable('');
305
+ const category = Observable('all');
850
306
 
851
- ## Integration with Other Features
852
-
853
- ### With Conditional Rendering
307
+ const filtered = products.where({
308
+ name: includes(search),
309
+ category: or(equals('all'), equals(category))
310
+ });
854
311
 
855
- ```javascript
856
- const items = Observable.array([]);
857
- const showEmptyState = items.check(arr => arr.length === 0);
858
-
859
- const ItemList = Div([
860
- ShowIf(showEmptyState,
861
- Div({ class: 'empty-state' }, 'No items found')
862
- ),
863
- HideIf(showEmptyState,
864
- ForEachArray(items, item => ItemComponent(item))
865
- )
866
- ]);
312
+ Div([
313
+ Input({ placeholder: 'Search...', value: search }),
314
+ ForEachArray(filtered, product => ProductCard(product))
315
+ ])
867
316
  ```
868
317
 
869
- ### With Forms and Validation
318
+ ### Drag-and-drop reordering
870
319
 
871
320
  ```javascript
872
- const formFields = Observable.array([
873
- { name: 'firstName', label: 'First Name', value: '', required: true },
874
- { name: 'lastName', label: 'Last Name', value: '', required: true },
875
- { name: 'email', label: 'Email', value: '', required: true }
876
- ]);
321
+ let draggedIndex = null;
877
322
 
878
- const DynamicForm = Form([
879
- ForEachArray(formFields, field =>
880
- Div({ class: 'form-group' }, [
881
- Label(field.label + (field.required ? ' *' : '')),
882
- Input({
883
- name: field.name,
884
- value: field.value,
885
- required: field.required
886
- }),
887
- ShowIf(field.error,
888
- Div({ class: 'error' }, field.error)
889
- )
890
- ])
891
- ),
892
- Button({ type: 'submit' }, 'Submit')
893
- ]);
323
+ ForEachArray(items, (item, index) =>
324
+ Div({ class: 'item', draggable: true }, item.text)
325
+ .nd
326
+ .onDragStart(() => { draggedIndex = index.$value; })
327
+ .onDragOver(e => e.preventDefault())
328
+ .onDrop(e => {
329
+ e.preventDefault();
330
+ const dropIndex = index.$value;
331
+ if (draggedIndex !== null && draggedIndex !== dropIndex) {
332
+ items.swap(draggedIndex, dropIndex);
333
+ }
334
+ draggedIndex = null;
335
+ })
336
+ )
894
337
  ```
895
338
 
896
- ## Advanced Patterns
897
-
898
- ### Infinite Scrolling
339
+ ### Infinite scroll
899
340
 
900
341
  ```javascript
901
- const items = Observable.array([]);
342
+ const items = Observable.array([]);
902
343
  const isLoading = Observable(false);
903
- const hasMore = Observable(true);
344
+ const hasMore = Observable(true);
904
345
 
905
- const loadMoreItems = async () => {
346
+ const loadMore = async () => {
906
347
  if (isLoading.val()) return;
907
-
908
348
  isLoading.set(true);
909
- try {
910
- const newItems = await fetchItems(items.val().length);
911
- if (newItems.length === 0) {
912
- hasMore.set(false);
913
- } else {
914
- items.push(...newItems);
915
- }
916
- } finally {
917
- isLoading.set(false);
918
- }
349
+ const next = await fetchItems(items.val().length);
350
+ next.length ? items.merge(next) : hasMore.set(false);
351
+ isLoading.set(false);
919
352
  };
920
353
 
921
- const InfiniteList = Div([
354
+ Div([
922
355
  ForEachArray(items, item => ItemComponent(item)),
923
- ShowIf(isLoading, LoadingSpinner()),
924
- ShowIf(hasMore.check(more => more && !isLoading.val()),
925
- Button('Load More').nd.onClick(loadMoreItems)
926
- )
927
- ]);
928
-
929
- ```
930
-
931
- ### Drag and Drop Reordering
932
-
933
- ```javascript
934
- const draggableItems = Observable.array([
935
- { id: 1, text: 'Item 1' },
936
- { id: 2, text: 'Item 2' },
937
- { id: 3, text: 'Item 3' }
938
- ]);
939
-
940
- let draggedIndex = null;
941
-
942
- const DraggableList = Div([
943
- ForEachArray(draggableItems, (item, indexObservable) =>
944
- Div({
945
- class: 'draggable-item',
946
- draggable: true
947
- }, item.text)
948
- .nd.onDragStart((e) => {
949
- draggedIndex = indexObservable.val();
950
- })
951
- .nd.onDragOver((e) => e.preventDefault())
952
- .nd.onDrop((e) => {
953
- e.preventDefault();
954
- const dropIndex = indexObservable.val();
955
- if (draggedIndex !== null && draggedIndex !== dropIndex) {
956
- const items = draggableItems.val();
957
- const draggedItem = items[draggedIndex];
958
-
959
- // Remove from old position
960
- items.splice(draggedIndex, 1);
961
- // Insert at new position
962
- items.splice(dropIndex, 0, draggedItem);
963
-
964
- draggableItems.set([...items]);
965
- }
966
- draggedIndex = null;
967
- }),
968
- 'id'
356
+ ShowIf(isLoading.isTruthy(), Div('Loading...')),
357
+ ShowIf(hasMore.isTruthy(),
358
+ Button('Load more').nd.onClick(loadMore)
969
359
  )
970
- ]);
360
+ ])
971
361
  ```
972
362
 
973
- ## Debugging List Rendering
974
-
975
- ### Logging Updates
363
+ ---
976
364
 
977
- ```javascript
978
- const items = Observable.array([]);
365
+ ## Best Practices
979
366
 
980
- // Log all array operations
981
- items.subscribe((newItems, oldItems, operations) => {
982
- console.log('Array operation:', operations);
983
- console.log('Old items:', oldItems);
984
- console.log('New items:', newItems);
985
- });
367
+ 1. Use `ForEachArray` for arrays of objects - it's optimized for that case
368
+ 2. Use `ForEach` for objects (non-array) and arrays of primitives
369
+ 3. Always pass a key when items can be reordered - avoids unnecessary re-rendering
370
+ 4. Use `.where()` for filtering instead of filtering inside the callback
371
+ 5. Use `shouldKeepItemsInCache: true` when items toggle in and out frequently
372
+ 6. Never mutate `.val()` directly - use observable array methods (`push`, `splice`, `swap`, etc.)
986
373
 
987
- const DebugList = ForEachArray(items, (item, index) => {
988
- console.log('Rendering item:', item, 'at index:', index?.val());
989
- return ItemComponent(item);
990
- });
991
- ```
374
+ ---
992
375
 
993
376
  ## Next Steps
994
377
 
995
- Now that you understand list rendering, explore these related topics:
996
-
997
- - **[Conditional Rendering](conditional-rendering.md)** - Show/hide content dynamically
998
- - **[Routing](routing.md)** - Navigation and URL management
999
- - **[State Management](state-management.md)** - Global state patterns
1000
- - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
1001
- - **[NDElement](native-document-element.md)** - Native Document Element
1002
- - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
1003
- - **[Advanced Components](advanced-components.md)** - Template caching and singleton views
1004
- - **[Args Validation](validation.md)** - Function Argument Validation
1005
- - **[State Management](state-management.md)** - Managing application state
1006
- - **[Memory Management](memory-management.md)** - Understanding cleanup and memory
378
+ - **[Conditional Rendering](./conditional-rendering.md)** - ShowIf, Match, Switch
379
+ - **[Observables](./observables.md)** - Observable arrays and `.where()` in depth
380
+ - **[Advanced Components](./advanced-components.md)** - `ForEachArray` with `useCache`
381
+ - **[Filters](./filters.md)** - Full filter reference
1007
382
 
1008
383
  ## Utilities
1009
384
 
1010
- - **[Cache](docs/utils/cache.md)** - Lazy initialization and singleton patterns
1011
- - **[NativeFetch](docs/utils/native-fetch.md)** - HTTP client with interceptors
1012
- - **[Filters](docs/utils/filters.md)** - Data filtering helpers
385
+ - **[Cache](./cache.md)** - Lazy initialization and singleton patterns
386
+ - **[NativeFetch](./native-fetch.md)** - HTTP client with interceptors
387
+ - **[Filters](./filters.md)** - Data filtering helpers