Skip to content
This repository was archived by the owner on Feb 18, 2026. It is now read-only.

Commit 5477b28

Browse files
committed
Merge branch 'main' of https://github.com/efflore/ui-element into main
2 parents f9c559c + 79f49c6 commit 5477b28

25 files changed

+699
-239
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ In the `connectedCallback()` you setup references to inner elements, add event l
1616

1717
`UIElement` is fast. In fact, faster than any JavaScript framework. Only direct fine-grained DOM updates in vanilla JavaScript can beat its performance. But then, you have no loose coupling of components and need to parse attributes and track changes yourself. This tends to get tedious and messy rather quickly. `UIElement` provides a structured way to keep your components simple, consistent and self-contained.
1818

19-
`UIElement` is tiny. 914 bytes gzipped over the wire. And it has zero dependiences. If you want to understand how it works, you have to study the source code of [one single file](./index.js).
19+
`UIElement` is tiny. 930 bytes gzipped over the wire. And it has zero dependiences. If you want to understand how it works, you have to study the source code of [one single file](./index.js).
2020

2121
That's all.
2222

@@ -203,7 +203,7 @@ It consists of three functions:
203203
- `derive()` returns a getter function for the current value of the derived computation
204204
- `effect()` accepts a callback function to be exectuted when used signals change
205205

206-
Cause & Effect is possibly the simplest way to turn JavaScript into a reactive language – with just 312 bytes gezipped code. Unlike the [TC39 Signals Proposal](https://github.com/tc39/proposal-signals), Cause & Effect uses a much simpler approach, effectively just decorator functions around signal getters and setters. All work is done synchronously and eagerly. As long as your computed functions are pure and DOM side effects are kept to a minimum, this should pose no issues and is even faster than doing all the checks and memoization in the more sophisticated push-then-pull approach of the Signals Proposal.
206+
Cause & Effect is possibly the simplest way to turn JavaScript into a reactive language – with just 340 bytes gezipped code. Unlike the [TC39 Signals Proposal](https://github.com/tc39/proposal-signals), Cause & Effect uses a much simpler approach, effectively just decorator functions around signal getters and setters. All work is done synchronously and eagerly. As long as your computed functions are pure and DOM side effects are kept to a minimum, this should pose no issues and is even faster than doing all the checks and memoization in the more sophisticated push-then-pull approach of the Signals Proposal.
207207

208208
If you however want to use side-effects or expensive work in computed function or updating / rendering in the DOM in effects takes longer than an animation frame, you might encounter glitches. If that's what you are doing, you are better off with a mature, full-fledged JavaScript framework.
209209

cause-effect.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const isState = (value) => isFunction(value) && isFunction(value.set);
2121
* Define a reactive state
2222
*
2323
* @since 0.1.0
24-
* @param {unknown} value - initial value of the state; may be a function for derived state
24+
* @param {any} value - initial value of the state; may be a function for derived state
2525
* @returns {UIState} getter function for the current value with a `set` method to update the value
2626
*/
2727
const cause = (value) => {
@@ -46,10 +46,19 @@ const cause = (value) => {
4646
* Create a derived state from an existing state
4747
*
4848
* @since 0.1.0
49-
* @param {() => unknown} fn - existing state to derive from
50-
* @returns {() => unknown} derived state
49+
* @param {() => any} fn - existing state to derive from
50+
* @returns {() => any} derived state
5151
*/
52-
const derive = (fn) => fn;
52+
const derive = (fn) => {
53+
const computed = () => {
54+
const prev = active;
55+
active = computed;
56+
const value = fn();
57+
active = prev;
58+
return value;
59+
};
60+
return computed;
61+
};
5362
/**
5463
* Define what happens when a reactive state changes
5564
*
@@ -66,7 +75,7 @@ const effect = (fn) => {
6675
targets.get(element).add(domFn);
6776
});
6877
active = prev;
69-
queueMicrotask(() => {
78+
(targets.size || cleanup) && queueMicrotask(() => {
7079
for (const domFns of targets.values()) {
7180
for (const domFn of domFns)
7281
domFn();

cause-effect.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eslint.config.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
// @ts-nocheck
2-
import globals from "globals";
3-
import pluginJs from "@eslint/js";
4-
2+
import globals from 'globals';
3+
import eslint from '@eslint/js';
4+
import tseslint from 'typescript-eslint';
55

66
export default [
7-
{languageOptions: { globals: globals.browser }},
8-
pluginJs.configs.recommended,
7+
{
8+
languageOptions: {
9+
globals: globals.browser
10+
},
11+
},
12+
eslint.configs.recommended,
13+
...tseslint.configs.recommended,
914
];

index.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const isState = (value) => isFunction(value) && isFunction(value.set);
2121
* Define a reactive state
2222
*
2323
* @since 0.1.0
24-
* @param {unknown} value - initial value of the state; may be a function for derived state
24+
* @param {any} value - initial value of the state; may be a function for derived state
2525
* @returns {UIState} getter function for the current value with a `set` method to update the value
2626
*/
2727
const cause = (value) => {
@@ -58,7 +58,7 @@ const effect = (fn) => {
5858
targets.get(element).add(domFn);
5959
});
6060
active = prev;
61-
queueMicrotask(() => {
61+
(targets.size || cleanup) && queueMicrotask(() => {
6262
for (const domFns of targets.values()) {
6363
for (const domFn of domFns)
6464
domFn();
@@ -105,13 +105,24 @@ class ContextRequestEvent extends Event {
105105
}
106106
}
107107

108+
/* === Internal function === */
109+
/**
110+
* Parse a attribute or context mapping value into a key-value pair
111+
*
112+
* @param {[PropertyKey, UIAttributeParser | UIContextParser] | UIAttributeParser | UIContextParser} value
113+
* @param {PropertyKey} defaultKey
114+
* @returns {[PropertyKey, UIAttributeParser | UIContextParser]}
115+
*/
116+
const getArrayMapping = (value, defaultKey) => {
117+
return Array.isArray(value) ? value : [defaultKey, isFunction(value) ? value : (v) => v];
118+
};
108119
/* === Default export === */
109120
/**
110121
* Base class for reactive custom elements
111122
*
112123
* @class UIElement
113124
* @extends HTMLElement
114-
* @type {UIElement}
125+
* @type {IUIElement}
115126
*/
116127
class UIElement extends HTMLElement {
117128
/**
@@ -154,11 +165,9 @@ class UIElement extends HTMLElement {
154165
attributeChangedCallback(name, old, value) {
155166
if (value !== old) {
156167
const input = this.attributeMap[name];
157-
const [key, parser] = Array.isArray(input)
158-
? input :
159-
[name, input];
160-
this.set(key, isFunction(parser)
161-
? parser(value, this, old)
168+
const [key, fn] = getArrayMapping(input, name);
169+
this.set(key, isFunction(fn)
170+
? fn(value, this, old)
162171
: value);
163172
}
164173
}
@@ -180,9 +189,7 @@ class UIElement extends HTMLElement {
180189
proto.consumedContexts?.forEach((context) => {
181190
const event = new ContextRequestEvent(context, (value) => {
182191
const input = this.contextMap[context];
183-
const [key, fn] = Array.isArray(input)
184-
? input
185-
: [context, input];
192+
const [key, fn] = getArrayMapping(input, context);
186193
this.#states.set(key || context, isFunction(fn)
187194
? fn(value, this)
188195
: value);

index.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)