diff --git a/src/browser/ui/dom/DOMProperty.js b/src/browser/ui/dom/DOMProperty.js index 85487cd799..8d3d6626d0 100644 --- a/src/browser/ui/dom/DOMProperty.js +++ b/src/browser/ui/dom/DOMProperty.js @@ -34,6 +34,7 @@ var DOMPropertyInjection = { 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 @@ -115,6 +116,8 @@ var DOMPropertyInjection = { propConfig & DOMPropertyInjection.HAS_NUMERIC_VALUE; DOMProperty.hasPositiveNumericValue[propName] = propConfig & DOMPropertyInjection.HAS_POSITIVE_NUMERIC_VALUE; + DOMProperty.hasOverloadedBooleanValue[propName] = + propConfig & DOMPropertyInjection.HAS_OVERLOADED_BOOLEAN_VALUE; invariant( !DOMProperty.mustUseAttribute[propName] || @@ -129,9 +132,11 @@ var DOMPropertyInjection = { propName ); invariant( - !DOMProperty.hasBooleanValue[propName] || - !DOMProperty.hasNumericValue[propName], - 'DOMProperty: Cannot have both boolean and numeric value: %s', + !!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 ); } @@ -231,6 +236,14 @@ var DOMProperty = { */ 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. */ diff --git a/src/browser/ui/dom/DOMPropertyOperations.js b/src/browser/ui/dom/DOMPropertyOperations.js index d7968e3ee1..12a3502bb9 100644 --- a/src/browser/ui/dom/DOMPropertyOperations.js +++ b/src/browser/ui/dom/DOMPropertyOperations.js @@ -29,7 +29,8 @@ function shouldIgnoreValue(name, value) { return value == null || (DOMProperty.hasBooleanValue[name] && !value) || (DOMProperty.hasNumericValue[name] && isNaN(value)) || - (DOMProperty.hasPositiveNumericValue[name] && (value < 1)); + (DOMProperty.hasPositiveNumericValue[name] && (value < 1)) || + (DOMProperty.hasOverloadedBooleanValue[name] && value === false); } var processAttributeNameAndPrefix = memoizeStringOnly(function(name) { @@ -96,7 +97,8 @@ var DOMPropertyOperations = { return ''; } var attributeName = DOMProperty.getAttributeName[name]; - if (DOMProperty.hasBooleanValue[name]) { + if (DOMProperty.hasBooleanValue[name] || + (DOMProperty.hasOverloadedBooleanValue[name] && value === true)) { return escapeTextForBrowser(attributeName); } return processAttributeNameAndPrefix(attributeName) + diff --git a/src/browser/ui/dom/DefaultDOMPropertyConfig.js b/src/browser/ui/dom/DefaultDOMPropertyConfig.js index f1dd5cc55b..1e04bc7a03 100644 --- a/src/browser/ui/dom/DefaultDOMPropertyConfig.js +++ b/src/browser/ui/dom/DefaultDOMPropertyConfig.js @@ -29,6 +29,8 @@ var HAS_SIDE_EFFECTS = DOMProperty.injection.HAS_SIDE_EFFECTS; var HAS_NUMERIC_VALUE = DOMProperty.injection.HAS_NUMERIC_VALUE; var HAS_POSITIVE_NUMERIC_VALUE = DOMProperty.injection.HAS_POSITIVE_NUMERIC_VALUE; +var HAS_OVERLOADED_BOOLEAN_VALUE = + DOMProperty.injection.HAS_OVERLOADED_BOOLEAN_VALUE; var DefaultDOMPropertyConfig = { isCustomAttribute: RegExp.prototype.test.bind( @@ -66,7 +68,7 @@ var DefaultDOMPropertyConfig = { defer: HAS_BOOLEAN_VALUE, dir: null, disabled: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE, - download: null, + download: HAS_OVERLOADED_BOOLEAN_VALUE, draggable: null, encType: null, form: MUST_USE_ATTRIBUTE, diff --git a/src/browser/ui/dom/__tests__/DOMPropertyOperations-test.js b/src/browser/ui/dom/__tests__/DOMPropertyOperations-test.js index 4a2131dfcb..2475d33fcb 100644 --- a/src/browser/ui/dom/__tests__/DOMPropertyOperations-test.js +++ b/src/browser/ui/dom/__tests__/DOMPropertyOperations-test.js @@ -99,6 +99,48 @@ describe('DOMPropertyOperations', function() { )).toBe(''); }); + it('should create markup for booleanish properties', function() { + expect(DOMPropertyOperations.createMarkupForProperty( + 'download', + 'simple' + )).toBe('download="simple"'); + + expect(DOMPropertyOperations.createMarkupForProperty( + 'download', + true + )).toBe('download'); + + expect(DOMPropertyOperations.createMarkupForProperty( + 'download', + 'true' + )).toBe('download="true"'); + + expect(DOMPropertyOperations.createMarkupForProperty( + 'download', + false + )).toBe(''); + + expect(DOMPropertyOperations.createMarkupForProperty( + 'download', + 'false' + )).toBe('download="false"'); + + expect(DOMPropertyOperations.createMarkupForProperty( + 'download', + undefined + )).toBe(''); + + expect(DOMPropertyOperations.createMarkupForProperty( + 'download', + null + )).toBe(''); + + expect(DOMPropertyOperations.createMarkupForProperty( + 'download', + 0 + )).toBe('download="0"'); + }); + it('should create markup for custom attributes', function() { expect(DOMPropertyOperations.createMarkupForProperty( 'aria-label',