@w-lfpup/wctk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- name: Build and Test
1
+ name: Builds
2
2
 
3
3
  on:
4
4
  push:
@@ -10,7 +10,7 @@ jobs:
10
10
  build_and_test:
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
- - uses: actions/checkout@v5
13
+ - uses: actions/checkout@v6
14
14
  - uses: actions/setup-node@v4
15
15
  - name: Install
16
16
  run: npm ci
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Wctk-JS
2
2
 
3
- A web component tool kit.
3
+ A web component tool kit without dependencies.
4
+
5
+ [![Builds](https://github.com/w-lfpup/wctk-js/actions/workflows/builds.yml/badge.svg)](https://github.com/w-lfpup/wctk-js/actions/workflows/builds.yml)
4
6
 
5
7
  ## About
6
8
 
@@ -14,11 +16,9 @@ A half-dozen controllers help developers:
14
16
  - [query](./docs/query_selector.md) the shadow dom
15
17
  - [bind](./docs/bind.md) functions to elements
16
18
 
17
- All features are compositional and built to support [declarative shadow dom](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#declaratively_with_html) SSR.
18
-
19
- There are no base classes or decorators.
19
+ All features are compositional and built to support [declarative shadow dom](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#declaratively_with_html) and SSR.
20
20
 
21
- `Wctk-js` even supports `#private` methods as callbacks, fully encapsulating a web component's API (aside from required [lifecycle methods](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks)).
21
+ There are no base classes, decorators, or mixins.
22
22
 
23
23
  ## Install
24
24
 
package/dist/events.d.ts CHANGED
@@ -1,17 +1,26 @@
1
- export type Callbacks = Array<[string, EventListenerOrEventListenerObject]>;
2
- export interface EventsInterface {
3
- connect(): void;
4
- disconnect(): void;
1
+ interface GenericEventListener<E> {
2
+ (evt: E): void;
5
3
  }
6
- export interface EventElementInterface {
7
- addEventListener: EventTarget["addEventListener"];
8
- removeEventListener: EventTarget["removeEventListener"];
4
+ interface GenericEventListenerObject<E> {
5
+ handleEvent(object: E): void;
6
+ }
7
+ type GenericCallbacks<E> = GenericEventListener<E> | GenericEventListenerObject<E>;
8
+ type EventMap = Partial<{
9
+ [Property in keyof GlobalEventHandlersEventMap]: GenericCallbacks<GlobalEventHandlersEventMap[Property]>;
10
+ }>;
11
+ interface EventElementInterface {
12
+ addEventListener: Element["addEventListener"];
13
+ removeEventListener: Element["removeEventListener"];
9
14
  }
10
15
  export interface EventParamsInterface {
11
- host: EventElementInterface;
16
+ callbacks: EventMap;
12
17
  connected?: boolean;
18
+ host: EventElementInterface;
13
19
  target?: EventElementInterface;
14
- callbacks: Callbacks;
20
+ }
21
+ export interface EventsInterface {
22
+ connect(): void;
23
+ disconnect(): void;
15
24
  }
16
25
  export declare class Events implements EventsInterface {
17
26
  #private;
@@ -19,3 +28,4 @@ export declare class Events implements EventsInterface {
19
28
  connect(): void;
20
29
  disconnect(): void;
21
30
  }
31
+ export {};
package/dist/events.js CHANGED
@@ -28,12 +28,15 @@ export class Events {
28
28
  }
29
29
  function getBoundCallbacks(host, callbacks) {
30
30
  let boundCallbacks = [];
31
- for (let [name, callback] of callbacks) {
31
+ for (let [name, callback] of Object.entries(callbacks)) {
32
32
  if (callback instanceof Function &&
33
33
  !callback.hasOwnProperty("prototype")) {
34
34
  callback = callback.bind(host);
35
35
  }
36
- boundCallbacks.push([name, callback]);
36
+ boundCallbacks.push([
37
+ name,
38
+ callback,
39
+ ]);
37
40
  }
38
41
  return boundCallbacks;
39
42
  }
package/dist/wc.js CHANGED
@@ -6,18 +6,20 @@ export class Wc {
6
6
  #internals;
7
7
  #shadowRoot;
8
8
  constructor(params) {
9
- let { host, shadowRootInit, adoptedStyleSheets, formValue, formState } = params;
9
+ let { adoptedStyleSheets, host, formState, formValue, shadowRootInit } = params;
10
10
  this.#internals = host.attachInternals();
11
11
  let { shadowRoot } = this.#internals;
12
- if (!shadowRoot) {
12
+ if (shadowRoot) {
13
+ this.#shadowRoot = shadowRoot;
14
+ }
15
+ else {
13
16
  this.#declarative = false;
14
- shadowRoot = host.attachShadow(shadowRootInit ?? shadowRootInitFallback);
17
+ this.#shadowRoot = host.attachShadow(shadowRootInit ?? shadowRootInitFallback);
15
18
  }
16
- this.#shadowRoot = shadowRoot;
17
- if (formValue)
18
- this.setFormValue(formValue, formState);
19
19
  if (adoptedStyleSheets)
20
20
  this.adoptedStyleSheets = adoptedStyleSheets;
21
+ if (formValue)
22
+ this.setFormValue(formValue, formState);
21
23
  }
22
24
  get declarative() {
23
25
  return this.#declarative;
package/docs/events.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Events Controller
2
2
 
3
- Add methods as event listener callbacks.
3
+ Add event listeners to web components.
4
4
 
5
5
  ## How to use
6
6
 
@@ -12,14 +12,14 @@ An Events `params` object has four properties:
12
12
 
13
13
  ```ts
14
14
  interface EventParams {
15
- host: Node;
16
- callbacks: Array<[string, EventListener]>;
15
+ callbacks: Record<string, EventListenerOrEventListenerObject>;
17
16
  connected?: boolean;
17
+ host: Node;
18
18
  target?: Node;
19
19
  }
20
20
  ```
21
21
 
22
- The `Events` controller binds a set of `callbacks` to a `host`.
22
+ The `Events` controller binds a record of `callbacks` to a `host`.
23
23
 
24
24
  Afterwards, the `Events` controller adds the callbacks as event listeners on a `target` node.
25
25
 
@@ -36,20 +36,21 @@ import { Events, Wc } from "wctk";
36
36
 
37
37
  class MyElement extends HTMLElement {
38
38
  #wc = new Wc({ this: host });
39
+
39
40
  #ec = new Events({
40
41
  host: this,
41
42
  target: this.#wc.shadowRoot,
42
- callbacks: [
43
- ["click", this.#onClick],
44
- ["keydown", this.#onKeyDown],
45
- ],
43
+ callbacks: {
44
+ click: this.#onClick,
45
+ keydown: this.#onKeyDown,
46
+ },
46
47
  });
47
48
 
48
49
  #onClick(e: PointerEvent) {
49
50
  // do something with pointer events here!
50
51
  }
51
52
 
52
- #onKeyDown(e: KeyEvent) {
53
+ #onKeyDown(e: KeyboardEvent) {
53
54
  // do something with key events here!
54
55
  }
55
56
 
@@ -78,10 +79,10 @@ class MyElement extends HTMLElement {
78
79
  host: this,
79
80
  target: this.#wc.shadowRoot,
80
81
  connected: true,
81
- callbacks: [
82
- ["click", this.#onClick],
83
- ["keydown", this.#onKeyDown],
84
- ],
82
+ callbacks: {
83
+ click: this.#onClick,
84
+ keydown: this.#onKeyDown,
85
+ },
85
86
  });
86
87
 
87
88
  #onClick(e: PointerEvent) {
@@ -21,6 +21,7 @@ import { QuerySelector } from "wctk";
21
21
 
22
22
  class MyElement extends HTMLElement {
23
23
  #wc = new Wc({ host: this });
24
+
24
25
  #qc = new QuerySelector({
25
26
  parent: this.#wc.shadowRoot,
26
27
  });
package/docs/wc.md CHANGED
@@ -4,9 +4,7 @@ Build a web component.
4
4
 
5
5
  ## How to use
6
6
 
7
- Add a `Wc` controller to a custom element.
8
-
9
- One line is all it takes.
7
+ Add a `Wc` controller to a custom element with only one line
10
8
 
11
9
  ```ts
12
10
  import { Wc } from "wctk";
@@ -18,7 +16,7 @@ class MyElement extends HTMLElement {
18
16
 
19
17
  ## Adopted stylesheets and form values
20
18
 
21
- The `Wc` controller is also a facade for core web componet APIs like adopted stylesheets and form values.
19
+ The `Wc` controller is also a facade for fast access to core web componet APIs like adopted stylesheets and form values.
22
20
 
23
21
  ```ts
24
22
  class MyElement extends HTMLElement {
@@ -1,17 +1,19 @@
1
- import { Wc, Events } from "wctk";
2
1
  /*
3
2
  Custom Element with state and interactivity.
4
3
  */
4
+ import { Wc, Events } from "wctk";
5
5
  class Counter extends HTMLElement {
6
6
  #wc = new Wc({ host: this });
7
7
  #ev = new Events({
8
8
  host: this,
9
9
  target: this.#wc.shadowRoot,
10
10
  connected: true,
11
- callbacks: [["click", this.#clickHandler]],
11
+ callbacks: {
12
+ click: this.#onClick,
13
+ },
12
14
  });
13
15
  #state = getStateFromDOM(this.#wc.shadowRoot);
14
- #clickHandler(e) {
16
+ #onClick(e) {
15
17
  if (!this.#state)
16
18
  return;
17
19
  let increment = getIncrement(e);
@@ -1,3 +1,7 @@
1
+ /*
2
+ Custom Element with state and interactivity.
3
+ */
4
+
1
5
  import { Wc, Events } from "wctk";
2
6
 
3
7
  interface State {
@@ -5,9 +9,6 @@ interface State {
5
9
  count: number;
6
10
  }
7
11
 
8
- /*
9
- Custom Element with state and interactivity.
10
- */
11
12
  class Counter extends HTMLElement {
12
13
  #wc = new Wc({ host: this });
13
14
 
@@ -15,12 +16,14 @@ class Counter extends HTMLElement {
15
16
  host: this,
16
17
  target: this.#wc.shadowRoot,
17
18
  connected: true,
18
- callbacks: [["click", this.#clickHandler]],
19
+ callbacks: {
20
+ click: this.#onClick,
21
+ },
19
22
  });
20
23
 
21
24
  #state?: State = getStateFromDOM(this.#wc.shadowRoot);
22
25
 
23
- #clickHandler(e: Event) {
26
+ #onClick(e: PointerEvent) {
24
27
  if (!this.#state) return;
25
28
 
26
29
  let increment = getIncrement(e);
@@ -31,7 +34,7 @@ class Counter extends HTMLElement {
31
34
  }
32
35
  }
33
36
 
34
- function getStateFromDOM(shadowRoot: ShadowRoot) {
37
+ function getStateFromDOM(shadowRoot: ShadowRoot): State | undefined {
35
38
  let slot = shadowRoot.querySelector("slot");
36
39
  if (slot)
37
40
  for (let el of slot.assignedNodes()) {
@@ -1,7 +1,7 @@
1
- import { Wc, Events } from "wctk";
2
1
  /*
3
2
  Form associated custom element.
4
3
  */
4
+ import { Wc, Events } from "wctk";
5
5
  export class TextInput extends HTMLElement {
6
6
  static formAssociated = true;
7
7
  #wc = new Wc({ host: this });
@@ -9,7 +9,9 @@ export class TextInput extends HTMLElement {
9
9
  host: this,
10
10
  target: this.#wc.shadowRoot,
11
11
  connected: true,
12
- callbacks: [["change", this.#changeHandler]],
12
+ callbacks: {
13
+ change: this.#changeHandler,
14
+ },
13
15
  });
14
16
  #changeHandler(event) {
15
17
  let { target } = event;
@@ -1,8 +1,9 @@
1
- import { Wc, Events } from "wctk";
2
-
3
1
  /*
4
2
  Form associated custom element.
5
3
  */
4
+
5
+ import { Wc, Events } from "wctk";
6
+
6
7
  export class TextInput extends HTMLElement {
7
8
  static formAssociated = true;
8
9
 
@@ -11,10 +12,12 @@ export class TextInput extends HTMLElement {
11
12
  host: this,
12
13
  target: this.#wc.shadowRoot,
13
14
  connected: true,
14
- callbacks: [["change", this.#changeHandler]],
15
+ callbacks: {
16
+ change: this.#changeHandler,
17
+ },
15
18
  });
16
19
 
17
- #changeHandler(event: Event) {
20
+ #changeHandler(event: Event): void {
18
21
  let { target } = event;
19
22
  if (target instanceof HTMLInputElement)
20
23
  this.#wc.setFormValue(target.value);
@@ -1,8 +1,23 @@
1
1
  import { Stopwatch } from "./stopwatch.js";
2
2
  customElements.define("stopwatch-wc", Stopwatch);
3
3
  const stopwatch = document.querySelector("stopwatch-wc");
4
+ let receipt;
5
+ function draw(timestamp) {
6
+ if (!stopwatch)
7
+ return;
8
+ receipt = requestAnimationFrame(draw);
9
+ stopwatch.update(timestamp);
10
+ }
11
+ function start() {
12
+ if (!stopwatch)
13
+ return;
14
+ requestAnimationFrame(draw);
15
+ }
16
+ function pause() {
17
+ cancelAnimationFrame(receipt);
18
+ }
4
19
  document.addEventListener("click", function (e) {
5
20
  if (stopwatch && e.target instanceof HTMLButtonElement) {
6
- e.target.hasAttribute("start") ? stopwatch.start() : stopwatch.pause();
21
+ e.target.hasAttribute("start") ? start() : pause();
7
22
  }
8
23
  });
@@ -1,11 +1,27 @@
1
1
  import { Stopwatch } from "./stopwatch.js";
2
2
 
3
3
  customElements.define("stopwatch-wc", Stopwatch);
4
-
5
4
  const stopwatch = document.querySelector<Stopwatch>("stopwatch-wc");
6
5
 
6
+ let receipt: number;
7
+ function draw(timestamp: DOMHighResTimeStamp) {
8
+ if (!stopwatch) return;
9
+
10
+ receipt = requestAnimationFrame(draw);
11
+ stopwatch.update(timestamp);
12
+ }
13
+
14
+ function start() {
15
+ if (!stopwatch) return;
16
+ requestAnimationFrame(draw);
17
+ }
18
+
19
+ function pause() {
20
+ cancelAnimationFrame(receipt);
21
+ }
22
+
7
23
  document.addEventListener("click", function (e: Event) {
8
24
  if (stopwatch && e.target instanceof HTMLButtonElement) {
9
- e.target.hasAttribute("start") ? stopwatch.start() : stopwatch.pause();
25
+ e.target.hasAttribute("start") ? start() : pause();
10
26
  }
11
27
  });
@@ -1,35 +1,23 @@
1
- import { Wc, Microtask } from "wctk";
2
1
  /*
3
2
  Custom Element with performant and "asynchronous" renders
4
3
  on the microtask queue.
5
4
  */
6
- export class Stopwatch extends HTMLElement {
5
+ import { Wc, Microtask } from "wctk";
6
+ class Stopwatch extends HTMLElement {
7
7
  #wc = new Wc({ host: this });
8
8
  #rc = new Microtask({ host: this, callback: this.#render });
9
- #boundUpdate = this.#update.bind(this);
10
9
  #state = getStateFromShadowDOM(this.#wc.shadowRoot);
11
- #render() {
12
- if (this.#state)
13
- this.#state.el.textContent = this.#state.count.toFixed(2);
14
- }
15
- #update(timestamp) {
16
- if (!this.#state)
10
+ update(timestamp) {
11
+ if (!this.#state || timestamp < this.#state.prevTimestamp)
17
12
  return;
18
- this.#state.receipt = requestAnimationFrame(this.#boundUpdate);
19
13
  this.#state.count += (timestamp - this.#state.prevTimestamp) * 0.001;
20
14
  this.#state.prevTimestamp = timestamp;
21
15
  // push render to microtask queue
22
16
  this.#rc.queue();
23
17
  }
24
- start() {
25
- if (!this.#state || this.#state?.receipt)
26
- return;
27
- this.#state.receipt = requestAnimationFrame(this.#boundUpdate);
28
- this.#state.prevTimestamp = performance.now();
29
- }
30
- pause() {
31
- if (this.#state && this.#state.receipt)
32
- this.#state.receipt = cancelAnimationFrame(this.#state.receipt);
18
+ #render() {
19
+ if (this.#state)
20
+ this.#state.el.textContent = this.#state.count.toFixed(2);
33
21
  }
34
22
  }
35
23
  function getStateFromShadowDOM(shadowRoot) {
@@ -43,3 +31,4 @@ function getStateFromShadowDOM(shadowRoot) {
43
31
  };
44
32
  }
45
33
  }
34
+ export { Stopwatch };
@@ -1,4 +1,9 @@
1
- import { Bind, Wc, Microtask } from "wctk";
1
+ /*
2
+ Custom Element with performant and "asynchronous" renders
3
+ on the microtask queue.
4
+ */
5
+
6
+ import { Wc, Microtask } from "wctk";
2
7
 
3
8
  interface State {
4
9
  receipt: number | void;
@@ -7,26 +12,14 @@ interface State {
7
12
  el: HTMLSpanElement;
8
13
  }
9
14
 
10
- /*
11
- Custom Element with performant and "asynchronous" renders
12
- on the microtask queue.
13
- */
14
- export class Stopwatch extends HTMLElement {
15
+ class Stopwatch extends HTMLElement {
15
16
  #wc = new Wc({ host: this });
16
17
  #rc = new Microtask({ host: this, callback: this.#render });
17
18
 
18
- #boundUpdate = this.#update.bind(this);
19
19
  #state?: State = getStateFromShadowDOM(this.#wc.shadowRoot);
20
20
 
21
- #render() {
22
- if (this.#state)
23
- this.#state.el.textContent = this.#state.count.toFixed(2);
24
- }
25
-
26
- #update(timestamp: DOMHighResTimeStamp) {
27
- if (!this.#state) return;
28
-
29
- this.#state.receipt = requestAnimationFrame(this.#boundUpdate);
21
+ update(timestamp: DOMHighResTimeStamp) {
22
+ if (!this.#state || timestamp < this.#state.prevTimestamp) return;
30
23
 
31
24
  this.#state.count += (timestamp - this.#state.prevTimestamp) * 0.001;
32
25
  this.#state.prevTimestamp = timestamp;
@@ -35,16 +28,9 @@ export class Stopwatch extends HTMLElement {
35
28
  this.#rc.queue();
36
29
  }
37
30
 
38
- start() {
39
- if (!this.#state || this.#state?.receipt) return;
40
-
41
- this.#state.receipt = requestAnimationFrame(this.#boundUpdate);
42
- this.#state.prevTimestamp = performance.now();
43
- }
44
-
45
- pause() {
46
- if (this.#state && this.#state.receipt)
47
- this.#state.receipt = cancelAnimationFrame(this.#state.receipt);
31
+ #render() {
32
+ if (this.#state)
33
+ this.#state.el.textContent = this.#state.count.toFixed(2);
48
34
  }
49
35
  }
50
36
 
@@ -59,3 +45,5 @@ function getStateFromShadowDOM(shadowRoot: ShadowRoot): State | undefined {
59
45
  };
60
46
  }
61
47
  }
48
+
49
+ export { Stopwatch };
package/package.json CHANGED
@@ -4,9 +4,9 @@
4
4
  "main": "dist/mod.js",
5
5
  "description": "A bare-metal web component toolkit",
6
6
  "license": "BSD-3-Clause",
7
- "version": "0.1.0",
7
+ "version": "0.1.1",
8
8
  "scripts": {
9
- "prepare": "npm run build && npm run build:examples",
9
+ "prepare": "npm run build",
10
10
  "build": "npx tsc --project ./src",
11
11
  "build:examples": "npx tsc --project ./examples",
12
12
  "format": "prettier --write ./"
package/src/events.ts CHANGED
@@ -1,20 +1,38 @@
1
- export type Callbacks = Array<[string, EventListenerOrEventListenerObject]>;
1
+ interface GenericEventListener<E> {
2
+ (evt: E): void;
3
+ }
2
4
 
3
- export interface EventsInterface {
4
- connect(): void;
5
- disconnect(): void;
5
+ interface GenericEventListenerObject<E> {
6
+ handleEvent(object: E): void;
6
7
  }
7
8
 
8
- export interface EventElementInterface {
9
- addEventListener: EventTarget["addEventListener"];
10
- removeEventListener: EventTarget["removeEventListener"];
9
+ type GenericCallbacks<E> =
10
+ | GenericEventListener<E>
11
+ | GenericEventListenerObject<E>;
12
+
13
+ type EventMap = Partial<{
14
+ [Property in keyof GlobalEventHandlersEventMap]: GenericCallbacks<
15
+ GlobalEventHandlersEventMap[Property]
16
+ >;
17
+ }>;
18
+
19
+ type Callbacks = Array<[string, EventListenerOrEventListenerObject]>;
20
+
21
+ interface EventElementInterface {
22
+ addEventListener: Element["addEventListener"];
23
+ removeEventListener: Element["removeEventListener"];
11
24
  }
12
25
 
13
26
  export interface EventParamsInterface {
14
- host: EventElementInterface;
27
+ callbacks: EventMap;
15
28
  connected?: boolean;
29
+ host: EventElementInterface;
16
30
  target?: EventElementInterface;
17
- callbacks: Callbacks;
31
+ }
32
+
33
+ export interface EventsInterface {
34
+ connect(): void;
35
+ disconnect(): void;
18
36
  }
19
37
 
20
38
  export class Events implements EventsInterface {
@@ -50,9 +68,9 @@ export class Events implements EventsInterface {
50
68
  }
51
69
  }
52
70
 
53
- function getBoundCallbacks(host: Object, callbacks: Callbacks): Callbacks {
71
+ function getBoundCallbacks(host: Object, callbacks: EventMap): Callbacks {
54
72
  let boundCallbacks: Callbacks = [];
55
- for (let [name, callback] of callbacks) {
73
+ for (let [name, callback] of Object.entries(callbacks)) {
56
74
  if (
57
75
  callback instanceof Function &&
58
76
  !callback.hasOwnProperty("prototype")
@@ -60,7 +78,10 @@ function getBoundCallbacks(host: Object, callbacks: Callbacks): Callbacks {
60
78
  callback = callback.bind(host);
61
79
  }
62
80
 
63
- boundCallbacks.push([name, callback]);
81
+ boundCallbacks.push([
82
+ name,
83
+ callback as EventListenerOrEventListenerObject,
84
+ ]);
64
85
  }
65
86
 
66
87
  return boundCallbacks;
package/src/wc.ts CHANGED
@@ -32,21 +32,22 @@ export class Wc implements WcInterface {
32
32
  #shadowRoot: ShadowRoot;
33
33
 
34
34
  constructor(params: WcParamsInterface) {
35
- let { host, shadowRootInit, adoptedStyleSheets, formValue, formState } =
36
- params;
35
+ let { adoptedStyleSheets, host, formState, formValue, shadowRootInit } = params;
36
+
37
37
  this.#internals = host.attachInternals();
38
38
 
39
39
  let { shadowRoot } = this.#internals;
40
- if (!shadowRoot) {
40
+ if (shadowRoot) {
41
+ this.#shadowRoot = shadowRoot;
42
+ } else {
41
43
  this.#declarative = false;
42
- shadowRoot = host.attachShadow(
44
+ this.#shadowRoot = host.attachShadow(
43
45
  shadowRootInit ?? shadowRootInitFallback,
44
46
  );
45
47
  }
46
- this.#shadowRoot = shadowRoot;
47
48
 
48
- if (formValue) this.setFormValue(formValue, formState);
49
49
  if (adoptedStyleSheets) this.adoptedStyleSheets = adoptedStyleSheets;
50
+ if (formValue) this.setFormValue(formValue, formState);
50
51
  }
51
52
 
52
53
  get declarative(): boolean {