/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/forms/signals/src/directive/native.ts
211 строк
7 KB
Pawel Kozlowski
Revert "fix(forms): preserve intermediate number values in signal forms"
20 июл 2026, 11:45
20 июл 2026, 11:45
7a62617
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import {ɵformatRuntimeError as formatRuntimeError, untracked} from '@angular/core'; import {NativeInputParseError, WithoutFieldTree} from '../api/rules'; import type {ParseResult} from '../api/transformed_value'; import {RuntimeErrorCode} from '../errors'; import type {InputValidityMonitor} from './input_validity_monitor'; // Re-export shared native utilities from main forms package export { ɵelementAcceptsMinMax as elementAcceptsMinMax, ɵisNativeFormElement as isNativeFormElement, ɵisTextualFormElement as isTextualFormElement, ɵsetNativeDomProperty as setNativeDomProperty, type ɵNativeFormControl as NativeFormControl, } from '@angular/forms'; import type {ɵNativeFormControl as NativeFormControl} from '@angular/forms'; /** * Returns the value from a native control element. * * @param element The native control element. * @param currentValue A function that returns the current value from the control's corresponding * field state. * * The type of the returned value depends on the `type` property of the control, and will attempt to * match the current value's type. For example, the value of `<input type="number">` can be read as * a `string` or a `number`. If the current value is a `number`, then this will return a `number`. * Otherwise, this will return the value as a `string`. */ export function getNativeControlValue( element: NativeFormControl, currentValue: () => unknown, validityMonitor: InputValidityMonitor, ): ParseResult<unknown> { let modelValue: unknown; if (isInput(element) && validityMonitor.isBadInput(element)) { return { error: new NativeInputParseError() as WithoutFieldTree<NativeInputParseError>, }; } // Special cases for specific input types. switch (element.type) { case 'checkbox': return {value: element.checked}; case 'number': case 'range': case 'datetime-local': // We can read a `number` or a `string` from this input type. Prefer whichever is consistent // with the current type. modelValue = untracked(currentValue); if (typeof modelValue === 'number' || modelValue === null) { return {value: element.value === '' ? null : element.valueAsNumber}; } break; case 'date': case 'month': case 'time': case 'week': // We can read a `Date | null`, `number`, or `string` from this input type. Prefer whichever // is consistent with the current type. modelValue = untracked(currentValue); if (modelValue === null || modelValue instanceof Date) { return {value: element.valueAsDate}; } else if (typeof modelValue === 'number') { return {value: element.valueAsNumber}; } break; } // For text-like <input> elements, parse numeric values if the model is numeric. if (element.tagName === 'INPUT' && element.type === 'text') { modelValue ??= untracked(currentValue); if (typeof modelValue === 'number' || modelValue === null) { if (element.value === '') { return {value: null}; } const parsed = Number(element.value); if (Number.isNaN(parsed)) { return {error: new NativeInputParseError() as WithoutFieldTree<NativeInputParseError>}; } return {value: parsed}; } } // Default to reading the value as a string. return {value: element.value}; } /** * Sets a native control element's value. * * @param element The native control element. * @param value The new value to set. */ export function setNativeControlValue(element: NativeFormControl, value: unknown) { // Special cases for specific input types. switch (element.type) { case 'checkbox': element.checked = value as boolean; return; case 'radio': // Although HTML behavior is to clear the input already, we do this just in case. It seems // like it might be necessary in certain environments (e.g. Domino). element.checked = value === element.value; return; case 'number': case 'range': case 'datetime-local': // This input type can receive a `number` or a `string`. if (typeof value === 'number') { setNativeNumberControlValue(element, value); return; } else if (value === null) { element.value = ''; return; } break; case 'date': case 'month': case 'time': case 'week': // This input type can receive a `Date | null` or a `number` or a `string`. if (value === null || value instanceof Date) { element.valueAsDate = value; return; } else if (typeof value === 'number') { setNativeNumberControlValue(element, value); return; } } // For text-like <input> elements, handle numeric and null values. if (element.tagName === 'INPUT' && element.type === 'text') { if (typeof value === 'number') { element.value = isNaN(value) ? '' : String(value); return; } if (value === null) { if (typeof ngDevMode !== 'undefined' && ngDevMode) { console.warn( formatRuntimeError( RuntimeErrorCode.TEXT_INPUT_NULL_VALUE, `The text input ${element.name} received a null value. Text inputs should use empty strings to represent null values. ` + ` The input's value will be set to an empty string instead.`, ), ); } element.value = ''; return; } } // Default to setting the value as a string. element.value = value as string; } /** Writes a value to a native <input type="number">. */ export function setNativeNumberControlValue(element: HTMLInputElement, value: number) { // Writing `NaN` causes a warning in the console, so we instead write `''`. // This allows the user to safely use `NaN` as a number value that means "clear the input". if (isNaN(value)) { element.value = ''; } else { element.valueAsNumber = value; } } export function isInput(element: HTMLElement): element is HTMLInputElement { return element.tagName === 'INPUT'; } export function inputRequiresValidityTracking(input: HTMLInputElement): boolean { return ( input.type === 'date' || input.type === 'datetime-local' || input.type === 'month' || input.type === 'time' || input.type === 'week' ); } function formatDateForInput(date: Date, type: 'date' | 'month'): string { const year = date.getUTCFullYear(); const month = String(date.getUTCMonth() + 1).padStart(2, '0'); if (type === 'month') { return `${year}-${month}`; } const day = String(date.getUTCDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } export function formatDateForMinMax(name: string, value: unknown, type: string): unknown { if ( value instanceof Date && (name === 'min' || name === 'max') && (type === 'date' || type === 'month') ) { return formatDateForInput(value, type); } return value; }