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