UNPKG

@harvest-profit/npk

Version:
310 lines (286 loc) 11 kB
import { change, getMonthNames, getMonthNamesStartingWith, today } from '../Calendar/utils'; import { rule, MaskType } from '../hooks/useMask'; interface NumericMaskProps { maximumFractionDigits?: number; minimumFractionDigits?: number; minValue?: number; maxValue?: number; separator?: string | false; } export const numericMask: MaskType = (props: NumericMaskProps = {}) => { const { maximumFractionDigits, minimumFractionDigits, minValue, maxValue, separator } = props; return { mask: [ rule('-', ({ nextValue, cursorIndex, target }) => { if (Number.isFinite(minValue) && minValue >= 0) return false; if (nextValue.match(/-/g).length > 1 || cursorIndex !== 0) return false; return true; }), rule('.', ({ nextValue, cursorIndex }) => { if (nextValue[cursorIndex] === '-') return false; if (Number.isInteger(maximumFractionDigits) && maximumFractionDigits <= 0) return false; return nextValue.match(/\./g).length <= 1; }), rule(',', ({ nextValue, previousValue, cursorIndex }) => { if (nextValue[cursorIndex] === '-') return false; if (previousValue[cursorIndex - 1] === (separator || ',')) return false; if (previousValue.slice(0, cursorIndex).split('.')[1]) return false; return true; }), rule(/^[0-9]$/, ({ cursorIndex, nextValue }) => { if (nextValue[cursorIndex] === '-') return false; const numberValue = parseFloat(nextValue.replace(/,/g, '')); if (Number.isFinite(maxValue) && numberValue > maxValue) return false; if (Number.isFinite(minValue) && numberValue < minValue) return false; if (Number.isInteger(maximumFractionDigits) && (nextValue.split('.')[1]?.length || 0) > maximumFractionDigits) return false; return true; }), ], formatter: (value) => { if (!value || value.length === 0) return value; const numberValue = parseFloat(value.replace(/,/g, '')); if (isNaN(numberValue)) return ''; const numberString = isFinite(maximumFractionDigits) ? numberValue.toFixed(maximumFractionDigits) : numberValue.toString(); const parts = numberString.split('.'); if (separator !== false) { parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, (separator || ',')); } if (isFinite(minimumFractionDigits)) { const decimalNumber = parseFloat(`0.${parts[1] || '0'}`); parts[1] = isNaN(decimalNumber) ? '0' : (`${decimalNumber}`.split('.')[1] || '0'); parts[1] = parts[1].padEnd(minimumFractionDigits, '0') } else if (parseInt(parts[1]) === 0) { parts.pop(); } return parts.join('.'); } } } export const calendarDayMask: MaskType = ({ dateValue = today() } = {}) => { const performRule = (nextInputValue: string) => { if (nextInputValue === '0') return true; if (nextInputValue.length > 2) return false; // max of 2 digits for day const nextDateValue = change(dateValue, nextInputValue, 'day'); if (dateValue.getMonth() !== nextDateValue.getMonth()) return false; if (dateValue.getFullYear() !== nextDateValue.getFullYear()) return false; if (nextDateValue.getDate() !== parseInt(nextInputValue, 10)) return false; return true; } return { shiftFocusIf: (nextValue, key) => { if (key == '/') return true; if (nextValue === '0') return false; return performRule(nextValue) && !performRule(`${nextValue}0`); }, mask: [ rule(/^[0-9]$/, ({ nextValue }) => { return performRule(nextValue) }), ], formatter: (value) => { if (!value || value.length === 0) return value; if (value === '0') return '01'; // Special case for zero return `${value}`.padStart(2, '0'); }, aria: { 'aria-valuemin': 1, 'aria-valuemax': 31 } } } export const calendarMonthMask: MaskType = ({ dateValue = today() } = {}) => { const performRule = (nextInputValue: string) => { if (nextInputValue === '0') return true; if (nextInputValue.length > 2) return false; // max of 2 digits for month const monthNumber = parseInt(nextInputValue, 10) - 1; // Convert to zero-based month index const nextDateValue = change(dateValue, monthNumber, 'monthIndex'); if (nextDateValue.getMonth() !== monthNumber) return false; if (dateValue.getFullYear() !== nextDateValue.getFullYear()) return false; if (dateValue.getDate() !== nextDateValue.getDate()) return false; return true; } return { shiftFocusIf: (nextValue, key) => { if (key == '/') return true; if (nextValue === '0') return false; return performRule(nextValue) && !performRule(`${nextValue}0`); }, mask: [ rule(/^[0-9]$/, ({ nextValue }) => performRule(nextValue)), ], formatter: (value) => { if (!value || value.length === 0) return value; if (value === '0') return '01'; // Special case for zero return `${value}`.padStart(2, '0'); }, aria: { 'aria-valuemin': 1, 'aria-valuemax': 12 } } } export const calendarYearMask: MaskType = (props = {}) => { const performRule = (nextInputValue: string) => { if (nextInputValue.length <= 4) return true; // needs at least 4 digits for year return false; } return { shiftFocusIf: (nextValue, key) => key == '/' || (performRule(nextValue) && !performRule(`${nextValue}0`)), mask: [ rule(/^[0-9]$/, ({ nextValue }) => performRule(nextValue)), ], formatter: (value) => { if (!value) return value; return `${value}`.padStart(3, '0').padStart(4, '2');; }, aria: { 'aria-valuemin': 1, 'aria-valuemax': 9999 } } } export const calendarHourMask: MaskType = ({ militaryTime = false } = {}) => { const performRule = (nextInputValue: string) => { if (nextInputValue === '0') return true; if (nextInputValue.length > 2) return false; // max of 2 digits for hour const numberValue = parseInt(nextInputValue, 10); if (militaryTime) { if (numberValue >= 0 && numberValue <= 23) return true; // 00-23 for military time } else { if (numberValue >= 1 && numberValue <= 12) return true; // 01-12 for standard time } return false; } return { shiftFocusIf: (nextValue, key) => { if (key == ':') return true; if (nextValue === '0') return false; return (performRule(nextValue) && !performRule(`${nextValue}0`)) }, mask: [ rule(/^[0-9]$/, ({ nextValue }) => performRule(nextValue)), ], formatter: (value) => { if (!value || value.length === 0) return value; if (value === '0' && !militaryTime) return '01'; // Special case for zero return `${value}`.padStart(2, '0'); }, aria: { 'aria-valuemin': 1, 'aria-valuemax': 23 } } } export const calendarMinuteMask: MaskType = (props = {}) => { const performRule = (nextInputValue: string) => { if (nextInputValue === '0') return true; if (nextInputValue.length > 2) return false; // max of 2 digits for minute const numberValue = parseInt(nextInputValue, 10); if (numberValue >= 0 && numberValue <= 59) return true; return false; } return { shiftFocusIf: (nextValue, key) => { if (key == ':') return true; if (nextValue === '0') return false; return (performRule(nextValue) && !performRule(`${nextValue}0`)) }, mask: [ rule(/^[0-9]$/, ({ nextValue }) => performRule(nextValue)), ], formatter: (value) => { if (!value || value.length === 0) return value; return `${value}`.padStart(2, '0'); }, aria: { 'aria-valuemin': 0, 'aria-valuemax': 59 } } } export const calendarTimeOfDayMask: MaskType = (props = {}) => { return { shiftFocusIf: (_nextValue, key) => { const lowerKey = key.toLowerCase(); if (lowerKey == 'a' || lowerKey == 'p') return true; return false; }, mask: [ rule(/^[aApP]$/) ], autoComplete: (value, key) => { if (key.toLowerCase() === 'a') return 'AM'; if (key.toLowerCase() === 'p') return 'PM'; return value; }, formatter: (value) => { if (!value || value.length === 0) return value; if (value.toLowerCase().startsWith('a')) return 'AM'; if (value.toLowerCase().startsWith('p')) return 'PM'; return value; }, aria: { 'aria-valuemin': 0, 'aria-valuemax': 1 } } } // For english for example, this will return 'Jul' for july (since June also starts with "Ju"), and "Oct" for "o" since it is unique. function autoCompleteKeyForAbbreviation(abbrev: string, characterCount = 1) { if (abbrev.length === characterCount) return abbrev; const matches = getMonthNamesStartingWith(abbrev.slice(0, characterCount), 'default', 'short'); if (matches.length === 1) { return abbrev.toLowerCase().slice(0, characterCount); } else if (matches.length === 0) { return 'noclue'; } else { return autoCompleteKeyForAbbreviation(abbrev, characterCount + 1); } } export const calendarMonthNameMask: MaskType = (props = {}) => { const monthAbbrevs = getMonthNames('default', 'short'); const autoCompleteKeys = monthAbbrevs.map((abbrev) => [autoCompleteKeyForAbbreviation(abbrev), abbrev]); const autoComplete = (value: string) => { const foundAutoCompleteKey = autoCompleteKeys.find((keyMatch) => value.startsWith(keyMatch[0])); if (!foundAutoCompleteKey) return value; return foundAutoCompleteKey[1]; } return { shiftFocusIf: (nextValue) => { if (autoComplete(nextValue) !== nextValue) return true; if (nextValue.length >= 3) return true; return false; }, mask: [ rule(/^[a-zA-Z]$/, ({ nextValue }) => { if (nextValue.length > 3) return false; // max of 3 letters for month name for (let i = 0; i < monthAbbrevs.length; i++) { if (monthAbbrevs[i].toLowerCase().startsWith(nextValue.toLowerCase())) return true; // Check if the next value starts with a valid month abbreviation } return false; // If it doesn't match any month abbreviation, return false }) ], autoComplete, formatter: (value) => { if (!value || value.length === 0) return value; for (let i = 0; i < monthAbbrevs.length; i++) { const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); if (monthAbbrevs[i].toLowerCase().startsWith(value.toLowerCase())) return capitalize(monthAbbrevs[i]); // Check if the next value starts with a valid month abbreviation } return value; }, aria: { 'aria-valuemin': 1, 'aria-valuemax': 12 } } } export default { 'number': numericMask, 'calendar-day': calendarDayMask, 'calendar-month': calendarMonthMask, 'calendar-year': calendarYearMask, 'time-hour': calendarHourMask, 'time-minute': calendarMinuteMask, 'time-tod': calendarTimeOfDayMask }