Skip to content

Commit 89134c3

Browse files
Refactor types and fix some issues
- When assigning a value to `$e.state`, the state is correctly merged and the component is rendered; - When returning a value from `connectedCallback`, the state is correctly merged; - The HOF `withDOM` now works with Deno DOM; - Implement `factorizeTemplate` to simplify creating a template element;
1 parent cd099af commit 89134c3

File tree

7 files changed

+119
-127
lines changed

7 files changed

+119
-127
lines changed

library/component.d.ts

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,54 @@ export type CustomElement = HTMLElement & {
1111
connectedCallback?(): void;
1212
disconnectedCallback?(): void;
1313
elements?: { [k: string]: HTMLElement };
14-
[StateSymbol]: State;
14+
state: Partial<State>;
1515
};
16-
export type Constructor<E extends CustomElement> = {
16+
export type Constructor<E extends CustomElement = CustomElement> = {
1717
new (): E;
1818
} & { observedAttributes: Array<string> };
19-
export type HOF<E extends CustomElement, S extends State> = (
19+
export type HOF<S extends State, E extends CustomElement = CustomElement> = (
2020
factorize: (
2121
f: (Component: Constructor<E>, render: (e: E, s: S) => void) => void,
2222
) => void,
23-
construct?: (f: (e: E) => void) => void,
23+
construct: (f: (e: E) => void) => void,
2424
) => void;
25+
export type AsyncRenderCallback<
26+
S extends State,
27+
E extends CustomElement = CustomElement,
28+
> = ($e: E, s: S, e: Event) => S | undefined;
29+
export type AsyncRender<
30+
S extends State,
31+
E extends CustomElement = CustomElement,
32+
> = (
33+
f: AsyncRenderCallback<S, E>,
34+
) => (e: Event) => void;
35+
export type Render<S extends State, E extends CustomElement = CustomElement> = (
36+
element: E,
37+
state: S,
38+
) => void;
39+
export type AttributeCallback<
40+
S extends State,
41+
E extends CustomElement = CustomElement,
42+
> = <X extends ValueOf<S>>(
43+
attributes: { name: keyof S; oldValue: X; value: X },
44+
element?: E,
45+
state?: S,
46+
) => Partial<S> | boolean;
47+
export type LifeCycleCallback<
48+
S extends State,
49+
E extends CustomElement = CustomElement,
50+
> =
51+
| ((
52+
element: E,
53+
render: AsyncRender<S, E>,
54+
) => void)
55+
| ((
56+
element: E,
57+
render: AsyncRender<S, E>,
58+
name: string,
59+
oldValue: string,
60+
value: string,
61+
) => S | void);
2562

2663
export declare enum Callbacks {
2764
"adoptedCallback",
@@ -33,45 +70,42 @@ export declare enum Callbacks {
3370
type ValueOf<T> = T[keyof T];
3471

3572
export function factorizeComponent<
36-
E extends CustomElement = CustomElement,
3773
S extends State,
74+
E extends CustomElement = CustomElement,
3875
>(
39-
render: (element: E, state: S) => void,
76+
render: Render<S, E>,
4077
state: S,
41-
...fs: Array<HOF<E, S>>
78+
...fs: Array<HOF<S, E>>
4279
): Constructor<E>;
4380

44-
export function useAttributes<E extends CustomElement, S extends State>(
45-
validateAttribute: <X extends ValueOf<S>>(
46-
attributes: { name: keyof S; oldValue: X; value: X },
47-
element?: E,
48-
state?: S,
49-
) => Partial<S> | boolean,
81+
export function useAttributes<
82+
S extends State,
83+
E extends CustomElement = CustomElement,
84+
>(
85+
validateAttribute: AttributeCallback<S, E>,
5086
map?: { [K in keyof S]: (x: string) => S[K] },
51-
): HOF<E, S>;
87+
): HOF<S, E>;
5288

53-
export function useCallbacks<E extends CustomElement, S extends State>(
89+
export function useCallbacks<
90+
S extends State,
91+
E extends CustomElement = CustomElement,
92+
>(
5493
callbacks: {
55-
[K in keyof typeof Callbacks]?:
56-
| ((
57-
element: E,
58-
render: ($e: E, s: State, e: Event) => void,
59-
) => void)
60-
| ((
61-
element: E,
62-
name: string,
63-
oldValue: string,
64-
value: string,
65-
render: ($e: E, s: State, e: Event) => void,
66-
) => void);
94+
[K in keyof typeof Callbacks]?: LifeCycleCallback<S, E>;
6795
},
68-
): HOF<E, S>;
96+
): HOF<S, E>;
6997

70-
export function useShadow<E extends CustomElement, S extends State>(
98+
export function useShadow<
99+
S extends State,
100+
E extends CustomElement = CustomElement,
101+
>(
71102
options?: { mode: "open" | "closed" },
72-
): HOF<E, S>;
103+
): HOF<S, E>;
73104

74-
export function useTemplate<E extends CustomElement, S extends State>(
105+
export function useTemplate<
106+
S extends State,
107+
E extends CustomElement = CustomElement,
108+
>(
75109
getTemplate: () => HTMLTemplateElement,
76110
map?: { [k: string]: (e: E) => CustomElement },
77-
): HOF<E, S>;
111+
): HOF<S, E>;

library/component.js

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,19 @@ export const factorizeComponent = (render, state, ...fs) => {
185185
[
186186
(e) => {
187187
e[StateSymbol] = Object.assign({}, state);
188+
189+
Object.defineProperty(
190+
e,
191+
"state",
192+
{
193+
get() {
194+
return this[StateSymbol];
195+
},
196+
set(s) {
197+
render(this, Object.assign({}, this[StateSymbol], s));
198+
},
199+
},
200+
);
188201
},
189202
...constructors,
190203
],
@@ -196,18 +209,6 @@ export const factorizeComponent = (render, state, ...fs) => {
196209
Object.setPrototypeOf(Component.prototype, FunctionalComponent.prototype);
197210
Object.setPrototypeOf(Component, FunctionalComponent);
198211

199-
Object.defineProperty(
200-
Component.prototype,
201-
"state",
202-
{
203-
get() {
204-
return this[StateSymbol];
205-
},
206-
set() {
207-
},
208-
},
209-
);
210-
211212
const _render = wrapRender(render);
212213

213214
for (const f of fs) {
@@ -231,8 +232,9 @@ export const factorizeComponent = (render, state, ...fs) => {
231232
enumerable: false,
232233
value() {
233234
return maybeCall(_connectedCallback, this)
234-
.then(() => {
235-
_render(this, Object.assign({}, this[StateSymbol]), {
235+
.then((s = {}) => {
236+
this[StateSymbol] = Object.assign({}, state, s);
237+
_render(this, this[StateSymbol], {
236238
name: "connectedCallback",
237239
data: {},
238240
});
@@ -512,12 +514,12 @@ export const useCallbacks = (callbacks) =>
512514
.then(() =>
513515
f(
514516
this,
515-
...xs,
516517
asyncRender(
517518
this,
518519
render,
519520
Component.observedAttributes || [],
520521
),
522+
...xs,
521523
)
522524
);
523525
},

library/testing.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,7 @@ export function factorizeSpy<F extends (...xs: Array<unknown>) => unknown>(
2727

2828
export function test(name: string, f: () => void): void;
2929

30-
export function withDom(f: () => void): () => Promise<void>;
30+
export function withDom(
31+
f: (document: HTMLDocument) => void,
32+
t?: string,
33+
): () => Promise<void>;

library/testing.js

Lines changed: 9 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { randomUUID } from "./utilities.js";
1+
import { maybeCall, randomUUID } from "./utilities.js";
22

33
export const TestsSymbol = Symbol.for("iy-tests");
44

@@ -65,93 +65,26 @@ export const test = (name, f) => {
6565
tests.set(f, { name });
6666
};
6767

68-
export const withDom = (f) => {
68+
export const withDom = (f, template = "<div></div>") => {
6969
if (!globalThis.requestAnimationFrame) {
7070
globalThis.requestAnimationFrame = (f) => setTimeout(f);
7171
}
7272

73-
if (window.HTMLElement) return f;
73+
if (globalThis.HTMLElement) return f;
7474

7575
return () =>
7676
Promise.all(
77-
["https://esm.sh/htmlparser2", "https://esm.sh/domhandler"].map((x) =>
77+
["https://deno.land/x/deno_dom@v0.1.26-alpha/deno-dom-wasm.ts"].map((x) =>
7878
import(x)
7979
),
8080
)
81-
.then(() => {
82-
const noop = (_) => undefined;
81+
.then(([{ DOMParser, Element }]) => {
82+
const document = new DOMParser().parseFromString(template, "text/html");
8383

84-
const factorizeHTMLElement = (f = (x) => x) => {
85-
return f(new window.HTMLElement());
86-
};
84+
globalThis.HTMLElement = class HTMLElement extends Element {};
85+
globalThis.document = document;
8786

88-
window.HTMLElement = class {
89-
#state = {};
90-
91-
constructor() {
92-
this.observedAttributes = this.constructor.observedAttributes;
93-
}
94-
95-
appendChild() {
96-
}
97-
98-
attachShadow() {
99-
this.shadowRoot = new window.HTMLElement();
100-
}
101-
102-
dispatchEvent() {
103-
}
104-
105-
getAttribute(n) {
106-
return this.#state[n];
107-
}
108-
109-
querySelector() {
110-
return new window.HTMLElement();
111-
}
112-
113-
setAttribute(n, v) {
114-
if (Reflect.get(this, "observedAttributes")?.includes(n)) {
115-
this.attributeChangedCallback(n, this.#state[n], v);
116-
}
117-
this.#state[n] = v;
118-
}
119-
};
120-
121-
window.document = window.document || {};
122-
123-
window.document.createElement = (selector) =>
124-
(selector === "template")
125-
? factorizeHTMLElement(
126-
(t) =>
127-
Object.defineProperty(
128-
t,
129-
"content",
130-
{
131-
enumerable: true,
132-
value: {
133-
cloneNode: () => new window.HTMLElement(),
134-
},
135-
},
136-
),
137-
)
138-
: factorizeHTMLElement(
139-
(t) =>
140-
Object.defineProperties(
141-
t,
142-
{
143-
"innerHTML": {
144-
set(x) {
145-
noop(x);
146-
},
147-
},
148-
},
149-
),
150-
);
151-
152-
const p = f();
153-
154-
return (p instanceof Promise ? p : Promise.resolve(p))
87+
return maybeCall(f, null, document)
15588
.finally(() => {
15689
window.HTMLElement = undefined;
15790
window.document = undefined;

library/utilities.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ export function deferUntilNextFrame(): Promise<void>;
2323

2424
export function disconnectAllElements<E extends HTMLElement>(e: E): void;
2525

26+
export function factorizeTemplate(
27+
html: string,
28+
): (document: HTMLDocument) => HTMLTemplateElement;
29+
2630
export function intersects(xs: Array<unknown>, ys: Array<unknown>): boolean;
2731

2832
export function maybeCall<X, Y, E extends HTMLElement, XS extends Array<X>>(

library/utilities.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ export const disconnectAllElements = (e) => {
3232
}
3333
};
3434

35+
export const factorizeTemplate = (html) =>
36+
(document) => {
37+
const t = document.createElement("template");
38+
t.innerHTML = html;
39+
return t;
40+
};
41+
3542
export const intersects = (xs, ys) =>
3643
ys && ys.reduce(
3744
(b, y) => !xs.includes(y) ? false : b,
@@ -90,7 +97,7 @@ export const parsePascalCaseToSpineCase = (x) =>
9097
x.split(/(?=[A-Z0-9])/).join("-").toLowerCase();
9198

9299
export const parseSpineCaseToCamelCase = (x) =>
93-
x.replace(/(\-\w)/g, (m) => m[1].toUpperCase());
100+
x.replace(/(-\w)/g, (m) => m[1].toUpperCase());
94101

95102
/**
96103
* Renders elements given an iterable.

tsconfig.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
{
22
"compilerOptions": {
3-
"lib": ["ESNext", "dom", "dom.iterable", "dom.asynciterable", "deno.ns"],
4-
"target": "ESNext"
3+
"allowJs": true,
4+
"experimentalDecorators": true,
5+
"jsx": "react",
6+
"lib": [
7+
"esnext",
8+
"dom",
9+
"dom.iterable",
10+
"dom.asynciterable"
11+
],
12+
13+
"strict": true,
514
}
615
}

0 commit comments

Comments
 (0)