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.
296 lines
10 KiB
296 lines
10 KiB
10 years ago
|
/**
|
||
|
* 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;
|