shadowRoot.activeElement is not working in safari - web-component

Currently I am working on stencilJS which has feature to implement shadow dom. I am facing an issue related to activeElement of the shadowRoot.It is working fine with Chrome but when I am testing my component then activeElement is getting null in safari.
Here is the code snippet
import { Component, Prop, Listen } from '#stencil/core';
#Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true
})
export class MyComponent {
/**
* The first name
*/
#Prop() first: string;
/**
* The middle name
*/
#Prop() middle: string;
/**
* The last name
*/
#Prop() last: string;
#Listen('click')
onHadnleClickEvent(ev) {
console.log('===== 31 =====', ev.target.shadowRoot.activeElement)// getting null in safari
}
render() {
return ( <div>
<button>Click Me!!!</button>
</div>
)
}
}

I found the result to get the clicked element when shadowDom is enabled. Here is the solution:
#Listen('click')
onHadnleClickEvent(ev) {
console.log('===== 31 =====', ev.composedPath()[0]// It will give you the clicked element
}

Related

How can I toggle a class in a LitElement Web Component

I am working with precompiled stylesheet (from SASS) and only need to toggle classes.
I have two elements that will be writing to an event. Based on the event being true/false I want to to toggle a class on my component.
Would this work:
import { LitElement, html } from 'lit-element'
/**
*
*
* #export
* #class MenuMainButton
* #extends {LitElement}
*/
export class MenuMainButton extends LitElement {
static get properties() {
return {
name: { type: String },
toggled: { type: String }
}
}
constructor() {
super()
this.name = 'Menu'
this.toggled = ''
this.addEventListener('toggle-main-menu', this.handleEvents)
}
render() {
return html`
<a #click=${this._onClick} class="menu-button wk-app-menu-button app-menu-open ${this.toggled} govuk-body"
>${this.name}</a
>
`
}
handleEvents(event) {
this.toggled = event.toggle ? 'hide-item' : ''
}
_onClick() {
const toggleMainMenu = new CustomEvent('toggle-main-menu', {
toggle: this.toggled === '' ? 1 : 0
})
this.dispatchEvent(toggleMainMenu)
}
}
window.customElements.define('main-menu-button', MenuMainButton)
One way to make styles dynamic is to add bindings to the class or style attributes in your template.
The lit-html library offers two directives, classMap and styleMap, to conveniently apply classes and styles in HTML templates.
Styles - LitElement

Polymer 3.0 dom-repeat not repeating template

Hi I was working on a todo list app with Polymer 3.0 and am having some trouble getting dom-repeat to work.
Here is the code I have:
/** #format */
import { html, PolymerElement } from '#polymer/polymer/polymer-element.js';
import '#polymer/paper-button/paper-button.js';
import '#polymer/paper-checkbox/paper-checkbox.js';
import '#polymer/paper-input/paper-input.js';
import '#polymer/polymer/lib/elements/dom-repeat.js';
/**
* `todo-element`
*
*
* #customElement
* #polymer
* #demo demo/index.html
*/
class TodoElement extends PolymerElement {
static get template() {
return html`
<style>
:host {
display: block;
}
</style>
<h2>[[name]]</h2>
<div class="todo-list>
<dom-repeat items={{tasks}}>
<template>
<div class="task">
<paper-checkbox></paper-checkbox>
<paper-input label="Task: " value="[[task]]"></paper-input>
</div>
</template>
</dom-repeat>
<paper-button>Add Task</paper-button>
<h4>[[sub]]</h4>
</div>
`;
}
static get properties() {
return {
name: {
type: String,
value: 'Todo list',
},
sub: {
type: String,
value: 'Completed',
},
tasks: {
type: Array,
value: () => ['task1', 'task2', 'task3'],
},
};
}
}
window.customElements.define('todo-element', TodoElement);
Here what I see on the webapp:
What I am expecting is to see 3 of the checkbox, input, and button but I only get one. Thanks in advance for any assistance on this!
I was missing a " in my <div class="todo-list>. When I switched it to it started working

Using proper CSS media queries in Angular

I read that in Angular it is a very bad practice to use the CSS hidden element to hide an element like this:
.container{
background-color : powderblue;
height : 50px;
width : 100%
}
#media (max-width: 400px){
.container{
display: none;
}
}
<div class="container"></div>
And I know the Angular way to show or hide an element is using the *ngIf directive.
Question
How can I get the * ngIf to react on the media query in an 'Angular fashion'?
You can use angular/breakpoints-angular-cdk
follow these steps
on the terminal
npm install #angular/cdk
Then import the layout module and and add it to your NgModule’s list of imports
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { LayoutModule } from '#angular/cdk/layout';
import { AppComponent } from './app.component';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
LayoutModule
],
providers: [],
bootstrap: [AppComponent]
})
right after you can use it in your component, just import these classes from #angular/cdk/layout
import { Component, OnInit } from '#angular/core';
import { BreakpointObserver, BreakpointState } from '#angular/cdk/layout';
#Component({ ... })
export class AppComponent implements OnInit {
public showContainer: boolean;
constructor(public breakpointObserver: BreakpointObserver) {}
ngOnInit() {
this.breakpointObserver
.observe(['(min-width: 400px)'])
.subscribe((state: BreakpointState) => {
if (state.matches) {
this.showContainer = true;
} else {
this.showContainer = false;
}
});
}
}
Check the docs it is a simple API
Angular flex layout is better solution for this. You wouldn't need media queries and it has special responsive feature to show and hide for example
fxShow: This markup specifies if its host element should be displayed (or not)
<div fxShow [fxShow.xs]="isVisibleOnMobile()"></div>
fxHide: This markup specifies if its host element should NOT be displayed
<div fxHide [fxHide.gt-sm]="isVisibleOnDesktop()"></div>
No need to write lot of css and it's very compatible with angular material.
https://github.com/angular/flex-layout
I came up with the following base class and have found it works well.
import { HostBinding, OnDestroy, OnInit } from '#angular/core';
import { MediaObserver } from '#angular/flex-layout';
import { Subscription } from 'rxjs';
export class MediaQueryClassBaseComponent implements OnInit, OnDestroy {
#HostBinding('class.xl') private xl: boolean;
#HostBinding('class.lg') private lg: boolean;
#HostBinding('class.md') private md: boolean;
#HostBinding('class.sm') private sm: boolean;
#HostBinding('class.xs') private xs: boolean;
private mediaObserverSubscription: Subscription | undefined = undefined;
constructor(protected readonly mediaObserver: MediaObserver) {}
ngOnInit(): void {
if (this.mediaObserverSubscription)
return;
this.mediaObserverSubscription = this.mediaObserver.media$.subscribe(x => {
this.xl = x.mqAlias == 'xl';
this.lg = x.mqAlias == 'lg';
this.md = x.mqAlias == 'md';
this.sm = x.mqAlias == 'sm';
this.xs = x.mqAlias == 'xs';
});
}
ngOnDestroy(): void {
if (!this.mediaObserverSubscription)
return;
this.mediaObserverSubscription.unsubscribe();
this.mediaObserverSubscription = undefined;
}
}
If you inherit (extend) your component from this class, the host element of your component will have a class added to it with the media query alias.
For example...
<app-search-bar class="some-class" _nghost-c5 ...>
...will become...
<app-search-bar class="some-class lg" _nghost-c5 ...>
Note the added media query alias 'lg' which will change according to the window size. This makes it easy to add responsive styles to each media size by
wrapping the size-specific styles in your component's SCSS files.
Like this...
:host-context(.sm, .md) { // styles specific to both sm and md media sizes
.header {
padding: 6px;
width: 420px;
}
}
:host-context(.lg, .xl) { // styles specific to both lg and xl media sizes
.header {
padding: 10px;
width: 640px;
}
}
I've put the full file on my gist https://gist.github.com/NickStrupat/b80bda11daeea06a1a67d2d9c41d4993
Check here, it's forked solution found somewhere on internet with my customization, but it works for me (not only hiding element with display:none, but removing if from DOM - like *ngIf works)
import {
Input,
Directive,
TemplateRef,
ViewContainerRef,
OnDestroy,
ChangeDetectorRef
} from '#angular/core';
/**
* How to use this directive?
*
* ```
*
* Div element will exist only when media query matches, and created/destroyed when the viewport size changes.
*
* ```
*/
#Directive({
selector: '[mqIf]'
})
export class MqIfDirective implements OnDestroy {
private prevCondition: boolean = null;
i = 0;
private mql: MediaQueryList;
private mqlListener: (mql: MediaQueryList) => void; // reference kept for cleaning up in ngOnDestroy()
constructor(private viewContainer: ViewContainerRef,
private templateRef: TemplateRef,
private ref: ChangeDetectorRef) {
}
/**
* Called whenever the media query input value changes.
*/
#Input()
set mqIf(newMediaQuery: string) {
if (!this.mql) {
this.mql = window.matchMedia(newMediaQuery);
/* Register for future events */
this.mqlListener = (mq) => {
this.onMediaMatchChange(mq.matches);
};
this.mql.addListener(this.mqlListener);
}
this.onMediaMatchChange(this.mql.matches);
}
ngOnDestroy() {
this.mql.removeListener(this.mqlListener);
this.mql = this.mqlListener = null;
}
private onMediaMatchChange(matches: boolean) {
if (matches && !this.prevCondition) {
this.prevCondition = true;
this.viewContainer.createEmbeddedView(this.templateRef);
} else if (!matches && this.prevCondition) {
this.prevCondition = false;
this.viewContainer.clear();
}
/**
* Infinitive loop when we fire detectChanges during initialization
* (first run on that func)
*/
if (this.i > 0) {
this.ref.detectChanges();
}
else
this.i++;
}
}
See here
.container{
background-color : powderblue;
height : 50px;
width : 100%
}
#media (max-width: 400px){
.container{
display: flex;
}
}
<div class="container"></div>

