UNPKG

@deck.gl/react

Version:

React Components for deck.gl

136 lines (124 loc) 4.18 kB
// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import * as React from 'react'; import {createElement} from 'react'; import {inheritsFrom} from './inherits-from'; import {Layer, View} from '@deck.gl/core'; import {isComponent} from './evaluate-children'; import type {LayersList, Viewport} from '@deck.gl/core'; export type DeckGLRenderCallbackArgs = { /** * the left offset of the current view, in pixels */ x: number; /** * the top offset of the current view, in pixels */ y: number; /** * the width of the current view, in pixels */ width: number; /** * the height of the current view, in pixels */ height: number; /** * the view state of the current view */ viewState: any; /** * the `Viewport` instance of the current view */ viewport: Viewport; }; export type DeckGLRenderCallback = (args: DeckGLRenderCallbackArgs) => React.ReactNode; // recursively wrap render callbacks in `View` function wrapInView(node: React.ReactNode | DeckGLRenderCallback): React.ReactNode { if (typeof node === 'function') { // React.Children does not traverse functions. // All render callbacks must be protected under a <View> // @ts-expect-error View is not a ReactJSXElement constructor. Only used as a temporary wrapper and will be removed in extractJSXLayers return createElement(View, {}, node); } if (Array.isArray(node)) { return node.map(wrapInView); } if (isComponent(node)) { if (node.type === React.Fragment) { return wrapInView(node.props.children); } if (inheritsFrom(node.type, View)) { return node; } } return node; } // extracts any deck.gl layers masquerading as react elements from props.children export default function extractJSXLayers({ children, layers = [], views = null }: { children?: React.ReactNode | DeckGLRenderCallback; layers?: LayersList; views?: View | View[] | null; }): { children: React.ReactNode[]; layers: LayersList; views: View | View[] | null; } { const reactChildren: React.ReactNode[] = []; // extract real react elements (i.e. not deck.gl layers) const jsxLayers: LayersList = []; // extracted layer from react children, will add to deck.gl layer array const jsxViews: Record<string, View> = {}; // React.children React.Children.forEach(wrapInView(children), reactElement => { if (isComponent(reactElement)) { // For some reason Children.forEach doesn't filter out `null`s const ElementType = reactElement.type; if (inheritsFrom(ElementType, Layer)) { const layer = createLayer(ElementType, reactElement.props); jsxLayers.push(layer); } else { reactChildren.push(reactElement); } // empty id => default view if (inheritsFrom(ElementType, View) && ElementType !== View && reactElement.props.id) { // @ts-ignore Cannot instantiate an abstract class (View) const view = new ElementType(reactElement.props); jsxViews[view.id] = view; } } else if (reactElement) { reactChildren.push(reactElement); } }); // Avoid modifying views if no JSX views were found if (Object.keys(jsxViews).length > 0) { // If a view is specified in both views prop and JSX, use the one in views if (Array.isArray(views)) { views.forEach(view => { jsxViews[view.id] = view; }); } else if (views) { jsxViews[views.id] = views; } views = Object.values(jsxViews); } // Avoid modifying layers array if no JSX layers were found layers = jsxLayers.length > 0 ? [...jsxLayers, ...layers] : layers; return {layers, children: reactChildren, views}; } function createLayer(LayerType: typeof Layer, reactProps: any): Layer { const props = {}; // Layer.defaultProps is treated as ReactElement.defaultProps and merged into react props // Remove them const defaultProps = LayerType.defaultProps || {}; for (const key in reactProps) { if (defaultProps[key] !== reactProps[key]) { props[key] = reactProps[key]; } } // @ts-ignore Cannot instantiate an abstract class (Layer) return new LayerType(props); }