@davidsouther/jiffies 2026.24.0 → 2026.24.2
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.
- package/lib/esm/assert.d.ts +26 -0
- package/lib/esm/assert.js +38 -0
- package/lib/esm/awaitable.js +1 -0
- package/lib/esm/case.d.ts +1 -0
- package/lib/esm/case.js +5 -0
- package/lib/esm/components/accordion.d.ts +5 -0
- package/lib/esm/components/accordion.js +9 -0
- package/lib/esm/components/alert.d.ts +7 -0
- package/lib/esm/components/alert.js +31 -0
- package/lib/esm/components/button_bar.d.ts +8 -0
- package/lib/esm/components/button_bar.js +25 -0
- package/lib/esm/components/card.d.ts +8 -0
- package/lib/esm/components/card.js +31 -0
- package/lib/esm/components/children.d.ts +2 -0
- package/{src/components/children.ts → lib/esm/components/children.js} +2 -6
- package/lib/esm/components/form.d.ts +5 -0
- package/lib/esm/components/form.js +13 -0
- package/{src/components/index.ts → lib/esm/components/index.d.ts} +2 -15
- package/lib/esm/components/index.js +10 -0
- package/lib/esm/components/inline_edit.d.ts +12 -0
- package/lib/esm/components/inline_edit.js +48 -0
- package/lib/esm/components/link.d.ts +5 -0
- package/lib/esm/components/link.js +11 -0
- package/lib/esm/components/logger.d.ts +6 -0
- package/lib/esm/components/logger.js +22 -0
- package/lib/esm/components/modal.d.ts +2 -0
- package/{src/components/modal.ts → lib/esm/components/modal.js} +3 -8
- package/lib/esm/components/nav.d.ts +11 -0
- package/lib/esm/components/nav.js +27 -0
- package/lib/esm/components/property.d.ts +9 -0
- package/lib/esm/components/property.js +16 -0
- package/lib/esm/components/select.d.ts +10 -0
- package/lib/esm/components/select.js +3 -0
- package/lib/esm/components/tabs.d.ts +20 -0
- package/lib/esm/components/tabs.js +45 -0
- package/lib/esm/components/virtual_scroll.d.ts +42 -0
- package/lib/esm/components/virtual_scroll.js +94 -0
- package/lib/esm/debounce.d.ts +1 -0
- package/lib/esm/debounce.js +11 -0
- package/lib/esm/diff.d.ts +15 -0
- package/lib/esm/diff.js +50 -0
- package/lib/esm/display.d.ts +5 -0
- package/lib/esm/display.js +11 -0
- package/lib/esm/dom/css/border.d.ts +11 -0
- package/lib/esm/dom/css/border.js +27 -0
- package/lib/esm/dom/css/constants.d.ts +31 -0
- package/lib/esm/dom/css/constants.js +28 -0
- package/lib/esm/dom/css/core.d.ts +5 -0
- package/lib/esm/dom/css/core.js +24 -0
- package/lib/esm/dom/css/fstyle.d.ts +5 -0
- package/lib/esm/dom/css/fstyle.js +32 -0
- package/lib/esm/dom/css/sizing.d.ts +5 -0
- package/lib/esm/dom/css/sizing.js +10 -0
- package/lib/esm/dom/dom.d.ts +36 -0
- package/lib/esm/dom/dom.js +217 -0
- package/lib/esm/dom/fc.d.ts +10 -0
- package/lib/esm/dom/fc.js +32 -0
- package/lib/esm/dom/form/form.app.d.ts +1 -0
- package/lib/esm/dom/form/form.app.js +19 -0
- package/lib/esm/dom/form/form.d.ts +27 -0
- package/lib/esm/dom/form/form.js +65 -0
- package/lib/esm/dom/html.d.ts +112 -0
- package/{src/dom/html.ts → lib/esm/dom/html.js} +2 -14
- package/lib/esm/dom/hydrate.d.ts +39 -0
- package/lib/esm/dom/hydrate.js +187 -0
- package/lib/esm/dom/index.js +2 -0
- package/lib/esm/dom/navigation/index.d.ts +76 -0
- package/lib/esm/dom/navigation/index.js +292 -0
- package/lib/esm/dom/observable.d.ts +2 -0
- package/lib/esm/dom/observable.js +6 -0
- package/lib/esm/dom/provide.d.ts +3 -0
- package/lib/esm/dom/provide.js +7 -0
- package/lib/esm/dom/render.d.ts +8 -0
- package/lib/esm/dom/render.js +28 -0
- package/lib/esm/dom/router/link.d.ts +6 -0
- package/lib/esm/dom/router/link.js +3 -0
- package/lib/esm/dom/router/router.d.ts +13 -0
- package/lib/esm/dom/router/router.js +52 -0
- package/lib/esm/dom/svg.d.ts +64 -0
- package/{src/dom/svg.ts → lib/esm/dom/svg.js} +2 -19
- package/lib/esm/dom/types/css.d.ts +6590 -0
- package/lib/esm/dom/types/css.js +1 -0
- package/lib/esm/dom/types/dom.js +1 -0
- package/lib/esm/dom/types/html.d.ts +614 -0
- package/lib/esm/dom/types/html.js +1 -0
- package/lib/esm/dom/xml.d.ts +1 -0
- package/lib/esm/dom/xml.js +4 -0
- package/lib/esm/equal.d.ts +11 -0
- package/lib/esm/equal.js +43 -0
- package/lib/esm/fs.d.ts +72 -0
- package/lib/esm/fs.js +227 -0
- package/lib/esm/fs_node.d.ts +15 -0
- package/lib/esm/fs_node.js +45 -0
- package/lib/esm/generator.d.ts +1 -0
- package/lib/esm/generator.js +10 -0
- package/lib/esm/lock.d.ts +1 -0
- package/lib/esm/lock.js +23 -0
- package/lib/esm/log.d.ts +69 -0
- package/lib/esm/log.js +211 -0
- package/lib/esm/observable/event.d.ts +35 -0
- package/lib/esm/observable/event.js +46 -0
- package/lib/esm/observable/observable.d.ts +134 -0
- package/lib/esm/observable/observable.js +349 -0
- package/lib/esm/range.d.ts +1 -0
- package/lib/esm/range.js +7 -0
- package/lib/esm/result.d.ts +31 -0
- package/lib/esm/result.js +66 -0
- package/lib/esm/safe.d.ts +1 -0
- package/lib/esm/safe.js +10 -0
- package/lib/esm/server/http/apps.d.ts +5 -0
- package/lib/esm/server/http/apps.js +23 -0
- package/lib/esm/server/http/css.d.ts +5 -0
- package/lib/esm/server/http/css.js +43 -0
- package/lib/esm/server/http/index.d.ts +16 -0
- package/lib/esm/server/http/index.js +78 -0
- package/lib/esm/server/http/response.d.ts +4 -0
- package/lib/esm/server/http/response.js +43 -0
- package/lib/esm/server/http/sitemap.d.ts +2 -0
- package/lib/esm/server/http/sitemap.js +22 -0
- package/lib/esm/server/http/static.d.ts +2 -0
- package/lib/esm/server/http/static.js +22 -0
- package/lib/esm/server/http/typescript.d.ts +5 -0
- package/lib/esm/server/http/typescript.js +40 -0
- package/lib/esm/server/live-reload.d.ts +46 -0
- package/lib/esm/server/live-reload.js +161 -0
- package/lib/esm/server/main.d.ts +2 -0
- package/{src/server/main.ts → lib/esm/server/main.js} +8 -15
- package/lib/esm/server/ws/frame.d.ts +2 -0
- package/lib/esm/server/ws/frame.js +35 -0
- package/lib/esm/server/ws/handshake.d.ts +4 -0
- package/lib/esm/server/ws/handshake.js +32 -0
- package/lib/esm/server/ws/index.d.ts +14 -0
- package/lib/esm/server/ws/index.js +68 -0
- package/lib/esm/ssg/bundle.d.ts +14 -0
- package/lib/esm/ssg/bundle.js +73 -0
- package/lib/esm/ssg/copy-public.d.ts +6 -0
- package/lib/esm/ssg/copy-public.js +34 -0
- package/lib/esm/ssg/discover.d.ts +15 -0
- package/lib/esm/ssg/discover.js +117 -0
- package/lib/esm/ssg/main.d.ts +2 -0
- package/lib/esm/ssg/main.js +122 -0
- package/lib/esm/ssg/rewrite.d.ts +9 -0
- package/{src/ssg/rewrite.ts → lib/esm/ssg/rewrite.js} +6 -9
- package/lib/esm/ssg/ssg.d.ts +26 -0
- package/lib/esm/ssg/ssg.js +84 -0
- package/lib/esm/transpile.d.mts +3 -0
- package/lib/esm/transpile.mjs +12 -0
- package/package.json +11 -7
- package/src/404.html +0 -14
- package/src/assert.ts +0 -56
- package/src/case.ts +0 -5
- package/src/components/_notes +0 -33
- package/src/components/accordion.ts +0 -25
- package/src/components/alert.ts +0 -47
- package/src/components/button_bar.ts +0 -42
- package/src/components/card.ts +0 -54
- package/src/components/form.ts +0 -25
- package/src/components/inline_edit.ts +0 -78
- package/src/components/link.ts +0 -22
- package/src/components/logger.ts +0 -35
- package/src/components/nav.ts +0 -42
- package/src/components/property.ts +0 -32
- package/src/components/select.ts +0 -22
- package/src/components/tabs.ts +0 -82
- package/src/components/virtual_scroll.ts +0 -199
- package/src/debounce.ts +0 -14
- package/src/diff.ts +0 -82
- package/src/display.ts +0 -18
- package/src/dom/README.md +0 -107
- package/src/dom/SKILL.md +0 -201
- package/src/dom/css/border.ts +0 -47
- package/src/dom/css/constants.ts +0 -34
- package/src/dom/css/core.ts +0 -28
- package/src/dom/css/fstyle.ts +0 -42
- package/src/dom/css/sizing.ts +0 -11
- package/src/dom/dom.ts +0 -327
- package/src/dom/fc.ts +0 -81
- package/src/dom/form/form.app.ts +0 -44
- package/src/dom/form/form.ts +0 -151
- package/src/dom/form/index.html +0 -15
- package/src/dom/hydrate.ts +0 -206
- package/src/dom/navigation/index.ts +0 -349
- package/src/dom/observable.ts +0 -11
- package/src/dom/provide.ts +0 -11
- package/src/dom/render.ts +0 -41
- package/src/dom/router/link.ts +0 -14
- package/src/dom/router/router.ts +0 -72
- package/src/dom/types/css.ts +0 -10088
- package/src/dom/types/html.ts +0 -629
- package/src/dom/xml.ts +0 -11
- package/src/equal.ts +0 -66
- package/src/favicon.ico +0 -0
- package/src/fs.ts +0 -300
- package/src/fs_node.ts +0 -57
- package/src/generator.ts +0 -12
- package/src/hooks/_notes +0 -6
- package/src/lock.ts +0 -23
- package/src/log.ts +0 -307
- package/src/observable/_notes +0 -26
- package/src/observable/event.ts +0 -93
- package/src/observable/observable.ts +0 -484
- package/src/range.ts +0 -7
- package/src/result.ts +0 -107
- package/src/safe.ts +0 -12
- package/src/server/http/apps.ts +0 -26
- package/src/server/http/css.ts +0 -49
- package/src/server/http/index.ts +0 -127
- package/src/server/http/response.ts +0 -60
- package/src/server/http/sitemap.ts +0 -24
- package/src/server/http/static.ts +0 -28
- package/src/server/http/typescript.ts +0 -46
- package/src/server/live-reload.ts +0 -208
- package/src/server/ws/frame.ts +0 -36
- package/src/server/ws/handshake.ts +0 -42
- package/src/server/ws/index.ts +0 -100
- package/src/ssg/bundle.ts +0 -85
- package/src/ssg/copy-public.ts +0 -44
- package/src/ssg/discover.ts +0 -143
- package/src/ssg/main.ts +0 -168
- package/src/ssg/ssg.ts +0 -134
- package/src/transpile.mjs +0 -16
- package/src/zip/spec.txt +0 -3260
- package/tsconfig.json +0 -34
- /package/{src/awaitable.ts → lib/esm/awaitable.d.ts} +0 -0
- /package/{src/dom/index.ts → lib/esm/dom/index.d.ts} +0 -0
- /package/{src/dom/types/dom.ts → lib/esm/dom/types/dom.d.ts} +0 -0
package/src/dom/css/constants.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export const Sizes = {
|
|
2
|
-
none: "0px",
|
|
3
|
-
sm: "0.125rem",
|
|
4
|
-
"": "0.25rem",
|
|
5
|
-
md: "0.375rem",
|
|
6
|
-
lg: "0.5rem",
|
|
7
|
-
xl: "0.75rem",
|
|
8
|
-
"2xl": "1rem",
|
|
9
|
-
"3xl": "1.5rem",
|
|
10
|
-
full: "9999px",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const Sides = {
|
|
14
|
-
"": "",
|
|
15
|
-
t: "Top",
|
|
16
|
-
r: "Right",
|
|
17
|
-
l: "Left",
|
|
18
|
-
b: "Bottom",
|
|
19
|
-
tl: "TopLeft",
|
|
20
|
-
tr: "TopRight",
|
|
21
|
-
bl: "BottomLeft",
|
|
22
|
-
br: "BottomRight",
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const Widths = {
|
|
26
|
-
"1/4": "25%",
|
|
27
|
-
"1/2": "50%",
|
|
28
|
-
"3/4": "75%",
|
|
29
|
-
full: "100%",
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type Size = keyof typeof Sizes;
|
|
33
|
-
export type Side = keyof typeof Sides;
|
|
34
|
-
export type Width = keyof typeof Widths;
|
package/src/dom/css/core.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type Side, Sides, type Size, Sizes } from "./constants.ts";
|
|
2
|
-
|
|
3
|
-
export function isSide(v: string): v is Side {
|
|
4
|
-
return Sides[v as keyof typeof Sides] !== undefined;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function isSize(v: string): v is Size {
|
|
8
|
-
return Sizes[v as keyof typeof Sizes] !== undefined;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function getSize(size: keyof typeof Sizes) {
|
|
12
|
-
return Sizes[size];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function getSide(side: Side): string[] {
|
|
16
|
-
switch (side) {
|
|
17
|
-
case "t":
|
|
18
|
-
return [...getSide("tl"), ...getSide("tr")];
|
|
19
|
-
case "r":
|
|
20
|
-
return [...getSide("tr"), ...getSide("br")];
|
|
21
|
-
case "b":
|
|
22
|
-
return [...getSide("br"), ...getSide("bl")];
|
|
23
|
-
case "l":
|
|
24
|
-
return [...getSide("tl"), ...getSide("bl")];
|
|
25
|
-
default:
|
|
26
|
-
return [Sides[side]];
|
|
27
|
-
}
|
|
28
|
-
}
|
package/src/dom/css/fstyle.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { dashCase } from "../../case.ts";
|
|
2
|
-
import type { Properties } from "../types/css.ts";
|
|
3
|
-
|
|
4
|
-
export type FStyle =
|
|
5
|
-
| Properties
|
|
6
|
-
| {
|
|
7
|
-
[k: string]: FStyle;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export function compileFStyle(fstyle: FStyle, prefix = ""): string {
|
|
11
|
-
const properties: { key: string; value: string }[] = [];
|
|
12
|
-
const rules: { key: string; value: FStyle }[] = [];
|
|
13
|
-
|
|
14
|
-
for (const [key, value] of Object.entries(fstyle)) {
|
|
15
|
-
if (typeof value === "string") {
|
|
16
|
-
properties.push({ key, value });
|
|
17
|
-
} else {
|
|
18
|
-
rules.push({ key, value });
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let rule = "";
|
|
23
|
-
|
|
24
|
-
if (properties.length > 0) {
|
|
25
|
-
rule += `${prefix} {\n`;
|
|
26
|
-
for (const { key, value } of properties) {
|
|
27
|
-
rule += ` ${dashCase(key)}: ${value};\n`;
|
|
28
|
-
}
|
|
29
|
-
rule += "}\n\n";
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
for (const { key, value } of rules) {
|
|
33
|
-
if (key.startsWith("@media")) {
|
|
34
|
-
rule += `${key} {\n`;
|
|
35
|
-
rule += compileFStyle(value, " ");
|
|
36
|
-
rule += "}\n\n";
|
|
37
|
-
} else {
|
|
38
|
-
rule += compileFStyle(value, `${prefix} ${key}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return rule;
|
|
42
|
-
}
|
package/src/dom/css/sizing.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type Width, Widths } from "./constants.ts";
|
|
2
|
-
|
|
3
|
-
export function width(amount: Width, block?: "inline") {
|
|
4
|
-
if (amount === undefined && Widths[block as Width] !== undefined) {
|
|
5
|
-
amount = block as Width;
|
|
6
|
-
}
|
|
7
|
-
return {
|
|
8
|
-
...(block === "inline" ? { display: "inline-block" } : {}),
|
|
9
|
-
width: Widths[amount] ?? "0",
|
|
10
|
-
};
|
|
11
|
-
}
|
package/src/dom/dom.ts
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import { assert, assertExists } from "../assert.ts";
|
|
2
|
-
import type { Properties as SVGProperties } from "./types/css.ts";
|
|
3
|
-
|
|
4
|
-
if (typeof window === "undefined") {
|
|
5
|
-
const { JSDOM } = await import("jsdom");
|
|
6
|
-
// biome-ignore lint/suspicious/noGlobalAssign: Load JSDom globally
|
|
7
|
-
window = global.window = new JSDOM().window as unknown as Window &
|
|
8
|
-
typeof globalThis;
|
|
9
|
-
global.HTMLElement ??= window.HTMLElement;
|
|
10
|
-
global.customElements ??= window.customElements;
|
|
11
|
-
// Unconditional: jsdom's dispatchEvent instanceof-checks its own Event class, so Node's native Event must be replaced.
|
|
12
|
-
global.Event = window.Event as unknown as typeof Event;
|
|
13
|
-
global.MouseEvent ??= window.MouseEvent as unknown as typeof MouseEvent;
|
|
14
|
-
global.Element ??= window.Element as unknown as typeof Element;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const XHTML_NAMESPACE_URI = "http://www.w3.org/1999/xhtml";
|
|
18
|
-
export const SVG_NAMESPACE_URI = "http://www.w3.org/2000/svg";
|
|
19
|
-
|
|
20
|
-
const Events = Symbol("events");
|
|
21
|
-
export const CLEAR = Symbol("Clear children");
|
|
22
|
-
|
|
23
|
-
// Node.ELEMENT_NODE; the Node global is not installed in the jsdom bootstrap
|
|
24
|
-
// above, so the numeric constant is used directly (cf. nodeType 3 for text).
|
|
25
|
-
const ELEMENT_NODE = 1;
|
|
26
|
-
|
|
27
|
-
export type EventHandler = EventListenerOrEventListenerObject;
|
|
28
|
-
export type DenormChildren =
|
|
29
|
-
| Node
|
|
30
|
-
| string
|
|
31
|
-
| typeof CLEAR
|
|
32
|
-
| null
|
|
33
|
-
| undefined
|
|
34
|
-
| false;
|
|
35
|
-
|
|
36
|
-
export type DOMElement = Element & ElementCSSInlineStyle;
|
|
37
|
-
|
|
38
|
-
export type DomAttrs = {
|
|
39
|
-
class: string | string[];
|
|
40
|
-
style: Partial<SVGProperties> | string;
|
|
41
|
-
role: "button" | "list" | "listbox";
|
|
42
|
-
events: Partial<{
|
|
43
|
-
[K in keyof HTMLElementEventMap]: EventHandler | null;
|
|
44
|
-
}>;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export type Attrs<E extends Omit<Element, "update">, S = object> = Partial<
|
|
48
|
-
Omit<{ [k in keyof E]: string | number | boolean }, "style" | "toString"> &
|
|
49
|
-
S &
|
|
50
|
-
DomAttrs
|
|
51
|
-
>;
|
|
52
|
-
|
|
53
|
-
export type DenormAttrs<E extends Omit<Element, "update">, S = object> =
|
|
54
|
-
| Attrs<E, S>
|
|
55
|
-
| DenormChildren;
|
|
56
|
-
|
|
57
|
-
declare global {
|
|
58
|
-
interface Element {
|
|
59
|
-
[Events]: Map<string, EventHandler>;
|
|
60
|
-
update(attrs?: DenormAttrs<Element>, ...children: DenormChildren[]): this;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export type DOMUpdates<E extends Element = Element> =
|
|
65
|
-
| [DenormAttrs<E>, ...DenormChildren[]]
|
|
66
|
-
| DenormChildren[];
|
|
67
|
-
|
|
68
|
-
function isAttrs<E extends Element>(
|
|
69
|
-
attrs: DenormAttrs<E> | undefined,
|
|
70
|
-
): attrs is Attrs<E> {
|
|
71
|
-
if (!attrs) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
if (typeof attrs === "object") {
|
|
75
|
-
return !(attrs as Node).nodeType;
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function normalizeArguments<E extends Element>(
|
|
81
|
-
attrs?: DenormAttrs<E>,
|
|
82
|
-
children: DenormChildren[] = [],
|
|
83
|
-
defaultAttrs: Attrs<E> = {},
|
|
84
|
-
): [Attrs<E>, DenormChildren[]] {
|
|
85
|
-
let attributes: Attrs<E>;
|
|
86
|
-
if (isAttrs(attrs)) {
|
|
87
|
-
attributes = attrs;
|
|
88
|
-
} else {
|
|
89
|
-
if (attrs !== undefined) {
|
|
90
|
-
children.unshift(attrs as DenormChildren);
|
|
91
|
-
}
|
|
92
|
-
attributes = defaultAttrs;
|
|
93
|
-
}
|
|
94
|
-
// Drop conditional/absent children (React's `{cond && <X/>}` idiom): null,
|
|
95
|
-
// undefined, and false. `0` and `""` are kept — they are legitimate text
|
|
96
|
-
// nodes, and dropping them would reintroduce the React `0`-renders-nothing bug.
|
|
97
|
-
return [attributes, children.flat().filter((c) => c != null && c !== false)];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function up<E extends Element>(
|
|
101
|
-
element: Omit<E, "update">,
|
|
102
|
-
attrs?: DenormAttrs<E>,
|
|
103
|
-
...children: DenormChildren[]
|
|
104
|
-
): E {
|
|
105
|
-
return update(element, ...normalizeArguments(attrs, children)) as E;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* (Re)attach a single listener for `type`, replacing any handler `events`
|
|
110
|
-
* already tracks for it, so each event has exactly one live handler — no
|
|
111
|
-
* stacking, no orphans. `events` is the element's own `[Events]` map; it
|
|
112
|
-
* stays the single source of truth.
|
|
113
|
-
*/
|
|
114
|
-
function setListener(
|
|
115
|
-
target: EventTarget,
|
|
116
|
-
events: Map<string, EventHandler>,
|
|
117
|
-
type: string,
|
|
118
|
-
handler: EventHandler,
|
|
119
|
-
): void {
|
|
120
|
-
if (events.has(type)) {
|
|
121
|
-
target.removeEventListener(type, assertExists(events.get(type)));
|
|
122
|
-
}
|
|
123
|
-
target.addEventListener(type, handler);
|
|
124
|
-
events.set(type, handler);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** Detach the listener `events` tracks for `type`, if any, and forget it. */
|
|
128
|
-
function clearListener(
|
|
129
|
-
target: EventTarget,
|
|
130
|
-
events: Map<string, EventHandler>,
|
|
131
|
-
type: string,
|
|
132
|
-
): void {
|
|
133
|
-
if (events.has(type)) {
|
|
134
|
-
target.removeEventListener(type, assertExists(events.get(type)));
|
|
135
|
-
events.delete(type);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function update(
|
|
140
|
-
element: Omit<Element, "update">,
|
|
141
|
-
attrs: Attrs<Element>,
|
|
142
|
-
children: DenormChildren[],
|
|
143
|
-
): Element {
|
|
144
|
-
element[Events] ??= new Map<string, EventHandler>();
|
|
145
|
-
const $events = element[Events];
|
|
146
|
-
|
|
147
|
-
for (const [k, v] of Object.entries(attrs.events ?? {})) {
|
|
148
|
-
if (v === null) {
|
|
149
|
-
clearListener(element, $events, k);
|
|
150
|
-
} else if (v !== undefined) {
|
|
151
|
-
setListener(element, $events, k, v);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
element.toggleAttribute("data-hydrate", $events.size > 0);
|
|
155
|
-
|
|
156
|
-
const _style = (element as { style?: Partial<CSSStyleDeclaration> }).style;
|
|
157
|
-
if (_style) {
|
|
158
|
-
if (typeof attrs.style === "string") {
|
|
159
|
-
_style.cssText = attrs.style;
|
|
160
|
-
} else {
|
|
161
|
-
for (const [k, v] of Object.entries(
|
|
162
|
-
(attrs.style as Partial<CSSStyleDeclaration>) ?? {},
|
|
163
|
-
)) {
|
|
164
|
-
// @ts-expect-error Object.entries is unable to statically look into args
|
|
165
|
-
_style[k] = v;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
for (const [k, v] of Object.entries(attrs)) {
|
|
171
|
-
if (k === "style") {
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (k === "events") {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (k === "class") {
|
|
180
|
-
const cs = Array.isArray(v) ? v : String(v).split(/\s+/m).filter(Boolean);
|
|
181
|
-
for (const c of cs) {
|
|
182
|
-
if (c.startsWith("!")) {
|
|
183
|
-
element.classList.remove(c.substring(1));
|
|
184
|
-
} else {
|
|
185
|
-
element.classList.add(c);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (!v) {
|
|
192
|
-
element.removeAttribute(k);
|
|
193
|
-
} else if (v === true) {
|
|
194
|
-
element.setAttribute(k, k);
|
|
195
|
-
} else {
|
|
196
|
-
element.setAttribute(k, String(v));
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (children?.length > 0) {
|
|
201
|
-
reconcileChildren(
|
|
202
|
-
element,
|
|
203
|
-
children[0] === CLEAR ? [] : (children as (string | Node)[]),
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
(element as Element).update ??= (attrs, ...children) =>
|
|
208
|
-
update(element, ...normalizeArguments(attrs, children));
|
|
209
|
-
|
|
210
|
-
return element as Element;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Reconcile `element`'s mounted children against expected `children`, mutating the live DOM in place.
|
|
215
|
-
*/
|
|
216
|
-
export function reconcileChildren(
|
|
217
|
-
element: Node,
|
|
218
|
-
children: (string | Node)[],
|
|
219
|
-
): void {
|
|
220
|
-
const desired = findDesiredNodes(element, children);
|
|
221
|
-
|
|
222
|
-
const { mountedSet, unclaimed } = findUnclaimedNodes(desired, element);
|
|
223
|
-
|
|
224
|
-
patchUnclaimedNodes(desired, mountedSet, unclaimed);
|
|
225
|
-
|
|
226
|
-
clearUnwantedNodes(desired, element);
|
|
227
|
-
|
|
228
|
-
insertDesiredNodes(element, desired);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function findDesiredNodes(element: Node, children: (string | Node)[]): Node[] {
|
|
232
|
-
const doc = element.ownerDocument ?? window.document;
|
|
233
|
-
const desired: Node[] = children.map((child) =>
|
|
234
|
-
typeof child === "string" ? doc.createTextNode(child) : child,
|
|
235
|
-
);
|
|
236
|
-
return desired;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function insertDesiredNodes(element: Node, desired: Node[]) {
|
|
240
|
-
let cursor: ChildNode | null = element.firstChild;
|
|
241
|
-
for (const node of desired) {
|
|
242
|
-
if (node === cursor) {
|
|
243
|
-
cursor = cursor.nextSibling;
|
|
244
|
-
} else {
|
|
245
|
-
element.insertBefore(node, cursor);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function clearUnwantedNodes(desired: Node[], element: Node) {
|
|
251
|
-
const keep = new Set(desired);
|
|
252
|
-
for (const mounted of Array.from(element.childNodes)) {
|
|
253
|
-
if (!keep.has(mounted)) {
|
|
254
|
-
element.removeChild(mounted);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function patchUnclaimedNodes(
|
|
260
|
-
desired: Node[],
|
|
261
|
-
mountedSet: Set<Node>,
|
|
262
|
-
unclaimed: Node[],
|
|
263
|
-
) {
|
|
264
|
-
let claim = 0;
|
|
265
|
-
for (let i = 0; i < desired.length; i++) {
|
|
266
|
-
const node = desired[i];
|
|
267
|
-
if (node.nodeType !== ELEMENT_NODE || mountedSet.has(node)) {
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
if (claim < unclaimed.length) {
|
|
271
|
-
if (unclaimed[claim].nodeName === node.nodeName) {
|
|
272
|
-
patchNode(unclaimed[claim] as Element, node as Element);
|
|
273
|
-
desired[i] = unclaimed[claim];
|
|
274
|
-
}
|
|
275
|
-
claim++;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function findUnclaimedNodes(
|
|
281
|
-
desired: Node[],
|
|
282
|
-
element: Node,
|
|
283
|
-
): { mountedSet: Set<Node>; unclaimed: Node[] } {
|
|
284
|
-
const unclaimed: Node[] = [];
|
|
285
|
-
const desiredSet = new Set<Node>(desired);
|
|
286
|
-
const mountedSet = new Set<Node>(element.childNodes);
|
|
287
|
-
for (const mounted of Array.from(element.childNodes)) {
|
|
288
|
-
if (mounted.nodeType === ELEMENT_NODE && !desiredSet.has(mounted)) {
|
|
289
|
-
unclaimed.push(mounted);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return { mountedSet, unclaimed };
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export function patchNode(kept: Element, fresh: Element): void {
|
|
296
|
-
assert(kept.nodeName === fresh.nodeName, "patching nodes of different types");
|
|
297
|
-
|
|
298
|
-
// Remove `kept` attributes that aren't on `fresh`, then add `fresh` attributes not on `kept`.
|
|
299
|
-
for (const { name } of Array.from(kept.attributes)) {
|
|
300
|
-
if (!fresh.hasAttribute(name)) {
|
|
301
|
-
kept.removeAttribute(name);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
for (const { name, value } of Array.from(fresh.attributes)) {
|
|
305
|
-
if (kept.getAttribute(name) !== value) {
|
|
306
|
-
kept.setAttribute(name, value);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Similar to attributes, but operating in a map on the side rather than the node itself.
|
|
311
|
-
kept[Events] ??= new Map<string, EventHandler>();
|
|
312
|
-
const keptEvents = kept[Events];
|
|
313
|
-
const freshEvents = fresh[Events] ?? new Map<string, EventHandler>();
|
|
314
|
-
for (const [type] of keptEvents) {
|
|
315
|
-
if (!freshEvents.has(type)) {
|
|
316
|
-
clearListener(kept, keptEvents, type);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
for (const [type, handler] of freshEvents) {
|
|
320
|
-
setListener(kept, keptEvents, type, handler);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Custom elements rebuild their own subtrees
|
|
324
|
-
if (customElements.get(kept.localName)) return;
|
|
325
|
-
|
|
326
|
-
reconcileChildren(kept, Array.from(fresh.childNodes));
|
|
327
|
-
}
|
package/src/dom/fc.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CLEAR,
|
|
3
|
-
type DenormChildren,
|
|
4
|
-
type DomAttrs,
|
|
5
|
-
normalizeArguments,
|
|
6
|
-
reconcileChildren,
|
|
7
|
-
update,
|
|
8
|
-
} from "./dom.ts";
|
|
9
|
-
|
|
10
|
-
export type Attrs<S> = S & Partial<DomAttrs>;
|
|
11
|
-
|
|
12
|
-
export const State = Symbol();
|
|
13
|
-
export interface FCComponent<Props extends object, State extends object>
|
|
14
|
-
extends Element {
|
|
15
|
-
[State]?: Partial<State>;
|
|
16
|
-
update(
|
|
17
|
-
attrs?: Partial<Attrs<Props> & DomAttrs> | DenormChildren,
|
|
18
|
-
...children: DenormChildren[]
|
|
19
|
-
): this;
|
|
20
|
-
}
|
|
21
|
-
export type RenderFn<Props extends object, State extends object> = (
|
|
22
|
-
el: FCComponent<Props, State>,
|
|
23
|
-
attrs: Attrs<Props>,
|
|
24
|
-
children: DenormChildren[],
|
|
25
|
-
) => Element | Element[];
|
|
26
|
-
|
|
27
|
-
export type FCComponentCtor<Props extends object, State extends object> = (
|
|
28
|
-
attrs?: Attrs<Props> | DenormChildren,
|
|
29
|
-
...children: DenormChildren[]
|
|
30
|
-
) => FCComponent<Props, State>;
|
|
31
|
-
|
|
32
|
-
export function FC<Props extends object, State extends object = object>(
|
|
33
|
-
name: string,
|
|
34
|
-
component: RenderFn<Props, State>,
|
|
35
|
-
): FCComponentCtor<Props, State> {
|
|
36
|
-
class FCImpl extends HTMLElement implements FCComponent<Props, State> {
|
|
37
|
-
[State]: Partial<State> = {};
|
|
38
|
-
#attrs: Attrs<Props> = {} as Attrs<Props>;
|
|
39
|
-
#children: DenormChildren[] = [];
|
|
40
|
-
|
|
41
|
-
update(
|
|
42
|
-
attrs?: Attrs<Props> | DenormChildren,
|
|
43
|
-
...children: DenormChildren[]
|
|
44
|
-
) {
|
|
45
|
-
[attrs, children] = normalizeArguments(attrs, children) as [
|
|
46
|
-
Attrs<Props>,
|
|
47
|
-
DenormChildren[],
|
|
48
|
-
];
|
|
49
|
-
if (children[0] === CLEAR) {
|
|
50
|
-
this.#children = [];
|
|
51
|
-
} else if (children.length > 0) {
|
|
52
|
-
this.#children = children;
|
|
53
|
-
}
|
|
54
|
-
this.#attrs = { ...this.#attrs, ...(attrs as Attrs<Props>) };
|
|
55
|
-
|
|
56
|
-
// Apply updates from the attrs to the dom node itself
|
|
57
|
-
update(this, this.#attrs, []);
|
|
58
|
-
|
|
59
|
-
// Re-run the component function using new element, attrs, and children.
|
|
60
|
-
const rendered = [component(this, this.#attrs, this.#children)];
|
|
61
|
-
reconcileChildren(this, rendered.flat());
|
|
62
|
-
return this;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
customElements.define(name, FCImpl);
|
|
67
|
-
|
|
68
|
-
const ctor: FCComponentCtor<Props, State> = (
|
|
69
|
-
attrs?: Attrs<Props> | DenormChildren,
|
|
70
|
-
...children: DenormChildren[]
|
|
71
|
-
): FCComponent<Props, State> => {
|
|
72
|
-
const element = window.document.createElement(name) as FCComponent<
|
|
73
|
-
Props,
|
|
74
|
-
State
|
|
75
|
-
>;
|
|
76
|
-
element.update(attrs, ...children);
|
|
77
|
-
return element;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
return ctor;
|
|
81
|
-
}
|
package/src/dom/form/form.app.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { button, div, main, small } from "../html.ts";
|
|
2
|
-
import { Form, Input } from "./form.ts";
|
|
3
|
-
|
|
4
|
-
export const App = () =>
|
|
5
|
-
main(
|
|
6
|
-
Form(
|
|
7
|
-
{
|
|
8
|
-
events: {
|
|
9
|
-
submit(event) {
|
|
10
|
-
console.log(
|
|
11
|
-
"Should see fields for firstname, lastname, email, etc",
|
|
12
|
-
);
|
|
13
|
-
console.log(event);
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
div(
|
|
18
|
-
{ class: "grid" },
|
|
19
|
-
Input({ id: "firstname", placeholder: "First name" }),
|
|
20
|
-
Input({ id: "lastname", placeholder: "Last name" }),
|
|
21
|
-
),
|
|
22
|
-
Input(
|
|
23
|
-
{
|
|
24
|
-
id: "email",
|
|
25
|
-
type: "email",
|
|
26
|
-
placeholder: "Email address",
|
|
27
|
-
required: true,
|
|
28
|
-
},
|
|
29
|
-
small("We will never share your email with anyone."),
|
|
30
|
-
),
|
|
31
|
-
button({ type: "submit" }, "Submit"),
|
|
32
|
-
div(
|
|
33
|
-
{ class: "grid" },
|
|
34
|
-
Input({ id: "valid", placeholder: "Valid", "aria-invalid": "false" }),
|
|
35
|
-
Input({
|
|
36
|
-
id: "invalid",
|
|
37
|
-
placeholder: "Invalid",
|
|
38
|
-
"aria-invalid": "true",
|
|
39
|
-
}),
|
|
40
|
-
Input({ id: "disabled", placeholder: "Disabled", disabled: true }),
|
|
41
|
-
Input({ id: "readonly", value: "Readonly", readOnly: true }),
|
|
42
|
-
),
|
|
43
|
-
),
|
|
44
|
-
);
|