@shortfuse/materialdesignweb 0.7.6 → 0.9.0
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/README.md +87 -90
- package/bin/mdw-css.js +1 -1
- package/components/Badge.js +14 -7
- package/components/Body.js +3 -0
- package/components/BottomAppBar.js +4 -13
- package/components/BottomSheet.js +424 -0
- package/components/Box.js +20 -28
- package/components/Button.js +85 -79
- package/components/Button.md +9 -9
- package/components/Card.js +60 -72
- package/components/Checkbox.js +33 -42
- package/components/CheckboxIcon.js +11 -29
- package/components/Chip.js +13 -11
- package/components/Dialog.js +214 -394
- package/components/DialogActions.js +2 -2
- package/components/Display.js +55 -0
- package/components/Divider.js +3 -3
- package/components/Fab.js +83 -18
- package/components/FabContainer.js +48 -0
- package/components/FilterChip.js +35 -33
- package/components/Grid.js +176 -0
- package/components/Headline.js +5 -28
- package/components/Icon.js +61 -76
- package/components/IconButton.js +72 -126
- package/components/Input.js +859 -1
- package/components/InputChip.js +161 -0
- package/components/Label.js +3 -0
- package/components/List.js +4 -6
- package/components/ListItem.js +46 -30
- package/components/ListOption.js +86 -53
- package/components/Listbox.js +248 -0
- package/components/Menu.js +69 -528
- package/components/MenuItem.js +33 -36
- package/components/NavBar.js +49 -86
- package/components/NavDrawer.js +16 -15
- package/components/NavDrawerItem.js +4 -5
- package/components/NavItem.js +58 -41
- package/components/NavRail.js +30 -20
- package/components/NavRailItem.js +8 -3
- package/components/Page.js +105 -0
- package/components/Pane.js +11 -274
- package/components/Popup.js +29 -0
- package/components/Progress.js +24 -23
- package/components/Radio.js +40 -36
- package/components/RadioIcon.js +12 -16
- package/components/Ripple.js +13 -10
- package/components/Root.js +209 -0
- package/components/Scrim.js +87 -0
- package/components/Search.js +77 -0
- package/components/SegmentedButton.js +33 -45
- package/components/SegmentedButtonGroup.js +25 -13
- package/components/Select.js +10 -11
- package/components/Shape.js +5 -65
- package/components/SideSheet.js +308 -0
- package/components/Slider.js +108 -78
- package/components/Snackbar.js +26 -21
- package/components/SnackbarContainer.js +42 -0
- package/components/Surface.js +17 -12
- package/components/Switch.js +45 -24
- package/components/SwitchIcon.js +49 -39
- package/components/Tab.js +64 -43
- package/components/TabContent.js +5 -3
- package/components/TabList.js +62 -34
- package/components/TabPanel.js +11 -8
- package/components/Table.js +116 -0
- package/components/TextArea.js +54 -50
- package/components/Title.js +4 -9
- package/components/Tooltip.js +44 -22
- package/components/TopAppBar.js +68 -172
- package/constants/shapes.js +36 -0
- package/constants/typography.js +127 -0
- package/core/Composition.js +1164 -645
- package/core/CompositionAdapter.js +314 -0
- package/core/CustomElement.js +701 -286
- package/core/css.js +121 -15
- package/core/customTypes.js +157 -40
- package/core/dom.js +17 -11
- package/{utils → core}/jsonMergePatch.js +42 -18
- package/core/observe.js +343 -244
- package/core/optimizations.js +23 -0
- package/core/template.js +19 -56
- package/core/uid.js +13 -0
- package/dist/index.min.js +85 -184
- package/dist/index.min.js.map +4 -4
- package/dist/meta.json +1 -1
- package/dom/HTMLOptionsCollectionProxy.js +106 -0
- package/loaders/palette.js +13 -0
- package/loaders/theme.js +12 -0
- package/mixins/AriaReflectorMixin.js +53 -14
- package/mixins/AriaToolbarMixin.js +5 -3
- package/mixins/ControlMixin.js +92 -41
- package/mixins/DelegatesFocusMixin.js +54 -0
- package/mixins/DensityMixin.js +2 -3
- package/mixins/ElevationMixin.js +62 -0
- package/mixins/FlexableMixin.js +67 -39
- package/mixins/FormAssociatedMixin.js +71 -16
- package/mixins/HyperlinkMixin.js +66 -0
- package/mixins/InputMixin.js +205 -39
- package/mixins/KeyboardNavMixin.js +22 -7
- package/mixins/NavigationListenerMixin.js +33 -0
- package/mixins/PopupMixin.js +725 -0
- package/mixins/RTLObserverMixin.js +0 -1
- package/mixins/ResizeObserverMixin.js +16 -5
- package/mixins/RippleMixin.js +11 -10
- package/mixins/ScrollListenerMixin.js +42 -33
- package/mixins/SemiStickyMixin.js +97 -0
- package/mixins/ShapeMaskedMixin.js +117 -0
- package/mixins/ShapeMixin.js +17 -194
- package/mixins/StateMixin.js +80 -39
- package/mixins/TextFieldMixin.js +139 -183
- package/mixins/ThemableMixin.js +71 -161
- package/mixins/TooltipTriggerMixin.js +292 -366
- package/mixins/TouchTargetMixin.js +5 -4
- package/mixins/TypographyMixin.js +121 -0
- package/package.json +111 -71
- package/services/rtl.js +10 -0
- package/services/svgAlias.js +17 -0
- package/{theming/index.js → services/theme.js} +25 -175
- package/types/bin/mdw-css.d.ts +3 -0
- package/types/bin/mdw-css.d.ts.map +1 -0
- package/types/components/Badge.d.ts +39 -0
- package/types/components/Badge.d.ts.map +1 -0
- package/types/components/Body.d.ts +29 -0
- package/types/components/Body.d.ts.map +1 -0
- package/types/components/BottomAppBar.d.ts +73 -0
- package/types/components/BottomAppBar.d.ts.map +1 -0
- package/types/components/BottomSheet.d.ts +109 -0
- package/types/components/BottomSheet.d.ts.map +1 -0
- package/types/components/Box.d.ts +16 -0
- package/types/components/Box.d.ts.map +1 -0
- package/types/components/Button.d.ts +714 -0
- package/types/components/Button.d.ts.map +1 -0
- package/types/components/Card.d.ts +412 -0
- package/types/components/Card.d.ts.map +1 -0
- package/types/components/Checkbox.d.ts +205 -0
- package/types/components/Checkbox.d.ts.map +1 -0
- package/types/components/CheckboxIcon.d.ts +44 -0
- package/types/components/CheckboxIcon.d.ts.map +1 -0
- package/types/components/Chip.d.ts +1425 -0
- package/types/components/Chip.d.ts.map +1 -0
- package/types/components/Dialog.d.ts +286 -0
- package/types/components/Dialog.d.ts.map +1 -0
- package/types/components/DialogActions.d.ts +4 -0
- package/types/components/DialogActions.d.ts.map +1 -0
- package/types/components/Display.d.ts +45 -0
- package/types/components/Display.d.ts.map +1 -0
- package/types/components/Divider.d.ts +10 -0
- package/types/components/Divider.d.ts.map +1 -0
- package/types/components/Fab.d.ts +741 -0
- package/types/components/Fab.d.ts.map +1 -0
- package/types/components/FabContainer.d.ts +10 -0
- package/types/components/FabContainer.d.ts.map +1 -0
- package/types/components/FilterChip.d.ts +4283 -0
- package/types/components/FilterChip.d.ts.map +1 -0
- package/types/components/Grid.d.ts +37 -0
- package/types/components/Grid.d.ts.map +1 -0
- package/types/components/Headline.d.ts +47 -0
- package/types/components/Headline.d.ts.map +1 -0
- package/types/components/Icon.d.ts +103 -0
- package/types/components/Icon.d.ts.map +1 -0
- package/types/components/IconButton.d.ts +1486 -0
- package/types/components/IconButton.d.ts.map +1 -0
- package/types/components/Input.d.ts +51288 -0
- package/types/components/Input.d.ts.map +1 -0
- package/types/components/InputChip.d.ts +243 -0
- package/types/components/InputChip.d.ts.map +1 -0
- package/types/components/Label.d.ts +29 -0
- package/types/components/Label.d.ts.map +1 -0
- package/types/components/List.d.ts +31 -0
- package/types/components/List.d.ts.map +1 -0
- package/types/components/ListItem.d.ts +349 -0
- package/types/components/ListItem.d.ts.map +1 -0
- package/types/components/ListOption.d.ts +1493 -0
- package/types/components/ListOption.d.ts.map +1 -0
- package/types/components/Listbox.d.ts +12012 -0
- package/types/components/Listbox.d.ts.map +1 -0
- package/types/components/Menu.d.ts +119 -0
- package/types/components/Menu.d.ts.map +1 -0
- package/types/components/MenuItem.d.ts +3109 -0
- package/types/components/MenuItem.d.ts.map +1 -0
- package/types/components/NavBar.d.ts +18 -0
- package/types/components/NavBar.d.ts.map +1 -0
- package/types/components/NavBarItem.d.ts +186 -0
- package/types/components/NavBarItem.d.ts.map +1 -0
- package/types/components/NavDrawer.d.ts +108 -0
- package/types/components/NavDrawer.d.ts.map +1 -0
- package/types/components/NavDrawerItem.d.ts +186 -0
- package/types/components/NavDrawerItem.d.ts.map +1 -0
- package/types/components/NavItem.d.ts +190 -0
- package/types/components/NavItem.d.ts.map +1 -0
- package/types/components/NavRail.d.ts +109 -0
- package/types/components/NavRail.d.ts.map +1 -0
- package/types/components/NavRailItem.d.ts +186 -0
- package/types/components/NavRailItem.d.ts.map +1 -0
- package/types/components/Page.d.ts +24 -0
- package/types/components/Page.d.ts.map +1 -0
- package/types/components/Pane.d.ts +44 -0
- package/types/components/Pane.d.ts.map +1 -0
- package/types/components/Popup.d.ts +76 -0
- package/types/components/Popup.d.ts.map +1 -0
- package/types/components/Progress.d.ts +19 -0
- package/types/components/Progress.d.ts.map +1 -0
- package/types/components/Radio.d.ts +199 -0
- package/types/components/Radio.d.ts.map +1 -0
- package/types/components/RadioIcon.d.ts +46 -0
- package/types/components/RadioIcon.d.ts.map +1 -0
- package/types/components/Ripple.d.ts +34 -0
- package/types/components/Ripple.d.ts.map +1 -0
- package/types/components/Root.d.ts +68 -0
- package/types/components/Root.d.ts.map +1 -0
- package/types/components/Scrim.d.ts +6 -0
- package/types/components/Scrim.d.ts.map +1 -0
- package/types/components/Search.d.ts +16 -0
- package/types/components/Search.d.ts.map +1 -0
- package/types/components/SegmentedButton.d.ts +718 -0
- package/types/components/SegmentedButton.d.ts.map +1 -0
- package/types/components/SegmentedButtonGroup.d.ts +44 -0
- package/types/components/SegmentedButtonGroup.d.ts.map +1 -0
- package/types/components/Select.d.ts +1361 -0
- package/types/components/Select.d.ts.map +1 -0
- package/types/components/Shape.d.ts +10 -0
- package/types/components/Shape.d.ts.map +1 -0
- package/types/components/SideSheet.d.ts +106 -0
- package/types/components/SideSheet.d.ts.map +1 -0
- package/types/components/Slider.d.ts +382 -0
- package/types/components/Slider.d.ts.map +1 -0
- package/types/components/Snackbar.d.ts +65 -0
- package/types/components/Snackbar.d.ts.map +1 -0
- package/types/components/SnackbarContainer.d.ts +6 -0
- package/types/components/SnackbarContainer.d.ts.map +1 -0
- package/types/components/Surface.d.ts +45 -0
- package/types/components/Surface.d.ts.map +1 -0
- package/types/components/Switch.d.ts +183 -0
- package/types/components/Switch.d.ts.map +1 -0
- package/types/components/SwitchIcon.d.ts +169 -0
- package/types/components/SwitchIcon.d.ts.map +1 -0
- package/types/components/Tab.d.ts +879 -0
- package/types/components/Tab.d.ts.map +1 -0
- package/types/components/TabContent.d.ts +122 -0
- package/types/components/TabContent.d.ts.map +1 -0
- package/types/components/TabList.d.ts +6266 -0
- package/types/components/TabList.d.ts.map +1 -0
- package/types/components/TabPanel.d.ts +28 -0
- package/types/components/TabPanel.d.ts.map +1 -0
- package/types/components/Table.d.ts +2 -0
- package/types/components/Table.d.ts.map +1 -0
- package/types/components/TextArea.d.ts +1394 -0
- package/types/components/TextArea.d.ts.map +1 -0
- package/types/components/Title.d.ts +47 -0
- package/types/components/Title.d.ts.map +1 -0
- package/types/components/Tooltip.d.ts +49 -0
- package/types/components/Tooltip.d.ts.map +1 -0
- package/types/components/TopAppBar.d.ts +130 -0
- package/types/components/TopAppBar.d.ts.map +1 -0
- package/types/constants/colorKeywords.d.ts +2 -0
- package/types/constants/colorKeywords.d.ts.map +1 -0
- package/types/constants/shapes.d.ts +38 -0
- package/types/constants/shapes.d.ts.map +1 -0
- package/types/constants/typography.d.ts +212 -0
- package/types/constants/typography.d.ts.map +1 -0
- package/types/core/Composition.d.ts +252 -0
- package/types/core/Composition.d.ts.map +1 -0
- package/types/core/CompositionAdapter.d.ts +92 -0
- package/types/core/CompositionAdapter.d.ts.map +1 -0
- package/types/core/CustomElement.d.ts +302 -0
- package/types/core/CustomElement.d.ts.map +1 -0
- package/types/core/css.d.ts +44 -0
- package/types/core/css.d.ts.map +1 -0
- package/types/core/customTypes.d.ts +26 -0
- package/types/core/customTypes.d.ts.map +1 -0
- package/types/core/dom.d.ts +32 -0
- package/types/core/dom.d.ts.map +1 -0
- package/types/core/jsonMergePatch.d.ts +31 -0
- package/types/core/jsonMergePatch.d.ts.map +1 -0
- package/types/core/observe.d.ts +113 -0
- package/types/core/observe.d.ts.map +1 -0
- package/types/core/optimizations.d.ts +7 -0
- package/types/core/optimizations.d.ts.map +1 -0
- package/types/core/template.d.ts +47 -0
- package/types/core/template.d.ts.map +1 -0
- package/types/core/uid.d.ts +6 -0
- package/types/core/uid.d.ts.map +1 -0
- package/types/dom/HTMLOptionsCollectionProxy.d.ts +18 -0
- package/types/dom/HTMLOptionsCollectionProxy.d.ts.map +1 -0
- package/types/index.d.ts +88 -0
- package/types/index.d.ts.map +1 -0
- package/types/loaders/palette.d.ts +2 -0
- package/types/loaders/palette.d.ts.map +1 -0
- package/types/loaders/theme.d.ts +2 -0
- package/types/loaders/theme.d.ts.map +1 -0
- package/types/mixins/AriaReflectorMixin.d.ts +23 -0
- package/types/mixins/AriaReflectorMixin.d.ts.map +1 -0
- package/types/mixins/AriaToolbarMixin.d.ts +32 -0
- package/types/mixins/AriaToolbarMixin.d.ts.map +1 -0
- package/types/mixins/ControlMixin.d.ts +124 -0
- package/types/mixins/ControlMixin.d.ts.map +1 -0
- package/types/mixins/DelegatesFocusMixin.d.ts +5 -0
- package/types/mixins/DelegatesFocusMixin.d.ts.map +1 -0
- package/types/mixins/DensityMixin.d.ts +5 -0
- package/types/mixins/DensityMixin.d.ts.map +1 -0
- package/types/mixins/ElevationMixin.d.ts +33 -0
- package/types/mixins/ElevationMixin.d.ts.map +1 -0
- package/types/mixins/FlexableMixin.d.ts +13 -0
- package/types/mixins/FlexableMixin.d.ts.map +1 -0
- package/types/mixins/FormAssociatedMixin.d.ts +122 -0
- package/types/mixins/FormAssociatedMixin.d.ts.map +1 -0
- package/types/mixins/HyperlinkMixin.d.ts +22 -0
- package/types/mixins/HyperlinkMixin.d.ts.map +1 -0
- package/types/mixins/InputMixin.d.ts +179 -0
- package/types/mixins/InputMixin.d.ts.map +1 -0
- package/types/mixins/KeyboardNavMixin.d.ts +47 -0
- package/types/mixins/KeyboardNavMixin.d.ts.map +1 -0
- package/types/mixins/NavigationListenerMixin.d.ts +8 -0
- package/types/mixins/NavigationListenerMixin.d.ts.map +1 -0
- package/types/mixins/PopupMixin.d.ts +82 -0
- package/types/mixins/PopupMixin.d.ts.map +1 -0
- package/types/mixins/RTLObserverMixin.d.ts +7 -0
- package/types/mixins/RTLObserverMixin.d.ts.map +1 -0
- package/types/mixins/ResizeObserverMixin.d.ts +12 -0
- package/types/mixins/ResizeObserverMixin.d.ts.map +1 -0
- package/types/mixins/RippleMixin.d.ts +92 -0
- package/types/mixins/RippleMixin.d.ts.map +1 -0
- package/types/mixins/ScrollListenerMixin.d.ts +41 -0
- package/types/mixins/ScrollListenerMixin.d.ts.map +1 -0
- package/types/mixins/SemiStickyMixin.d.ts +50 -0
- package/types/mixins/SemiStickyMixin.d.ts.map +1 -0
- package/types/mixins/ShapeMaskedMixin.d.ts +9 -0
- package/types/mixins/ShapeMaskedMixin.d.ts.map +1 -0
- package/types/mixins/ShapeMixin.d.ts +38 -0
- package/types/mixins/ShapeMixin.d.ts.map +1 -0
- package/types/mixins/StateMixin.d.ts +27 -0
- package/types/mixins/StateMixin.d.ts.map +1 -0
- package/types/mixins/TextFieldMixin.d.ts +1354 -0
- package/types/mixins/TextFieldMixin.d.ts.map +1 -0
- package/types/mixins/ThemableMixin.d.ts +9 -0
- package/types/mixins/ThemableMixin.d.ts.map +1 -0
- package/types/mixins/TooltipTriggerMixin.d.ts +106 -0
- package/types/mixins/TooltipTriggerMixin.d.ts.map +1 -0
- package/types/mixins/TouchTargetMixin.d.ts +3 -0
- package/types/mixins/TouchTargetMixin.d.ts.map +1 -0
- package/types/mixins/TypographyMixin.d.ts +17 -0
- package/types/mixins/TypographyMixin.d.ts.map +1 -0
- package/types/services/rtl.d.ts +3 -0
- package/types/services/rtl.d.ts.map +1 -0
- package/types/services/svgAlias.d.ts +13 -0
- package/types/services/svgAlias.d.ts.map +1 -0
- package/types/services/theme.d.ts +45 -0
- package/types/services/theme.d.ts.map +1 -0
- package/types/utils/cli.d.ts +3 -0
- package/types/utils/cli.d.ts.map +1 -0
- package/types/utils/function.d.ts +3 -0
- package/types/utils/function.d.ts.map +1 -0
- package/types/utils/jsx-runtime.d.ts +20 -0
- package/types/utils/jsx-runtime.d.ts.map +1 -0
- package/types/utils/material-color/blend.d.ts +34 -0
- package/types/utils/material-color/blend.d.ts.map +1 -0
- package/types/utils/material-color/hct/Cam16.d.ts +142 -0
- package/types/utils/material-color/hct/Cam16.d.ts.map +1 -0
- package/types/utils/material-color/hct/Hct.d.ts +93 -0
- package/types/utils/material-color/hct/Hct.d.ts.map +1 -0
- package/types/utils/material-color/hct/ViewingConditions.d.ts +69 -0
- package/types/utils/material-color/hct/ViewingConditions.d.ts.map +1 -0
- package/types/utils/material-color/hct/hctSolver.d.ts +30 -0
- package/types/utils/material-color/hct/hctSolver.d.ts.map +1 -0
- package/types/utils/material-color/helper.d.ts +8 -0
- package/types/utils/material-color/helper.d.ts.map +1 -0
- package/types/utils/material-color/palettes/CorePalette.d.ts +75 -0
- package/types/utils/material-color/palettes/CorePalette.d.ts.map +1 -0
- package/types/utils/material-color/palettes/TonalPalette.d.ts +38 -0
- package/types/utils/material-color/palettes/TonalPalette.d.ts.map +1 -0
- package/types/utils/material-color/scheme/Scheme.d.ts +264 -0
- package/types/utils/material-color/scheme/Scheme.d.ts.map +1 -0
- package/types/utils/material-color/utils/color.d.ts +172 -0
- package/types/utils/material-color/utils/color.d.ts.map +1 -0
- package/types/utils/material-color/utils/math.d.ts +94 -0
- package/types/utils/material-color/utils/math.d.ts.map +1 -0
- package/types/utils/pixelmatch.d.ts +22 -0
- package/types/utils/pixelmatch.d.ts.map +1 -0
- package/types/utils/popup.d.ts +106 -0
- package/types/utils/popup.d.ts.map +1 -0
- package/types/utils/searchParams.d.ts +3 -0
- package/types/utils/searchParams.d.ts.map +1 -0
- package/types/utils/svg.d.ts +7 -0
- package/types/utils/svg.d.ts.map +1 -0
- package/utils/{hct → material-color}/blend.js +8 -10
- package/utils/{hct → material-color/hct}/Cam16.js +196 -69
- package/utils/{hct → material-color/hct}/Hct.js +61 -19
- package/utils/{hct → material-color/hct}/ViewingConditions.js +3 -3
- package/utils/{hct → material-color/hct}/hctSolver.js +9 -16
- package/utils/{hct → material-color}/helper.js +11 -18
- package/utils/{hct → material-color/palettes}/CorePalette.js +79 -19
- package/utils/{hct → material-color/palettes}/TonalPalette.js +12 -4
- package/utils/material-color/scheme/Scheme.js +376 -0
- package/utils/{hct/colorUtils.js → material-color/utils/color.js} +61 -1
- package/utils/pixelmatch.js +360 -0
- package/utils/popup.js +127 -30
- package/utils/searchParams.js +19 -0
- package/components/ExtendedFab.js +0 -36
- package/components/Layout.js +0 -35
- package/components/ListSelect.js +0 -220
- package/components/Nav.js +0 -40
- package/components/Option.js +0 -91
- package/core/ICustomElement.d.ts +0 -291
- package/core/ICustomElement.js +0 -1
- package/core/identify.js +0 -40
- package/core/typings.d.ts +0 -136
- package/core/typings.js +0 -1
- package/mixins/SurfaceMixin.js +0 -181
- package/theming/loader.js +0 -22
- package/utils/hct/Scheme.js +0 -587
- /package/{utils/color_keywords.js → constants/colorKeywords.js} +0 -0
- /package/utils/{hct/mathUtils.js → material-color/utils/math.js} +0 -0
package/core/Composition.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/* eslint-disable sort-class-members/sort-class-members */
|
|
2
|
+
|
|
3
|
+
import CompositionAdapter from './CompositionAdapter.js';
|
|
2
4
|
import { generateCSSStyleSheets, generateHTMLStyleElements } from './css.js';
|
|
3
|
-
import { identifierFromElement } from './identify.js';
|
|
4
5
|
import { observeFunction } from './observe.js';
|
|
6
|
+
import { createEmptyComment, createEmptyTextNode } from './optimizations.js';
|
|
5
7
|
import { generateFragment, inlineFunctions } from './template.js';
|
|
8
|
+
import { generateUID } from './uid.js';
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* @template T
|
|
9
|
-
* @typedef {Composition<?>|HTMLStyleElement|CSSStyleSheet|DocumentFragment|
|
|
12
|
+
* @typedef {Composition<?>|HTMLStyleElement|CSSStyleSheet|DocumentFragment|string} CompositionPart
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
15
|
/**
|
|
@@ -18,24 +21,363 @@ import { generateFragment, inlineFunctions } from './template.js';
|
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* @template T
|
|
21
|
-
* @typedef {Object}
|
|
22
|
-
* @prop {
|
|
23
|
-
* @prop {
|
|
24
|
+
* @typedef {Object} RenderOptions
|
|
25
|
+
* @prop {T} [defaults] what
|
|
26
|
+
* @prop {T} [store] what
|
|
27
|
+
* @prop {DocumentFragment|HTMLElement|Element} [target] where
|
|
28
|
+
* @prop {ShadowRoot} [shadowRoot] where
|
|
29
|
+
* @prop {any} [context] `this` on callbacks/events
|
|
30
|
+
* @prop {any} [injections]
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** @typedef {HTMLElementEventMap & { input: InputEvent; } } HTMLElementEventMapFixed */
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {(
|
|
37
|
+
* Pick<HTMLElementEventMapFixed,
|
|
38
|
+
* 'auxclick' |
|
|
39
|
+
* 'beforeinput' |
|
|
40
|
+
* 'click' |
|
|
41
|
+
* 'compositionstart' |
|
|
42
|
+
* 'contextmenu' |
|
|
43
|
+
* 'drag' |
|
|
44
|
+
* 'dragenter' |
|
|
45
|
+
* 'dragover' |
|
|
46
|
+
* 'dragstart' |
|
|
47
|
+
* 'drop' |
|
|
48
|
+
* 'invalid' |
|
|
49
|
+
* 'keydown' |
|
|
50
|
+
* 'keypress' |
|
|
51
|
+
* 'keyup' |
|
|
52
|
+
* 'mousedown' |
|
|
53
|
+
* 'mousemove' |
|
|
54
|
+
* 'mouseout' |
|
|
55
|
+
* 'mouseover' |
|
|
56
|
+
* 'mouseup' |
|
|
57
|
+
* 'pointerdown' |
|
|
58
|
+
* 'pointermove' |
|
|
59
|
+
* 'pointerout' |
|
|
60
|
+
* 'pointerover' |
|
|
61
|
+
* 'pointerup' |
|
|
62
|
+
* 'reset' |
|
|
63
|
+
* 'selectstart' |
|
|
64
|
+
* 'submit' |
|
|
65
|
+
* 'touchend' |
|
|
66
|
+
* 'touchmove' |
|
|
67
|
+
* 'touchstart' |
|
|
68
|
+
* 'wheel'
|
|
69
|
+
* >
|
|
70
|
+
* )} HTMLElementCancellableEventMap
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @typedef {(
|
|
75
|
+
* HTMLElementEventMapFixed
|
|
76
|
+
* & {[P in keyof HTMLElementCancellableEventMap as `~${P}`]: HTMLElementCancellableEventMap[P]}
|
|
77
|
+
* & Record<string, Event|CustomEvent>
|
|
78
|
+
* )} CompositionEventMap
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @template {any} T
|
|
83
|
+
* @template {keyof CompositionEventMap} [K = keyof CompositionEventMap]
|
|
84
|
+
* @typedef {{
|
|
85
|
+
* type?: K
|
|
86
|
+
* tag?: string,
|
|
87
|
+
* capture?: boolean;
|
|
88
|
+
* once?: boolean;
|
|
89
|
+
* passive?: boolean;
|
|
90
|
+
* signal?: AbortSignal;
|
|
91
|
+
* handleEvent?: (
|
|
92
|
+
* this: T,
|
|
93
|
+
* event: (K extends keyof CompositionEventMap ? CompositionEventMap[K] : Event) & {currentTarget:HTMLElement}
|
|
94
|
+
* ) => any;
|
|
95
|
+
* prop?: string;
|
|
96
|
+
* deepProp?: string[],
|
|
97
|
+
* }} CompositionEventListener
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @template T
|
|
102
|
+
* @typedef {{
|
|
103
|
+
* [P in keyof CompositionEventMap]?: (keyof T & string)
|
|
104
|
+
* | ((this: T, event: CompositionEventMap[P] & {currentTarget:HTMLElement}) => any)
|
|
105
|
+
* | CompositionEventListener<T, P>
|
|
106
|
+
* }} CompositionEventListenerObject
|
|
24
107
|
*/
|
|
25
108
|
|
|
26
109
|
/**
|
|
27
110
|
* @template {any} T
|
|
28
111
|
* @typedef {Object} NodeBindEntry
|
|
29
|
-
* @prop {string}
|
|
30
|
-
* @prop {number}
|
|
31
|
-
* @prop {string}
|
|
112
|
+
* @prop {string} [key]
|
|
113
|
+
* @prop {number} [index]
|
|
114
|
+
* @prop {string} tag
|
|
115
|
+
* @prop {string|number} subnode Index of childNode or attrName
|
|
116
|
+
* @prop {string[]} props
|
|
117
|
+
* @prop {string[][]} deepProps
|
|
32
118
|
* @prop {boolean} [negate]
|
|
33
119
|
* @prop {boolean} [doubleNegate]
|
|
34
|
-
* @prop {Function} [
|
|
35
|
-
* @prop {
|
|
120
|
+
* @prop {Function} [expression]
|
|
121
|
+
* @prop {(options: RenderOptions<?>, element: Element, changes:any, data:any) => any} [render] custom render function
|
|
122
|
+
* @prop {CompositionEventListener<T>[]} [listeners]
|
|
123
|
+
* @prop {Composition<any>} [composition] // Sub composition templating (eg: array)
|
|
36
124
|
* @prop {T} defaultValue
|
|
37
125
|
*/
|
|
38
126
|
|
|
127
|
+
/** @typedef {any[]} RenderState */
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @typedef RenderGraphSearch
|
|
131
|
+
* @prop {(state:InitializationState, changes:any, data:any) => any} invocation
|
|
132
|
+
* @prop {number} cacheIndex
|
|
133
|
+
* @prop {number} searchIndex
|
|
134
|
+
* @prop {string | Function | string[]} query
|
|
135
|
+
* @prop {Function} [expression]
|
|
136
|
+
* @prop {string} prop
|
|
137
|
+
* @prop {string[]} deepProp
|
|
138
|
+
* @prop {string[]} propsUsed
|
|
139
|
+
* @prop {string[][]} deepPropsUsed
|
|
140
|
+
* @prop {any} defaultValue
|
|
141
|
+
* @prop {RenderGraphSearch} [subSearch]
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @typedef RenderGraphAction
|
|
146
|
+
* @prop {(state:InitializationState, value:any, changes: any, data:any) => any} invocation
|
|
147
|
+
* @prop {number} [commentIndex]
|
|
148
|
+
* @prop {number} [nodeIndex]
|
|
149
|
+
* @prop {number} [cacheIndex]
|
|
150
|
+
* @prop {string} [attrName]
|
|
151
|
+
* @prop {any} [defaultValue]
|
|
152
|
+
* @prop {RenderGraphSearch} search
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @type {RenderGraphAction['invocation']}
|
|
157
|
+
* @this {RenderGraphAction}
|
|
158
|
+
*/
|
|
159
|
+
function writeDOMAttribute({ nodes }, value) {
|
|
160
|
+
const { nodeIndex, attrName } = this;
|
|
161
|
+
/** @type {Element} */
|
|
162
|
+
const element = nodes[nodeIndex];
|
|
163
|
+
switch (value) {
|
|
164
|
+
case undefined:
|
|
165
|
+
case null:
|
|
166
|
+
case false:
|
|
167
|
+
element.removeAttribute(attrName);
|
|
168
|
+
return false;
|
|
169
|
+
case true:
|
|
170
|
+
element.setAttribute(attrName, '');
|
|
171
|
+
return '';
|
|
172
|
+
default:
|
|
173
|
+
element.setAttribute(attrName, value);
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @type {RenderGraphAction['invocation']}
|
|
180
|
+
* @this {RenderGraphAction}
|
|
181
|
+
*/
|
|
182
|
+
function writeDynamicNode({ nodeStates, comments, nodes }, value) {
|
|
183
|
+
const { commentIndex, nodeIndex } = this;
|
|
184
|
+
const nodeState = nodeStates[nodeIndex];
|
|
185
|
+
// eslint-disable-next-line no-bitwise
|
|
186
|
+
const hidden = nodeState & 0b0001;
|
|
187
|
+
const show = value != null && value !== false;
|
|
188
|
+
if (!show) {
|
|
189
|
+
// Should be hidden
|
|
190
|
+
if (hidden) return;
|
|
191
|
+
// Replace whatever node is there with the comment
|
|
192
|
+
let comment = comments[commentIndex];
|
|
193
|
+
if (!comment) {
|
|
194
|
+
comment = createEmptyComment();
|
|
195
|
+
comments[commentIndex] = comment;
|
|
196
|
+
}
|
|
197
|
+
nodes[nodeIndex].replaceWith(comment);
|
|
198
|
+
// eslint-disable-next-line no-bitwise
|
|
199
|
+
nodeStates[nodeIndex] |= 0b0001;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Must be shown
|
|
203
|
+
// Update node first (offscreen rendering)
|
|
204
|
+
const node = nodes[nodeIndex];
|
|
205
|
+
// eslint-disable-next-line no-bitwise
|
|
206
|
+
const isDynamicNode = nodeState & 0b0010;
|
|
207
|
+
|
|
208
|
+
if (typeof value === 'object') {
|
|
209
|
+
// Not string data, need to replace
|
|
210
|
+
console.warn('Dynamic nodes not supported yet');
|
|
211
|
+
} else if (isDynamicNode) {
|
|
212
|
+
const textNode = new Text(value);
|
|
213
|
+
node.replaceWith(textNode);
|
|
214
|
+
nodes[nodeIndex] = textNode;
|
|
215
|
+
// eslint-disable-next-line no-bitwise
|
|
216
|
+
nodeStates[nodeIndex] &= ~0b0010;
|
|
217
|
+
} else {
|
|
218
|
+
node.data = value;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Updated, now set hidden state
|
|
222
|
+
|
|
223
|
+
if (hidden) {
|
|
224
|
+
const comment = comments[commentIndex];
|
|
225
|
+
comment.replaceWith(node);
|
|
226
|
+
// eslint-disable-next-line no-bitwise
|
|
227
|
+
nodeStates[nodeIndex] &= ~0b0001;
|
|
228
|
+
}
|
|
229
|
+
// Done
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @type {RenderGraphAction['invocation']}
|
|
234
|
+
* @this {RenderGraphAction}
|
|
235
|
+
*/
|
|
236
|
+
function writeDOMElementAttachedState({ nodeStates, nodes, comments }, value) {
|
|
237
|
+
const { commentIndex, nodeIndex } = this;
|
|
238
|
+
// eslint-disable-next-line no-bitwise
|
|
239
|
+
const hidden = nodeStates[nodeIndex] & 1;
|
|
240
|
+
const show = value != null && value !== false;
|
|
241
|
+
if (show === !hidden) return;
|
|
242
|
+
|
|
243
|
+
const element = nodes[nodeIndex];
|
|
244
|
+
let comment = comments[commentIndex];
|
|
245
|
+
if (!comment) {
|
|
246
|
+
comment = createEmptyComment();
|
|
247
|
+
comments[commentIndex] = comment;
|
|
248
|
+
}
|
|
249
|
+
if (show) {
|
|
250
|
+
comment.replaceWith(element);
|
|
251
|
+
// eslint-disable-next-line no-bitwise
|
|
252
|
+
nodeStates[nodeIndex] &= ~0b0001;
|
|
253
|
+
} else {
|
|
254
|
+
element.replaceWith(comment);
|
|
255
|
+
// eslint-disable-next-line no-bitwise
|
|
256
|
+
nodeStates[nodeIndex] |= 0b0001;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* @type {RenderGraphAction['invocation']}
|
|
262
|
+
* @this {RenderGraphAction}
|
|
263
|
+
*/
|
|
264
|
+
function writeDOMHideNodeOnInit({ comments, nodeStates, nodes }) {
|
|
265
|
+
const { commentIndex, nodeIndex } = this;
|
|
266
|
+
|
|
267
|
+
const comment = createEmptyComment();
|
|
268
|
+
comments[commentIndex] = comment;
|
|
269
|
+
// eslint-disable-next-line no-bitwise
|
|
270
|
+
nodeStates[nodeIndex] |= 1;
|
|
271
|
+
|
|
272
|
+
nodes[nodeIndex].replaceWith(comment);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @param {RenderGraphSearch} search
|
|
277
|
+
* @param {Parameters<RenderGraphSearch['invocation']>} args
|
|
278
|
+
*/
|
|
279
|
+
function executeSearch(search, ...args) {
|
|
280
|
+
const [{ caches, searchStates }] = args;
|
|
281
|
+
const { cacheIndex, searchIndex, subSearch, invocation } = search;
|
|
282
|
+
const cachedValue = caches[cacheIndex];
|
|
283
|
+
const searchState = searchStates[searchIndex];
|
|
284
|
+
|
|
285
|
+
// Ran = 0b0001
|
|
286
|
+
// Dirty = 0b0010
|
|
287
|
+
// eslint-disable-next-line no-bitwise
|
|
288
|
+
if (searchState & 0b0001) {
|
|
289
|
+
// Return last result
|
|
290
|
+
return {
|
|
291
|
+
value: cachedValue,
|
|
292
|
+
// eslint-disable-next-line no-bitwise
|
|
293
|
+
dirty: ((searchState & 0b0010) === 0b0010),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// eslint-disable-next-line no-bitwise
|
|
298
|
+
searchStates[searchIndex] |= 0b0001;
|
|
299
|
+
let result;
|
|
300
|
+
if (invocation) {
|
|
301
|
+
if (subSearch) {
|
|
302
|
+
const subResult = executeSearch(subSearch, ...args);
|
|
303
|
+
// Use last cached value (if any)
|
|
304
|
+
if (!subResult.dirty && cachedValue !== undefined) {
|
|
305
|
+
// eslint-disable-next-line no-bitwise
|
|
306
|
+
searchStates[searchIndex] &= ~0b0010;
|
|
307
|
+
return { value: cachedValue, dirty: false };
|
|
308
|
+
}
|
|
309
|
+
// Pass from subquery
|
|
310
|
+
result = search.invocation(subResult.value);
|
|
311
|
+
} else {
|
|
312
|
+
result = search.invocation(...args);
|
|
313
|
+
}
|
|
314
|
+
if ((result === undefined) || (cachedValue === result)) {
|
|
315
|
+
// Return from cache
|
|
316
|
+
return { value: result, dirty: false };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Overwrite cache and flag as dirty
|
|
321
|
+
caches[cacheIndex] = result;
|
|
322
|
+
// eslint-disable-next-line no-bitwise
|
|
323
|
+
searchStates[searchIndex] |= 0b0010;
|
|
324
|
+
return { value: result, dirty: true };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* @type {RenderGraphSearch['invocation']}
|
|
329
|
+
* @this {RenderGraphSearch}
|
|
330
|
+
*/
|
|
331
|
+
function searchWithExpression({ options: { context, store, injections } }, changes, data) {
|
|
332
|
+
return this.expression.call(
|
|
333
|
+
context,
|
|
334
|
+
store ?? data,
|
|
335
|
+
injections,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @type {RenderGraphSearch['invocation']}
|
|
341
|
+
* @this {RenderGraphSearch}
|
|
342
|
+
*/
|
|
343
|
+
function searchWithProp(state, changes) {
|
|
344
|
+
return changes[this.prop];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @type {RenderGraphSearch['invocation']}
|
|
349
|
+
* @this {RenderGraphSearch}
|
|
350
|
+
*/
|
|
351
|
+
function searchWithDeepProp(state, changes, data) {
|
|
352
|
+
let scope = changes;
|
|
353
|
+
for (const prop of this.deepProp) {
|
|
354
|
+
if (scope === null) return null;
|
|
355
|
+
if (prop in scope === false) return undefined;
|
|
356
|
+
scope = scope[prop];
|
|
357
|
+
}
|
|
358
|
+
return scope;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* @typedef InterpolateOptions
|
|
363
|
+
* @prop {Record<string,any>} [defaults] Default values to use for interpolation
|
|
364
|
+
* @prop {{iterable:string} & Record<string,any>} [injections] Context-specific injected properties. (Experimental)
|
|
365
|
+
*/
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* @typedef InitializationState
|
|
369
|
+
* @prop {Element} lastElement
|
|
370
|
+
* @prop {ChildNode} lastChildNode
|
|
371
|
+
* @prop {(Element|Text)[]} nodes
|
|
372
|
+
* @prop {any[]} caches
|
|
373
|
+
* @prop {Comment[]} comments
|
|
374
|
+
* @prop {Uint8Array} nodeStates
|
|
375
|
+
* @prop {Uint8Array} searchStates
|
|
376
|
+
* @prop {Element[]} refs
|
|
377
|
+
* @prop {number} lastChildNodeIndex
|
|
378
|
+
* @prop {RenderOptions<?>} options
|
|
379
|
+
*/
|
|
380
|
+
|
|
39
381
|
/** Splits: `{template}text{template}` as `['', 'template', 'text', 'template', '']` */
|
|
40
382
|
const STRING_INTERPOLATION_REGEX = /{([^}]*)}/g;
|
|
41
383
|
|
|
@@ -54,33 +396,23 @@ function buildShadowRootChildListener(fn) {
|
|
|
54
396
|
}
|
|
55
397
|
|
|
56
398
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* @param {
|
|
60
|
-
* @param {
|
|
61
|
-
* @
|
|
62
|
-
* @return {Object}
|
|
399
|
+
* @example
|
|
400
|
+
* propFromObject('foo', {foo:'bar'}) == ['foo', 'bar'];
|
|
401
|
+
* @param {string} prop
|
|
402
|
+
* @param {any} source
|
|
403
|
+
* @return {any}
|
|
63
404
|
*/
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const scopedKey = scope ? `${scope}.${key}` : key;
|
|
68
|
-
target[scopedKey] = value;
|
|
69
|
-
if (value != null && typeof value === 'object') {
|
|
70
|
-
flattenObject(value, syntax, target, scopedKey);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (Array.isArray(object)) {
|
|
74
|
-
const scopedKey = scope ? `${scope}.length` : 'length';
|
|
75
|
-
target[scopedKey] = object.length;
|
|
405
|
+
function propFromObject(prop, source) {
|
|
406
|
+
if (source) {
|
|
407
|
+
return source[prop];
|
|
76
408
|
}
|
|
77
|
-
return
|
|
409
|
+
return undefined;
|
|
78
410
|
}
|
|
79
411
|
|
|
80
412
|
/**
|
|
81
413
|
* @example
|
|
82
|
-
*
|
|
83
|
-
* 'address
|
|
414
|
+
* deepPropFromObject(
|
|
415
|
+
* ['address', 'home, 'houseNumber'],
|
|
84
416
|
* {
|
|
85
417
|
* address: {
|
|
86
418
|
* home: {
|
|
@@ -88,22 +420,25 @@ function flattenObject(object, syntax = 'dot', target = {}, scope = '') {
|
|
|
88
420
|
* },
|
|
89
421
|
* }
|
|
90
422
|
* }
|
|
91
|
-
* )
|
|
92
|
-
* @param {string}
|
|
423
|
+
* ) == [houseNumber, 35]
|
|
424
|
+
* @param {string[]} nameArray
|
|
93
425
|
* @param {any} source
|
|
94
|
-
* @return {
|
|
426
|
+
* @return {any}
|
|
95
427
|
*/
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
let
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
428
|
+
function deepPropFromObject(nameArray, source) {
|
|
429
|
+
if (!source) return undefined;
|
|
430
|
+
let scope = source;
|
|
431
|
+
let prop;
|
|
432
|
+
for (prop of nameArray) {
|
|
433
|
+
if (typeof scope === 'object') {
|
|
434
|
+
if (scope === null) return null;
|
|
435
|
+
if (!(prop in scope)) return undefined;
|
|
436
|
+
scope = scope[prop];
|
|
437
|
+
} else {
|
|
438
|
+
return scope[prop];
|
|
439
|
+
}
|
|
104
440
|
}
|
|
105
|
-
|
|
106
|
-
return [child, value];
|
|
441
|
+
return scope;
|
|
107
442
|
}
|
|
108
443
|
|
|
109
444
|
/**
|
|
@@ -123,30 +458,76 @@ function valueFromPropName(prop, source) {
|
|
|
123
458
|
return value;
|
|
124
459
|
}
|
|
125
460
|
|
|
461
|
+
const compositionCache = new Map();
|
|
462
|
+
|
|
126
463
|
/** @template T */
|
|
127
464
|
export default class Composition {
|
|
465
|
+
static EVENT_PREFIX_REGEX = /^([*1~]+)?(.*)$/;
|
|
466
|
+
|
|
467
|
+
_interpolationState = {
|
|
468
|
+
nodeIndex: -1,
|
|
469
|
+
searchIndex: 0,
|
|
470
|
+
cacheIndex: 0,
|
|
471
|
+
commentIndex: 0,
|
|
472
|
+
/** @type {this['nodesToBind'][0]} */
|
|
473
|
+
nodeEntry: null,
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// eslint-disable-next-line symbol-description
|
|
477
|
+
static shadowRootTag = Symbol();
|
|
478
|
+
|
|
479
|
+
/** @type {{tag:string, textNodes: number[]}[]} */
|
|
480
|
+
nodesToBind = [];
|
|
481
|
+
|
|
482
|
+
/** @type {string[]} */
|
|
483
|
+
props = [];
|
|
484
|
+
|
|
485
|
+
/** @type {RenderGraphSearch[]} */
|
|
486
|
+
searches = [];
|
|
487
|
+
|
|
488
|
+
/** @type {any[]} */
|
|
489
|
+
initCache = [];
|
|
490
|
+
|
|
128
491
|
/**
|
|
129
|
-
*
|
|
130
|
-
* @type {Map<
|
|
492
|
+
* Index of searches by query (dotted notation for deep props)
|
|
493
|
+
* @type {Map<Function|string, RenderGraphSearch>}
|
|
131
494
|
*/
|
|
132
|
-
|
|
495
|
+
searchByQuery;
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Index of searches by query (dotted notation for deep props)
|
|
499
|
+
* @type {Map<string, RenderGraphAction[]>}
|
|
500
|
+
*/
|
|
501
|
+
actionsByPropsUsed;
|
|
502
|
+
|
|
503
|
+
/** @type {RenderGraphAction[]} */
|
|
504
|
+
postInitActions = [];
|
|
505
|
+
|
|
506
|
+
/** @type {Set<string>} */
|
|
507
|
+
tagsWithBindings;
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Array of element tags
|
|
511
|
+
* @type {string[]}
|
|
512
|
+
*/
|
|
513
|
+
tags = [];
|
|
133
514
|
|
|
134
515
|
/**
|
|
135
516
|
* Data of arrays used in templates
|
|
136
|
-
* Usage of a [
|
|
517
|
+
* Usage of a [mdw-for] will create an ArrayLike expectation based on key
|
|
137
518
|
* Only store metadata, not actual data. Currently only needs length.
|
|
138
519
|
* TBD if more is needed later
|
|
139
520
|
* Referenced by property key (string)
|
|
140
|
-
* @type {
|
|
521
|
+
* @type {CompositionAdapter}
|
|
141
522
|
*/
|
|
142
|
-
|
|
523
|
+
adapter;
|
|
143
524
|
|
|
144
525
|
/**
|
|
145
526
|
* Collection of events to bind.
|
|
146
527
|
* Indexed by ID
|
|
147
|
-
* @type {Map<string,
|
|
528
|
+
* @type {Map<string|symbol, CompositionEventListener<any>[]>}
|
|
148
529
|
*/
|
|
149
|
-
events
|
|
530
|
+
events;
|
|
150
531
|
|
|
151
532
|
/**
|
|
152
533
|
* Snapshot of composition at initial state.
|
|
@@ -156,14 +537,6 @@ export default class Composition {
|
|
|
156
537
|
*/
|
|
157
538
|
cloneable;
|
|
158
539
|
|
|
159
|
-
/**
|
|
160
|
-
* Result of interpolation of the composition template.
|
|
161
|
-
* Includes all DOM elements, which is used to reference for adding and
|
|
162
|
-
* removing DOM elements during render.
|
|
163
|
-
* @type {DocumentFragment}
|
|
164
|
-
*/
|
|
165
|
-
interpolation;
|
|
166
|
-
|
|
167
540
|
/** @type {(HTMLStyleElement|CSSStyleSheet)[]} */
|
|
168
541
|
styles = [];
|
|
169
542
|
|
|
@@ -173,25 +546,19 @@ export default class Composition {
|
|
|
173
546
|
/** @type {DocumentFragment} */
|
|
174
547
|
stylesFragment;
|
|
175
548
|
|
|
176
|
-
/** @type {((this:T, changes:T) => any)[]} */
|
|
177
|
-
watchers = [];
|
|
178
|
-
|
|
179
549
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* @type {WeakMap<Element|DocumentFragment, Map<string,HTMLElement>>}
|
|
550
|
+
* List of IDs used by template elements
|
|
551
|
+
* May be needed to be removed when adding to non-DocumentFragment
|
|
552
|
+
* @type {string[]}
|
|
184
553
|
*/
|
|
185
|
-
|
|
554
|
+
allIds = [];
|
|
186
555
|
|
|
187
556
|
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* `cloneable` due to default state. Used to reconstruct conditional elements
|
|
191
|
-
* with conditional children in default state as well (unlike `interpolation`).
|
|
192
|
-
* @type {Map<string, {element: Element, id: string, parentId: string, commentCache: WeakMap<Element|DocumentFragment,Comment>}>}
|
|
557
|
+
* Collection of IDs used for referencing elements
|
|
558
|
+
* Not meant for live DOM. Removed before attaching to document
|
|
193
559
|
*/
|
|
194
|
-
|
|
560
|
+
/** @type {Set<string>} */
|
|
561
|
+
temporaryIds;
|
|
195
562
|
|
|
196
563
|
/** Flag set when template and styles have been interpolated */
|
|
197
564
|
interpolated = false;
|
|
@@ -212,9 +579,24 @@ export default class Composition {
|
|
|
212
579
|
yield part;
|
|
213
580
|
}
|
|
214
581
|
yield this.template;
|
|
215
|
-
|
|
216
|
-
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* @template T
|
|
586
|
+
* @param {ConstructorParameters<typeof Composition<T>>} parts
|
|
587
|
+
* @return {Composition<T>}
|
|
588
|
+
*/
|
|
589
|
+
static compose(...parts) {
|
|
590
|
+
for (const [cache, comp] of compositionCache) {
|
|
591
|
+
if (cache.length !== parts.length) continue;
|
|
592
|
+
if (parts.every((part, index) => part === cache[index])) {
|
|
593
|
+
return comp;
|
|
594
|
+
}
|
|
217
595
|
}
|
|
596
|
+
|
|
597
|
+
const composition = new Composition(...parts);
|
|
598
|
+
compositionCache.set(parts, composition);
|
|
599
|
+
return composition;
|
|
218
600
|
}
|
|
219
601
|
|
|
220
602
|
/**
|
|
@@ -224,8 +606,6 @@ export default class Composition {
|
|
|
224
606
|
for (const part of parts) {
|
|
225
607
|
if (typeof part === 'string') {
|
|
226
608
|
this.append(generateFragment(part.trim()));
|
|
227
|
-
} else if (typeof part === 'function') {
|
|
228
|
-
this.watchers.push(part);
|
|
229
609
|
} else if (part instanceof Composition) {
|
|
230
610
|
this.append(...part);
|
|
231
611
|
} else if (part instanceof DocumentFragment) {
|
|
@@ -238,259 +618,272 @@ export default class Composition {
|
|
|
238
618
|
return this;
|
|
239
619
|
}
|
|
240
620
|
|
|
241
|
-
/** @param {
|
|
621
|
+
/** @param {CompositionEventListener<T>} listener */
|
|
242
622
|
addCompositionEventListener(listener) {
|
|
243
|
-
const key = listener.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
623
|
+
const key = listener.tag ?? '';
|
|
624
|
+
// eslint-disable-next-line no-multi-assign
|
|
625
|
+
const events = (this.events ??= new Map());
|
|
626
|
+
if (events.has(key)) {
|
|
627
|
+
events.get(key).push(listener);
|
|
628
|
+
} else {
|
|
629
|
+
events.set(key, [listener]);
|
|
248
630
|
}
|
|
249
|
-
set.add(listener);
|
|
250
631
|
return this;
|
|
251
632
|
}
|
|
252
633
|
|
|
253
634
|
/**
|
|
635
|
+
* @param {string|symbol} tag
|
|
636
|
+
* @param {EventTarget} target
|
|
637
|
+
* @param {any} [context]
|
|
638
|
+
* @return {void}
|
|
639
|
+
*/
|
|
640
|
+
#bindCompositionEventListeners(tag, target, context) {
|
|
641
|
+
if (!this.events?.has(tag)) return;
|
|
642
|
+
for (const event of this.events.get(tag)) {
|
|
643
|
+
let listener;
|
|
644
|
+
if (event.handleEvent) {
|
|
645
|
+
listener = event.handleEvent;
|
|
646
|
+
} else if (event.deepProp.length) {
|
|
647
|
+
listener = deepPropFromObject(event.deepProp, this.interpolateOptions.defaults);
|
|
648
|
+
} else {
|
|
649
|
+
listener = propFromObject(event.prop, this.interpolateOptions.defaults);
|
|
650
|
+
}
|
|
651
|
+
target.addEventListener(event.type, context ? listener.bind(context) : listener, event);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* TODO: Add types and clean up closure leak
|
|
254
657
|
* Updates component nodes based on data.
|
|
255
658
|
* Expects data in JSON Merge Patch format
|
|
256
659
|
* @see https://www.rfc-editor.org/rfc/rfc7386
|
|
257
|
-
* @
|
|
258
|
-
* @param {Partial
|
|
259
|
-
* @param {
|
|
260
|
-
* @param {
|
|
261
|
-
* @return {
|
|
660
|
+
* @template {Object} T
|
|
661
|
+
* @param {Partial<T>} changes what specifically
|
|
662
|
+
* @param {T} [data]
|
|
663
|
+
* @param {RenderOptions<T>} [options]
|
|
664
|
+
* @return {Function & {target:Element}} anchor
|
|
262
665
|
*/
|
|
263
|
-
render(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
666
|
+
render(changes, data, options = {}) {
|
|
667
|
+
// console.log('render', changes, options);
|
|
668
|
+
if (!this.interpolated) {
|
|
669
|
+
this.interpolate({
|
|
670
|
+
defaults: data ?? changes,
|
|
671
|
+
...options,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const instanceFragment = /** @type {DocumentFragment} */ (this.cloneable.cloneNode(true));
|
|
676
|
+
|
|
677
|
+
const shadowRoot = options.shadowRoot;
|
|
678
|
+
const target = shadowRoot ?? options.target ?? instanceFragment.firstElementChild;
|
|
679
|
+
|
|
680
|
+
/** @type {InitializationState} */
|
|
681
|
+
const initState = {
|
|
682
|
+
lastChildNode: null,
|
|
683
|
+
lastChildNodeIndex: 0,
|
|
684
|
+
lastElement: null,
|
|
685
|
+
nodeStates: new Uint8Array(this._interpolationState.nodeIndex + 1),
|
|
686
|
+
searchStates: new Uint8Array(this._interpolationState.searchIndex),
|
|
687
|
+
comments: [],
|
|
688
|
+
nodes: [],
|
|
689
|
+
caches: this.initCache.slice(),
|
|
690
|
+
refs: [],
|
|
691
|
+
options,
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
const { nodes, refs, searchStates, caches } = initState;
|
|
695
|
+
for (const { tag, textNodes } of this.nodesToBind) {
|
|
696
|
+
/** @type {Text} */
|
|
697
|
+
let textNode;
|
|
698
|
+
if (tag === '') {
|
|
699
|
+
if (!textNodes.length) {
|
|
700
|
+
console.warn('why was root tagged?');
|
|
289
701
|
continue;
|
|
290
702
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
703
|
+
console.warn('found empty tag??');
|
|
704
|
+
refs.push(null);
|
|
705
|
+
nodes.push(null);
|
|
706
|
+
textNode = instanceFragment.firstChild;
|
|
707
|
+
} else {
|
|
708
|
+
const element = instanceFragment.getElementById(tag);
|
|
709
|
+
refs.push(element);
|
|
710
|
+
nodes.push(element);
|
|
711
|
+
this.#bindCompositionEventListeners(tag, element, options.context);
|
|
712
|
+
if (!textNodes.length) continue;
|
|
713
|
+
textNode = element.firstChild;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
let currentIndex = 0;
|
|
717
|
+
for (const index of textNodes) {
|
|
718
|
+
while (index !== currentIndex) {
|
|
719
|
+
textNode = textNode.nextSibling;
|
|
720
|
+
currentIndex++;
|
|
295
721
|
}
|
|
722
|
+
nodes.push(textNode);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
this.#bindCompositionEventListeners('', options.context);
|
|
726
|
+
this.#bindCompositionEventListeners(Composition.shadowRootTag, options.context.shadowRoot, options.context);
|
|
296
727
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
args[prop] = store[prop];
|
|
318
|
-
} else {
|
|
319
|
-
// Relying on props being sorted...
|
|
320
|
-
console.debug('need deep', prop);
|
|
321
|
-
let entry;
|
|
322
|
-
let propSearchKey = prop;
|
|
323
|
-
let lastPropSearchKey = prop;
|
|
324
|
-
while (!entry) {
|
|
325
|
-
entry = entryFromPropName(propSearchKey, args);
|
|
326
|
-
if (entry) {
|
|
327
|
-
const propName = lastPropSearchKey.slice(propSearchKey.length + 1);
|
|
328
|
-
entry[1][propName] = valueFromPropName(lastPropSearchKey, store);
|
|
329
|
-
break;
|
|
330
|
-
}
|
|
331
|
-
if (lastIndexOfDot === -1) break;
|
|
332
|
-
lastPropSearchKey = prop;
|
|
333
|
-
propSearchKey = prop.slice(0, lastIndexOfDot);
|
|
334
|
-
lastIndexOfDot = propSearchKey.lastIndexOf(',');
|
|
335
|
-
}
|
|
336
|
-
if (!entry) {
|
|
337
|
-
console.warn('what do?');
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
value = fn.call(context, args);
|
|
342
|
-
fnResults.set(fn, value);
|
|
728
|
+
for (const action of this.postInitActions) {
|
|
729
|
+
action.invocation(initState);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* @param {Partial<T>} changes
|
|
734
|
+
* @param {T} data
|
|
735
|
+
*/
|
|
736
|
+
const draw = (changes, data) => {
|
|
737
|
+
let ranSearch = false;
|
|
738
|
+
for (const prop of this.props) {
|
|
739
|
+
if (!this.actionsByPropsUsed?.has(prop)) continue;
|
|
740
|
+
if (!(prop in changes)) continue;
|
|
741
|
+
const actions = this.actionsByPropsUsed.get(prop);
|
|
742
|
+
for (const action of actions) {
|
|
743
|
+
ranSearch = true;
|
|
744
|
+
const { dirty, value } = executeSearch(action.search, initState, changes, data);
|
|
745
|
+
if (dirty) {
|
|
746
|
+
// console.log('dirty, updating from batch', initState.nodes[action.nodeIndex], 'with', value);
|
|
747
|
+
action.invocation(initState, value, changes, data);
|
|
343
748
|
}
|
|
344
|
-
} else {
|
|
345
|
-
value = rawValue;
|
|
346
749
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
750
|
+
}
|
|
751
|
+
if (!ranSearch) return;
|
|
752
|
+
searchStates.fill(0);
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
if (shadowRoot) {
|
|
756
|
+
options.context ??= shadowRoot.host;
|
|
757
|
+
if ('adoptedStyleSheets' in shadowRoot) {
|
|
758
|
+
if (this.adoptedStyleSheets.length) {
|
|
759
|
+
shadowRoot.adoptedStyleSheets = [
|
|
760
|
+
...shadowRoot.adoptedStyleSheets,
|
|
761
|
+
...this.adoptedStyleSheets,
|
|
762
|
+
];
|
|
353
763
|
}
|
|
764
|
+
} else if (this.stylesFragment.hasChildNodes()) {
|
|
765
|
+
instanceFragment.prepend(this.stylesFragment.cloneNode(true));
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
options.context ??= target;
|
|
769
|
+
}
|
|
354
770
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
: Number.parseInt(node.slice('#text'.length), 10);
|
|
360
|
-
let nodesFound = 0;
|
|
361
|
-
for (const childNode of ref.childNodes) {
|
|
362
|
-
if (childNode.nodeType !== Node.TEXT_NODE) continue;
|
|
363
|
-
if (index !== nodesFound++) continue;
|
|
364
|
-
childNode.nodeValue = value ?? '';
|
|
365
|
-
break;
|
|
366
|
-
}
|
|
367
|
-
if (index > nodesFound) {
|
|
368
|
-
console.warn('Node not found, adding?');
|
|
369
|
-
ref.append(value);
|
|
370
|
-
}
|
|
371
|
-
} else if (node === '_if') {
|
|
372
|
-
const attached = root.contains(ref);
|
|
373
|
-
const orphaned = ref.parentElement == null && ref.parentNode !== root;
|
|
374
|
-
const shouldShow = value !== null && value !== false;
|
|
375
|
-
if (orphaned && ref.parentNode) {
|
|
376
|
-
console.warn('Orphaned with parent node?', id, { attached, orphaned, shouldShow }, ref.parentNode);
|
|
377
|
-
}
|
|
378
|
-
if (attached !== !orphaned) {
|
|
379
|
-
console.warn('Conditional state', id, { attached, orphaned, shouldShow });
|
|
380
|
-
console.warn('Not attached and not orphaned. Should do nothing?', ref, ref.parentElement);
|
|
381
|
-
}
|
|
382
|
-
if (shouldShow) {
|
|
383
|
-
if (orphaned) {
|
|
384
|
-
const metadata = this.conditionalElementMetadata.get(id);
|
|
385
|
-
if (!metadata) {
|
|
386
|
-
console.error(id);
|
|
387
|
-
throw new Error('Could not find conditional element metadata');
|
|
388
|
-
}
|
|
771
|
+
if (changes !== this.interpolateOptions.defaults) {
|
|
772
|
+
// Not default, overwrite nodes
|
|
773
|
+
draw(changes, data);
|
|
774
|
+
}
|
|
389
775
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
comment.replaceWith(ref);
|
|
414
|
-
} else {
|
|
415
|
-
console.warn('Could not add', id, 'back to parent');
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
} else if (!orphaned) {
|
|
419
|
-
const metadata = this.conditionalElementMetadata.get(id);
|
|
420
|
-
if (!metadata) {
|
|
421
|
-
console.error(id);
|
|
422
|
-
throw new Error(`Could not find conditional element metadata for ${id}`);
|
|
423
|
-
}
|
|
424
|
-
let comment = metadata.commentCache.get(root);
|
|
425
|
-
if (!comment) {
|
|
426
|
-
comment = new Comment(`{#${id}}`);
|
|
427
|
-
metadata.commentCache.set(this, comment);
|
|
428
|
-
}
|
|
429
|
-
console.debug('Composition: Remove', id, ref.outerHTML);
|
|
430
|
-
ref.replaceWith(comment);
|
|
431
|
-
}
|
|
432
|
-
} else if (value === false || value == null) {
|
|
433
|
-
ref.removeAttribute(node);
|
|
434
|
-
} else {
|
|
435
|
-
ref.setAttribute(node, value === true ? '' : value);
|
|
776
|
+
if (shadowRoot) {
|
|
777
|
+
shadowRoot.append(instanceFragment);
|
|
778
|
+
customElements.upgrade(shadowRoot);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
draw.target = target;
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* @param {keyof T & string} prop
|
|
785
|
+
* @param {any} value
|
|
786
|
+
* @param {Partial<T>} [data]
|
|
787
|
+
*/
|
|
788
|
+
draw.byProp = (prop, value, data) => {
|
|
789
|
+
if (!this.actionsByPropsUsed?.has(prop)) return;
|
|
790
|
+
let ranSearch = false;
|
|
791
|
+
|
|
792
|
+
// Update search
|
|
793
|
+
if (this.searchByQuery?.has(prop)) {
|
|
794
|
+
ranSearch = true;
|
|
795
|
+
const search = this.searchByQuery.get(prop);
|
|
796
|
+
const cachedValue = caches[search.cacheIndex];
|
|
797
|
+
if (cachedValue === value) {
|
|
798
|
+
return;
|
|
436
799
|
}
|
|
800
|
+
caches[search.cacheIndex] = value;
|
|
801
|
+
searchStates[search.searchIndex] = 0b0011;
|
|
802
|
+
}
|
|
437
803
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
804
|
+
let changes;
|
|
805
|
+
const actions = this.actionsByPropsUsed.get(prop);
|
|
806
|
+
for (const action of actions) {
|
|
807
|
+
if (action.search.query === prop) {
|
|
808
|
+
action.invocation(initState, value);
|
|
809
|
+
} else {
|
|
810
|
+
changes ??= { [prop]: value };
|
|
811
|
+
data ??= changes;
|
|
812
|
+
ranSearch = true;
|
|
813
|
+
const result = executeSearch(action.search, initState, changes, data);
|
|
814
|
+
if (result.dirty) {
|
|
815
|
+
// console.debug('dirty, updating by prop', prop, initState.nodes[action.nodeIndex], 'with', result.value);
|
|
816
|
+
action.invocation(initState, result.value, changes, data);
|
|
817
|
+
}
|
|
443
818
|
}
|
|
444
|
-
set.add(node);
|
|
445
819
|
}
|
|
446
|
-
|
|
820
|
+
|
|
821
|
+
if (!ranSearch) return;
|
|
822
|
+
searchStates.fill(0);
|
|
823
|
+
};
|
|
824
|
+
draw.state = initState;
|
|
825
|
+
return draw;
|
|
447
826
|
}
|
|
448
827
|
|
|
449
828
|
/**
|
|
450
829
|
* @param {Attr|Text} node
|
|
451
|
-
* @param {Element} element
|
|
452
|
-
* @param {
|
|
830
|
+
* @param {Element|null} [element]
|
|
831
|
+
* @param {InterpolateOptions} [options]
|
|
453
832
|
* @param {string} [parsedValue]
|
|
454
|
-
* @return {
|
|
833
|
+
* @return {true|undefined} remove node
|
|
455
834
|
*/
|
|
456
|
-
#interpolateNode(node, element,
|
|
835
|
+
#interpolateNode(node, element, options, parsedValue) {
|
|
457
836
|
const { nodeValue, nodeName, nodeType } = node;
|
|
458
837
|
|
|
838
|
+
/** @type {Attr} */
|
|
839
|
+
let attr;
|
|
840
|
+
/** @type {Text} */
|
|
841
|
+
let text;
|
|
842
|
+
if (nodeType === Node.ATTRIBUTE_NODE) {
|
|
843
|
+
attr = /** @type {Attr} */ (node);
|
|
844
|
+
} else {
|
|
845
|
+
text = /** @type {Text} */ (node);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Get template strings(s) in node if not passed
|
|
459
849
|
if (parsedValue == null) {
|
|
460
|
-
if (!nodeValue) return
|
|
850
|
+
if (!nodeValue) return;
|
|
461
851
|
const trimmed = nodeValue.trim();
|
|
462
|
-
if (!trimmed) return
|
|
463
|
-
if (
|
|
464
|
-
if (trimmed[0] !== '{') return
|
|
852
|
+
if (!trimmed) return;
|
|
853
|
+
if (attr || element?.tagName === 'STYLE') {
|
|
854
|
+
if (trimmed[0] !== '{') return;
|
|
465
855
|
const { length } = trimmed;
|
|
466
|
-
if (trimmed[length - 1] !== '}') return
|
|
856
|
+
if (trimmed[length - 1] !== '}') return;
|
|
467
857
|
parsedValue = trimmed.slice(1, -1);
|
|
858
|
+
// TODO: Support segmented attribute values
|
|
468
859
|
} else {
|
|
469
860
|
// Split text node into segments
|
|
470
861
|
// TODO: Benchmark indexOf pre-check vs regex
|
|
471
862
|
|
|
472
863
|
const segments = trimmed.split(STRING_INTERPOLATION_REGEX);
|
|
473
|
-
if (segments.length < 3) return
|
|
864
|
+
if (segments.length < 3) return;
|
|
474
865
|
if (segments.length === 3 && !segments[0] && !segments[2]) {
|
|
475
866
|
parsedValue = segments[1];
|
|
476
867
|
} else {
|
|
477
|
-
segments.
|
|
868
|
+
for (const [index, segment] of segments.entries()) {
|
|
478
869
|
// is even = is template string
|
|
479
870
|
if (index % 2) {
|
|
480
|
-
const newNode =
|
|
481
|
-
|
|
482
|
-
this.#interpolateNode(newNode, element,
|
|
483
|
-
} else {
|
|
484
|
-
|
|
485
|
-
node.before(segment);
|
|
871
|
+
const newNode = createEmptyTextNode();
|
|
872
|
+
text.before(newNode);
|
|
873
|
+
this.#interpolateNode(newNode, element, options, segment);
|
|
874
|
+
} else if (segment) {
|
|
875
|
+
text.before(segment);
|
|
486
876
|
}
|
|
487
|
-
}
|
|
488
|
-
//
|
|
877
|
+
}
|
|
878
|
+
// eslint-disable-next-line consistent-return
|
|
489
879
|
return true;
|
|
490
880
|
}
|
|
491
881
|
}
|
|
492
882
|
}
|
|
493
883
|
|
|
884
|
+
// Check mutations
|
|
885
|
+
|
|
886
|
+
const query = parsedValue;
|
|
494
887
|
const negate = parsedValue[0] === '!';
|
|
495
888
|
let doubleNegate = false;
|
|
496
889
|
if (negate) {
|
|
@@ -504,245 +897,552 @@ export default class Composition {
|
|
|
504
897
|
let isEvent;
|
|
505
898
|
let textNodeIndex;
|
|
506
899
|
|
|
507
|
-
if (
|
|
900
|
+
if (text) {
|
|
508
901
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
509
|
-
if (element !==
|
|
902
|
+
if (element !== text.parentElement) {
|
|
510
903
|
console.warn('mismatch?');
|
|
511
|
-
element =
|
|
904
|
+
element = text.parentElement;
|
|
512
905
|
}
|
|
513
906
|
textNodeIndex = 0;
|
|
514
|
-
|
|
907
|
+
/** @type {ChildNode} */
|
|
908
|
+
let prev = text;
|
|
515
909
|
while ((prev = prev.previousSibling)) {
|
|
516
|
-
|
|
517
|
-
textNodeIndex++;
|
|
518
|
-
}
|
|
910
|
+
textNodeIndex++;
|
|
519
911
|
}
|
|
520
912
|
} else {
|
|
521
913
|
// @ts-ignore Skip cast
|
|
522
914
|
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
523
|
-
if (element !==
|
|
915
|
+
if (element !== attr.ownerElement) {
|
|
524
916
|
console.warn('mismatch?');
|
|
525
|
-
element =
|
|
917
|
+
element = attr.ownerElement;
|
|
526
918
|
}
|
|
527
919
|
if (nodeName.startsWith('on')) {
|
|
528
920
|
// Do not interpolate inline event listeners
|
|
529
|
-
|
|
530
|
-
|
|
921
|
+
const hyphenIndex = nodeName.indexOf('-');
|
|
922
|
+
if (hyphenIndex === -1) return;
|
|
923
|
+
isEvent = hyphenIndex === 2;
|
|
531
924
|
}
|
|
532
925
|
}
|
|
533
926
|
|
|
534
|
-
const id = identifierFromElement(element, true);
|
|
535
|
-
|
|
536
927
|
if (isEvent) {
|
|
928
|
+
element.removeAttribute(nodeName);
|
|
929
|
+
const tag = this.#tagElement(element);
|
|
537
930
|
const eventType = nodeName.slice(3);
|
|
538
931
|
const [, flags, type] = eventType.match(/^([*1~]+)?(.*)$/);
|
|
539
|
-
|
|
932
|
+
|
|
933
|
+
let handleEvent;
|
|
934
|
+
/** @type {string} */
|
|
935
|
+
let prop;
|
|
936
|
+
/** @type {string[]} */
|
|
937
|
+
let deepProp = [];
|
|
938
|
+
if (parsedValue.startsWith('#')) {
|
|
939
|
+
handleEvent = inlineFunctions.get(parsedValue).fn;
|
|
940
|
+
} else {
|
|
941
|
+
const parsedProps = parsedValue.split('.');
|
|
942
|
+
if (parsedProps.length === 1) {
|
|
943
|
+
prop = parsedValue;
|
|
944
|
+
deepProp = [];
|
|
945
|
+
} else {
|
|
946
|
+
prop = parsedProps[0];
|
|
947
|
+
deepProp = parsedProps;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
this.addCompositionEventListener({
|
|
952
|
+
tag,
|
|
953
|
+
type,
|
|
954
|
+
handleEvent,
|
|
955
|
+
prop,
|
|
956
|
+
deepProp,
|
|
540
957
|
once: flags?.includes('1'),
|
|
541
958
|
passive: flags?.includes('~'),
|
|
542
959
|
capture: flags?.includes('*'),
|
|
543
|
-
};
|
|
960
|
+
});
|
|
544
961
|
|
|
545
|
-
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
546
964
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
965
|
+
/** @type {RenderGraphSearch} */
|
|
966
|
+
let search;
|
|
967
|
+
|
|
968
|
+
if (this.searchByQuery?.has(query)) {
|
|
969
|
+
search = this.searchByQuery.get(query);
|
|
970
|
+
} else {
|
|
971
|
+
// Has subquery?
|
|
972
|
+
const subquery = parsedValue;
|
|
973
|
+
const isSubquery = subquery !== query;
|
|
974
|
+
/** @type {RenderGraphSearch} */
|
|
975
|
+
let subSearch;
|
|
976
|
+
if (isSubquery && this.searchByQuery?.has(subquery)) {
|
|
977
|
+
subSearch = this.searchByQuery.get(subquery);
|
|
554
978
|
} else {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
979
|
+
// Construct subsearch, even is not subquery.
|
|
980
|
+
/** @type {Function} */
|
|
981
|
+
let expression;
|
|
982
|
+
/** @type {string[]} */
|
|
983
|
+
let propsUsed;
|
|
984
|
+
/** @type {string[][]} */
|
|
985
|
+
let deepPropsUsed;
|
|
986
|
+
let defaultValue;
|
|
987
|
+
let prop;
|
|
988
|
+
let deepProp;
|
|
989
|
+
let invocation;
|
|
990
|
+
|
|
991
|
+
let inlineFunctionOptions;
|
|
992
|
+
// Is Inline Function?
|
|
993
|
+
if (parsedValue.startsWith('#')) {
|
|
994
|
+
inlineFunctionOptions = inlineFunctions.get(parsedValue);
|
|
995
|
+
if (!inlineFunctionOptions) {
|
|
996
|
+
console.warn(`Invalid interpolation value: ${parsedValue}`);
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
expression = inlineFunctionOptions.fn;
|
|
1000
|
+
invocation = searchWithExpression;
|
|
1001
|
+
if (inlineFunctionOptions.props) {
|
|
1002
|
+
// console.log('This function has already been called. Reuse props', inlineFunctionOptions, this);
|
|
1003
|
+
propsUsed = inlineFunctionOptions.props;
|
|
1004
|
+
deepPropsUsed = inlineFunctionOptions.deepProps;
|
|
1005
|
+
defaultValue = inlineFunctionOptions.defaultValue ?? null;
|
|
1006
|
+
} else {
|
|
1007
|
+
defaultValue = inlineFunctionOptions.fn;
|
|
1008
|
+
}
|
|
1009
|
+
} else {
|
|
1010
|
+
defaultValue = null;
|
|
1011
|
+
if (options?.defaults) {
|
|
1012
|
+
defaultValue = deepPropFromObject(parsedValue.split('.'), options.defaults) ?? null;
|
|
1013
|
+
}
|
|
1014
|
+
if (defaultValue == null && options?.injections) {
|
|
1015
|
+
defaultValue = valueFromPropName(parsedValue, options.injections);
|
|
1016
|
+
// console.log('default value from injection', parsedValue, { defaultValue });
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
if (!propsUsed) {
|
|
1021
|
+
if (typeof defaultValue === 'function') {
|
|
1022
|
+
// Value must be reinterpolated and function observed
|
|
1023
|
+
const observeResult = observeFunction.call(this, defaultValue, options?.defaults, options?.injections);
|
|
1024
|
+
const uniqueProps = new Set([
|
|
1025
|
+
...observeResult.props.this,
|
|
1026
|
+
...observeResult.props.args[0],
|
|
1027
|
+
...observeResult.props.args[1],
|
|
1028
|
+
]);
|
|
1029
|
+
const uniqueDeepProps = new Set([
|
|
1030
|
+
...observeResult.deepPropStrings.this,
|
|
1031
|
+
...observeResult.deepPropStrings.args[0],
|
|
1032
|
+
]);
|
|
1033
|
+
expression = defaultValue;
|
|
1034
|
+
defaultValue = observeResult.defaultValue;
|
|
1035
|
+
propsUsed = [...uniqueProps];
|
|
1036
|
+
deepPropsUsed = [...uniqueDeepProps].map((deepPropString) => deepPropString.split('.'));
|
|
1037
|
+
invocation = searchWithExpression;
|
|
1038
|
+
// console.log(this.static.name, fn.name || parsedValue, combinedSet);
|
|
1039
|
+
} else {
|
|
1040
|
+
// property binding
|
|
1041
|
+
const parsedProps = parsedValue.split('.');
|
|
1042
|
+
if (parsedProps.length === 1) {
|
|
1043
|
+
prop = parsedValue;
|
|
1044
|
+
propsUsed = [prop];
|
|
1045
|
+
invocation = searchWithProp;
|
|
1046
|
+
} else {
|
|
1047
|
+
propsUsed = [parsedProps[0]];
|
|
1048
|
+
deepProp = parsedProps;
|
|
1049
|
+
deepPropsUsed = [parsedProps];
|
|
1050
|
+
invocation = searchWithDeepProp;
|
|
1051
|
+
}
|
|
559
1052
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
1053
|
+
// TODO: Rewrite property as deep with array index?
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
if (inlineFunctionOptions) {
|
|
1058
|
+
inlineFunctionOptions.defaultValue = defaultValue;
|
|
1059
|
+
inlineFunctionOptions.props = propsUsed;
|
|
1060
|
+
inlineFunctionOptions.deepProps = deepPropsUsed;
|
|
1061
|
+
}
|
|
1062
|
+
subSearch = {
|
|
1063
|
+
cacheIndex: this._interpolationState.cacheIndex++,
|
|
1064
|
+
searchIndex: this._interpolationState.searchIndex++,
|
|
1065
|
+
query: subquery,
|
|
1066
|
+
defaultValue,
|
|
1067
|
+
subSearch: null,
|
|
1068
|
+
prop,
|
|
1069
|
+
propsUsed,
|
|
1070
|
+
deepProp,
|
|
1071
|
+
deepPropsUsed,
|
|
1072
|
+
invocation,
|
|
1073
|
+
expression,
|
|
1074
|
+
};
|
|
1075
|
+
this.addSearch(subSearch);
|
|
574
1076
|
}
|
|
575
|
-
if (
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
1077
|
+
if (isSubquery) {
|
|
1078
|
+
search = {
|
|
1079
|
+
cacheIndex: this._interpolationState.cacheIndex++,
|
|
1080
|
+
searchIndex: this._interpolationState.searchIndex++,
|
|
1081
|
+
query,
|
|
1082
|
+
subSearch,
|
|
1083
|
+
negate,
|
|
1084
|
+
doubleNegate,
|
|
1085
|
+
prop: subSearch.prop,
|
|
1086
|
+
deepProp: subSearch.deepProp,
|
|
1087
|
+
propsUsed: subSearch.propsUsed,
|
|
1088
|
+
deepPropsUsed: subSearch.deepPropsUsed,
|
|
1089
|
+
defaultValue: doubleNegate ? !!subSearch.defaultValue
|
|
1090
|
+
: (negate ? !subSearch.defaultValue : subSearch.defaultValue),
|
|
1091
|
+
invocation(value) {
|
|
1092
|
+
if (this.doubleNegate) return !!value;
|
|
1093
|
+
if (this.negate) return !value;
|
|
1094
|
+
console.warn('Unknown query mutation', this.query);
|
|
1095
|
+
return value;
|
|
1096
|
+
},
|
|
1097
|
+
};
|
|
1098
|
+
this.addSearch(search);
|
|
579
1099
|
} else {
|
|
580
|
-
|
|
1100
|
+
// Store as search instead
|
|
1101
|
+
search = subSearch;
|
|
581
1102
|
}
|
|
582
|
-
} else {
|
|
583
|
-
defaultValue = valueFromPropName(parsedValue, defaults);
|
|
584
1103
|
}
|
|
585
1104
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
1105
|
+
// Tag
|
|
1106
|
+
let tag;
|
|
1107
|
+
let subnode = null;
|
|
1108
|
+
let defaultValue = search.defaultValue;
|
|
1109
|
+
if (text) {
|
|
1110
|
+
subnode = textNodeIndex;
|
|
1111
|
+
} else if (nodeName === 'mdw-if') {
|
|
1112
|
+
tag = this.#tagElement(element);
|
|
1113
|
+
element.removeAttribute(nodeName);
|
|
1114
|
+
defaultValue = defaultValue != null && defaultValue !== false;
|
|
1115
|
+
} else {
|
|
1116
|
+
subnode = nodeName;
|
|
1117
|
+
if (nodeName === 'id' || defaultValue == null || defaultValue === false) {
|
|
1118
|
+
element.removeAttribute(nodeName);
|
|
594
1119
|
} else {
|
|
595
|
-
|
|
1120
|
+
element.setAttribute(nodeName, defaultValue === true ? '' : defaultValue);
|
|
596
1121
|
}
|
|
597
1122
|
}
|
|
598
1123
|
|
|
599
|
-
|
|
600
|
-
console.warn(': Invalid binding:', parsedValue);
|
|
601
|
-
defaultValue = null;
|
|
602
|
-
}
|
|
1124
|
+
tag ??= this.#tagElement(element);
|
|
603
1125
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
1126
|
+
// Node entry
|
|
1127
|
+
let nodeEntry = this._interpolationState.nodeEntry;
|
|
1128
|
+
if (!nodeEntry || nodeEntry.tag !== tag) {
|
|
1129
|
+
nodeEntry = {
|
|
1130
|
+
tag,
|
|
1131
|
+
textNodes: [],
|
|
1132
|
+
};
|
|
1133
|
+
this._interpolationState.nodeEntry = nodeEntry;
|
|
1134
|
+
this.nodesToBind.push(nodeEntry);
|
|
1135
|
+
this._interpolationState.nodeIndex++;
|
|
608
1136
|
}
|
|
609
1137
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
1138
|
+
/** @type {RenderGraphAction} */
|
|
1139
|
+
let action;
|
|
1140
|
+
|
|
1141
|
+
// Node Action
|
|
1142
|
+
if (text) {
|
|
1143
|
+
nodeEntry.textNodes.push(textNodeIndex);
|
|
614
1144
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
1145
|
+
this._interpolationState.nodeIndex++;
|
|
1146
|
+
action = {
|
|
1147
|
+
nodeIndex: this._interpolationState.nodeIndex,
|
|
1148
|
+
commentIndex: this._interpolationState.commentIndex++,
|
|
1149
|
+
invocation: writeDynamicNode,
|
|
1150
|
+
defaultValue,
|
|
1151
|
+
search,
|
|
1152
|
+
};
|
|
1153
|
+
text.data = typeof defaultValue === 'string' ? defaultValue : '';
|
|
1154
|
+
} else if (subnode) {
|
|
1155
|
+
action = {
|
|
1156
|
+
nodeIndex: this._interpolationState.nodeIndex,
|
|
1157
|
+
attrName: subnode,
|
|
1158
|
+
defaultValue,
|
|
1159
|
+
invocation: writeDOMAttribute,
|
|
1160
|
+
search,
|
|
1161
|
+
};
|
|
1162
|
+
} else {
|
|
1163
|
+
action = {
|
|
1164
|
+
nodeIndex: this._interpolationState.nodeIndex,
|
|
1165
|
+
commentIndex: this._interpolationState.commentIndex++,
|
|
1166
|
+
defaultValue,
|
|
1167
|
+
invocation: writeDOMElementAttachedState,
|
|
1168
|
+
search,
|
|
1169
|
+
};
|
|
1170
|
+
if (!defaultValue) {
|
|
1171
|
+
this.postInitActions.push({
|
|
1172
|
+
...action,
|
|
1173
|
+
invocation: writeDOMHideNodeOnInit,
|
|
1174
|
+
});
|
|
623
1175
|
}
|
|
624
|
-
set.add(entry);
|
|
625
1176
|
}
|
|
626
1177
|
|
|
627
|
-
|
|
1178
|
+
this.addAction(action);
|
|
1179
|
+
// eslint-disable-next-line no-multi-assign
|
|
1180
|
+
const tagsWithBindings = (this.tagsWithBindings ??= new Set());
|
|
1181
|
+
tagsWithBindings.add(tag);
|
|
1182
|
+
}
|
|
628
1183
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
1184
|
+
/**
|
|
1185
|
+
* @param {Element} element
|
|
1186
|
+
* @return {string}
|
|
1187
|
+
*/
|
|
1188
|
+
#tagElement(element) {
|
|
1189
|
+
if (!element) return '';
|
|
1190
|
+
let id = element.id;
|
|
1191
|
+
if (id) {
|
|
1192
|
+
if (!this.allIds.includes(id)) {
|
|
1193
|
+
this.allIds.push(id);
|
|
636
1194
|
}
|
|
637
|
-
} else if (defaultValue == null || defaultValue === false) {
|
|
638
|
-
element.removeAttribute(nodeName);
|
|
639
1195
|
} else {
|
|
640
|
-
|
|
1196
|
+
id = generateUID();
|
|
1197
|
+
// eslint-disable-next-line no-multi-assign
|
|
1198
|
+
const temporaryIds = (this.temporaryIds ??= new Set());
|
|
1199
|
+
temporaryIds.add(id);
|
|
1200
|
+
this.allIds.push(id);
|
|
1201
|
+
element.id = id;
|
|
1202
|
+
}
|
|
1203
|
+
return id;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/**
|
|
1207
|
+
* TODO: Subtemplating lacks optimization, though functional.
|
|
1208
|
+
* - Would benefit from custom type handler for arrays
|
|
1209
|
+
* to avoid multi-iteration change-detection.
|
|
1210
|
+
* - Could benefit from debounced/throttled render
|
|
1211
|
+
* - Consider remap of {item.prop} as {array[index].prop}
|
|
1212
|
+
* @param {Element} element
|
|
1213
|
+
* @param {InterpolateOptions} options
|
|
1214
|
+
* @return {?Composition<?>}
|
|
1215
|
+
*/
|
|
1216
|
+
#interpolateIterable(element, options) {
|
|
1217
|
+
// TODO: Microbenchmark element.attributes
|
|
1218
|
+
const forAttr = element.getAttribute('mdw-for');
|
|
1219
|
+
const trimmed = forAttr?.trim();
|
|
1220
|
+
if (!trimmed) {
|
|
1221
|
+
console.warn('Malformed mdw-for found at', element);
|
|
1222
|
+
return null;
|
|
641
1223
|
}
|
|
642
|
-
|
|
1224
|
+
|
|
1225
|
+
if (trimmed[0] !== '{') {
|
|
1226
|
+
console.warn('Malformed mdw-for found at', element);
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
const { length } = trimmed;
|
|
1230
|
+
if (trimmed[length - 1] !== '}') {
|
|
1231
|
+
console.warn('Malformed mdw-for found at', element);
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
const parsedValue = trimmed.slice(1, -1);
|
|
1235
|
+
const [valueName, iterableName] = parsedValue.split(/\s+of\s+/);
|
|
1236
|
+
element.removeAttribute('mdw-for');
|
|
1237
|
+
// Create a new composition targetting element as root
|
|
1238
|
+
|
|
1239
|
+
const elementAnchor = element.ownerDocument.createElement('template');
|
|
1240
|
+
element.replaceWith(elementAnchor);
|
|
1241
|
+
const tag = this.#tagElement(elementAnchor);
|
|
1242
|
+
// console.log('tagging placeholder element with', elementAnchor, tag);
|
|
1243
|
+
|
|
1244
|
+
let nodeEntry = this._interpolationState.nodeEntry;
|
|
1245
|
+
if (!nodeEntry || nodeEntry.tag !== tag) {
|
|
1246
|
+
nodeEntry = {
|
|
1247
|
+
tag,
|
|
1248
|
+
textNodes: [],
|
|
1249
|
+
};
|
|
1250
|
+
this._interpolationState.nodeEntry = nodeEntry;
|
|
1251
|
+
this.nodesToBind.push(nodeEntry);
|
|
1252
|
+
this._interpolationState.nodeIndex++;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
const newComposition = new Composition();
|
|
1256
|
+
newComposition.template.append(element);
|
|
1257
|
+
// Move uninterpolated element to new composition template.
|
|
1258
|
+
const injections = {
|
|
1259
|
+
...options.injections,
|
|
1260
|
+
[valueName]: null,
|
|
1261
|
+
index: null,
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
const propsUsed = [iterableName];
|
|
1265
|
+
/** @type {RenderGraphSearch} */
|
|
1266
|
+
const search = {
|
|
1267
|
+
cacheIndex: this._interpolationState.cacheIndex++,
|
|
1268
|
+
searchIndex: this._interpolationState.searchIndex++,
|
|
1269
|
+
propsUsed,
|
|
1270
|
+
deepPropsUsed: [[iterableName]],
|
|
1271
|
+
defaultValue: {},
|
|
1272
|
+
invocation: null,
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
/** @type {RenderGraphAction} */
|
|
1276
|
+
const action = {
|
|
1277
|
+
defaultValue: null,
|
|
1278
|
+
nodeIndex: this._interpolationState.nodeIndex,
|
|
1279
|
+
search,
|
|
1280
|
+
commentIndex: this._interpolationState.commentIndex++,
|
|
1281
|
+
injections,
|
|
1282
|
+
invocation(state, value, changes, data) {
|
|
1283
|
+
if (!newComposition.adapter) {
|
|
1284
|
+
// console.log({ state.options });
|
|
1285
|
+
const instanceAnchorElement = state.nodes[this.nodeIndex];
|
|
1286
|
+
const anchorNode = createEmptyComment();
|
|
1287
|
+
// Avoid leak
|
|
1288
|
+
state.comments[this.commentIndex] = anchorNode;
|
|
1289
|
+
instanceAnchorElement.replaceWith(anchorNode);
|
|
1290
|
+
newComposition.adapter = new CompositionAdapter({
|
|
1291
|
+
anchorNode,
|
|
1292
|
+
composition: newComposition,
|
|
1293
|
+
renderOptions: {
|
|
1294
|
+
target: null,
|
|
1295
|
+
context: state.options.context,
|
|
1296
|
+
store: state.options.store,
|
|
1297
|
+
injections: this.injections,
|
|
1298
|
+
},
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
const { adapter } = newComposition;
|
|
1302
|
+
const iterable = (data ?? state.options.store)[iterableName];
|
|
1303
|
+
// Remove oversized
|
|
1304
|
+
if (!iterable || iterable.length === 0) {
|
|
1305
|
+
adapter.removeEntries();
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
const changeList = changes[iterableName];
|
|
1309
|
+
const innerChanges = { ...changes };
|
|
1310
|
+
const needTargetAll = newComposition.props.some((prop) => prop !== iterableName && prop in changes);
|
|
1311
|
+
|
|
1312
|
+
adapter.startBatch();
|
|
1313
|
+
let isArray;
|
|
1314
|
+
if (!needTargetAll && !(isArray = Array.isArray(changeList))) {
|
|
1315
|
+
const iterator = isArray ? changeList.entries() : Object.entries(changeList);
|
|
1316
|
+
// console.log('changeList render', iterator);
|
|
1317
|
+
for (const [key, change] of iterator) {
|
|
1318
|
+
if (key === 'length') continue;
|
|
1319
|
+
if (change === null) {
|
|
1320
|
+
// console.warn('null?', 'remove?', key);
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
const index = (+key);
|
|
1324
|
+
const resource = iterable[index];
|
|
1325
|
+
innerChanges[valueName] = change;
|
|
1326
|
+
this.injections[valueName] = resource;
|
|
1327
|
+
this.injections.index = index;
|
|
1328
|
+
|
|
1329
|
+
adapter.renderData(index, innerChanges, data, resource, change);
|
|
1330
|
+
}
|
|
1331
|
+
} else {
|
|
1332
|
+
if (!changeList) {
|
|
1333
|
+
delete innerChanges[valueName];
|
|
1334
|
+
}
|
|
1335
|
+
// console.log('full array render', iterable);
|
|
1336
|
+
for (const [index, resource] of iterable.entries()) {
|
|
1337
|
+
let change;
|
|
1338
|
+
if (changeList) {
|
|
1339
|
+
// console.warn('full array render has changeList?', changeList);
|
|
1340
|
+
if (!needTargetAll && !(index in changeList)) {
|
|
1341
|
+
console.warn('huh?');
|
|
1342
|
+
continue;
|
|
1343
|
+
}
|
|
1344
|
+
change = changeList[index];
|
|
1345
|
+
if (change === null) {
|
|
1346
|
+
// console.warn('remove?');
|
|
1347
|
+
continue;
|
|
1348
|
+
}
|
|
1349
|
+
innerChanges[valueName] = change;
|
|
1350
|
+
}
|
|
1351
|
+
this.injections[valueName] = resource;
|
|
1352
|
+
this.injections.index = index;
|
|
1353
|
+
|
|
1354
|
+
adapter.renderData(index, innerChanges, data, resource, change);
|
|
1355
|
+
// adapter.renderIndex(index, innerChanges, data, resource);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
adapter.stopBatch();
|
|
1359
|
+
|
|
1360
|
+
adapter.removeEntries(iterable.length);
|
|
1361
|
+
},
|
|
1362
|
+
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
newComposition.interpolate({
|
|
1366
|
+
defaults: options.defaults,
|
|
1367
|
+
injections,
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
propsUsed.push(...newComposition.props);
|
|
1371
|
+
this.addSearch(search);
|
|
1372
|
+
this.addAction(action);
|
|
1373
|
+
// eslint-disable-next-line no-multi-assign
|
|
1374
|
+
const tagsWithBindings = (this.tagsWithBindings ??= new Set());
|
|
1375
|
+
tagsWithBindings.add(tag);
|
|
1376
|
+
// console.log('adding', iterable, 'bind to', this);
|
|
1377
|
+
// this.addBinding(iterable, entry);
|
|
1378
|
+
return newComposition;
|
|
643
1379
|
}
|
|
644
1380
|
|
|
645
1381
|
/**
|
|
646
|
-
* @param {
|
|
1382
|
+
* @param {InterpolateOptions} [options]
|
|
647
1383
|
*/
|
|
648
|
-
interpolate(
|
|
1384
|
+
interpolate(options) {
|
|
1385
|
+
this.interpolateOptions = options;
|
|
649
1386
|
// console.log('Template', [...this.template.children].map((child) => child.outerHTML).join('\n'));
|
|
650
1387
|
|
|
651
1388
|
// Copy template before working on it
|
|
652
1389
|
// Store into `cloneable` to split later into `interpolation`
|
|
653
1390
|
this.cloneable = /** @type {DocumentFragment} */ (this.template.cloneNode(true));
|
|
654
1391
|
|
|
655
|
-
/**
|
|
656
|
-
* Track elements to be removed before using for cloning
|
|
657
|
-
* @type {Element[]}
|
|
658
|
-
*/
|
|
659
|
-
const removalList = [];
|
|
660
|
-
|
|
661
1392
|
const TREE_WALKER_FILTER = 5; /* NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT */
|
|
662
1393
|
|
|
663
1394
|
const treeWalker = document.createTreeWalker(this.cloneable, TREE_WALKER_FILTER);
|
|
1395
|
+
/** Note: `node` and treeWalker.currentNode may deviate */
|
|
664
1396
|
let node = treeWalker.nextNode();
|
|
665
1397
|
while (node) {
|
|
666
1398
|
/** @type {Element} */
|
|
667
1399
|
let element = null;
|
|
668
|
-
let removeElement = false;
|
|
669
1400
|
switch (node.nodeType) {
|
|
670
1401
|
case Node.ELEMENT_NODE:
|
|
671
|
-
element = node;
|
|
672
|
-
if (element
|
|
673
|
-
node = treeWalker.
|
|
1402
|
+
element = /** @type {Element} */ (node);
|
|
1403
|
+
if (element.tagName === 'TEMPLATE') {
|
|
1404
|
+
while (element.contains(node = treeWalker.nextNode()));
|
|
674
1405
|
continue;
|
|
675
1406
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
if (node.parentNode === this.cloneable) {
|
|
679
|
-
this.styles.push(node);
|
|
680
|
-
node.remove();
|
|
681
|
-
node = treeWalker.nextSibling();
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
console.warn('<style> element not moved');
|
|
685
|
-
}
|
|
686
|
-
if (node instanceof HTMLScriptElement) {
|
|
1407
|
+
|
|
1408
|
+
if (element.tagName === 'SCRIPT') {
|
|
687
1409
|
console.warn('<script> element found.');
|
|
688
|
-
node.
|
|
689
|
-
node = treeWalker.nextSibling();
|
|
1410
|
+
while (element.contains(node = treeWalker.nextNode()));
|
|
690
1411
|
continue;
|
|
691
1412
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1413
|
+
|
|
1414
|
+
if (element.hasAttribute('mdw-for')) {
|
|
1415
|
+
while (element.contains(node = treeWalker.nextNode()));
|
|
1416
|
+
this.#interpolateIterable(element, options);
|
|
1417
|
+
continue;
|
|
1418
|
+
} else {
|
|
1419
|
+
const idAttr = element.attributes.id;
|
|
1420
|
+
if (idAttr) {
|
|
1421
|
+
this.#interpolateNode(idAttr, element, options);
|
|
1422
|
+
this.#tagElement(element);
|
|
1423
|
+
}
|
|
1424
|
+
for (const attr of [...element.attributes].reverse()) {
|
|
1425
|
+
if (attr.nodeName === 'id') continue; // Already handled
|
|
1426
|
+
this.#interpolateNode(attr, element, options);
|
|
705
1427
|
}
|
|
706
|
-
removeElement ||= this.#interpolateNode(attr, element, defaults);
|
|
707
1428
|
}
|
|
708
1429
|
|
|
709
1430
|
break;
|
|
710
1431
|
case Node.TEXT_NODE:
|
|
711
|
-
element = node.
|
|
712
|
-
if (this.#interpolateNode(/** @type {Text} */ (node), element,
|
|
1432
|
+
element = node.parentElement;
|
|
1433
|
+
if (this.#interpolateNode(/** @type {Text} */ (node), element, options)) {
|
|
713
1434
|
const nextNode = treeWalker.nextNode();
|
|
714
1435
|
node.remove();
|
|
715
1436
|
node = nextNode;
|
|
716
1437
|
continue;
|
|
717
1438
|
}
|
|
718
|
-
|
|
719
1439
|
break;
|
|
720
1440
|
default:
|
|
721
1441
|
throw new Error(`Unexpected node type: ${node.nodeType}`);
|
|
722
1442
|
}
|
|
723
|
-
if (removeElement) {
|
|
724
|
-
removalList.push(element);
|
|
725
|
-
}
|
|
726
1443
|
node = treeWalker.nextNode();
|
|
727
1444
|
}
|
|
728
1445
|
|
|
729
|
-
// Split into `interpolation` before removing elements
|
|
730
|
-
/** @type {DocumentFragment} */
|
|
731
|
-
this.interpolation = /** @type {DocumentFragment} */ (this.cloneable.cloneNode(true));
|
|
732
|
-
|
|
733
|
-
// console.debug('Interpolated', [...this.interpolation.children].map((child) => child.outerHTML).join('\n'));
|
|
734
|
-
|
|
735
|
-
// Remove elements from `cloneable` and place comment placeholders
|
|
736
|
-
// Remove in reverse so conditionals within conditionals are properly isolated
|
|
737
|
-
for (const element of [...removalList].reverse()) {
|
|
738
|
-
const { id } = element;
|
|
739
|
-
element.replaceWith(new Comment(`{#${id}}`));
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
for (const watcher of this.watchers) {
|
|
743
|
-
this.bindWatcher(watcher, defaults);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
1446
|
if ('adoptedStyleSheets' in document) {
|
|
747
1447
|
this.adoptedStyleSheets = [
|
|
748
1448
|
...generateCSSStyleSheets(this.styles),
|
|
@@ -754,235 +1454,54 @@ export default class Composition {
|
|
|
754
1454
|
);
|
|
755
1455
|
}
|
|
756
1456
|
|
|
757
|
-
this.
|
|
1457
|
+
this.props = this.actionsByPropsUsed
|
|
1458
|
+
? [...this.actionsByPropsUsed.keys()]
|
|
1459
|
+
: [];
|
|
758
1460
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
* @see https://www.rfc-editor.org/rfc/rfc7386
|
|
766
|
-
* @param {DocumentFragment|ShadowRoot} root where
|
|
767
|
-
* @param {Partial<?>} data what
|
|
768
|
-
* @return {void}
|
|
769
|
-
*/
|
|
770
|
-
initialRender(root, data) {
|
|
771
|
-
if (!this.interpolated) this.interpolate(data);
|
|
772
|
-
|
|
773
|
-
if ('adoptedStyleSheets' in root) {
|
|
774
|
-
root.adoptedStyleSheets = [
|
|
775
|
-
...root.adoptedStyleSheets,
|
|
776
|
-
...this.adoptedStyleSheets,
|
|
777
|
-
];
|
|
778
|
-
} else if (root instanceof ShadowRoot) {
|
|
779
|
-
root.append(this.stylesFragment.cloneNode(true));
|
|
780
|
-
} else {
|
|
781
|
-
// TODO: Support document styles
|
|
782
|
-
// console.warn('Cannot apply styles to singular element');
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
root.append(this.cloneable.cloneNode(true));
|
|
786
|
-
|
|
787
|
-
// console.log('Initial render', [...root.children].map((child) => child.outerHTML).join('\n'));
|
|
788
|
-
|
|
789
|
-
/** @type {EventTarget} */
|
|
790
|
-
const rootEventTarget = root instanceof ShadowRoot ? root.host : root;
|
|
791
|
-
// Bind events in reverse order to support stopImmediatePropagation
|
|
792
|
-
for (const [id, events] of [...this.events].reverse()) {
|
|
793
|
-
// Prepare all event listeners first
|
|
794
|
-
for (const entry of [...events].reverse()) {
|
|
795
|
-
let listener = entry.listener;
|
|
796
|
-
if (!listener) {
|
|
797
|
-
if (root instanceof ShadowRoot) {
|
|
798
|
-
listener = entry.handleEvent ?? valueFromPropName(entry.prop, data);
|
|
799
|
-
if (id) {
|
|
800
|
-
// Wrap to retarget this
|
|
801
|
-
listener = buildShadowRootChildListener(listener);
|
|
802
|
-
}
|
|
803
|
-
// Cache and reuse
|
|
804
|
-
entry.listener = listener;
|
|
805
|
-
// console.log('caching listener', entry);
|
|
806
|
-
} else {
|
|
807
|
-
throw new TypeError('Anonymous event listeners cannot be used in templates');
|
|
808
|
-
// console.warn('creating new listener', entry);
|
|
809
|
-
// listener = entry.handleEvent ?? ((event) => {
|
|
810
|
-
// valueFromPropName(entry.prop, data)(event);
|
|
811
|
-
// });
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
const eventTarget = id ? root.getElementById(id) : rootEventTarget;
|
|
815
|
-
if (!eventTarget) {
|
|
816
|
-
// Element is not available yet. Bind on reference
|
|
817
|
-
console.debug('Composition: Skip bind events for', id);
|
|
818
|
-
continue;
|
|
819
|
-
}
|
|
820
|
-
eventTarget.addEventListener(entry.type, listener, entry);
|
|
1461
|
+
for (const id of this.allIds) {
|
|
1462
|
+
if (!this.tagsWithBindings?.has(id)) {
|
|
1463
|
+
this.nodesToBind.push({
|
|
1464
|
+
tag: id,
|
|
1465
|
+
textNodes: [],
|
|
1466
|
+
});
|
|
821
1467
|
}
|
|
822
1468
|
}
|
|
823
1469
|
|
|
824
|
-
this.
|
|
825
|
-
|
|
1470
|
+
this.tags = this.nodesToBind.map((n) => n.tag);
|
|
1471
|
+
this.interpolated = true;
|
|
826
1472
|
|
|
827
|
-
|
|
828
|
-
* @param {string} id
|
|
829
|
-
* @param {Element} element
|
|
830
|
-
*/
|
|
831
|
-
attachEventListeners(id, element) {
|
|
832
|
-
const events = this.events.get(id);
|
|
833
|
-
if (events) {
|
|
834
|
-
console.debug('attaching events for', id);
|
|
835
|
-
} else {
|
|
836
|
-
// console.log('no events for', id);
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
for (const entry of [...this.events.get(id)].reverse()) {
|
|
840
|
-
const { listener } = entry;
|
|
841
|
-
if (!listener) {
|
|
842
|
-
throw new Error('Template must be interpolated before attaching events');
|
|
843
|
-
}
|
|
844
|
-
element.addEventListener(entry.type, listener, entry);
|
|
845
|
-
}
|
|
1473
|
+
// console.log('Cloneable', [...this.cloneable.children].map((child) => child.outerHTML).join('\n'));
|
|
846
1474
|
}
|
|
847
1475
|
|
|
848
1476
|
/**
|
|
849
|
-
* @param {
|
|
850
|
-
* @return {
|
|
1477
|
+
* @param {RenderGraphSearch} search
|
|
1478
|
+
* @return {RenderGraphSearch}
|
|
851
1479
|
*/
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
if (
|
|
855
|
-
|
|
856
|
-
this.
|
|
1480
|
+
addSearch(search) {
|
|
1481
|
+
this.searches.push(search);
|
|
1482
|
+
if (search.query) {
|
|
1483
|
+
// eslint-disable-next-line no-multi-assign
|
|
1484
|
+
const searchByQuery = (this.searchByQuery ??= new Map());
|
|
1485
|
+
searchByQuery.set(search.query, search);
|
|
1486
|
+
this.initCache[search.cacheIndex] = search.defaultValue;
|
|
857
1487
|
}
|
|
858
|
-
return
|
|
1488
|
+
return search;
|
|
859
1489
|
}
|
|
860
1490
|
|
|
861
1491
|
/**
|
|
862
|
-
* @param {
|
|
863
|
-
* @
|
|
864
|
-
* @return {Element}
|
|
1492
|
+
* @param {RenderGraphAction} action
|
|
1493
|
+
* @return {RenderGraphAction}
|
|
865
1494
|
*/
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
}
|
|
873
|
-
if (element === null) return null; // Cached null response
|
|
874
|
-
|
|
875
|
-
// Undefined
|
|
876
|
-
|
|
877
|
-
// console.log('Search in DOM', id);
|
|
878
|
-
element = root.getElementById(id);
|
|
879
|
-
|
|
880
|
-
if (element) {
|
|
881
|
-
// console.log('Found in DOM', id);
|
|
882
|
-
references.set(id, element);
|
|
883
|
-
return element;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// Element not in DOM means child of conditional element
|
|
887
|
-
/** @type {Element} */
|
|
888
|
-
let anchorElement;
|
|
889
|
-
|
|
890
|
-
// Check if element is conditional
|
|
891
|
-
let cloneTarget = this.conditionalElementMetadata.get(id)?.element;
|
|
892
|
-
|
|
893
|
-
if (!cloneTarget) {
|
|
894
|
-
// Check if element even exists in interpolation
|
|
895
|
-
// Check interpolation (full-tree) first
|
|
896
|
-
const interpolatedElement = this.interpolation.getElementById(id);
|
|
897
|
-
if (!interpolatedElement) {
|
|
898
|
-
console.warn('Not in full-tree', id);
|
|
899
|
-
// Cache not in full composition
|
|
900
|
-
references.set(id, null);
|
|
901
|
-
return null;
|
|
902
|
-
}
|
|
903
|
-
// Iterate backgrounds until closest conditional element
|
|
904
|
-
// const anchorElementId = this.template.getElementById(id).closest('[_if]').id;
|
|
905
|
-
// anchorElement = this.references.get(anchorElementId) this.interpolation.getElementById(anchorElementId).cloneNode(true);
|
|
906
|
-
let parentElement = interpolatedElement;
|
|
907
|
-
while ((parentElement = parentElement.parentElement) != null) {
|
|
908
|
-
const parentId = parentElement.id;
|
|
909
|
-
if (!parentId) {
|
|
910
|
-
console.warn('Parent does not have ID!');
|
|
911
|
-
cloneTarget = parentElement;
|
|
912
|
-
continue;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// Parent already referenced
|
|
916
|
-
const referencedParent = references.get(parentId);
|
|
917
|
-
if (referencedParent) {
|
|
918
|
-
// Element may have been removed without ever tree-walking
|
|
919
|
-
console.debug('Parent already referenced', parentId, '>', id);
|
|
920
|
-
anchorElement = referencedParent;
|
|
921
|
-
break;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
const liveElement = root.getElementById(parentId);
|
|
925
|
-
if (liveElement) {
|
|
926
|
-
console.warn('Parent in DOM and not referenced', parentId, '>', id);
|
|
927
|
-
// Parent already in DOM. Cache reference
|
|
928
|
-
references.set(parentId, liveElement);
|
|
929
|
-
anchorElement = referencedParent;
|
|
930
|
-
break;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
const conditionalParent = this.conditionalElementMetadata.get(parentId)?.element;
|
|
934
|
-
if (conditionalParent) {
|
|
935
|
-
console.debug('Found parent conditional element', parentId, '>', id);
|
|
936
|
-
cloneTarget = conditionalParent;
|
|
937
|
-
break;
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
cloneTarget = parentElement;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
anchorElement ??= /** @type {Element} */ (cloneTarget.cloneNode(true));
|
|
945
|
-
|
|
946
|
-
// Iterate downwards and cache all references
|
|
947
|
-
let node = anchorElement;
|
|
948
|
-
const iterator = document.createTreeWalker(anchorElement, NodeFilter.SHOW_ELEMENT);
|
|
949
|
-
do {
|
|
950
|
-
const nodeIdentifier = node.id;
|
|
951
|
-
if (!element && nodeIdentifier === id) {
|
|
952
|
-
element = node;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
if (nodeIdentifier) {
|
|
956
|
-
// console.debug('Caching element', nodeIdentifier);
|
|
957
|
-
references.set(nodeIdentifier, node);
|
|
958
|
-
if (cloneTarget) {
|
|
959
|
-
// Attach events regardless of DOM state.
|
|
960
|
-
// EventTargets should still fire even if not part of live document
|
|
961
|
-
this.attachEventListeners(id, element);
|
|
962
|
-
}
|
|
1495
|
+
addAction(action) {
|
|
1496
|
+
// eslint-disable-next-line no-multi-assign
|
|
1497
|
+
const actionsByPropsUsed = (this.actionsByPropsUsed ??= new Map());
|
|
1498
|
+
for (const prop of action.search.propsUsed) {
|
|
1499
|
+
if (actionsByPropsUsed.has(prop)) {
|
|
1500
|
+
actionsByPropsUsed.get(prop).push(action);
|
|
963
1501
|
} else {
|
|
964
|
-
|
|
965
|
-
}
|
|
966
|
-
} while ((node = iterator.nextNode()));
|
|
967
|
-
return element;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
/**
|
|
971
|
-
* @param {*} fn
|
|
972
|
-
* @param {any} defaults
|
|
973
|
-
* @return {boolean} reusable
|
|
974
|
-
*/
|
|
975
|
-
bindWatcher(fn, defaults) {
|
|
976
|
-
const { props, defaultValue, reusable } = observeFunction(fn, defaults);
|
|
977
|
-
const entry = { fn, props, defaultValue };
|
|
978
|
-
for (const prop of props) {
|
|
979
|
-
let set = this.bindings.get(prop);
|
|
980
|
-
if (!set) {
|
|
981
|
-
set = new Set();
|
|
982
|
-
this.bindings.set(prop, set);
|
|
1502
|
+
actionsByPropsUsed.set(prop, [action]);
|
|
983
1503
|
}
|
|
984
|
-
set.add(entry);
|
|
985
1504
|
}
|
|
986
|
-
return
|
|
1505
|
+
return action;
|
|
987
1506
|
}
|
|
988
1507
|
}
|