diff --git a/demo/css-typed-om-polyfill.js b/demo/css-typed-om-polyfill.js index 4e5a241..1ba3138 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)}function parseCSSText(cssText){if(keywordValues.includes(cssText))return new CSSKeywordValue(cssText);const isUnitValue=String(cssText).match(unitParsingMatcher);if(isUnitValue){const[,value,unit]=isUnitValue;return new CSSUnitValue(value,unitKeys[unitValues.indexOf(unit||"")])}return function(cssText){const cssStyleValue=Object.create(CSSStyleValue.prototype);return Object.defineProperty(cssStyleValue,"value",{value:cssText}),cssStyleValue}(cssText)}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("|")})?$`),keywordValues=["absolute","auto","block","bold","bolder","border-box","both","bottom","capitalize","center","circle","col-resize","collapse","column","column-reverse","contain","content-box","cover","crosshair","dashed","default","dotted","double","e-resize","ellipsis","fixed","flex","flex-end","flex-start","grid","groove","hidden","hide","inherit","initial","inline","inline-block","inline-flex","inline-grid","inline-table","inset","italic","justify","left","lighter","line-through","list-item","lowercase","ltr","middle","move","n-resize","ne-resize","none","normal","nowrap","nw-resize","oblique","outset","overline","pointer","relative","repeat","repeat-x","repeat-y","revert","ridge","right","row","row-resize","row-reverse","rtl","run-in","s-resize","scroll","se-resize","separate","show","solid","space-around","space-between","space-evenly","square","static","sticky","stretch","sw-resize","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","text","top","underline","unset","uppercase","vertical-text","visible","w-resize","wait","wrap","wrap-reverse"];const cssProperties=["align-content","align-items","align-self","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","background","background-attachment","background-color","background-image","background-position","background-repeat","background-size","border","border-collapse","border-spacing","box-shadow","box-sizing","caption-side","clear","clip","color","contain","cursor","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","font-family","font-size","font-style","font-weight","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-gap","grid-gap","grid-row","grid-row-gap","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","height","justify-content","left","letter-spacing","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","mask","object-fit","object-position","opacity","outline","overflow","padding","pointer-events","position","resize","right","table-layout","text-align","text-decoration","text-indent","text-overflow","text-shadow","text-transform","top","transition","transition-delay","transition-duration","transition-property","transition-timing-function","user-select","vertical-align","visibility","white-space","width","will-change","word-break","word-spacing","word-wrap","z-index"];class CSSStyleValue{constructor(){if("CSSStyleValue"===this.constructor.name)throw new TypeError("Illegal constructor")}toString(){return String(this.value)}static parse(propertyName=null,cssText=null){if(!propertyName||!cssText)throw new TypeError("Failed to execute 'parse' on 'CSSStyleValue': propertyName and cssText arguments are required.");if(propertyName.startsWith("--"))return void console.warn("CSSStyleValue.parse does not handle CSS custom properties yet.");const prop=propertyName.toLowerCase();if(!function(propertyName){return cssProperties.includes(propertyName)}(prop))throw new TypeError(`Failed to execute 'parse' on 'CSSStyleValue': ${prop} is an invalid property name`);return parseCSSText(cssText)}static parseAll(){console.warn("CSSStyleValue.parseAll not implemented yet.")}}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/CSSStyleValue.js b/lib/CSSStyleValue.js index 14d92d2..04a5aba 100644 --- a/lib/CSSStyleValue.js +++ b/lib/CSSStyleValue.js @@ -1,53 +1,83 @@ -import parseAsValue from "./parseCSSText.js"; +import parseCSSText from "./parseCSSText.js"; +import { isValidProperty } from "./util.js"; /** + * @see {@link https://drafts.css-houdini.org/css-typed-om/#cssstylevalue} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleValue} * @typedef {object} CSSStyleValue */ export default class CSSStyleValue { constructor() { - // We allow for classes that extend CSSStyleValue to have constructors - // but not CSSStyleValue itself. + /* + We allow for CSSStyleValue subclasses to have constructors but not + CSSStyleValue itself. For direct instances we use cssStyleValueCreator. + */ if (this.constructor.name === "CSSStyleValue") { throw new TypeError("Illegal constructor"); } } + /** + * @see {@link https://drafts.css-houdini.org/css-typed-om/#stylevalue-serialization} + */ + toString() { + return String(this.value); + } + /** * @param {string} property * @param {string} cssText - * See https://drafts.css-houdini.org/css-typed-om/#parse-a-cssstylevalue - * - * FIXME: This still isn't returning the exact shape we want yet. + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleValue/parse_static} + * @see {@link https://drafts.css-houdini.org/css-typed-om/#parse-a-cssstylevalue} */ - static parse(property, cssText) { - const isCustomProp = property.startsWith("--"); + static parse(propertyName = null, cssText = null) { + if (!propertyName || !cssText) { + throw new TypeError( + `Failed to execute 'parse' on 'CSSStyleValue': propertyName and cssText arguments are required.`, + ); + } + // TODO: Implement custom propery handling + const isCustomProp = propertyName.startsWith("--"); if (isCustomProp) { - // FIXME: Custom properties should return a CSSUnparsedValue + console.warn( + "CSSStyleValue.parse does not handle CSS custom properties yet.", + ); return undefined; - } else { - const prop = property.toLowerCase(); - - // FIXME: Test if this is a valid CSS prop - const isValid = true; - - if (!isValid) { - throw new TypeError(`${prop} is not a valid CSS property.`); - } + } - // FIXME: Account for cssText with calc - return parseAsValue(cssText); + const prop = propertyName.toLowerCase(); + if (!isValidProperty(prop)) { + throw new TypeError( + `Failed to execute 'parse' on 'CSSStyleValue': ${prop} is an invalid property name`, + ); } + + return parseCSSText(cssText); } /** - * @param {string} property + * @param {string} propertyName * @param {string} cssText - * See https://drafts.css-houdini.org/css-typed-om/#parse-a-cssstylevalue + * @see {@link https://drafts.css-houdini.org/css-typed-om/#parse-a-cssstylevalue} + * @todo implement */ - // eslint-disable-next-line - static parseAll(property, cssText) { - // FIXME: Implement + static parseAll() { + console.warn("CSSStyleValue.parseAll not implemented yet."); return undefined; } } + +/** + * Create a CSSStyleValue instance with the given cssText + * + * @param {string} cssText + * @returns {CSSStyleValue} + */ +export function cssStyleValueCreator(cssText) { + const cssStyleValue = Object.create(CSSStyleValue.prototype); + Object.defineProperty(cssStyleValue, "value", { + value: cssText, + }); + return cssStyleValue; +} diff --git a/lib/parseCSSText.js b/lib/parseCSSText.js index 875a272..fdd203c 100644 --- a/lib/parseCSSText.js +++ b/lib/parseCSSText.js @@ -1,21 +1,140 @@ import units from "./units.js"; +import { cssStyleValueCreator } from "./CSSStyleValue.js"; import CSSUnitValue from "./CSSUnitValue.js"; import CSSKeywordValue from "./CSSKeywordValue.js"; -export default (string) => { - const unitParsingMatch = String(string).match(unitParsingMatcher); - - if (unitParsingMatch) { - const [, value, unit] = unitParsingMatch; +/** + * @todo Handle the remaining CSSStyleValue subclasses + * @see {@link https://drafts.css-houdini.org/css-typed-om/#stylevalue-subclasses} + * - Needed: CSSNumericValue, CSSMathValue, CSSTransformValue, CSSImageValue, CSSColorValue + */ +export default function parseCSSText(cssText) { + const isKeywordValue = keywordValues.includes(cssText); + if (isKeywordValue) { + return new CSSKeywordValue(cssText); + } + const isUnitValue = String(cssText).match(unitParsingMatcher); + if (isUnitValue) { + const [, value, unit] = isUnitValue; return new CSSUnitValue(value, unitKeys[unitValues.indexOf(unit || "")]); } - return new CSSKeywordValue(string); -}; + return cssStyleValueCreator(cssText); +} const unitKeys = Object.keys(units); const unitValues = Object.values(units); const unitParsingMatcher = new RegExp( `^([-+]?[0-9]*.?[0-9]+)(${unitValues.join("|")})?$`, ); + +const keywordValues = [ + "absolute", + "auto", + "block", + "bold", + "bolder", + "border-box", + "both", + "bottom", + "capitalize", + "center", + "circle", + "col-resize", + "collapse", + "column", + "column-reverse", + "contain", + "content-box", + "cover", + "crosshair", + "dashed", + "default", + "dotted", + "double", + "e-resize", + "ellipsis", + "fixed", + "flex", + "flex-end", + "flex-start", + "grid", + "groove", + "hidden", + "hide", + "inherit", + "initial", + "inline", + "inline-block", + "inline-flex", + "inline-grid", + "inline-table", + "inset", + "italic", + "justify", + "left", + "lighter", + "line-through", + "list-item", + "lowercase", + "ltr", + "middle", + "move", + "n-resize", + "ne-resize", + "none", + "normal", + "nowrap", + "nw-resize", + "oblique", + "outset", + "overline", + "pointer", + "relative", + "repeat", + "repeat-x", + "repeat-y", + "revert", + "ridge", + "right", + "row", + "row-resize", + "row-reverse", + "rtl", + "run-in", + "s-resize", + "scroll", + "se-resize", + "separate", + "show", + "solid", + "space-around", + "space-between", + "space-evenly", + "square", + "static", + "sticky", + "stretch", + "sw-resize", + "table", + "table-caption", + "table-cell", + "table-column", + "table-column-group", + "table-footer-group", + "table-header-group", + "table-row", + "table-row-group", + "text", + "top", + "underline", + "unset", + "uppercase", + "vertical-text", + "visible", + "w-resize", + "wait", + "wrap", + "wrap-reverse", +]; diff --git a/lib/parseCSSText.test.js b/lib/parseCSSText.test.js index e041aa5..3acdf25 100644 --- a/lib/parseCSSText.test.js +++ b/lib/parseCSSText.test.js @@ -1,17 +1,40 @@ import { describe, it } from "node:test"; import assert from "node:assert/strict"; -import parseAsValue from "./parseCSSText.js"; +import parseCSSText from "./parseCSSText.js"; -describe("#parseAsValue", () => { - it("returns CSSUnitValue when expected", () => { - ["10rem", "20px"].forEach((val) => { - assert.equal(parseAsValue(val).constructor.name, "CSSUnitValue"); +describe("#parseCSSText", () => { + /** + * @see {@link https://drafts.css-houdini.org/css-typed-om/#direct-cssstylevalue} + */ + it("parses values that don't have a specialized subclass", () => { + const tests = ["1px solid red", "#f1f1f1"]; + + tests.forEach((t, i) => { + const result = parseCSSText(t); + assert.equal(result.constructor.name, "CSSStyleValue"); + assert.equal(result.toString(), tests[i]); }); }); - it("returns CSSKeywordValue when expected", () => { - ["auto", "red", "rgb(0, 0, 0)"].forEach((val) => { - assert.equal(parseAsValue(val).constructor.name, "CSSKeywordValue"); + /** + * @see {@link https://drafts.css-houdini.org/css-typed-om/#keywordvalue-objects} + */ + it("parses values that are CSSKeywordValue objects", () => { + const tests = ["initial", "block", "static"]; + + tests.forEach((t, i) => { + const result = parseCSSText(t); + assert.equal(result.constructor.name, "CSSKeywordValue"); + assert.equal(result.toString(), tests[i]); + }); + }); + + /** + * @see {@link https://drafts.css-houdini.org/css-typed-om/#simple-numeric} + */ + it("parses values that produce CSSUnitValue", () => { + ["10rem", "20px"].forEach((val) => { + assert.equal(parseCSSText(val).constructor.name, "CSSUnitValue"); }); }); });