From 953778dc5008243d8d42bd6ada108aab31998952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Thu, 19 Dec 2019 21:54:35 +0100 Subject: [PATCH] [IMP] component/tags: add inline css tag This add an important feature: defining completely standalone owl components, with the template/style and javascript code together. closes #284 --- doc/readme.md | 18 ++-- doc/reference/component.md | 4 + doc/reference/tags.md | 92 ++++++++++++++++++++- doc/tooling.md | 29 +++++-- src/component/component.ts | 18 ++++ src/component/styles.ts | 56 +++++++++++++ src/tags.ts | 17 ++++ tests/component/component.test.ts | 2 +- tests/component/slots.test.ts | 7 -- tests/component/styles.test.ts | 132 ++++++++++++++++++++++++++++++ 10 files changed, 345 insertions(+), 30 deletions(-) create mode 100644 src/component/styles.ts create mode 100644 tests/component/styles.test.ts diff --git a/doc/readme.md b/doc/readme.md index 501bf5c3..9f89ae62 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -56,15 +56,15 @@ useState Link config RouteComponent mode Router core tags - EventBus xml - Observer utils -hooks debounce - onWillStart escape - onMounted loadJS - onWillUpdateProps loadFile - onWillPatch shallowEqual - onPatched whenReady - onWillUnmount + EventBus css + Observer xml +hooks utils + onWillStart debounce + onMounted escape + onWillUpdateProps loadJS + onWillPatch loadFile + onPatched shallowEqual + onWillUnmount whenReady useContext useState useRef diff --git a/doc/reference/component.md b/doc/reference/component.md index 9ab28e20..6808786e 100644 --- a/doc/reference/component.md +++ b/doc/reference/component.md @@ -225,6 +225,10 @@ to be called in the constructor. } ``` +- **`style`** (string, optional): it should be the return value of the [`css tag](tags.md#css-tag), + which is used to inject stylesheet whenever the component is visible on the + screen. + There is another static property defined on the `Component` class: `current`. This property is set to the currently being defined component (in the constructor). This is the way [hooks](hooks.md) are able to get a reference to the target diff --git a/doc/reference/tags.md b/doc/reference/tags.md index f235303e..f0ad8623 100644 --- a/doc/reference/tags.md +++ b/doc/reference/tags.md @@ -4,16 +4,19 @@ - [Overview](#overview) - [`xml` tag](#xml-tag) +- [`css` tag](#css-tag) ## Overview -Tags are very small helpers to make it easy to write inline templates. There is -only one currently available tag: `xml`, but we plan to add other tags later, -such as a `css` tag, which will be used to write [single file components](../tooling.md#single-file-component). +Tags are very small helpers intended to make it easy to write inline templates +or styles. There are currently two tags: `css` and `xml`. With these functions, +it is possible to write [single file components](../tooling.md#single-file-component). ## XML tag -Without tags, creating a standalone component would look like this: +The `xml` tag is certainly the most useful tag. It is used to define an inline +QWeb template for a component. Without tags, creating a standalone component +would look like this: ```js import { Component } from 'owl' @@ -52,3 +55,84 @@ class MyComponent extends Component { ... } ``` + +## CSS tag + +The CSS tag is useful to define a css stylesheet in the javascript file: + +```js +class MyComponent extends Component { + static template = xml` +
some template
+ `; + static css` + .my-component { + color: red; + } + `; +} +``` + +The `css` tag registers internally the css information. Then, whenever the first +instance of the component is created, will add a ``); + + await app.mount(fixture); + const style = getComputedStyle(app.el!); + expect(style.color).toBe("red"); + expect(fixture.innerHTML).toBe('
text
'); + }); + + test("inherited components properly apply css", async () => { + class App extends Component { + static template = xml`
text
`; + static style = css` + .app { + color: red; + } + `; + } + class SubApp extends App { + static style = css` + .app { + font-weight: bold; + } + `; + } + expect(document.head.innerHTML).toBe(""); + const app = new SubApp(); + + expect(document.head.innerHTML).toBe(``); + + await app.mount(fixture); + const style = getComputedStyle(app.el!); + expect(style.color).toBe("red"); + expect(style.fontWeight).toBe("bold"); + expect(fixture.innerHTML).toBe('
text
'); + }); + + test("get a meaningful error message if css helper is missing", async () => { + class App extends Component { + static template = xml`
text
`; + static style = `.app {color: red;}`; + } + let error; + try { + new App(); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toBe( + "Invalid css stylesheet for component 'App'. Did you forget to use the 'css' tag helper?" + ); + }); + + test("inline stylesheets are processed", async () => { + class App extends Component { + static template = xml`
text
`; + static style = css` + .app { + color: red; + .some-class { + font-weight: bold; + width: 40px; + } + + display: block; + } + `; + } + new App(); + + expect(document.head.querySelector("style")!.innerHTML).toBe(`.app { + color: red; +} +.app .some-class { + font-weight: bold; + width: 40px; +} +.app { + display: block; +}`); + }); +});