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.
- package/.vitepress/config.js +166 -0
- package/CHANGELOG.md +153 -0
- package/components.d.ts +2 -0
- package/components.js +2 -1
- package/devtools/widget.js +1 -1
- package/dist/native-document.components.min.js +11589 -2983
- package/dist/native-document.dev.js +2280 -396
- package/dist/native-document.dev.js.map +1 -1
- package/dist/native-document.min.js +1 -1
- package/docs/advanced-components.md +213 -608
- package/docs/anchor.md +173 -312
- package/docs/cache.md +95 -803
- package/docs/cli.md +179 -0
- package/docs/components/accordion.md +172 -0
- package/docs/components/alert.md +99 -0
- package/docs/components/avatar.md +160 -0
- package/docs/components/badge.md +102 -0
- package/docs/components/breadcrumb.md +89 -0
- package/docs/components/button.md +183 -0
- package/docs/components/card.md +69 -0
- package/docs/components/context-menu.md +118 -0
- package/docs/components/data-table.md +345 -0
- package/docs/components/dropdown.md +214 -0
- package/docs/components/form/autocomplete-field.md +81 -0
- package/docs/components/form/checkbox-field.md +41 -0
- package/docs/components/form/checkbox-group-field.md +54 -0
- package/docs/components/form/color-field.md +64 -0
- package/docs/components/form/date-field.md +92 -0
- package/docs/components/form/field-collection.md +63 -0
- package/docs/components/form/file-field.md +203 -0
- package/docs/components/form/form-control.md +87 -0
- package/docs/components/form/image-field.md +90 -0
- package/docs/components/form/index.md +115 -0
- package/docs/components/form/number-field.md +65 -0
- package/docs/components/form/radio-field.md +51 -0
- package/docs/components/form/select-field.md +123 -0
- package/docs/components/form/slider.md +136 -0
- package/docs/components/form/string-field.md +134 -0
- package/docs/components/form/textarea-field.md +65 -0
- package/docs/components/form-fields.md +372 -0
- package/docs/components/getting-started.md +264 -0
- package/docs/components/index.md +337 -0
- package/docs/components/layout.md +279 -0
- package/docs/components/list.md +73 -0
- package/docs/components/menu.md +215 -0
- package/docs/components/modal.md +156 -0
- package/docs/components/pagination.md +95 -0
- package/docs/components/popover.md +131 -0
- package/docs/components/progress.md +111 -0
- package/docs/components/shortcut-manager.md +221 -0
- package/docs/components/simple-table.md +107 -0
- package/docs/components/skeleton.md +155 -0
- package/docs/components/spinner.md +100 -0
- package/docs/components/splitter.md +133 -0
- package/docs/components/stepper.md +163 -0
- package/docs/components/switch.md +113 -0
- package/docs/components/tabs.md +153 -0
- package/docs/components/toast.md +119 -0
- package/docs/components/tooltip.md +151 -0
- package/docs/components/traits.md +261 -0
- package/docs/conditional-rendering.md +170 -588
- package/docs/contributing.md +300 -25
- package/docs/core-concepts.md +205 -374
- package/docs/elements.md +251 -367
- package/docs/extending-native-document-element.md +192 -207
- package/docs/filters.md +153 -1122
- package/docs/getting-started.md +193 -267
- package/docs/i18n.md +241 -0
- package/docs/index.md +76 -0
- package/docs/lifecycle-events.md +143 -75
- package/docs/list-rendering.md +227 -852
- package/docs/memory-management.md +134 -47
- package/docs/native-document-element.md +337 -186
- package/docs/native-fetch.md +99 -630
- package/docs/observable-resource.md +364 -0
- package/docs/observables.md +592 -526
- package/docs/routing.md +244 -653
- package/docs/state-management.md +134 -241
- package/docs/svg-elements.md +231 -0
- package/docs/theming.md +409 -0
- package/docs/validation.md +95 -97
- package/docs/vitepress-conventions.md +219 -0
- package/eslint.config.js +28 -33
- package/i18n.js +1 -1
- package/i18n.ts +2 -0
- package/index.js +3 -0
- package/package.json +36 -14
- package/readme.md +269 -89
- package/src/components/$traits/has-draggable/HasDraggable.d.ts +4 -0
- package/src/components/$traits/has-draggable/HasDraggable.js +13 -0
- package/src/components/$traits/has-items/HasItems.d.ts +9 -0
- package/src/components/$traits/has-items/HasItems.js +6 -6
- package/src/components/$traits/has-position/HasFullPosition.d.ts +14 -0
- package/src/components/$traits/has-position/HasFullPosition.js +44 -0
- package/src/components/$traits/has-position/HasPosition.d.ts +7 -0
- package/src/components/$traits/has-position/HasPosition.js +23 -1
- package/src/components/$traits/has-resizable/HasResizable.d.ts +13 -0
- package/src/components/$traits/has-resizable/HasResizable.js +9 -0
- package/src/components/$traits/has-validation/HasValidation.d.ts +17 -0
- package/src/components/$traits/has-validation/HasValidation.js +54 -7
- package/src/components/BaseComponent.d.ts +32 -0
- package/src/components/BaseComponent.js +65 -9
- package/src/components/accordion/Accordion.js +39 -14
- package/src/components/accordion/AccordionItem.js +45 -14
- package/src/components/accordion/index.js +2 -2
- package/src/components/accordion/types/Accordion.d.ts +47 -0
- package/src/components/accordion/types/AccordionItem.d.ts +48 -0
- package/src/components/alert/Alert.js +70 -38
- package/src/components/alert/index.js +2 -2
- package/src/components/alert/types/Alert.d.ts +62 -0
- package/src/components/avatar/Avatar.js +49 -12
- package/src/components/avatar/AvatarGroup.js +50 -2
- package/src/components/avatar/index.js +2 -2
- package/src/components/avatar/types/Avatar.d.ts +74 -0
- package/src/components/avatar/types/AvatarGroup.d.ts +32 -0
- package/src/components/badge/Badge.js +125 -5
- package/src/components/badge/index.js +2 -2
- package/src/components/badge/types/Badge.d.ts +51 -0
- package/src/components/breadcrumb/BreadCrumb.js +61 -5
- package/src/components/breadcrumb/index.js +2 -2
- package/src/components/breadcrumb/types/BreadCrumb.d.ts +42 -0
- package/src/components/button/Button.js +164 -9
- package/src/components/button/index.js +1 -1
- package/src/components/button/types/Button.d.ts +62 -0
- package/src/components/card/Card.js +204 -32
- package/src/components/card/index.js +4 -4
- package/src/components/card/types/Card.d.ts +42 -0
- package/src/components/context-menu/ContextMenu.js +49 -5
- package/src/components/context-menu/ContextMenuGroup.js +15 -2
- package/src/components/context-menu/ContextMenuItem.js +14 -2
- package/src/components/context-menu/index.js +5 -5
- package/src/components/context-menu/types/ContextMenu.d.ts +30 -0
- package/src/components/context-menu/types/ContextMenuGroup.d.ts +18 -0
- package/src/components/context-menu/types/ContextMenuItem.d.ts +18 -0
- package/src/components/divider/Divider.js +120 -4
- package/src/components/divider/index.js +3 -3
- package/src/components/divider/types/Divider.d.ts +55 -0
- package/src/components/dropdown/Dropdown.js +239 -16
- package/src/components/dropdown/DropdownDivider.js +22 -2
- package/src/components/dropdown/DropdownGroup.js +44 -5
- package/src/components/dropdown/DropdownItem.js +76 -3
- package/src/components/dropdown/DropdownTrigger.js +49 -20
- package/src/components/dropdown/helpers.js +1 -1
- package/src/components/dropdown/index.js +6 -6
- package/src/components/dropdown/types/Dropdown.d.ts +88 -0
- package/src/components/dropdown/types/DropdownDivider.d.ts +20 -0
- package/src/components/dropdown/types/DropdownGroup.d.ts +25 -0
- package/src/components/dropdown/types/DropdownItem.d.ts +41 -0
- package/src/components/dropdown/types/DropdownTrigger.d.ts +32 -0
- package/src/components/form/FormControl.js +156 -13
- package/src/components/form/field/Field.js +172 -9
- package/src/components/form/field/FieldCollection.js +116 -12
- package/src/components/form/field/types/AutocompleteField.js +92 -2
- package/src/components/form/field/types/CheckboxField.js +43 -2
- package/src/components/form/field/types/CheckboxGroupField.js +83 -6
- package/src/components/form/field/types/ColorField.js +56 -3
- package/src/components/form/field/types/DateField.js +155 -4
- package/src/components/form/field/types/EmailField.js +54 -4
- package/src/components/form/field/types/FileField.js +140 -6
- package/src/components/form/field/types/HiddenField.js +27 -1
- package/src/components/form/field/types/ImageField.js +82 -3
- package/src/components/form/field/types/NumberField.js +97 -4
- package/src/components/form/field/types/PasswordField.js +103 -7
- package/src/components/form/field/types/RadioField.js +75 -4
- package/src/components/form/field/types/RangeField.js +67 -1
- package/src/components/form/field/types/SearchField.js +41 -2
- package/src/components/form/field/types/SelectField.js +133 -4
- package/src/components/form/field/types/StringField.js +91 -2
- package/src/components/form/field/types/TelField.js +55 -4
- package/src/components/form/field/types/TextAreaField.js +76 -2
- package/src/components/form/field/types/TimeField.js +120 -5
- package/src/components/form/field/types/UrlField.js +59 -4
- package/src/components/form/field/types/file-field-mode/FileAvatarMode.js +83 -4
- package/src/components/form/field/types/file-field-mode/FileDropzoneMode.js +61 -3
- package/src/components/form/field/types/file-field-mode/FileItemPreview.js +79 -3
- package/src/components/form/field/types/file-field-mode/FileNativeMode.js +24 -2
- package/src/components/form/field/types/file-field-mode/FileUploadButtonMode.js +64 -3
- package/src/components/form/field/types/file-field-mode/FileWallMode.js +56 -3
- package/src/components/form/index.js +28 -28
- package/src/components/form/types/Field.d.ts +73 -0
- package/src/components/form/types/FieldCollection.d.ts +53 -0
- package/src/components/form/types/FormControl.d.ts +64 -0
- package/src/components/form/types/fields/AutocompleteField.d.ts +48 -0
- package/src/components/form/types/fields/CheckboxField.d.ts +33 -0
- package/src/components/form/types/fields/CheckboxGroupField.d.ts +49 -0
- package/src/components/form/types/fields/ColorField.d.ts +37 -0
- package/src/components/form/types/fields/DateField.d.ts +70 -0
- package/src/components/form/types/fields/EmailField.d.ts +35 -0
- package/src/components/form/types/fields/FileAvatarMode.d.ts +46 -0
- package/src/components/form/types/fields/FileDropzoneMode.d.ts +28 -0
- package/src/components/form/types/fields/FileField.d.ts +56 -0
- package/src/components/form/types/fields/FileItemPreview.d.ts +35 -0
- package/src/components/form/types/fields/FileNativeMode.d.ts +21 -0
- package/src/components/form/types/fields/FileUploadButtonMode.d.ts +34 -0
- package/src/components/form/types/fields/FileWallMode.d.ts +32 -0
- package/src/components/form/types/fields/HiddenField.d.ts +26 -0
- package/src/components/form/types/fields/ImageField.d.ts +45 -0
- package/src/components/form/types/fields/NumberField.d.ts +48 -0
- package/src/components/form/types/fields/PasswordField.d.ts +46 -0
- package/src/components/form/types/fields/RadioField.d.ts +48 -0
- package/src/components/form/types/fields/RangeField.d.ts +44 -0
- package/src/components/form/types/fields/SearchField.d.ts +34 -0
- package/src/components/form/types/fields/SelectField.d.ts +71 -0
- package/src/components/form/types/fields/StringField.d.ts +48 -0
- package/src/components/form/types/fields/TelField.d.ts +37 -0
- package/src/components/form/types/fields/TextAreaField.d.ts +44 -0
- package/src/components/form/types/fields/TimeField.d.ts +51 -0
- package/src/components/form/types/fields/UrlField.d.ts +35 -0
- package/src/components/form/validation/Validation.js +54 -54
- package/src/components/index.d.ts +160 -0
- package/src/components/list/HasListItem.js +171 -0
- package/src/components/list/List.js +85 -67
- package/src/components/list/ListDivider.js +39 -0
- package/src/components/list/ListGroup.js +105 -38
- package/src/components/list/ListItem.js +158 -49
- package/src/components/list/index.js +8 -6
- package/src/components/list/types/List.d.ts +43 -0
- package/src/components/list/types/ListGroup.d.ts +37 -0
- package/src/components/list/types/ListItem.d.ts +53 -0
- package/src/components/menu/HasMenuItem.js +55 -6
- package/src/components/menu/Menu.js +113 -22
- package/src/components/menu/MenuDivider.js +18 -2
- package/src/components/menu/MenuGroup.js +61 -6
- package/src/components/menu/MenuItem.js +95 -11
- package/src/components/menu/MenuLink.js +27 -2
- package/src/components/menu/index.js +6 -6
- package/src/components/menu/types/Menu.d.ts +60 -0
- package/src/components/menu/types/MenuDivider.d.ts +19 -0
- package/src/components/menu/types/MenuGroup.d.ts +44 -0
- package/src/components/menu/types/MenuItem.d.ts +46 -0
- package/src/components/menu/types/MenuLink.d.ts +16 -0
- package/src/components/modal/Modal.js +258 -17
- package/src/components/modal/index.js +3 -3
- package/src/components/modal/types/Modal.d.ts +94 -0
- package/src/components/pagination/Pagination.js +155 -7
- package/src/components/pagination/index.js +3 -3
- package/src/components/pagination/types/Pagination.d.ts +68 -0
- package/src/components/popover/Popover.js +198 -11
- package/src/components/popover/PopoverFooter.js +33 -9
- package/src/components/popover/PopoverHeader.js +33 -8
- package/src/components/popover/index.js +4 -4
- package/src/components/popover/types/Popover.d.ts +83 -0
- package/src/components/popover/types/PopoverFooter.d.ts +24 -0
- package/src/components/popover/types/PopoverHeader.d.ts +26 -0
- package/src/components/progress/Progress.js +182 -13
- package/src/components/progress/index.js +3 -3
- package/src/components/progress/types/Progress.d.ts +77 -0
- package/src/components/skeleton/Skeleton.js +117 -49
- package/src/components/skeleton/index.js +3 -3
- package/src/components/skeleton/types/Skeleton.d.ts +55 -0
- package/src/components/slider/Slider.js +207 -10
- package/src/components/slider/index.js +2 -2
- package/src/components/slider/types/Slider.d.ts +82 -0
- package/src/components/spacer/Spacer.js +12 -3
- package/src/components/spacer/index.js +2 -2
- package/src/components/spacer/types/Spacer.d.ts +19 -0
- package/src/components/spinner/Spinner.js +180 -9
- package/src/components/spinner/index.js +3 -3
- package/src/components/spinner/types/Spinner.d.ts +71 -0
- package/src/components/splitter/Splitter.js +76 -13
- package/src/components/splitter/SplitterGutter.js +67 -5
- package/src/components/splitter/SplitterPanel.js +69 -2
- package/src/components/splitter/index.js +5 -5
- package/src/components/splitter/types/Splitter.d.ts +38 -0
- package/src/components/splitter/types/SplitterGutter.d.ts +38 -0
- package/src/components/splitter/types/SplitterPanel.d.ts +41 -0
- package/src/components/stacks/AbsoluteStack.js +23 -3
- package/src/components/stacks/FixedStack.js +23 -3
- package/src/components/stacks/HStack.js +24 -3
- package/src/components/stacks/PositionStack.js +111 -3
- package/src/components/stacks/RelativeStack.js +23 -3
- package/src/components/stacks/Stack.js +73 -2
- package/src/components/stacks/VStack.js +24 -4
- package/src/components/stacks/index.js +7 -7
- package/src/components/stacks/types/AbsoluteStack.d.ts +16 -0
- package/src/components/stacks/types/FixedStack.d.ts +16 -0
- package/src/components/stacks/types/HStack.d.ts +16 -0
- package/src/components/stacks/types/PositionStack.d.ts +54 -0
- package/src/components/stacks/types/RelativeStack.d.ts +17 -0
- package/src/components/stacks/types/Stack.d.ts +39 -0
- package/src/components/stacks/types/VStack.d.ts +16 -0
- package/src/components/stepper/Stepper.js +152 -12
- package/src/components/stepper/StepperStep.js +104 -3
- package/src/components/stepper/index.js +4 -4
- package/src/components/stepper/types/Stepper.d.ts +68 -0
- package/src/components/stepper/types/StepperStep.d.ts +54 -0
- package/src/components/switch/Switch.js +143 -6
- package/src/components/switch/index.js +1 -1
- package/src/components/switch/types/Switch.d.ts +55 -0
- package/src/components/table/Column.js +105 -6
- package/src/components/table/ColumnGroup.js +48 -3
- package/src/components/table/DataTable.js +256 -19
- package/src/components/table/SimpleTable.js +58 -4
- package/src/components/table/index.js +2 -2
- package/src/components/table/types/Column.d.ts +49 -0
- package/src/components/table/types/ColumnGroup.d.ts +28 -0
- package/src/components/table/types/DataTable.d.ts +97 -0
- package/src/components/table/types/SimpleTable.d.ts +40 -0
- package/src/components/tabs/Tabs.js +192 -5
- package/src/components/tabs/index.js +3 -3
- package/src/components/tabs/types/Tabs.d.ts +78 -0
- package/src/components/toast/Toast.js +133 -5
- package/src/components/toast/index.js +3 -3
- package/src/components/toast/types/Toast.d.ts +57 -0
- package/src/components/toast/types/ToastError.d.ts +7 -0
- package/src/components/toast/types/ToastInfo.d.ts +7 -0
- package/src/components/toast/types/ToastSuccess.d.ts +7 -0
- package/src/components/toast/types/ToastWarning.d.ts +7 -0
- package/src/components/tooltip/Tooltip.js +157 -13
- package/src/components/tooltip/index.js +2 -2
- package/src/components/tooltip/prototypes.js +1 -1
- package/src/components/tooltip/types/Tooltip.d.ts +65 -0
- package/src/core/data/MemoryManager.js +2 -2
- package/src/core/data/Observable.js +15 -18
- package/src/core/data/ObservableArray.js +118 -46
- package/src/core/data/ObservableChecker.js +2 -2
- package/src/core/data/ObservableItem.js +135 -21
- package/src/core/data/ObservableObject.js +126 -35
- package/src/core/data/ObservableResource.js +118 -3
- package/src/core/data/Store.js +142 -26
- package/src/core/data/observable-helpers/observable.is-to.js +196 -1
- package/src/core/data/observable-helpers/observable.prototypes.js +35 -8
- package/src/core/elements/anchor/anchor-with-sentinel.js +23 -2
- package/src/core/elements/anchor/anchor.js +16 -7
- package/src/core/elements/anchor/one-child-anchor-overwriting.js +2 -2
- package/src/core/elements/content-formatter.js +1 -1
- package/src/core/elements/control/for-each-array.js +9 -9
- package/src/core/elements/control/for-each.js +14 -14
- package/src/core/elements/control/show-if.js +11 -11
- package/src/core/elements/control/show-when.js +5 -5
- package/src/core/elements/control/switch.js +14 -14
- package/src/core/elements/description-list.js +1 -1
- package/src/core/elements/form.js +2 -2
- package/src/core/elements/fragment.js +1 -1
- package/src/core/elements/html5-semantics.js +1 -1
- package/src/core/elements/img.js +3 -3
- package/src/core/elements/interactive.js +1 -1
- package/src/core/elements/list.js +1 -1
- package/src/core/elements/medias.js +1 -1
- package/src/core/elements/meta-data.js +1 -1
- package/src/core/elements/svg.js +1 -1
- package/src/core/elements/table.js +1 -1
- package/src/core/errors/ArgTypesError.js +1 -1
- package/src/core/utils/HasEventEmitter.js +36 -2
- package/src/core/utils/args-types.js +9 -9
- package/src/core/utils/cache.js +1 -1
- package/src/core/utils/callback-handler.js +29 -0
- package/src/core/utils/debug-manager.js +6 -6
- package/src/core/utils/events.js +139 -139
- package/src/core/utils/filters/date.js +84 -3
- package/src/core/utils/filters/standard.js +136 -11
- package/src/core/utils/filters/strings.js +34 -2
- package/src/core/utils/filters/utils.js +40 -4
- package/src/core/utils/formatters.js +4 -4
- package/src/core/utils/helpers.js +39 -7
- package/src/core/utils/localstorage.js +11 -11
- package/src/core/utils/memoize.js +56 -3
- package/src/core/utils/plugins-manager.js +3 -3
- package/src/core/utils/property-accumulator.js +6 -6
- package/src/core/utils/prototypes.js +26 -1
- package/src/core/utils/shortcut-manager.js +2 -2
- package/src/core/utils/validator.js +8 -8
- package/src/core/wrappers/AttributesWrapper.js +32 -22
- package/src/core/wrappers/DocumentObserver.js +3 -3
- package/src/core/wrappers/ElementCreator.js +5 -5
- package/src/core/wrappers/HtmlElementWrapper.js +38 -12
- package/src/core/wrappers/NDElement.js +328 -22
- package/src/core/wrappers/NdPrototype.js +60 -16
- package/src/core/wrappers/SingletonView.js +50 -2
- package/src/core/wrappers/SvgElementWrapper.js +1 -1
- package/src/core/wrappers/constants.js +35 -2
- package/src/core/wrappers/prototypes/attributes-extensions.js +7 -7
- package/src/core/wrappers/prototypes/nd-element-extensions.js +72 -6
- package/src/core/wrappers/prototypes/nd-element.transition.extensions.js +42 -2
- package/src/core/wrappers/template-cloner/NodeCloner.js +53 -8
- package/src/core/wrappers/template-cloner/TemplateCloner.js +75 -6
- package/src/core/wrappers/template-cloner/attributes-hydrator.js +58 -2
- package/src/core/wrappers/template-cloner/utils.js +42 -6
- package/src/fetch/NativeFetch.js +3 -3
- package/src/i18n/bin/scan.js +6 -6
- package/src/i18n/index.d.ts +2 -0
- package/src/i18n/service/I18nService.d.ts +27 -0
- package/src/i18n/service/I18nService.js +5 -5
- package/src/i18n/service/functions.d.ts +22 -0
- package/src/i18n/service/functions.js +2 -2
- package/src/router/Route.js +3 -3
- package/src/router/RouteGroupHelper.js +2 -2
- package/src/router/Router.js +15 -15
- package/src/router/RouterComponent.js +33 -7
- package/src/router/link.js +4 -4
- package/src/router/modes/HashRouter.js +2 -2
- package/src/router/modes/HistoryRouter.js +2 -2
- package/src/router/modes/MemoryRouter.js +1 -1
- package/src/ui/components/accordion/AccordionItemRender.js +3 -3
- package/src/ui/components/accordion/AccordionRender.js +1 -1
- package/src/ui/components/alert/AlertRender.js +10 -10
- package/src/ui/components/avatar/avata-group/AvatarGroupRender.js +1 -1
- package/src/ui/components/avatar/avatar/AvatarRender.js +1 -1
- package/src/ui/components/breadcrumb/BreadcrumbRender.js +2 -2
- package/src/ui/components/button/ButtonRender.js +1 -1
- package/src/ui/components/card/CardRender.js +133 -0
- package/src/ui/components/card/card.css +169 -0
- package/src/ui/components/contextmenu/ContextmenuRender.js +6 -6
- package/src/ui/components/dropdown/DropdownRender.js +8 -8
- package/src/ui/components/dropdown/group/DropdownGroupRender.js +2 -2
- package/src/ui/components/dropdown/item/DropdownItemRender.js +1 -1
- package/src/ui/components/form/FieldCollectionRender.js +2 -2
- package/src/ui/components/form/FormControlRender.js +5 -5
- package/src/ui/components/form/fields/AutocompleteFieldRender.js +3 -3
- package/src/ui/components/form/fields/CheckboxFieldRender.js +1 -1
- package/src/ui/components/form/fields/CheckboxGroupFieldRender.js +1 -1
- package/src/ui/components/form/fields/DateFieldRender.js +7 -7
- package/src/ui/components/form/fields/EmailFieldRender.js +1 -1
- package/src/ui/components/form/fields/FieldRender.js +4 -4
- package/src/ui/components/form/fields/FileFieldRender.js +1 -1
- package/src/ui/components/form/fields/PasswordFieldRender.js +2 -2
- package/src/ui/components/form/fields/RadioFieldRender.js +1 -1
- package/src/ui/components/form/fields/RangeFieldRender.js +1 -1
- package/src/ui/components/form/fields/SelectFieldRender.js +2 -2
- package/src/ui/components/form/fields/SliderFieldRender.js +6 -6
- package/src/ui/components/form/fields/StringFieldRender.js +1 -1
- package/src/ui/components/form/fields/TelFieldRender.js +1 -1
- package/src/ui/components/form/fields/TextAreaFieldRender.js +1 -1
- package/src/ui/components/form/fields/TimeFieldRender.js +3 -3
- package/src/ui/components/form/fields/UrlFieldRender.js +1 -1
- package/src/ui/components/form/file-upload-mode/FileAvatarModeRender.js +1 -1
- package/src/ui/components/form/file-upload-mode/FileDropzoneModeRender.js +2 -2
- package/src/ui/components/form/file-upload-mode/FileUploadButtonModeRender.js +2 -2
- package/src/ui/components/form/file-upload-mode/FileWallModeRender.js +1 -1
- package/src/ui/components/form/helpers.js +8 -8
- package/src/ui/components/form/index.js +27 -27
- package/src/ui/components/list/ListRender.js +18 -0
- package/src/ui/components/list/divider/ListDividerRender.js +10 -0
- package/src/ui/components/list/divider/list-divider.css +12 -0
- package/src/ui/components/list/group/ListGroupRender.js +61 -0
- package/src/ui/components/list/group/list-group.css +62 -0
- package/src/ui/components/list/item/ListItemRender.js +238 -0
- package/src/ui/components/list/item/list-item.css +191 -0
- package/src/ui/components/list/list.css +24 -0
- package/src/ui/components/menu/MenuDividerRender.js +1 -1
- package/src/ui/components/menu/MenuGroupRender.js +3 -3
- package/src/ui/components/menu/MenuItemRender.js +2 -2
- package/src/ui/components/menu/MenuLinkRender.js +3 -3
- package/src/ui/components/menu/helpers.js +4 -4
- package/src/ui/components/modal/ModalRender.js +4 -4
- package/src/ui/components/pagination/PaginationRender.js +9 -9
- package/src/ui/components/popover/PopoverRender.js +7 -7
- package/src/ui/components/progress/ProgressRender.js +12 -12
- package/src/ui/components/skeleton/SkeletonRender.js +56 -0
- package/src/ui/components/spacer/SpacerRender.js +10 -0
- package/src/ui/components/splitter/SplitterGutterRender.js +1 -1
- package/src/ui/components/splitter/SplitterPanelRender.js +2 -2
- package/src/ui/components/stacks/PositionStackRender.js +1 -1
- package/src/ui/components/stacks/StackRender.js +1 -1
- package/src/ui/components/stacks/absolute-stack/AbsoluteStackRender.js +1 -1
- package/src/ui/components/stacks/fixed-stack/FixedStackRender.js +1 -1
- package/src/ui/components/stacks/h-stack/HStackRender.js +1 -1
- package/src/ui/components/stacks/index.js +5 -5
- package/src/ui/components/stacks/relative-stack/RelativeStackRender.js +1 -1
- package/src/ui/components/stacks/v-stack/VStackRender.js +1 -1
- package/src/ui/components/stepper/StepperRender.js +2 -2
- package/src/ui/components/stepper/StepperStepRender.js +4 -4
- package/src/ui/components/switch/SwitchRender.js +4 -4
- package/src/ui/components/table/data-table/DataTableRender.js +5 -5
- package/src/ui/components/table/data-table/bulk-actions.js +7 -7
- package/src/ui/components/table/data-table/pagination.js +6 -6
- package/src/ui/components/table/data-table/tables.js +25 -25
- package/src/ui/components/table/data-table/toolbar.js +3 -3
- package/src/ui/components/table/simple-table/SimpleTableRender.js +8 -8
- package/src/ui/components/tabs/TabsRender.js +11 -11
- package/src/ui/components/toast/ToastRender.js +3 -3
- package/src/ui/components/tooltip/TooltipRender.js +1 -1
- package/src/ui/index.js +44 -36
- package/types/elements.d.ts +163 -1037
- package/types/forms.d.ts +16 -20
- package/types/globals.d.ts +543 -0
- package/types/images.d.ts +2 -2
- package/types/observable-resource.d.ts +3 -0
- package/types/property-accumulator.d.ts +4 -4
- package/types/store.d.ts +26 -2
- package/types/validator.ts +3 -3
- package/ui.js +1 -0
- package/src/components/form/field/DefaultRender.js +0 -77
- package/src/components/form/field/FieldFactory.js +0 -107
- package/src/components/skeleton/SkeletonList.js +0 -0
- package/src/components/skeleton/SkeletonParagraph.js +0 -0
- package/src/components/skeleton/SkeletonTable.js +0 -0
- /package/{src/components/skeleton/SkeletonCard.js → docs/tutorials/.gitkeep} +0 -0
|
@@ -1,722 +1,304 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Conditional Rendering
|
|
3
|
+
description: Dynamically show, hide, or switch between content based on reactive state - ShowIf, Switch, Match, When, and more
|
|
4
|
+
---
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
# Conditional Rendering
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
NativeDocument's conditional rendering utilities automatically update the DOM when observable values change. All functions work with observables and manage DOM updates for you.
|
|
8
9
|
|
|
9
10
|
```javascript
|
|
10
|
-
import { ShowIf,
|
|
11
|
-
|
|
12
|
-
const isVisible = Observable(false);
|
|
13
|
-
|
|
14
|
-
// Content automatically appears/disappears based on isVisible
|
|
15
|
-
const conditionalContent = ShowIf(isVisible, 'This content toggles!');
|
|
11
|
+
import { ShowIf, HideIf, ShowWhen, Switch, Match, When } from 'native-document/elements';
|
|
16
12
|
```
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
---
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
## `ShowIf` - Basic Conditional Display
|
|
17
|
+
|
|
18
|
+
Renders content only when the condition is truthy. When false, the content is removed from the DOM.
|
|
21
19
|
|
|
22
20
|
```javascript
|
|
23
|
-
const
|
|
21
|
+
const isVisible = Observable(false);
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Button('Logout').nd.onClick(() =>
|
|
30
|
-
user.set({ ...user.val(), isLoggedIn: false })
|
|
31
|
-
),
|
|
32
|
-
|
|
33
|
-
// Shows only when user is logged in
|
|
34
|
-
ShowIf(user.check(u => u.isLoggedIn), 'Welcome back!')
|
|
35
|
-
]);
|
|
23
|
+
ShowIf(isVisible, Div('Hello!'))
|
|
24
|
+
|
|
25
|
+
// With a boolean (non-reactive)
|
|
26
|
+
ShowIf(true, Div('Always visible'))
|
|
36
27
|
```
|
|
37
28
|
|
|
38
|
-
###
|
|
29
|
+
### Function children
|
|
39
30
|
|
|
40
|
-
|
|
31
|
+
Pass a function to create content that reflects current observable values:
|
|
41
32
|
|
|
42
33
|
```javascript
|
|
43
34
|
const notifications = Observable.array([]);
|
|
44
35
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
// When notifications change, the badge updates automatically
|
|
51
|
-
notifications.push('New message');
|
|
36
|
+
ShowIf(notifications.isNotEmpty(),
|
|
37
|
+
() => Span({ class: 'badge' }, notifications.toLength())
|
|
38
|
+
)
|
|
52
39
|
```
|
|
53
40
|
|
|
54
|
-
###
|
|
55
|
-
|
|
56
|
-
Observable checkers create derived conditions for cleaner code:
|
|
41
|
+
### Options
|
|
57
42
|
|
|
58
43
|
```javascript
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const WeatherApp = Div([
|
|
64
|
-
Div(['Current temperature: ', temperature, '°C']),
|
|
65
|
-
ShowIf(isCold, Div({ class: 'cold' }, '🧥 It\'s cold! Wear a jacket.')),
|
|
66
|
-
ShowIf(isHot, Div({ class: 'hot' }, '☀️ It\'s hot! Stay hydrated.')),
|
|
67
|
-
Div([
|
|
68
|
-
Button('-').nd.onClick(() => --temperature.$value),
|
|
69
|
-
Button('+').nd.onClick(() => ++temperature.$value),
|
|
70
|
-
])
|
|
71
|
-
]);
|
|
44
|
+
ShowIf(condition, content, {
|
|
45
|
+
comment: 'my-component', // label visible in DOM comments (debug)
|
|
46
|
+
shouldKeepInCache: false // default true - set to false to recreate on each show
|
|
47
|
+
})
|
|
72
48
|
```
|
|
73
49
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
These functions provide convenient inverses to `ShowIf`:
|
|
50
|
+
### Practical example
|
|
77
51
|
|
|
78
52
|
```javascript
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
HideIfNot(isLoading, Div({ class: 'spinner' }, 'Loading...')),
|
|
53
|
+
const user = Observable({ name: 'Alice', isLoggedIn: false });
|
|
54
|
+
|
|
55
|
+
Div([
|
|
56
|
+
Button('Login').nd.onClick(() =>
|
|
57
|
+
user.set({ ...user.val(), isLoggedIn: true })
|
|
58
|
+
),
|
|
59
|
+
ShowIf(user.is(u => u.isLoggedIn),
|
|
60
|
+
() => Div(['Welcome back, ', user.select(u => u.name), '!'])
|
|
61
|
+
)
|
|
89
62
|
]);
|
|
90
63
|
```
|
|
91
64
|
|
|
92
|
-
|
|
93
|
-
```javascript
|
|
94
|
-
const condition = Observable(true);
|
|
95
|
-
|
|
96
|
-
// These are equivalent:
|
|
97
|
-
ShowIf(condition, content)
|
|
98
|
-
HideIfNot(condition, content)
|
|
99
|
-
|
|
100
|
-
// These are equivalent:
|
|
101
|
-
HideIf(condition, content)
|
|
102
|
-
ShowIf(condition.check(val => !val), content)
|
|
103
|
-
```
|
|
65
|
+
---
|
|
104
66
|
|
|
105
|
-
##
|
|
67
|
+
## `HideIf` / `HideIfNot` - Inverse Conditions
|
|
106
68
|
|
|
107
|
-
|
|
69
|
+
Convenient inverses of `ShowIf`:
|
|
108
70
|
|
|
109
|
-
### Basic Usage
|
|
110
71
|
```javascript
|
|
111
|
-
const
|
|
72
|
+
const isLoading = Observable(true);
|
|
112
73
|
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
Div({ class: 'spinner' }, 'Loading...')
|
|
116
|
-
);
|
|
74
|
+
// Hide content while loading
|
|
75
|
+
HideIf(isLoading, Div('Data is ready'))
|
|
117
76
|
|
|
118
|
-
// Show content
|
|
119
|
-
|
|
120
|
-
Div({ class: 'success' }, '✅ Success!')
|
|
121
|
-
);
|
|
77
|
+
// Show content only while loading (equivalent to ShowIf)
|
|
78
|
+
HideIfNot(isLoading, Div('Loading...'))
|
|
122
79
|
```
|
|
123
80
|
|
|
124
|
-
|
|
81
|
+
Equivalences:
|
|
125
82
|
|
|
126
|
-
**Option 1: Three arguments (Observable, value, content)**
|
|
127
83
|
```javascript
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
ShowWhen(theme, 'dark',
|
|
131
|
-
Div({ class: 'dark-mode-indicator' }, '🌙 Dark Mode')
|
|
132
|
-
);
|
|
84
|
+
ShowIf(condition, content) // same as HideIfNot(condition, content)
|
|
85
|
+
HideIf(condition, content) // same as ShowIf(condition.isFalsy(), content)
|
|
133
86
|
```
|
|
134
87
|
|
|
135
|
-
|
|
136
|
-
```javascript
|
|
137
|
-
const theme = Observable('light');
|
|
138
|
-
const isDark = theme.when('dark'); // Returns ObservableWhen
|
|
88
|
+
---
|
|
139
89
|
|
|
140
|
-
ShowWhen
|
|
141
|
-
Div({ class: 'dark-mode-indicator' }, '🌙 Dark Mode')
|
|
142
|
-
);
|
|
143
|
-
```
|
|
90
|
+
## `ShowWhen` - Value Matching
|
|
144
91
|
|
|
145
|
-
|
|
146
|
-
```javascript
|
|
147
|
-
const connectionStatus = Observable('disconnected');
|
|
92
|
+
Shows content when an observable matches a specific value.
|
|
148
93
|
|
|
149
|
-
|
|
150
|
-
ShowWhen(connectionStatus, 'connecting',
|
|
151
|
-
Span({ class: 'status connecting' }, '🔄 Connecting...')
|
|
152
|
-
),
|
|
153
|
-
ShowWhen(connectionStatus, 'connected',
|
|
154
|
-
Span({ class: 'status connected' }, '✅ Connected')
|
|
155
|
-
),
|
|
156
|
-
ShowWhen(connectionStatus, 'disconnected',
|
|
157
|
-
Span({ class: 'status disconnected' }, '❌ Disconnected')
|
|
158
|
-
),
|
|
159
|
-
ShowWhen(connectionStatus, 'error',
|
|
160
|
-
Span({ class: 'status error' }, '⚠️ Connection Error')
|
|
161
|
-
)
|
|
162
|
-
]);
|
|
163
|
-
|
|
164
|
-
// Update status
|
|
165
|
-
setTimeout(() => connectionStatus.set('connecting'), 1000);
|
|
166
|
-
setTimeout(() => connectionStatus.set('connected'), 3000);
|
|
167
|
-
```
|
|
94
|
+
**Two-argument form** - pass an `ObservableWhen` result from `.when()`:
|
|
168
95
|
|
|
169
|
-
Both can handle multiple states, but they serve different purposes:
|
|
170
96
|
```javascript
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
// ShowWhen: Multiple independent conditions
|
|
174
|
-
Div([
|
|
175
|
-
ShowWhen(phase, 'loading', LoadingSpinner()),
|
|
176
|
-
ShowWhen(phase, 'success', SuccessMessage()),
|
|
177
|
-
ShowWhen(phase, 'error', ErrorMessage())
|
|
178
|
-
]);
|
|
97
|
+
const theme = Observable('light');
|
|
98
|
+
const isDark = theme.when('dark'); // ObservableWhen result
|
|
179
99
|
|
|
180
|
-
|
|
181
|
-
Match(phase, {
|
|
182
|
-
loading: LoadingSpinner(),
|
|
183
|
-
success: SuccessMessage(),
|
|
184
|
-
error: ErrorMessage()
|
|
185
|
-
});
|
|
100
|
+
ShowWhen(isDark, Div('Dark mode active'))
|
|
186
101
|
```
|
|
187
102
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
`Switch` efficiently toggles between exactly two pieces of content based on a boolean condition:
|
|
103
|
+
**Three-argument form** - pass the observable, the target value, and the content:
|
|
191
104
|
|
|
192
105
|
```javascript
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const ThemeToggle = Div([
|
|
196
|
-
Button('Toggle Theme').nd.onClick(() => isDarkMode.set(!isDarkMode.val())),
|
|
197
|
-
|
|
198
|
-
Switch(isDarkMode,
|
|
199
|
-
Div({ class: 'dark-indicator' }, '🌙 Dark Mode'), // when true
|
|
200
|
-
Div({ class: 'light-indicator' }, '☀️ Light Mode') // when false
|
|
201
|
-
)
|
|
202
|
-
]);
|
|
106
|
+
ShowWhen(theme, 'dark', Div('Dark mode active'))
|
|
203
107
|
```
|
|
204
108
|
|
|
205
|
-
###
|
|
206
|
-
|
|
207
|
-
Functions allow for reactive content that updates with current values:
|
|
109
|
+
### Practical example
|
|
208
110
|
|
|
209
111
|
```javascript
|
|
210
|
-
const
|
|
112
|
+
const status = Observable('disconnected');
|
|
211
113
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
(
|
|
215
|
-
(
|
|
216
|
-
)
|
|
114
|
+
Div({ class: 'status-bar' }, [
|
|
115
|
+
ShowWhen(status, 'connecting', Span('Connecting...')),
|
|
116
|
+
ShowWhen(status, 'connected', Span('Connected')),
|
|
117
|
+
ShowWhen(status, 'disconnected', Span('Disconnected')),
|
|
118
|
+
ShowWhen(status, 'error', Span('Connection Error'))
|
|
119
|
+
]);
|
|
217
120
|
```
|
|
218
121
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
`Match` provides switch-case like functionality for handling multiple states:
|
|
222
|
-
|
|
223
|
-
```javascript
|
|
224
|
-
const requestStatus = Observable('idle');
|
|
225
|
-
|
|
226
|
-
const StatusDisplay = Match(requestStatus, {
|
|
227
|
-
idle: Div({ class: 'status-idle' }, 'Ready to make request'),
|
|
228
|
-
loading: Div({ class: 'status-loading' }, [
|
|
229
|
-
Span({ class: 'spinner' }),
|
|
230
|
-
' Loading...'
|
|
231
|
-
]),
|
|
232
|
-
success: Div({ class: 'status-success' }, '✅ Request completed'),
|
|
233
|
-
error: Div({ class: 'status-error' }, '❌ Request failed'),
|
|
234
|
-
timeout: Div({ class: 'status-timeout' }, '⏰ Request timed out')
|
|
235
|
-
});
|
|
236
|
-
```
|
|
122
|
+
---
|
|
237
123
|
|
|
238
|
-
|
|
124
|
+
## `Switch` - Binary Content
|
|
239
125
|
|
|
240
|
-
|
|
126
|
+
Toggles between exactly two pieces of content based on a boolean observable:
|
|
241
127
|
|
|
242
128
|
```javascript
|
|
243
|
-
const
|
|
244
|
-
phase: 'menu',
|
|
245
|
-
score: 0,
|
|
246
|
-
level: 1
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
const GameDisplay = Match(gameState.check(state => state.phase), {
|
|
250
|
-
menu: () => Div({ class: 'game-menu' }, [
|
|
251
|
-
H1('Welcome to the Game'),
|
|
252
|
-
Button('Start Game').nd.onClick(() =>
|
|
253
|
-
gameState.set({ ...gameState.val(), phase: 'playing' })
|
|
254
|
-
)
|
|
255
|
-
]),
|
|
256
|
-
|
|
257
|
-
playing: () => Div({ class: 'game-ui' }, [
|
|
258
|
-
Div(['Score: ', gameState.check(s => s.score)]),
|
|
259
|
-
Div(['Level: ', gameState.check(s => s.level)]),
|
|
260
|
-
Button('Pause').nd.onClick(() =>
|
|
261
|
-
gameState.set({ ...gameState.val(), phase: 'paused' })
|
|
262
|
-
),
|
|
263
|
-
Button('Game Over').nd.onClick(() =>
|
|
264
|
-
gameState.set({ ...gameState.val(), phase: 'gameOver' })
|
|
265
|
-
)
|
|
266
|
-
]),
|
|
267
|
-
|
|
268
|
-
paused: () => Div({ class: 'game-paused' }, [
|
|
269
|
-
H2('Game Paused'),
|
|
270
|
-
Button('Resume').nd.onClick(() =>
|
|
271
|
-
gameState.set({ ...gameState.val(), phase: 'playing' })
|
|
272
|
-
)
|
|
273
|
-
]),
|
|
274
|
-
|
|
275
|
-
gameOver: () => Div({ class: 'game-over' }, [
|
|
276
|
-
H2('Game Over'),
|
|
277
|
-
Div(['Final Score: ', gameState.check(s => s.score)]),
|
|
278
|
-
Button('Play Again').nd.onClick(() =>
|
|
279
|
-
gameState.set({ phase: 'menu', score: 0, level: 1 })
|
|
280
|
-
)
|
|
281
|
-
])
|
|
282
|
-
});
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
### Match with Default Cases
|
|
286
|
-
|
|
287
|
-
Handle unexpected values with default cases:
|
|
129
|
+
const isDarkMode = Observable(false);
|
|
288
130
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
admin: AdminMenu(),
|
|
294
|
-
moderator: ModeratorMenu(),
|
|
295
|
-
user: UserMenu(),
|
|
296
|
-
// Default case for unknown roles
|
|
297
|
-
default: GuestMenu()
|
|
298
|
-
});
|
|
131
|
+
Switch(isDarkMode,
|
|
132
|
+
Div('Dark mode'), // when true
|
|
133
|
+
Div('Light mode') // when false
|
|
134
|
+
)
|
|
299
135
|
```
|
|
300
136
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
`When` provides a chainable interface for conditional rendering:
|
|
137
|
+
With function children:
|
|
304
138
|
|
|
305
139
|
```javascript
|
|
306
|
-
const
|
|
140
|
+
const user = Observable({ name: 'Guest', isLoggedIn: false });
|
|
307
141
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
142
|
+
Switch(user.is(u => u.isLoggedIn),
|
|
143
|
+
() => Div(['Welcome back, ', user.select(u => u.name)]),
|
|
144
|
+
() => Div('Please sign in')
|
|
145
|
+
)
|
|
311
146
|
```
|
|
312
147
|
|
|
313
|
-
|
|
148
|
+
> `Switch` is built on top of `Match` using `.toBoolean()`.
|
|
314
149
|
|
|
315
|
-
|
|
316
|
-
const user = Observable({
|
|
317
|
-
age: 25,
|
|
318
|
-
hasLicense: true,
|
|
319
|
-
hasInsurance: false
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
const DrivingEligibility = When(user.check(u =>
|
|
323
|
-
u.age >= 18 && u.hasLicense && u.hasInsurance
|
|
324
|
-
))
|
|
325
|
-
.show(() => Div({ class: 'eligible' }, [
|
|
326
|
-
'✅ You can drive legally',
|
|
327
|
-
Div(`Age: ${user.val().age}, License: Yes, Insurance: Yes`)
|
|
328
|
-
]))
|
|
329
|
-
.otherwise(() => {
|
|
330
|
-
const u = user.val();
|
|
331
|
-
const issues = [];
|
|
332
|
-
if (u.age < 18) issues.push('Must be 18 or older');
|
|
333
|
-
if (!u.hasLicense) issues.push('Need a valid license');
|
|
334
|
-
if (!u.hasInsurance) issues.push('Need insurance coverage');
|
|
335
|
-
|
|
336
|
-
return Div({ class: 'not-eligible' }, [
|
|
337
|
-
'❌ Cannot drive legally',
|
|
338
|
-
Div(['Issues: ', issues.join(', ')])
|
|
339
|
-
]);
|
|
340
|
-
});
|
|
341
|
-
```
|
|
150
|
+
---
|
|
342
151
|
|
|
343
|
-
##
|
|
152
|
+
## `Match` - Multiple States
|
|
344
153
|
|
|
345
|
-
|
|
154
|
+
Handles multiple states like a switch-case. Each key maps to a content value or function:
|
|
346
155
|
|
|
347
156
|
```javascript
|
|
348
|
-
const
|
|
349
|
-
email: '',
|
|
350
|
-
password: '',
|
|
351
|
-
confirmPassword: ''
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
const isValidEmail = formData.email.check(e =>
|
|
355
|
-
e.includes('@') && e.includes('.') && e.length > 5
|
|
356
|
-
);
|
|
357
|
-
|
|
358
|
-
const isValidPassword = formData.password.check(p => p.length >= 8);
|
|
359
|
-
|
|
360
|
-
const passwordsMatch = Observable.computed(() => {
|
|
361
|
-
const data = formData.$value;
|
|
362
|
-
return data.password === data.confirmPassword && data.password.length > 0;
|
|
363
|
-
}, [formData.password, formData.confirmPassword]);
|
|
364
|
-
|
|
365
|
-
const canSubmit = Observable.computed(() =>
|
|
366
|
-
isValidEmail.val() && isValidPassword.val() && passwordsMatch.val(),
|
|
367
|
-
[isValidEmail, isValidPassword, passwordsMatch]
|
|
368
|
-
);
|
|
157
|
+
const status = Observable('idle');
|
|
369
158
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
ShowIf(formData.email.check(e => e.length > 0 && !isValidEmail.val()),
|
|
378
|
-
Div({ class: 'error' }, 'Please enter a valid email address')
|
|
379
|
-
),
|
|
380
|
-
|
|
381
|
-
// Password field
|
|
382
|
-
Input({
|
|
383
|
-
type: 'password',
|
|
384
|
-
placeholder: 'Password',
|
|
385
|
-
value: formData.password
|
|
386
|
-
}),
|
|
387
|
-
ShowIf(formData.password.check(p => p.length > 0 && p.length < 8),
|
|
388
|
-
Div({ class: 'error' }, 'Password must be at least 8 characters')
|
|
389
|
-
),
|
|
390
|
-
|
|
391
|
-
// Confirm password (only show after password is valid)
|
|
392
|
-
ShowIf(isValidPassword, [
|
|
393
|
-
Input({
|
|
394
|
-
type: 'password',
|
|
395
|
-
placeholder: 'Confirm Password',
|
|
396
|
-
value: formData.confirmPassword
|
|
397
|
-
}),
|
|
398
|
-
ShowIf(formData.confirmPassword.check(p => p.length > 0 && !passwordsMatch.val()),
|
|
399
|
-
Div({ class: 'error' }, 'Passwords do not match')
|
|
400
|
-
)
|
|
401
|
-
]),
|
|
402
|
-
|
|
403
|
-
// Submit button
|
|
404
|
-
Switch(canSubmit,
|
|
405
|
-
Button('Create Account').nd.onClick(() => {
|
|
406
|
-
console.log('Creating account...', formData.$value);
|
|
407
|
-
}),
|
|
408
|
-
Button({ disabled: true, class: 'disabled' }, 'Create Account')
|
|
409
|
-
)
|
|
410
|
-
]);
|
|
159
|
+
Match(status, {
|
|
160
|
+
idle: Div('Ready'),
|
|
161
|
+
loading: Div('Loading...'),
|
|
162
|
+
success: Div('Done!'),
|
|
163
|
+
error: Div('Something went wrong'),
|
|
164
|
+
default: Div('Unknown state')
|
|
165
|
+
})
|
|
411
166
|
```
|
|
412
167
|
|
|
413
|
-
|
|
168
|
+
With function values for access to current observable state:
|
|
414
169
|
|
|
415
170
|
```javascript
|
|
416
|
-
const
|
|
417
|
-
user: null,
|
|
418
|
-
currentView: 'welcome',
|
|
419
|
-
settings: { theme: 'light', notifications: true }
|
|
420
|
-
});
|
|
171
|
+
const phase = Observable('menu');
|
|
421
172
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
// Header - changes based on auth state
|
|
427
|
-
Header({ class: 'app-header' }, [
|
|
428
|
-
H1('My App'),
|
|
429
|
-
Switch(isLoggedIn,
|
|
430
|
-
// Authenticated header
|
|
431
|
-
() => Div({ class: 'user-menu' }, [
|
|
432
|
-
Span(['Welcome, ', appState.user.val().name]),
|
|
433
|
-
Button('Settings').nd.onClick(() =>
|
|
434
|
-
appState.currentView.set('settings')
|
|
435
|
-
),
|
|
436
|
-
Button('Logout').nd.onClick(() => {
|
|
437
|
-
appState.user.set(null);
|
|
438
|
-
appState.currentView.set('welcome');
|
|
439
|
-
})
|
|
440
|
-
]),
|
|
441
|
-
// Guest header
|
|
442
|
-
Div({ class: 'auth-buttons' }, [
|
|
443
|
-
Button('Sign In').nd.onClick(() =>
|
|
444
|
-
appState.currentView.set('login')
|
|
445
|
-
),
|
|
446
|
-
Button('Sign Up').nd.onClick(() =>
|
|
447
|
-
appState.currentView.set('register')
|
|
448
|
-
)
|
|
449
|
-
])
|
|
450
|
-
)
|
|
173
|
+
Match(phase, {
|
|
174
|
+
menu: () => Div([
|
|
175
|
+
H1('Welcome'),
|
|
176
|
+
Button('Start').nd.onClick(() => phase.set('playing'))
|
|
451
177
|
]),
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
appState.currentView.set('welcome');
|
|
462
|
-
return Div('Redirecting...');
|
|
463
|
-
}),
|
|
464
|
-
settings: When(isLoggedIn)
|
|
465
|
-
.show(() => SettingsView(appState))
|
|
466
|
-
.otherwise(() => {
|
|
467
|
-
appState.currentView.set('welcome');
|
|
468
|
-
return Div('Please log in to access settings');
|
|
469
|
-
})
|
|
470
|
-
})
|
|
471
|
-
]);
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
## Performance Considerations
|
|
475
|
-
|
|
476
|
-
### Efficient Content Updates
|
|
477
|
-
|
|
478
|
-
NativeDocument optimizes conditional rendering by only updating the DOM when conditions actually change:
|
|
479
|
-
|
|
480
|
-
```javascript
|
|
481
|
-
const isVisible = Observable(true);
|
|
482
|
-
|
|
483
|
-
// This content is created once and reused
|
|
484
|
-
const expensiveContent = ShowIf(isVisible, () => {
|
|
485
|
-
console.log('Creating expensive content'); // Only called when becoming visible
|
|
486
|
-
return createComplexComponent();
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
// Toggling rapidly won't recreate content unnecessarily
|
|
490
|
-
isVisible.set(false);
|
|
491
|
-
isVisible.set(true); // Content is recreated
|
|
492
|
-
isVisible.set(true); // No recreation, already visible
|
|
178
|
+
playing: () => Div([
|
|
179
|
+
Div(['Score: ', score]),
|
|
180
|
+
Button('Pause').nd.onClick(() => phase.set('paused'))
|
|
181
|
+
]),
|
|
182
|
+
paused: () => Div([
|
|
183
|
+
H2('Paused'),
|
|
184
|
+
Button('Resume').nd.onClick(() => phase.set('playing'))
|
|
185
|
+
])
|
|
186
|
+
})
|
|
493
187
|
```
|
|
494
188
|
|
|
495
|
-
###
|
|
189
|
+
### Dynamic add / remove
|
|
496
190
|
|
|
497
|
-
|
|
191
|
+
`Match` exposes `.add()` and `.remove()` methods to update the available states at runtime:
|
|
498
192
|
|
|
499
193
|
```javascript
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const TabContent = Match(currentTab, {
|
|
504
|
-
home: () => {
|
|
505
|
-
console.log('Creating home tab');
|
|
506
|
-
return HomeTabComponent(); // Only created when needed
|
|
507
|
-
},
|
|
508
|
-
profile: () => {
|
|
509
|
-
console.log('Creating profile tab');
|
|
510
|
-
return ProfileTabComponent(); // Only created when needed
|
|
511
|
-
},
|
|
512
|
-
settings: () => {
|
|
513
|
-
console.log('Creating settings tab');
|
|
514
|
-
return SettingsTabComponent(); // Only created when needed
|
|
515
|
-
}
|
|
194
|
+
const view = Match(status, {
|
|
195
|
+
idle: Div('Ready'),
|
|
196
|
+
loading: Div('Loading...')
|
|
516
197
|
});
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
### Avoiding Unnecessary Computations
|
|
520
|
-
|
|
521
|
-
Use Observable.computed for expensive condition calculations:
|
|
522
198
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
// Computed once, reused in multiple places
|
|
528
|
-
const filteredItems = Observable.computed(() => {
|
|
529
|
-
return items.val().filter(item =>
|
|
530
|
-
item.name.toLowerCase().includes(searchTerm.val().toLowerCase())
|
|
531
|
-
);
|
|
532
|
-
}, [items, searchTerm]);
|
|
533
|
-
|
|
534
|
-
const hasResults = filteredItems.check(results => results.length > 0);
|
|
199
|
+
// Add a new state
|
|
200
|
+
view.nd.add('success', Div('Done!'));
|
|
201
|
+
view.nd.add('success', Div('Done!'), true); // third arg: immediately switch to this state
|
|
535
202
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
ForEach(filteredItems, item => ItemComponent(item))
|
|
539
|
-
),
|
|
540
|
-
HideIf(hasResults, 'No results found')
|
|
541
|
-
]);
|
|
203
|
+
// Remove a state
|
|
204
|
+
view.nd.remove('idle');
|
|
542
205
|
```
|
|
543
206
|
|
|
544
|
-
|
|
207
|
+
### `shouldKeepInCache`
|
|
545
208
|
|
|
546
|
-
|
|
209
|
+
By default, `Match` caches each rendered state. Pass `false` to recreate content on every switch:
|
|
547
210
|
|
|
548
211
|
```javascript
|
|
549
|
-
|
|
550
|
-
ShowIf(isVisible, content)
|
|
551
|
-
|
|
552
|
-
// Good: Use Switch for binary choices
|
|
553
|
-
Switch(isLoggedIn, welcomeMessage, loginPrompt)
|
|
554
|
-
|
|
555
|
-
// Good: Use Match for multiple states
|
|
556
|
-
Match(status, { loading: '...', success: '...', error: '...' })
|
|
557
|
-
|
|
558
|
-
// Less ideal: Using Match for simple boolean
|
|
559
|
-
Match(isVisible, { true: content, false: null })
|
|
212
|
+
Match(status, { loading: Div('...'), success: Div('Done') }, false)
|
|
560
213
|
```
|
|
561
214
|
|
|
562
|
-
|
|
215
|
+
---
|
|
563
216
|
|
|
564
|
-
|
|
565
|
-
// Good: Function creates fresh content with current values
|
|
566
|
-
ShowIf(user.check(u => u.isAdmin),
|
|
567
|
-
() => Div(`Admin: ${user.val().name}`)
|
|
568
|
-
)
|
|
217
|
+
## `When` - Fluent Builder
|
|
569
218
|
|
|
570
|
-
|
|
571
|
-
ShowIf(user.check(u => u.isAdmin),
|
|
572
|
-
Div(`Admin: ${user.val().name}`) // Name won't update if user changes
|
|
573
|
-
)
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
### 3. Combine Conditions Logically
|
|
219
|
+
A chainable interface for conditional rendering:
|
|
577
220
|
|
|
578
221
|
```javascript
|
|
579
|
-
|
|
580
|
-
const canEdit = Observable.computed(() => {
|
|
581
|
-
const u = user.val();
|
|
582
|
-
const p = post.val();
|
|
583
|
-
return u.isLoggedIn && (u.role === 'admin' || u.id === p.authorId);
|
|
584
|
-
}, [user, post]);
|
|
585
|
-
|
|
586
|
-
ShowIf(canEdit, editButton)
|
|
222
|
+
const score = Observable(85);
|
|
587
223
|
|
|
588
|
-
|
|
589
|
-
|
|
224
|
+
When(score.isGreaterThanOrEqualTo(90))
|
|
225
|
+
.show(() => Div('Excellent!'))
|
|
226
|
+
.otherwise(() => Div('Keep going'))
|
|
590
227
|
```
|
|
591
228
|
|
|
592
|
-
|
|
229
|
+
Convert to a DOM element explicitly with `.toNdElement()`:
|
|
593
230
|
|
|
594
231
|
```javascript
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
() =>
|
|
598
|
-
)
|
|
232
|
+
const greeting = When(isLoggedIn)
|
|
233
|
+
.show(() => Div('Welcome back!'))
|
|
234
|
+
.otherwise(() => Div('Please sign in'));
|
|
599
235
|
|
|
600
|
-
//
|
|
601
|
-
|
|
236
|
+
// .otherwise() already returns the element
|
|
237
|
+
// .toNdElement() is available if you build the chain without calling .otherwise()
|
|
238
|
+
const el = greeting.toNdElement();
|
|
602
239
|
```
|
|
603
240
|
|
|
604
|
-
|
|
241
|
+
---
|
|
605
242
|
|
|
606
|
-
|
|
607
|
-
// Good: Clear intent
|
|
608
|
-
const isUserAuthenticated = user.check(u => u.token !== null);
|
|
609
|
-
const hasUnreadMessages = messages.check(m => m.some(msg => !msg.read));
|
|
243
|
+
## Choosing the Right Tool
|
|
610
244
|
|
|
611
|
-
|
|
245
|
+
| Situation | Use |
|
|
246
|
+
|---|---|
|
|
247
|
+
| Simple show / hide | `ShowIf` |
|
|
248
|
+
| Inverse show / hide | `HideIf` / `HideIfNot` |
|
|
249
|
+
| Show when value matches | `ShowWhen` |
|
|
250
|
+
| Two options (true / false) | `Switch` |
|
|
251
|
+
| Multiple named states | `Match` |
|
|
252
|
+
| Fluent chaining style | `When` |
|
|
612
253
|
|
|
613
|
-
|
|
614
|
-
const check1 = user.check(u => u.token !== null);
|
|
615
|
-
const check2 = messages.check(m => m.some(msg => !msg.read));
|
|
616
|
-
```
|
|
254
|
+
---
|
|
617
255
|
|
|
618
|
-
##
|
|
256
|
+
## Best Practices
|
|
619
257
|
|
|
620
|
-
|
|
258
|
+
**Use functions for dynamic content** - static values are evaluated once at creation time:
|
|
621
259
|
|
|
622
260
|
```javascript
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
data: null,
|
|
626
|
-
error: null
|
|
627
|
-
});
|
|
261
|
+
// Good - content reflects current value when shown
|
|
262
|
+
ShowIf(isAdmin, () => Div(user.select(u => u.name)))
|
|
628
263
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
loading: Div({ class: 'loading' }, 'Loading...'),
|
|
632
|
-
success: () => DataDisplay(requestState.data.val()),
|
|
633
|
-
error: () => Div({ class: 'error' }, [
|
|
634
|
-
'Error: ', requestState.error,
|
|
635
|
-
Button('Retry').nd.onClick(loadData)
|
|
636
|
-
])
|
|
637
|
-
});
|
|
264
|
+
// Risky - name captured at creation, won't update
|
|
265
|
+
ShowIf(isAdmin, Div(user.val().name))
|
|
638
266
|
```
|
|
639
267
|
|
|
640
|
-
|
|
268
|
+
**Use `Observable.computed()` for complex conditions:**
|
|
641
269
|
|
|
642
270
|
```javascript
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
});
|
|
271
|
+
const canEdit = Observable.computed((u, p) =>
|
|
272
|
+
u.isLoggedIn && (u.role === 'admin' || u.id === p.authorId),
|
|
273
|
+
[user, post]
|
|
274
|
+
);
|
|
648
275
|
|
|
649
|
-
|
|
650
|
-
// Theme switching
|
|
651
|
-
Switch(features.darkMode,
|
|
652
|
-
Div({ class: 'app dark-theme' }, content),
|
|
653
|
-
Div({ class: 'app light-theme' }, content)
|
|
654
|
-
),
|
|
655
|
-
|
|
656
|
-
// Beta features for power users
|
|
657
|
-
ShowIf(features.betaFeatures, BetaPanel()),
|
|
658
|
-
|
|
659
|
-
// A/B testing new dashboard
|
|
660
|
-
Switch(features.newDashboard,
|
|
661
|
-
NewDashboard(),
|
|
662
|
-
LegacyDashboard()
|
|
663
|
-
)
|
|
664
|
-
]);
|
|
276
|
+
ShowIf(canEdit, editButton)
|
|
665
277
|
```
|
|
666
278
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
### Using Console Logs in Conditions
|
|
279
|
+
**Use shorthand checkers instead of manual conditions:**
|
|
670
280
|
|
|
671
281
|
```javascript
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
});
|
|
282
|
+
// Good
|
|
283
|
+
ShowIf(list.isEmpty(), Div('No items'))
|
|
284
|
+
ShowIf(name.isTruthy(), Div(['Hello, ', name]))
|
|
676
285
|
|
|
677
|
-
|
|
286
|
+
// Avoid
|
|
287
|
+
ShowIf(list.check(l => l.length === 0), Div('No items'))
|
|
678
288
|
```
|
|
679
289
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
```javascript
|
|
683
|
-
const status = Observable('idle');
|
|
684
|
-
|
|
685
|
-
// Log all status changes
|
|
686
|
-
status.subscribe(newStatus => {
|
|
687
|
-
console.log('Status changed to:', newStatus);
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
// Debug what content is being rendered
|
|
691
|
-
const StatusContent = Match(status, {
|
|
692
|
-
idle: () => {
|
|
693
|
-
console.log('Rendering idle state');
|
|
694
|
-
return IdleComponent();
|
|
695
|
-
},
|
|
696
|
-
loading: () => {
|
|
697
|
-
console.log('Rendering loading state');
|
|
698
|
-
return LoadingComponent();
|
|
699
|
-
}
|
|
700
|
-
});
|
|
701
|
-
```
|
|
290
|
+
---
|
|
702
291
|
|
|
703
292
|
## Next Steps
|
|
704
293
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
- **[
|
|
708
|
-
- **[
|
|
709
|
-
- **[
|
|
710
|
-
- **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
|
|
711
|
-
- **[NDElement](native-document-element.md)** - Native Document Element
|
|
712
|
-
- **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
|
|
713
|
-
- **[Advanced Components](advanced-components.md)** - Template caching and singleton views
|
|
714
|
-
- **[Args Validation](validation.md)** - Function Argument Validation
|
|
715
|
-
- **[Memory Management](memory-management.md)** - Memory management
|
|
716
|
-
- **[Anchor](anchor.md)** - Anchor
|
|
294
|
+
- **[List Rendering](./list-rendering.md)** - ForEach and dynamic lists
|
|
295
|
+
- **[Observables](./observables.md)** - Reactive state management
|
|
296
|
+
- **[Elements](./elements.md)** - Creating and composing UI
|
|
297
|
+
- **[State Management](./state-management.md)** - Global state patterns
|
|
298
|
+
- **[Anchor](./anchor.md)** - How conditional rendering works under the hood
|
|
717
299
|
|
|
718
300
|
## Utilities
|
|
719
301
|
|
|
720
|
-
- **[Cache](
|
|
721
|
-
- **[NativeFetch](
|
|
722
|
-
- **[Filters](
|
|
302
|
+
- **[Cache](./cache.md)** - Lazy initialization and singleton patterns
|
|
303
|
+
- **[NativeFetch](./native-fetch.md)** - HTTP client with interceptors
|
|
304
|
+
- **[Filters](./filters.md)** - Data filtering helpers
|