AngularJS NumberOnly指令iPhone Safari减号输入异常问题
It looks like the issue stems from how iOS Safari handles input value modifications via Angular's ngModel parsers, combined with regex logic that doesn't account for the browser's quirks with cursor positioning and event timing. When you type a minus sign, the original directive's replacement behavior could be causing iOS Safari to interpret the change as a deletion instead of adding the character.
Here's a revised directive that addresses this by directly handling input events, controlling cursor position, and validating input more robustly:
app.directive('numberOnly', function () { return { restrict: 'A', require: 'ngModel', link: function (scope, elm, attrs, ngModel) { // Convert attributes to proper boolean/number types var allowMinus = attrs.allowMinus === 'true'; var digitsLength = parseInt(attrs.digitsLength) || 0; // Handle input events to manage view value and cursor position elm.on('input', function () { const oldValue = elm.val(); const selectionStart = this.selectionStart; const selectionEnd = this.selectionEnd; // Step 1: Remove all non-allowed characters first let newValue = oldValue.replace(/[^0-9-]/g, ''); // Step 2: Enforce minus rules if allowed if (allowMinus) { const minusCount = (newValue.match(/-/g) || []).length; if (minusCount > 1) { // Keep only the first minus (if at start) newValue = newValue.replace(/-/g, (match, index) => index === 0 ? match : ''); } else if (minusCount === 1 && newValue.indexOf('-') !== 0) { // Remove minus if it's not at the start newValue = newValue.replace(/-/g, ''); } } else { // Remove all minuses if not allowed newValue = newValue.replace(/-/g, ''); } // Step 3: Apply digit length limit if specified if (digitsLength > 0) { const numericPart = allowMinus && newValue.startsWith('-') ? newValue.slice(1) : newValue; if (numericPart.length > digitsLength) { const trimmedNumeric = numericPart.slice(0, digitsLength); newValue = allowMinus && newValue.startsWith('-') ? `-${trimmedNumeric}` : trimmedNumeric; } } // Update only if value changed, and restore cursor position if (newValue !== oldValue) { elm.val(newValue); ngModel.$setViewValue(newValue); ngModel.$render(); // Adjust cursor position to account for added/removed characters const lengthDiff = newValue.length - oldValue.length; let newStart = Math.max(0, selectionStart + lengthDiff); let newEnd = Math.max(0, selectionEnd + lengthDiff); // Special case: if we removed a minus that was at the cursor position if (oldValue.slice(selectionStart - 1, selectionStart) === '-' && !newValue.includes('-')) { newStart--; newEnd--; } this.setSelectionRange(newStart, newEnd); } }); // Ensure model value stays in sync with view validation ngModel.$parsers.push(function (value) { if (!value) return value; let processedValue = value.replace(/[^0-9-]/g, ''); if (allowMinus) { const minusIndex = processedValue.indexOf('-'); if (minusIndex !== -1 && minusIndex !== 0) { processedValue = processedValue.replace(/-/g, ''); } else if ((processedValue.match(/-/g) || []).length > 1) { processedValue = `-${processedValue.replace(/-/g, '')}`; } } else { processedValue = processedValue.replace(/-/g, ''); } if (digitsLength > 0) { const numericPart = allowMinus && processedValue.startsWith('-') ? processedValue.slice(1) : processedValue; if (numericPart.length > digitsLength) { const trimmedNumeric = numericPart.slice(0, digitsLength); processedValue = allowMinus && processedValue.startsWith('-') ? `-${trimmedNumeric}` : trimmedNumeric; } } if (processedValue !== value) { ngModel.$setViewValue(processedValue); ngModel.$render(); } return processedValue; }); } }; });
Key Fixes & Improvements:
- Direct Input Event Handling: We listen to the
inputevent to immediately control the view value, avoiding iOS Safari's quirky interpretation of value changes from ngModel parsers. - Cursor Position Restoration: After modifying the input value, we save and restore the cursor position. This prevents the cursor from jumping to the end or causing unintended deletions—likely the root cause of your minus-key-as-backspace issue.
- Robust Minus Validation: We explicitly allow only one minus sign, and only at the start of the input. Any other minuses are removed cleanly.
- Type Safety: Attributes like
allow-minusare converted to proper boolean values (useallow-minus="true"in your HTML instead of justallow-minus). - Dual Validation: We handle both user input (view) and programmatic changes (model) to keep everything in sync.
Usage Example:
<input type="text" number-only allow-minus="true" digits-length="10" ng-model="myNumber">
Test this thoroughly on iOS Safari, especially edge cases like typing a minus as the first character, inserting a minus in the middle of digits, deleting the first character and retyping a minus, and pasting text with invalid characters.
内容的提问来源于stack exchange,提问作者Min Min

