Simple TypeScript wrapper for creating a Web Component.
npm install @pictogrammers/elementExample Usage: Element-Hello-World
To make things easier setup the project assuming the custom element <hello-world message="Hello World!"></hello-world>.
π src/
π hello/
π world/
π world.ts
π world.html
π world.css
π jest.config.json
π package.json
π tsconfig.json
π webpack.config.js
import { Component, Prop, Part } from '@pictogrammers/element';
import template from "./world.html";
import style from './world.css';
@Component({
selector: 'hello-world',
style,
template
})
export default class HelloWorld extends HTMLElement {
@Prop() message = 'Hello World';
@Part() $message: HTMLDivElement;
render(changes) {
if (changes.message) {
this.$message.textContent = this.message;
}
}
}<div part="message">Default!</div>:host {
display: block;
}
[part=message] {
/* Style Part */
}It is recommended to use primitives for props where possible. To make this easier functions are provided to normalize values for booleans, integers, numbers, and strings.
import { Component, Prop, normalizeBoolean } from '@pictogrammers/element';
// ...
@Prop(normalizeBoolean) selected = false;Which is equivalent to...
import { Component, Prop, normalizeBoolean } from '@pictogrammers/element';
// ...
#selected = false;
@Prop()
get selected() {
return this.#selected;
}
set selected(value: string | boolean) {
this.#selected = normalizeBoolean(value);
}Note: Instead of ever using
get/setalways use therendermethod for managing changes to prevent unncessary operations.
normalizeInt- Wrapper forparseInt(`${value}`, 10).normalizeFloat- Wrapper forparseFloat(`${value}`).normalizeBoolean- Handlesbooltype including string'true'/'false'.normalizeString- Wrapper for`${value}`.
Components can create repeated lists of other components by using the forEach utility. Any updates will sync values to the component provided in the type function.
import { forEach } from '@pictogrammers/element';
import UiItem from 'ui/item';
// ... in element class
// Public
@Prop() options: any[] = [];
// Private
@Prop() #options: any[] = [];
connectedCallback() {
forEach({
container: this.$items,
items: this.options,
type: (item) => {
return UiItem;
},
create: ($item, item) => {
// after creation of $item element
},
connect: ($item, item, $items) => {
// after connectedCallback
},
disconnect: ($item, item, $items) => {
// before disconnectedCallback
},
update: ($item, item, $items) => {
// after every $item update
},
minIndex: (items) => {
return 0; // start range to monitor node changes
},
maxIndex: (items) => {
return items.length; // end range to monitor node changes
}
});
}Components can have methods for performing actions. For instance validating or resetting a form.
import { Component } from '@pictogrammers/element';
@Component({
selector: 'hello-world'
})
export default class HelloWorld extends HTMLElement {
method(arg) {
// code
}
#privateMethod(arg) {
// not accessible
}
}Starting with a simple component can allow one to extend it with more features later on. This can be done by extending components.
π src/
π hello/
π world/
π world.ts
π world.html
π world.css
π worldButton/
π worldButton.ts
π worldButton.html
π worldButton.css
import { Component, Prop, Part } from '@pictogrammers/element';
import HelloWorld from '../world/world';
import style from './worldButton.css';
import template from './worldButton.html';
@Component({
selector: 'hello-world-button',
style,
template
})
export default class HelloWorldButton extends HelloWorld {
@Part() $button: HTMLButtonElement;
renderCallback() {
this.$button.addEventListener('click', () => {
alert(this.message);
});
}
}<button part="button">
<parent /> <!-- <div>Default!</div> -->
</button>[part=button] {
border-radius: 0.25rem;
border: #ddd;
color: #222;
}To access localStorage values bind them to a class level property with a Map type.
// store:toggle
@Local('store') store = new Map([
['toggle', false]
]);
// Caches to a private property
@Local('store') #store = new Map([
['someobj', null]
]);
// somehere in your code
this.store.get('toggle');
this.store.set('toggle' true);# Build
npm run build
# View files in dist/
# Then link for use locally
npm link
# Within a local project directory
npm link @pictogrammers/elementAfter making changes run build.
npm run buildAlways run tests before submitting any updates.
npm testSome other notes about unique use cases that are handled.
Utility base classes can be defined without a config. These are rarely used, but are supported.
import { Component } from '@pictogrammers/element';
@Component()
export default class HelloOverlay extends HtmlElement {
static open() {
}
close() {
}
}See Proxy Docs. Specifically getProxyValue method.
selectComponent<T>(tag: string): TselectPart<T>(component: HTMLElement, name: string): TgetProps(tag: string): string[]
import { selectComponent, getProps } from '@pictogrammers/element';
import './world';
import HelloWorld from './world';
const HELLO_WORLD = 'hello-world';
describe('hello-world', () => {
const DEFAULT_MESSAGE = 'None';
beforeEach(() => {
var c = document.createElement(HELLO_WORLD);
document.body.appendChild(c);
});
afterEach(() => {
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
});
it('should be registered', () => {
expect(customElements.get(HELLO_WORLD)).toBeDefined();
});
it('should only expose known props', () => {
const props = getProps(HELLO_WORLD);
expect(props.length).toBe(2);
expect(props).toContain('message');
expect(props).toContain('count');
});
});