alit-element
Advanced tools
Comparing version 0.1.1 to 0.2.0
@@ -5,6 +5,21 @@ import 'reflect-metadata'; | ||
export { TemplateResult } from 'lit-html/lit-html.js'; | ||
export interface EventListenerDeclaration { | ||
eventName: string; | ||
target: string | EventTarget; | ||
handler: (event?: Event) => void; | ||
} | ||
export interface ChangeRecord { | ||
path: string; | ||
value: any; | ||
oldValue: any; | ||
} | ||
export declare type ObserveHandler = (changeRecords: ChangeRecord[]) => void; | ||
export declare class AlitElement extends LitElement { | ||
static readonly __listeners: EventListenerDeclaration[]; | ||
static readonly __observers: { | ||
[name: string]: ObserveHandler[]; | ||
}; | ||
private _$; | ||
/** | ||
* Get element with specified if in the element's shadow root | ||
* Get element with specified ID in the element's shadow root | ||
* @param id Id of element | ||
@@ -22,3 +37,3 @@ */ | ||
*/ | ||
$$All(selector: string): any; | ||
$$All(selector: string): NodeList; | ||
/** | ||
@@ -37,7 +52,4 @@ * Fires a custom event with the specified name | ||
readonly node: HTMLElement; | ||
connectedCallback(): void; | ||
_propertiesChanged(currentProps: object, changedProps: object, oldProps: object): void; | ||
} | ||
/*********************************** | ||
* Functions to support decorators | ||
***********************************/ | ||
export declare function element(name: string): (c: any) => void; | ||
export declare function property(): (prototype: any, propertyName: string) => void; |
@@ -10,4 +10,6 @@ import 'reflect-metadata'; | ||
} | ||
static get __listeners() { return []; } | ||
static get __observers() { return {}; } | ||
/** | ||
* Get element with specified if in the element's shadow root | ||
* Get element with specified ID in the element's shadow root | ||
* @param id Id of element | ||
@@ -64,29 +66,47 @@ */ | ||
} | ||
} | ||
/*********************************** | ||
* Functions to support decorators | ||
***********************************/ | ||
export function element(name) { | ||
return (c) => { | ||
if (name) { | ||
window.customElements.define(name, c); | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
const listeners = this.constructor.__listeners; | ||
for (const listener of listeners) { | ||
if (listener.eventName && listener.handler) { | ||
const target = (typeof listener.target === 'string') ? this.$$(listener.target) : listener.target; | ||
if (target && target.addEventListener) { | ||
target.addEventListener(listener.eventName, (e) => { | ||
listener.handler.call(this, e); | ||
}); | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
export function property() { | ||
return (prototype, propertyName) => { | ||
const constructor = prototype.constructor; | ||
if (!constructor.hasOwnProperty('properties')) { | ||
Object.defineProperty(constructor, 'properties', { value: {} }); | ||
} | ||
_propertiesChanged(currentProps, changedProps, oldProps) { | ||
const observers = this.constructor.__observers; | ||
const map = new Map(); | ||
for (const propName in changedProps) { | ||
const handlers = observers[propName]; | ||
if (handlers && handlers.length) { | ||
const changeRecord = { | ||
path: propName, | ||
value: changedProps[propName], | ||
oldValue: oldProps[propName] | ||
}; | ||
for (const handler of handlers) { | ||
if (!map.has(handler)) { | ||
map.set(handler, [changeRecord]); | ||
} | ||
else { | ||
map.get(handler).push(changeRecord); | ||
} | ||
} | ||
} | ||
} | ||
constructor.properties[propertyName] = { type: getType(prototype, propertyName) || String }; | ||
}; | ||
} | ||
function getType(prototype, propertyName) { | ||
if (Reflect.hasMetadata) { | ||
if (Reflect.hasMetadata('design:type', prototype, propertyName)) { | ||
return Reflect.getMetadata('design:type', prototype, propertyName); | ||
for (const handler of map.keys()) { | ||
try { | ||
handler.call(this, map.get(handler)); | ||
} | ||
catch (err) { | ||
console.warn(err); | ||
} | ||
} | ||
super._propertiesChanged(currentProps, changedProps, oldProps); | ||
} | ||
return null; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { AlitElement, TemplateResult } from '../alit-element'; | ||
import { AlitElement, TemplateResult, ChangeRecord } from '../alit-element'; | ||
export declare class AlitCard extends AlitElement { | ||
@@ -8,3 +8,9 @@ name?: string; | ||
description: string; | ||
card?: HTMLDivElement; | ||
toggleBorder(): void; | ||
randomizeAge(): void; | ||
ageChanged(records: ChangeRecord[]): void; | ||
documentClick(): void; | ||
private borderShowing; | ||
_render(): TemplateResult; | ||
} |
@@ -10,3 +10,4 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | ||
}; | ||
import { AlitElement, html, element, property } from '../alit-element'; | ||
import { AlitElement, html } from '../alit-element'; | ||
import { element, property, query, listen, observe } from '../alit-element-decorators'; | ||
let AlitCard = class AlitCard extends AlitElement { | ||
@@ -17,5 +18,21 @@ constructor() { | ||
this.description = 'This is the default description'; | ||
this.borderShowing = false; | ||
} | ||
toggleBorder() { | ||
this.borderShowing = !this.borderShowing; | ||
this.card.style.border = this.borderShowing ? '2px solid' : 'none'; | ||
} | ||
randomizeAge() { | ||
this.age = Math.round(Math.random() * 60 + 20); | ||
this.description = `This guy is aged ${this.age}`; | ||
} | ||
ageChanged(records) { | ||
for (const r of records) { | ||
console.log(`${r.path} changed from '${r.oldValue}' to '${r.value}'`); | ||
} | ||
} | ||
documentClick() { | ||
console.log('document clicked'); | ||
} | ||
_render() { | ||
console.log(this.constructor.properties); | ||
return html ` | ||
@@ -32,3 +49,2 @@ <style> | ||
display: inline-block; | ||
box-shadow: 0px 0px 11px 0px rgba(0, 0, 0, 0.3); | ||
} | ||
@@ -56,2 +72,6 @@ | ||
<p>${this.description}</p> | ||
<p> | ||
<button id="toggleButton">Toggle border</button> | ||
<button id="randomizeButton">Randomize age</button> | ||
</p> | ||
</div> | ||
@@ -81,2 +101,30 @@ `; | ||
], AlitCard.prototype, "description", void 0); | ||
__decorate([ | ||
query('.card'), | ||
__metadata("design:type", HTMLDivElement) | ||
], AlitCard.prototype, "card", void 0); | ||
__decorate([ | ||
listen('click', '#toggleButton'), | ||
__metadata("design:type", Function), | ||
__metadata("design:paramtypes", []), | ||
__metadata("design:returntype", void 0) | ||
], AlitCard.prototype, "toggleBorder", null); | ||
__decorate([ | ||
listen('click', '#randomizeButton'), | ||
__metadata("design:type", Function), | ||
__metadata("design:paramtypes", []), | ||
__metadata("design:returntype", void 0) | ||
], AlitCard.prototype, "randomizeAge", null); | ||
__decorate([ | ||
observe('age', 'description'), | ||
__metadata("design:type", Function), | ||
__metadata("design:paramtypes", [Array]), | ||
__metadata("design:returntype", void 0) | ||
], AlitCard.prototype, "ageChanged", null); | ||
__decorate([ | ||
listen('click', document), | ||
__metadata("design:type", Function), | ||
__metadata("design:paramtypes", []), | ||
__metadata("design:returntype", void 0) | ||
], AlitCard.prototype, "documentClick", null); | ||
AlitCard = __decorate([ | ||
@@ -86,4 +134,1 @@ element('alit-card') | ||
export { AlitCard }; | ||
// export function as<T extends GuildElement>(node: HTMLElement): T { | ||
// return (node as any) as T; | ||
// } |
{ | ||
"name": "alit-element", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"description": "A simple wrapper for lit-element with some enhanced functions", | ||
@@ -5,0 +5,0 @@ "main": "dist/alit-element.js", |
148
README.md
@@ -1,2 +0,146 @@ | ||
# alit-element | ||
A simple extension for lit-element | ||
# AlitElement | ||
A simple base class that extends [lit-element](https://github.com/Polymer/lit-element) with some utility functions. | ||
It also defines decorators, similar to [polymer-decorators](https://github.com/Polymer/polymer-decorators#observetargets-string), which makes development of web components in typescript super easy. | ||
## Sample Code | ||
Here's some sample code that showcases decorators. ([full code](https://github.com/pshihn/alit-element/blob/master/src/example/card.ts)) | ||
```javascript | ||
@element('alit-card') | ||
export class AlitCard extends AlitElement { | ||
@property() name?: string; | ||
@property() age: number = 30; | ||
@property() image?: string; | ||
@property() description: string = 'This is the default description'; | ||
@query('.card') | ||
card?: HTMLDivElement; | ||
@listen('click', '#toggleButton') | ||
toggleBorder() { | ||
this.borderShowing = !this.borderShowing; | ||
this.card!.style.border = this.borderShowing ? '2px solid' : 'none'; | ||
} | ||
@observe('age', 'description') | ||
ageChanged(records: ChangeRecord[]) { | ||
for (const r of records) { | ||
console.log(`${r.path} changed from '${r.oldValue}' to '${r.value}'`); | ||
} | ||
} | ||
_render(): TemplateResult { | ||
return html` | ||
... | ||
... | ||
<div class="card"> | ||
... | ||
</div> | ||
... | ||
`; | ||
} | ||
``` | ||
## Methods | ||
#### $(id: string): HTMLElement | ||
Get element with specified ID in the element's shadow root. | ||
```javascript | ||
const button = this.$('toggleButton'); | ||
``` | ||
#### $$(selector: string): HTMLElement | ||
Find first element macthing the slector in the element's shadow root. | ||
```javascript | ||
const card = this.$$('.card'); | ||
``` | ||
#### $$All(selector: string): NodeList | ||
Find all elements matching the selector in the element's shadow root. | ||
```javascript | ||
const allCards = this.$$All('.card'); | ||
``` | ||
#### fireEvent(name: string, detail?: any, bubbles: boolean = true, composed: boolean = true) | ||
Utility method to fire custom events | ||
```javascript | ||
this.fireEvent('selected'); | ||
this.fireEvent('selected', {selection: this.currentSelection}); | ||
``` | ||
## Decorators | ||
#### @element(tagname?: string) | ||
Defines a custom element with the associated class | ||
```javascript | ||
@element('hello-world') | ||
export class HelloWorld extends AlitElement { | ||
_render() { | ||
return html` | ||
<div>Hello World</div> | ||
`; | ||
} | ||
} | ||
``` | ||
#### @property() | ||
Declared a property to be used by LitElement. | ||
The type is infered using reflected metadata. | ||
```javascript | ||
@element('hello-world') | ||
export class HelloWorld extends AlitElement { | ||
@property() name?: string; | ||
@property() job?: string; | ||
@property() age: number = 30; | ||
} | ||
``` | ||
#### @query(selector: string) | ||
Replace this property with a getter that returns the element matching the specified selector. | ||
```javascript | ||
@query('.card') | ||
card?: HTMLDivElement; | ||
``` | ||
#### @queryAll(selector: string) | ||
Replace this property with a getter that returns the NodeList of all elements matching the specified selector. | ||
```javascript | ||
@queryAll('my-widget') | ||
widgets: NodeListOf<MyWidgetElement>; | ||
``` | ||
#### @listen(eventName: string, target: string | EventTarget) | ||
Add an event listener for `eventName` on `target`. | ||
`target` can be an object reference, or the selector string to find the element in the shadow root. | ||
```javascript | ||
@listen('click', '#toggleButton') | ||
toggleBorder() { | ||
this.borderShowing = !this.borderShowing; | ||
} | ||
``` | ||
```javascript | ||
@listen('click', document) | ||
documentClick() { | ||
console.log('document clicked'); | ||
} | ||
``` | ||
#### observe(...properties: string[]) | ||
Add observers to the specified set of properties. This does not support children of properties or wildcards. | ||
```javascript | ||
@observe('age', 'description') | ||
ageChanged(records: ChangeRecord[]) { | ||
// do stuff when age or description changes | ||
} | ||
``` | ||
A `ChangeRecord` is defined as follows: | ||
```javascript | ||
interface ChangeRecord { | ||
path: string; // property name | ||
value: any; | ||
oldValue: any; | ||
} | ||
``` |
@@ -6,7 +6,23 @@ import 'reflect-metadata'; | ||
export interface EventListenerDeclaration { | ||
eventName: string; | ||
target: string | EventTarget; | ||
handler: (event?: Event) => void; | ||
} | ||
export interface ChangeRecord { | ||
path: string; | ||
value: any; | ||
oldValue: any; | ||
} | ||
export type ObserveHandler = (changeRecords: ChangeRecord[]) => void; | ||
export class AlitElement extends LitElement { | ||
static get __listeners(): EventListenerDeclaration[] { return []; } | ||
static get __observers(): { [name: string]: ObserveHandler[] } { return {}; } | ||
private _$: { [id: string]: HTMLElement } = {}; | ||
/** | ||
* Get element with specified if in the element's shadow root | ||
* Get element with specified ID in the element's shadow root | ||
* @param id Id of element | ||
@@ -36,3 +52,3 @@ */ | ||
*/ | ||
$$All(selector: string) { | ||
$$All(selector: string): NodeList { | ||
return this.shadowRoot.querySelectorAll(selector); | ||
@@ -68,33 +84,47 @@ } | ||
} | ||
} | ||
/*********************************** | ||
* Functions to support decorators | ||
***********************************/ | ||
export function element(name: string) { | ||
return (c: any) => { | ||
if (name) { | ||
window.customElements.define(name, c); | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
const listeners = (<typeof AlitElement>this.constructor).__listeners; | ||
for (const listener of listeners) { | ||
if (listener.eventName && listener.handler) { | ||
const target = (typeof listener.target === 'string') ? this.$$(listener.target) : listener.target; | ||
if (target && target.addEventListener) { | ||
target.addEventListener(listener.eventName, (e) => { | ||
listener.handler.call(this, e); | ||
}); | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
} | ||
export function property() { | ||
return (prototype: any, propertyName: string) => { | ||
const constructor = prototype.constructor; | ||
if (!constructor.hasOwnProperty('properties')) { | ||
Object.defineProperty(constructor, 'properties', { value: {} }); | ||
_propertiesChanged(currentProps: object, changedProps: object, oldProps: object): void { | ||
const observers = (<typeof AlitElement>this.constructor).__observers; | ||
const map = new Map<ObserveHandler, ChangeRecord[]>(); | ||
for (const propName in changedProps) { | ||
const handlers = observers[propName]; | ||
if (handlers && handlers.length) { | ||
const changeRecord: ChangeRecord = { | ||
path: propName, | ||
value: changedProps[propName], | ||
oldValue: oldProps[propName] | ||
}; | ||
for (const handler of handlers) { | ||
if (!map.has(handler)) { | ||
map.set(handler, [changeRecord]); | ||
} else { | ||
map.get(handler)!.push(changeRecord); | ||
} | ||
} | ||
} | ||
} | ||
constructor.properties[propertyName] = { type: getType(prototype, propertyName) || String }; | ||
}; | ||
} | ||
function getType(prototype: any, propertyName: string): any { | ||
if (Reflect.hasMetadata) { | ||
if (Reflect.hasMetadata('design:type', prototype, propertyName)) { | ||
return Reflect.getMetadata('design:type', prototype, propertyName); | ||
for (const handler of map.keys()) { | ||
try { | ||
handler.call(this, map.get(handler)); | ||
} catch (err) { | ||
console.warn(err); | ||
} | ||
} | ||
super._propertiesChanged(currentProps, changedProps, oldProps); | ||
} | ||
return null; | ||
} |
@@ -1,2 +0,3 @@ | ||
import { AlitElement, html, TemplateResult, element, property } from '../alit-element'; | ||
import { AlitElement, html, TemplateResult, ChangeRecord } from '../alit-element'; | ||
import { element, property, query, listen, observe } from '../alit-element-decorators'; | ||
@@ -11,5 +12,32 @@ @element('alit-card') | ||
@query('.card') | ||
card?: HTMLDivElement; | ||
@listen('click', '#toggleButton') | ||
toggleBorder() { | ||
this.borderShowing = !this.borderShowing; | ||
this.card!.style.border = this.borderShowing ? '2px solid' : 'none'; | ||
} | ||
@listen('click', '#randomizeButton') | ||
randomizeAge() { | ||
this.age = Math.round(Math.random() * 60 + 20); | ||
this.description = `This guy is aged ${this.age}`; | ||
} | ||
@observe('age', 'description') | ||
ageChanged(records: ChangeRecord[]) { | ||
for (const r of records) { | ||
console.log(`${r.path} changed from '${r.oldValue}' to '${r.value}'`); | ||
} | ||
} | ||
@listen('click', document) | ||
documentClick() { | ||
console.log('document clicked'); | ||
} | ||
private borderShowing = false; | ||
_render(): TemplateResult { | ||
console.log((this.constructor as any).properties); | ||
return html` | ||
@@ -26,3 +54,2 @@ <style> | ||
display: inline-block; | ||
box-shadow: 0px 0px 11px 0px rgba(0, 0, 0, 0.3); | ||
} | ||
@@ -50,9 +77,9 @@ | ||
<p>${this.description}</p> | ||
<p> | ||
<button id="toggleButton">Toggle border</button> | ||
<button id="randomizeButton">Randomize age</button> | ||
</p> | ||
</div> | ||
`; | ||
} | ||
} | ||
// export function as<T extends GuildElement>(node: HTMLElement): T { | ||
// return (node as any) as T; | ||
// } | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32010
15
807
147