3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jsebrech/tiny-signals",
3 | "version": "1.0.0",
4 | "description": "The tiniest implementation of signals, ideal for vanilla JavaScript projects.",
5 | "main": "signals.js",
6 | "type": "module",
7 | "scripts": {
8 | "start": "npx http-server .",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/jsebrech/tiny-signals.git"
14 | },
15 | "author": "Joeri Sebrechts ",
16 | "license": "Unlicense",
17 | "bugs": {
18 | "url": "https://github.com/jsebrech/tiny-signals/issues"
19 | },
20 | "homepage": "https://github.com/jsebrech/tiny-signals#readme"
21 | }
22 |
--------------------------------------------------------------------------------
/example/adder.js:
--------------------------------------------------------------------------------
1 | import { signal, computed } from '../signals.js';
2 |
3 | customElements.define('x-adder', class extends HTMLElement {
4 | a = signal(1);
5 | b = signal(2);
6 | result = computed((a, b) => `${a} + ${b} = ${+a + +b}`, [this.a, this.b]);
7 |
8 | connectedCallback() {
9 | if (this.querySelector('input')) return;
10 |
11 | this.innerHTML = `
12 |
13 |
14 |
15 | `;
16 | this.result.effect(
17 | () => this.querySelector('p').textContent = this.result);
18 | this.addEventListener('input',
19 | e => this[e.target.name].value = e.target.value);
20 | }
21 | });
22 |
--------------------------------------------------------------------------------
/signals.js:
--------------------------------------------------------------------------------
1 | export class Signal extends EventTarget {
2 | #value;
3 | get value () { return this.#value; }
4 | set value (value) {
5 | if (this.#value === value) return;
6 | this.#value = value;
7 | this.dispatchEvent(new CustomEvent('change'));
8 | }
9 |
10 | constructor (value) {
11 | super();
12 | this.#value = value;
13 | }
14 |
15 | effect(fn) {
16 | fn();
17 | this.addEventListener('change', fn);
18 | return () => this.removeEventListener('change', fn);
19 | }
20 |
21 | valueOf () { return this.#value; }
22 | toString () { return String(this.#value); }
23 | }
24 |
25 | export class Computed extends Signal {
26 | constructor (fn, deps) {
27 | super(fn(...deps));
28 | for (const dep of deps) {
29 | if (dep instanceof Signal)
30 | dep.addEventListener('change', () => this.value = fn(...deps));
31 | }
32 | }
33 | }
34 |
35 | export const signal = _ => new Signal(_);
36 | export const computed = (fn, deps) => new Computed(fn, deps);
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tiny Signals
2 |
3 | The tiniest implementation of signals, ideal for vanilla JavaScript projects.
4 |
5 | Based loosely on the [signals API](https://preactjs.com/guide/v10/signals/) of Preact.
6 |
7 | Part of the [Plain Vanilla Web](https://plainvanillaweb.com) project.
8 |
9 | ## Usage
10 |
11 | ### Without NPM
12 |
13 | Copy `signals.js` into your project.
14 |
15 | Use it like this:
16 |
17 | ```js
18 | import { signal, computed } from './signals.js';
19 |
20 | const name = signal('Jane');
21 | const surname = signal('Doe');
22 | const fullName = computed(() => `${name} ${surname}`, [name, surname]);
23 | // Logs name every time it changes:
24 | fullName.effect(() => console.log(fullName.value));
25 | // -> Jane Doe
26 |
27 | // Updating `name` updates `fullName`, which triggers the effect again:
28 | name.value = 'John';
29 | // -> John Doe
30 | ```
31 |
32 | ### With NPM
33 |
34 | Run `npm install @jsebrech/tiny-signals`
35 |
36 | Use it like this:
37 |
38 | ```js
39 | import { signal, computed } from '@jsebrech/tiny-signals';
40 | // ...
41 | ```
42 |
43 | ### API
44 |
45 | - `const mySignal = signal(val)`: creates a signal.
46 | - `mySignal.value`: get or set the signal's value
47 | - `const dispose = mySignal.effect(fn)`: call the function every time the signal's value changes, also call it initially. The `dispose()` function unregisters the effect from the signal.
48 | - `const result = computed(() => 'hello ' + mySignal.value, [mySignal])`: create a signal that is computed from other signals and values by a function,
49 | and will automatically update when the value of a dependency changes
50 | - `mySignal.addEventListener('change', fn)`: subscribe to changes without calling the function initially
51 |
52 | ## Example
53 |
54 | Run a static server:
55 |
56 | `npx http-server .`
57 |
58 | Browse to http://localhost:8080/example/adder.html
59 |
60 | ## Other versions
61 |
62 | Typescript: [felixranesberger/tiny-signals-ts](https://github.com/felixranesberger/tiny-signals-ts)
63 |
--------------------------------------------------------------------------------