StencilJS Component Styles

What is the proper way to use the styles option/property of the ComponentDecorator? Using the styles property with the default my-name component from the repository stencil-component-starter doesn't seem to affect the styles of the respective component nor generate something like a <style> tag in the <head>. How is styles intended to work? Or has it not been implemented yet? If the goal is to avoid having a separate CSS asset that needs to be loaded, but provide styles for the component, would styles be the right choice or is there another property such as host would need to be used?
Below is a sample component generated from the stencil-component-starter]1 with stylesUrl #Component property replaced with a styles property and setting font-size property. No errors are generated during dev or build tasks.
import { Component, Prop } from '#stencil/core';
#Component({
tag: 'my-name',
styles: `my-name { font-size: 24px; }`
})
export class MyName {
#Prop() first: string;
render() {
return (
<div>
Hello, my name is {this.first}
</div>
);
}
}
ComponentDecorator is defined as:
export interface ComponentOptions {
tag: string;
styleUrl?: string;
styleUrls?: string[] | ModeStyles;
styles?: string;
shadow?: boolean;
host?: HostMeta;
assetsDir?: string;
assetsDirs?: string[];
}
Thank you for any help you can provide!
I just tried with latest version 0.0.6-22, and it seems to work completely now.
While compiling, it will tell you if your styles contents is valid or not (mainly looking for valid selectors).
Here is a working example (with a simple string):
import { Component, Prop } from "#stencil/core";
#Component({
tag: "inline-css-example",
styles: 'inline-css-example { font-size: 24px; }'
})
export class InlineCSSExampleComponent {
#Prop() first: string;
render() {
return <div>Hello, my name is {this.first}</div>;
}
}
This one works too, with ES6 Template Strings (just showing multiline):
import { Component, Prop } from "#stencil/core";
#Component({
tag: "inline-templatestring-css-example",
styles: `
inline-templatestring-css-example {
font-size: 24px;
}
`
})
export class InlineCSSExampleComponent {
#Prop() first: string;
render() {
return <div>Hello, my name is {this.first}</div>;
}
}
(EDITed to show evolution since 0.0.6-13)

