From 74bd119bf56e915a29c434ed66c4063cdb05f48d Mon Sep 17 00:00:00 2001 From: mcm-odoo Date: Thu, 9 Jan 2020 09:35:48 +0100 Subject: [PATCH] [IMP] styles: handle & selector This commit adds the support of the & selector. This selector is useful to join a subrule with a parent selector. example: button { &:hover { background-color: red; } } will give button:hover { background-color: red; } --- doc/reference/tags.md | 46 ++++++++++++++++++++++++++++++++++ src/component/styles.ts | 7 ++++-- tests/component/styles.test.ts | 25 ++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/doc/reference/tags.md b/doc/reference/tags.md index f0ad8623..6b7db03f 100644 --- a/doc/reference/tags.md +++ b/doc/reference/tags.md @@ -101,6 +101,52 @@ will be formatted as: } ``` +This extension brings another useful feature: the `&` selector which refers to +the parent selector. For example, we want our component to be red when hovered. +We would like to write something like: + +```scss +.my-component { + display: block; + :hover { + color: red; + } +} +``` + +but it will be formatted as: + +```css +.my-component { + display: block; +} +.my-component :hover { + color: red; +} +``` + +The `&` selector can be used to solve this problem: + +```scss +.my-component { + display: block; + &:hover { + color: red; + } +} +``` + +will be formatted as: + +```css +.my-component { + display: block; +} +.my-component:hover { + color: red; +} +``` + Now, there is no additional processing done by the `css` tag. However, since it is done in javascript at runtime, we actually have more power. For example: diff --git a/src/component/styles.ts b/src/component/styles.ts index ceb5a604..c9c1e604 100644 --- a/src/component/styles.ts +++ b/src/component/styles.ts @@ -15,13 +15,16 @@ export function processSheet(str: string): string { function generateSelector(stackIndex: number, parentSelector?: string) { const parts: string[] = []; for (const selector of selectorStack[stackIndex]) { - let part = parentSelector && parentSelector + ' ' + selector || selector; + let part = (parentSelector && parentSelector + " " + selector) || selector; + if (part.includes("&")) { + part = selector.replace(/&/g, parentSelector || ""); + } if (stackIndex < selectorStack.length - 1) { part = generateSelector(stackIndex + 1, part); } parts.push(part); } - return parts.join(', '); + return parts.join(", "); } function generateRules() { if (rules.length) { diff --git a/tests/component/styles.test.ts b/tests/component/styles.test.ts index f72cb9bb..6fc0fab3 100644 --- a/tests/component/styles.test.ts +++ b/tests/component/styles.test.ts @@ -142,4 +142,29 @@ describe("styles and component", () => { color: red; }`); }); + + test("handle & selector", async () => { + let sheet = processSheet(`.btn { + &.danger { + color: red; + } + }`); + + expect(sheet).toBe(`.btn.danger { + color: red; +}`); + + sheet = processSheet(`.some-class { + &.btn { + .other-class ~ & { + color: red; + } + } + }`); + + expect(sheet).toBe(`.other-class ~ .some-class.btn { + color: red; +}`); + }); + });