grommet
Version:
focus on the essential experience
223 lines (217 loc) • 10.4 kB
JavaScript
var _excluded = ["children", "options", "property", "range"];
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
import React, { useContext, useMemo, useState } from 'react';
import { DataContext } from '../../contexts/DataContext';
import { DataForm } from '../Data/DataForm';
import { FormField } from '../FormField';
import { CheckBoxGroup } from '../CheckBoxGroup';
import { RangeSelector } from '../RangeSelector';
import { SelectMultiple } from '../SelectMultiple';
import { DataFilterPropTypes } from './propTypes';
import { getDecimalCount } from '../RangeSelector/RangeSelector';
import { DataFormContext } from '../../contexts/DataFormContext';
import { selectInputId } from '../Select/utils';
import { useThemeValue } from '../../utils/useThemeValue';
// empirical constants for when we change inputs
var maxCheckBoxGroupOptions = 4;
var minSelectSearchOptions = 10;
var defaultRangeSteps = 20;
var _getValueAt = function getValueAt(valueObject, pathArg) {
if (!pathArg || valueObject === undefined) return undefined;
var path = Array.isArray(pathArg) ? pathArg : pathArg.split('.');
if (path.length === 1) return valueObject[path];
return _getValueAt(valueObject[path.shift()], path);
};
var generateOptions = function generateOptions(data, property) {
return Array.from(new Set(data.map(function (d) {
return _getValueAt(d, property);
}))).filter(function (v) {
return v !== undefined && v !== '';
})
// ensure number values are sorted appropriately
// [132, 15, 100] --> [15, 100, 132]
// empty sort() would result in [100, 132, 15]
.sort(function (a, b) {
if (typeof a === 'number' && typeof b === 'number') return a - b;
if (typeof a === 'string' && typeof b === 'string' || typeof a === 'boolean' && typeof b === 'boolean') return a < b ? -1 : 1;
return 0;
});
};
// ensure floating point calculations are in integers
var alignMax = function alignMax(value, interval) {
var multiplier = Math.pow(10, Math.max(getDecimalCount(value), getDecimalCount(interval)));
var integerValue = value * multiplier;
var integerInterval = interval * multiplier;
if (value > 0) return (integerValue - integerValue % integerInterval + integerInterval) / multiplier;
if (value < 0) return (integerValue + integerValue % integerInterval) / multiplier;
return value;
};
// ensure floating point calculations are in integers
var alignMin = function alignMin(value, interval) {
var multiplier = Math.pow(10, Math.max(getDecimalCount(value), getDecimalCount(interval)));
var integerValue = value * multiplier;
var integerInterval = interval * multiplier;
if (value > 0) return (integerValue - integerValue % integerInterval) / multiplier;
if (value < 0) return (integerValue - integerValue % integerInterval - integerInterval) / multiplier;
return value;
};
var booleanOptions = [{
label: 'true',
value: true
}, {
label: 'false',
value: false
}];
export var DataFilter = function DataFilter(_ref) {
var _properties$property3, _properties$property5;
var children = _ref.children,
optionsProp = _ref.options,
property = _ref.property,
rangeProp = _ref.range,
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
var _useContext = useContext(DataContext),
data = _useContext.data,
dataId = _useContext.id,
properties = _useContext.properties,
unfilteredData = _useContext.unfilteredData;
var _useContext2 = useContext(DataFormContext),
inDataForm = _useContext2.inDataForm;
var _useState = useState(''),
searchText = _useState[0],
setSearchText = _useState[1];
var _useThemeValue = useThemeValue(),
theme = _useThemeValue.theme;
var _useMemo = useMemo(function () {
var _properties$property, _properties$property2;
if (children) return [undefined, undefined]; // caller driving
var optionsIn = optionsProp || (properties == null || (_properties$property = properties[property]) == null ? void 0 : _properties$property.options);
var rangeIn = rangeProp || (properties == null || (_properties$property2 = properties[property]) == null ? void 0 : _properties$property2.range);
if (optionsIn) return [optionsIn, undefined];
if (rangeIn && 'min' in rangeIn && 'max' in rangeIn) return [undefined, [rangeIn.min, rangeIn.max]];
// generate options from all values for property
var uniqueValues = generateOptions(unfilteredData || data, property);
// if less than two values, nothing to filter
if (uniqueValues.length < 2) return [undefined, undefined];
// if any values aren't numeric, treat as options
if (uniqueValues.some(function (v) {
return v !== undefined && typeof v !== 'number';
})) return [uniqueValues, undefined];
// all values are numeric, treat as range
var delta = uniqueValues[uniqueValues.length - 1] - uniqueValues[0];
var interval = Number.parseFloat((delta / 3).toPrecision(1));
var min = uniqueValues[0];
var max = uniqueValues[uniqueValues.length - 1];
// normalize to make it friendler, so [1.3, 4.895] becomes [1, 5]
if (getDecimalCount(min) > 0 || getDecimalCount(max) > 0) {
min = alignMin(min, interval);
max = alignMax(max, interval);
}
return [undefined, [min, max]];
}, [children, data, optionsProp, properties, property, rangeProp, unfilteredData]),
options = _useMemo[0],
range = _useMemo[1];
var searchedOptions = useMemo(function () {
if (!searchText) return options;
// The line below escapes regular expression special characters:
// [ \ ^ $ . | ? * + ( )
var escapedText = searchText.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
// Create the regular expression with modified value which
// handles escaping special characters. Without escaping special
// characters, errors will appear in the console
var exp = new RegExp(escapedText, 'i');
return options.filter(function (o) {
return typeof o === 'string' ? exp.test(o) : exp.test(o.label);
});
}, [options, searchText]);
var id = dataId + "-" + property;
// only add aria-label for no form examples
var ariaLabel = !inDataForm ? "" + ((properties == null || (_properties$property3 = properties[property]) == null ? void 0 : _properties$property3.label) || property) : undefined;
var htmlFor = id;
var content = children;
if (!content) {
if (range) {
var _properties$property4, _theme$dataFilter;
var step =
// from `range` on DataFilter
(rangeProp == null ? void 0 : rangeProp.step) || (// from range in Data `properties`
properties == null || (_properties$property4 = properties[property]) == null || (_properties$property4 = _properties$property4.range) == null ? void 0 : _properties$property4.step);
if (!step) {
// avoid floating point issues where 4.4 - 2 returns 2.4000000000000004
// and instead return 2.4 by doing calculations in integers then
// restore order of magnitude
var multiplicationFactor = Math.pow(10, Math.max(getDecimalCount(range[1]), getDecimalCount(range[0])));
var delta = (range[1] * multiplicationFactor - range[0] * multiplicationFactor) / multiplicationFactor;
// avoid floating point issues where
// 0.012 / 20 returns 0.0006000000000000001
// and instead return 0.0006
// or 2.8 / 20 returns 0.13999999999999999
// and istead return 0.14
var decimalCount = getDecimalCount(delta);
if (decimalCount) {
var multiplier = Math.pow(10, decimalCount);
step = multiplier * delta / (multiplier * defaultRangeSteps);
} else step = delta / defaultRangeSteps;
}
content = /*#__PURE__*/React.createElement(RangeSelector, _extends({
"aria-label": ariaLabel,
id: id,
name: property + "._range",
defaultValues: range,
label: true,
min: range[0],
max: range[1],
step: step
}, (_theme$dataFilter = theme.dataFilter) == null ? void 0 : _theme$dataFilter.rangeSelector));
} else if (options) {
if (options.length === 2 && options[1] === true && options[0] === false) {
// special case boolean properties
content = /*#__PURE__*/React.createElement(CheckBoxGroup, {
"aria-label": ariaLabel,
id: id,
name: property,
options: booleanOptions
});
} else if (options.length <= maxCheckBoxGroupOptions) {
content = /*#__PURE__*/React.createElement(CheckBoxGroup, {
"aria-label": ariaLabel,
id: id,
name: property,
options: options
});
} else {
var _theme$dataFilter2;
content = /*#__PURE__*/React.createElement(SelectMultiple, _extends({
"aria-label": ariaLabel,
id: id,
name: property,
showSelectedInline: true,
options: searchedOptions,
onSearch: options.length >= minSelectSearchOptions ? setSearchText : undefined,
onClose: function onClose() {
return setSearchText('');
},
labelKey: "label",
valueKey: {
key: 'value',
reduce: true
}
}, (_theme$dataFilter2 = theme.dataFilter) == null ? void 0 : _theme$dataFilter2.selectMultiple));
htmlFor = selectInputId(id);
}
}
}
if (!content) return null;
if (!inDataForm)
// likely in Toolbar
content = /*#__PURE__*/React.createElement(DataForm, {
footer: false,
updateOn: "change"
}, content);else content = /*#__PURE__*/React.createElement(FormField, _extends({
htmlFor: htmlFor,
name: property,
label: (properties == null || (_properties$property5 = properties[property]) == null ? void 0 : _properties$property5.label) || property
}, rest), content);
return content;
};
DataFilter.propTypes = DataFilterPropTypes;