Home Reference Source

src/components/ContainerControl.js

/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
/**
 * 
 *  ██▓ ███▄    █  ██ ▄█▀     ██████  ██▓███   ██▓    ▄▄▄     ▄▄▄█████▓
 *  ▓██▒ ██ ▀█   █  ██▄█▒    ▒██    ▒ ▓██░  ██▒▓██▒   ▒████▄   ▓  ██▒ ▓▒
 *  ▒██▒▓██  ▀█ ██▒▓███▄░    ░ ▓██▄   ▓██░ ██▓▒▒██░   ▒██  ▀█▄ ▒ ▓██░ ▒░
 *  ░██░▓██▒  ▐▌██▒▓██ █▄      ▒   ██▒▒██▄█▓▒ ▒▒██░   ░██▄▄▄▄██░ ▓██▓ ░
 *  ░██░▒██░   ▓██░▒██▒ █▄   ▒██████▒▒▒██▒ ░  ░░██████▒▓█   ▓██▒ ▒██▒ ░
 *  ░▓  ░ ▒░   ▒ ▒ ▒ ▒▒ ▓▒   ▒ ▒▓▒ ▒ ░▒▓▒░ ░  ░░ ▒░▓  ░▒▒   ▓▒█░ ▒ ░░
 *  ▒ ░░ ░░   ░ ▒░░ ░▒ ▒░   ░ ░▒  ░ ░░▒ ░     ░ ░ ▒  ░ ▒   ▒▒ ░   ░
 *  ▒ ░   ░   ░ ░ ░ ░░ ░    ░  ░  ░  ░░         ░ ░    ░   ▒    ░
 *  ░           ░ ░  ░            ░               ░  ░     ░  ░
 * 
 * LICENSE: MIT
 */
import React from 'react';
import Control from './Control';
import { isClassOrComponentFactory } from '../utils/isClassOrComponent';

/**
 * Provides focus-management functionality for controls that can function as a container
 * for other controls.
 */
export default class ContainerControl extends Control {
    /**
     * Returns a boolean which indicates if the provided `anyComponentOrClass` parameter
     * is an instance of `ContainerControl` (if passed an object) or a class derived from
     * `ContainerControl` (if passed a function) or a component derived from `ContainerControl`.
     * 
     * @param {*} anyComponentOrClass Any valid React component or React component class or instance
     * or derived component or class or instance.
     * @return `true` if `anyComponentOrClass` validates, `false` otherwise.
     */
    static isContainerControl(anyComponentOrClass) {
        return isClassOrComponentFactory(ContainerControl)(anyComponentOrClass);
    }

    constructor(props) {
        super(props);
        this.state = {
            ...this.state,
            activeControl: ''
        };
    }

    /**
     * Returns a boolean which indicates if a ContainerControl instance is focusable.
     * 
     * The default behavior is to:
     * (1) check containsFocus(): if true, return false (already focused); if false, proceed to
     * (2) return containsFocusable()
     */
    canFocus() {
        return (!this.containsFocus() && this.containsFocusable());
    }

    /**
     * Returns a boolean which indicates if the Control instance is un-focusable.
     * 
     * Most Controls which exist will be focusable. It is the deault behavior that a derived
     * Control class is always focusable.
     * 
     * If you want to disable focusable in a specific way, you can override the function and
     * return false instead of true.
     * 
     * For example, consider a TextField whose input might be disabled for now. During the
     * disabled state, canFocus() would return false, but otherwise return true.
     */
    canBlur() {
        return this.isFocused();
    }

    select(control) {
        // disable linter for now
        // TODO: fix this routine
        this.canFocus();
        if (Control.isDerivedControl(control)) {
            if (control.canFocus()) {
                if (control.props.onFocus) {
                    control.props.onFocus();
                }
            }
        }
    }

    /**
     * 
     *  In a command line interface, where focusable controls exist (think Visual Basic for DOS)
     *  to get an idea of the concept, we can't completely blur out of EVERY single control.
     *
     *  Something is always going to ALWAYS need to be accept / capture input, be it a Button
     *  or TextField or a Menu or a Tab or Tree. But the concept of the Control component is
     *  that it can easily allow focus to shift from one component to the next, and allow those
     *  components which extend the Control class to easily display one type of blurred output
     *  while accepting input once focused.
     *
     *  In order to guarantee this, blur will locate the forward (or reverse component)
     *  and 'select' it instead.
     */
    blur() {
        if (this.canBlur()) {
            // let our listeners know 
            if (this.props.onBlur) {
                this.props.onBlur();
            }
        }
    }

    isFocused() {
        return this.props.focused;
    }

    containsFocusable() {
        for (let c = 0; c < this.props.children.length; c++) {
            if (this.props.children[c].containsFocus &&
                this.props.children[c].containsFocus())
                return true;
        }
        return false;
    }

    renderChildren() {
        // let countDerived = 0;
        // we are going to modify the children array such that when one of our children components
        // blurs, we will bubble it out to select the next focusable control
        if (this.props.children) {
            const modifiedChildren = this.props.children.map((el) =>
            // expanded: const modifiedChildren = this.props.children.map((el, i, all) =>
                Control.isDerivedControl(el) ? React.cloneElement(el, {

                }) : el
            );
            return modifiedChildren;
        }
        return null;
    }

    render() {
        return this.renderChildren();
    }
}