# All files / foundation foundation.ts

96.88% Statements 31/32
96.15% Branches 25/26
100% Functions 15/15
96.88% Lines 31/32

    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
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    17615x

    15x
    15x

    15x

    268x

    268x

    43x
    43x

    43x
    8x

    35x

    35x
    18x

    35x

    35x

    8x

    320x
    1301x

    320x

    324x

    683x
    1870x

    48x
    327x

    2197x

    2197x

    51x
    67x

    46x

    15x
    15x
     import * as React from "react";
    import * as ReactDOM from "react-dom";
    import { get, isPlainObject, pick, set } from "lodash-es";
    import { IFoundationProps } from "./foundation.props";

    /**
     * Describes the object that stores memoized reference resolver functions
     */
    export interface IReferenceResolverStore {
        [key: string]: IReferenceResolverStore | ReferenceResolver;
    }

    /**
     * Describes the object that stores all resolved react element and component references
     */
    export interface IReferenceStore {
        [key: string]: IReferenceStore | React.ReactNode;
    }

    /**
     * Describes the object that enumerates all handled props for a component. This
     * object includes all props that are in some way consumed or manipulated by component
     * code. These props will not be mapped onto the underlying root DOM node
     */
    export type HandledProps<T> = {
        [P in keyof T]: void
    };

    /**
     * Describes a function that that resolves a react reference element or component.
     */
    export type ReferenceResolver = <T>(reference: T) => void;

    /**
     * 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.
     */
    class Foundation<H, U, S> extends React.Component<H & U & IFoundationProps, 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: IReferenceResolverStore = {};

        /**
         * Location where all react element and component references are stored
         */
        protected referenceStore: IReferenceStore = {};

        /**
         * 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 | IReferenceResolverStore = 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 !(Foundation.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;
        }

        /*
         * Return an array of all nodes who's slot prop matches the provided slot.
         * If no nodes are provided, `this.props.children` will be used
         */
        protected withSlot<T>(
            slot: T | T[],
            nodes: React.ReactNode = this.props.children
        ): React.ReactNode {
            return React.Children.map(nodes, (node: React.ReactNode): React.ReactNode | null => {
                return this.hasSlot(slot, node)
                    ? node
                    : null;
            });
        }

        protected withoutSlot<T>(
            slot: T | T[],
            nodes: React.ReactNode = this.props.children
        ): React.ReactNode {
            return React.Children.map(nodes, (node: React.ReactNode): React.ReactNode | null => {
                return !this.hasSlot(slot, node)
                    ? node
                    : null;
            });
        }

        /**
         * Determine if a single node has a slot property
         */
        private hasSlot<T>(slot: T | T[], node: React.ReactNode): boolean {
            const nodeSlot: T = get(node, "props.slot");

            return Array.isArray(slot)
                ? slot.indexOf(nodeSlot) !== -1
                : slot === nodeSlot;
        }

        /**
         * 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 Foundation;
    export { IFoundationProps };