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
package/docs/routing.md
CHANGED
|
@@ -1,826 +1,417 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Routing
|
|
3
|
+
description: Build single-page applications with NativeDocument's full-featured router - hash, history, and memory modes with layouts, middlewares, and named routes
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# Routing
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
NativeDocument's routing system enables single-page applications with client-side navigation. The router manages URL changes, renders the right components, and maintains application state without full page reloads.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
## Setup
|
|
8
11
|
|
|
9
12
|
```javascript
|
|
10
|
-
import { Router } from 'native-document/router';
|
|
13
|
+
import { Router, Link } from 'native-document/router';
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
const router = Router.create({ mode: 'history' }, (router) => {
|
|
15
|
+
Router.create({ name: 'default', mode: 'history' }, (router) => {
|
|
14
16
|
router.add('/', HomePage);
|
|
15
17
|
router.add('/about', AboutPage);
|
|
16
18
|
router.add('/users/{id}', UserProfile);
|
|
17
19
|
});
|
|
18
|
-
|
|
19
|
-
// Mount to DOM
|
|
20
|
-
router.mount('#app');
|
|
21
20
|
```
|
|
22
21
|
|
|
23
22
|
## Router Modes
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
### History mode (recommended)
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Uses HTML5 History API for clean URLs without hash symbols:
|
|
26
|
+
Uses the HTML5 History API for clean URLs:
|
|
30
27
|
|
|
31
28
|
```javascript
|
|
32
|
-
|
|
33
|
-
router.add('/',
|
|
34
|
-
router.add('/products',
|
|
35
|
-
router.add('/contact', () => Div('Contact Us'));
|
|
29
|
+
Router.create({ name: 'app', mode: 'history' }, (router) => {
|
|
30
|
+
router.add('/', HomePage);
|
|
31
|
+
router.add('/products', ProductList);
|
|
36
32
|
});
|
|
37
|
-
|
|
38
|
-
// URLs: /products, /contact, /users/123
|
|
33
|
+
// URLs: /products, /users/123
|
|
39
34
|
```
|
|
40
35
|
|
|
41
|
-
### Hash
|
|
36
|
+
### Hash mode
|
|
42
37
|
|
|
43
|
-
Uses URL fragments
|
|
38
|
+
Uses URL fragments - compatible with static hosting:
|
|
44
39
|
|
|
45
40
|
```javascript
|
|
46
|
-
|
|
47
|
-
router.add('/',
|
|
48
|
-
router.add('/dashboard',
|
|
41
|
+
Router.create({ name: 'app', mode: 'hash' }, (router) => {
|
|
42
|
+
router.add('/', HomePage);
|
|
43
|
+
router.add('/dashboard', Dashboard);
|
|
49
44
|
});
|
|
50
|
-
|
|
51
45
|
// URLs: #/dashboard, #/users/456
|
|
52
46
|
```
|
|
53
47
|
|
|
54
|
-
### Memory
|
|
48
|
+
### Memory mode
|
|
55
49
|
|
|
56
|
-
Keeps routing state in memory without URL
|
|
50
|
+
Keeps routing state in memory without changing the URL - useful for testing or embedded flows:
|
|
57
51
|
|
|
58
52
|
```javascript
|
|
59
|
-
|
|
60
|
-
router.add('/',
|
|
61
|
-
router.add('/
|
|
53
|
+
Router.create({ name: 'app', mode: 'memory' }, (router) => {
|
|
54
|
+
router.add('/', HomePage);
|
|
55
|
+
router.add('/step2', Step2);
|
|
62
56
|
});
|
|
63
|
-
|
|
64
|
-
// URLs don't change, navigation happens programmatically
|
|
65
57
|
```
|
|
66
58
|
|
|
67
|
-
|
|
59
|
+
---
|
|
68
60
|
|
|
69
|
-
|
|
61
|
+
## Defining Routes
|
|
70
62
|
|
|
71
63
|
```javascript
|
|
72
|
-
|
|
64
|
+
Router.create({ name: 'app', mode: 'history' }, (router) => {
|
|
73
65
|
// Static routes
|
|
74
|
-
router.add('/',
|
|
75
|
-
router.add('/about',
|
|
76
|
-
|
|
77
|
-
//
|
|
78
|
-
router.add('/users/{id}', ({ params }) =>
|
|
79
|
-
Div(['User
|
|
66
|
+
router.add('/', HomePage);
|
|
67
|
+
router.add('/about', AboutPage);
|
|
68
|
+
|
|
69
|
+
// Route with parameter
|
|
70
|
+
router.add('/users/{id}', ({ params }) =>
|
|
71
|
+
Div(['User: ', params.id])
|
|
80
72
|
);
|
|
81
|
-
|
|
73
|
+
|
|
82
74
|
// Multiple parameters
|
|
83
|
-
router.add('/posts/{category}/{slug}', ({ params }) =>
|
|
84
|
-
|
|
85
|
-
'Category: ', params.category,
|
|
86
|
-
', Post: ', params.slug
|
|
87
|
-
])
|
|
75
|
+
router.add('/posts/{category}/{slug}', ({ params }) =>
|
|
76
|
+
BlogPost({ category: params.category, slug: params.slug })
|
|
88
77
|
);
|
|
78
|
+
|
|
79
|
+
// Catch-all 404 (must be last)
|
|
80
|
+
router.add('{*}', NotFoundPage);
|
|
89
81
|
});
|
|
90
82
|
```
|
|
91
83
|
|
|
92
|
-
|
|
84
|
+
---
|
|
93
85
|
|
|
94
|
-
|
|
86
|
+
## Route Parameters
|
|
95
87
|
|
|
96
|
-
### Basic
|
|
88
|
+
### Basic parameters
|
|
97
89
|
|
|
98
90
|
```javascript
|
|
99
|
-
router.add('/users/{
|
|
100
|
-
const user = getUserById(params.userId);
|
|
101
|
-
return UserProfileComponent(user);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
router.add('/blog/{year}/{month}', ({ params }) => {
|
|
105
|
-
return BlogArchive({
|
|
106
|
-
year: parseInt(params.year),
|
|
107
|
-
month: parseInt(params.month)
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Parameter Validation
|
|
113
|
-
|
|
114
|
-
Define custom patterns for parameter validation:
|
|
91
|
+
router.add('/users/{id}', ({ params }) => UserProfile(params.id));
|
|
115
92
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const router = Router.create({ mode: 'history' }, (router) => {
|
|
121
|
-
// Only numeric IDs - inline pattern
|
|
122
|
-
router.add('/users/{id}', ({ params }) =>
|
|
123
|
-
UserProfile(params.id),
|
|
124
|
-
{ with: { id: '[0-9]+' }}
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
// OR using global pattern
|
|
128
|
-
router.add('/users/{id:number}', ({ params }) =>
|
|
129
|
-
UserProfile(params.id)
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
// Custom patterns - inline
|
|
133
|
-
router.add('/posts/{slug}', ({ params }) =>
|
|
134
|
-
BlogPost(params.slug),
|
|
135
|
-
{ with: { slug: '[a-z0-9-]+' }}
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
// OR define and use pattern
|
|
139
|
-
RouteParamPatterns.slug = '[a-z0-9-]+';
|
|
140
|
-
router.add('/posts/{slug:slug}', ({ params }) =>
|
|
141
|
-
BlogPost(params.slug)
|
|
142
|
-
);
|
|
143
|
-
});
|
|
93
|
+
router.add('/blog/{year}/{month}', ({ params }) =>
|
|
94
|
+
BlogArchive({ year: parseInt(params.year), month: parseInt(params.month) })
|
|
95
|
+
);
|
|
144
96
|
```
|
|
145
97
|
|
|
146
|
-
|
|
98
|
+
### Parameter validation
|
|
147
99
|
|
|
148
|
-
|
|
100
|
+
Use `{ with: { paramName: pattern } }` to restrict a parameter to a regex pattern:
|
|
149
101
|
|
|
150
102
|
```javascript
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return SearchResults({
|
|
155
|
-
searchTerm: term,
|
|
156
|
-
category: category,
|
|
157
|
-
currentPage: parseInt(page)
|
|
158
|
-
});
|
|
159
|
-
});
|
|
103
|
+
// Inline pattern - only numeric ids
|
|
104
|
+
router.add('/users/{id}', UserProfile, { with: { id: '[0-9]+' } });
|
|
160
105
|
|
|
161
|
-
//
|
|
162
|
-
|
|
106
|
+
// Slug pattern
|
|
107
|
+
router.add('/posts/{slug}', BlogPost, { with: { slug: '[a-z0-9-]+' } });
|
|
163
108
|
```
|
|
164
109
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
Navigate programmatically using router methods:
|
|
168
|
-
|
|
169
|
-
### Push Navigation
|
|
170
|
-
|
|
171
|
-
Add new entries to browser history. **Specify router name** when using multiple routers:
|
|
110
|
+
You can also register patterns globally via `RouteParamPatterns` and reference them as `{param:patternName}`:
|
|
172
111
|
|
|
173
112
|
```javascript
|
|
174
|
-
|
|
175
|
-
Button('Go to About').nd.onClick(() => {
|
|
176
|
-
Router.push('/about'); // Uses default router
|
|
177
|
-
}),
|
|
178
|
-
|
|
179
|
-
Button('Go to About (Main Router)').nd.onClick(() => {
|
|
180
|
-
Router.push('/about', 'main'); // Uses named router
|
|
181
|
-
}),
|
|
182
|
-
|
|
183
|
-
Button('View User 123').nd.onClick(() => {
|
|
184
|
-
// Navigate in specific router
|
|
185
|
-
Router.push('/users/123', 'app');
|
|
186
|
-
}),
|
|
187
|
-
|
|
188
|
-
Button('Search Products').nd.onClick(() => {
|
|
189
|
-
Router.push('/search?term=laptop&category=electronics', 'main');
|
|
190
|
-
})
|
|
191
|
-
]);
|
|
113
|
+
import { RouteParamPatterns } from 'native-document/router';
|
|
192
114
|
```
|
|
193
115
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
116
|
+
Built-in patterns:
|
|
117
|
+
|
|
118
|
+
| Name | Pattern | Matches |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `id` | `[0-9]+` | Numeric IDs |
|
|
121
|
+
| `uuid` | `[0-9a-f]{8}-...` | UUIDs |
|
|
122
|
+
| `slug` | `[a-z0-9]+(?:-[a-z0-9]+)*` | URL slugs |
|
|
123
|
+
| `hash` | `[a-f0-9]{32,64}` | MD5/SHA hashes |
|
|
124
|
+
| `alpha` | `[a-zA-Z]+` | Letters only |
|
|
125
|
+
| `alphanum` | `[a-zA-Z0-9]+` | Letters and digits |
|
|
126
|
+
| `string` | `[^/]+` | Any non-slash string |
|
|
127
|
+
| `any` | `.*` | Anything including slashes |
|
|
128
|
+
| `int` | `[0-9]+` | Integers |
|
|
129
|
+
| `float` | `[0-9]+\.[0-9]+` | Floats |
|
|
130
|
+
| `number` | `[0-9]+(\.[0-9]+)?` | Integers or floats |
|
|
131
|
+
| `positive` | `[1-9][0-9]*` | Positive integers (no zero) |
|
|
132
|
+
| `locale` | `[a-z]{2}(-[A-Z]{2})?` | Locale codes (`fr`, `en-US`) |
|
|
133
|
+
| `lang` | `[a-z]{2}` | Language codes (`fr`, `en`) |
|
|
134
|
+
| `token` | `[A-Za-z0-9_\-]+` | Tokens and API keys |
|
|
197
135
|
|
|
198
136
|
```javascript
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// Or specify router
|
|
204
|
-
Router.replace('/dashboard', 'main');
|
|
205
|
-
|
|
206
|
-
return Div('Redirecting...');
|
|
207
|
-
};
|
|
137
|
+
router.add('/users/{id:id}', UserProfile); // numeric only
|
|
138
|
+
router.add('/posts/{slug:slug}', BlogPost); // url-safe slug
|
|
139
|
+
router.add('/files/{hash:hash}', FileView); // hash string
|
|
140
|
+
router.add('/lang/{code:locale}', LocalePage); // fr, en-US
|
|
208
141
|
```
|
|
209
142
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
Navigate through browser history:
|
|
143
|
+
You can also extend `RouteParamPatterns` with your own:
|
|
213
144
|
|
|
214
145
|
```javascript
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// Navigate specific router's history
|
|
220
|
-
Button('Back in Main').nd.onClick(() => Router.back('main')),
|
|
221
|
-
Button('Forward in Admin').nd.onClick(() => Router.forward('admin'))
|
|
222
|
-
]);
|
|
146
|
+
RouteParamPatterns.year = '[0-9]{4}';
|
|
147
|
+
RouteParamPatterns.color = '[0-9a-fA-F]{6}';
|
|
148
|
+
|
|
149
|
+
router.add('/archive/{year:year}', ArchivePage);
|
|
223
150
|
```
|
|
224
151
|
|
|
225
|
-
|
|
152
|
+
---
|
|
226
153
|
|
|
227
|
-
|
|
154
|
+
## Query Parameters
|
|
228
155
|
|
|
229
156
|
```javascript
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
router.add('/posts/{category}/{slug}', BlogPost, { name: 'blog.post' });
|
|
157
|
+
router.add('/search', ({ query }) => {
|
|
158
|
+
const { term, category, page = '1' } = query;
|
|
159
|
+
return SearchResults({ term, category, page: parseInt(page) });
|
|
234
160
|
});
|
|
235
|
-
|
|
236
|
-
// Generate URLs by name
|
|
237
|
-
const userUrl = router.generateUrl('user.profile', { id: 123 });
|
|
238
|
-
// Result: '/users/123'
|
|
239
|
-
|
|
240
|
-
const blogUrl = router.generateUrl('blog.post', { category: 'javascript', slug: 'getting-started' }, { ref: 'newsletter' });
|
|
241
|
-
// Result: '/posts/javascript/getting-started?ref=newsletter'
|
|
161
|
+
// URL: /search?term=javascript&category=tutorials&page=2
|
|
242
162
|
```
|
|
243
163
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
```javascript
|
|
247
|
-
const navigation = Div([
|
|
248
|
-
Button('Home').nd.onClick(() =>
|
|
249
|
-
Router.push({ name: 'home' }) // Uses router containing this route
|
|
250
|
-
),
|
|
251
|
-
|
|
252
|
-
Button('My Profile').nd.onClick(() =>
|
|
253
|
-
Router.push({
|
|
254
|
-
name: 'user.profile',
|
|
255
|
-
params: { id: currentUser.id }
|
|
256
|
-
}, 'main') // Specify router if needed
|
|
257
|
-
),
|
|
258
|
-
|
|
259
|
-
Button('Latest Post').nd.onClick(() =>
|
|
260
|
-
Router.push({
|
|
261
|
-
name: 'blog.post',
|
|
262
|
-
params: { category: 'news', slug: 'latest-update' },
|
|
263
|
-
query: { highlight: 'new-features' }
|
|
264
|
-
}, 'blog') // Navigate in blog router
|
|
265
|
-
)
|
|
266
|
-
]);
|
|
267
|
-
```
|
|
164
|
+
---
|
|
268
165
|
|
|
269
166
|
## Route Groups
|
|
270
167
|
|
|
271
168
|
Organize related routes with shared configuration:
|
|
272
169
|
|
|
273
170
|
```javascript
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
router.add('/
|
|
279
|
-
router.add('/
|
|
171
|
+
Router.create({ name: 'app', mode: 'history' }, (router) => {
|
|
172
|
+
|
|
173
|
+
// Group with layout
|
|
174
|
+
router.group('', { layout: DefaultLayout }, () => {
|
|
175
|
+
router.add('/', HomePage, { name: 'home' });
|
|
176
|
+
router.add('/about', AboutPage, { name: 'about' });
|
|
280
177
|
});
|
|
281
|
-
|
|
282
|
-
//
|
|
283
|
-
router.group('/
|
|
284
|
-
router.add('/',
|
|
285
|
-
router.add('/
|
|
286
|
-
router.add('/settings', UserSettings, { name: 'settings' }); // dashboard.settings
|
|
178
|
+
|
|
179
|
+
// Group with middleware and name prefix
|
|
180
|
+
router.group('/admin', { middlewares: [requireAuth], name: 'admin' }, () => {
|
|
181
|
+
router.add('/', AdminDashboard, { name: 'dashboard' }); // admin.dashboard
|
|
182
|
+
router.add('/users', AdminUsers, { name: 'users' }); // admin.users
|
|
287
183
|
});
|
|
184
|
+
|
|
288
185
|
});
|
|
289
186
|
```
|
|
290
187
|
|
|
291
|
-
|
|
188
|
+
---
|
|
292
189
|
|
|
293
|
-
|
|
190
|
+
## Named Routes
|
|
294
191
|
|
|
295
|
-
|
|
192
|
+
Naming routes lets you generate URLs and navigate without hardcoding paths:
|
|
296
193
|
|
|
297
194
|
```javascript
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
Router.replace({
|
|
304
|
-
name: 'auth.login',
|
|
305
|
-
query: { redirect: path }
|
|
306
|
-
});
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Continue to route component
|
|
311
|
-
next();
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const requireAdmin = (context, next) => {
|
|
315
|
-
if (!isUserAdmin()) {
|
|
316
|
-
Router.replace({ name: 'errors.forbidden' });
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
next();
|
|
320
|
-
};
|
|
321
|
-
```
|
|
195
|
+
Router.create({ name: 'app', mode: 'history' }, (router) => {
|
|
196
|
+
router.add('/', HomePage, { name: 'home' });
|
|
197
|
+
router.add('/users/{id}', UserProfile, { name: 'user.profile' });
|
|
198
|
+
router.add('/posts/{category}/{slug}', BlogPost, { name: 'blog.post' });
|
|
199
|
+
});
|
|
322
200
|
|
|
323
|
-
|
|
201
|
+
// Generate a URL by name
|
|
202
|
+
const router = Router.get('app');
|
|
203
|
+
router.generateUrl('user.profile', { id: 123 });
|
|
204
|
+
// -> '/users/123'
|
|
324
205
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
// Continue to route component
|
|
331
|
-
next();
|
|
332
|
-
|
|
333
|
-
// Hide loading indicator
|
|
334
|
-
setTimeout(() => hideGlobalLoader(), 100);
|
|
335
|
-
};
|
|
206
|
+
router.generateUrl('blog.post',
|
|
207
|
+
{ category: 'javascript', slug: 'getting-started' },
|
|
208
|
+
{ ref: 'newsletter' }
|
|
209
|
+
);
|
|
210
|
+
// -> '/posts/javascript/getting-started?ref=newsletter'
|
|
336
211
|
```
|
|
337
212
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
```javascript
|
|
341
|
-
const analyticsMiddleware = (context, next) => {
|
|
342
|
-
const { route, params, path } = context;
|
|
343
|
-
|
|
344
|
-
// Track page view
|
|
345
|
-
analytics.track('page_view', {
|
|
346
|
-
path: path,
|
|
347
|
-
route_name: route.name(),
|
|
348
|
-
timestamp: Date.now()
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
next();
|
|
352
|
-
};
|
|
353
|
-
```
|
|
213
|
+
---
|
|
354
214
|
|
|
355
|
-
##
|
|
215
|
+
## Navigation
|
|
356
216
|
|
|
357
|
-
|
|
217
|
+
### `Router.push(target, routerName?)`
|
|
358
218
|
|
|
359
|
-
|
|
219
|
+
Add a new entry to the browser history:
|
|
360
220
|
|
|
361
221
|
```javascript
|
|
362
|
-
|
|
222
|
+
Router.push('/about'); // path - uses default router
|
|
223
|
+
Router.push('/about', 'app'); // path - named router
|
|
363
224
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
Link({ to: '/contact' }, 'Contact'),
|
|
368
|
-
|
|
369
|
-
// External links open in new tab
|
|
370
|
-
Link.blank({ href: 'https://example.com' }, 'External Site')
|
|
371
|
-
]);
|
|
225
|
+
Router.push({ name: 'home' }); // named route
|
|
226
|
+
Router.push({ name: 'user.profile', params: { id: 42 } }, 'app');
|
|
227
|
+
Router.push({ name: 'search', query: { term: 'laptop' } });
|
|
372
228
|
```
|
|
373
229
|
|
|
374
|
-
###
|
|
230
|
+
### `Router.replace(target, routerName?)`
|
|
375
231
|
|
|
376
|
-
|
|
377
|
-
const UserMenu = Div([
|
|
378
|
-
Link({
|
|
379
|
-
to: {
|
|
380
|
-
name: 'user.profile',
|
|
381
|
-
params: { id: currentUser.id },
|
|
382
|
-
router: 'main' // Specify which router to use
|
|
383
|
-
}
|
|
384
|
-
}, 'My Profile'),
|
|
385
|
-
|
|
386
|
-
Link({
|
|
387
|
-
to: {
|
|
388
|
-
name: 'user.settings',
|
|
389
|
-
params: { id: currentUser.id },
|
|
390
|
-
query: { tab: 'preferences' },
|
|
391
|
-
router: 'main'
|
|
392
|
-
}
|
|
393
|
-
}, 'Settings'),
|
|
394
|
-
|
|
395
|
-
// Cross-router navigation
|
|
396
|
-
Link({
|
|
397
|
-
to: {
|
|
398
|
-
name: 'admin.dashboard',
|
|
399
|
-
router: 'admin'
|
|
400
|
-
}
|
|
401
|
-
}, 'Admin Panel')
|
|
402
|
-
]);
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
### Active Link Styling
|
|
232
|
+
Replace the current history entry without adding to the stack:
|
|
406
233
|
|
|
407
234
|
```javascript
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const isActive = Observable(router.currentState().path === path);
|
|
411
|
-
router.subscribe((state) => {
|
|
412
|
-
isActive.set(state.path === path);
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
return Link({
|
|
416
|
-
to: path,
|
|
417
|
-
class: { 'nav-link': true, 'active': isActive }
|
|
418
|
-
}, text);
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
const MainNav = Nav([
|
|
422
|
-
NavItem('/', 'Home', 'main'),
|
|
423
|
-
NavItem('/products', 'Products', 'main'),
|
|
424
|
-
NavItem('/services', 'Services', 'main')
|
|
425
|
-
]);
|
|
426
|
-
|
|
427
|
-
// Multi-router navigation
|
|
428
|
-
const AppNav = Nav([
|
|
429
|
-
NavItem('/', 'Main App', 'main'),
|
|
430
|
-
NavItem('/admin', 'Admin', 'admin'),
|
|
431
|
-
NavItem('/blog', 'Blog', 'blog')
|
|
432
|
-
]);
|
|
433
|
-
|
|
235
|
+
Router.replace('/dashboard');
|
|
236
|
+
Router.replace({ name: 'login', query: { redirect: '/dashboard' } });
|
|
434
237
|
```
|
|
435
238
|
|
|
436
|
-
|
|
239
|
+
### `Router.redirectTo(pathOrRouteName, params?, routerName?)`
|
|
437
240
|
|
|
438
|
-
|
|
241
|
+
Redirect to a path or named route. If a route name is found, it navigates by name; otherwise it treats the argument as a path:
|
|
439
242
|
|
|
440
243
|
```javascript
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
(router) => {
|
|
445
|
-
router.add('/', HomePage);
|
|
446
|
-
router.add('/products', ProductList);
|
|
447
|
-
router.add('/admin', AdminApp);
|
|
448
|
-
}
|
|
449
|
-
);
|
|
450
|
-
|
|
451
|
-
// Admin-specific router (named)
|
|
452
|
-
const adminRouter = Router.create(
|
|
453
|
-
{ mode: 'history', name: 'admin', entry: '/admin' },
|
|
454
|
-
(router) => {
|
|
455
|
-
router.add('/', AdminDashboard);
|
|
456
|
-
router.add('/users', AdminUsers);
|
|
457
|
-
router.add('/reports', AdminReports);
|
|
458
|
-
}
|
|
459
|
-
);
|
|
460
|
-
|
|
461
|
-
// Access routers by name
|
|
462
|
-
const mainRouter = Router.get('main');
|
|
463
|
-
const adminRouter = Router.get('admin');
|
|
464
|
-
|
|
465
|
-
// Or access via routers object
|
|
466
|
-
const mainRouter = Router.routers.main;
|
|
467
|
-
const adminRouter = Router.routers.admin;
|
|
468
|
-
|
|
469
|
-
// Cross-router navigation
|
|
470
|
-
Button('Go to Admin').nd.onClick(() => {
|
|
471
|
-
Router.push('/admin/users', 'admin'); // Specify router name
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
Button('Back to Main').nd.onClick(() => {
|
|
475
|
-
Router.push('/', 'main'); // Navigate in main router
|
|
476
|
-
});
|
|
244
|
+
Router.redirectTo('home');
|
|
245
|
+
Router.redirectTo('user.profile', { id: 42 });
|
|
246
|
+
Router.redirectTo('/legacy-path');
|
|
477
247
|
```
|
|
478
248
|
|
|
479
|
-
### Router
|
|
249
|
+
### `Router.back(routerName?)` / `Router.forward(routerName?)`
|
|
480
250
|
|
|
481
|
-
|
|
251
|
+
Navigate through browser history:
|
|
482
252
|
|
|
483
253
|
```javascript
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
// This becomes the default router
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
// ❌ BAD: Multiple unnamed routers cause conflicts
|
|
490
|
-
const router2 = Router.create({ mode: 'history' }, (router) => {
|
|
491
|
-
// This overwrites the default router!
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
// ✅ GOOD: Named routers
|
|
495
|
-
const appRouter = Router.create({ mode: 'history', name: 'app' }, (router) => {
|
|
496
|
-
router.add('/', HomePage);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
const modalRouter = Router.create({ mode: 'memory', name: 'modal' }, (router) => {
|
|
500
|
-
router.add('/confirm', ConfirmDialog);
|
|
501
|
-
});
|
|
254
|
+
Button('Back').nd.onClick(() => Router.back());
|
|
255
|
+
Button('Forward').nd.onClick(() => Router.forward('app'));
|
|
502
256
|
```
|
|
503
257
|
|
|
504
|
-
|
|
258
|
+
---
|
|
505
259
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
```javascript
|
|
509
|
-
// Create default router
|
|
510
|
-
Router.create({ mode: 'history' }, (router) => {
|
|
511
|
-
router.add('/', HomePage);
|
|
512
|
-
});
|
|
260
|
+
## Link Component
|
|
513
261
|
|
|
514
|
-
|
|
515
|
-
const defaultRouter = Router.get(); // Returns router named "default"
|
|
516
|
-
const defaultRouter = Router.get('default'); // Same as above
|
|
517
|
-
const defaultRouter = Router.routers.default; // Direct access
|
|
262
|
+
`to` takes a route **name** (string) or an object with `name`, `params`, `query`, and `router`. Use `href` for direct path links:
|
|
518
263
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
Router.push('/', 'default'); // Explicitly use default router
|
|
522
|
-
```
|
|
264
|
+
```javascript
|
|
265
|
+
import { Link } from 'native-document/router';
|
|
523
266
|
|
|
524
|
-
|
|
267
|
+
// Named route
|
|
268
|
+
Link({ to: 'home' }, 'Home')
|
|
525
269
|
|
|
526
|
-
|
|
270
|
+
// Named route with params
|
|
271
|
+
Link({ to: { name: 'user.profile', params: { id: 42 } } }, 'Profile')
|
|
527
272
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const router1 = Router.create({ mode: 'history' }, (router) => {
|
|
531
|
-
// This is stored as Router.routers.default
|
|
532
|
-
});
|
|
273
|
+
// Named route with query
|
|
274
|
+
Link({ to: { name: 'search', query: { term: 'js' } } }, 'Search')
|
|
533
275
|
|
|
534
|
-
//
|
|
535
|
-
|
|
536
|
-
// This overwrites Router.routers.default!
|
|
537
|
-
});
|
|
276
|
+
// Named route on a specific router
|
|
277
|
+
Link({ to: { name: 'admin.dashboard', router: 'admin' } }, 'Admin')
|
|
538
278
|
|
|
539
|
-
//
|
|
540
|
-
|
|
541
|
-
router.add('/', HomePage);
|
|
542
|
-
// Stored as Router.routers.app
|
|
543
|
-
});
|
|
279
|
+
// Direct path link
|
|
280
|
+
Link({ href: '/about' }, 'About')
|
|
544
281
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
// Stored as Router.routers.modal
|
|
548
|
-
});
|
|
282
|
+
// External link - opens in new tab
|
|
283
|
+
Link.blank({ href: 'https://example.com' }, 'External')
|
|
549
284
|
```
|
|
550
285
|
|
|
551
|
-
|
|
286
|
+
---
|
|
552
287
|
|
|
553
|
-
|
|
288
|
+
## Middleware
|
|
554
289
|
|
|
555
|
-
|
|
290
|
+
Middleware runs before the route component is rendered. It receives a `context` object and a `next` function:
|
|
556
291
|
|
|
557
292
|
```javascript
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
fetchUser(userId)
|
|
566
|
-
.then(userData => {
|
|
567
|
-
user.set(userData);
|
|
568
|
-
loading.set(false);
|
|
569
|
-
})
|
|
570
|
-
.catch(err => {
|
|
571
|
-
error.set(err.message);
|
|
572
|
-
loading.set(false);
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
return Match(loading, {
|
|
576
|
-
true: LoadingSpinner,
|
|
577
|
-
false: () => Switch(error, ErrorMessage(error), UserProfileView(user) )
|
|
578
|
-
});
|
|
293
|
+
const requireAuth = (context, next) => {
|
|
294
|
+
const { path } = context;
|
|
295
|
+
if (!isAuthenticated()) {
|
|
296
|
+
Router.replace({ name: 'login', query: { redirect: path } });
|
|
297
|
+
return; // stop - don't call next()
|
|
298
|
+
}
|
|
299
|
+
next();
|
|
579
300
|
};
|
|
580
301
|
|
|
581
|
-
|
|
302
|
+
const analytics = (context, next) => {
|
|
303
|
+
trackPageView(context.path);
|
|
304
|
+
next();
|
|
305
|
+
};
|
|
582
306
|
```
|
|
583
307
|
|
|
584
|
-
|
|
308
|
+
Apply middleware to a group or individual route:
|
|
585
309
|
|
|
586
310
|
```javascript
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
};
|
|
311
|
+
Router.create({ name: 'app', mode: 'history' }, (router) => {
|
|
312
|
+
|
|
313
|
+
router.add('/', HomePage, { name: 'home' });
|
|
314
|
+
|
|
315
|
+
router.group('/dashboard', { middlewares: [requireAuth, analytics] }, () => {
|
|
316
|
+
router.add('/', Dashboard, { name: 'dashboard' });
|
|
317
|
+
router.add('/settings', Settings, { name: 'settings' });
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Route-level middleware
|
|
321
|
+
router.add('/admin', AdminPage, {
|
|
322
|
+
name: 'admin',
|
|
323
|
+
middlewares: [requireAuth, requireAdmin]
|
|
324
|
+
});
|
|
601
325
|
|
|
602
|
-
// Add to all routes that need protection
|
|
603
|
-
router.group('/', { middlewares: [authGuard] }, () => {
|
|
604
|
-
router.add('/dashboard', Dashboard, { name: 'dashboard' });
|
|
605
|
-
router.add('/profile', Profile, { name: 'profile' });
|
|
606
|
-
router.add('/settings', Settings, { name: 'settings' });
|
|
607
326
|
});
|
|
608
327
|
```
|
|
609
328
|
|
|
610
|
-
|
|
329
|
+
---
|
|
611
330
|
|
|
612
|
-
|
|
331
|
+
## Multiple Routers
|
|
613
332
|
|
|
614
|
-
|
|
333
|
+
Always name your routers to avoid conflicts:
|
|
615
334
|
|
|
616
335
|
```javascript
|
|
617
|
-
|
|
336
|
+
Router.create({ name: 'main', mode: 'history' }, (router) => {
|
|
618
337
|
router.add('/', HomePage);
|
|
619
|
-
router.add('/
|
|
620
|
-
|
|
621
|
-
// Catch-all route for 404s (must be last)
|
|
622
|
-
router.add('.*', ({ params }) =>
|
|
623
|
-
NotFoundPage({ attemptedPath: params.path })
|
|
624
|
-
);
|
|
338
|
+
router.add('/products', ProductList);
|
|
625
339
|
});
|
|
626
340
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
]);
|
|
632
|
-
```
|
|
341
|
+
Router.create({ name: 'admin', mode: 'history', entry: '/admin' }, (router) => {
|
|
342
|
+
router.add('/', AdminDashboard);
|
|
343
|
+
router.add('/users', AdminUsers);
|
|
344
|
+
});
|
|
633
345
|
|
|
634
|
-
|
|
346
|
+
// Access by name
|
|
347
|
+
const main = Router.get('main');
|
|
348
|
+
const admin = Router.get('admin');
|
|
635
349
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
next();
|
|
640
|
-
} catch (error) {
|
|
641
|
-
console.error('Route error:', error);
|
|
642
|
-
|
|
643
|
-
// Navigate to error page
|
|
644
|
-
Router.replace({
|
|
645
|
-
name: 'error',
|
|
646
|
-
query: { message: error.message }
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
};
|
|
350
|
+
// Or via the routers object (plain object, not reactive)
|
|
351
|
+
Router.routers.main;
|
|
352
|
+
Router.routers.admin;
|
|
650
353
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
);
|
|
354
|
+
// Navigate in a specific router
|
|
355
|
+
Button('Go to admin').nd.onClick(() => Router.push('/admin/users', 'admin'));
|
|
654
356
|
```
|
|
655
357
|
|
|
656
|
-
|
|
358
|
+
> Two unnamed routers would both be stored as `Router.routers.default` - the second would overwrite the first.
|
|
657
359
|
|
|
658
|
-
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Current State
|
|
659
363
|
|
|
660
364
|
```javascript
|
|
661
|
-
const
|
|
662
|
-
// Public routes
|
|
663
|
-
router.add('/', HomePage, { name: 'home' });
|
|
664
|
-
router.add('/products', ProductCatalog, { name: 'products' });
|
|
665
|
-
router.add('/products/{category}', CategoryPage, { name: 'category' });
|
|
666
|
-
router.add('/products/{category}/{id}', ProductDetail, { name: 'product' });
|
|
667
|
-
|
|
668
|
-
// User account routes
|
|
669
|
-
router.group('/account', { middlewares: [requireAuth] }, () => {
|
|
670
|
-
router.add('/', AccountDashboard, { name: 'account.dashboard' });
|
|
671
|
-
router.add('/orders', OrderHistory, { name: 'account.orders' });
|
|
672
|
-
router.add('/profile', UserProfile, { name: 'account.profile' });
|
|
673
|
-
router.add('/addresses', AddressBook, { name: 'account.addresses' });
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
// Shopping cart
|
|
677
|
-
router.add('/cart', ShoppingCart, { name: 'cart' });
|
|
678
|
-
router.add('/checkout', CheckoutProcess, { name: 'checkout', middlewares: [requireAuth] });
|
|
679
|
-
|
|
680
|
-
// Search and filters
|
|
681
|
-
router.add('/search', SearchResults, { name: 'search' });
|
|
682
|
-
// URL: /search?q=laptop&category=electronics&price_min=100&price_max=1000
|
|
683
|
-
});
|
|
365
|
+
const router = Router.get('app');
|
|
684
366
|
|
|
685
|
-
//
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
return ShowIf(product, () => {
|
|
693
|
-
const p = product.val();
|
|
694
|
-
return Div([
|
|
695
|
-
// Breadcrumb navigation
|
|
696
|
-
Nav([
|
|
697
|
-
Link({ to: { name: 'home' } }, 'Home'),
|
|
698
|
-
' > ',
|
|
699
|
-
Link({ to: { name: 'category', params: { category } } }, category),
|
|
700
|
-
' > ',
|
|
701
|
-
Span(p.name)
|
|
702
|
-
]),
|
|
703
|
-
|
|
704
|
-
// Product details
|
|
705
|
-
H1(p.name),
|
|
706
|
-
Div({ class: 'price' }, p.price),
|
|
707
|
-
P(p.description),
|
|
708
|
-
|
|
709
|
-
// Add to cart with redirect to login if needed
|
|
710
|
-
Button('Add to Cart').nd.onClick(() => {
|
|
711
|
-
if (!isAuthenticated()) {
|
|
712
|
-
Router.push({
|
|
713
|
-
name: 'login',
|
|
714
|
-
query: {
|
|
715
|
-
redirect: Router.get().currentState().path
|
|
716
|
-
}
|
|
717
|
-
});
|
|
718
|
-
} else {
|
|
719
|
-
addToCart(p.id);
|
|
720
|
-
Router.push({ name: 'cart' });
|
|
721
|
-
}
|
|
722
|
-
})
|
|
723
|
-
]);
|
|
724
|
-
});
|
|
725
|
-
};
|
|
367
|
+
// Read current state
|
|
368
|
+
const { route, params, query, path, hash } = router.currentState();
|
|
369
|
+
|
|
370
|
+
// Subscribe to navigation changes
|
|
371
|
+
router.subscribe(state => {
|
|
372
|
+
console.log('Navigated to:', state.path);
|
|
373
|
+
});
|
|
726
374
|
```
|
|
727
375
|
|
|
728
|
-
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Error Handling
|
|
729
379
|
|
|
730
380
|
```javascript
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
step1: { name: '', email: '' },
|
|
734
|
-
step2: { address: '', city: '' },
|
|
735
|
-
step3: { payment: '', terms: false }
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
router.add('/form/personal', () =>
|
|
739
|
-
PersonalInfoStep(formData),
|
|
740
|
-
{ name: 'form.personal' }
|
|
741
|
-
);
|
|
742
|
-
|
|
743
|
-
router.add('/form/address', () =>
|
|
744
|
-
AddressStep(formData),
|
|
745
|
-
{ name: 'form.address', middlewares: [validateStep1] }
|
|
746
|
-
);
|
|
747
|
-
|
|
748
|
-
router.add('/form/payment', () =>
|
|
749
|
-
PaymentStep(formData),
|
|
750
|
-
{ name: 'form.payment', middlewares: [validateStep1, validateStep2] }
|
|
751
|
-
);
|
|
752
|
-
|
|
753
|
-
router.add('/form/review', () =>
|
|
754
|
-
ReviewStep(formData),
|
|
755
|
-
{ name: 'form.review', middlewares: [validateAllSteps] }
|
|
756
|
-
);
|
|
757
|
-
});
|
|
381
|
+
Router.create({ name: 'app', mode: 'history' }, (router) => {
|
|
382
|
+
router.add('/', HomePage);
|
|
758
383
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
// Step indicator
|
|
765
|
-
Div({ class: 'step-indicator' },
|
|
766
|
-
steps.map((step, index) =>
|
|
767
|
-
Span({
|
|
768
|
-
class: index <= currentIndex ? 'step active' : 'step'
|
|
769
|
-
}, step)
|
|
770
|
-
)
|
|
771
|
-
),
|
|
772
|
-
|
|
773
|
-
// Navigation buttons
|
|
774
|
-
Div({ class: 'nav-buttons' }, [
|
|
775
|
-
ShowIf(Observable(currentIndex > 0),
|
|
776
|
-
Button('Previous').nd.onClick(() => {
|
|
777
|
-
const prevStep = steps[currentIndex - 1];
|
|
778
|
-
Router.push({ name: `form.${prevStep}` });
|
|
779
|
-
})
|
|
780
|
-
),
|
|
781
|
-
|
|
782
|
-
ShowIf(Observable(currentIndex < steps.length - 1),
|
|
783
|
-
Button('Next').nd.onClick(() => {
|
|
784
|
-
const nextStep = steps[currentIndex + 1];
|
|
785
|
-
Router.push({ name: `form.${nextStep}` });
|
|
786
|
-
})
|
|
787
|
-
),
|
|
788
|
-
|
|
789
|
-
ShowIf(Observable(currentIndex === steps.length - 1),
|
|
790
|
-
Button('Submit').nd.onClick(() => {
|
|
791
|
-
submitForm(formData.$value);
|
|
792
|
-
})
|
|
793
|
-
)
|
|
384
|
+
// Catch-all - must be last
|
|
385
|
+
router.add('{*}', ({ params }) =>
|
|
386
|
+
Div([
|
|
387
|
+
H1('Page not found'),
|
|
388
|
+
Link({ href: '/' }, 'Go home')
|
|
794
389
|
])
|
|
795
|
-
|
|
796
|
-
};
|
|
390
|
+
);
|
|
391
|
+
});
|
|
797
392
|
```
|
|
798
393
|
|
|
394
|
+
---
|
|
395
|
+
|
|
799
396
|
## Best Practices
|
|
800
397
|
|
|
801
|
-
1.
|
|
802
|
-
2.
|
|
803
|
-
3.
|
|
804
|
-
4.
|
|
805
|
-
5.
|
|
806
|
-
6. **Use middleware** for cross-cutting concerns like authentication
|
|
807
|
-
7. **Keep route components focused** - extract complex logic to separate modules
|
|
808
|
-
8. **Test navigation flows** with different router modes
|
|
398
|
+
1. Always name your routers - unnamed routers default to `'default'` and will conflict
|
|
399
|
+
2. Use named routes for all `Link` and `push` calls - avoids hardcoded paths
|
|
400
|
+
3. Use `router.group()` for shared middleware and layout
|
|
401
|
+
4. Use `Router.replace()` after login or form submission to avoid back-button issues
|
|
402
|
+
5. Define the catch-all `{*}` route last
|
|
809
403
|
|
|
810
|
-
|
|
404
|
+
---
|
|
811
405
|
|
|
812
|
-
|
|
406
|
+
## Next Steps
|
|
813
407
|
|
|
814
|
-
- **[State Management](state-management.md)** - Global state patterns
|
|
815
|
-
- **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
|
|
816
|
-
- **[NDElement](native-document-element.md)** - Native Document Element
|
|
817
|
-
- **[
|
|
818
|
-
- **[Args Validation](validation.md)** - Function Argument Validation
|
|
819
|
-
- **[Memory Management](memory-management.md)** - Memory management
|
|
820
|
-
- **[Anchor](anchor.md)** - Anchor
|
|
408
|
+
- **[State Management](./state-management.md)** - Global state patterns
|
|
409
|
+
- **[Lifecycle Events](./lifecycle-events.md)** - Lifecycle events
|
|
410
|
+
- **[NDElement](./native-document-element.md)** - Native Document Element
|
|
411
|
+
- **[Anchor](./anchor.md)** - Anchor
|
|
821
412
|
|
|
822
413
|
## Utilities
|
|
823
414
|
|
|
824
|
-
- **[Cache](
|
|
825
|
-
- **[NativeFetch](
|
|
826
|
-
- **[Filters](
|
|
415
|
+
- **[Cache](./cache.md)** - Lazy initialization and singleton patterns
|
|
416
|
+
- **[NativeFetch](./native-fetch.md)** - HTTP client with interceptors
|
|
417
|
+
- **[Filters](./filters.md)** - Data filtering helpers
|