Dynamic styling of Shadow Host - craftkit code pattern
Tags: craftkit, javascript, shadowdom, webdev
Published: 2020-11-03 14:50:53

Usually, it is enough to define style only for the `.root` and its children. But sometimes, you want to edit style for host element. Here is patterns for this.

## Define Shadow Host style

To define Shadow Host style, just write CSS in `:host` as usual. 

```javascript 
class Example extends Craft.UI.View {
	style(componentId){
		return `
			:host {
				padding-top: env(safe-area-inset-top);
				padding-left: env(safe-area-inset-left);
				padding-right: env(safe-area-inset-right);
			}
		`;
	}
}
``` 

## Direct Host style modification

You can directly update Host element styling via `this.shadow.host.style[*]`.

```javascript 
class Example extends Craft.UI.View {
	updateShadowHostWidth(){
		this.shadow.host.style.width = '200px';
	}
	style(componentId){
		return `
			:host {
				width: 100px;
			}
		`;
	}
}
``` 

## Swap Shadow Host CSS class

If you would like to change style of Host element on the fly, your target class should be defined in the parent component to be able to affect the shadow element. 

```javascript 
class Example extends Craft.UI.View {
	viewDidLoad(callback){
		this.appendView(new ExampleCore());
		if(callback){ callback(); }
	}
	style(componentId){
		return `
			.light { color:#333; background-color: #fff; }
			.dark { color:#fff; background-color: #333; }
		`;
	}
}

class ExampleWrapped extends Craft.UI.View {
	constructor(options){
		super(options);
		this.data = { mode:0 };
	}
	toggleMode(){
		if( this.data.mode++ % 2 ){
			this.shadow.host.classList.add('light')
			this.shadow.host.classList.remove('dark')
		}else{
			this.shadow.host.classList.add('dark');
			this.shadow.host.classList.remove('light')
		}
	}
	style(componentId){
		return `
			:host { color:#333; background-color: #fff; }
			.root { 
				width:100px; marign-left:auto; marign-right:auto;
			}
		`;
	}
	template(componentId){
		return `
			<div id="root" class="root" 
			onclick="${componentId}.toggleMode()">
				Hello!
			</div>
		`;
	}
}
``` 

## Discouraged pattern

You can write something like the following code, but the code goes back and forth. So, this pattern should be avoided. 

```javascript 
class ExampleWrapper extends Craft.UI.View {
	constructor(options){
		super(options);
		this.data = { mode:1 };
		this.views = { example:null };
	}
	viewDidLoad(callback){
		this.views.example = new ExampleWrapped({delegate:this});
		this.appendView(this.views.example);
		if(callback){ callback(); }
	}
	toggleMode(){
		if( this.data.mode++ % 2 ){
			this.views.example.darkMode();
		}else{
			this.views.example.lightMode();
		}
	}
	style(componentId){
		return `
			.light { color:#333; background-color: #fff; }
			.dark { color:#fff; background-color: #333; }
		`;
	}
}

class ExampleWrapped extends Craft.UI.View {
	constructor(options){
		super(options);
		this.delegate = options.delegate;
	}
	lightMode(){
		this.shadow.host.classList.add('light')
		this.shadow.host.classList.remove('dark')
	}
	darkMode(){
		this.shadow.host.classList.add('dark');
		this.shadow.host.classList.remove('light')
	}
	style(componentId){
		return `
			:host { color:#333; background-color: #fff; }
			.root { 
				width:100px; marign-left:auto; marign-right:auto;
			}
		`;
	}
	template(componentId){
		return `
			<div id="root" class="root" 
			onclick="${componentId}.delegate.toggleMode()">
				Hello!
			</div>
		`;
	}
}
``` 

## NOTE

Above examples are runnable on playground.

```javascript 
var view = new Example();
view.loadView();
Craft.Core.Context.getRootViewController().appendSubView(view);
``` 

🛺 Try CraftKit Playground:
https://github.com/craftkit/craftkit-playground