Toggle routeLink in angular 4

I'm creating a web app that will have a side menu loaded from another page in angular 4.
My menu-button with routerLink is defined in here.
<a class="menu-button"routerLink="/menu">
<div class="menu" >
<div class="bar" id="bar-1"></div>
<div class="bar" id="bar-2"></div>
<div class="bar" id="bar-3"></div>
</div>
</a>
The navbar will be visible even when the menu is open, and I was wondering if there's a way in angular 4 to toggle the routerLink from "/menu" to "/home" when the menu is open.
I just had the need to toggle between routes and the app root route. I added that capability with the following directive:
import { Directive, HostListener, Input, Self } from '#angular/core';
import { RouterLink, Router } from '#angular/router';
/**
* This directive adds the ability to toggle a route with Angular's
* {#link RouterLink} directive.
*
* ### Example:
* ```html
* <button routerLink="/my-path" routerLinkToggle></button>
* ```
* The example above creates a toggle button that navigates between
* `http://localhost/my-path` and `http://localhost`.
*/
#Directive({
selector: '[routerLinkToggle]'
})
export class RouterLinkToggleDirective {
/**
* The {#link RouterLink.onClick} method.
*/
private readonly _onClick: () => boolean;
/**
* Navigation commands.
*/
private commands: any[] = [];
constructor(#Self() private routerLink: RouterLink,
private router: Router) {
// Keep a reference to the original `routerLink.onClick` method.
this._onClick = this.routerLink.onClick;
// Replace the `routerLink.onClick` method with a dummy method.
// This is needed because the order in which directives on the
// same host element are executed is undefined. By overwriting
// routerLink's onClick method but keeping a reference to it, we
// now have control over when it will be called.
this.routerLink.onClick = () => true;
}
/**
* Set the path of the route that you want to toggle to. If no path
* is provided, the directive navigates to the root of the app.
*/
#Input()
set routerLinkToggle(commands: any[] | string) {
if (commands != null) {
this.commands = Array.isArray(commands) ? commands : [commands];
} else {
this.commands = [];
}
}
#HostListener('click')
onClick() {
if (this.router.isActive(this.routerLink.urlTree, true)) {
this.router.navigate(this.commands);
} else {
// Call the `_onClick` method within its original `routerLink` conext.
this._onClick.call(this.routerLink);
}
}
}
Not sure if answers your question,
But you may use routerLinkActive directive to check which route is active and based upon that hide or show respective links.
Check below implementation,
#Component({
selector: 'my-app',
template: `<h1>Hello {{name}}</h1>
<hr />
<a routerLink="/home" [ngClass]="{'hide': !menu.isActive}"
routerLinkActive #home="routerLinkActive" >Home</a>
<a routerLink="/menu" [ngClass]="{'hide': !home.isActive}"
routerLinkActive #menu="routerLinkActive" >Menu</a>
<hr />
<router-outlet></router-outlet>
`,
styles:[
`
.hide{
display: none;
}
`
]
})
class AppComponent { name = 'Angular'; }
Here is the Plunker!!

Resources