@tounsoo/input-number-mask 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +19 -16
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @tounsoo/input-number-mask
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@tounsoo/input-number-mask)
|
|
4
|
+
[](https://bundlephobia.com/package/@tounsoo/input-number-mask)
|
|
5
|
+
[](https://github.com/tounsoo/input-number-mask/blob/main/LICENSE)
|
|
6
|
+
|
|
3
7
|
A lightweight, dependency-free React hook for masking input values. Perfect for phone numbers, dates, credit cards, and more.
|
|
4
8
|
|
|
5
9
|
## Features
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/utils/maskUtils.ts","../src/useInputNumberMask.ts"],"sourcesContent":["export const isDigit = (char: string) => /\\d/.test(char);\n\nexport const cleanInput = (input: string, template: string): string => {\n let extracted = '';\n let t = 0;\n\n for (let i = 0; i < input.length; i++) {\n const char = input[i];\n\n if (t >= template.length) {\n break;\n }\n\n if (template[t] === 'd') {\n if (isDigit(char)) {\n extracted += char;\n t++;\n } else {\n // Ignore placeholders or garbage\n }\n } else if (template[t] === char) {\n t++;\n } else {\n let nextT = t;\n while (nextT < template.length && template[nextT] !== 'd' && template[nextT] !== char) {\n nextT++;\n }\n if (nextT < template.length && template[nextT] === 'd') {\n if (isDigit(char)) {\n extracted += char;\n t = nextT + 1;\n }\n } else if (nextT < template.length && template[nextT] === char) {\n t = nextT + 1;\n }\n }\n }\n return extracted;\n};\n\nexport const formatWithMask = (digits: string, template: string, placeholder?: string): string => {\n let res = '';\n let dIdx = 0;\n\n for (let i = 0; i < template.length; i++) {\n const isSlot = template[i] === 'd';\n\n if (isSlot) {\n if (dIdx < digits.length) {\n res += digits[dIdx++];\n } else {\n // Empty slot\n if (placeholder && i < placeholder.length) {\n res += placeholder[i];\n } else {\n if (!placeholder) {\n break;\n }\n }\n }\n } else {\n res += template[i];\n }\n }\n return res;\n};\n","import { useRef, useState, useLayoutEffect } from 'react';\nimport { cleanInput, formatWithMask, isDigit } from './utils/maskUtils';\n\n/**\n * useInputNumberMask\n * \n * A hook for masking number inputs with template support.\n * \n * @param template The mask template. 'd' represents a digit. All other characters are treated as literals.\n * Example: \"+1 (ddd) ddd-dddd\" for US phone numbers.\n * @param placeholder Optional full-length placeholder. Characters that match the template literals \n * will be displayed. 'd' positions can be filled with a placeholder char or kept as is.\n * Example: \"dd/mm/yyyy\" for a date mask \"dd/mm/yyyy\".\n * @param keepPosition If true, deleting a character will replace it with the placeholder char (if valid)\n * or keep the cursor position without shifting subsequent characters (if supported).\n * For now, we'll implement standard behavior where backspace deletes and shifts, \n * unless specific requirements enforce strict position keeping.\n * TODO: fully implement \"keep position\" logic if needed.\n */\nexport interface UseInputNumberMaskProps {\n template: string;\n placeholder?: string;\n keepPosition?: boolean;\n}\n\nexport interface UseInputNumberMaskReturn {\n value: string; // The raw value with formatting (e.g. \"+1 (234) 567-8900\")\n displayValue: string; // The value to display in the input\n rawValue: string; // The unmasked digits (e.g. \"12345678\")\n ref: React.RefObject<HTMLInputElement | null>;\n}\n\nexport function useInputNumberMask({\n template,\n placeholder,\n keepPosition = false,\n}: UseInputNumberMaskProps): UseInputNumberMaskReturn {\n\n const [value, setValue] = useState(() => formatWithMask('', template, placeholder));\n const [cursor, setCursor] = useState<number | null>(null);\n const ref = useRef<HTMLInputElement>(null);\n\n useLayoutEffect(() => {\n if (ref.current && cursor !== null) {\n ref.current.setSelectionRange(cursor, cursor);\n }\n }, [cursor, value]);\n\n // Ref-based Event Handling\n useLayoutEffect(() => {\n const input = ref.current;\n if (!input) return;\n\n const handleKeyDown = (e: globalThis.KeyboardEvent) => {\n const el = e.target as HTMLInputElement;\n const start = el.selectionStart || 0;\n const end = el.selectionEnd || 0;\n const isSelection = start !== end;\n\n if (e.key === 'Backspace') {\n if (start === 0 && !isSelection) return;\n // We prevent default to handle the state update manually\n e.preventDefault();\n\n let deleteIndex = start - 1;\n\n if (isSelection) {\n if (keepPosition) {\n // Replace range with placeholders\n let newVal = value;\n for (let i = start; i < end; i++) {\n if (template[i] === 'd') {\n const pChar = placeholder && i < placeholder.length ? placeholder[i] : '_';\n newVal = newVal.substring(0, i) + pChar + newVal.substring(i + 1);\n }\n }\n setValue(newVal);\n setCursor(start);\n return;\n } else {\n // Standard delete range (shift)\n const newValRaw = value.slice(0, start) + value.slice(end);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n return;\n }\n }\n\n // Single char backspace\n while (deleteIndex >= 0) {\n const isTemplateLiteral = template[deleteIndex] !== 'd';\n if (isTemplateLiteral) {\n deleteIndex--;\n } else {\n break;\n }\n }\n\n if (deleteIndex < 0) return; // Nothing to delete\n\n if (keepPosition) {\n const pChar = placeholder && deleteIndex < placeholder.length ? placeholder[deleteIndex] : '_';\n const newVal = value.substring(0, deleteIndex) + pChar + value.substring(deleteIndex + 1);\n setValue(newVal);\n setCursor(deleteIndex);\n } else {\n // Shift behavior\n const newValRaw = value.slice(0, deleteIndex) + value.slice(deleteIndex + 1);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(deleteIndex);\n }\n } else if (e.key === 'Delete') {\n e.preventDefault();\n // Delete forward\n if (isSelection) {\n if (keepPosition) {\n let newVal = value;\n for (let i = start; i < end; i++) {\n if (template[i] === 'd') {\n const pChar = placeholder && i < placeholder.length ? placeholder[i] : '_';\n newVal = newVal.substring(0, i) + pChar + newVal.substring(i + 1);\n }\n }\n setValue(newVal);\n setCursor(start);\n return;\n } else {\n const newValRaw = value.slice(0, start) + value.slice(end);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n return;\n }\n }\n\n // Single char delete\n let deleteIndex = start;\n while (deleteIndex < template.length) {\n const isTemplateLiteral = template[deleteIndex] !== 'd';\n if (isTemplateLiteral) {\n deleteIndex++;\n } else {\n break;\n }\n }\n\n if (deleteIndex >= value.length) return;\n\n if (keepPosition) {\n const pChar = placeholder && deleteIndex < placeholder.length ? placeholder[deleteIndex] : '_';\n const newVal = value.substring(0, deleteIndex) + pChar + value.substring(deleteIndex + 1);\n setValue(newVal);\n setCursor(start);\n } else {\n // Shift behavior\n const newValRaw = value.slice(0, deleteIndex) + value.slice(deleteIndex + 1);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n }\n }\n };\n\n const handleInput = (e: Event) => {\n // We cast to InputEvent or basic Event. target is HTMLInputElement.\n const target = e.target as HTMLInputElement;\n const inputVal = target.value;\n const rawCursor = target.selectionStart || 0;\n\n // Standard behavior logic\n const standardChange = () => {\n const beforeCursor = inputVal.slice(0, rawCursor);\n const digitsBeforeCursor = cleanInput(beforeCursor, template).length;\n\n const newDigits = cleanInput(inputVal, template);\n const newFormatted = formatWithMask(newDigits, template, placeholder);\n\n setValue(newFormatted);\n\n let currentDigits = 0;\n let newCursor = 0;\n for (let i = 0; i < newFormatted.length; i++) {\n if (currentDigits >= digitsBeforeCursor) {\n break;\n }\n if (isDigit(newFormatted[i]) && template[i] === 'd') {\n currentDigits++;\n }\n newCursor++;\n }\n\n while (newCursor < newFormatted.length && template[newCursor] !== 'd') {\n newCursor++;\n }\n\n setCursor(newCursor);\n };\n\n if (keepPosition) {\n // Measure diff against current state `value` (mapped in closure)\n // NOTE: `value` in this closure is stale if not included in dependency array.\n // We need strict dependency on `value`.\n if (inputVal.length > value.length) {\n const insertIndex = rawCursor - 1;\n const char = inputVal[insertIndex];\n\n if (!isDigit(char)) {\n standardChange();\n return;\n }\n\n let targetIndex = insertIndex;\n while (targetIndex < template.length) {\n if (template[targetIndex] === 'd') {\n break;\n }\n targetIndex++;\n }\n\n if (targetIndex >= template.length) {\n standardChange();\n return;\n }\n\n const newValue = value.substring(0, targetIndex) + char + value.substring(targetIndex + 1);\n\n setValue(newValue);\n setCursor(targetIndex + 1);\n return;\n }\n }\n\n standardChange();\n };\n\n input.addEventListener('keydown', handleKeyDown);\n input.addEventListener('input', handleInput);\n\n return () => {\n input.removeEventListener('keydown', handleKeyDown);\n input.removeEventListener('input', handleInput);\n };\n }, [value, template, placeholder, keepPosition]); // Re-bind when value changes to have fresh closure\n\n const rawValue = cleanInput(value, template);\n\n return {\n value,\n displayValue: value,\n rawValue,\n ref\n };\n}\n"],"names":["isDigit","char","cleanInput","input","template","extracted","t","i","nextT","formatWithMask","digits","placeholder","res","dIdx","useInputNumberMask","keepPosition","value","setValue","useState","cursor","setCursor","ref","useRef","useLayoutEffect","handleKeyDown","e","el","start","end","isSelection","deleteIndex","newVal","pChar","newValRaw","newDigits","formatted","handleInput","target","inputVal","rawCursor","standardChange","beforeCursor","digitsBeforeCursor","newFormatted","currentDigits","newCursor","insertIndex","targetIndex","newValue","rawValue"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/utils/maskUtils.ts","../src/useInputNumberMask.ts"],"sourcesContent":["export const isDigit = (char: string) => /\\d/.test(char);\n\nexport const cleanInput = (input: string, template: string): string => {\n let extracted = '';\n let t = 0;\n\n for (let i = 0; i < input.length; i++) {\n const char = input[i];\n\n if (t >= template.length) {\n break;\n }\n\n if (template[t] === 'd') {\n if (isDigit(char)) {\n extracted += char;\n t++;\n } else {\n // Ignore placeholders or garbage\n }\n } else if (template[t] === char) {\n t++;\n } else {\n let nextT = t;\n while (nextT < template.length && template[nextT] !== 'd' && template[nextT] !== char) {\n nextT++;\n }\n if (nextT < template.length && template[nextT] === 'd') {\n if (isDigit(char)) {\n extracted += char;\n t = nextT + 1;\n }\n } else if (nextT < template.length && template[nextT] === char) {\n t = nextT + 1;\n }\n }\n }\n return extracted;\n};\n\nexport const formatWithMask = (digits: string, template: string, placeholder?: string): string => {\n let res = '';\n let dIdx = 0;\n\n for (let i = 0; i < template.length; i++) {\n const isSlot = template[i] === 'd';\n\n if (isSlot) {\n if (dIdx < digits.length) {\n res += digits[dIdx++];\n } else {\n // Empty slot\n if (placeholder && i < placeholder.length) {\n res += placeholder[i];\n } else {\n if (!placeholder) {\n break;\n }\n }\n }\n } else {\n res += template[i];\n }\n }\n return res;\n};\n","import { useRef, useState, useLayoutEffect } from 'react';\nimport { cleanInput, formatWithMask, isDigit } from './utils/maskUtils';\n\nexport interface UseInputNumberMaskProps {\n /**\n * The mask template. 'd' represents a digit slot.\n * All other characters are treated as literals.\n * @example \"+1 (ddd) ddd-dddd\" for US phone numbers\n */\n template: string;\n\n /**\n * Optional full-length placeholder shown in empty slots.\n * Should match the template length.\n * @example \"mm/dd/yyyy\" for a date mask\n */\n placeholder?: string;\n\n /**\n * If true, deletion replaces with placeholder char\n * instead of shifting subsequent digits left.\n * @default false\n */\n keepPosition?: boolean;\n}\n\nexport interface UseInputNumberMaskReturn {\n /** \n * The raw value with formatting applied \n * @example \"+1 (234) 567-8900\" \n */\n value: string;\n /** \n * Deprecated: alias for value. The value to display in the input. \n * @example \"+1 (234) 567-8900\" \n */\n displayValue: string;\n /** \n * The unmasked digits only \n * @example \"1234567890\" \n */\n rawValue: string;\n /** \n * Ref to be attached to the HTML input element \n */\n ref: React.RefObject<HTMLInputElement | null>;\n}\n\nexport function useInputNumberMask({\n template,\n placeholder,\n keepPosition = false,\n}: UseInputNumberMaskProps): UseInputNumberMaskReturn {\n\n const [value, setValue] = useState(() => formatWithMask('', template, placeholder));\n const [cursor, setCursor] = useState<number | null>(null);\n const ref = useRef<HTMLInputElement>(null);\n\n useLayoutEffect(() => {\n if (ref.current && cursor !== null) {\n ref.current.setSelectionRange(cursor, cursor);\n }\n }, [cursor, value]);\n\n // Ref-based Event Handling\n useLayoutEffect(() => {\n const input = ref.current;\n if (!input) return;\n\n const handleKeyDown = (e: globalThis.KeyboardEvent) => {\n const el = e.target as HTMLInputElement;\n const start = el.selectionStart || 0;\n const end = el.selectionEnd || 0;\n const isSelection = start !== end;\n\n if (e.key === 'Backspace') {\n if (start === 0 && !isSelection) return;\n // We prevent default to handle the state update manually\n e.preventDefault();\n\n let deleteIndex = start - 1;\n\n if (isSelection) {\n if (keepPosition) {\n // Replace range with placeholders\n let newVal = value;\n for (let i = start; i < end; i++) {\n if (template[i] === 'd') {\n const pChar = placeholder && i < placeholder.length ? placeholder[i] : '_';\n newVal = newVal.substring(0, i) + pChar + newVal.substring(i + 1);\n }\n }\n setValue(newVal);\n setCursor(start);\n return;\n } else {\n // Standard delete range (shift)\n const newValRaw = value.slice(0, start) + value.slice(end);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n return;\n }\n }\n\n // Single char backspace\n while (deleteIndex >= 0) {\n const isTemplateLiteral = template[deleteIndex] !== 'd';\n if (isTemplateLiteral) {\n deleteIndex--;\n } else {\n break;\n }\n }\n\n if (deleteIndex < 0) return; // Nothing to delete\n\n if (keepPosition) {\n const pChar = placeholder && deleteIndex < placeholder.length ? placeholder[deleteIndex] : '_';\n const newVal = value.substring(0, deleteIndex) + pChar + value.substring(deleteIndex + 1);\n setValue(newVal);\n setCursor(deleteIndex);\n } else {\n // Shift behavior\n const newValRaw = value.slice(0, deleteIndex) + value.slice(deleteIndex + 1);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(deleteIndex);\n }\n } else if (e.key === 'Delete') {\n e.preventDefault();\n // Delete forward\n if (isSelection) {\n if (keepPosition) {\n let newVal = value;\n for (let i = start; i < end; i++) {\n if (template[i] === 'd') {\n const pChar = placeholder && i < placeholder.length ? placeholder[i] : '_';\n newVal = newVal.substring(0, i) + pChar + newVal.substring(i + 1);\n }\n }\n setValue(newVal);\n setCursor(start);\n return;\n } else {\n const newValRaw = value.slice(0, start) + value.slice(end);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n return;\n }\n }\n\n // Single char delete\n let deleteIndex = start;\n while (deleteIndex < template.length) {\n const isTemplateLiteral = template[deleteIndex] !== 'd';\n if (isTemplateLiteral) {\n deleteIndex++;\n } else {\n break;\n }\n }\n\n if (deleteIndex >= value.length) return;\n\n if (keepPosition) {\n const pChar = placeholder && deleteIndex < placeholder.length ? placeholder[deleteIndex] : '_';\n const newVal = value.substring(0, deleteIndex) + pChar + value.substring(deleteIndex + 1);\n setValue(newVal);\n setCursor(start);\n } else {\n // Shift behavior\n const newValRaw = value.slice(0, deleteIndex) + value.slice(deleteIndex + 1);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n }\n }\n };\n\n const handleInput = (e: Event) => {\n // We cast to InputEvent or basic Event. target is HTMLInputElement.\n const target = e.target as HTMLInputElement;\n const inputVal = target.value;\n const rawCursor = target.selectionStart || 0;\n\n // Standard behavior logic\n const standardChange = () => {\n const beforeCursor = inputVal.slice(0, rawCursor);\n const digitsBeforeCursor = cleanInput(beforeCursor, template).length;\n\n const newDigits = cleanInput(inputVal, template);\n const newFormatted = formatWithMask(newDigits, template, placeholder);\n\n setValue(newFormatted);\n\n let currentDigits = 0;\n let newCursor = 0;\n for (let i = 0; i < newFormatted.length; i++) {\n if (currentDigits >= digitsBeforeCursor) {\n break;\n }\n if (isDigit(newFormatted[i]) && template[i] === 'd') {\n currentDigits++;\n }\n newCursor++;\n }\n\n while (newCursor < newFormatted.length && template[newCursor] !== 'd') {\n newCursor++;\n }\n\n setCursor(newCursor);\n };\n\n if (keepPosition) {\n // Measure diff against current state `value` (mapped in closure)\n // NOTE: `value` in this closure is stale if not included in dependency array.\n // We need strict dependency on `value`.\n if (inputVal.length > value.length) {\n const insertIndex = rawCursor - 1;\n const char = inputVal[insertIndex];\n\n if (!isDigit(char)) {\n standardChange();\n return;\n }\n\n let targetIndex = insertIndex;\n while (targetIndex < template.length) {\n if (template[targetIndex] === 'd') {\n break;\n }\n targetIndex++;\n }\n\n if (targetIndex >= template.length) {\n standardChange();\n return;\n }\n\n const newValue = value.substring(0, targetIndex) + char + value.substring(targetIndex + 1);\n\n setValue(newValue);\n setCursor(targetIndex + 1);\n return;\n }\n }\n\n standardChange();\n };\n\n input.addEventListener('keydown', handleKeyDown);\n input.addEventListener('input', handleInput);\n\n return () => {\n input.removeEventListener('keydown', handleKeyDown);\n input.removeEventListener('input', handleInput);\n };\n }, [value, template, placeholder, keepPosition]); // Re-bind when value changes to have fresh closure\n\n const rawValue = cleanInput(value, template);\n\n return {\n value,\n displayValue: value,\n rawValue,\n ref\n };\n}\n"],"names":["isDigit","char","cleanInput","input","template","extracted","t","i","nextT","formatWithMask","digits","placeholder","res","dIdx","useInputNumberMask","keepPosition","value","setValue","useState","cursor","setCursor","ref","useRef","useLayoutEffect","handleKeyDown","e","el","start","end","isSelection","deleteIndex","newVal","pChar","newValRaw","newDigits","formatted","handleInput","target","inputVal","rawCursor","standardChange","beforeCursor","digitsBeforeCursor","newFormatted","currentDigits","newCursor","insertIndex","targetIndex","newValue","rawValue"],"mappings":"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"}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/utils/maskUtils.ts","../src/useInputNumberMask.ts"],"sourcesContent":["export const isDigit = (char: string) => /\\d/.test(char);\n\nexport const cleanInput = (input: string, template: string): string => {\n let extracted = '';\n let t = 0;\n\n for (let i = 0; i < input.length; i++) {\n const char = input[i];\n\n if (t >= template.length) {\n break;\n }\n\n if (template[t] === 'd') {\n if (isDigit(char)) {\n extracted += char;\n t++;\n } else {\n // Ignore placeholders or garbage\n }\n } else if (template[t] === char) {\n t++;\n } else {\n let nextT = t;\n while (nextT < template.length && template[nextT] !== 'd' && template[nextT] !== char) {\n nextT++;\n }\n if (nextT < template.length && template[nextT] === 'd') {\n if (isDigit(char)) {\n extracted += char;\n t = nextT + 1;\n }\n } else if (nextT < template.length && template[nextT] === char) {\n t = nextT + 1;\n }\n }\n }\n return extracted;\n};\n\nexport const formatWithMask = (digits: string, template: string, placeholder?: string): string => {\n let res = '';\n let dIdx = 0;\n\n for (let i = 0; i < template.length; i++) {\n const isSlot = template[i] === 'd';\n\n if (isSlot) {\n if (dIdx < digits.length) {\n res += digits[dIdx++];\n } else {\n // Empty slot\n if (placeholder && i < placeholder.length) {\n res += placeholder[i];\n } else {\n if (!placeholder) {\n break;\n }\n }\n }\n } else {\n res += template[i];\n }\n }\n return res;\n};\n","import { useRef, useState, useLayoutEffect } from 'react';\nimport { cleanInput, formatWithMask, isDigit } from './utils/maskUtils';\n\n/**\n * useInputNumberMask\n * \n * A hook for masking number inputs with template support.\n * \n * @param template The mask template. 'd' represents a digit. All other characters are treated as literals.\n * Example: \"+1 (ddd) ddd-dddd\" for US phone numbers.\n * @param placeholder Optional full-length placeholder. Characters that match the template literals \n * will be displayed. 'd' positions can be filled with a placeholder char or kept as is.\n * Example: \"dd/mm/yyyy\" for a date mask \"dd/mm/yyyy\".\n * @param keepPosition If true, deleting a character will replace it with the placeholder char (if valid)\n * or keep the cursor position without shifting subsequent characters (if supported).\n * For now, we'll implement standard behavior where backspace deletes and shifts, \n * unless specific requirements enforce strict position keeping.\n * TODO: fully implement \"keep position\" logic if needed.\n */\nexport interface UseInputNumberMaskProps {\n template: string;\n placeholder?: string;\n keepPosition?: boolean;\n}\n\nexport interface UseInputNumberMaskReturn {\n value: string; // The raw value with formatting (e.g. \"+1 (234) 567-8900\")\n displayValue: string; // The value to display in the input\n rawValue: string; // The unmasked digits (e.g. \"12345678\")\n ref: React.RefObject<HTMLInputElement | null>;\n}\n\nexport function useInputNumberMask({\n template,\n placeholder,\n keepPosition = false,\n}: UseInputNumberMaskProps): UseInputNumberMaskReturn {\n\n const [value, setValue] = useState(() => formatWithMask('', template, placeholder));\n const [cursor, setCursor] = useState<number | null>(null);\n const ref = useRef<HTMLInputElement>(null);\n\n useLayoutEffect(() => {\n if (ref.current && cursor !== null) {\n ref.current.setSelectionRange(cursor, cursor);\n }\n }, [cursor, value]);\n\n // Ref-based Event Handling\n useLayoutEffect(() => {\n const input = ref.current;\n if (!input) return;\n\n const handleKeyDown = (e: globalThis.KeyboardEvent) => {\n const el = e.target as HTMLInputElement;\n const start = el.selectionStart || 0;\n const end = el.selectionEnd || 0;\n const isSelection = start !== end;\n\n if (e.key === 'Backspace') {\n if (start === 0 && !isSelection) return;\n // We prevent default to handle the state update manually\n e.preventDefault();\n\n let deleteIndex = start - 1;\n\n if (isSelection) {\n if (keepPosition) {\n // Replace range with placeholders\n let newVal = value;\n for (let i = start; i < end; i++) {\n if (template[i] === 'd') {\n const pChar = placeholder && i < placeholder.length ? placeholder[i] : '_';\n newVal = newVal.substring(0, i) + pChar + newVal.substring(i + 1);\n }\n }\n setValue(newVal);\n setCursor(start);\n return;\n } else {\n // Standard delete range (shift)\n const newValRaw = value.slice(0, start) + value.slice(end);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n return;\n }\n }\n\n // Single char backspace\n while (deleteIndex >= 0) {\n const isTemplateLiteral = template[deleteIndex] !== 'd';\n if (isTemplateLiteral) {\n deleteIndex--;\n } else {\n break;\n }\n }\n\n if (deleteIndex < 0) return; // Nothing to delete\n\n if (keepPosition) {\n const pChar = placeholder && deleteIndex < placeholder.length ? placeholder[deleteIndex] : '_';\n const newVal = value.substring(0, deleteIndex) + pChar + value.substring(deleteIndex + 1);\n setValue(newVal);\n setCursor(deleteIndex);\n } else {\n // Shift behavior\n const newValRaw = value.slice(0, deleteIndex) + value.slice(deleteIndex + 1);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(deleteIndex);\n }\n } else if (e.key === 'Delete') {\n e.preventDefault();\n // Delete forward\n if (isSelection) {\n if (keepPosition) {\n let newVal = value;\n for (let i = start; i < end; i++) {\n if (template[i] === 'd') {\n const pChar = placeholder && i < placeholder.length ? placeholder[i] : '_';\n newVal = newVal.substring(0, i) + pChar + newVal.substring(i + 1);\n }\n }\n setValue(newVal);\n setCursor(start);\n return;\n } else {\n const newValRaw = value.slice(0, start) + value.slice(end);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n return;\n }\n }\n\n // Single char delete\n let deleteIndex = start;\n while (deleteIndex < template.length) {\n const isTemplateLiteral = template[deleteIndex] !== 'd';\n if (isTemplateLiteral) {\n deleteIndex++;\n } else {\n break;\n }\n }\n\n if (deleteIndex >= value.length) return;\n\n if (keepPosition) {\n const pChar = placeholder && deleteIndex < placeholder.length ? placeholder[deleteIndex] : '_';\n const newVal = value.substring(0, deleteIndex) + pChar + value.substring(deleteIndex + 1);\n setValue(newVal);\n setCursor(start);\n } else {\n // Shift behavior\n const newValRaw = value.slice(0, deleteIndex) + value.slice(deleteIndex + 1);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n }\n }\n };\n\n const handleInput = (e: Event) => {\n // We cast to InputEvent or basic Event. target is HTMLInputElement.\n const target = e.target as HTMLInputElement;\n const inputVal = target.value;\n const rawCursor = target.selectionStart || 0;\n\n // Standard behavior logic\n const standardChange = () => {\n const beforeCursor = inputVal.slice(0, rawCursor);\n const digitsBeforeCursor = cleanInput(beforeCursor, template).length;\n\n const newDigits = cleanInput(inputVal, template);\n const newFormatted = formatWithMask(newDigits, template, placeholder);\n\n setValue(newFormatted);\n\n let currentDigits = 0;\n let newCursor = 0;\n for (let i = 0; i < newFormatted.length; i++) {\n if (currentDigits >= digitsBeforeCursor) {\n break;\n }\n if (isDigit(newFormatted[i]) && template[i] === 'd') {\n currentDigits++;\n }\n newCursor++;\n }\n\n while (newCursor < newFormatted.length && template[newCursor] !== 'd') {\n newCursor++;\n }\n\n setCursor(newCursor);\n };\n\n if (keepPosition) {\n // Measure diff against current state `value` (mapped in closure)\n // NOTE: `value` in this closure is stale if not included in dependency array.\n // We need strict dependency on `value`.\n if (inputVal.length > value.length) {\n const insertIndex = rawCursor - 1;\n const char = inputVal[insertIndex];\n\n if (!isDigit(char)) {\n standardChange();\n return;\n }\n\n let targetIndex = insertIndex;\n while (targetIndex < template.length) {\n if (template[targetIndex] === 'd') {\n break;\n }\n targetIndex++;\n }\n\n if (targetIndex >= template.length) {\n standardChange();\n return;\n }\n\n const newValue = value.substring(0, targetIndex) + char + value.substring(targetIndex + 1);\n\n setValue(newValue);\n setCursor(targetIndex + 1);\n return;\n }\n }\n\n standardChange();\n };\n\n input.addEventListener('keydown', handleKeyDown);\n input.addEventListener('input', handleInput);\n\n return () => {\n input.removeEventListener('keydown', handleKeyDown);\n input.removeEventListener('input', handleInput);\n };\n }, [value, template, placeholder, keepPosition]); // Re-bind when value changes to have fresh closure\n\n const rawValue = cleanInput(value, template);\n\n return {\n value,\n displayValue: value,\n rawValue,\n ref\n };\n}\n"],"names":["isDigit","char","cleanInput","input","template","extracted","t","i","nextT","formatWithMask","digits","placeholder","res","dIdx","useInputNumberMask","keepPosition","value","setValue","useState","cursor","setCursor","ref","useRef","useLayoutEffect","handleKeyDown","e","el","start","end","isSelection","deleteIndex","newVal","pChar","newValRaw","newDigits","formatted","handleInput","target","inputVal","rawCursor","standardChange","beforeCursor","digitsBeforeCursor","newFormatted","currentDigits","newCursor","insertIndex","targetIndex","newValue","rawValue"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/utils/maskUtils.ts","../src/useInputNumberMask.ts"],"sourcesContent":["export const isDigit = (char: string) => /\\d/.test(char);\n\nexport const cleanInput = (input: string, template: string): string => {\n let extracted = '';\n let t = 0;\n\n for (let i = 0; i < input.length; i++) {\n const char = input[i];\n\n if (t >= template.length) {\n break;\n }\n\n if (template[t] === 'd') {\n if (isDigit(char)) {\n extracted += char;\n t++;\n } else {\n // Ignore placeholders or garbage\n }\n } else if (template[t] === char) {\n t++;\n } else {\n let nextT = t;\n while (nextT < template.length && template[nextT] !== 'd' && template[nextT] !== char) {\n nextT++;\n }\n if (nextT < template.length && template[nextT] === 'd') {\n if (isDigit(char)) {\n extracted += char;\n t = nextT + 1;\n }\n } else if (nextT < template.length && template[nextT] === char) {\n t = nextT + 1;\n }\n }\n }\n return extracted;\n};\n\nexport const formatWithMask = (digits: string, template: string, placeholder?: string): string => {\n let res = '';\n let dIdx = 0;\n\n for (let i = 0; i < template.length; i++) {\n const isSlot = template[i] === 'd';\n\n if (isSlot) {\n if (dIdx < digits.length) {\n res += digits[dIdx++];\n } else {\n // Empty slot\n if (placeholder && i < placeholder.length) {\n res += placeholder[i];\n } else {\n if (!placeholder) {\n break;\n }\n }\n }\n } else {\n res += template[i];\n }\n }\n return res;\n};\n","import { useRef, useState, useLayoutEffect } from 'react';\nimport { cleanInput, formatWithMask, isDigit } from './utils/maskUtils';\n\nexport interface UseInputNumberMaskProps {\n /**\n * The mask template. 'd' represents a digit slot.\n * All other characters are treated as literals.\n * @example \"+1 (ddd) ddd-dddd\" for US phone numbers\n */\n template: string;\n\n /**\n * Optional full-length placeholder shown in empty slots.\n * Should match the template length.\n * @example \"mm/dd/yyyy\" for a date mask\n */\n placeholder?: string;\n\n /**\n * If true, deletion replaces with placeholder char\n * instead of shifting subsequent digits left.\n * @default false\n */\n keepPosition?: boolean;\n}\n\nexport interface UseInputNumberMaskReturn {\n /** \n * The raw value with formatting applied \n * @example \"+1 (234) 567-8900\" \n */\n value: string;\n /** \n * Deprecated: alias for value. The value to display in the input. \n * @example \"+1 (234) 567-8900\" \n */\n displayValue: string;\n /** \n * The unmasked digits only \n * @example \"1234567890\" \n */\n rawValue: string;\n /** \n * Ref to be attached to the HTML input element \n */\n ref: React.RefObject<HTMLInputElement | null>;\n}\n\nexport function useInputNumberMask({\n template,\n placeholder,\n keepPosition = false,\n}: UseInputNumberMaskProps): UseInputNumberMaskReturn {\n\n const [value, setValue] = useState(() => formatWithMask('', template, placeholder));\n const [cursor, setCursor] = useState<number | null>(null);\n const ref = useRef<HTMLInputElement>(null);\n\n useLayoutEffect(() => {\n if (ref.current && cursor !== null) {\n ref.current.setSelectionRange(cursor, cursor);\n }\n }, [cursor, value]);\n\n // Ref-based Event Handling\n useLayoutEffect(() => {\n const input = ref.current;\n if (!input) return;\n\n const handleKeyDown = (e: globalThis.KeyboardEvent) => {\n const el = e.target as HTMLInputElement;\n const start = el.selectionStart || 0;\n const end = el.selectionEnd || 0;\n const isSelection = start !== end;\n\n if (e.key === 'Backspace') {\n if (start === 0 && !isSelection) return;\n // We prevent default to handle the state update manually\n e.preventDefault();\n\n let deleteIndex = start - 1;\n\n if (isSelection) {\n if (keepPosition) {\n // Replace range with placeholders\n let newVal = value;\n for (let i = start; i < end; i++) {\n if (template[i] === 'd') {\n const pChar = placeholder && i < placeholder.length ? placeholder[i] : '_';\n newVal = newVal.substring(0, i) + pChar + newVal.substring(i + 1);\n }\n }\n setValue(newVal);\n setCursor(start);\n return;\n } else {\n // Standard delete range (shift)\n const newValRaw = value.slice(0, start) + value.slice(end);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n return;\n }\n }\n\n // Single char backspace\n while (deleteIndex >= 0) {\n const isTemplateLiteral = template[deleteIndex] !== 'd';\n if (isTemplateLiteral) {\n deleteIndex--;\n } else {\n break;\n }\n }\n\n if (deleteIndex < 0) return; // Nothing to delete\n\n if (keepPosition) {\n const pChar = placeholder && deleteIndex < placeholder.length ? placeholder[deleteIndex] : '_';\n const newVal = value.substring(0, deleteIndex) + pChar + value.substring(deleteIndex + 1);\n setValue(newVal);\n setCursor(deleteIndex);\n } else {\n // Shift behavior\n const newValRaw = value.slice(0, deleteIndex) + value.slice(deleteIndex + 1);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(deleteIndex);\n }\n } else if (e.key === 'Delete') {\n e.preventDefault();\n // Delete forward\n if (isSelection) {\n if (keepPosition) {\n let newVal = value;\n for (let i = start; i < end; i++) {\n if (template[i] === 'd') {\n const pChar = placeholder && i < placeholder.length ? placeholder[i] : '_';\n newVal = newVal.substring(0, i) + pChar + newVal.substring(i + 1);\n }\n }\n setValue(newVal);\n setCursor(start);\n return;\n } else {\n const newValRaw = value.slice(0, start) + value.slice(end);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n return;\n }\n }\n\n // Single char delete\n let deleteIndex = start;\n while (deleteIndex < template.length) {\n const isTemplateLiteral = template[deleteIndex] !== 'd';\n if (isTemplateLiteral) {\n deleteIndex++;\n } else {\n break;\n }\n }\n\n if (deleteIndex >= value.length) return;\n\n if (keepPosition) {\n const pChar = placeholder && deleteIndex < placeholder.length ? placeholder[deleteIndex] : '_';\n const newVal = value.substring(0, deleteIndex) + pChar + value.substring(deleteIndex + 1);\n setValue(newVal);\n setCursor(start);\n } else {\n // Shift behavior\n const newValRaw = value.slice(0, deleteIndex) + value.slice(deleteIndex + 1);\n const newDigits = cleanInput(newValRaw, template);\n const formatted = formatWithMask(newDigits, template, placeholder);\n setValue(formatted);\n setCursor(start);\n }\n }\n };\n\n const handleInput = (e: Event) => {\n // We cast to InputEvent or basic Event. target is HTMLInputElement.\n const target = e.target as HTMLInputElement;\n const inputVal = target.value;\n const rawCursor = target.selectionStart || 0;\n\n // Standard behavior logic\n const standardChange = () => {\n const beforeCursor = inputVal.slice(0, rawCursor);\n const digitsBeforeCursor = cleanInput(beforeCursor, template).length;\n\n const newDigits = cleanInput(inputVal, template);\n const newFormatted = formatWithMask(newDigits, template, placeholder);\n\n setValue(newFormatted);\n\n let currentDigits = 0;\n let newCursor = 0;\n for (let i = 0; i < newFormatted.length; i++) {\n if (currentDigits >= digitsBeforeCursor) {\n break;\n }\n if (isDigit(newFormatted[i]) && template[i] === 'd') {\n currentDigits++;\n }\n newCursor++;\n }\n\n while (newCursor < newFormatted.length && template[newCursor] !== 'd') {\n newCursor++;\n }\n\n setCursor(newCursor);\n };\n\n if (keepPosition) {\n // Measure diff against current state `value` (mapped in closure)\n // NOTE: `value` in this closure is stale if not included in dependency array.\n // We need strict dependency on `value`.\n if (inputVal.length > value.length) {\n const insertIndex = rawCursor - 1;\n const char = inputVal[insertIndex];\n\n if (!isDigit(char)) {\n standardChange();\n return;\n }\n\n let targetIndex = insertIndex;\n while (targetIndex < template.length) {\n if (template[targetIndex] === 'd') {\n break;\n }\n targetIndex++;\n }\n\n if (targetIndex >= template.length) {\n standardChange();\n return;\n }\n\n const newValue = value.substring(0, targetIndex) + char + value.substring(targetIndex + 1);\n\n setValue(newValue);\n setCursor(targetIndex + 1);\n return;\n }\n }\n\n standardChange();\n };\n\n input.addEventListener('keydown', handleKeyDown);\n input.addEventListener('input', handleInput);\n\n return () => {\n input.removeEventListener('keydown', handleKeyDown);\n input.removeEventListener('input', handleInput);\n };\n }, [value, template, placeholder, keepPosition]); // Re-bind when value changes to have fresh closure\n\n const rawValue = cleanInput(value, template);\n\n return {\n value,\n displayValue: value,\n rawValue,\n ref\n };\n}\n"],"names":["isDigit","char","cleanInput","input","template","extracted","t","i","nextT","formatWithMask","digits","placeholder","res","dIdx","useInputNumberMask","keepPosition","value","setValue","useState","cursor","setCursor","ref","useRef","useLayoutEffect","handleKeyDown","e","el","start","end","isSelection","deleteIndex","newVal","pChar","newValRaw","newDigits","formatted","handleInput","target","inputVal","rawCursor","standardChange","beforeCursor","digitsBeforeCursor","newFormatted","currentDigits","newCursor","insertIndex","targetIndex","newValue","rawValue"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tounsoo/input-number-mask",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A lightweight, dependency-free React hook for masking input values. Perfect for phone numbers, dates, credit cards, and more.",
|
|
5
|
+
"packageManager": "pnpm@10.23.0",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "./dist/index.cjs",
|
|
7
8
|
"module": "./dist/index.mjs",
|
|
@@ -23,6 +24,21 @@
|
|
|
23
24
|
"README.md",
|
|
24
25
|
"LICENSE"
|
|
25
26
|
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "vite",
|
|
29
|
+
"build": "tsc -b && vite build",
|
|
30
|
+
"build:lib": "tsc -p tsconfig.lib.json && vite build --config vite.config.lib.ts",
|
|
31
|
+
"lint": "eslint .",
|
|
32
|
+
"preview": "vite preview",
|
|
33
|
+
"storybook": "storybook dev -p 6006",
|
|
34
|
+
"build-storybook": "storybook build",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:ci": "vitest run --config vitest.config.ci.ts",
|
|
37
|
+
"prepublishOnly": "pnpm build:lib",
|
|
38
|
+
"changeset": "changeset",
|
|
39
|
+
"version": "changeset version",
|
|
40
|
+
"release": "pnpm build:lib && changeset publish --publish pnpm publish"
|
|
41
|
+
},
|
|
26
42
|
"peerDependencies": {
|
|
27
43
|
"react": "^18.0.0 || ^19.0.0",
|
|
28
44
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
@@ -83,20 +99,7 @@
|
|
|
83
99
|
"author": "tounsoo",
|
|
84
100
|
"license": "MIT",
|
|
85
101
|
"publishConfig": {
|
|
86
|
-
"access": "public"
|
|
87
|
-
|
|
88
|
-
"scripts": {
|
|
89
|
-
"dev": "vite",
|
|
90
|
-
"build": "tsc -b && vite build",
|
|
91
|
-
"build:lib": "tsc -p tsconfig.lib.json && vite build --config vite.config.lib.ts",
|
|
92
|
-
"lint": "eslint .",
|
|
93
|
-
"preview": "vite preview",
|
|
94
|
-
"storybook": "storybook dev -p 6006",
|
|
95
|
-
"build-storybook": "storybook build",
|
|
96
|
-
"test": "vitest run",
|
|
97
|
-
"test:ci": "vitest run --config vitest.config.ci.ts",
|
|
98
|
-
"changeset": "changeset",
|
|
99
|
-
"version": "changeset version",
|
|
100
|
-
"release": "pnpm build:lib && changeset publish"
|
|
102
|
+
"access": "public",
|
|
103
|
+
"provenance": true
|
|
101
104
|
}
|
|
102
105
|
}
|