All files / boundless/packages/boundless-fitted-text index.js

83.33% Statements 25/30
62.5% Branches 10/16
80% Functions 8/10
86.21% Lines 25/29
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            1x     27x       9x 9x 9x   9x 5x     9x 9x     9x         9x 9x     9x     9x                 5x 5x     5x       5x   5x 5x                                                                             5x       5x       4x       5x       9x                  
import React, {PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import cx from 'classnames';
 
import omit from 'boundless-utils-omit-keys';
 
const instances = [];
 
function toI(stringNumber) {
    return parseInt(stringNumber, 10);
}
 
function rescale(instance) {
    const node = findDOMNode(instance);
    const containerBox = window.getComputedStyle(node.parentNode);
    const fontSize = toI(window.getComputedStyle(node).fontSize);
 
    if (instance.baseFontSize === null) {
        instance.baseFontSize = fontSize;
    }
 
    let containerHeight = toI(containerBox.height);
    let containerWidth = toI(containerBox.width);
 
    // need to account for padding
    Iif (containerBox.boxSizing === 'border-box' || containerBox.boxSizing === 'padding-box') {
        containerHeight -= toI(containerBox.paddingTop) + toI(containerBox.paddingBottom);
        containerWidth -= toI(containerBox.paddingLeft) + toI(containerBox.paddingRight);
    }
 
    const optimizeForHeight = Math.floor((fontSize / node.offsetHeight) * containerHeight);
    const optimizeForWidth = Math.floor((fontSize / node.offsetWidth) * containerWidth);
 
    // if upscaling is allowed, that changes the math a bit
    Iif (instance.props.upscale) {
        node.style.fontSize = (Math.max(optimizeForHeight, optimizeForWidth) || 1) + 'px';
    } else {
        node.style.fontSize = (Math.min(instance.baseFontSize, optimizeForHeight, optimizeForWidth) || 1) + 'px';
    }
}
 
function handleWindowResize() {
    instances.forEach((instance) => rescale(instance));
}
 
function registerInstance(instance) {
    Eif (instances.length === 0) {
        window.addEventListener('resize', handleWindowResize, true);
    }
 
    instances.push(instance);
}
 
function unregisterInstance(instance) {
    instances.splice(instances.indexOf(instance), 1);
 
    Eif (instances.length === 0) {
        window.removeEventListener('resize', handleWindowResize, true);
    }
}
 
/**
__Fit single-line text inside a parent container, obeying implict constraints.__
 
This component can be useful in situations where an internationalized string is being placed into the UI and it's unclear if all variations of it will fit without excessive amounts of edge-case CSS. Ultimately, it's good at making sure what you put in doesn't overflow.
 */
export default class FittedText extends React.PureComponent {
    static propTypes = {
        /**
         * any [React-supported attribute](https://facebook.github.io/react/docs/tags-and-attributes.html#html-attributes)
         */
        '*': PropTypes.any,
 
        /**
         * any valid HTML tag name
         */
        component: PropTypes.string,
 
        /**
         * controls if FittedText will automatically scale up the content to fit the available space; normally the component
         * only scales text down as needed to fit
         */
        upscale: PropTypes.bool,
    }
 
    static defaultProps = {
        component: 'span',
        upscale: false,
    }
 
    static internalKeys = Object.keys(FittedText.defaultProps)
 
    // set during the first rescale() run
    baseFontSize = null
 
    componentDidMount() {
        rescale(this);
 
        // there are likely to be multiple instances of this component on a page, so it makes sense to just use
        // a shared global resize listener instead of each component having its own
        registerInstance(this);
    }
 
    componentDidUpdate() {
        rescale(this);
    }
 
    componentWillUnmount() {
        unregisterInstance(this);
    }
 
    render() {
        return (
            <this.props.component
                {...omit(this.props, FittedText.internalKeys)}
                className={cx('b-text', this.props.className)}>
                {this.props.children}
            </this.props.component>
        );
    }
}