native-document 1.0.165 → 1.0.166
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/components.d.ts +2 -0
- package/devtools/widget.js +1 -1
- package/dist/native-document.components.min.js +11074 -2735
- package/dist/native-document.dev.js +2269 -392
- package/dist/native-document.dev.js.map +1 -1
- package/dist/native-document.min.js +1 -1
- package/eslint.config.js +28 -33
- package/i18n.js +1 -1
- package/i18n.ts +2 -0
- package/index.js +3 -0
- package/package.json +3 -2
- 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 +128 -10
- package/src/components/card/index.js +3 -3
- 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/List.js +99 -15
- package/src/components/list/ListGroup.js +58 -8
- package/src/components/list/ListItem.js +79 -18
- package/src/components/list/index.js +5 -5
- 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 +42 -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 +11 -2
- 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 +113 -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 +33 -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/contextmenu/ContextmenuRender.js +5 -5
- 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/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/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 +36 -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/SkeletonCard.js +0 -0
- package/src/components/skeleton/SkeletonList.js +0 -0
- package/src/components/skeleton/SkeletonParagraph.js +0 -0
- package/src/components/skeleton/SkeletonTable.js +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' } = {}) => {
|
|
@@ -2381,13 +3194,38 @@ var NativeDocument = (function (exports) {
|
|
|
2381
3194
|
* @returns {ObservableChecker}
|
|
2382
3195
|
*/
|
|
2383
3196
|
ObservableItem.prototype.check = function(callback) {
|
|
2384
|
-
return new ObservableChecker(this, callback)
|
|
3197
|
+
return new ObservableChecker(this, callback);
|
|
2385
3198
|
};
|
|
2386
3199
|
|
|
2387
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);
|
|
@@ -2457,7 +3295,7 @@ var NativeDocument = (function (exports) {
|
|
|
2457
3295
|
{
|
|
2458
3296
|
if (!Formatters[type]) {
|
|
2459
3297
|
throw new NativeDocumentError(
|
|
2460
|
-
`Observable.format : unknown type '${type}'. Available : ${Object.keys(Formatters).join(', ')}
|
|
3298
|
+
`Observable.format : unknown type '${type}'. Available : ${Object.keys(Formatters).join(', ')}.`,
|
|
2461
3299
|
);
|
|
2462
3300
|
}
|
|
2463
3301
|
}
|
|
@@ -2466,7 +3304,7 @@ var NativeDocument = (function (exports) {
|
|
|
2466
3304
|
const localeObservable = Formatters.locale;
|
|
2467
3305
|
|
|
2468
3306
|
return ObservableItem.computed(() => formatter(self.val(), localeObservable.val(), options),
|
|
2469
|
-
[self, localeObservable]
|
|
3307
|
+
[self, localeObservable],
|
|
2470
3308
|
);
|
|
2471
3309
|
};
|
|
2472
3310
|
|
|
@@ -2478,6 +3316,28 @@ var NativeDocument = (function (exports) {
|
|
|
2478
3316
|
ERRORED: 'errored',
|
|
2479
3317
|
};
|
|
2480
3318
|
|
|
3319
|
+
/**
|
|
3320
|
+
* Reactive async data fetcher with built-in state management.
|
|
3321
|
+
* Tracks loading, ready, refreshing, and error states automatically.
|
|
3322
|
+
* Use Observable.resource() rather than instantiating directly.
|
|
3323
|
+
*
|
|
3324
|
+
* @constructor
|
|
3325
|
+
* @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.
|
|
3326
|
+
* @param {ObservableItem[]} deps - Observable dependencies — resource re-fetches when any changes
|
|
3327
|
+
* @param {{ auto?: boolean, lazy?: boolean, debounce?: number, into?: ObservableItem, apply?: Function }} config - Configuration
|
|
3328
|
+
* @param {boolean} [config.auto=false] - If true, fetch runs automatically on creation (or when deps change)
|
|
3329
|
+
* @param {boolean} [config.lazy=false] - If true with deps, does not fetch immediately — waits for first dep change
|
|
3330
|
+
* @param {number} [config.debounce=0] - Debounce delay in ms for dependency-triggered re-fetches
|
|
3331
|
+
* @param {ObservableItem} [config.into] - Observable to write results into instead of creating a new one
|
|
3332
|
+
* @param {Function} [config.apply] - Custom function to apply the result to this.data
|
|
3333
|
+
* @example
|
|
3334
|
+
* const userId = Observable(1);
|
|
3335
|
+
* const user = Observable.resource(
|
|
3336
|
+
* async (id, signal) => fetch(`/api/users/${id}`, { signal }).then(r => r.json()),
|
|
3337
|
+
* [userId],
|
|
3338
|
+
* { auto: true }
|
|
3339
|
+
* );
|
|
3340
|
+
*/
|
|
2481
3341
|
function ObservableResource(fn, deps, config) {
|
|
2482
3342
|
this.$fn = (config.debounce > 0) ? debounce(fn, config.debounce) : fn;
|
|
2483
3343
|
this.$dependencies = deps;
|
|
@@ -2485,13 +3345,13 @@ var NativeDocument = (function (exports) {
|
|
|
2485
3345
|
this.$controller = null;
|
|
2486
3346
|
this.$subscriptions = [];
|
|
2487
3347
|
|
|
2488
|
-
this.data = config.
|
|
3348
|
+
this.data = config.into ?? new ObservableItem(null);
|
|
2489
3349
|
this.error = new ObservableItem(null);
|
|
2490
3350
|
this.state = new ObservableItem(STATE.UNRESOLVED);
|
|
2491
3351
|
|
|
2492
3352
|
this.loading = ObservableItem.computed(
|
|
2493
3353
|
(state) => state === STATE.PENDING || state === STATE.REFRESHING,
|
|
2494
|
-
[this.state]
|
|
3354
|
+
[this.state],
|
|
2495
3355
|
);
|
|
2496
3356
|
|
|
2497
3357
|
if (config.auto) {
|
|
@@ -2503,6 +3363,14 @@ var NativeDocument = (function (exports) {
|
|
|
2503
3363
|
}
|
|
2504
3364
|
}
|
|
2505
3365
|
|
|
3366
|
+
ObservableResource.prototype.$applyResult = function(result) {
|
|
3367
|
+
if(this.$config.apply) {
|
|
3368
|
+
this.$config.apply(result, this.data);
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3371
|
+
this.data.set(result);
|
|
3372
|
+
};
|
|
3373
|
+
|
|
2506
3374
|
ObservableResource.prototype.$abort = function() {
|
|
2507
3375
|
if (this.$controller) {
|
|
2508
3376
|
this.$controller.abort();
|
|
@@ -2530,7 +3398,7 @@ var NativeDocument = (function (exports) {
|
|
|
2530
3398
|
if (signal.aborted) {
|
|
2531
3399
|
return;
|
|
2532
3400
|
}
|
|
2533
|
-
this
|
|
3401
|
+
this.$applyResult(result);
|
|
2534
3402
|
this.error.set(null);
|
|
2535
3403
|
this.state.set(STATE.READY);
|
|
2536
3404
|
this.$controller = null;
|
|
@@ -2555,7 +3423,7 @@ var NativeDocument = (function (exports) {
|
|
|
2555
3423
|
|
|
2556
3424
|
Promise.resolve(this.$fn(...args))
|
|
2557
3425
|
.then(result => {
|
|
2558
|
-
this
|
|
3426
|
+
this.$applyResult(result);
|
|
2559
3427
|
this.error.set(null);
|
|
2560
3428
|
this.state.set(STATE.READY);
|
|
2561
3429
|
})
|
|
@@ -2590,48 +3458,138 @@ var NativeDocument = (function (exports) {
|
|
|
2590
3458
|
}
|
|
2591
3459
|
};
|
|
2592
3460
|
|
|
3461
|
+
/**
|
|
3462
|
+
* Sets a custom function to apply fetched results to this.data.
|
|
3463
|
+
* Useful when the raw response needs transformation before storing.
|
|
3464
|
+
*
|
|
3465
|
+
* @param {(result: *, data: ObservableItem) => void} fn - Function receiving the result and the data observable
|
|
3466
|
+
* @returns {this}
|
|
3467
|
+
* @example
|
|
3468
|
+
* resource.apply((result, data) => data.set(result.items));
|
|
3469
|
+
*/
|
|
3470
|
+
ObservableResource.prototype.apply = function(fn) {
|
|
3471
|
+
this.$config.apply = fn;
|
|
3472
|
+
return this;
|
|
3473
|
+
};
|
|
3474
|
+
|
|
3475
|
+
/**
|
|
3476
|
+
* Redirects fetched results into an existing ObservableItem instead of the default internal one.
|
|
3477
|
+
* Updates both this.data reference and config.into.
|
|
3478
|
+
*
|
|
3479
|
+
* @param {ObservableItem} $observable - Target observable to write results into
|
|
3480
|
+
* @returns {this}
|
|
3481
|
+
* @example
|
|
3482
|
+
* const items = Observable([]);
|
|
3483
|
+
* resource.into(items);
|
|
3484
|
+
*/
|
|
3485
|
+
ObservableResource.prototype.into = function($observable) {
|
|
3486
|
+
this.$config.into = $observable;
|
|
3487
|
+
this.data = $observable;
|
|
3488
|
+
return this;
|
|
3489
|
+
};
|
|
3490
|
+
|
|
3491
|
+
/**
|
|
3492
|
+
* Triggers a fresh fetch (state transitions to 'pending').
|
|
3493
|
+
* Use when no prior data exists or when a full reload is needed.
|
|
3494
|
+
*
|
|
3495
|
+
* @returns {this}
|
|
3496
|
+
*/
|
|
2593
3497
|
ObservableResource.prototype.fetch = function() {
|
|
2594
3498
|
this.$run(false);
|
|
2595
3499
|
return this;
|
|
2596
3500
|
};
|
|
2597
3501
|
|
|
3502
|
+
/**
|
|
3503
|
+
* Triggers a re-fetch (state transitions to 'refreshing' if data already exists).
|
|
3504
|
+
* Use when you want to reload while keeping the previous data visible.
|
|
3505
|
+
*
|
|
3506
|
+
* @returns {this}
|
|
3507
|
+
*/
|
|
2598
3508
|
ObservableResource.prototype.refetch = function() {
|
|
2599
3509
|
this.$run(true);
|
|
2600
3510
|
return this;
|
|
2601
3511
|
};
|
|
2602
3512
|
|
|
3513
|
+
/**
|
|
3514
|
+
* Manually sets the data value and marks the state as 'ready'.
|
|
3515
|
+
* Useful for optimistic updates or seeding initial data without a network call.
|
|
3516
|
+
*
|
|
3517
|
+
* @param {*} value - New value to set on this.data
|
|
3518
|
+
* @returns {this}
|
|
3519
|
+
* @example
|
|
3520
|
+
* resource.mutate([...resource.data.val(), newItem]);
|
|
3521
|
+
*/
|
|
2603
3522
|
ObservableResource.prototype.mutate = function(value) {
|
|
2604
3523
|
this.data.set(value);
|
|
2605
3524
|
this.state.set(STATE.READY);
|
|
2606
3525
|
return this;
|
|
2607
3526
|
};
|
|
2608
3527
|
|
|
3528
|
+
/**
|
|
3529
|
+
* Cancels any pending request, unsubscribes from all dependencies, and clears subscriptions.
|
|
3530
|
+
* Call this when the component using this resource is unmounted.
|
|
3531
|
+
*
|
|
3532
|
+
* @returns {void}
|
|
3533
|
+
*/
|
|
2609
3534
|
ObservableResource.prototype.destroy = function() {
|
|
2610
3535
|
this.$abort();
|
|
2611
3536
|
this.$subscriptions.forEach(unsub => unsub());
|
|
2612
3537
|
this.$subscriptions = [];
|
|
2613
3538
|
};
|
|
2614
3539
|
|
|
3540
|
+
/**
|
|
3541
|
+
* Returns a derived observable that emits true when the state is 'ready'.
|
|
3542
|
+
*
|
|
3543
|
+
* @returns {ObservableChecker<boolean>}
|
|
3544
|
+
*/
|
|
2615
3545
|
ObservableResource.prototype.isReady = function() {
|
|
2616
3546
|
return this.state.isEqualTo(STATE.READY);
|
|
2617
3547
|
};
|
|
2618
3548
|
|
|
3549
|
+
/**
|
|
3550
|
+
* Returns a derived observable that emits true when the state is 'pending' (initial load).
|
|
3551
|
+
*
|
|
3552
|
+
* @returns {ObservableChecker<boolean>}
|
|
3553
|
+
*/
|
|
2619
3554
|
ObservableResource.prototype.isPending = function() {
|
|
2620
3555
|
return this.state.isEqualTo(STATE.PENDING);
|
|
2621
3556
|
};
|
|
2622
3557
|
|
|
3558
|
+
/**
|
|
3559
|
+
* Returns a derived observable that emits true when the state is 'refreshing' (reload with existing data).
|
|
3560
|
+
*
|
|
3561
|
+
* @returns {ObservableChecker<boolean>}
|
|
3562
|
+
*/
|
|
2623
3563
|
ObservableResource.prototype.isRefreshing = function() {
|
|
2624
3564
|
return this.state.isEqualTo(STATE.REFRESHING);
|
|
2625
3565
|
};
|
|
2626
3566
|
|
|
3567
|
+
/**
|
|
3568
|
+
* Returns a derived observable that emits true when the state is 'errored'.
|
|
3569
|
+
*
|
|
3570
|
+
* @returns {ObservableChecker<boolean>}
|
|
3571
|
+
*/
|
|
2627
3572
|
ObservableResource.prototype.isErrored = function() {
|
|
2628
3573
|
return this.state.isEqualTo(STATE.ERRORED);
|
|
2629
3574
|
};
|
|
2630
3575
|
|
|
3576
|
+
/**
|
|
3577
|
+
* Returns a derived observable that emits true when no fetch has been triggered yet.
|
|
3578
|
+
*
|
|
3579
|
+
* @returns {ObservableChecker<boolean>}
|
|
3580
|
+
*/
|
|
2631
3581
|
ObservableResource.prototype.isUnresolved = function() {
|
|
2632
3582
|
return this.state.isEqualTo(STATE.UNRESOLVED);
|
|
2633
3583
|
};
|
|
2634
3584
|
|
|
3585
|
+
/**
|
|
3586
|
+
* Registers a callback that is called every time a fetch completes successfully.
|
|
3587
|
+
*
|
|
3588
|
+
* @param {(value: *) => void} callback - Called with the fetched data value
|
|
3589
|
+
* @returns {this}
|
|
3590
|
+
* @example
|
|
3591
|
+
* resource.onSuccess((data) => console.log('Loaded:', data));
|
|
3592
|
+
*/
|
|
2635
3593
|
ObservableResource.prototype.onSuccess = function(callback) {
|
|
2636
3594
|
this.data.subscribe((value) => {
|
|
2637
3595
|
if (this.state.val() === STATE.READY) {
|
|
@@ -2641,6 +3599,14 @@ var NativeDocument = (function (exports) {
|
|
|
2641
3599
|
return this;
|
|
2642
3600
|
};
|
|
2643
3601
|
|
|
3602
|
+
/**
|
|
3603
|
+
* Registers a callback called every time a fetch fails.
|
|
3604
|
+
*
|
|
3605
|
+
* @param {(error: Error) => void} callback - Called with the error object
|
|
3606
|
+
* @returns {this}
|
|
3607
|
+
* @example
|
|
3608
|
+
* resource.onError((err) => console.error('Failed:', err.message));
|
|
3609
|
+
*/
|
|
2644
3610
|
ObservableResource.prototype.onError = function(callback) {
|
|
2645
3611
|
this.error.subscribe((err) => {
|
|
2646
3612
|
if (err !== null) {
|
|
@@ -2802,8 +3768,8 @@ var NativeDocument = (function (exports) {
|
|
|
2802
3768
|
}
|
|
2803
3769
|
|
|
2804
3770
|
dependencies.forEach(dependency => {
|
|
2805
|
-
if(
|
|
2806
|
-
dependency
|
|
3771
|
+
if(dependency.__$isObservableObject) {
|
|
3772
|
+
dependency.observables().forEach((observable) => {
|
|
2807
3773
|
observable.subscribe(updatedValue);
|
|
2808
3774
|
});
|
|
2809
3775
|
return;
|
|
@@ -2817,7 +3783,7 @@ var NativeDocument = (function (exports) {
|
|
|
2817
3783
|
|
|
2818
3784
|
|
|
2819
3785
|
Observable.init = function(initialValue, configs = null) {
|
|
2820
|
-
return new ObservableObject(initialValue, configs)
|
|
3786
|
+
return new ObservableObject(initialValue, configs);
|
|
2821
3787
|
};
|
|
2822
3788
|
|
|
2823
3789
|
/**
|
|
@@ -2846,9 +3812,6 @@ var NativeDocument = (function (exports) {
|
|
|
2846
3812
|
if(data?.__$Observable) {
|
|
2847
3813
|
return data.val();
|
|
2848
3814
|
}
|
|
2849
|
-
if(Validator.isProxy(data)) {
|
|
2850
|
-
return data.$value;
|
|
2851
|
-
}
|
|
2852
3815
|
return data;
|
|
2853
3816
|
};
|
|
2854
3817
|
|
|
@@ -2869,9 +3832,12 @@ var NativeDocument = (function (exports) {
|
|
|
2869
3832
|
};
|
|
2870
3833
|
|
|
2871
3834
|
/**
|
|
3835
|
+
* Applies a reactive class map to an HTMLElement.
|
|
3836
|
+
* Each key is a CSS class name; each value is a boolean or ObservableItem<boolean>.
|
|
3837
|
+
* If the value is an ObservableChecker emitting a string, toggles the class name dynamically.
|
|
2872
3838
|
*
|
|
2873
|
-
* @param {HTMLElement} element
|
|
2874
|
-
* @param {
|
|
3839
|
+
* @param {HTMLElement} element - Target element
|
|
3840
|
+
* @param {Record<string, boolean|ObservableItem<boolean>|ObservableChecker<boolean|string>>} data - Class map
|
|
2875
3841
|
*/
|
|
2876
3842
|
const bindClassAttribute = (element, data) => {
|
|
2877
3843
|
for(const className in data) {
|
|
@@ -2879,7 +3845,7 @@ var NativeDocument = (function (exports) {
|
|
|
2879
3845
|
if(value.__$Observable) {
|
|
2880
3846
|
if(value.__$isObservableChecker) {
|
|
2881
3847
|
let lastClass = value.val();
|
|
2882
|
-
if(typeof lastClass ===
|
|
3848
|
+
if(typeof lastClass === 'string') {
|
|
2883
3849
|
element.classes.toggle(lastClass, true);
|
|
2884
3850
|
value.subscribe((currentValue) => {
|
|
2885
3851
|
element.classes.remove(lastClass);
|
|
@@ -2902,9 +3868,13 @@ var NativeDocument = (function (exports) {
|
|
|
2902
3868
|
};
|
|
2903
3869
|
|
|
2904
3870
|
/**
|
|
3871
|
+
* Applies a reactive style map to an HTMLElement.
|
|
3872
|
+
* Each key is a CSS property name (camelCase or CSS custom property `--var`);
|
|
3873
|
+
* each value is a string or ObservableItem<string>.
|
|
3874
|
+
* CSS custom properties are set via element.style.setProperty().
|
|
2905
3875
|
*
|
|
2906
|
-
* @param {HTMLElement} element
|
|
2907
|
-
* @param {
|
|
3876
|
+
* @param {HTMLElement} element - Target element
|
|
3877
|
+
* @param {Record<string, string|ObservableItem<string>>} data - Style map
|
|
2908
3878
|
*/
|
|
2909
3879
|
const bindStyleAttribute = (element, data) => {
|
|
2910
3880
|
for(const styleName in data) {
|
|
@@ -2952,24 +3922,27 @@ var NativeDocument = (function (exports) {
|
|
|
2952
3922
|
const bindBooleanAttribute = (element, attributeName, value) => {
|
|
2953
3923
|
const isObservable = value.__$isObservable;
|
|
2954
3924
|
const defaultValue = isObservable? value.val() : value;
|
|
3925
|
+
|
|
3926
|
+
const attributeRealName = BOOL_ATTRIBUTES_NAME[attributeName];
|
|
3927
|
+
|
|
2955
3928
|
if(Validator.isBoolean(defaultValue)) {
|
|
2956
|
-
element[
|
|
3929
|
+
element[attributeRealName] = defaultValue;
|
|
2957
3930
|
}
|
|
2958
3931
|
else {
|
|
2959
|
-
element[
|
|
3932
|
+
element[attributeRealName] = defaultValue === element.value;
|
|
2960
3933
|
}
|
|
2961
3934
|
if(isObservable) {
|
|
2962
3935
|
if(attributeName === 'checked') {
|
|
2963
3936
|
if(typeof defaultValue === 'boolean') {
|
|
2964
|
-
element.addEventListener('input', () => value.set(element[
|
|
3937
|
+
element.addEventListener('input', () => value.set(element[attributeRealName]));
|
|
2965
3938
|
}
|
|
2966
3939
|
else {
|
|
2967
3940
|
element.addEventListener('input', () => value.set(element.value));
|
|
2968
3941
|
}
|
|
2969
|
-
value.subscribe((newValue) => element[
|
|
3942
|
+
value.subscribe((newValue) => element[attributeRealName] = newValue);
|
|
2970
3943
|
return;
|
|
2971
3944
|
}
|
|
2972
|
-
value.subscribe((newValue) => element[
|
|
3945
|
+
value.subscribe((newValue) => element[attributeRealName] = (newValue === element.value));
|
|
2973
3946
|
}
|
|
2974
3947
|
};
|
|
2975
3948
|
|
|
@@ -3005,7 +3978,7 @@ var NativeDocument = (function (exports) {
|
|
|
3005
3978
|
|
|
3006
3979
|
for(const originalAttributeName in attributes) {
|
|
3007
3980
|
const attributeName = originalAttributeName.toLowerCase();
|
|
3008
|
-
|
|
3981
|
+
const value = attributes[originalAttributeName];
|
|
3009
3982
|
if(value == null) {
|
|
3010
3983
|
continue;
|
|
3011
3984
|
}
|
|
@@ -3075,7 +4048,7 @@ var NativeDocument = (function (exports) {
|
|
|
3075
4048
|
* @returns {Text}
|
|
3076
4049
|
*/
|
|
3077
4050
|
createStaticTextNode: (parent, value) => {
|
|
3078
|
-
|
|
4051
|
+
const text = ElementCreator.createTextNode();
|
|
3079
4052
|
text.nodeValue = value;
|
|
3080
4053
|
parent && parent.appendChild(text);
|
|
3081
4054
|
return text;
|
|
@@ -3107,7 +4080,7 @@ var NativeDocument = (function (exports) {
|
|
|
3107
4080
|
{
|
|
3108
4081
|
PluginsManager$1.emit('BeforeProcessChildren', parent);
|
|
3109
4082
|
}
|
|
3110
|
-
|
|
4083
|
+
const child = ElementCreator.getChild(children);
|
|
3111
4084
|
if(child) {
|
|
3112
4085
|
parent.appendChild(child);
|
|
3113
4086
|
}
|
|
@@ -3154,6 +4127,16 @@ var NativeDocument = (function (exports) {
|
|
|
3154
4127
|
processStyleAttribute: bindStyleAttribute,
|
|
3155
4128
|
};
|
|
3156
4129
|
|
|
4130
|
+
/**
|
|
4131
|
+
* Creates an augmented DocumentFragment with comment sentinel nodes and a MutationObserver
|
|
4132
|
+
* that fires when the fragment is inserted into the live DOM.
|
|
4133
|
+
* Used as the base for Anchor — not intended for direct use in application code.
|
|
4134
|
+
*
|
|
4135
|
+
* @internal
|
|
4136
|
+
* @constructor
|
|
4137
|
+
* @param {string} name - Debug label used in comment node text content
|
|
4138
|
+
* @returns {AnchorWithSentinel} Augmented DocumentFragment instance
|
|
4139
|
+
*/
|
|
3157
4140
|
function AnchorWithSentinel(name) {
|
|
3158
4141
|
const instance = Reflect.construct(DocumentFragment, [], AnchorWithSentinel);
|
|
3159
4142
|
const sentinel = document.createComment((name || '') + ' Anchor Sentinel');
|
|
@@ -3184,11 +4167,24 @@ var NativeDocument = (function (exports) {
|
|
|
3184
4167
|
AnchorWithSentinel.prototype = Object.create(DocumentFragment.prototype);
|
|
3185
4168
|
AnchorWithSentinel.prototype.constructor = AnchorWithSentinel;
|
|
3186
4169
|
|
|
4170
|
+
/**
|
|
4171
|
+
* Registers a callback to call every time the sentinel is connected to the live DOM.
|
|
4172
|
+
* The callback receives the parent node as its argument.
|
|
4173
|
+
*
|
|
4174
|
+
* @param {(parent: Node) => void} callback - Called each time the fragment is inserted
|
|
4175
|
+
* @returns {this}
|
|
4176
|
+
*/
|
|
3187
4177
|
AnchorWithSentinel.prototype.onConnected = function(callback) {
|
|
3188
4178
|
this.$events.connected = callback;
|
|
3189
4179
|
return this;
|
|
3190
4180
|
};
|
|
3191
4181
|
|
|
4182
|
+
/**
|
|
4183
|
+
* Registers a callback to call the first time the sentinel is connected to the live DOM.
|
|
4184
|
+
* After the first connection, the MutationObserver is disconnected automatically.
|
|
4185
|
+
*
|
|
4186
|
+
* @param {(parent: Node) => void} callback - Called once on first insertion
|
|
4187
|
+
*/
|
|
3192
4188
|
AnchorWithSentinel.prototype.onConnectedOnce = function(callback) {
|
|
3193
4189
|
this.$events.connected = (parent) => {
|
|
3194
4190
|
callback(parent);
|
|
@@ -3260,6 +4256,15 @@ var NativeDocument = (function (exports) {
|
|
|
3260
4256
|
};
|
|
3261
4257
|
}
|
|
3262
4258
|
|
|
4259
|
+
/**
|
|
4260
|
+
* Creates an anchor fragment — a managed DocumentFragment delimited by comment sentinels.
|
|
4261
|
+
* Used internally by ForEach, ShowIf, Switch, Match and other control-flow directives
|
|
4262
|
+
* to manage dynamic DOM regions without a real container element.
|
|
4263
|
+
*
|
|
4264
|
+
* @param {string} name - Debug name for the anchor (visible as HTML comments in the DOM)
|
|
4265
|
+
* @param {boolean} [isUniqueChild=false] - If true, optimises rendering when this anchor is the only child of its parent
|
|
4266
|
+
* @returns {AnchorDocumentFragment} An augmented DocumentFragment with anchor management methods
|
|
4267
|
+
*/
|
|
3263
4268
|
function Anchor(name, isUniqueChild = false) {
|
|
3264
4269
|
const anchorFragment = new AnchorWithSentinel(name);
|
|
3265
4270
|
|
|
@@ -3342,7 +4347,7 @@ var NativeDocument = (function (exports) {
|
|
|
3342
4347
|
parentNode.nativeInsertBefore(child, anchorStart);
|
|
3343
4348
|
return;
|
|
3344
4349
|
}
|
|
3345
|
-
parentNode.insertBefore(child, anchorStart);
|
|
4350
|
+
parentNode.insertBefore(child, anchorStart.nextSibling);
|
|
3346
4351
|
};
|
|
3347
4352
|
|
|
3348
4353
|
anchorFragment.removeChildren = function() {
|
|
@@ -3458,7 +4463,7 @@ var NativeDocument = (function (exports) {
|
|
|
3458
4463
|
|
|
3459
4464
|
class ArgTypesError extends Error {
|
|
3460
4465
|
constructor(message, errors) {
|
|
3461
|
-
super(`${message}\n\n${errors.join(
|
|
4466
|
+
super(`${message}\n\n${errors.join('\n')}\n\n`);
|
|
3462
4467
|
}
|
|
3463
4468
|
}
|
|
3464
4469
|
|
|
@@ -3503,8 +4508,8 @@ var NativeDocument = (function (exports) {
|
|
|
3503
4508
|
name,
|
|
3504
4509
|
type: 'oneOf',
|
|
3505
4510
|
types: argTypes,
|
|
3506
|
-
validate: (v) => argTypes.some(type => type.validate(v))
|
|
3507
|
-
})
|
|
4511
|
+
validate: (v) => argTypes.some(type => type.validate(v)),
|
|
4512
|
+
}),
|
|
3508
4513
|
};
|
|
3509
4514
|
|
|
3510
4515
|
|
|
@@ -3544,7 +4549,7 @@ var NativeDocument = (function (exports) {
|
|
|
3544
4549
|
});
|
|
3545
4550
|
|
|
3546
4551
|
if (errors.length > 0) {
|
|
3547
|
-
throw new ArgTypesError(
|
|
4552
|
+
throw new ArgTypesError('Argument validation failed', errors);
|
|
3548
4553
|
}
|
|
3549
4554
|
};
|
|
3550
4555
|
|
|
@@ -3572,7 +4577,7 @@ var NativeDocument = (function (exports) {
|
|
|
3572
4577
|
return { props, children };
|
|
3573
4578
|
}
|
|
3574
4579
|
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 }
|
|
4580
|
+
return { props: children, children: props };
|
|
3576
4581
|
}
|
|
3577
4582
|
return { props, children };
|
|
3578
4583
|
};
|
|
@@ -3695,7 +4700,7 @@ var NativeDocument = (function (exports) {
|
|
|
3695
4700
|
DocumentObserver.unmountedSupposedSize--;
|
|
3696
4701
|
}
|
|
3697
4702
|
data = null;
|
|
3698
|
-
}
|
|
4703
|
+
},
|
|
3699
4704
|
};
|
|
3700
4705
|
|
|
3701
4706
|
const addListener = (type, callback) => {
|
|
@@ -3753,11 +4758,18 @@ var NativeDocument = (function (exports) {
|
|
|
3753
4758
|
|
|
3754
4759
|
off: (type, callback) => {
|
|
3755
4760
|
removeListener(type, callback);
|
|
3756
|
-
}
|
|
4761
|
+
},
|
|
3757
4762
|
};
|
|
3758
|
-
}
|
|
4763
|
+
},
|
|
3759
4764
|
};
|
|
3760
4765
|
|
|
4766
|
+
/**
|
|
4767
|
+
* Wraps an HTMLElement with NativeDocument's reactivity and lifecycle API.
|
|
4768
|
+
* Created automatically by HtmlElementWrapper — not intended to be instantiated directly.
|
|
4769
|
+
*
|
|
4770
|
+
* @constructor
|
|
4771
|
+
* @param {HTMLElement} element - The underlying HTML element to wrap
|
|
4772
|
+
*/
|
|
3761
4773
|
function NDElement(element) {
|
|
3762
4774
|
this.$element = element;
|
|
3763
4775
|
this.$attachements = null;
|
|
@@ -3766,10 +4778,18 @@ var NativeDocument = (function (exports) {
|
|
|
3766
4778
|
}
|
|
3767
4779
|
}
|
|
3768
4780
|
|
|
4781
|
+
|
|
3769
4782
|
NDElement.prototype.__$isNDElement = true;
|
|
3770
4783
|
|
|
3771
4784
|
NDElement.$getChild = (el) => el;
|
|
3772
4785
|
|
|
4786
|
+
/**
|
|
4787
|
+
* Appends a child element to an internal DocumentFragment (ghost DOM),
|
|
4788
|
+
* keeping it detached from the main document until explicitly mounted.
|
|
4789
|
+
*
|
|
4790
|
+
* @param {HTMLElement|DocumentFragment|NDElement} element - Element to append
|
|
4791
|
+
* @returns {this}
|
|
4792
|
+
*/
|
|
3773
4793
|
NDElement.prototype.ghostDom = function(element) {
|
|
3774
4794
|
if(!this.$attachements) {
|
|
3775
4795
|
this.$attachements = document.createDocumentFragment();
|
|
@@ -3778,15 +4798,47 @@ var NativeDocument = (function (exports) {
|
|
|
3778
4798
|
return this;
|
|
3779
4799
|
};
|
|
3780
4800
|
|
|
4801
|
+
/**
|
|
4802
|
+
* Returns the underlying HTMLElement. Used internally for type coercion.
|
|
4803
|
+
*
|
|
4804
|
+
* @returns {HTMLElement}
|
|
4805
|
+
*/
|
|
3781
4806
|
NDElement.prototype.valueOf = function() {
|
|
3782
4807
|
return this.$element;
|
|
3783
4808
|
};
|
|
3784
4809
|
|
|
4810
|
+
/**
|
|
4811
|
+
* Stores the underlying HTMLElement in target[name].
|
|
4812
|
+
* Use this to keep a reference to the raw DOM node.
|
|
4813
|
+
*
|
|
4814
|
+
* @param {Record<string, any>} target - Object to store the reference in
|
|
4815
|
+
* @param {string} name - Property name to assign on the target object
|
|
4816
|
+
* @returns {this}
|
|
4817
|
+
* @example
|
|
4818
|
+
* const refs = {};
|
|
4819
|
+
* Input({ type: 'text' }).nd.ref(refs, 'emailInput');
|
|
4820
|
+
* refs.emailInput.focus();
|
|
4821
|
+
*/
|
|
3785
4822
|
NDElement.prototype.ref = function(target, name) {
|
|
3786
4823
|
target[name] = this.$element;
|
|
3787
4824
|
return this;
|
|
3788
4825
|
};
|
|
3789
4826
|
|
|
4827
|
+
/**
|
|
4828
|
+
* Stores the NDElement instance itself in target[name].
|
|
4829
|
+
* Use this to expose a component's public API to a parent (via .with()).
|
|
4830
|
+
*
|
|
4831
|
+
* @param {Record<string, any>} target - Object to store the reference in
|
|
4832
|
+
* @param {string} name - Property name to assign on the target object
|
|
4833
|
+
* @returns {this}
|
|
4834
|
+
* @example
|
|
4835
|
+
* const refs = {};
|
|
4836
|
+
* Counter()
|
|
4837
|
+
* .nd.with({ increment() { count.$value++; return this; } })
|
|
4838
|
+
* .refSelf(refs, 'counter');
|
|
4839
|
+
*
|
|
4840
|
+
* refs.counter.increment();
|
|
4841
|
+
*/
|
|
3790
4842
|
NDElement.prototype.refSelf = function(target, name) {
|
|
3791
4843
|
target[name] = this;
|
|
3792
4844
|
// TODO: @DIM to check
|
|
@@ -3794,6 +4846,12 @@ var NativeDocument = (function (exports) {
|
|
|
3794
4846
|
return this;
|
|
3795
4847
|
};
|
|
3796
4848
|
|
|
4849
|
+
/**
|
|
4850
|
+
* Calls .nd.remove() on all child NDElements before removing this element.
|
|
4851
|
+
* Used to propagate lifecycle cleanup through the component tree.
|
|
4852
|
+
*
|
|
4853
|
+
* @returns {this}
|
|
4854
|
+
*/
|
|
3797
4855
|
NDElement.prototype.unmountChildren = function() {
|
|
3798
4856
|
let element = this.$element;
|
|
3799
4857
|
for(let i = 0, length = element.children.length; i < length; i++) {
|
|
@@ -3807,6 +4865,12 @@ var NativeDocument = (function (exports) {
|
|
|
3807
4865
|
return this;
|
|
3808
4866
|
};
|
|
3809
4867
|
|
|
4868
|
+
/**
|
|
4869
|
+
* Removes the element from the DOM and cleans up its lifecycle observers.
|
|
4870
|
+
* Also calls unmountChildren() recursively.
|
|
4871
|
+
*
|
|
4872
|
+
* @returns {this}
|
|
4873
|
+
*/
|
|
3810
4874
|
NDElement.prototype.remove = function() {
|
|
3811
4875
|
let element = this.$element;
|
|
3812
4876
|
element.nd.unmountChildren();
|
|
@@ -3819,6 +4883,19 @@ var NativeDocument = (function (exports) {
|
|
|
3819
4883
|
};
|
|
3820
4884
|
|
|
3821
4885
|
const $lifeCycleObservers = new WeakMap();
|
|
4886
|
+
|
|
4887
|
+
/**
|
|
4888
|
+
* Registers mounted and/or unmounted lifecycle callbacks for this element.
|
|
4889
|
+
* Uses MutationObserver internally to detect DOM insertion and removal.
|
|
4890
|
+
*
|
|
4891
|
+
* @param {{ mounted?: (el: HTMLElement) => void, unmounted?: (el: HTMLElement) => boolean|void }} states - Lifecycle hooks
|
|
4892
|
+
* @returns {this}
|
|
4893
|
+
* @example
|
|
4894
|
+
* Div({}).nd.lifecycle({
|
|
4895
|
+
* mounted: (el) => console.log('mounted', el),
|
|
4896
|
+
* unmounted: (el) => console.log('unmounted', el),
|
|
4897
|
+
* });
|
|
4898
|
+
*/
|
|
3822
4899
|
NDElement.prototype.lifecycle = function(states) {
|
|
3823
4900
|
const el = this.$element;
|
|
3824
4901
|
if (!$lifeCycleObservers.has(el)) {
|
|
@@ -3837,31 +4914,68 @@ var NativeDocument = (function (exports) {
|
|
|
3837
4914
|
return this;
|
|
3838
4915
|
};
|
|
3839
4916
|
|
|
4917
|
+
/**
|
|
4918
|
+
* Registers an unmounted callback that cleans up all beforeUnmount handlers
|
|
4919
|
+
* and aborts any pending async operations on this element and its children.
|
|
4920
|
+
*
|
|
4921
|
+
* @returns {this}
|
|
4922
|
+
*/
|
|
3840
4923
|
NDElement.prototype.destroyOnUnmount = function() {
|
|
3841
|
-
this.unmounted(() =>
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
child.__$controller?.abort();
|
|
3845
|
-
child.__$controller = null;
|
|
3846
|
-
$lifeCycleObservers.delete(child);
|
|
3847
|
-
});
|
|
4924
|
+
this.unmounted(() => this.destroy());
|
|
4925
|
+
return this;
|
|
4926
|
+
};
|
|
3848
4927
|
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
4928
|
+
/**
|
|
4929
|
+
* aborts any pending async operations on this element and its children.
|
|
4930
|
+
*
|
|
4931
|
+
* @returns {this}
|
|
4932
|
+
*/
|
|
4933
|
+
NDElement.prototype.destroy = function() {
|
|
4934
|
+
this.$element?.querySelectorAll('[data--nd-before-unmount]').forEach(child => {
|
|
4935
|
+
child.remove();
|
|
4936
|
+
child.__$controller?.abort();
|
|
4937
|
+
child.__$controller = null;
|
|
4938
|
+
$lifeCycleObservers.delete(child);
|
|
3853
4939
|
});
|
|
3854
|
-
|
|
4940
|
+
|
|
4941
|
+
this.$element.__$controller?.abort();
|
|
4942
|
+
this.$element.__$controller = null;
|
|
4943
|
+
$lifeCycleObservers.delete(this.$element);
|
|
4944
|
+
this.$element = null;
|
|
3855
4945
|
};
|
|
3856
4946
|
|
|
4947
|
+
/**
|
|
4948
|
+
* Shorthand for lifecycle({ mounted: callback }).
|
|
4949
|
+
*
|
|
4950
|
+
* @param {(el: HTMLElement) => void} callback - Called when element is inserted into the DOM
|
|
4951
|
+
* @returns {this}
|
|
4952
|
+
*/
|
|
3857
4953
|
NDElement.prototype.mounted = function(callback) {
|
|
3858
4954
|
return this.lifecycle({ mounted: callback });
|
|
3859
4955
|
};
|
|
3860
4956
|
|
|
4957
|
+
/**
|
|
4958
|
+
* Shorthand for lifecycle({ unmounted: callback }).
|
|
4959
|
+
*
|
|
4960
|
+
* @param {(el: HTMLElement) => boolean|void} callback - Called when element is removed from the DOM
|
|
4961
|
+
* @returns {this}
|
|
4962
|
+
*/
|
|
3861
4963
|
NDElement.prototype.unmounted = function(callback) {
|
|
3862
4964
|
return this.lifecycle({ unmounted: callback });
|
|
3863
4965
|
};
|
|
3864
4966
|
|
|
4967
|
+
/**
|
|
4968
|
+
* Registers an async callback to run before this element is removed from the DOM.
|
|
4969
|
+
* The element's .remove() is delayed until all beforeUnmount callbacks resolve.
|
|
4970
|
+
*
|
|
4971
|
+
* @param {string} id - Unique identifier for this callback (allows overwriting)
|
|
4972
|
+
* @param {(this: NDElement, el: HTMLElement) => void|Promise<void>} callback - Async-compatible callback
|
|
4973
|
+
* @returns {this}
|
|
4974
|
+
* @example
|
|
4975
|
+
* Div({}).nd.beforeUnmount('fade-out', async (el) => {
|
|
4976
|
+
* await el.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 300 }).finished;
|
|
4977
|
+
* });
|
|
4978
|
+
*/
|
|
3865
4979
|
NDElement.prototype.beforeUnmount = function(id, callback) {
|
|
3866
4980
|
const el = this.$element;
|
|
3867
4981
|
|
|
@@ -3894,18 +5008,31 @@ var NativeDocument = (function (exports) {
|
|
|
3894
5008
|
return this;
|
|
3895
5009
|
};
|
|
3896
5010
|
|
|
5011
|
+
/**
|
|
5012
|
+
* Returns the underlying HTMLElement.
|
|
5013
|
+
* Alias: .node()
|
|
5014
|
+
*
|
|
5015
|
+
* @returns {HTMLElement}
|
|
5016
|
+
*/
|
|
3897
5017
|
NDElement.prototype.htmlElement = function() {
|
|
3898
5018
|
return this.$element;
|
|
3899
5019
|
};
|
|
3900
5020
|
|
|
3901
5021
|
NDElement.prototype.node = NDElement.prototype.htmlElement;
|
|
3902
5022
|
|
|
5023
|
+
/**
|
|
5024
|
+
* Attaches a Shadow DOM to this element, redirecting all child appends to the shadow root.
|
|
5025
|
+
*
|
|
5026
|
+
* @param {'open'|'closed'} mode - Shadow DOM encapsulation mode
|
|
5027
|
+
* @param {string|null} [style=null] - Optional CSS string to inject into the shadow root
|
|
5028
|
+
* @returns {this}
|
|
5029
|
+
*/
|
|
3903
5030
|
NDElement.prototype.shadow = function(mode, style = null) {
|
|
3904
5031
|
const $element = this.$element;
|
|
3905
5032
|
const children = Array.from($element.childNodes);
|
|
3906
5033
|
const shadowRoot = $element.attachShadow({ mode });
|
|
3907
5034
|
if(style) {
|
|
3908
|
-
const styleNode = document.createElement(
|
|
5035
|
+
const styleNode = document.createElement('style');
|
|
3909
5036
|
styleNode.textContent = style;
|
|
3910
5037
|
shadowRoot.appendChild(styleNode);
|
|
3911
5038
|
}
|
|
@@ -3916,10 +5043,22 @@ var NativeDocument = (function (exports) {
|
|
|
3916
5043
|
return this;
|
|
3917
5044
|
};
|
|
3918
5045
|
|
|
5046
|
+
/**
|
|
5047
|
+
* Shorthand for .shadow('open', style).
|
|
5048
|
+
*
|
|
5049
|
+
* @param {string|null} [style=null] - Optional CSS string to inject into the shadow root
|
|
5050
|
+
* @returns {this}
|
|
5051
|
+
*/
|
|
3919
5052
|
NDElement.prototype.openShadow = function(style = null) {
|
|
3920
5053
|
return this.shadow('open', style);
|
|
3921
5054
|
};
|
|
3922
5055
|
|
|
5056
|
+
/**
|
|
5057
|
+
* Shorthand for .shadow('closed', style).
|
|
5058
|
+
*
|
|
5059
|
+
* @param {string|null} [style=null] - Optional CSS string to inject into the shadow root
|
|
5060
|
+
* @returns {this}
|
|
5061
|
+
*/
|
|
3923
5062
|
NDElement.prototype.closedShadow = function(style = null) {
|
|
3924
5063
|
return this.shadow('closed', style);
|
|
3925
5064
|
};
|
|
@@ -3968,7 +5107,16 @@ var NativeDocument = (function (exports) {
|
|
|
3968
5107
|
return this;
|
|
3969
5108
|
};
|
|
3970
5109
|
|
|
3971
|
-
|
|
5110
|
+
/**
|
|
5111
|
+
* Sets a single attribute on the element.
|
|
5112
|
+
* If value is an ObservableItem, the attribute is updated reactively.
|
|
5113
|
+
*
|
|
5114
|
+
* @param {string} name - Attribute name
|
|
5115
|
+
* @param {string|ObservableItem<string>} value - Attribute value, static or reactive
|
|
5116
|
+
* @returns {this}
|
|
5117
|
+
* @example
|
|
5118
|
+
* Input({}).nd.attr('placeholder', label); // reactive placeholder
|
|
5119
|
+
*/
|
|
3972
5120
|
NDElement.prototype.attr = function(name, value) {
|
|
3973
5121
|
if(value?.__$Observable) {
|
|
3974
5122
|
bindAttributeWithObservable(this.$element, name, value);
|
|
@@ -3978,16 +5126,37 @@ var NativeDocument = (function (exports) {
|
|
|
3978
5126
|
return this;
|
|
3979
5127
|
};
|
|
3980
5128
|
|
|
5129
|
+
/**
|
|
5130
|
+
* Applies a batch of attributes to the element via AttributesWrapper.
|
|
5131
|
+
* Supports reactive values, class maps, and style maps.
|
|
5132
|
+
*
|
|
5133
|
+
* @param {Object} attrs - Attributes object (same format as HtmlElementWrapper props)
|
|
5134
|
+
* @returns {this}
|
|
5135
|
+
*/
|
|
3981
5136
|
NDElement.prototype.attrs = function(attrs) {
|
|
3982
5137
|
AttributesWrapper(this.$element, attrs);
|
|
3983
5138
|
return this;
|
|
3984
5139
|
};
|
|
3985
5140
|
|
|
5141
|
+
/**
|
|
5142
|
+
* Applies a reactive class map to the element.
|
|
5143
|
+
* Each key is a class name; each value is a boolean or ObservableItem<boolean>.
|
|
5144
|
+
*
|
|
5145
|
+
* @param {Record<string, boolean|ObservableItem<boolean>>} classes - Class map
|
|
5146
|
+
* @returns {this}
|
|
5147
|
+
*/
|
|
3986
5148
|
NDElement.prototype.class = function(classes) {
|
|
3987
5149
|
bindClassAttribute(this.$element, classes);
|
|
3988
5150
|
return this;
|
|
3989
5151
|
};
|
|
3990
5152
|
|
|
5153
|
+
/**
|
|
5154
|
+
* Applies a reactive style map to the element.
|
|
5155
|
+
* Each key is a CSS property; each value is a string or ObservableItem<string>.
|
|
5156
|
+
*
|
|
5157
|
+
* @param {Record<string, string|ObservableItem<string>>} style - Style map
|
|
5158
|
+
* @returns {this}
|
|
5159
|
+
*/
|
|
3991
5160
|
NDElement.prototype.style = function(style) {
|
|
3992
5161
|
bindStyleAttribute(this.$element, style);
|
|
3993
5162
|
return this;
|
|
@@ -4023,7 +5192,7 @@ var NativeDocument = (function (exports) {
|
|
|
4023
5192
|
const protectedMethods = new Set([
|
|
4024
5193
|
'constructor', 'valueOf', '$element', '$observer',
|
|
4025
5194
|
'ref', 'remove', 'cleanup', 'with', 'extend', 'attach',
|
|
4026
|
-
'lifecycle', 'mounted', 'unmounted', 'unmountChildren'
|
|
5195
|
+
'lifecycle', 'mounted', 'unmounted', 'unmountChildren',
|
|
4027
5196
|
]);
|
|
4028
5197
|
|
|
4029
5198
|
for (const name in methods) {
|
|
@@ -4056,160 +5225,302 @@ var NativeDocument = (function (exports) {
|
|
|
4056
5225
|
return NDElement;
|
|
4057
5226
|
};
|
|
4058
5227
|
|
|
5228
|
+
|
|
5229
|
+
/**
|
|
5230
|
+
* The global sanitizer function used by nd.html() when sanitize option is enabled.
|
|
5231
|
+
* Must be set via NDElement.setSanitizer() before using {sanitize: true}.
|
|
5232
|
+
*
|
|
5233
|
+
* @type {Function|null}
|
|
5234
|
+
*/
|
|
5235
|
+
NDElement.$sanitizer = null;
|
|
5236
|
+
|
|
5237
|
+
/**
|
|
5238
|
+
* Configures the global sanitizer for nd.html().
|
|
5239
|
+
* The sanitizer function receives the HTML string and an optional config object.
|
|
5240
|
+
* Designed to be decoupled from any specific sanitizer library.
|
|
5241
|
+
*
|
|
5242
|
+
* @param {Function} sanitizerFn - Sanitizer function (html, config) => string
|
|
5243
|
+
* @returns {typeof NDElement}
|
|
5244
|
+
* @throws {NativeDocumentError} If sanitizerFn is not a function
|
|
5245
|
+
* @example
|
|
5246
|
+
* import DOMPurify from 'dompurify';
|
|
5247
|
+
* NDElement.setSanitizer((html, config) => DOMPurify.sanitize(html, config));
|
|
5248
|
+
*/
|
|
5249
|
+
NDElement.setSanitizer = function(sanitizerFn) {
|
|
5250
|
+
if(typeof sanitizerFn !== 'function') {
|
|
5251
|
+
throw new NativeDocumentError('NDElement.setSanitizer() expects a function');
|
|
5252
|
+
}
|
|
5253
|
+
NDElement.$sanitizer = sanitizerFn;
|
|
5254
|
+
return NDElement;
|
|
5255
|
+
};
|
|
5256
|
+
|
|
5257
|
+
/**
|
|
5258
|
+
* Sets the inner HTML of the element.
|
|
5259
|
+
* Requires either {unsafe: true} to bypass security checks,
|
|
5260
|
+
* or {sanitize: true|Object|Function} to sanitize the content.
|
|
5261
|
+
* Supports Observable values for reactive HTML updates.
|
|
5262
|
+
*
|
|
5263
|
+
* @param {string|ObservableItem} content - HTML string or Observable<string>
|
|
5264
|
+
* @param {Object} [options={}]
|
|
5265
|
+
* @param {boolean} [options.unsafe=false] - Bypass security check. Use only with trusted content.
|
|
5266
|
+
* @param {boolean|Object|Function} [options.sanitize=false] - Sanitize strategy:
|
|
5267
|
+
* - true: use global sanitizer with default config
|
|
5268
|
+
* - Object: use global sanitizer with custom config
|
|
5269
|
+
* - Function: use this function directly as sanitizer (html) => string
|
|
5270
|
+
* @returns {this}
|
|
5271
|
+
* @throws {NativeDocumentError} If sanitize is true/Object but no global sanitizer is configured
|
|
5272
|
+
* @example
|
|
5273
|
+
* // Unsafe — trusted content only
|
|
5274
|
+
* el.nd.html('<strong>Hello</strong>', {unsafe: true})
|
|
5275
|
+
*
|
|
5276
|
+
* // Global sanitizer with default config
|
|
5277
|
+
* el.nd.html($userContent, {sanitize: true})
|
|
5278
|
+
*
|
|
5279
|
+
* // Global sanitizer with custom config
|
|
5280
|
+
* el.nd.html($userContent, {sanitize: {
|
|
5281
|
+
* ALLOWED_TAGS: ['b', 'i', 'strong', 'a'],
|
|
5282
|
+
* ALLOWED_ATTR: ['href'],
|
|
5283
|
+
* }})
|
|
5284
|
+
*
|
|
5285
|
+
* // Custom sanitizer function for this specific case
|
|
5286
|
+
* el.nd.html($userContent, {sanitize: (html) => myCustomSanitizer(html)})
|
|
5287
|
+
*
|
|
5288
|
+
* // Reactive with sanitize
|
|
5289
|
+
* el.nd.html($content, {sanitize: true})
|
|
5290
|
+
*/
|
|
5291
|
+
NDElement.prototype.html = function(content, {unsafe = false, sanitize = false} = {}) {
|
|
5292
|
+
const $element = this.$element;
|
|
5293
|
+
const apply = (value) => {
|
|
5294
|
+
if(sanitize) {
|
|
5295
|
+
if(typeof sanitize === 'function') {
|
|
5296
|
+
$element.innerHTML = sanitize(value);
|
|
5297
|
+
return;
|
|
5298
|
+
}
|
|
5299
|
+
|
|
5300
|
+
if(!NDElement.$sanitizer) {
|
|
5301
|
+
throw new NativeDocumentError('nd.html() — no sanitizer configured. Call NDElement.setSanitizer() first.');
|
|
5302
|
+
}
|
|
5303
|
+
const config = sanitize === true ? {} : sanitize;
|
|
5304
|
+
$element.innerHTML = NDElement.$sanitizer(value, config);
|
|
5305
|
+
return;
|
|
5306
|
+
}
|
|
5307
|
+
|
|
5308
|
+
if(!unsafe) {
|
|
5309
|
+
console.warn('nd.html() — use {unsafe: true} or {sanitize: true|Object|Function}');
|
|
5310
|
+
return;
|
|
5311
|
+
}
|
|
5312
|
+
|
|
5313
|
+
$element.innerHTML = value;
|
|
5314
|
+
};
|
|
5315
|
+
|
|
5316
|
+
if(content?.__$Observable) {
|
|
5317
|
+
content.subscribe(apply);
|
|
5318
|
+
apply(content.val());
|
|
5319
|
+
return this;
|
|
5320
|
+
}
|
|
5321
|
+
|
|
5322
|
+
apply(content);
|
|
5323
|
+
return this;
|
|
5324
|
+
};
|
|
5325
|
+
|
|
5326
|
+
/**
|
|
5327
|
+
* Makes the element content editable and binds it to an Observable.
|
|
5328
|
+
* Changes in the DOM update the Observable, and changes to the Observable
|
|
5329
|
+
* update the DOM (only when the element is not focused to avoid cursor issues).
|
|
5330
|
+
*
|
|
5331
|
+
* @param {ObservableItem} $obs - Observable to bind to the element content
|
|
5332
|
+
* @param {Object} [options={}]
|
|
5333
|
+
* @param {string} [options.format='html'] - 'html' uses innerHTML, 'text' uses innerText
|
|
5334
|
+
* @returns {this}
|
|
5335
|
+
* @example
|
|
5336
|
+
* // Basic usage
|
|
5337
|
+
* const $content = $('<b>Hello</b>');
|
|
5338
|
+
* Div({}).nd.contentEditable($content)
|
|
5339
|
+
*
|
|
5340
|
+
* // Text only
|
|
5341
|
+
* Div({}).nd.contentEditable($content, {format: 'text'})
|
|
5342
|
+
*/
|
|
5343
|
+
NDElement.prototype.contentEditable = function($obs, {format = 'html'} = {}) {
|
|
5344
|
+
this.$element.contentEditable = true;
|
|
5345
|
+
|
|
5346
|
+
const getValue = format === 'text'
|
|
5347
|
+
? () => this.$element.innerText
|
|
5348
|
+
: () => this.$element.innerHTML;
|
|
5349
|
+
|
|
5350
|
+
const setValue = format === 'text'
|
|
5351
|
+
? (value) => { this.$element.innerText = value; }
|
|
5352
|
+
: (value) => { this.$element.innerHTML = value; };
|
|
5353
|
+
|
|
5354
|
+
if($obs?.__$Observable) {
|
|
5355
|
+
$obs.subscribe((value) => {
|
|
5356
|
+
if(document.activeElement !== this.$element) {
|
|
5357
|
+
setValue(value);
|
|
5358
|
+
}
|
|
5359
|
+
});
|
|
5360
|
+
setValue($obs.val() || '');
|
|
5361
|
+
}
|
|
5362
|
+
|
|
5363
|
+
this.$element.addEventListener('input', () => {
|
|
5364
|
+
$obs?.set(getValue());
|
|
5365
|
+
}, { signal: this.$getSignal() });
|
|
5366
|
+
|
|
5367
|
+
return this;
|
|
5368
|
+
};
|
|
5369
|
+
|
|
4059
5370
|
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
|
-
|
|
5371
|
+
'Click',
|
|
5372
|
+
'DblClick',
|
|
5373
|
+
'MouseDown',
|
|
5374
|
+
'MouseEnter',
|
|
5375
|
+
'MouseLeave',
|
|
5376
|
+
'MouseMove',
|
|
5377
|
+
'MouseOut',
|
|
5378
|
+
'MouseOver',
|
|
5379
|
+
'MouseUp',
|
|
5380
|
+
'Wheel',
|
|
5381
|
+
'KeyDown',
|
|
5382
|
+
'KeyPress',
|
|
5383
|
+
'KeyUp',
|
|
5384
|
+
'Blur',
|
|
5385
|
+
'Change',
|
|
5386
|
+
'Focus',
|
|
5387
|
+
'Input',
|
|
5388
|
+
'Invalid',
|
|
5389
|
+
'Reset',
|
|
5390
|
+
'Search',
|
|
5391
|
+
'Select',
|
|
5392
|
+
'Submit',
|
|
5393
|
+
'Drag',
|
|
5394
|
+
'DragEnd',
|
|
5395
|
+
'DragEnter',
|
|
5396
|
+
'DragLeave',
|
|
5397
|
+
'DragOver',
|
|
5398
|
+
'DragStart',
|
|
5399
|
+
'Drop',
|
|
5400
|
+
'AfterPrint',
|
|
5401
|
+
'BeforePrint',
|
|
5402
|
+
'BeforeUnload',
|
|
5403
|
+
'Error',
|
|
5404
|
+
'HashChange',
|
|
5405
|
+
'Load',
|
|
5406
|
+
'Offline',
|
|
5407
|
+
'Online',
|
|
5408
|
+
'PageHide',
|
|
5409
|
+
'PageShow',
|
|
5410
|
+
'Resize',
|
|
5411
|
+
'Scroll',
|
|
5412
|
+
'Unload',
|
|
5413
|
+
'Abort',
|
|
5414
|
+
'CanPlay',
|
|
5415
|
+
'CanPlayThrough',
|
|
5416
|
+
'DurationChange',
|
|
5417
|
+
'Emptied',
|
|
5418
|
+
'Ended',
|
|
5419
|
+
'LoadedData',
|
|
5420
|
+
'LoadedMetadata',
|
|
5421
|
+
'LoadStart',
|
|
5422
|
+
'Pause',
|
|
5423
|
+
'Play',
|
|
5424
|
+
'Playing',
|
|
5425
|
+
'Progress',
|
|
5426
|
+
'RateChange',
|
|
5427
|
+
'Seeked',
|
|
5428
|
+
'Seeking',
|
|
5429
|
+
'Stalled',
|
|
5430
|
+
'Suspend',
|
|
5431
|
+
'TimeUpdate',
|
|
5432
|
+
'VolumeChange',
|
|
5433
|
+
'Waiting',
|
|
5434
|
+
|
|
5435
|
+
'TouchCancel',
|
|
5436
|
+
'TouchEnd',
|
|
5437
|
+
'TouchMove',
|
|
5438
|
+
'TouchStart',
|
|
5439
|
+
'AnimationEnd',
|
|
5440
|
+
'AnimationIteration',
|
|
5441
|
+
'AnimationStart',
|
|
5442
|
+
'TransitionEnd',
|
|
5443
|
+
'Copy',
|
|
5444
|
+
'Cut',
|
|
5445
|
+
'Paste',
|
|
5446
|
+
'FocusIn',
|
|
5447
|
+
'FocusOut',
|
|
5448
|
+
'ContextMenu',
|
|
4138
5449
|
];
|
|
4139
5450
|
|
|
4140
5451
|
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
|
-
|
|
5452
|
+
'Click',
|
|
5453
|
+
'DblClick',
|
|
5454
|
+
'MouseDown',
|
|
5455
|
+
'MouseUp',
|
|
5456
|
+
'Wheel',
|
|
5457
|
+
'KeyDown',
|
|
5458
|
+
'KeyPress',
|
|
5459
|
+
'Invalid',
|
|
5460
|
+
'Reset',
|
|
5461
|
+
'Submit',
|
|
5462
|
+
'DragOver',
|
|
5463
|
+
'Drop',
|
|
5464
|
+
'BeforeUnload',
|
|
5465
|
+
'TouchCancel',
|
|
5466
|
+
'TouchEnd',
|
|
5467
|
+
'TouchMove',
|
|
5468
|
+
'TouchStart',
|
|
5469
|
+
'Copy',
|
|
5470
|
+
'Cut',
|
|
5471
|
+
'Paste',
|
|
5472
|
+
'ContextMenu',
|
|
4162
5473
|
];
|
|
4163
5474
|
|
|
4164
5475
|
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
|
-
|
|
5476
|
+
'Click',
|
|
5477
|
+
'DblClick',
|
|
5478
|
+
'MouseDown',
|
|
5479
|
+
'MouseMove',
|
|
5480
|
+
'MouseOut',
|
|
5481
|
+
'MouseOver',
|
|
5482
|
+
'MouseUp',
|
|
5483
|
+
'Wheel',
|
|
5484
|
+
'KeyDown',
|
|
5485
|
+
'KeyPress',
|
|
5486
|
+
'KeyUp',
|
|
5487
|
+
'Change',
|
|
5488
|
+
'Input',
|
|
5489
|
+
'Invalid',
|
|
5490
|
+
'Reset',
|
|
5491
|
+
'Search',
|
|
5492
|
+
'Select',
|
|
5493
|
+
'Submit',
|
|
5494
|
+
'Drag',
|
|
5495
|
+
'DragEnd',
|
|
5496
|
+
'DragEnter',
|
|
5497
|
+
'DragLeave',
|
|
5498
|
+
'DragOver',
|
|
5499
|
+
'DragStart',
|
|
5500
|
+
'Drop',
|
|
5501
|
+
'BeforeUnload',
|
|
5502
|
+
'HashChange',
|
|
5503
|
+
'TouchCancel',
|
|
5504
|
+
'TouchEnd',
|
|
5505
|
+
'TouchMove',
|
|
5506
|
+
'TouchStart',
|
|
5507
|
+
'AnimationEnd',
|
|
5508
|
+
'AnimationIteration',
|
|
5509
|
+
'AnimationStart',
|
|
5510
|
+
'TransitionEnd',
|
|
5511
|
+
'Copy',
|
|
5512
|
+
'Cut',
|
|
5513
|
+
'Paste',
|
|
5514
|
+
'FocusIn',
|
|
5515
|
+
'FocusOut',
|
|
5516
|
+
'ContextMenu',
|
|
4206
5517
|
];
|
|
4207
5518
|
|
|
4208
5519
|
const property = {
|
|
4209
5520
|
configurable: true,
|
|
4210
5521
|
get() {
|
|
4211
5522
|
return new NDElement(this);
|
|
4212
|
-
}
|
|
5523
|
+
},
|
|
4213
5524
|
};
|
|
4214
5525
|
|
|
4215
5526
|
Object.defineProperty(HTMLElement.prototype, 'nd', property);
|
|
@@ -4220,7 +5531,7 @@ var NativeDocument = (function (exports) {
|
|
|
4220
5531
|
configurable: true,
|
|
4221
5532
|
get: function() {
|
|
4222
5533
|
return this;
|
|
4223
|
-
}
|
|
5534
|
+
},
|
|
4224
5535
|
});
|
|
4225
5536
|
|
|
4226
5537
|
|
|
@@ -4233,7 +5544,7 @@ var NativeDocument = (function (exports) {
|
|
|
4233
5544
|
NDElement.prototype['on'+eventSourceName] = function(callback = null, options = {}) {
|
|
4234
5545
|
this.$element.addEventListener(eventName, callback, {
|
|
4235
5546
|
signal: this.$getSignal(),
|
|
4236
|
-
...options
|
|
5547
|
+
...options,
|
|
4237
5548
|
});
|
|
4238
5549
|
return this;
|
|
4239
5550
|
};
|
|
@@ -4244,14 +5555,14 @@ var NativeDocument = (function (exports) {
|
|
|
4244
5555
|
NDElement.prototype['onStop'+eventSourceName] = function(callback = null, options = {}) {
|
|
4245
5556
|
_stop(this.$element, eventName, callback, {
|
|
4246
5557
|
signal: this.$getSignal(),
|
|
4247
|
-
...options
|
|
5558
|
+
...options,
|
|
4248
5559
|
});
|
|
4249
5560
|
return this;
|
|
4250
5561
|
};
|
|
4251
5562
|
NDElement.prototype['onPreventStop'+eventSourceName] = function(callback = null, options = {}) {
|
|
4252
5563
|
_preventStop(this.$element, eventName, callback, {
|
|
4253
5564
|
signal: this.$getSignal(),
|
|
4254
|
-
...options
|
|
5565
|
+
...options,
|
|
4255
5566
|
});
|
|
4256
5567
|
return this;
|
|
4257
5568
|
};
|
|
@@ -4262,12 +5573,20 @@ var NativeDocument = (function (exports) {
|
|
|
4262
5573
|
NDElement.prototype['onPrevent'+eventSourceName] = function(callback = null, options = {}) {
|
|
4263
5574
|
_prevent(this.$element, eventName, callback, {
|
|
4264
5575
|
signal: this.$getSignal(),
|
|
4265
|
-
...options
|
|
5576
|
+
...options,
|
|
4266
5577
|
});
|
|
4267
5578
|
return this;
|
|
4268
5579
|
};
|
|
4269
5580
|
});
|
|
4270
5581
|
|
|
5582
|
+
/**
|
|
5583
|
+
* Retrieves or creates an AbortController signal tied to this element's lifecycle.
|
|
5584
|
+
* The signal is automatically aborted when the element is removed via .nd.remove().
|
|
5585
|
+
* Used internally by all event listeners (onClick, onInput, etc.) to auto-cleanup on unmount.
|
|
5586
|
+
*
|
|
5587
|
+
* @internal
|
|
5588
|
+
* @returns {AbortSignal} The signal for this element's AbortController
|
|
5589
|
+
*/
|
|
4271
5590
|
NDElement.prototype.$getSignal = function() {
|
|
4272
5591
|
if(!this.$element.__$controller) {
|
|
4273
5592
|
this.$element.__$controller = new AbortController();
|
|
@@ -4275,27 +5594,63 @@ var NativeDocument = (function (exports) {
|
|
|
4275
5594
|
return this.$element.__$controller.signal;
|
|
4276
5595
|
};
|
|
4277
5596
|
|
|
5597
|
+
|
|
5598
|
+
/**
|
|
5599
|
+
* Adds a native event listener to the underlying HTMLElement.
|
|
5600
|
+
* The listener is automatically removed when the element is unmounted (via AbortSignal).
|
|
5601
|
+
*
|
|
5602
|
+
* @param {string} name - Event name (case-insensitive, e.g. 'click', 'input')
|
|
5603
|
+
* @param {EventListener} callback - Handler to call when the event fires
|
|
5604
|
+
* @param {boolean|AddEventListenerOptions} [options] - Listener options merged with the element's AbortSignal
|
|
5605
|
+
* @returns {this}
|
|
5606
|
+
*/
|
|
4278
5607
|
NDElement.prototype.on = function(name, callback, options) {
|
|
4279
5608
|
this.$element.addEventListener(name.toLowerCase(), callback, {
|
|
4280
5609
|
signal: this.$getSignal(),
|
|
4281
|
-
...options
|
|
5610
|
+
...options,
|
|
4282
5611
|
});
|
|
4283
5612
|
return this;
|
|
4284
5613
|
};
|
|
4285
5614
|
|
|
5615
|
+
/**
|
|
5616
|
+
* Removes a previously registered event listener from the underlying HTMLElement.
|
|
5617
|
+
*
|
|
5618
|
+
* @param {string} name - Event name (case-insensitive)
|
|
5619
|
+
* @param {EventListener} callback - The exact handler reference to remove
|
|
5620
|
+
* @returns {this}
|
|
5621
|
+
*/
|
|
4286
5622
|
NDElement.prototype.off = function(name, callback) {
|
|
4287
5623
|
this.$element.removeEventListener(name.toLowerCase(), callback);
|
|
4288
5624
|
return this;
|
|
4289
5625
|
};
|
|
4290
5626
|
|
|
5627
|
+
/**
|
|
5628
|
+
* Adds a one-time event listener that removes itself after the first call.
|
|
5629
|
+
* Uses the element's AbortSignal for lifecycle-safe cleanup.
|
|
5630
|
+
*
|
|
5631
|
+
* @param {string} name - Event name (case-insensitive)
|
|
5632
|
+
* @param {EventListener} callback - Handler called once when the event fires
|
|
5633
|
+
* @returns {this}
|
|
5634
|
+
*/
|
|
4291
5635
|
NDElement.prototype.once = function(name, callback) {
|
|
4292
5636
|
this.$element.addEventListener(name.toLowerCase(), callback, {
|
|
4293
5637
|
signal: this.$getSignal(),
|
|
4294
|
-
once: true
|
|
5638
|
+
once: true,
|
|
4295
5639
|
});
|
|
4296
5640
|
return this;
|
|
4297
5641
|
};
|
|
4298
5642
|
|
|
5643
|
+
/**
|
|
5644
|
+
* Dispatches a custom event on the underlying HTMLElement.
|
|
5645
|
+
* The event bubbles and is cancelable by default.
|
|
5646
|
+
*
|
|
5647
|
+
* @param {string} name - Custom event name
|
|
5648
|
+
* @param {*} [detail=null] - Data passed in event.detail
|
|
5649
|
+
* @returns {this}
|
|
5650
|
+
* @example
|
|
5651
|
+
* Button('Save').nd.emit('saved', { id: 42 });
|
|
5652
|
+
* // Triggers: element.addEventListener('saved', e => console.log(e.detail.id))
|
|
5653
|
+
*/
|
|
4299
5654
|
NDElement.prototype.emit = function(name, detail = null) {
|
|
4300
5655
|
const event = new CustomEvent(name, {
|
|
4301
5656
|
detail,
|
|
@@ -4379,7 +5734,7 @@ var NativeDocument = (function (exports) {
|
|
|
4379
5734
|
},
|
|
4380
5735
|
contains(value) {
|
|
4381
5736
|
return this.getClasses().indexOf(value) >= 0;
|
|
4382
|
-
}
|
|
5737
|
+
},
|
|
4383
5738
|
};
|
|
4384
5739
|
|
|
4385
5740
|
Object.defineProperty(HTMLElement.prototype, 'classes', {
|
|
@@ -4387,17 +5742,42 @@ var NativeDocument = (function (exports) {
|
|
|
4387
5742
|
get() {
|
|
4388
5743
|
return {
|
|
4389
5744
|
$element: this,
|
|
4390
|
-
...classListMethods
|
|
5745
|
+
...classListMethods,
|
|
4391
5746
|
};
|
|
4392
|
-
}
|
|
5747
|
+
},
|
|
4393
5748
|
});
|
|
4394
5749
|
|
|
4395
5750
|
DocumentFragment.prototype.__IS_FRAGMENT = true;
|
|
4396
5751
|
|
|
5752
|
+
/**
|
|
5753
|
+
* Wraps a function with argument validation based on an ArgTypes schema.
|
|
5754
|
+
* Throws an ArgTypesError if the arguments don't match the schema.
|
|
5755
|
+
*
|
|
5756
|
+
* @param {...ArgType} args - ArgType descriptors defining expected argument types
|
|
5757
|
+
* @returns {Function} Wrapped function that validates its arguments before executing
|
|
5758
|
+
* @example
|
|
5759
|
+
* function greet(name, age) { ... }
|
|
5760
|
+
* const safeGreet = greet.args(ArgTypes.string('name'), ArgTypes.number('age'));
|
|
5761
|
+
* safeGreet('John', 25); // OK
|
|
5762
|
+
* safeGreet('John', 'old'); // throws ArgTypesError
|
|
5763
|
+
*/
|
|
4397
5764
|
Function.prototype.args = function(...args) {
|
|
4398
5765
|
return exports.withValidation(this, args);
|
|
4399
5766
|
};
|
|
4400
5767
|
|
|
5768
|
+
|
|
5769
|
+
/**
|
|
5770
|
+
* Wraps a function with a try/catch error boundary.
|
|
5771
|
+
* If the function throws, the callback is called with the error and context instead.
|
|
5772
|
+
*
|
|
5773
|
+
* @param {(error: Error, context: { caller: Function, args: any[] }) => *} callback - Error handler
|
|
5774
|
+
* @returns {Function} Wrapped function with error boundary
|
|
5775
|
+
* @example
|
|
5776
|
+
* const safeRender = render.errorBoundary((err, { args }) => {
|
|
5777
|
+
* console.error('Render failed:', err.message);
|
|
5778
|
+
* return Div({}, 'Error');
|
|
5779
|
+
* });
|
|
5780
|
+
*/
|
|
4401
5781
|
Function.prototype.errorBoundary = function(callback) {
|
|
4402
5782
|
const handler = (...args) => {
|
|
4403
5783
|
try {
|
|
@@ -4417,36 +5797,85 @@ var NativeDocument = (function (exports) {
|
|
|
4417
5797
|
|
|
4418
5798
|
NDElement.$getChild = ElementCreator.getChild;
|
|
4419
5799
|
|
|
5800
|
+
/**
|
|
5801
|
+
* Converts a string to a reactive text node.
|
|
5802
|
+
*
|
|
5803
|
+
* @returns {Text} Static text node containing the string value
|
|
5804
|
+
*/
|
|
4420
5805
|
String.prototype.toNdElement = function () {
|
|
4421
5806
|
return ElementCreator.createStaticTextNode(null, this);
|
|
4422
5807
|
};
|
|
4423
5808
|
|
|
5809
|
+
/**
|
|
5810
|
+
* Converts a number to a static text node.
|
|
5811
|
+
*
|
|
5812
|
+
* @returns {Text} Static text node containing the number as a string
|
|
5813
|
+
*/
|
|
4424
5814
|
Number.prototype.toNdElement = function () {
|
|
4425
5815
|
return ElementCreator.createStaticTextNode(null, this.toString());
|
|
4426
5816
|
};
|
|
4427
5817
|
|
|
5818
|
+
/**
|
|
5819
|
+
* Returns the element itself (identity for DOM compatibility).
|
|
5820
|
+
*
|
|
5821
|
+
* @returns {Element} this
|
|
5822
|
+
*/
|
|
4428
5823
|
Element.prototype.toNdElement = function () {
|
|
4429
5824
|
return this;
|
|
4430
5825
|
};
|
|
5826
|
+
|
|
5827
|
+
/**
|
|
5828
|
+
* Returns the text node itself (identity for DOM compatibility).
|
|
5829
|
+
*
|
|
5830
|
+
* @returns {Text} this
|
|
5831
|
+
*/
|
|
4431
5832
|
Text.prototype.toNdElement = function () {
|
|
4432
5833
|
return this;
|
|
4433
5834
|
};
|
|
5835
|
+
|
|
5836
|
+
/**
|
|
5837
|
+
* Returns the comment node itself (identity for DOM compatibility).
|
|
5838
|
+
*
|
|
5839
|
+
* @returns {Comment} this
|
|
5840
|
+
*/
|
|
4434
5841
|
Comment.prototype.toNdElement = function () {
|
|
4435
5842
|
return this;
|
|
4436
5843
|
};
|
|
5844
|
+
|
|
5845
|
+
/**
|
|
5846
|
+
* Returns the document itself (identity for DOM compatibility).
|
|
5847
|
+
*
|
|
5848
|
+
* @returns {Document} this
|
|
5849
|
+
*/
|
|
4437
5850
|
Document.prototype.toNdElement = function () {
|
|
4438
5851
|
return this;
|
|
4439
5852
|
};
|
|
5853
|
+
|
|
5854
|
+
/**
|
|
5855
|
+
* Returns the document fragment itself (identity for DOM compatibility).
|
|
5856
|
+
*
|
|
5857
|
+
* @returns {DocumentFragment} this
|
|
5858
|
+
*/
|
|
4440
5859
|
DocumentFragment.prototype.toNdElement = function () {
|
|
4441
5860
|
return this;
|
|
4442
5861
|
};
|
|
4443
5862
|
|
|
5863
|
+
/**
|
|
5864
|
+
* Converts the ObservableItem to a reactive text node that updates automatically when the value changes.
|
|
5865
|
+
*
|
|
5866
|
+
* @returns {Text} Reactive text node bound to this observable
|
|
5867
|
+
*/
|
|
4444
5868
|
ObservableItem.prototype.toNdElement = function () {
|
|
4445
5869
|
return ElementCreator.createObservableNode(null, this);
|
|
4446
5870
|
};
|
|
4447
5871
|
|
|
4448
5872
|
ObservableChecker.prototype.toNdElement = ObservableItem.prototype.toNdElement;
|
|
4449
5873
|
|
|
5874
|
+
/**
|
|
5875
|
+
* Converts the NDElement to its underlying HTMLElement (or ghost DOM fragment if ghostDom was used).
|
|
5876
|
+
*
|
|
5877
|
+
* @returns {HTMLElement|DocumentFragment} The underlying DOM node
|
|
5878
|
+
*/
|
|
4450
5879
|
NDElement.prototype.toNdElement = function () {
|
|
4451
5880
|
const element = this.$element ?? this.$build?.() ?? this.build?.() ?? null;
|
|
4452
5881
|
if(this.$attachements) {
|
|
@@ -4458,6 +5887,12 @@ var NativeDocument = (function (exports) {
|
|
|
4458
5887
|
return element;
|
|
4459
5888
|
};
|
|
4460
5889
|
|
|
5890
|
+
/**
|
|
5891
|
+
* Converts the array to a DocumentFragment containing all elements.
|
|
5892
|
+
* Each item is processed through ElementCreator.getChild().
|
|
5893
|
+
*
|
|
5894
|
+
* @returns {DocumentFragment} Fragment containing all array children
|
|
5895
|
+
*/
|
|
4461
5896
|
Array.prototype.toNdElement = function () {
|
|
4462
5897
|
const fragment = document.createDocumentFragment();
|
|
4463
5898
|
for(let i = 0, length = this.length; i < length; i++) {
|
|
@@ -4468,6 +5903,12 @@ var NativeDocument = (function (exports) {
|
|
|
4468
5903
|
return fragment;
|
|
4469
5904
|
};
|
|
4470
5905
|
|
|
5906
|
+
/**
|
|
5907
|
+
* Calls the function and converts its return value to a DOM node.
|
|
5908
|
+
* Used internally by ElementCreator to process function-based children.
|
|
5909
|
+
*
|
|
5910
|
+
* @returns {Node} The DOM node returned by the function
|
|
5911
|
+
*/
|
|
4471
5912
|
Function.prototype.toNdElement = function () {
|
|
4472
5913
|
const child = this;
|
|
4473
5914
|
{
|
|
@@ -4476,6 +5917,11 @@ var NativeDocument = (function (exports) {
|
|
|
4476
5917
|
return ElementCreator.getChild(child());
|
|
4477
5918
|
};
|
|
4478
5919
|
|
|
5920
|
+
/**
|
|
5921
|
+
* Converts the TemplateBinding to a hydratable DOM node for use in TemplateCloner.
|
|
5922
|
+
*
|
|
5923
|
+
* @returns {Node} Hydratable node
|
|
5924
|
+
*/
|
|
4479
5925
|
TemplateBinding.prototype.toNdElement = function () {
|
|
4480
5926
|
return ElementCreator.createHydratableNode(null, this);
|
|
4481
5927
|
};
|
|
@@ -4514,6 +5960,16 @@ var NativeDocument = (function (exports) {
|
|
|
4514
5960
|
});
|
|
4515
5961
|
};
|
|
4516
5962
|
|
|
5963
|
+
/**
|
|
5964
|
+
* Registers a beforeUnmount hook that plays an exit CSS transition before the element is removed.
|
|
5965
|
+
* Adds the class `{transitionName}-exit`, waits for the transition/animation to end, then removes it.
|
|
5966
|
+
*
|
|
5967
|
+
* @param {string} transitionName - CSS class prefix for the exit transition
|
|
5968
|
+
* @returns {this}
|
|
5969
|
+
* @example
|
|
5970
|
+
* Div({ class: 'modal' }).nd.transitionOut('fade');
|
|
5971
|
+
* // Adds 'fade-exit' before removal, waits for transitionend/animationend
|
|
5972
|
+
*/
|
|
4517
5973
|
NDElement.prototype.transitionOut = function(transitionName) {
|
|
4518
5974
|
const exitClass = transitionName + '-exit';
|
|
4519
5975
|
const el = this.$element;
|
|
@@ -4525,6 +5981,17 @@ var NativeDocument = (function (exports) {
|
|
|
4525
5981
|
return this;
|
|
4526
5982
|
};
|
|
4527
5983
|
|
|
5984
|
+
/**
|
|
5985
|
+
* Plays an enter CSS transition when the element is mounted into the DOM.
|
|
5986
|
+
* Adds `{transitionName}-enter-from` immediately, then swaps to `{transitionName}-enter-to`
|
|
5987
|
+
* on the next animation frame, and cleans up after the transition ends.
|
|
5988
|
+
*
|
|
5989
|
+
* @param {string} transitionName - CSS class prefix for the enter transition
|
|
5990
|
+
* @returns {this}
|
|
5991
|
+
* @example
|
|
5992
|
+
* Div({ class: 'modal' }).nd.transitionIn('fade');
|
|
5993
|
+
* // On mount: adds 'fade-enter-from', then swaps to 'fade-enter-to'
|
|
5994
|
+
*/
|
|
4528
5995
|
NDElement.prototype.transitionIn = function(transitionName) {
|
|
4529
5996
|
const startClass = transitionName + '-enter-from';
|
|
4530
5997
|
const endClass = transitionName + '-enter-to';
|
|
@@ -4548,13 +6015,32 @@ var NativeDocument = (function (exports) {
|
|
|
4548
6015
|
return this;
|
|
4549
6016
|
};
|
|
4550
6017
|
|
|
4551
|
-
|
|
6018
|
+
/**
|
|
6019
|
+
* Applies both enter and exit transitions to the element.
|
|
6020
|
+
* Shorthand for calling .transitionIn(name) and .transitionOut(name).
|
|
6021
|
+
*
|
|
6022
|
+
* @param {string} transitionName - CSS class prefix for both enter and exit transitions
|
|
6023
|
+
* @returns {this}
|
|
6024
|
+
* @example
|
|
6025
|
+
* Div({}).nd.transition('slide');
|
|
6026
|
+
* // On mount: enter transition; on unmount: exit transition
|
|
6027
|
+
*/
|
|
4552
6028
|
NDElement.prototype.transition = function (transitionName) {
|
|
4553
6029
|
this.transitionIn(transitionName);
|
|
4554
6030
|
this.transitionOut(transitionName);
|
|
4555
6031
|
return this;
|
|
4556
6032
|
};
|
|
4557
6033
|
|
|
6034
|
+
/**
|
|
6035
|
+
* Immediately applies a CSS animation class to the element.
|
|
6036
|
+
* Removes the class automatically once the animation ends.
|
|
6037
|
+
*
|
|
6038
|
+
* @param {string} animationName - CSS animation class name to add
|
|
6039
|
+
* @returns {this}
|
|
6040
|
+
* @example
|
|
6041
|
+
* Button('Click me').nd.animate('shake');
|
|
6042
|
+
* // Adds 'shake' class, removes it when animationend fires
|
|
6043
|
+
*/
|
|
4558
6044
|
NDElement.prototype.animate = function(animationName) {
|
|
4559
6045
|
const el = this.$element;
|
|
4560
6046
|
el.classes.add(animationName);
|
|
@@ -4577,14 +6063,17 @@ var NativeDocument = (function (exports) {
|
|
|
4577
6063
|
|
|
4578
6064
|
ObservableChecker.prototype.handleNdAttribute = ObservableItem.prototype.handleNdAttribute;
|
|
4579
6065
|
|
|
4580
|
-
|
|
6066
|
+
TemplateBinding.prototype.handleNdAttribute = function(element, attributeName) {
|
|
4581
6067
|
this.$hydrate(element, attributeName);
|
|
4582
6068
|
};
|
|
4583
6069
|
|
|
4584
6070
|
/**
|
|
6071
|
+
* Creates a reactive or static text node from the given value.
|
|
6072
|
+
* If the value has a .toNdElement() method, delegates to it.
|
|
6073
|
+
* Otherwise creates an empty text node.
|
|
4585
6074
|
*
|
|
4586
|
-
* @param {
|
|
4587
|
-
* @returns {Text}
|
|
6075
|
+
* @param {string|number|ObservableItem|*} value - Value to convert to a text node
|
|
6076
|
+
* @returns {Text} Text node, reactive if value is an ObservableItem
|
|
4588
6077
|
*/
|
|
4589
6078
|
const createTextNode = (value) => {
|
|
4590
6079
|
if(value) {
|
|
@@ -4594,8 +6083,18 @@ var NativeDocument = (function (exports) {
|
|
|
4594
6083
|
};
|
|
4595
6084
|
|
|
4596
6085
|
|
|
6086
|
+
/**
|
|
6087
|
+
* Applies attributes and children to an existing HTMLElement.
|
|
6088
|
+
* Used internally by HtmlElementWrapper on each cloned node.
|
|
6089
|
+
*
|
|
6090
|
+
* @internal
|
|
6091
|
+
* @param {HTMLElement} element - Element to configure
|
|
6092
|
+
* @param {Object|null} _attributes - Attributes object or children if no attrs provided
|
|
6093
|
+
* @param {ValidChild|null} [_children=null] - Children to append
|
|
6094
|
+
* @returns {HTMLElement} The configured element
|
|
6095
|
+
*/
|
|
4597
6096
|
const createHtmlElement = (element, _attributes, _children = null) => {
|
|
4598
|
-
|
|
6097
|
+
const { props: attributes, children = null } = normalizeComponentArgs(_attributes, _children);
|
|
4599
6098
|
|
|
4600
6099
|
ElementCreator.processAttributes(element, attributes);
|
|
4601
6100
|
ElementCreator.processChildren(children, element);
|
|
@@ -4603,10 +6102,22 @@ var NativeDocument = (function (exports) {
|
|
|
4603
6102
|
};
|
|
4604
6103
|
|
|
4605
6104
|
/**
|
|
6105
|
+
* Creates a reusable element factory function for the given HTML tag.
|
|
6106
|
+
* The factory clones a cached template node on each call for performance.
|
|
6107
|
+
* Optionally wraps the created element with a custom wrapper function.
|
|
4606
6108
|
*
|
|
4607
|
-
* @param {string} name
|
|
4608
|
-
* @param {
|
|
4609
|
-
* @returns {
|
|
6109
|
+
* @param {string} name - HTML tag name (e.g. 'div', 'button', 'input'). Pass empty string to create a Fragment.
|
|
6110
|
+
* @param {((element: HTMLElement) => HTMLElement)|null} [customWrapper=null] - Optional function to augment the element before returning
|
|
6111
|
+
* @returns {(attr?: Object, children?: ValidChild) => HTMLElement} Element factory function
|
|
6112
|
+
* @example
|
|
6113
|
+
* const Div = HtmlElementWrapper('div');
|
|
6114
|
+
* Div({ class: 'container' }, 'Hello');
|
|
6115
|
+
*
|
|
6116
|
+
* // With custom wrapper
|
|
6117
|
+
* const Form = HtmlElementWrapper('form', (el) => {
|
|
6118
|
+
* el.submit = (action) => { ... };
|
|
6119
|
+
* return el;
|
|
6120
|
+
* });
|
|
4610
6121
|
*/
|
|
4611
6122
|
function HtmlElementWrapper(name, customWrapper = null) {
|
|
4612
6123
|
if(name) {
|
|
@@ -4619,7 +6130,7 @@ var NativeDocument = (function (exports) {
|
|
|
4619
6130
|
};
|
|
4620
6131
|
return createHtmlElement(customWrapper(node.cloneNode()), attr, children); };
|
|
4621
6132
|
|
|
4622
|
-
return (attr, children) => createElement(attr, children)
|
|
6133
|
+
return (attr, children) => createElement(attr, children);
|
|
4623
6134
|
}
|
|
4624
6135
|
|
|
4625
6136
|
let node = null;
|
|
@@ -4631,7 +6142,7 @@ var NativeDocument = (function (exports) {
|
|
|
4631
6142
|
return createHtmlElement(node.cloneNode(), attr, children);
|
|
4632
6143
|
};
|
|
4633
6144
|
|
|
4634
|
-
return (attr, children) => createElement(attr, children)
|
|
6145
|
+
return (attr, children) => createElement(attr, children);
|
|
4635
6146
|
}
|
|
4636
6147
|
return (children, name = '') => {
|
|
4637
6148
|
const anchor = Anchor(name);
|
|
@@ -4640,6 +6151,15 @@ var NativeDocument = (function (exports) {
|
|
|
4640
6151
|
};
|
|
4641
6152
|
}
|
|
4642
6153
|
|
|
6154
|
+
/**
|
|
6155
|
+
* Stores deferred attribute, class, style, and event bindings for a cloneable element.
|
|
6156
|
+
* Used internally by TemplateCloner to apply per-instance data to cloned DOM nodes.
|
|
6157
|
+
* Not intended for direct use in application code.
|
|
6158
|
+
*
|
|
6159
|
+
* @internal
|
|
6160
|
+
* @constructor
|
|
6161
|
+
* @param {HTMLElement} $element - The template element to clone
|
|
6162
|
+
*/
|
|
4643
6163
|
function NodeCloner($element) {
|
|
4644
6164
|
this.$element = $element;
|
|
4645
6165
|
this.$classes = null;
|
|
@@ -4679,6 +6199,12 @@ var NativeDocument = (function (exports) {
|
|
|
4679
6199
|
return cache;
|
|
4680
6200
|
};
|
|
4681
6201
|
|
|
6202
|
+
/**
|
|
6203
|
+
* Pre-compiles all registered bindings into a sequence of optimised steps.
|
|
6204
|
+
* Called once before the first clone operation. Subsequent calls are no-ops.
|
|
6205
|
+
*
|
|
6206
|
+
* @internal
|
|
6207
|
+
*/
|
|
4682
6208
|
NodeCloner.prototype.resolve = function() {
|
|
4683
6209
|
if(this.$content) {
|
|
4684
6210
|
return;
|
|
@@ -4765,26 +6291,56 @@ var NativeDocument = (function (exports) {
|
|
|
4765
6291
|
};
|
|
4766
6292
|
};
|
|
4767
6293
|
|
|
6294
|
+
/**
|
|
6295
|
+
* Clones the template element and applies all compiled binding steps with the given data.
|
|
6296
|
+
*
|
|
6297
|
+
* @internal
|
|
6298
|
+
* @param {Array} data - Data array passed to each binding callback
|
|
6299
|
+
* @returns {HTMLElement} The cloned and hydrated element
|
|
6300
|
+
*/
|
|
4768
6301
|
NodeCloner.prototype.cloneNode = function(data) {
|
|
4769
6302
|
return this.$element.cloneNode(false);
|
|
4770
6303
|
};
|
|
4771
6304
|
|
|
6305
|
+
/**
|
|
6306
|
+
* Registers an NDElement method binding (e.g. onClick, onInput) to be applied on each clone.
|
|
6307
|
+
*
|
|
6308
|
+
* @internal
|
|
6309
|
+
* @param {string} methodName - Name of the NDElement method to call (e.g. 'onClick')
|
|
6310
|
+
* @param {Function} callback - Callback function to pass to the method
|
|
6311
|
+
* @returns {NodeCloner} this
|
|
6312
|
+
*/
|
|
4772
6313
|
NodeCloner.prototype.attach = function(methodName, callback) {
|
|
4773
6314
|
this.$ndMethods = this.$ndMethods || {};
|
|
4774
6315
|
this.$ndMethods[methodName] = callback;
|
|
4775
6316
|
return this;
|
|
4776
6317
|
};
|
|
4777
6318
|
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
6319
|
+
/**
|
|
6320
|
+
* Registers a reactive text content binding for the element.
|
|
6321
|
+
*
|
|
6322
|
+
* @internal
|
|
6323
|
+
* @param {Function} valueorProperty - Function receiving data and returning the text content
|
|
6324
|
+
* @returns {NodeCloner} this
|
|
6325
|
+
*/
|
|
6326
|
+
NodeCloner.prototype.text = function(valueorProperty) {
|
|
6327
|
+
this.$content = valueorProperty;
|
|
6328
|
+
if(typeof valueorProperty === 'function') {
|
|
6329
|
+
this.cloneNode = (data) => createTextNode(valueorProperty.apply(null, data));
|
|
4782
6330
|
return this;
|
|
4783
6331
|
}
|
|
4784
|
-
this.cloneNode = (data) => createTextNode(data[0][
|
|
6332
|
+
this.cloneNode = (data) => createTextNode(data[0][valueorProperty]);
|
|
4785
6333
|
return this;
|
|
4786
6334
|
};
|
|
4787
6335
|
|
|
6336
|
+
/**
|
|
6337
|
+
* Registers an attribute binding to be applied on each clone.
|
|
6338
|
+
*
|
|
6339
|
+
* @internal
|
|
6340
|
+
* @param {string} attrName - Attribute name
|
|
6341
|
+
* @param {{property: string, value: *}} value - Function receiving data and returning the attribute value
|
|
6342
|
+
* @returns {NodeCloner} this
|
|
6343
|
+
*/
|
|
4788
6344
|
NodeCloner.prototype.attr = function(attrName, value) {
|
|
4789
6345
|
if(attrName === 'class') {
|
|
4790
6346
|
this.$classes = this.$classes || {};
|
|
@@ -4801,6 +6357,16 @@ var NativeDocument = (function (exports) {
|
|
|
4801
6357
|
return this;
|
|
4802
6358
|
};
|
|
4803
6359
|
|
|
6360
|
+
/**
|
|
6361
|
+
* Applies a binding to a DOM node via its NodeCloner, routing to the correct binding type.
|
|
6362
|
+
* Called internally by TemplateCloner's binder methods (text, style, class, attr, event).
|
|
6363
|
+
*
|
|
6364
|
+
* @internal
|
|
6365
|
+
* @param {Function|string} value - Binding callback or property name
|
|
6366
|
+
* @param {'value'|'style'|'class'|'attach'|string} targetType - Binding type determining which NodeCloner method to call
|
|
6367
|
+
* @param {HTMLElement} element - Target DOM element
|
|
6368
|
+
* @param {string} property - Attribute name or method name (used for 'attach' and 'attr' types)
|
|
6369
|
+
*/
|
|
4804
6370
|
const $hydrateFn = function(value, targetType, element, property) {
|
|
4805
6371
|
element.nodeCloner = element.nodeCloner || new NodeCloner(element);
|
|
4806
6372
|
if(targetType === 'value') {
|
|
@@ -4814,6 +6380,23 @@ var NativeDocument = (function (exports) {
|
|
|
4814
6380
|
element.nodeCloner.attr(targetType, { property, value });
|
|
4815
6381
|
};
|
|
4816
6382
|
|
|
6383
|
+
/**
|
|
6384
|
+
* Creates a high-performance template cloner for repeated rendering of the same structure.
|
|
6385
|
+
* On the first call, builds the template by calling $fn with a binder object.
|
|
6386
|
+
* On subsequent calls, clones the compiled template and hydrates it with new data.
|
|
6387
|
+
* Used internally by ForEachArray and other list renderers.
|
|
6388
|
+
*
|
|
6389
|
+
* @constructor
|
|
6390
|
+
* @param {(binder: TemplateCloner) => HTMLElement} $fn - Function that builds the template using binder methods
|
|
6391
|
+
* @example
|
|
6392
|
+
* const cloner = new TemplateCloner((t) =>
|
|
6393
|
+
* Div({},
|
|
6394
|
+
* Span(t.text((item) => item.name)),
|
|
6395
|
+
* Button({}, 'Delete').nd.onClick(t.event((item) => () => list.removeItem(item)))
|
|
6396
|
+
* )
|
|
6397
|
+
* );
|
|
6398
|
+
* cloner.clone([item]); // returns a hydrated clone
|
|
6399
|
+
*/
|
|
4817
6400
|
function TemplateCloner($fn) {
|
|
4818
6401
|
let $node = null;
|
|
4819
6402
|
|
|
@@ -4858,6 +6441,14 @@ var NativeDocument = (function (exports) {
|
|
|
4858
6441
|
return containDynamicNode;
|
|
4859
6442
|
};
|
|
4860
6443
|
|
|
6444
|
+
|
|
6445
|
+
/**
|
|
6446
|
+
* Clones the compiled template and hydrates it with the given data.
|
|
6447
|
+
* On the first call, also compiles the template (builds and optimizes binding steps).
|
|
6448
|
+
*
|
|
6449
|
+
* @param {Array} data - Data array passed to all binding callbacks
|
|
6450
|
+
* @returns {HTMLElement} Cloned and hydrated DOM node
|
|
6451
|
+
*/
|
|
4861
6452
|
this.clone = (data) => {
|
|
4862
6453
|
const binder = createTemplateCloner(this);
|
|
4863
6454
|
$node = $fn(binder);
|
|
@@ -4876,25 +6467,69 @@ var NativeDocument = (function (exports) {
|
|
|
4876
6467
|
});
|
|
4877
6468
|
};
|
|
4878
6469
|
|
|
6470
|
+
/**
|
|
6471
|
+
* Creates a style binding — the result of fn(data) is applied as inline styles.
|
|
6472
|
+
*
|
|
6473
|
+
* @param {((...data: any[]) => Record<string, string>)} fn - Function returning a style object
|
|
6474
|
+
* @returns {TemplateBinding}
|
|
6475
|
+
*/
|
|
4879
6476
|
this.style = (fn) => {
|
|
4880
6477
|
return createBinding(fn, 'style');
|
|
4881
6478
|
};
|
|
6479
|
+
|
|
6480
|
+
/**
|
|
6481
|
+
* Creates a class binding — the result of fn(data) is applied as a class map.
|
|
6482
|
+
*
|
|
6483
|
+
* @param {((...data: any[]) => Record<string, boolean>)} fn - Function returning a class map
|
|
6484
|
+
* @returns {TemplateBinding}
|
|
6485
|
+
*/
|
|
4882
6486
|
this.class = (fn) => {
|
|
4883
6487
|
return createBinding(fn, 'class');
|
|
4884
6488
|
};
|
|
6489
|
+
|
|
4885
6490
|
this.property = (propertyName) => {
|
|
4886
6491
|
return this.value(propertyName);
|
|
4887
6492
|
};
|
|
6493
|
+
|
|
6494
|
+
/**
|
|
6495
|
+
* Creates a text/value binding — the result is set as text content or input value.
|
|
6496
|
+
* Alias: .text()
|
|
6497
|
+
*
|
|
6498
|
+
* @param {string|(((...data: any[]) => string))} callbackOrProperty - Property name (string) or callback returning the value
|
|
6499
|
+
* @returns {TemplateBinding}
|
|
6500
|
+
*/
|
|
4888
6501
|
this.value = (callbackOrProperty) => {
|
|
4889
6502
|
return createBinding(callbackOrProperty, 'value');
|
|
4890
6503
|
};
|
|
6504
|
+
|
|
6505
|
+
/**
|
|
6506
|
+
* Alias for .value() — creates a text content binding.
|
|
6507
|
+
*
|
|
6508
|
+
* @param {string|(((...data: any[]) => string))} callbackOrProperty
|
|
6509
|
+
* @returns {TemplateBinding}
|
|
6510
|
+
*/
|
|
4891
6511
|
this.text = this.value;
|
|
6512
|
+
|
|
6513
|
+
/**
|
|
6514
|
+
* Creates an attribute binding — the result of fn(data) is set as an attribute value.
|
|
6515
|
+
*
|
|
6516
|
+
* @param {((...data: any[]) => string)} fn - Function returning the attribute value
|
|
6517
|
+
* @returns {TemplateBinding}
|
|
6518
|
+
*/
|
|
4892
6519
|
this.attr = (fn) => {
|
|
4893
6520
|
return createBinding(fn, 'attributes');
|
|
4894
6521
|
};
|
|
6522
|
+
|
|
6523
|
+
/**
|
|
6524
|
+
* Creates an event binding — fn(data) returns the event handler to attach.
|
|
6525
|
+
*
|
|
6526
|
+
* @param {((...data: any[]) => EventListener)} fn - Function returning the event callback
|
|
6527
|
+
* @returns {TemplateBinding}
|
|
6528
|
+
*/
|
|
4895
6529
|
this.attach = (fn) => {
|
|
4896
6530
|
return createBinding(fn, 'attach');
|
|
4897
6531
|
};
|
|
6532
|
+
|
|
4898
6533
|
this.callback = this.attach;
|
|
4899
6534
|
}
|
|
4900
6535
|
|
|
@@ -4907,7 +6542,7 @@ var NativeDocument = (function (exports) {
|
|
|
4907
6542
|
}
|
|
4908
6543
|
if (typeof prop === 'symbol') return target[prop];
|
|
4909
6544
|
return target.value(prop);
|
|
4910
|
-
}
|
|
6545
|
+
},
|
|
4911
6546
|
});
|
|
4912
6547
|
};
|
|
4913
6548
|
|
|
@@ -4932,10 +6567,34 @@ var NativeDocument = (function (exports) {
|
|
|
4932
6567
|
};
|
|
4933
6568
|
}
|
|
4934
6569
|
|
|
6570
|
+
/**
|
|
6571
|
+
* Creates a singleton view — a component that is instantiated only once,
|
|
6572
|
+
* then reused across multiple renders. Useful for performance-critical components
|
|
6573
|
+
* that are frequently shown/hidden or repeated in lists.
|
|
6574
|
+
*
|
|
6575
|
+
* @constructor
|
|
6576
|
+
* @param {(instance: SingletonView) => Node} $viewCreator - Function that builds the view once and returns the root node.
|
|
6577
|
+
* Receives the SingletonView instance so it can call .createSection().
|
|
6578
|
+
* @example
|
|
6579
|
+
* const Card = useSingleton((view) => {
|
|
6580
|
+
* const nameSection = view.createSection('name');
|
|
6581
|
+
* return Div({ class: 'card' }, nameSection);
|
|
6582
|
+
* });
|
|
6583
|
+
* Card([{ name: 'John' }]); // Renders once, reused on subsequent calls
|
|
6584
|
+
*/
|
|
4935
6585
|
function SingletonView($viewCreator) {
|
|
4936
6586
|
let $cacheNode = null;
|
|
4937
6587
|
let $components = null;
|
|
4938
6588
|
|
|
6589
|
+
|
|
6590
|
+
/**
|
|
6591
|
+
* Renders the singleton view with the given data.
|
|
6592
|
+
* On the first call, create the view by calling $viewCreator.
|
|
6593
|
+
* On later calls, updates registered sections via their update functions.
|
|
6594
|
+
*
|
|
6595
|
+
* @param {Array} data - Array where data[0] is an object mapping section names to new content
|
|
6596
|
+
* @returns {Node} The cached root node
|
|
6597
|
+
*/
|
|
4939
6598
|
this.render = (data) => {
|
|
4940
6599
|
if(!$cacheNode) {
|
|
4941
6600
|
$cacheNode = $viewCreator(this);
|
|
@@ -4953,6 +6612,18 @@ var NativeDocument = (function (exports) {
|
|
|
4953
6612
|
return $cacheNode;
|
|
4954
6613
|
};
|
|
4955
6614
|
|
|
6615
|
+
|
|
6616
|
+
/**
|
|
6617
|
+
* Creates a named anchor section inside the singleton view.
|
|
6618
|
+
* The section can be updated later by passing new content through .render().
|
|
6619
|
+
*
|
|
6620
|
+
* @param {string} name - Unique section name used as the update key
|
|
6621
|
+
* @param {((content: *) => Node)?} [fn] - Optional transform function applied to new content before inserting
|
|
6622
|
+
* @returns {AnchorDocumentFragment} Anchor fragment to place inside the view's DOM
|
|
6623
|
+
* @example
|
|
6624
|
+
* const nameSection = view.createSection('name');
|
|
6625
|
+
* // Later: Card([{ name: 'Jane' }]); // replaces content in nameSection
|
|
6626
|
+
*/
|
|
4956
6627
|
this.createSection = (name, fn) => {
|
|
4957
6628
|
$components = $components || {};
|
|
4958
6629
|
const anchor = Anchor('Component ' + name);
|
|
@@ -4970,6 +6641,19 @@ var NativeDocument = (function (exports) {
|
|
|
4970
6641
|
}
|
|
4971
6642
|
|
|
4972
6643
|
|
|
6644
|
+
/**
|
|
6645
|
+
* Creates a memoized factory that returns a SingletonView instance.
|
|
6646
|
+
* The singleton is created on the first call and reused for all subsequent calls.
|
|
6647
|
+
*
|
|
6648
|
+
* @param {(instance: SingletonView) => Node} fn - View creator function passed to SingletonView
|
|
6649
|
+
* @returns {(...args: any[]) => Node} Function that renders the singleton with the given data
|
|
6650
|
+
* @example
|
|
6651
|
+
* const Card = useSingleton((view) => {
|
|
6652
|
+
* const title = view.createSection('title');
|
|
6653
|
+
* return Div({}, title);
|
|
6654
|
+
* });
|
|
6655
|
+
* Card([{ title: 'Hello' }]);
|
|
6656
|
+
*/
|
|
4973
6657
|
function useSingleton(fn) {
|
|
4974
6658
|
let $cache = null;
|
|
4975
6659
|
|
|
@@ -4982,7 +6666,7 @@ var NativeDocument = (function (exports) {
|
|
|
4982
6666
|
}
|
|
4983
6667
|
|
|
4984
6668
|
const cssPropertyAccumulator = function(initialValue = {}) {
|
|
4985
|
-
|
|
6669
|
+
const data = Validator.isString(initialValue) ? initialValue.split(';').filter(Boolean) : initialValue;
|
|
4986
6670
|
|
|
4987
6671
|
return {
|
|
4988
6672
|
add(key, value) {
|
|
@@ -5009,7 +6693,7 @@ var NativeDocument = (function (exports) {
|
|
|
5009
6693
|
};
|
|
5010
6694
|
|
|
5011
6695
|
const classPropertyAccumulator = function(initialValue = []) {
|
|
5012
|
-
let data = Validator.isString(initialValue) ? initialValue.split(
|
|
6696
|
+
let data = Validator.isString(initialValue) ? initialValue.split(' ').filter(Boolean) : initialValue;
|
|
5013
6697
|
|
|
5014
6698
|
return {
|
|
5015
6699
|
add(key, value = true) {
|
|
@@ -5052,6 +6736,18 @@ var NativeDocument = (function (exports) {
|
|
|
5052
6736
|
};
|
|
5053
6737
|
};
|
|
5054
6738
|
|
|
6739
|
+
/**
|
|
6740
|
+
* Wraps a function so it executes at most once.
|
|
6741
|
+
* Later calls return the cached result without re-executing the function.
|
|
6742
|
+
*
|
|
6743
|
+
* @template T
|
|
6744
|
+
* @param {(...args: any[]) => T} fn - Function to wrap
|
|
6745
|
+
* @returns {(...args: any[]) => T} Memoized function
|
|
6746
|
+
* @example
|
|
6747
|
+
* const init = once(() => expensiveSetup());
|
|
6748
|
+
* init(); // runs setup
|
|
6749
|
+
* init(); // returns cached result
|
|
6750
|
+
*/
|
|
5055
6751
|
const once$1 = (fn) => {
|
|
5056
6752
|
let result = null;
|
|
5057
6753
|
return (...args) => {
|
|
@@ -5063,6 +6759,18 @@ var NativeDocument = (function (exports) {
|
|
|
5063
6759
|
};
|
|
5064
6760
|
};
|
|
5065
6761
|
|
|
6762
|
+
/**
|
|
6763
|
+
* Creates a lazy proxy that calls fn() once on first property access,
|
|
6764
|
+
* then returns properties from the cached result for all later accesses.
|
|
6765
|
+
*
|
|
6766
|
+
* @template T
|
|
6767
|
+
* @param {() => T} fn - Factory function to call once
|
|
6768
|
+
* @returns {T} Proxy to the lazily created object
|
|
6769
|
+
* @example
|
|
6770
|
+
* const store = autoOnce(() => createExpensiveStore());
|
|
6771
|
+
* store.count; // triggers createExpensiveStore() on first access
|
|
6772
|
+
* store.name; // uses a cached result
|
|
6773
|
+
*/
|
|
5066
6774
|
const autoOnce = (fn) => {
|
|
5067
6775
|
let target = null;
|
|
5068
6776
|
return new Proxy({}, {
|
|
@@ -5072,10 +6780,22 @@ var NativeDocument = (function (exports) {
|
|
|
5072
6780
|
}
|
|
5073
6781
|
target = fn();
|
|
5074
6782
|
return target[key];
|
|
5075
|
-
}
|
|
6783
|
+
},
|
|
5076
6784
|
});
|
|
5077
6785
|
};
|
|
5078
6786
|
|
|
6787
|
+
/**
|
|
6788
|
+
* Wraps a function with key-based memoization.
|
|
6789
|
+
* The first argument is used as the cache key; later arguments are passed to fn.
|
|
6790
|
+
*
|
|
6791
|
+
* @template T
|
|
6792
|
+
* @param {(...args: any[]) => T} fn - Function to memoize
|
|
6793
|
+
* @returns {(key: any, ...args: any[]) => T} Memoized function
|
|
6794
|
+
* @example
|
|
6795
|
+
* const getUser = memoize((id) => fetchUser(id));
|
|
6796
|
+
* getUser('user-1', 1); // fetches
|
|
6797
|
+
* getUser('user-1', 1); // returns cached
|
|
6798
|
+
*/
|
|
5079
6799
|
const memoize$1 = (fn) => {
|
|
5080
6800
|
const cache = new Map();
|
|
5081
6801
|
return (...args) => {
|
|
@@ -5090,6 +6810,23 @@ var NativeDocument = (function (exports) {
|
|
|
5090
6810
|
};
|
|
5091
6811
|
};
|
|
5092
6812
|
|
|
6813
|
+
/**
|
|
6814
|
+
* Creates a proxy where each property access memorizes the result of calling fn with the property key.
|
|
6815
|
+
* If fn accepts arguments (fn.length > 0), the proxy returns a memoized function instead.
|
|
6816
|
+
*
|
|
6817
|
+
* @template T
|
|
6818
|
+
* @param {((key: string|symbol, ...args?: any[]) => T)} fn - Factory function
|
|
6819
|
+
* @returns {Record<string|symbol, T>} Proxy with per-key memoized results
|
|
6820
|
+
* @example
|
|
6821
|
+
* // fn with no args — result memoized by key
|
|
6822
|
+
* const icons = autoMemoize((name) => loadIcon(name));
|
|
6823
|
+
* icons.home; // calls loadIcon('home'), caches result
|
|
6824
|
+
* icons.home; // returns cached
|
|
6825
|
+
*
|
|
6826
|
+
* // fn with args — returns a memoized function per key
|
|
6827
|
+
* const formatters = autoMemoize((locale, value) => format(value, locale));
|
|
6828
|
+
* formatters.fr('hello'); // calls format('hello', 'fr'), caches under 'fr'
|
|
6829
|
+
*/
|
|
5093
6830
|
const autoMemoize = (fn) => {
|
|
5094
6831
|
const cache = new Map();
|
|
5095
6832
|
return new Proxy({}, {
|
|
@@ -5104,15 +6841,20 @@ var NativeDocument = (function (exports) {
|
|
|
5104
6841
|
const result = fn(...args, key);
|
|
5105
6842
|
cache.set(key, result);
|
|
5106
6843
|
return result;
|
|
5107
|
-
}
|
|
6844
|
+
};
|
|
5108
6845
|
}
|
|
5109
6846
|
const result = fn(key);
|
|
5110
6847
|
cache.set(key, result);
|
|
5111
6848
|
return result;
|
|
5112
|
-
}
|
|
6849
|
+
},
|
|
5113
6850
|
});
|
|
5114
6851
|
};
|
|
5115
6852
|
|
|
6853
|
+
const WRITE_METHODS = [
|
|
6854
|
+
'use', 'get', 'create', 'createResettable', 'createComposed',
|
|
6855
|
+
'createPersistent', 'createPersistentResettable', 'delete', 'reset',
|
|
6856
|
+
];
|
|
6857
|
+
|
|
5116
6858
|
const StoreFactory = function() {
|
|
5117
6859
|
|
|
5118
6860
|
const $stores = new Map();
|
|
@@ -5126,7 +6868,7 @@ var NativeDocument = (function (exports) {
|
|
|
5126
6868
|
if (!item) {
|
|
5127
6869
|
DebugManager$2.error('Store', `Store.${method}('${name}') : store not found. Did you call Store.create('${name}') first?`);
|
|
5128
6870
|
throw new NativeDocumentError(
|
|
5129
|
-
`Store.${method}('${name}') : store not found
|
|
6871
|
+
`Store.${method}('${name}') : store not found.`,
|
|
5130
6872
|
);
|
|
5131
6873
|
}
|
|
5132
6874
|
return item;
|
|
@@ -5139,7 +6881,7 @@ var NativeDocument = (function (exports) {
|
|
|
5139
6881
|
const readOnlyError = (method) => () => {
|
|
5140
6882
|
DebugManager$2.error('Store', `Store.${context}('${name}') is read-only. '${method}()' is not allowed.`);
|
|
5141
6883
|
throw new NativeDocumentError(
|
|
5142
|
-
`Store.${context}('${name}') is read-only
|
|
6884
|
+
`Store.${context}('${name}') is read-only.`,
|
|
5143
6885
|
);
|
|
5144
6886
|
};
|
|
5145
6887
|
observer.set = readOnlyError('set');
|
|
@@ -5151,7 +6893,7 @@ var NativeDocument = (function (exports) {
|
|
|
5151
6893
|
if(Array.isArray(value)) {
|
|
5152
6894
|
return Observable.array(value, options);
|
|
5153
6895
|
}
|
|
5154
|
-
if(
|
|
6896
|
+
if(Validator.isJson(value)) {
|
|
5155
6897
|
return Observable.object(value, options);
|
|
5156
6898
|
}
|
|
5157
6899
|
return Observable(value, options);
|
|
@@ -5170,7 +6912,7 @@ var NativeDocument = (function (exports) {
|
|
|
5170
6912
|
if ($stores.has(name)) {
|
|
5171
6913
|
DebugManager$2.warn('Store', `Store.create('${name}') : a store with this name already exists. Use Store.get('${name}') to retrieve it.`);
|
|
5172
6914
|
throw new NativeDocumentError(
|
|
5173
|
-
`Store.create('${name}') : a store with this name already exists
|
|
6915
|
+
`Store.create('${name}') : a store with this name already exists.`,
|
|
5174
6916
|
);
|
|
5175
6917
|
}
|
|
5176
6918
|
const observer = $createObservable(value);
|
|
@@ -5191,7 +6933,7 @@ var NativeDocument = (function (exports) {
|
|
|
5191
6933
|
if ($stores.has(name)) {
|
|
5192
6934
|
DebugManager$2.warn('Store', `Store.createResettable('${name}') : a store with this name already exists.`);
|
|
5193
6935
|
throw new NativeDocumentError(
|
|
5194
|
-
`Store.createResettable('${name}') : a store with this name already exists
|
|
6936
|
+
`Store.createResettable('${name}') : a store with this name already exists.`,
|
|
5195
6937
|
);
|
|
5196
6938
|
}
|
|
5197
6939
|
const observer = $createObservable(value, { reset: true });
|
|
@@ -5227,17 +6969,17 @@ var NativeDocument = (function (exports) {
|
|
|
5227
6969
|
if ($stores.has(name)) {
|
|
5228
6970
|
DebugManager$2.warn('Store', `Store.createComposed('${name}') : a store with this name already exists.`);
|
|
5229
6971
|
throw new NativeDocumentError(
|
|
5230
|
-
`Store.createComposed('${name}') : a store with this name already exists
|
|
6972
|
+
`Store.createComposed('${name}') : a store with this name already exists.`,
|
|
5231
6973
|
);
|
|
5232
6974
|
}
|
|
5233
6975
|
if (typeof computation !== 'function') {
|
|
5234
6976
|
throw new NativeDocumentError(
|
|
5235
|
-
`Store.createComposed('${name}') : computation must be a function
|
|
6977
|
+
`Store.createComposed('${name}') : computation must be a function.`,
|
|
5236
6978
|
);
|
|
5237
6979
|
}
|
|
5238
6980
|
if (!Array.isArray(dependencies) || dependencies.length === 0) {
|
|
5239
6981
|
throw new NativeDocumentError(
|
|
5240
|
-
`Store.createComposed('${name}') : dependencies must be a non-empty array of store names
|
|
6982
|
+
`Store.createComposed('${name}') : dependencies must be a non-empty array of store names.`,
|
|
5241
6983
|
);
|
|
5242
6984
|
}
|
|
5243
6985
|
|
|
@@ -5250,7 +6992,7 @@ var NativeDocument = (function (exports) {
|
|
|
5250
6992
|
if (!depItem) {
|
|
5251
6993
|
DebugManager$2.error('Store', `Store.createComposed('${name}') : dependency '${depName}' not found. Create it first.`);
|
|
5252
6994
|
throw new NativeDocumentError(
|
|
5253
|
-
`Store.createComposed('${name}') : dependency store '${depName}' not found
|
|
6995
|
+
`Store.createComposed('${name}') : dependency store '${depName}' not found.`,
|
|
5254
6996
|
);
|
|
5255
6997
|
}
|
|
5256
6998
|
return depItem.observer;
|
|
@@ -5284,13 +7026,13 @@ var NativeDocument = (function (exports) {
|
|
|
5284
7026
|
if (item.composed) {
|
|
5285
7027
|
DebugManager$2.error('Store', `Store.reset('${name}') : composed stores cannot be reset. Their value is derived from dependencies.`);
|
|
5286
7028
|
throw new NativeDocumentError(
|
|
5287
|
-
`Store.reset('${name}') : composed stores cannot be reset
|
|
7029
|
+
`Store.reset('${name}') : composed stores cannot be reset.`,
|
|
5288
7030
|
);
|
|
5289
7031
|
}
|
|
5290
7032
|
if (!item.resettable) {
|
|
5291
7033
|
DebugManager$2.error('Store', `Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`);
|
|
5292
7034
|
throw new NativeDocumentError(
|
|
5293
|
-
`Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create()
|
|
7035
|
+
`Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`,
|
|
5294
7036
|
);
|
|
5295
7037
|
}
|
|
5296
7038
|
item.observer.reset();
|
|
@@ -5311,7 +7053,7 @@ var NativeDocument = (function (exports) {
|
|
|
5311
7053
|
if (item.composed) {
|
|
5312
7054
|
DebugManager$2.error('Store', `Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`);
|
|
5313
7055
|
throw new NativeDocumentError(
|
|
5314
|
-
`Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead
|
|
7056
|
+
`Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`,
|
|
5315
7057
|
);
|
|
5316
7058
|
}
|
|
5317
7059
|
|
|
@@ -5349,7 +7091,9 @@ var NativeDocument = (function (exports) {
|
|
|
5349
7091
|
const { observer: originalObserver, subscribers } = $getStoreOrThrow('follow', name);
|
|
5350
7092
|
const observerFollower = $createObservable(originalObserver.val());
|
|
5351
7093
|
|
|
5352
|
-
const
|
|
7094
|
+
const originalSet = observerFollower.set.bind(observerFollower);
|
|
7095
|
+
const onStoreChange = value => originalSet(value);
|
|
7096
|
+
|
|
5353
7097
|
originalObserver.subscribe(onStoreChange);
|
|
5354
7098
|
|
|
5355
7099
|
$applyReadOnly(observerFollower, name, 'follow');
|
|
@@ -5405,6 +7149,7 @@ var NativeDocument = (function (exports) {
|
|
|
5405
7149
|
item.subscribers.clear();
|
|
5406
7150
|
item.observer.cleanup();
|
|
5407
7151
|
$stores.delete(name);
|
|
7152
|
+
$followersCache.delete(name);
|
|
5408
7153
|
},
|
|
5409
7154
|
/**
|
|
5410
7155
|
* Creates an isolated store group with its own state namespace.
|
|
@@ -5459,6 +7204,26 @@ var NativeDocument = (function (exports) {
|
|
|
5459
7204
|
callback && callback(store);
|
|
5460
7205
|
return store;
|
|
5461
7206
|
},
|
|
7207
|
+
|
|
7208
|
+
/**
|
|
7209
|
+
* Creates a store that is automatically persisted to localStorage.
|
|
7210
|
+
* On creation, the store is initialized with the value from localStorage
|
|
7211
|
+
* if it exists, otherwise falls back to the provided default value.
|
|
7212
|
+
* Every mutation is automatically saved to localStorage.
|
|
7213
|
+
*
|
|
7214
|
+
* @param {string} name - Store name
|
|
7215
|
+
* @param {*} value - Default value if nothing is found in localStorage
|
|
7216
|
+
* @param {string} [localstorage_key] - Custom localStorage key. Defaults to the store name.
|
|
7217
|
+
* @returns {ObservableItem}
|
|
7218
|
+
*
|
|
7219
|
+
* @example
|
|
7220
|
+
* const $theme = Store.createPersistent('theme', 'light');
|
|
7221
|
+
*
|
|
7222
|
+
* $theme.set('dark'); // saved to localStorage automatically
|
|
7223
|
+
*
|
|
7224
|
+
* // With a custom key
|
|
7225
|
+
* const $lang = Store.createPersistent('language', 'en', 'nd:lang');
|
|
7226
|
+
*/
|
|
5462
7227
|
createPersistent(name, value, localstorage_key) {
|
|
5463
7228
|
localstorage_key = localstorage_key || name;
|
|
5464
7229
|
const observer = this.create(name, $getFromStorage(localstorage_key, value));
|
|
@@ -5467,6 +7232,28 @@ var NativeDocument = (function (exports) {
|
|
|
5467
7232
|
observer.subscribe((val) => saver(localstorage_key, val));
|
|
5468
7233
|
return observer;
|
|
5469
7234
|
},
|
|
7235
|
+
|
|
7236
|
+
/**
|
|
7237
|
+
* Creates a resettable store that is automatically persisted to localStorage.
|
|
7238
|
+
* On creation, the store is initialized with the value from localStorage
|
|
7239
|
+
* if it exists, otherwise falls back to the provided default value.
|
|
7240
|
+
* Every mutation is automatically saved to localStorage.
|
|
7241
|
+
* Calling reset() restores the initial value AND removes the localStorage entry.
|
|
7242
|
+
*
|
|
7243
|
+
* @param {string} name - Store name
|
|
7244
|
+
* @param {*} value - Default value if nothing is found in localStorage
|
|
7245
|
+
* @param {string} [localstorage_key] - Custom localStorage key. Defaults to the store name.
|
|
7246
|
+
* @returns {ObservableItem}
|
|
7247
|
+
*
|
|
7248
|
+
* @example
|
|
7249
|
+
* const $filters = Store.createPersistentResettable('filters', { category: null, date: null });
|
|
7250
|
+
*
|
|
7251
|
+
* $filters.set({ category: 'news', date: '2024-01-01' }); // saved to localStorage
|
|
7252
|
+
* $filters.reset(); // restored to { category: null, date: null } + localStorage entry removed
|
|
7253
|
+
*
|
|
7254
|
+
* // With a custom key
|
|
7255
|
+
* const $prefs = Store.createPersistentResettable('preferences', { lang: 'en' }, 'nd:prefs');
|
|
7256
|
+
*/
|
|
5470
7257
|
createPersistentResettable(name, value, localstorage_key) {
|
|
5471
7258
|
localstorage_key = localstorage_key || name;
|
|
5472
7259
|
const observer = this.createResettable(name, $getFromStorage(localstorage_key, value));
|
|
@@ -5480,7 +7267,68 @@ var NativeDocument = (function (exports) {
|
|
|
5480
7267
|
};
|
|
5481
7268
|
|
|
5482
7269
|
return observer;
|
|
5483
|
-
}
|
|
7270
|
+
},
|
|
7271
|
+
/**
|
|
7272
|
+
* Returns a read-only proxy of this store.
|
|
7273
|
+
* All write operations (use, get, create, createResettable, createComposed,
|
|
7274
|
+
* createPersistent, createPersistentResettable, delete, reset) will throw.
|
|
7275
|
+
* Property access returns a read-only follower via follow().
|
|
7276
|
+
*
|
|
7277
|
+
* The recommended pattern is to keep the original store private
|
|
7278
|
+
* and export only the protected version as the public contract.
|
|
7279
|
+
*
|
|
7280
|
+
* @returns {Proxy}
|
|
7281
|
+
*
|
|
7282
|
+
* @example
|
|
7283
|
+
* // store/user.store.js
|
|
7284
|
+
*
|
|
7285
|
+
* const PrivateUserStore = Store.group('user', (group) => {
|
|
7286
|
+
* group.create('profile', null);
|
|
7287
|
+
* group.create('role', 'viewer');
|
|
7288
|
+
* group.create('token', null);
|
|
7289
|
+
* });
|
|
7290
|
+
*
|
|
7291
|
+
* // Only the read-only version is exported
|
|
7292
|
+
* export const UserStore = PrivateUserStore.protected();
|
|
7293
|
+
*
|
|
7294
|
+
* // --- In any other module ---
|
|
7295
|
+
*
|
|
7296
|
+
* import { UserStore } from './store/user.store.js';
|
|
7297
|
+
*
|
|
7298
|
+
* UserStore.profile // ✅ follower read-only
|
|
7299
|
+
* UserStore.follow('role') // ✅
|
|
7300
|
+
* UserStore.has('token') // ✅
|
|
7301
|
+
*
|
|
7302
|
+
* UserStore.use('profile') // ❌ throws — read-only store
|
|
7303
|
+
* UserStore.get('profile') // ❌ throws — read-only store
|
|
7304
|
+
* UserStore.create('x', 1) // ❌ throws — read-only store
|
|
7305
|
+
*/
|
|
7306
|
+
protected() {
|
|
7307
|
+
return new Proxy($api, {
|
|
7308
|
+
get(target, prop) {
|
|
7309
|
+
if (typeof prop === 'symbol' || prop.startsWith('$')) {
|
|
7310
|
+
return target[prop];
|
|
7311
|
+
}
|
|
7312
|
+
if (WRITE_METHODS.includes(prop)) {
|
|
7313
|
+
return () => {
|
|
7314
|
+
throw new NativeDocumentError(
|
|
7315
|
+
`Store.${prop}() is not allowed on a read-only store. Use the original store reference instead.`
|
|
7316
|
+
);
|
|
7317
|
+
};
|
|
7318
|
+
}
|
|
7319
|
+
if (target.has(prop)) {
|
|
7320
|
+
return target.follow(prop);
|
|
7321
|
+
}
|
|
7322
|
+
return target[prop];
|
|
7323
|
+
},
|
|
7324
|
+
set() {
|
|
7325
|
+
throw new NativeDocumentError('This store is read-only.');
|
|
7326
|
+
},
|
|
7327
|
+
deleteProperty() {
|
|
7328
|
+
throw new NativeDocumentError('This store is read-only.');
|
|
7329
|
+
},
|
|
7330
|
+
});
|
|
7331
|
+
},
|
|
5484
7332
|
};
|
|
5485
7333
|
|
|
5486
7334
|
|
|
@@ -5501,17 +7349,20 @@ var NativeDocument = (function (exports) {
|
|
|
5501
7349
|
},
|
|
5502
7350
|
set(target, prop, value) {
|
|
5503
7351
|
DebugManager$2.error('Store', `Forbidden: You cannot overwrite the store key '${String(prop)}'. Use .use('${String(prop)}').set(value) instead.`);
|
|
5504
|
-
throw new NativeDocumentError(
|
|
7352
|
+
throw new NativeDocumentError('Store structure is immutable. Use .set() on the observable.');
|
|
5505
7353
|
},
|
|
5506
7354
|
deleteProperty(target, prop) {
|
|
5507
|
-
throw new NativeDocumentError(
|
|
5508
|
-
}
|
|
7355
|
+
throw new NativeDocumentError('Store keys cannot be deleted.');
|
|
7356
|
+
},
|
|
5509
7357
|
});
|
|
5510
7358
|
};
|
|
5511
7359
|
|
|
5512
7360
|
const Store = StoreFactory();
|
|
5513
7361
|
|
|
5514
|
-
Store.create(
|
|
7362
|
+
Store.create(
|
|
7363
|
+
'locale',
|
|
7364
|
+
(typeof navigator !== 'undefined' ? navigator.language.split('-')[0] : 'en') || 'en'
|
|
7365
|
+
);
|
|
5515
7366
|
|
|
5516
7367
|
const SELF_RENDER$1 = (item) => item;
|
|
5517
7368
|
|
|
@@ -5541,7 +7392,7 @@ var NativeDocument = (function (exports) {
|
|
|
5541
7392
|
const blockEnd = element.endElement();
|
|
5542
7393
|
element.startElement();
|
|
5543
7394
|
|
|
5544
|
-
|
|
7395
|
+
const cache = new Map();
|
|
5545
7396
|
let lastKeyOrder = null;
|
|
5546
7397
|
const keyIds = new Set();
|
|
5547
7398
|
|
|
@@ -5585,9 +7436,9 @@ var NativeDocument = (function (exports) {
|
|
|
5585
7436
|
|
|
5586
7437
|
try {
|
|
5587
7438
|
const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
|
|
5588
|
-
|
|
7439
|
+
const child = ElementCreator.getChild(callback(item, indexObserver));
|
|
5589
7440
|
if(!child) {
|
|
5590
|
-
throw new NativeDocumentError(
|
|
7441
|
+
throw new NativeDocumentError('ForEach child can\'t be null or undefined!');
|
|
5591
7442
|
}
|
|
5592
7443
|
cache.set(keyId, { keyId, isNew: true, child: new WeakRef(child), indexObserver});
|
|
5593
7444
|
} catch (e) {
|
|
@@ -5611,7 +7462,7 @@ var NativeDocument = (function (exports) {
|
|
|
5611
7462
|
};
|
|
5612
7463
|
|
|
5613
7464
|
const diffingDOMUpdates = (parent) => {
|
|
5614
|
-
|
|
7465
|
+
const fragment = document.createDocumentFragment();
|
|
5615
7466
|
const newKeys = Array.from(keyIds);
|
|
5616
7467
|
Array.from(lastKeyOrder);
|
|
5617
7468
|
|
|
@@ -5700,7 +7551,7 @@ var NativeDocument = (function (exports) {
|
|
|
5700
7551
|
const element = Anchor('ForEach Array', configs.isParentUniqueChild);
|
|
5701
7552
|
const blockEnd = element.endElement();
|
|
5702
7553
|
|
|
5703
|
-
|
|
7554
|
+
const cache = new Map();
|
|
5704
7555
|
let lastNumberOfItems = 0;
|
|
5705
7556
|
const isIndexRequired = callback.length >= 2;
|
|
5706
7557
|
|
|
@@ -5731,7 +7582,7 @@ var NativeDocument = (function (exports) {
|
|
|
5731
7582
|
const child = ElementCreator.getChild(callback(item, null));
|
|
5732
7583
|
{
|
|
5733
7584
|
if(!child) {
|
|
5734
|
-
throw new NativeDocumentError(
|
|
7585
|
+
throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
|
|
5735
7586
|
}
|
|
5736
7587
|
}
|
|
5737
7588
|
cache.set(item, { child, indexObserver: null });
|
|
@@ -5743,7 +7594,7 @@ var NativeDocument = (function (exports) {
|
|
|
5743
7594
|
const child = ElementCreator.getChild(callback(item, indexObserver));
|
|
5744
7595
|
{
|
|
5745
7596
|
if(!child) {
|
|
5746
|
-
throw new NativeDocumentError(
|
|
7597
|
+
throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
|
|
5747
7598
|
}
|
|
5748
7599
|
}
|
|
5749
7600
|
cache.set(item, { child, indexObserver });
|
|
@@ -5754,7 +7605,7 @@ var NativeDocument = (function (exports) {
|
|
|
5754
7605
|
const child = ElementCreator.getChild(callback(item, indexKey));
|
|
5755
7606
|
{
|
|
5756
7607
|
if(!child) {
|
|
5757
|
-
throw new NativeDocumentError(
|
|
7608
|
+
throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
|
|
5758
7609
|
}
|
|
5759
7610
|
}
|
|
5760
7611
|
cache.set(item, { child, indexObserver: null });
|
|
@@ -5884,7 +7735,7 @@ var NativeDocument = (function (exports) {
|
|
|
5884
7735
|
const garbageFragment = document.createDocumentFragment();
|
|
5885
7736
|
|
|
5886
7737
|
if(deleted.length > 0) {
|
|
5887
|
-
|
|
7738
|
+
const firstItem = deleted[0];
|
|
5888
7739
|
if(deleted.length === 1) {
|
|
5889
7740
|
removeByItem(firstItem, garbageFragment);
|
|
5890
7741
|
} else if(deleted.length > 1) {
|
|
@@ -5936,7 +7787,7 @@ var NativeDocument = (function (exports) {
|
|
|
5936
7787
|
parent.insertBefore(childA, childBNext);
|
|
5937
7788
|
childA = null;
|
|
5938
7789
|
childB = null;
|
|
5939
|
-
}
|
|
7790
|
+
},
|
|
5940
7791
|
};
|
|
5941
7792
|
Actions.merge = Actions.add;
|
|
5942
7793
|
Actions.push = Actions.add;
|
|
@@ -5977,11 +7828,11 @@ var NativeDocument = (function (exports) {
|
|
|
5977
7828
|
*/
|
|
5978
7829
|
const ShowIf = function(condition, child, { comment = null, shouldKeepInCache = true} = {}) {
|
|
5979
7830
|
if(!Validator.isObservable(condition)) {
|
|
5980
|
-
if(typeof condition ===
|
|
7831
|
+
if(typeof condition === 'boolean') {
|
|
5981
7832
|
return condition ? ElementCreator.getChild(child) : null;
|
|
5982
7833
|
}
|
|
5983
7834
|
|
|
5984
|
-
return DebugManager$2.warn('ShowIf',
|
|
7835
|
+
return DebugManager$2.warn('ShowIf', 'ShowIf : condition must be an Observable or boolean / '+comment, condition);
|
|
5985
7836
|
}
|
|
5986
7837
|
const element = Anchor('Show if : '+(comment || ''));
|
|
5987
7838
|
|
|
@@ -6004,7 +7855,7 @@ var NativeDocument = (function (exports) {
|
|
|
6004
7855
|
}
|
|
6005
7856
|
|
|
6006
7857
|
condition.subscribe((value) => {
|
|
6007
|
-
if(
|
|
7858
|
+
if(value) {
|
|
6008
7859
|
element.appendChild(getChildElement());
|
|
6009
7860
|
return;
|
|
6010
7861
|
}
|
|
@@ -6079,7 +7930,7 @@ var NativeDocument = (function (exports) {
|
|
|
6079
7930
|
if(!Validator.isObservableWhenResult(observer)) {
|
|
6080
7931
|
throw new NativeDocumentError('showWhen observer must be an ObservableWhenResult', {
|
|
6081
7932
|
data: observer,
|
|
6082
|
-
'help': 'Use observer.when(target) to create an ObservableWhenResult'
|
|
7933
|
+
'help': 'Use observer.when(target) to create an ObservableWhenResult',
|
|
6083
7934
|
});
|
|
6084
7935
|
}
|
|
6085
7936
|
return ShowIf(observer, target);
|
|
@@ -6097,7 +7948,7 @@ var NativeDocument = (function (exports) {
|
|
|
6097
7948
|
data: [
|
|
6098
7949
|
'showWhen(observer, target, view)',
|
|
6099
7950
|
'showWhen(observerWhenResult, view)',
|
|
6100
|
-
]
|
|
7951
|
+
],
|
|
6101
7952
|
});
|
|
6102
7953
|
};
|
|
6103
7954
|
|
|
@@ -6124,7 +7975,7 @@ var NativeDocument = (function (exports) {
|
|
|
6124
7975
|
const Match = function($condition, values, shouldKeepInCache = true) {
|
|
6125
7976
|
|
|
6126
7977
|
if(!Validator.isObservable($condition)) {
|
|
6127
|
-
throw new NativeDocumentError(
|
|
7978
|
+
throw new NativeDocumentError('Toggle : condition must be an Observable');
|
|
6128
7979
|
}
|
|
6129
7980
|
|
|
6130
7981
|
const anchor = Anchor('Match');
|
|
@@ -6171,7 +8022,7 @@ var NativeDocument = (function (exports) {
|
|
|
6171
8022
|
shouldKeepInCache && cache.delete(key);
|
|
6172
8023
|
$condition.set([...cache.keys()].at(-1) ?? '');
|
|
6173
8024
|
delete values[key];
|
|
6174
|
-
}
|
|
8025
|
+
},
|
|
6175
8026
|
});
|
|
6176
8027
|
};
|
|
6177
8028
|
|
|
@@ -6193,7 +8044,7 @@ var NativeDocument = (function (exports) {
|
|
|
6193
8044
|
*/
|
|
6194
8045
|
const Switch = function ($condition, onTrue, onFalse) {
|
|
6195
8046
|
if(!Validator.isObservable($condition)) {
|
|
6196
|
-
throw new NativeDocumentError(
|
|
8047
|
+
throw new NativeDocumentError('Toggle : condition must be an Observable');
|
|
6197
8048
|
}
|
|
6198
8049
|
|
|
6199
8050
|
return Match($condition.toBoolean(), {
|
|
@@ -6215,7 +8066,7 @@ var NativeDocument = (function (exports) {
|
|
|
6215
8066
|
*/
|
|
6216
8067
|
const When = function($condition) {
|
|
6217
8068
|
if(!Validator.isObservable($condition)) {
|
|
6218
|
-
throw new NativeDocumentError(
|
|
8069
|
+
throw new NativeDocumentError('When : condition must be an Observable');
|
|
6219
8070
|
}
|
|
6220
8071
|
|
|
6221
8072
|
let $onTrue = null;
|
|
@@ -6232,8 +8083,8 @@ var NativeDocument = (function (exports) {
|
|
|
6232
8083
|
},
|
|
6233
8084
|
toNdElement() {
|
|
6234
8085
|
return Switch($condition, $onTrue, $onFalse);
|
|
6235
|
-
}
|
|
6236
|
-
}
|
|
8086
|
+
},
|
|
8087
|
+
};
|
|
6237
8088
|
};
|
|
6238
8089
|
|
|
6239
8090
|
/**
|
|
@@ -7303,7 +9154,7 @@ var NativeDocument = (function (exports) {
|
|
|
7303
9154
|
$path = '/'+trim($path, '/').replace(/\/+/, '/');
|
|
7304
9155
|
|
|
7305
9156
|
let $pattern = null;
|
|
7306
|
-
|
|
9157
|
+
const $name = $options.name || null;
|
|
7307
9158
|
|
|
7308
9159
|
const $middlewares = $options.middlewares || [];
|
|
7309
9160
|
const $shouldRebuild = $options.shouldRebuild || false;
|
|
@@ -7453,7 +9304,7 @@ var NativeDocument = (function (exports) {
|
|
|
7453
9304
|
}
|
|
7454
9305
|
}
|
|
7455
9306
|
return null;
|
|
7456
|
-
}
|
|
9307
|
+
},
|
|
7457
9308
|
};
|
|
7458
9309
|
|
|
7459
9310
|
function HashRouter() {
|
|
@@ -7687,6 +9538,9 @@ var NativeDocument = (function (exports) {
|
|
|
7687
9538
|
|
|
7688
9539
|
let $lastNodeInserted = null;
|
|
7689
9540
|
|
|
9541
|
+
const $lifecycles = new Map();
|
|
9542
|
+
let $currentPath = null;
|
|
9543
|
+
|
|
7690
9544
|
const getNodeAnchorForLayout = (node, path) => {
|
|
7691
9545
|
const existingAnchor = $routeInstanceAnchors.get(node);
|
|
7692
9546
|
if(existingAnchor) {
|
|
@@ -7724,7 +9578,7 @@ var NativeDocument = (function (exports) {
|
|
|
7724
9578
|
};
|
|
7725
9579
|
|
|
7726
9580
|
const updateContainerByLayout = (layout, node, route, path) => {
|
|
7727
|
-
|
|
9581
|
+
const nodeToInsert = getNodeToInsert(node);
|
|
7728
9582
|
|
|
7729
9583
|
const cachedLayout = $layoutCache.get(nodeToInsert);
|
|
7730
9584
|
if(cachedLayout) {
|
|
@@ -7758,7 +9612,7 @@ var NativeDocument = (function (exports) {
|
|
|
7758
9612
|
updateContainerByLayout(layout, node, route, path);
|
|
7759
9613
|
return;
|
|
7760
9614
|
}
|
|
7761
|
-
|
|
9615
|
+
const nodeToInsert = getNodeToInsert(node);
|
|
7762
9616
|
|
|
7763
9617
|
cleanContainer();
|
|
7764
9618
|
container.appendChild(nodeToInsert);
|
|
@@ -7769,16 +9623,39 @@ var NativeDocument = (function (exports) {
|
|
|
7769
9623
|
if(!state.route) {
|
|
7770
9624
|
return;
|
|
7771
9625
|
}
|
|
9626
|
+
|
|
7772
9627
|
const { route, params, query, path } = state;
|
|
9628
|
+
|
|
9629
|
+
if($currentPath && $currentPath !== path) {
|
|
9630
|
+
$lifecycles.get($currentPath)?.onLeave?.();
|
|
9631
|
+
}
|
|
9632
|
+
|
|
7773
9633
|
if($cache.has(path)) {
|
|
7774
9634
|
const cacheNode = $cache.get(path);
|
|
7775
9635
|
updateContainer(cacheNode, route);
|
|
9636
|
+
|
|
9637
|
+
$lifecycles.get(path)?.onEnter?.(params, query);
|
|
9638
|
+
$currentPath = path;
|
|
9639
|
+
|
|
7776
9640
|
return;
|
|
7777
9641
|
}
|
|
9642
|
+
const pathLifecycles = {};
|
|
9643
|
+
$lifecycles.set(path, pathLifecycles);
|
|
9644
|
+
|
|
9645
|
+
|
|
9646
|
+
|
|
7778
9647
|
const Component = route.component();
|
|
7779
|
-
const node = Component({
|
|
9648
|
+
const node = Component({
|
|
9649
|
+
params,
|
|
9650
|
+
query,
|
|
9651
|
+
onEnter: (cb) => { pathLifecycles.onEnter = cb; },
|
|
9652
|
+
onLeave: (cb) => { pathLifecycles.onLeave = cb; },
|
|
9653
|
+
});
|
|
7780
9654
|
$cache.set(path, node);
|
|
7781
9655
|
updateContainer(node, route, path);
|
|
9656
|
+
|
|
9657
|
+
pathLifecycles.onEnter?.(params, query);
|
|
9658
|
+
$currentPath = path;
|
|
7782
9659
|
};
|
|
7783
9660
|
|
|
7784
9661
|
router.subscribe(handleCurrentRouterState);
|
|
@@ -7840,7 +9717,7 @@ var NativeDocument = (function (exports) {
|
|
|
7840
9717
|
...options,
|
|
7841
9718
|
middlewares: RouteGroupHelper.fullMiddlewares($groupTree, options?.middlewares || []),
|
|
7842
9719
|
name: options?.name ? RouteGroupHelper.fullName($groupTree, options.name) : null,
|
|
7843
|
-
layout: options?.layout || RouteGroupHelper.layout($groupTree)
|
|
9720
|
+
layout: options?.layout || RouteGroupHelper.layout($groupTree),
|
|
7844
9721
|
});
|
|
7845
9722
|
$routes.push(route);
|
|
7846
9723
|
if(route.name()) {
|
|
@@ -7903,7 +9780,7 @@ var NativeDocument = (function (exports) {
|
|
|
7903
9780
|
route,
|
|
7904
9781
|
params: [],
|
|
7905
9782
|
query: [],
|
|
7906
|
-
path: route.url({ name: target })
|
|
9783
|
+
path: route.url({ name: target }),
|
|
7907
9784
|
};
|
|
7908
9785
|
}
|
|
7909
9786
|
}
|
|
@@ -7916,7 +9793,7 @@ var NativeDocument = (function (exports) {
|
|
|
7916
9793
|
route,
|
|
7917
9794
|
params: target.params,
|
|
7918
9795
|
query: target.query,
|
|
7919
|
-
path: route.url({ ...target })
|
|
9796
|
+
path: route.url({ ...target }),
|
|
7920
9797
|
};
|
|
7921
9798
|
}
|
|
7922
9799
|
|
|
@@ -8103,7 +9980,7 @@ var NativeDocument = (function (exports) {
|
|
|
8103
9980
|
|
|
8104
9981
|
const $interceptors = {
|
|
8105
9982
|
request: [],
|
|
8106
|
-
response: []
|
|
9983
|
+
response: [],
|
|
8107
9984
|
};
|
|
8108
9985
|
|
|
8109
9986
|
this.interceptors = {
|
|
@@ -8112,7 +9989,7 @@ var NativeDocument = (function (exports) {
|
|
|
8112
9989
|
},
|
|
8113
9990
|
request: (callback) => {
|
|
8114
9991
|
$interceptors.request.push(callback);
|
|
8115
|
-
}
|
|
9992
|
+
},
|
|
8116
9993
|
};
|
|
8117
9994
|
|
|
8118
9995
|
this.fetch = async function(method, endpoint, params = {}, options = {}) {
|
|
@@ -8129,7 +10006,7 @@ var NativeDocument = (function (exports) {
|
|
|
8129
10006
|
let configs = {
|
|
8130
10007
|
method,
|
|
8131
10008
|
headers: {
|
|
8132
|
-
...(options.headers || {})
|
|
10009
|
+
...(options.headers || {}),
|
|
8133
10010
|
},
|
|
8134
10011
|
};
|
|
8135
10012
|
if(params) {
|