Press n or j to go to the next uncovered block, b, p or k for the previous block.
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 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | 1x 1x 1x 55x 55x 43x 43x 8x 35x 18x 35x 35x 8x 6x 67x 6x 9x 5x 13x 5x 13x 26x 26x 51x 67x 46x 1x | import React from "react";
import { get, isPlainObject, pick, set } from "lodash-es";
import { FoundationProps } from "./foundation.props";
/**
* Describes the object that stores memoized reference resolver functions
*/
export interface ReferenceResolverStore {
[key: string]: ReferenceResolverStore | ReferenceResolver;
}
/**
* Describes the object that stores all resolved react element and component references
*/
export interface ReferenceStore {
[key: string]: ReferenceStore | 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.
*/
abstract class Foundation<H, U, S> extends React.Component<H & U & FoundationProps, 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;
}
if (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 && !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 { FoundationProps };
|