diff --git a/src/lib/src/parse.ts b/src/lib/src/parse.ts index 3be5fbeff..bd54d1461 100644 --- a/src/lib/src/parse.ts +++ b/src/lib/src/parse.ts @@ -131,7 +131,28 @@ export class LylParse { const media = matchArray[1]; if (media !== key && val.length) { const after = rules.get(media)!; - const newValue = after + key.replace(media + '{', '') + `{${val.join(';')}}`; + const sel = key.replace(media + '{', ''); + const newValue = after + val.reduce((previous, current) => { + + const last = previous[previous.length - 1]; + + if (current.startsWith('/* >> ds')) { + previous.push(current.replace(/\|\|\&\|\|/g, sel)); + } else if (current.startsWith('/* >> cc')) { + previous.push(transformCC(current, sel)); + } else { + if (Array.isArray(last)) { + last.push(current); + } else { + previous.push([current]); + } + } + return previous; + }, [] as (string | string[])[]) + .map(item => Array.isArray(item) ? `${sel}{${item.join('')}}` : item).join(''); + // const newValue = after + // + sel + // + `{${val.join('')}}`; rules.set(media, [newValue]); rules.delete(key); } @@ -146,17 +167,15 @@ export class LylParse { const css: string[] = []; const contentRendered: string[] = []; const set = new Set(); + for (let index = 0; index < contents.length; index++) { - let content = contents[index]; + const content = contents[index]; if (content) { if (content.startsWith('/* >> ds')) { contentRendered.push(content.replace(/\|\|\&\|\|/g, sel)); set.add(contentRendered); } else if (content.startsWith('/* >> cc')) { - content = content.replace(/\/\* >> cc[^\/\*]+\*\//g, ''); - let expression = content.slice(2, content.length - 1); - expression = `styleTemplateToString((${expression}), \`${sel}\`)`; - contentRendered.push(`\${${expression}}`); + contentRendered.push(transformCC(content, sel)); set.add(contentRendered); } else { // css += `${sel}{${content}}`; @@ -183,7 +202,7 @@ export class LylParse { // if (content.startsWith('/* >> cc')) { // content = content.replace(/\/\* >> cc[^\/\*]+\*\//g, ''); // let variable = content.slice(2, content.length - 1); - // variable = `styleTemplateToString((${variable}), \`${sel}\`)`; + // variable = `st2c((${variable}), \`${sel}\`)`; // return `\${${variable}}`; // } // // for non LylModule> @@ -227,18 +246,28 @@ export class LylParse { } +function transformCC(content: string, sel: string) { + content = content.replace(/\/\* >> cc[^\/\*]+\*\//g, ''); + let expression = content.slice(2, content.length - 1); + expression = `st2c((${expression}), \`${sel}\`)`; + return `\${${expression}}`; +} + export type StyleTemplate = (className: string) => string; -export function lyl(literals: TemplateStringsArray, ...placeholders: (string | Color | StyleCollection | number | StyleTemplate | null | undefined)[]) { +export function lyl(literals: TemplateStringsArray, ...placeholders: (string | Color | StyleCollection | number | StyleTemplate | LylDeclarationBlock | null | undefined)[]) { return (className: string) => { let result = ''; - const dsMap = new Map(); + const dsMap = new Map(); for (let i = 0; i < placeholders.length; i++) { const placeholder = placeholders[i]; result += literals[i]; if (result.endsWith('...')) { result = result.slice(0, result.length - 3); - if (typeof placeholder === 'function' || placeholder instanceof StyleCollection) { + if (typeof placeholder === 'function' + || placeholder instanceof StyleCollection + || Array.isArray(placeholder) + ) { const newID = createUniqueId(); dsMap.set(newID, placeholder); result += newID; @@ -253,13 +282,7 @@ export function lyl(literals: TemplateStringsArray, ...placeholders: (string | C const css = result.replace(STYLE_TEMPLATE_REGEX(), (str) => { if (dsMap.has(str)) { const fn = dsMap.get(str)!; - let template: StyleTemplate; - if (fn instanceof StyleCollection) { - template = fn.css; - } else { - template = fn; - } - return `${createUniqueCommentSelector('ds')}${template('||&||')}`; + return `${createUniqueCommentSelector('ds')}${st2c(fn, '||&||')}`; } return ''; }); @@ -322,12 +345,35 @@ export class StyleCollection { } +/** + * The declaration block can an array of declarations, + * where each declaration contains a property name and value. + * If a declaration is `null` or `undefined` it will not be rendered. + * e.g. + * + * ['color:red'] + */ +export type LylDeclarationBlock = (string | null | undefined)[]; -export function styleTemplateToString(fn: StyleTemplate | StyleCollection | null | undefined, className: string) { +/** + * Transform a ...{style} to css + * For internal use purposes only + * @param fn StyleTemplate or StyleCollection + * @param className class name + */ +export function st2c( + fn: StyleTemplate | StyleCollection | LylDeclarationBlock | null | undefined, + className: string) { + if (fn == null) { + return ''; + } if (fn instanceof StyleCollection) { return fn.css(className); } - return fn ? (fn)(className) : ''; + if (typeof fn === 'function') { + return (fn)(className); + } + return fn.filter((item) => !!item).join(';'); } // export function normalizeStyleTemplate( diff --git a/src/lib/style-compiler/compiler.test.ts b/src/lib/style-compiler/compiler.test.ts index 7ddf6d3ca..221016210 100644 --- a/src/lib/style-compiler/compiler.test.ts +++ b/src/lib/style-compiler/compiler.test.ts @@ -1,6 +1,6 @@ import anyTest, { TestInterface } from 'ava'; import { hasLylStyle, styleCompiler } from './compiler'; -import { styleTemplateToString, StyleCollection, lyl } from '../src/parse'; +import { st2c, StyleCollection, lyl } from '../src/parse'; import * as tsNode from 'ts-node'; const test = anyTest as TestInterface; @@ -12,7 +12,7 @@ class Context { }\` style('.y')`; - styleIntoObjectAsFunction = `${styleTemplateToString}\n${StyleCollection}\nconst styles = { + styleIntoObjectAsFunction = `${st2c}\n${StyleCollection}\nconst styles = { item: () => lyl \`{ color: \${'blue'} ...\${null} @@ -36,7 +36,7 @@ class Context { `; inheritanceStyle = ` - ${styleTemplateToString}\n + ${st2c}\n ${StyleCollection} const colorBlue = lyl \`{ color: blue @@ -307,9 +307,9 @@ test(`compile complex style`, async t => { import { LyTheme2, LyHostClass, - styleTemplateToString } from '@alyle/ui'; + st2c } from '@alyle/ui'; - const style = (className: string) => \`\${className}{color:red;}\${styleTemplateToString(( + const style = (className: string) => \`\${className}{color:red;}\${st2c(( topZero), \`\${className}\`)}\`; `); }); @@ -321,10 +321,10 @@ test(`compile simple and complex style`, async t => { import { LyTheme2, LyHostClass, - styleTemplateToString } from '@alyle/ui'; + st2c } from '@alyle/ui'; const topZero = (className: string) => \`\${className}{top:0;}\` - const style = (className: string) => \`\${className}{color:red;}\${styleTemplateToString(( + const style = (className: string) => \`\${className}{color:red;}\${st2c(( topZero), \`\${className}\`)}\`; `); }); @@ -367,7 +367,7 @@ import { Router, ActivatedRoute } from '@angular/router'; import { LyTheme2, LyHostClass, - styleTemplateToString } from '@alyle/ui'; + st2c } from '@alyle/ui'; import { AUIThemeVariables } from '@app/app.module'; @@ -375,9 +375,9 @@ const zero = 0; const topZero = (className: string) => \`\${className}{top:\${zero}px;}\`; -const colorRedAndTopZero = (className: string) => \`\${className}{color:red;}\${styleTemplateToString((item0), \`\${className}\`)}\`; +const colorRedAndTopZero = (className: string) => \`\${className}{color:red;}\${st2c((item0), \`\${className}\`)}\`; -const item2 = (className: string) => \`\${styleTemplateToString((item), \`\${className}\`)}\${className} ul{margin:0;padding:\${zero};list-style:none;}\${styleTemplateToString((item), \`\${className} ul\`)}\${className} li a{display:inline-block;}\${className} a{display:block;padding:6px \${12}px;text-decoration:none;}\${className} ul > li{list-style-type:none;}\${className} h2 + p{border-top:1px solid gray;}\${className} p ~ span{opacity:0.8;}\`; +const item2 = (className: string) => \`\${st2c((item), \`\${className}\`)}\${className} ul{margin:0;padding:\${zero};list-style:none;}\${st2c((item), \`\${className} ul\`)}\${className} li a{display:inline-block;}\${className} a{display:block;padding:6px \${12}px;text-decoration:none;}\${className} ul > li{list-style-type:none;}\${className} h2 + p{border-top:1px solid gray;}\${className} p ~ span{opacity:0.8;}\`; `); // tslint:enable }); @@ -438,6 +438,44 @@ test(`with comments`, async t => { t.is(css, `.y .a{color:blue;}`); t.is(lyl `${styleContent}`('.y'), `.y .a{color:blue;}`); }); +test(`media queries with inheritance style`, async t => { + const inStyle = lyl `{ + color: blue + sel { + prop: value + } + }`; + const styleContent = lyl `{ + @media all { + prop: value + prop2: value2 + ...${inStyle} + prop3: value3 + } + }`; + const css = evalScript(` + ${st2c} + ${StyleCollection} + const inStyle = lyl \`{ + color: blue + sel { + prop: value + } + }\`; + const style = lyl \`{ + @media all { + prop: value + prop2: value2 + ...\${inStyle} + prop3: value3 + } + }\`; + style('.y'); + `); + const expected = '@media all{.y{prop:value;prop2:value2;}.y{color:blue;}.y sel{prop:value;}.y{prop3:value3;}}'; + t.is(css, expected); + t.is(styleContent('.y').replace(/\/\* >> ds[^\/\*]+\*\//g, ''), expected); +}); function evalScript(script: string) { // tslint:disable-next-line: no-eval diff --git a/src/lib/style-compiler/compiler.ts b/src/lib/style-compiler/compiler.ts index f397ff153..a797763fe 100644 --- a/src/lib/style-compiler/compiler.ts +++ b/src/lib/style-compiler/compiler.ts @@ -83,7 +83,7 @@ function updateImport(content: string, numSimpleStyles: number, numComplexStyles const modulePath = importDeclaration.moduleSpecifier.getFullText(); if ((numSimpleStyles && numComplexStyles) || numComplexStyles) { imports = imports.map( - imp => imp === 'lyl' ? 'styleTemplateToString' : imp); + imp => imp === 'lyl' ? 'st2c' : imp); } else if (numSimpleStyles) { imports = imports.filter(imp => imp !== 'lyl'); }