From 03b8e1eaa976026d9cd63d2bec7b8eb69b701525 Mon Sep 17 00:00:00 2001 From: Tyler Gaw Date: Mon, 9 Oct 2023 17:21:55 -0400 Subject: [PATCH] Implements StylePropertyMap append --- demo/css-typed-om-polyfill.js | 2 +- lib/StylePropertyMap.js | 32 ++++++++++++++++++++++++++++-- lib/StylePropertyMap.test.js | 37 ++++++++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/demo/css-typed-om-polyfill.js b/demo/css-typed-om-polyfill.js index 4e5a241..735b010 100644 --- a/demo/css-typed-om-polyfill.js +++ b/demo/css-typed-om-polyfill.js @@ -1 +1 @@ -const _value$2=new WeakMap;class CSSKeywordValue{get value(){return _value$2.get(this)}set value(newValue){_value$2.set(this,String(newValue))}toString(){return`${this.value}`}constructor(...args){if(args.length<1)throw new TypeError(`Failed to construct 'CSSKeywordValue': 1 arguments required, but only ${args.length} present.`);_value$2.set(this,String(args[0]))}}Object.defineProperties(CSSKeywordValue.prototype,{value:{enumerable:!0}});const _value$1=new WeakMap;class CSSMathInvert{get operator(){return"invert"}get value(){return _value$1.get(this)}toString(){return`calc(1 / ${_value$1.get(this)})`}constructor(value){_value$1.set(this,value)}}const _values$3=new WeakMap;class CSSMathMax{get operator(){return"max"}get values(){return _values$3.get(this)}toString(){return`max(${_values$3.get(this).join(", ")})`}constructor(...values){_values$3.set(this,values)}}const _values$2=new WeakMap;class CSSMathMin{get operator(){return"min"}get values(){return _values$2.get(this)}toString(){return`min(${_values$2.get(this).join(", ")})`}constructor(...values){_values$2.set(this,values)}}const _values$1=new WeakMap;class CSSMathProduct{get operator(){return"product"}get values(){return _values$1.get(this)}toString(){return`calc(${_values$1.get(this).reduce(((contents,value)=>""+(value instanceof CSSMathInvert?`${contents?`${contents} / `:"1 / "}${value.value}`:`${contents?`${contents} * `:""}${value}`)),"")})`}constructor(...values){_values$1.set(this,values)}}const _values=new WeakMap;class CSSMathSum{get operator(){return"product"}get values(){return _values.get(this)}toString(){return`calc(${_values.get(this).reduce(((contents,value)=>`${contents?`${contents} + `:""}${value}`),"")})`}constructor(...values){_values.set(this,values)}}var units={number:"",percent:"%",em:"em",ex:"ex",ch:"ch",rem:"rem",vw:"vw",vh:"vh",vmin:"vmin",vmax:"vmax",cm:"cm",mm:"mm",in:"in",pt:"pt",pc:"pc",px:"px",Q:"Q",deg:"deg",grad:"grad",rad:"rad",turn:"turn",s:"s",ms:"ms",Hz:"Hz",kHz:"kHz",dpi:"dpi",dpcm:"dpcm",dppx:"dppx",fr:"fr"};class CSSNumericValue{add(...args){const Constructor=this.constructor,result=new Constructor(this.value,this.unit),values=[];for(let arg of args)if(arg instanceof Constructor)values.length||result.unit!==arg.unit?values.push(arg):result.value+=arg.value;else{if(!(arg instanceof CSSMathProduct||arg instanceof CSSMathMax||arg instanceof CSSMathMin||arg instanceof CSSMathInvert))return null;values.push(arg)}return values.length?new CSSMathSum(result,...values):result}div(...args){const Constructor=this.constructor,result=new Constructor(this.value,this.unit),values=[];for(let arg of args){if("number"==typeof arg&&(arg=new CSSUnitValue(arg,"number")),!(arg instanceof Constructor))return null;values.length||result.unit!==arg.unit&&"number"!==arg.unit?values.push(arg):result.value/=arg.value}return values.length?new CSSMathProduct(result,...values.map((value=>new CSSMathInvert(value)))):result}max(...args){const result=new CSSUnitValue(this.value,this.unit),values=[result];for(let arg of args){if(!(arg instanceof CSSUnitValue))return null;values.length>1||result.unit!==arg.unit?values.push(arg):result.value=Math.max(result.value,arg.value)}return values.length>1?new CSSMathMax(...values):result}min(...args){const result=new CSSUnitValue(this.value,this.unit),values=[result];for(let arg of args){if(!(arg instanceof CSSUnitValue))return null;values.length>1||result.unit!==arg.unit?values.push(arg):result.value=Math.min(result.value,arg.value)}return values.length>1?new CSSMathMin(...values):result}mul(...args){const Constructor=this.constructor,result=new Constructor(this.value,this.unit),values=[];for(let arg of args){if("number"==typeof arg&&(arg=new CSSUnitValue(arg,"number")),!(arg instanceof Constructor))return null;values.length||result.unit!==arg.unit&&"number"!==arg.unit?values.push(arg):result.value*=arg.value}return values.length?new CSSMathProduct(result,...values):result}sub(...args){const Constructor=this.constructor,result=new Constructor(this.value,this.unit),values=[];for(let arg of args){if(!(arg instanceof Constructor))return null;values.length||result.unit!==arg.unit?values.push(new Constructor(-1*arg.value,arg.unit)):result.value-=arg.value}return values.length?new CSSMathSum(result,...values):result}}const _value=new WeakMap,_unit=new WeakMap;class CSSUnitValue extends CSSNumericValue{get value(){return _value.get(this)}set value(newValue){_value.set(this,getFiniteNumber(newValue))}get unit(){return _unit.get(this)}toString(){return`${this.value}${units[this.unit]}`}constructor(...args){if(super(),args.length<2)throw new TypeError(`Failed to construct 'CSSUnitValue': 2 arguments required, but only ${args.length} present.`);_value.set(this,getFiniteNumber(args[0])),_unit.set(this,function(unit){if(!Object.keys(units).includes(unit))throw new TypeError(`Failed to construct 'CSSUnitValue': Invalid unit: ${unit}`);return unit}(args[1]))}}function getFiniteNumber(value){if(isNaN(value)||Math.abs(value)===1/0)throw new TypeError("Failed to set the 'value' property on 'CSSUnitValue': The provided double value is non-finite.");return Number(value)}Object.defineProperties(CSSUnitValue.prototype,{value:{enumerable:!0},unit:{enumerable:!0}});const unitKeys=Object.keys(units),unitValues=Object.values(units),unitParsingMatcher=new RegExp(`^([-+]?[0-9]*.?[0-9]+)(${unitValues.join("|")})?$`);class CSSStyleValue{constructor(){if("CSSStyleValue"===this.constructor.name)throw new TypeError("Illegal constructor")}static parse(property,cssText){return property.startsWith("--")?void 0:(property.toLowerCase(),(string=>{const unitParsingMatch=String(string).match(unitParsingMatcher);if(unitParsingMatch){const[,value,unit]=unitParsingMatch;return new CSSUnitValue(value,unitKeys[unitValues.indexOf(unit||"")])}return new CSSKeywordValue(string)})(cssText))}static parseAll(property,cssText){}}class CSSUnparsedValue extends CSSStyleValue{constructor(members=[]){if(super(!0),members.length<1)throw new TypeError(`Failed to construct 'CSSUnparsedValue': 1 argument required, but only ${members.length} present.`);this.members=members}*[Symbol.iterator](){yield*this.members}entries(){return this.members.entries()}forEach(callback,thisArg){this.members.forEach(callback,thisArg)}keys(){return this.members.keys()}values(){return this.members.values()}get(index){if(!(index<0||index>=this.length))return this.members[index]}set(index,val){if(index<0||index>=this.length)throw new RangeError(`Failed to set an indexed property on 'CSSUnparsedValue': The index provided (${index}) is outside the range [0, ${this.length-1}].`)}get length(){return this.members.length}}class StylePropertyMapReadOnly{constructor(){throw new TypeError("Illegal constructor")}get[Symbol.toStringTag](){return"StylePropertyMapReadOnly"}*[Symbol.iterator](){yield*this.entries()}get size(){return this.declarations.length}set size(_){return this.size}has(propertyName){let hasProp=!1;for(let i=0;i{unit in window.CSS||(window.CSS[unit]=value=>new CSSUnitValue(value,unit))})),"computedStyleMap"in window.Element.prototype||Object.defineProperty(window.Element.prototype,"computedStyleMap",{writable:!1,configurable:!1,value:function(){return function(declarations){const stylePropertyMapInstance=Object.create(StylePropertyMapReadOnly.prototype);return Object.defineProperty(stylePropertyMapInstance,"declarations",{value:declarations}),stylePropertyMapInstance}(window.getComputedStyle(this))}});const styleMapDescriptor={configurable:!1,enumerable:!0,get(){return function(declarations){const stylePropertyMap=Object.create(StylePropertyMap.prototype);return Object.defineProperty(stylePropertyMap,"declarations",{value:declarations}),stylePropertyMap}(this.style)}};"styleMap"in window.CSSStyleRule.prototype||Object.defineProperty(window.CSSStyleRule.prototype,"styleMap",styleMapDescriptor),"attributeStyleMap"in window.Element.prototype||Object.defineProperty(window.Element.prototype,"attributeStyleMap",styleMapDescriptor),window.CSSKeywordValue||(window.CSSKeywordValue=CSSKeywordValue),window.CSSMathInvert||(window.CSSMathInvert=CSSMathInvert),window.CSSMathMax||(window.CSSMathMax=CSSMathMax),window.CSSMathMin||(window.CSSMathMin=CSSMathMin),window.CSSMathProduct||(window.CSSMathProduct=CSSMathProduct),window.CSSMathSum||(window.CSSMathSum=CSSMathSum),window.CSSStyleValue||(window.CSSStyleValue=CSSStyleValue),window.CSSUnitValue||(window.CSSUnitValue=CSSUnitValue),window.CSSUnparsedValue||(window.CSSUnparsedValue=CSSUnparsedValue),window.StylePropertyMapReadOnly||(window.StylePropertyMapReadOnly=StylePropertyMapReadOnly),window.StylePropertyMap||(window.StylePropertyMap=StylePropertyMap)}export{CSSKeywordValue,CSSStyleValue,CSSUnitValue,StylePropertyMap,polyfill as default}; +const _value$2=new WeakMap;class CSSKeywordValue{get value(){return _value$2.get(this)}set value(newValue){_value$2.set(this,String(newValue))}toString(){return`${this.value}`}constructor(...args){if(args.length<1)throw new TypeError(`Failed to construct 'CSSKeywordValue': 1 arguments required, but only ${args.length} present.`);_value$2.set(this,String(args[0]))}}Object.defineProperties(CSSKeywordValue.prototype,{value:{enumerable:!0}});const _value$1=new WeakMap;class CSSMathInvert{get operator(){return"invert"}get value(){return _value$1.get(this)}toString(){return`calc(1 / ${_value$1.get(this)})`}constructor(value){_value$1.set(this,value)}}const _values$3=new WeakMap;class CSSMathMax{get operator(){return"max"}get values(){return _values$3.get(this)}toString(){return`max(${_values$3.get(this).join(", ")})`}constructor(...values){_values$3.set(this,values)}}const _values$2=new WeakMap;class CSSMathMin{get operator(){return"min"}get values(){return _values$2.get(this)}toString(){return`min(${_values$2.get(this).join(", ")})`}constructor(...values){_values$2.set(this,values)}}const _values$1=new WeakMap;class CSSMathProduct{get operator(){return"product"}get values(){return _values$1.get(this)}toString(){return`calc(${_values$1.get(this).reduce(((contents,value)=>""+(value instanceof CSSMathInvert?`${contents?`${contents} / `:"1 / "}${value.value}`:`${contents?`${contents} * `:""}${value}`)),"")})`}constructor(...values){_values$1.set(this,values)}}const _values=new WeakMap;class CSSMathSum{get operator(){return"product"}get values(){return _values.get(this)}toString(){return`calc(${_values.get(this).reduce(((contents,value)=>`${contents?`${contents} + `:""}${value}`),"")})`}constructor(...values){_values.set(this,values)}}var units={number:"",percent:"%",em:"em",ex:"ex",ch:"ch",rem:"rem",vw:"vw",vh:"vh",vmin:"vmin",vmax:"vmax",cm:"cm",mm:"mm",in:"in",pt:"pt",pc:"pc",px:"px",Q:"Q",deg:"deg",grad:"grad",rad:"rad",turn:"turn",s:"s",ms:"ms",Hz:"Hz",kHz:"kHz",dpi:"dpi",dpcm:"dpcm",dppx:"dppx",fr:"fr"};class CSSNumericValue{add(...args){const Constructor=this.constructor,result=new Constructor(this.value,this.unit),values=[];for(let arg of args)if(arg instanceof Constructor)values.length||result.unit!==arg.unit?values.push(arg):result.value+=arg.value;else{if(!(arg instanceof CSSMathProduct||arg instanceof CSSMathMax||arg instanceof CSSMathMin||arg instanceof CSSMathInvert))return null;values.push(arg)}return values.length?new CSSMathSum(result,...values):result}div(...args){const Constructor=this.constructor,result=new Constructor(this.value,this.unit),values=[];for(let arg of args){if("number"==typeof arg&&(arg=new CSSUnitValue(arg,"number")),!(arg instanceof Constructor))return null;values.length||result.unit!==arg.unit&&"number"!==arg.unit?values.push(arg):result.value/=arg.value}return values.length?new CSSMathProduct(result,...values.map((value=>new CSSMathInvert(value)))):result}max(...args){const result=new CSSUnitValue(this.value,this.unit),values=[result];for(let arg of args){if(!(arg instanceof CSSUnitValue))return null;values.length>1||result.unit!==arg.unit?values.push(arg):result.value=Math.max(result.value,arg.value)}return values.length>1?new CSSMathMax(...values):result}min(...args){const result=new CSSUnitValue(this.value,this.unit),values=[result];for(let arg of args){if(!(arg instanceof CSSUnitValue))return null;values.length>1||result.unit!==arg.unit?values.push(arg):result.value=Math.min(result.value,arg.value)}return values.length>1?new CSSMathMin(...values):result}mul(...args){const Constructor=this.constructor,result=new Constructor(this.value,this.unit),values=[];for(let arg of args){if("number"==typeof arg&&(arg=new CSSUnitValue(arg,"number")),!(arg instanceof Constructor))return null;values.length||result.unit!==arg.unit&&"number"!==arg.unit?values.push(arg):result.value*=arg.value}return values.length?new CSSMathProduct(result,...values):result}sub(...args){const Constructor=this.constructor,result=new Constructor(this.value,this.unit),values=[];for(let arg of args){if(!(arg instanceof Constructor))return null;values.length||result.unit!==arg.unit?values.push(new Constructor(-1*arg.value,arg.unit)):result.value-=arg.value}return values.length?new CSSMathSum(result,...values):result}}const _value=new WeakMap,_unit=new WeakMap;class CSSUnitValue extends CSSNumericValue{get value(){return _value.get(this)}set value(newValue){_value.set(this,getFiniteNumber(newValue))}get unit(){return _unit.get(this)}toString(){return`${this.value}${units[this.unit]}`}constructor(...args){if(super(),args.length<2)throw new TypeError(`Failed to construct 'CSSUnitValue': 2 arguments required, but only ${args.length} present.`);_value.set(this,getFiniteNumber(args[0])),_unit.set(this,function(unit){if(!Object.keys(units).includes(unit))throw new TypeError(`Failed to construct 'CSSUnitValue': Invalid unit: ${unit}`);return unit}(args[1]))}}function getFiniteNumber(value){if(isNaN(value)||Math.abs(value)===1/0)throw new TypeError("Failed to set the 'value' property on 'CSSUnitValue': The provided double value is non-finite.");return Number(value)}Object.defineProperties(CSSUnitValue.prototype,{value:{enumerable:!0},unit:{enumerable:!0}});const unitKeys=Object.keys(units),unitValues=Object.values(units),unitParsingMatcher=new RegExp(`^([-+]?[0-9]*.?[0-9]+)(${unitValues.join("|")})?$`);class CSSStyleValue{constructor(){if("CSSStyleValue"===this.constructor.name)throw new TypeError("Illegal constructor")}static parse(property,cssText){return property.startsWith("--")?void 0:(property.toLowerCase(),(string=>{const unitParsingMatch=String(string).match(unitParsingMatcher);if(unitParsingMatch){const[,value,unit]=unitParsingMatch;return new CSSUnitValue(value,unitKeys[unitValues.indexOf(unit||"")])}return new CSSKeywordValue(string)})(cssText))}static parseAll(property,cssText){}}class CSSUnparsedValue extends CSSStyleValue{constructor(members=[]){if(super(!0),members.length<1)throw new TypeError(`Failed to construct 'CSSUnparsedValue': 1 argument required, but only ${members.length} present.`);this.members=members}*[Symbol.iterator](){yield*this.members}entries(){return this.members.entries()}forEach(callback,thisArg){this.members.forEach(callback,thisArg)}keys(){return this.members.keys()}values(){return this.members.values()}get(index){if(!(index<0||index>=this.length))return this.members[index]}set(index,val){if(index<0||index>=this.length)throw new RangeError(`Failed to set an indexed property on 'CSSUnparsedValue': The index provided (${index}) is outside the range [0, ${this.length-1}].`)}get length(){return this.members.length}}class StylePropertyMapReadOnly{constructor(){throw new TypeError("Illegal constructor")}get[Symbol.toStringTag](){return"StylePropertyMapReadOnly"}*[Symbol.iterator](){yield*this.entries()}get size(){return this.declarations.length}set size(_){return this.size}has(propertyName){let hasProp=!1;for(let i=0;i{unit in window.CSS||(window.CSS[unit]=value=>new CSSUnitValue(value,unit))})),"computedStyleMap"in window.Element.prototype||Object.defineProperty(window.Element.prototype,"computedStyleMap",{writable:!1,configurable:!1,value:function(){return function(declarations){const stylePropertyMapInstance=Object.create(StylePropertyMapReadOnly.prototype);return Object.defineProperty(stylePropertyMapInstance,"declarations",{value:declarations}),stylePropertyMapInstance}(window.getComputedStyle(this))}});const styleMapDescriptor={configurable:!1,enumerable:!0,get(){return function(declarations){const stylePropertyMap=Object.create(StylePropertyMap.prototype);return Object.defineProperty(stylePropertyMap,"declarations",{value:declarations}),stylePropertyMap}(this.style)}};"styleMap"in window.CSSStyleRule.prototype||Object.defineProperty(window.CSSStyleRule.prototype,"styleMap",styleMapDescriptor),"attributeStyleMap"in window.Element.prototype||Object.defineProperty(window.Element.prototype,"attributeStyleMap",styleMapDescriptor),window.CSSKeywordValue||(window.CSSKeywordValue=CSSKeywordValue),window.CSSMathInvert||(window.CSSMathInvert=CSSMathInvert),window.CSSMathMax||(window.CSSMathMax=CSSMathMax),window.CSSMathMin||(window.CSSMathMin=CSSMathMin),window.CSSMathProduct||(window.CSSMathProduct=CSSMathProduct),window.CSSMathSum||(window.CSSMathSum=CSSMathSum),window.CSSStyleValue||(window.CSSStyleValue=CSSStyleValue),window.CSSUnitValue||(window.CSSUnitValue=CSSUnitValue),window.CSSUnparsedValue||(window.CSSUnparsedValue=CSSUnparsedValue),window.StylePropertyMapReadOnly||(window.StylePropertyMapReadOnly=StylePropertyMapReadOnly),window.StylePropertyMap||(window.StylePropertyMap=StylePropertyMap)}export{CSSKeywordValue,CSSStyleValue,CSSUnitValue,StylePropertyMap,polyfill as default}; diff --git a/lib/StylePropertyMap.js b/lib/StylePropertyMap.js index 27b0631..dac082a 100644 --- a/lib/StylePropertyMap.js +++ b/lib/StylePropertyMap.js @@ -19,9 +19,37 @@ export default class StylePropertyMap extends StylePropertyMapReadOnly { * @param {string} value * @returns {undefined} * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/StylePropertyMap/append} - * @todo Implement + * @see {@link https://drafts.css-houdini.org/css-typed-om/#dom-stylepropertymap-append} + * @todo implement */ - append() { + append(propertyName = null, propertyValue = null) { + if (!propertyName || !propertyValue) { + throw new TypeError( + `Failed to execute 'set' on 'StylePropertyMap': propertyName and propertyValue arguments are required.`, + ); + } + + if (Array.isArray(propertyValue)) { + if (propertyValue.length === 0) { + throw new TypeError( + `Failed to execute 'append' on 'StylePropertyMap': Invalid type for property`, + ); + } + } + + // 1. If property is not a custom property name string, set property to property ASCII lowercased. + const isCustomProp = propertyName.startsWith("--"); + // TODO: Handle custom property scenario + const prop = !isCustomProp ? propertyName.toLowerCase() : propertyName; + console.log(prop); + + // TODO: 2. If property is not a valid CSS property, throw a TypeError with: + // "Failed to execute 'append' on 'StylePropertyMap': Invalid propertyName: " + // TODO: 3. If property is not a list-valued property, throw a TypeError with: + // "Failed to execute 'append' on 'StylePropertyMap': Property does not support multiple values" + // TODO: 4. If any of the items in values have a non-null [[associatedProperty]] internal slot, and that slot’s value is anything other than property, throw a TypeError. + // TODO: 5. If any of the items in values are a CSSUnparsedValue or CSSVariableReferenceValue object, throw a TypeError. + console.warn("StylePropertyMap.append not implemented yet."); return undefined; } diff --git a/lib/StylePropertyMap.test.js b/lib/StylePropertyMap.test.js index c972b73..e67ac5c 100644 --- a/lib/StylePropertyMap.test.js +++ b/lib/StylePropertyMap.test.js @@ -12,6 +12,7 @@ import StylePropertyMap, { describe("class StylePropertyMap", () => { const mockStyles = { background: "red", + "background-image": "url(/path/to/url.jpg)", color: "#1f1f1f", width: "500px", }; @@ -31,7 +32,7 @@ describe("class StylePropertyMap", () => { }); it("the size property returns as expected", () => { - assert.equal(styleMapInstance.size, 3); + assert.equal(styleMapInstance.size, 4); }); it("#has returns true when a property exists", () => { @@ -110,4 +111,38 @@ describe("class StylePropertyMap", () => { assert.equal(expected.includes(val.toString()), true); } }); + + it("#append throws without required arguments", () => { + assert.throws(() => { + styleMapInstance.append(); + }, TypeError); + + assert.throws(() => { + styleMapInstance.append("background-image"); + }, TypeError); + + assert.throws(() => { + styleMapInstance.append("background-image", []); + }, TypeError); + }); + + it("#append adds the expected property and value", () => { + // Check the initial value that should have a single background image. + assert.equal( + styleMapInstance.get("background-image"), + "url(/path/to/url.jpg)", + ); + + // Append the new background image + styleMapInstance.append( + "background-image", + "linear-gradient(90deg, red, blue)", + ); + + // Check that the second background image has been appended + // assert.equal( + // styleMapInstance.get("background-image"), + // "url(/path/to/url.jpg) linear-gradient(90deg, red, blue)", + // ); + }); });