1+ /**
2+ * Copyright (c) 2013-present, Facebook, Inc.
3+ *
4+ * This source code is licensed under the MIT license found in the
5+ * LICENSE file in the root directory of this source tree.
6+ */
7+ 8+ import invariant from 'fbjs/lib/invariant' ;
9+ 10+ 11+ // These attributes should be all lowercase to allow for
12+ // case insensitive checks
13+ var RESERVED_PROPS = {
14+ children : true ,
15+ dangerouslySetInnerHTML : true ,
16+ defaultValue : true ,
17+ defaultChecked : true ,
18+ innerHTML : true ,
19+ suppressContentEditableWarning : true ,
20+ suppressHydrationWarning : true ,
21+ style : true
22+ } ;
23+ 24+ function checkMask ( value , bitmask ) {
25+ return ( value & bitmask ) === bitmask ;
26+ }
27+ 28+ var DOMPropertyInjection = {
29+ /**
30+ * Mapping from normalized, camelcased property names to a configuration that
31+ * specifies how the associated DOM property should be accessed or rendered.
32+ */
33+ MUST_USE_PROPERTY : 0x1 ,
34+ HAS_BOOLEAN_VALUE : 0x4 ,
35+ HAS_NUMERIC_VALUE : 0x8 ,
36+ HAS_POSITIVE_NUMERIC_VALUE : 0x10 | 0x8 ,
37+ HAS_OVERLOADED_BOOLEAN_VALUE : 0x20 ,
38+ HAS_STRING_BOOLEAN_VALUE : 0x40 ,
39+ 40+ /**
41+ * Inject some specialized knowledge about the DOM. This takes a config object
42+ * with the following properties:
43+ *
44+ * Properties: object mapping DOM property name to one of the
45+ * DOMPropertyInjection constants or null. If your attribute isn't in here,
46+ * it won't get written to the DOM.
47+ *
48+ * DOMAttributeNames: object mapping React attribute name to the DOM
49+ * attribute name. Attribute names not specified use the **lowercase**
50+ * normalized name.
51+ *
52+ * DOMAttributeNamespaces: object mapping React attribute name to the DOM
53+ * attribute namespace URL. (Attribute names not specified use no namespace.)
54+ *
55+ * DOMPropertyNames: similar to DOMAttributeNames but for DOM properties.
56+ * Property names not specified use the normalized name.
57+ *
58+ * DOMMutationMethods: Properties that require special mutation methods. If
59+ * `value` is undefined, the mutation method should unset the property.
60+ *
61+ * @param {object } domPropertyConfig the config as described above.
62+ */
63+ injectDOMPropertyConfig : function ( domPropertyConfig ) {
64+ var Injection = DOMPropertyInjection ;
65+ var Properties = domPropertyConfig . Properties || { } ;
66+ var DOMAttributeNamespaces =
67+ domPropertyConfig . DOMAttributeNamespaces || { } ;
68+ var DOMAttributeNames = domPropertyConfig . DOMAttributeNames || { } ;
69+ var DOMMutationMethods = domPropertyConfig . DOMMutationMethods || { } ;
70+ 71+ for ( var propName in Properties ) {
72+ ! ! properties . hasOwnProperty ( propName )
73+ ? invariant (
74+ false ,
75+ "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." ,
76+ propName
77+ )
78+ : void 0 ;
79+ 80+ var lowerCased = propName . toLowerCase ( ) ;
81+ var propConfig = Properties [ propName ] ;
82+ 83+ var propertyInfo = {
84+ attributeName : lowerCased ,
85+ attributeNamespace : null ,
86+ propertyName : propName ,
87+ mutationMethod : null ,
88+ 89+ mustUseProperty : checkMask ( propConfig , Injection . MUST_USE_PROPERTY ) ,
90+ hasBooleanValue : checkMask ( propConfig , Injection . HAS_BOOLEAN_VALUE ) ,
91+ hasNumericValue : checkMask ( propConfig , Injection . HAS_NUMERIC_VALUE ) ,
92+ hasPositiveNumericValue : checkMask (
93+ propConfig ,
94+ Injection . HAS_POSITIVE_NUMERIC_VALUE
95+ ) ,
96+ hasOverloadedBooleanValue : checkMask (
97+ propConfig ,
98+ Injection . HAS_OVERLOADED_BOOLEAN_VALUE
99+ ) ,
100+ hasStringBooleanValue : checkMask (
101+ propConfig ,
102+ Injection . HAS_STRING_BOOLEAN_VALUE
103+ )
104+ } ;
105+ ! (
106+ propertyInfo . hasBooleanValue +
107+ propertyInfo . hasNumericValue +
108+ propertyInfo . hasOverloadedBooleanValue <=
109+ 1
110+ )
111+ ? invariant (
112+ false ,
113+ "DOMProperty: Value can be one of boolean, overloaded boolean, or numeric value, but not a combination: %s" ,
114+ propName
115+ )
116+ : void 0 ;
117+ 118+ if ( DOMAttributeNames . hasOwnProperty ( propName ) ) {
119+ var attributeName = DOMAttributeNames [ propName ] ;
120+ 121+ propertyInfo . attributeName = attributeName ;
122+ }
123+ 124+ if ( DOMAttributeNamespaces . hasOwnProperty ( propName ) ) {
125+ propertyInfo . attributeNamespace = DOMAttributeNamespaces [ propName ] ;
126+ }
127+ 128+ if ( DOMMutationMethods . hasOwnProperty ( propName ) ) {
129+ propertyInfo . mutationMethod = DOMMutationMethods [ propName ] ;
130+ }
131+ 132+ // Downcase references to whitelist properties to check for membership
133+ // without case-sensitivity. This allows the whitelist to pick up
134+ // `allowfullscreen`, which should be written using the property configuration
135+ // for `allowFullscreen`
136+ properties [ propName ] = propertyInfo ;
137+ }
138+ }
139+ } ;
140+ 141+ /* eslint-disable max-len */
142+ var ATTRIBUTE_NAME_START_CHAR =
143+ ":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD" ;
144+ /* eslint-enable max-len */
145+ var ATTRIBUTE_NAME_CHAR =
146+ ATTRIBUTE_NAME_START_CHAR +
147+ "\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040" ;
148+ 149+ var ROOT_ATTRIBUTE_NAME = "data-reactroot" ;
150+ 151+ /**
152+ * Map from property "standard name" to an object with info about how to set
153+ * the property in the DOM. Each object contains:
154+ *
155+ * attributeName:
156+ * Used when rendering markup or with `*Attribute()`.
157+ * attributeNamespace
158+ * propertyName:
159+ * Used on DOM node instances. (This includes properties that mutate due to
160+ * external factors.)
161+ * mutationMethod:
162+ * If non-null, used instead of the property or `setAttribute()` after
163+ * initial render.
164+ * mustUseProperty:
165+ * Whether the property must be accessed and mutated as an object property.
166+ * hasBooleanValue:
167+ * Whether the property should be removed when set to a falsey value.
168+ * hasNumericValue:
169+ * Whether the property must be numeric or parse as a numeric and should be
170+ * removed when set to a falsey value.
171+ * hasPositiveNumericValue:
172+ * Whether the property must be positive numeric or parse as a positive
173+ * numeric and should be removed when set to a falsey value.
174+ * hasOverloadedBooleanValue:
175+ * Whether the property can be used as a flag as well as with a value.
176+ * Removed when strictly equal to false; present without a value when
177+ * strictly equal to true; present with a value otherwise.
178+ */
179+ 180+ var properties = { } ;
181+ 182+ /**
183+ * Checks whether a property name is a writeable attribute.
184+ * @method
185+ */
186+ function shouldSetAttribute ( name , value ) {
187+ if ( isReservedProp ( name ) ) {
188+ return false ;
189+ }
190+ if (
191+ name . length > 2 &&
192+ ( name [ 0 ] === "o" || name [ 0 ] === "O" ) &&
193+ ( name [ 1 ] === "n" || name [ 1 ] === "N" )
194+ ) {
195+ return false ;
196+ }
197+ if ( value === null ) {
198+ return true ;
199+ }
200+ switch ( typeof value ) {
201+ case "boolean" :
202+ return shouldAttributeAcceptBooleanValue ( name ) ;
203+ case "undefined" :
204+ case "number" :
205+ case "string" :
206+ case "object" :
207+ return true ;
208+ default :
209+ // function, symbol
210+ return false ;
211+ }
212+ }
213+ 214+ function getPropertyInfo ( name ) {
215+ return properties . hasOwnProperty ( name ) ? properties [ name ] : null ;
216+ }
217+ 218+ function shouldAttributeAcceptBooleanValue ( name ) {
219+ if ( isReservedProp ( name ) ) {
220+ return true ;
221+ }
222+ var propertyInfo = getPropertyInfo ( name ) ;
223+ if ( propertyInfo ) {
224+ return (
225+ propertyInfo . hasBooleanValue ||
226+ propertyInfo . hasStringBooleanValue ||
227+ propertyInfo . hasOverloadedBooleanValue
228+ ) ;
229+ }
230+ var prefix = name . toLowerCase ( ) . slice ( 0 , 5 ) ;
231+ return prefix === "data-" || prefix === "aria-" ;
232+ }
233+ 234+ /**
235+ * Checks to see if a property name is within the list of properties
236+ * reserved for internal React operations. These properties should
237+ * not be set on an HTML element.
238+ *
239+ * @private
240+ * @param {string } name
241+ * @return {boolean } If the name is within reserved props
242+ */
243+ function isReservedProp ( name ) {
244+ return RESERVED_PROPS . hasOwnProperty ( name ) ;
245+ }
246+ 247+ var injection = DOMPropertyInjection ;
248+ 249+ import quoteAttributeValueForBrowser from './quoteAttributeValueForBrowser' ;
250+ import warning from 'fbjs/lib/warning' ;
251+ 252+ // isAttributeNameSafe() is currently duplicated in DOMPropertyOperations.
253+ // TODO: Find a better place for this.
254+ var VALID_ATTRIBUTE_NAME_REGEX = new RegExp (
255+ "^[" + ATTRIBUTE_NAME_START_CHAR + "][" + ATTRIBUTE_NAME_CHAR + "]*$"
256+ ) ;
257+ var illegalAttributeNameCache = { } ;
258+ var validatedAttributeNameCache = { } ;
259+ function isAttributeNameSafe ( attributeName ) {
260+ if ( validatedAttributeNameCache . hasOwnProperty ( attributeName ) ) {
261+ return true ;
262+ }
263+ if ( illegalAttributeNameCache . hasOwnProperty ( attributeName ) ) {
264+ return false ;
265+ }
266+ if ( VALID_ATTRIBUTE_NAME_REGEX . test ( attributeName ) ) {
267+ validatedAttributeNameCache [ attributeName ] = true ;
268+ return true ;
269+ }
270+ illegalAttributeNameCache [ attributeName ] = true ;
271+ {
272+ warning ( false , "Invalid attribute name: `%s`" , attributeName ) ;
273+ }
274+ return false ;
275+ }
276+ 277+ // shouldIgnoreValue() is currently duplicated in DOMPropertyOperations.
278+ // TODO: Find a better place for this.
279+ function shouldIgnoreValue ( propertyInfo , value ) {
280+ return (
281+ value == null ||
282+ ( propertyInfo . hasBooleanValue && ! value ) ||
283+ ( propertyInfo . hasNumericValue && isNaN ( value ) ) ||
284+ ( propertyInfo . hasPositiveNumericValue && value < 1 ) ||
285+ ( propertyInfo . hasOverloadedBooleanValue && value === false )
286+ ) ;
287+ }
288+ 289+ /**
290+ * Operations for dealing with DOM properties.
291+ */
292+ 293+ /**
294+ * Creates markup for the ID property.
295+ *
296+ * @param {string } id Unescaped ID.
297+ * @return {string } Markup string.
298+ */
299+ 300+ export function createMarkupForRoot ( ) {
301+ return ROOT_ATTRIBUTE_NAME + '=""' ;
302+ }
303+ 304+ /**
305+ * Creates markup for a property.
306+ *
307+ * @param {string } name
308+ * @param {* } value
309+ * @return {?string } Markup string, or null if the property was invalid.
310+ */
311+ export function createMarkupForProperty ( name , value ) {
312+ var propertyInfo = getPropertyInfo ( name ) ;
313+ if ( propertyInfo ) {
314+ if ( shouldIgnoreValue ( propertyInfo , value ) ) {
315+ return "" ;
316+ }
317+ var attributeName = propertyInfo . attributeName ;
318+ if (
319+ propertyInfo . hasBooleanValue ||
320+ ( propertyInfo . hasOverloadedBooleanValue && value === true )
321+ ) {
322+ return attributeName + '=""' ;
323+ } else if (
324+ typeof value !== "boolean" ||
325+ shouldAttributeAcceptBooleanValue ( name )
326+ ) {
327+ return attributeName + "=" + quoteAttributeValueForBrowser ( value ) ;
328+ }
329+ } else if ( shouldSetAttribute ( name , value ) ) {
330+ if ( value == null ) {
331+ return "" ;
332+ }
333+ return name + "=" + quoteAttributeValueForBrowser ( value ) ;
334+ }
335+ return null ;
336+ }
337+ 338+ /**
339+ * Creates markup for a custom property.
340+ *
341+ * @param {string } name
342+ * @param {* } value
343+ * @return {string } Markup string, or empty string if the property was invalid.
344+ */
345+ export function createMarkupForCustomAttribute ( name , value ) {
346+ if ( ! isAttributeNameSafe ( name ) || value == null ) {
347+ return "" ;
348+ }
349+ return name + "=" + quoteAttributeValueForBrowser ( value ) ;
350+ }
0 commit comments