UNPKG

react-widgets

Version:

An à la carte set of polished, extensible, and accessible inputs built for React

523 lines (440 loc) 17 kB
"use strict"; exports.__esModule = true; exports.default = void 0; var _classnames = _interopRequireDefault(require("classnames")); var _querySelectorAll = _interopRequireDefault(require("dom-helpers/querySelectorAll")); var _propTypes = _interopRequireDefault(require("prop-types")); var _react = _interopRequireWildcard(require("react")); var _uncontrollable = require("uncontrollable"); var _Button = _interopRequireDefault(require("./Button")); var _DateTimePartInput = _interopRequireDefault(require("./DateTimePartInput")); var _Icon = require("./Icon"); var _Widget = _interopRequireDefault(require("./Widget")); var _dates = _interopRequireDefault(require("./dates")); var _useFocusManager = _interopRequireDefault(require("./useFocusManager")); const _excluded = ["value", "use12HourClock", "padValues", "emptyCharacter", "precision", "noClearButton", "hoursAddon", "minutesAddon", "secondsAddon", "millisecondsAddon", "className", "disabled", "readOnly", "datePart", "onChange", "onBlur", "onFocus"]; function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } const selectTextRange = el => { if (el instanceof HTMLInputElement) return el.select(); const range = document.createRange(); range.selectNodeContents(el); const selection = window.getSelection(); if (selection) { selection.removeAllRanges(); selection.addRange(range); } }; // prettier-ignore const isEmptyValue = (p, precision) => p.hours == null && p.minutes == null && (precision != 'seconds' && precision !== 'milliseconds' || p.seconds == null) && (precision !== 'milliseconds' || p.milliseconds == null); // prettier-ignore const isPartialValue = (p, precision) => p.hours == null || p.minutes == null || (precision === 'seconds' || precision === 'milliseconds') && p.seconds == null || precision === 'milliseconds' && p.milliseconds == null; const getValueParts = (value, use12HourClock) => { let hours, minutes, seconds, milliseconds; let meridiem = 'AM'; if (value) { hours = value.getHours(); if (use12HourClock) { meridiem = hours < 12 ? 'AM' : 'PM'; hours = hours % 12 || 12; } minutes = value.getMinutes(); seconds = value.getSeconds(); milliseconds = value.getMilliseconds(); } return { hours, minutes, seconds, milliseconds, meridiem }; }; const TEST_VALID = { hours: /^([1]?[0-9]|2[0-3])$/, hours12: /^(1[0-2]|0?[1-9])$/, minutes: /^([0-5]?\d)$/, seconds: /^([0-5]?\d)$/, milliseconds: /^(\d{1,3})$/ }; const TEST_COMPLETE = { hours: /^([3-9]|\d{2})$/, hours12: /^(\d{2}|[2-9])$/, minutes: /^(d{2}|[6-9])$/, seconds: /^(d{2}|[6-9])$/, milliseconds: /^(\d{3})$/ }; function testPart(value, part, use12HourClock, tests) { const key = part === 'hours' && use12HourClock ? 'hours12' : part; return tests[key].test(value); } const isValid = (value, part, use12HourClock) => testPart(value, part, use12HourClock, TEST_VALID); const isComplete = (value, part, use12HourClock) => testPart(value, part, use12HourClock, TEST_COMPLETE); const propTypes = { /** * @example ['valuePicker', [ ['new Date()'] ]] */ value: _propTypes.default.instanceOf(Date), /** * @example ['onChangePicker', [ ['new Date()'] ]] */ onChange: _propTypes.default.func, /** * The default date used to construct a new time when the `value` is empty * * @default new Date() **/ datePart: _propTypes.default.instanceOf(Date), /** * Use a 12 hour clock (with AM/PM) instead of 24 hour one. * The configured localizer may provide a default value . **/ use12HourClock: _propTypes.default.bool, /** Time part values will be padded by `0` */ padValues: _propTypes.default.bool, /** The string character used to pad empty, or cleared values */ emptyCharacter: _propTypes.default.string, /** Hide the input clear button */ noClearButton: _propTypes.default.bool, /** * @example ['disabled', ['new Date()']] */ disabled: _propTypes.default.bool, /** * @example ['readOnly', ['new Date()']] */ readOnly: _propTypes.default.bool, /** Controls how precise of a time can be input **/ precision: _propTypes.default.oneOf(['minutes', 'seconds', 'milliseconds']).isRequired, /** * The seperator between hours and minutes * @default ':' */ hoursAddon: _propTypes.default.node, /** * The seperator between hours and minutes * @default ':' */ minutesAddon: _propTypes.default.node, /** * The seperator between hours and minutes * @default ':' */ secondsAddon: _propTypes.default.node, /** * The seperator between hours and minutes * @default '.' */ millisecondsAddon: _propTypes.default.node }; const defaultProps = { hoursAddon: ':', padValues: true, precision: 'minutes', emptyCharacter: '-' }; // let count = 0 function useTimePartState(value, use12HourClock) { const [state, setState] = (0, _react.useState)(() => ({ value, use12HourClock, timeParts: getValueParts(value, use12HourClock) })); const setTimeParts = (0, _react.useCallback)(timeParts => setState(s => Object.assign({}, s, { timeParts })), [setState]); if (state.value !== value || state.use12HourClock !== use12HourClock) { // count++ // if (count < 100) setState({ value, use12HourClock, timeParts: getValueParts(value, use12HourClock) }); } return [state.timeParts, setTimeParts]; } function TimeInput(uncontrolledProps) { const _useUncontrolled = (0, _uncontrollable.useUncontrolled)(uncontrolledProps, { value: 'onChange' }), { value, use12HourClock, padValues: pad, emptyCharacter, precision, noClearButton, hoursAddon, minutesAddon, secondsAddon, millisecondsAddon, className, disabled, readOnly, datePart, onChange, onBlur, onFocus } = _useUncontrolled, props = _objectWithoutPropertiesLoose(_useUncontrolled, _excluded); let minsAddon = minutesAddon !== undefined ? minutesAddon : precision === 'seconds' || precision === 'milliseconds' ? ':' : ''; let secsAddon = secondsAddon !== undefined ? secondsAddon : precision === 'milliseconds' ? ':' : ''; const ref = (0, _react.useRef)(null); const hourRef = (0, _react.useRef)(null); const [focusEvents, focused] = (0, _useFocusManager.default)(ref, { disabled, onBlur, onFocus }, { didHandle: (focused, e) => { var _hourRef$current; if (!focused) return; if (!e.target.dataset.focusable) (_hourRef$current = hourRef.current) == null ? void 0 : _hourRef$current.focus();else select(e.target); } }); const [timeParts, setTimeParts] = useTimePartState(value != null ? value : null, use12HourClock != null ? use12HourClock : false); function getDatePart() { return _dates.default.startOf(datePart || new Date(), 'day'); } const getMin = part => part === 'hours' ? 1 : 0; const getMax = part => { if (part === 'hours') return use12HourClock ? 12 : 23; if (part === 'milliseconds') return 999; return 59; }; function select(target = document.activeElement) { window.Promise.resolve().then(() => { if (focused) selectTextRange(target); }); } /** * Handlers */ const handleClear = () => { var _hourRef$current2; (_hourRef$current2 = hourRef.current) == null ? void 0 : _hourRef$current2.focus(); if (value) onChange(null);else setTimeParts(getValueParts(null)); }; const handleChange = (part, event) => { const currentValue = timeParts[part]; const { target } = event; const rawValue = target.value; let strValue = `${currentValue || ''}${rawValue}`; let numValue = +strValue; if (isNaN(numValue) || strValue && !isValid(strValue, part, use12HourClock != null ? use12HourClock : false)) { // the combined value is now past the max or invalid so try the single // digit and "start over" filling the value if (isValid(rawValue, part, use12HourClock != null ? use12HourClock : false) && !isNaN(+rawValue)) { // change the effective current value strValue = rawValue; numValue = +rawValue; } else { return event.preventDefault(); } } const nextValue = target.value ? numValue : null; notifyChange({ [part]: nextValue }); if (nextValue != null && isComplete(strValue, part, use12HourClock != null ? use12HourClock : false)) { focusNext(event.currentTarget, +1); } else { select(target); } }; const handleSelect = ({ target }) => { select(target); }; const handleKeyDown = (part, event) => { const { key } = event; const input = event.currentTarget; const { selectionStart: start, selectionEnd: end } = input; const isRTL = getComputedStyle(input).getPropertyValue('direction') === 'rtl'; const isMeridiem = part === 'meridiem'; const isNext = key === (isRTL ? 'ArrowLeft' : 'ArrowRight'); const isPrev = key === (isRTL ? 'ArrowRight' : 'ArrowLeft'); if (key === 'ArrowUp') { event.preventDefault(); increment(part, 1); } if (key === 'ArrowDown') { event.preventDefault(); increment(part, -1); } if (isPrev && (isMeridiem || start - 1 < 0)) { event.preventDefault(); focusNext(input, -1); } if (isNext && (isMeridiem || input.value.length <= end + 1)) { event.preventDefault(); focusNext(input, +1); } if (readOnly && key !== 'Tab') { event.preventDefault(); } if (isMeridiem) { if (key === 'a' || key === 'A') notifyChange({ meridiem: 'AM' }); if (key === 'p' || key === 'P') notifyChange({ meridiem: 'PM' }); } }; const increment = (part, inc) => { let nextPart = timeParts[part]; if (part === 'meridiem') { nextPart = nextPart === 'AM' ? 'PM' : 'AM'; } else { nextPart = (nextPart || 0) + inc; if (!isValid(String(nextPart), part, use12HourClock != null ? use12HourClock : false)) return; } notifyChange({ [part]: nextPart }); select(); }; function notifyChange(updates) { const nextTimeParts = Object.assign({}, timeParts, updates); if (value && isEmptyValue(nextTimeParts, precision)) { return onChange(null); } if (isPartialValue(nextTimeParts, precision)) return setTimeParts(nextTimeParts); let { hours, minutes, seconds, milliseconds, meridiem } = nextTimeParts; let nextDate = new Date(value || getDatePart()); if (use12HourClock) { if (hours === 12) hours = 0; hours += meridiem === 'PM' ? 12 : 0; } nextDate.setHours(hours); nextDate.setMinutes(minutes); if (seconds != null) nextDate.setSeconds(seconds); if (milliseconds != null) nextDate.setMilliseconds(milliseconds); onChange(nextDate, { lastValue: value, timeParts }); } function focusNext(input, delta) { let nodes = (0, _querySelectorAll.default)(ref.current, '* [data-focusable]'); let next = nodes[nodes.indexOf(input) + delta]; next == null ? void 0 : next.focus(); select(next); } const { hours, minutes, seconds, milliseconds, meridiem } = timeParts; const showClear = !isEmptyValue(timeParts, precision); return /*#__PURE__*/_react.default.createElement(_Widget.default, _extends({}, props, { role: "group", ref: ref }, focusEvents, { focused: focused, disabled: disabled, readOnly: readOnly, className: (0, _classnames.default)(className, 'rw-time-input rw-widget-input') }), /*#__PURE__*/_react.default.createElement(_DateTimePartInput.default, { size: 2, pad: pad ? 2 : undefined, value: hours, disabled: disabled, readOnly: readOnly, "aria-label": "hours", min: getMin('hours'), max: getMax('hours'), ref: hourRef, emptyChar: emptyCharacter, onSelect: handleSelect, onChange: e => handleChange('hours', e), onKeyDown: e => handleKeyDown('hours', e) }), hoursAddon && /*#__PURE__*/_react.default.createElement("span", null, hoursAddon), /*#__PURE__*/_react.default.createElement(_DateTimePartInput.default, { size: 2, pad: pad ? 2 : undefined, value: minutes, disabled: disabled, readOnly: readOnly, "aria-label": "minutes", min: getMin('minutes'), max: getMax('minutes'), emptyChar: emptyCharacter, onSelect: handleSelect, onChange: e => handleChange('minutes', e), onKeyDown: e => handleKeyDown('minutes', e) }), minsAddon && /*#__PURE__*/_react.default.createElement("span", null, minsAddon), (precision === 'seconds' || precision === 'milliseconds') && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_DateTimePartInput.default, { size: 2, pad: pad ? 2 : undefined, value: seconds, disabled: disabled, readOnly: readOnly, "aria-label": "seconds", min: getMin('seconds'), max: getMax('seconds'), emptyChar: emptyCharacter, onSelect: handleSelect, onChange: e => handleChange('seconds', e), onKeyDown: e => handleKeyDown('seconds', e) }), secsAddon && /*#__PURE__*/_react.default.createElement("span", null, secsAddon)), precision === 'milliseconds' && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_DateTimePartInput.default, { size: 3, pad: pad ? 3 : undefined, value: milliseconds, disabled: disabled, readOnly: readOnly, "aria-label": "milliseconds", min: getMin('milliseconds'), max: getMax('milliseconds'), emptyChar: emptyCharacter, onSelect: handleSelect, onChange: e => handleChange('milliseconds', e), onKeyDown: e => handleKeyDown('milliseconds', e) }), millisecondsAddon && /*#__PURE__*/_react.default.createElement("span", null, millisecondsAddon)), use12HourClock && /*#__PURE__*/_react.default.createElement("div", { role: "listbox", "aria-label": "AM/PM", "aria-disabled": disabled, "aria-readonly": readOnly, className: "rw-time-part-meridiem" }, /*#__PURE__*/_react.default.createElement("div", { "data-focusable": true, role: "option", "aria-atomic": true, "aria-selected": true, "aria-setsize": 2, "aria-live": "assertive", "aria-disabled": disabled, "aria-readonly": readOnly, "aria-posinset": meridiem === 'AM' ? 1 : 2, tabIndex: !disabled ? 0 : void 0, onFocus: handleSelect, onSelect: handleSelect, onKeyDown: e => handleKeyDown('meridiem', e) }, /*#__PURE__*/_react.default.createElement("abbr", null, meridiem))), !noClearButton && /*#__PURE__*/_react.default.createElement(_Button.default, { label: 'clear input', onClick: handleClear, disabled: disabled || readOnly, className: (0, _classnames.default)('rw-time-input-clear', showClear && 'rw-show') }, _Icon.times)); } TimeInput.propTypes = propTypes; TimeInput.defaultProps = defaultProps; var _default = TimeInput; exports.default = _default;