-
Notifications
You must be signed in to change notification settings - Fork 47k
/
inputValueTracking.js
141 lines (124 loc) · 3.32 KB
/
inputValueTracking.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
type ValueTracker = {
getValue(): string,
setValue(value: string): void,
stopTracking(): void,
};
type WrapperState = {_valueTracker?: ?ValueTracker};
type ElementWithValueTracker = HTMLInputElement & WrapperState;
function isCheckable(elem: HTMLInputElement) {
const type = elem.type;
const nodeName = elem.nodeName;
return (
nodeName &&
nodeName.toLowerCase() === 'input' &&
(type === 'checkbox' || type === 'radio')
);
}
function getTracker(node: ElementWithValueTracker) {
return node._valueTracker;
}
function detachTracker(node: ElementWithValueTracker) {
node._valueTracker = null;
}
function getValueFromNode(node: HTMLInputElement): string {
let value = '';
if (!node) {
return value;
}
if (isCheckable(node)) {
value = node.checked ? 'true' : 'false';
} else {
value = node.value;
}
return value;
}
function trackValueOnNode(node: any): ?ValueTracker {
const valueField = isCheckable(node) ? 'checked' : 'value';
const descriptor = Object.getOwnPropertyDescriptor(
node.constructor.prototype,
valueField,
);
let currentValue = '' + node[valueField];
// if someone has already defined a value or Safari, then bail
// and don't track value will cause over reporting of changes,
// but it's better then a hard failure
// (needed for certain tests that spyOn input values and Safari)
if (
node.hasOwnProperty(valueField) ||
typeof descriptor === 'undefined' ||
typeof descriptor.get !== 'function' ||
typeof descriptor.set !== 'function'
) {
return;
}
const {get, set} = descriptor;
Object.defineProperty(node, valueField, {
configurable: true,
get: function() {
return get.call(this);
},
set: function(value) {
currentValue = '' + value;
set.call(this, value);
},
});
// We could've passed this the first time
// but it triggers a bug in IE11 and Edge 14/15.
// Calling defineProperty() again should be equivalent.
// https://github.com/facebook/react/issues/11768
Object.defineProperty(node, valueField, {
enumerable: descriptor.enumerable,
});
const tracker = {
getValue() {
return currentValue;
},
setValue(value) {
currentValue = '' + value;
},
stopTracking() {
detachTracker(node);
delete node[valueField];
},
};
return tracker;
}
export function track(node: ElementWithValueTracker) {
if (getTracker(node)) {
return;
}
// TODO: Once it's just Fiber we can move this to node._wrapperState
node._valueTracker = trackValueOnNode(node);
}
export function updateValueIfChanged(node: ElementWithValueTracker) {
if (!node) {
return false;
}
const tracker = getTracker(node);
// if there is no tracker at this point it's unlikely
// that trying again will succeed
if (!tracker) {
return true;
}
const lastValue = tracker.getValue();
const nextValue = getValueFromNode(node);
if (nextValue !== lastValue) {
tracker.setValue(nextValue);
return true;
}
return false;
}
export function stopTracking(node: ElementWithValueTracker) {
const tracker = getTracker(node);
if (tracker) {
tracker.stopTracking();
}
}