All files / pure-foundation pure-foundation.ts

95.83% Statements 23/24
94.44% Branches 17/18
100% Functions 10/10
95.83% Lines 23/24
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 1311x 1x                                   1x                       42x         42x                   43x 43x         43x         8x     35x     35x 18x     35x   35x                     8x             5x   59x             5x               9x                         51x   67x     46x           1x    
import * as React from "react";
import { get, isPlainObject, pick, set } from "lodash-es";
import { PureFoundationProps } from "./pure-foundation.props";
import { HandledProps, ReferenceResolver, ReferenceResolverStore, ReferenceStore } from "../foundation";
 
/**
 * The foundation component is the component that all fast base components are built on top of. It provides a common
 * set of utilities that each component inherits.
 * @param H - These are the props that are "handled". "handled" props are not mapped automatically to the root element
 * returned by the render function. Use handled props to expose inputs that will not map directly to DOM attributes
 * (eg a custom callback) or where the DOM attribute would be required.
 * @param U - These are "unhandled" props. Any props from this interface will be mapped onto the root DOM node of the
 * render function as-is. It is advised that these props map to valid HTML attributes - otherwise you will likely have HTML errors.
 * @param S - The state interface of the component.
 */
abstract class PureFoundation<H, U, S> extends React.PureComponent<H & U & PureFoundationProps, S> {
    /**
     * The props that should never be passed to the root element by unhandled props
     */
    private static defaultHandledProps: string[] = ["children"];
 
    /**
     * An enumeration of all handled props. All props passed to the component that are not enumerated here will be
     * treated as unhandled props
     */
    protected handledProps: HandledProps<H>;
 
    /**
     * Store all memoized ref callbacks so they can quickly be accessed. Storing the functions
     * allows us to not create new ref functions every update cycle
     */
    protected referenceResolverStore: ReferenceResolverStore = {};
 
    /**
     * Location where all react element and component references are stored
     */
    protected referenceStore: ReferenceStore = {};
 
    /**
     * Stores a react ref callback under the path provided as arguments. Paths are resolved using lodash's get/set API.
     * The reference object itself will be stored on the referenceStore under the path provided and can be accessed via
     * the getRef method under the same path.
     *
     * Usage: <div ref={this.setRef("content-container")} />
     */
    protected setRef(...args: Array<string | number>): ReferenceResolver {
        const storageKey: string = this.processStorageKey(args);
        let resolverFunction: ReferenceResolver | ReferenceResolverStore = get(
            this.referenceResolverStore,
            storageKey
        );
 
        if (
            !storageKey ||
            isPlainObject(resolverFunction) ||
            Array.isArray(resolverFunction)
        ) {
            return;
        }
 
        Iif (typeof resolverFunction === "function") {
            return resolverFunction;
        } else {
            resolverFunction = (ref: React.ReactNode): void => {
                set(this.referenceStore, storageKey, ref);
            };
 
            set(this.referenceResolverStore, storageKey, resolverFunction);
 
            return resolverFunction;
        }
    }
 
    /**
     * Get a reference by key , where function arguments are used as to create the keyname,
     * eg. getRef('foo', 'bar', 0) resolves to this.references.foo.bar[0];
     *
     * Usage: const contentContainer = this.getRef("content-container");
     */
    protected getRef(...args: Array<string | number>): React.ReactNode {
        return get(this.referenceStore, this.processStorageKey(args));
    }
 
    /**
     * Returns an object containing all props that are not enumerated as handledProps
     */
    protected unhandledProps(): U {
        const unhandledPropKeys: string[] = Object.keys(this.props).filter(
            (key: string) => {
                return (
                    !(PureFoundation.defaultHandledProps.indexOf(key) > -1) &&
                    !this.handledProps.hasOwnProperty(key)
                );
            }
        );
 
        return pick(this.props, unhandledPropKeys) as U;
    }
 
    /**
     * Joins any string with the className prop passed to the component. Used for applying a className to the root
     * element of a component's render function.
     */
    protected generateClassNames(componentClasses: string = ""): string | null {
        return (
            componentClasses
                .concat(` ${this.props.className || ""}`)
                .trim()
                .replace(/(\s){2,}/g, " ") || null
        );
    }
 
    /**
     * Generates a string that conforms to object/array accessor syntax that can be used by lodash's get / set,
     * eg. => ["foo", "bar", 0] => "foo[bar][0]"
     */
    private processStorageKey(args: Array<string | number>): string {
        return args
            .filter((item: string | number) => {
                return typeof item === "string" || typeof item === "number";
            })
            .map((item: string | number, index: number) => {
                return index === 0 ? item : `[${item}]`;
            })
            .join("");
    }
}
 
export default PureFoundation;
export { PureFoundationProps };