You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
295 lines
10 KiB
295 lines
10 KiB
/** |
|
* Copyright 2013-2015, Facebook, Inc. |
|
* All rights reserved. |
|
* |
|
* This source code is licensed under the BSD-style license found in the |
|
* LICENSE file in the root directory of this source tree. An additional grant |
|
* of patent rights can be found in the PATENTS file in the same directory. |
|
* |
|
* @providesModule DOMProperty |
|
* @typechecks static-only |
|
*/ |
|
|
|
/*jslint bitwise: true */ |
|
|
|
'use strict'; |
|
|
|
var invariant = require("./invariant"); |
|
|
|
function checkMask(value, bitmask) { |
|
return (value & bitmask) === bitmask; |
|
} |
|
|
|
var DOMPropertyInjection = { |
|
/** |
|
* Mapping from normalized, camelcased property names to a configuration that |
|
* specifies how the associated DOM property should be accessed or rendered. |
|
*/ |
|
MUST_USE_ATTRIBUTE: 0x1, |
|
MUST_USE_PROPERTY: 0x2, |
|
HAS_SIDE_EFFECTS: 0x4, |
|
HAS_BOOLEAN_VALUE: 0x8, |
|
HAS_NUMERIC_VALUE: 0x10, |
|
HAS_POSITIVE_NUMERIC_VALUE: 0x20 | 0x10, |
|
HAS_OVERLOADED_BOOLEAN_VALUE: 0x40, |
|
|
|
/** |
|
* Inject some specialized knowledge about the DOM. This takes a config object |
|
* with the following properties: |
|
* |
|
* isCustomAttribute: function that given an attribute name will return true |
|
* if it can be inserted into the DOM verbatim. Useful for data-* or aria-* |
|
* attributes where it's impossible to enumerate all of the possible |
|
* attribute names, |
|
* |
|
* Properties: object mapping DOM property name to one of the |
|
* DOMPropertyInjection constants or null. If your attribute isn't in here, |
|
* it won't get written to the DOM. |
|
* |
|
* DOMAttributeNames: object mapping React attribute name to the DOM |
|
* attribute name. Attribute names not specified use the **lowercase** |
|
* normalized name. |
|
* |
|
* DOMPropertyNames: similar to DOMAttributeNames but for DOM properties. |
|
* Property names not specified use the normalized name. |
|
* |
|
* DOMMutationMethods: Properties that require special mutation methods. If |
|
* `value` is undefined, the mutation method should unset the property. |
|
* |
|
* @param {object} domPropertyConfig the config as described above. |
|
*/ |
|
injectDOMPropertyConfig: function(domPropertyConfig) { |
|
var Properties = domPropertyConfig.Properties || {}; |
|
var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {}; |
|
var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {}; |
|
var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {}; |
|
|
|
if (domPropertyConfig.isCustomAttribute) { |
|
DOMProperty._isCustomAttributeFunctions.push( |
|
domPropertyConfig.isCustomAttribute |
|
); |
|
} |
|
|
|
for (var propName in Properties) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
!DOMProperty.isStandardName.hasOwnProperty(propName), |
|
'injectDOMPropertyConfig(...): You\'re trying to inject DOM property ' + |
|
'\'%s\' which has already been injected. You may be accidentally ' + |
|
'injecting the same DOM property config twice, or you may be ' + |
|
'injecting two configs that have conflicting property names.', |
|
propName |
|
) : invariant(!DOMProperty.isStandardName.hasOwnProperty(propName))); |
|
|
|
DOMProperty.isStandardName[propName] = true; |
|
|
|
var lowerCased = propName.toLowerCase(); |
|
DOMProperty.getPossibleStandardName[lowerCased] = propName; |
|
|
|
if (DOMAttributeNames.hasOwnProperty(propName)) { |
|
var attributeName = DOMAttributeNames[propName]; |
|
DOMProperty.getPossibleStandardName[attributeName] = propName; |
|
DOMProperty.getAttributeName[propName] = attributeName; |
|
} else { |
|
DOMProperty.getAttributeName[propName] = lowerCased; |
|
} |
|
|
|
DOMProperty.getPropertyName[propName] = |
|
DOMPropertyNames.hasOwnProperty(propName) ? |
|
DOMPropertyNames[propName] : |
|
propName; |
|
|
|
if (DOMMutationMethods.hasOwnProperty(propName)) { |
|
DOMProperty.getMutationMethod[propName] = DOMMutationMethods[propName]; |
|
} else { |
|
DOMProperty.getMutationMethod[propName] = null; |
|
} |
|
|
|
var propConfig = Properties[propName]; |
|
DOMProperty.mustUseAttribute[propName] = |
|
checkMask(propConfig, DOMPropertyInjection.MUST_USE_ATTRIBUTE); |
|
DOMProperty.mustUseProperty[propName] = |
|
checkMask(propConfig, DOMPropertyInjection.MUST_USE_PROPERTY); |
|
DOMProperty.hasSideEffects[propName] = |
|
checkMask(propConfig, DOMPropertyInjection.HAS_SIDE_EFFECTS); |
|
DOMProperty.hasBooleanValue[propName] = |
|
checkMask(propConfig, DOMPropertyInjection.HAS_BOOLEAN_VALUE); |
|
DOMProperty.hasNumericValue[propName] = |
|
checkMask(propConfig, DOMPropertyInjection.HAS_NUMERIC_VALUE); |
|
DOMProperty.hasPositiveNumericValue[propName] = |
|
checkMask(propConfig, DOMPropertyInjection.HAS_POSITIVE_NUMERIC_VALUE); |
|
DOMProperty.hasOverloadedBooleanValue[propName] = |
|
checkMask(propConfig, DOMPropertyInjection.HAS_OVERLOADED_BOOLEAN_VALUE); |
|
|
|
("production" !== process.env.NODE_ENV ? invariant( |
|
!DOMProperty.mustUseAttribute[propName] || |
|
!DOMProperty.mustUseProperty[propName], |
|
'DOMProperty: Cannot require using both attribute and property: %s', |
|
propName |
|
) : invariant(!DOMProperty.mustUseAttribute[propName] || |
|
!DOMProperty.mustUseProperty[propName])); |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
DOMProperty.mustUseProperty[propName] || |
|
!DOMProperty.hasSideEffects[propName], |
|
'DOMProperty: Properties that have side effects must use property: %s', |
|
propName |
|
) : invariant(DOMProperty.mustUseProperty[propName] || |
|
!DOMProperty.hasSideEffects[propName])); |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
!!DOMProperty.hasBooleanValue[propName] + |
|
!!DOMProperty.hasNumericValue[propName] + |
|
!!DOMProperty.hasOverloadedBooleanValue[propName] <= 1, |
|
'DOMProperty: Value can be one of boolean, overloaded boolean, or ' + |
|
'numeric value, but not a combination: %s', |
|
propName |
|
) : invariant(!!DOMProperty.hasBooleanValue[propName] + |
|
!!DOMProperty.hasNumericValue[propName] + |
|
!!DOMProperty.hasOverloadedBooleanValue[propName] <= 1)); |
|
} |
|
} |
|
}; |
|
var defaultValueCache = {}; |
|
|
|
/** |
|
* DOMProperty exports lookup objects that can be used like functions: |
|
* |
|
* > DOMProperty.isValid['id'] |
|
* true |
|
* > DOMProperty.isValid['foobar'] |
|
* undefined |
|
* |
|
* Although this may be confusing, it performs better in general. |
|
* |
|
* @see http://jsperf.com/key-exists |
|
* @see http://jsperf.com/key-missing |
|
*/ |
|
var DOMProperty = { |
|
|
|
ID_ATTRIBUTE_NAME: 'data-reactid', |
|
|
|
/** |
|
* Checks whether a property name is a standard property. |
|
* @type {Object} |
|
*/ |
|
isStandardName: {}, |
|
|
|
/** |
|
* Mapping from lowercase property names to the properly cased version, used |
|
* to warn in the case of missing properties. |
|
* @type {Object} |
|
*/ |
|
getPossibleStandardName: {}, |
|
|
|
/** |
|
* Mapping from normalized names to attribute names that differ. Attribute |
|
* names are used when rendering markup or with `*Attribute()`. |
|
* @type {Object} |
|
*/ |
|
getAttributeName: {}, |
|
|
|
/** |
|
* Mapping from normalized names to properties on DOM node instances. |
|
* (This includes properties that mutate due to external factors.) |
|
* @type {Object} |
|
*/ |
|
getPropertyName: {}, |
|
|
|
/** |
|
* Mapping from normalized names to mutation methods. This will only exist if |
|
* mutation cannot be set simply by the property or `setAttribute()`. |
|
* @type {Object} |
|
*/ |
|
getMutationMethod: {}, |
|
|
|
/** |
|
* Whether the property must be accessed and mutated as an object property. |
|
* @type {Object} |
|
*/ |
|
mustUseAttribute: {}, |
|
|
|
/** |
|
* Whether the property must be accessed and mutated using `*Attribute()`. |
|
* (This includes anything that fails `<propName> in <element>`.) |
|
* @type {Object} |
|
*/ |
|
mustUseProperty: {}, |
|
|
|
/** |
|
* Whether or not setting a value causes side effects such as triggering |
|
* resources to be loaded or text selection changes. We must ensure that |
|
* the value is only set if it has changed. |
|
* @type {Object} |
|
*/ |
|
hasSideEffects: {}, |
|
|
|
/** |
|
* Whether the property should be removed when set to a falsey value. |
|
* @type {Object} |
|
*/ |
|
hasBooleanValue: {}, |
|
|
|
/** |
|
* Whether the property must be numeric or parse as a |
|
* numeric and should be removed when set to a falsey value. |
|
* @type {Object} |
|
*/ |
|
hasNumericValue: {}, |
|
|
|
/** |
|
* Whether the property must be positive numeric or parse as a positive |
|
* numeric and should be removed when set to a falsey value. |
|
* @type {Object} |
|
*/ |
|
hasPositiveNumericValue: {}, |
|
|
|
/** |
|
* Whether the property can be used as a flag as well as with a value. Removed |
|
* when strictly equal to false; present without a value when strictly equal |
|
* to true; present with a value otherwise. |
|
* @type {Object} |
|
*/ |
|
hasOverloadedBooleanValue: {}, |
|
|
|
/** |
|
* All of the isCustomAttribute() functions that have been injected. |
|
*/ |
|
_isCustomAttributeFunctions: [], |
|
|
|
/** |
|
* Checks whether a property name is a custom attribute. |
|
* @method |
|
*/ |
|
isCustomAttribute: function(attributeName) { |
|
for (var i = 0; i < DOMProperty._isCustomAttributeFunctions.length; i++) { |
|
var isCustomAttributeFn = DOMProperty._isCustomAttributeFunctions[i]; |
|
if (isCustomAttributeFn(attributeName)) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
}, |
|
|
|
/** |
|
* Returns the default property value for a DOM property (i.e., not an |
|
* attribute). Most default values are '' or false, but not all. Worse yet, |
|
* some (in particular, `type`) vary depending on the type of element. |
|
* |
|
* TODO: Is it better to grab all the possible properties when creating an |
|
* element to avoid having to create the same element twice? |
|
*/ |
|
getDefaultValueForProperty: function(nodeName, prop) { |
|
var nodeDefaults = defaultValueCache[nodeName]; |
|
var testElement; |
|
if (!nodeDefaults) { |
|
defaultValueCache[nodeName] = nodeDefaults = {}; |
|
} |
|
if (!(prop in nodeDefaults)) { |
|
testElement = document.createElement(nodeName); |
|
nodeDefaults[prop] = testElement[prop]; |
|
} |
|
return nodeDefaults[prop]; |
|
}, |
|
|
|
injection: DOMPropertyInjection |
|
}; |
|
|
|
module.exports = DOMProperty;
|
|
|