0.0.58 • Published 9 months ago

@pictogrammers/element v0.0.58

Weekly downloads
-
License
MIT
Repository
github
Last release
9 months ago

Element

Simple TypeScript wrapper for creating a Web Component.

npm install @pictogrammers/element

Example Usage: Element-Hello-World

Basics

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

Class (world.ts)

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;
    }
  }
}

Template (world.html)

<div part="message">Default!</div>

CSS Styles (world.css)

:host {
  display: block;
}
[part=message] {
  /* Style Part */
}

Normalizing Props

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 / set always use the render method for managing changes to prevent unncessary operations.

  • normalizeInt - Wrapper for parseInt(`${value}`, 10).
  • normalizeFloat - Wrapper for parseFloat(`${value}`).
  • normalizeBoolean - Handles bool type including string 'true' / 'false'.
  • normalizeString - Wrapper for `${value}`.

Template Loops

Components can create repeated lists of other components by using the forEach utility. A unique key property is required in each item of the items array (defaults to a uuid if not provided). Any updates will sync values to the component provided in the type function.

Note: item in the callbacks is readonly and contains index.

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
      },
    });
  }

Advanced

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

TypeScript (worldButton.ts)

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);
    });
  }
}

Template (worldButton.html)

<button part="button">
  <parent/> <!-- <div>Default!</div> -->
</button>

CSS Styles (worldButton.css)

[part=button] {
  border-radius: 0.25rem;
  border: #ddd;
  color: #222;
}

@Local(key: string)

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);

Development

# Build
npm run build
# View files in dist/
# Then link for use locally
npm link
# Within a local project directory
npm link @pictogrammers/element

Utility Base Class

Some other notes about unique use cases that are handled.

Optional Component() Config

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() {

  }
}

Jest Utils

  • selectComponent<T>(tag: string): T
  • selectPart<T>(component: HTMLElement, name: string): T
  • getProps(tag: string): string[]

Basic

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');
  });

});
0.0.41

10 months ago

0.0.42

10 months ago

0.0.43

10 months ago

0.0.44

10 months ago

0.0.45

10 months ago

0.0.46

10 months ago

0.0.47

10 months ago

0.0.51

10 months ago

0.0.52

10 months ago

0.0.53

10 months ago

0.0.54

10 months ago

0.0.56

9 months ago

0.0.57

9 months ago

0.0.58

9 months ago

0.0.50

10 months ago

0.0.48

10 months ago

0.0.49

10 months ago

0.0.40

2 years ago

0.0.38

2 years ago

0.0.39

2 years ago

0.0.37

2 years ago

0.0.36

2 years ago

0.0.35

2 years ago

0.0.34

2 years ago

0.0.31

2 years ago

0.0.32

2 years ago

0.0.33

2 years ago

0.0.30

2 years ago

0.0.28

2 years ago

0.0.29

2 years ago

0.0.27

2 years ago

0.0.25

2 years ago

0.0.26

2 years ago

0.0.24

2 years ago

0.0.23

4 years ago

0.0.22

4 years ago

0.0.21

4 years ago

0.0.20

4 years ago