@davidsouther/jiffies 1.0.0 → 1.1.0
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/build/assert.d.ts +23 -23
- package/build/assert.js +33 -33
- package/build/case.d.ts +1 -1
- package/build/case.js +5 -5
- package/build/components/button_bar.d.ts +8 -8
- package/build/components/button_bar.js +27 -27
- package/build/components/inline_edit.d.ts +12 -12
- package/build/components/inline_edit.js +48 -48
- package/build/components/logger.d.ts +6 -7
- package/build/components/logger.js +22 -22
- package/build/components/select.d.ts +13 -10
- package/build/components/select.js +3 -3
- package/build/components/test.d.ts +1 -1
- package/build/components/test.js +2 -2
- package/build/components/virtual_scroll.d.ts +40 -41
- package/build/components/virtual_scroll.js +94 -94
- package/build/components/virtual_scroll.test.d.ts +1 -1
- package/build/components/virtual_scroll.test.js +21 -21
- package/build/context.d.ts +15 -15
- package/build/context.js +43 -43
- package/build/context.test.d.ts +1 -1
- package/build/context.test.js +46 -46
- package/build/debounce.d.ts +1 -1
- package/build/debounce.js +7 -7
- package/build/display.d.ts +5 -5
- package/build/display.js +11 -11
- package/build/dom/css/border.d.ts +11 -11
- package/build/dom/css/border.js +27 -27
- package/build/dom/css/constants.d.ts +31 -31
- package/build/dom/css/constants.js +28 -28
- package/build/dom/css/core.d.ts +5 -5
- package/build/dom/css/core.js +24 -24
- package/build/dom/css/fstyle.d.ts +5 -5
- package/build/dom/css/fstyle.js +32 -32
- package/build/dom/css/sizing.d.ts +5 -5
- package/build/dom/css/sizing.js +10 -10
- package/build/dom/dom.d.ts +26 -27
- package/build/dom/dom.js +95 -95
- package/build/dom/fc.d.ts +14 -14
- package/build/dom/fc.js +36 -35
- package/build/dom/fc.test.d.ts +1 -1
- package/build/dom/fc.test.js +21 -21
- package/build/dom/form/form.app.d.ts +1 -1
- package/build/dom/form/form.app.js +23 -23
- package/build/dom/form/form.d.ts +26 -26
- package/build/dom/form/form.js +34 -34
- package/build/dom/form/form.test.js +1 -1
- package/build/dom/html.d.ts +113 -117
- package/build/dom/html.js +114 -114
- package/build/dom/html.test.d.ts +1 -1
- package/build/dom/html.test.js +58 -58
- package/build/dom/provide.d.ts +3 -3
- package/build/dom/provide.js +7 -7
- package/build/dom/router/link.d.ts +6 -6
- package/build/dom/router/link.js +3 -3
- package/build/dom/router/router.d.ts +12 -12
- package/build/dom/router/router.js +49 -49
- package/build/dom/svg.d.ts +64 -64
- package/build/dom/svg.js +65 -65
- package/build/dom/test.d.ts +1 -1
- package/build/dom/test.js +2 -2
- package/build/dom/types/css.d.ts +6612 -6612
- package/build/dom/types/css.js +23 -23
- package/build/dom/types/dom.js +1 -1
- package/build/dom/types/html.d.ts +616 -616
- package/build/dom/types/html.js +1 -1
- package/build/dom/xml.d.ts +1 -1
- package/build/dom/xml.js +4 -5
- package/build/equal.d.ts +5 -5
- package/build/equal.js +37 -37
- package/build/equal.test.d.ts +1 -1
- package/build/equal.test.js +20 -20
- package/build/flags.d.ts +7 -7
- package/build/flags.js +48 -48
- package/build/flags.test.d.ts +1 -1
- package/build/flags.test.js +35 -35
- package/build/fs.d.ts +48 -48
- package/build/fs.js +144 -144
- package/build/fs.test.d.ts +1 -1
- package/build/fs.test.js +43 -43
- package/build/generator.d.ts +1 -1
- package/build/generator.js +10 -10
- package/build/generator.test.d.ts +1 -1
- package/build/generator.test.js +24 -24
- package/build/is_browser.d.ts +1 -1
- package/build/is_browser.js +1 -1
- package/build/loader.d.mts +22 -22
- package/build/loader.mjs +35 -35
- package/build/lock.d.ts +1 -1
- package/build/lock.js +23 -23
- package/build/lock.test.d.ts +1 -1
- package/build/lock.test.js +16 -16
- package/build/log.d.ts +26 -26
- package/build/log.js +46 -46
- package/build/observable/observable.d.ts +83 -0
- package/build/observable/observable.js +148 -0
- package/build/observable/observable.test.d.ts +1 -0
- package/build/observable/observable.test.js +21 -0
- package/build/range.d.ts +1 -1
- package/build/range.js +7 -7
- package/build/result.d.ts +31 -31
- package/build/result.js +65 -65
- package/build/result.test.d.ts +1 -1
- package/build/result.test.js +71 -71
- package/build/safe.d.ts +1 -1
- package/build/safe.js +10 -10
- package/build/scope/describe.d.ts +18 -14
- package/build/scope/describe.js +61 -52
- package/build/scope/display/console.d.ts +2 -2
- package/build/scope/display/console.js +21 -21
- package/build/scope/display/dom.d.ts +3 -3
- package/build/scope/display/dom.js +26 -26
- package/build/scope/display/junit.d.ts +2 -2
- package/build/scope/display/junit.js +17 -17
- package/build/scope/execute.d.ts +12 -12
- package/build/scope/execute.js +85 -85
- package/build/scope/expect.d.ts +23 -23
- package/build/scope/expect.js +108 -108
- package/build/scope/fix.d.ts +4 -4
- package/build/scope/fix.js +22 -22
- package/build/scope/index.d.ts +3 -3
- package/build/scope/index.js +3 -3
- package/build/scope/scope.d.ts +17 -17
- package/build/scope/scope.js +1 -1
- package/build/server/http/apps.d.ts +5 -5
- package/build/server/http/apps.js +23 -23
- package/build/server/http/css.d.ts +5 -5
- package/build/server/http/css.js +50 -47
- package/build/server/http/index.d.ts +21 -21
- package/build/server/http/index.js +73 -73
- package/build/server/http/response.d.ts +4 -4
- package/build/server/http/response.js +40 -40
- package/build/server/http/sitemap.d.ts +2 -2
- package/build/server/http/sitemap.js +42 -42
- package/build/server/http/static.d.ts +2 -2
- package/build/server/http/static.js +21 -21
- package/build/server/http/typescript.d.ts +5 -5
- package/build/server/http/typescript.js +40 -40
- package/build/server/main.d.ts +2 -2
- package/build/server/main.js +9 -9
- package/build/test.d.mts +2 -2
- package/build/test.mjs +23 -23
- package/build/test_all.d.ts +7 -7
- package/build/test_all.js +18 -18
- package/build/transpile.d.mts +3 -3
- package/build/transpile.mjs +18 -18
- package/package.json +3 -3
- package/src/components/logger.ts +2 -3
- package/src/components/virtual_scroll.test.ts +4 -4
- package/src/components/virtual_scroll.ts +8 -8
- package/src/diff.test.ts +48 -0
- package/src/diff.ts +84 -0
- package/src/dom/dom.ts +73 -61
- package/src/dom/fc.ts +10 -9
- package/src/dom/html.test.ts +5 -5
- package/src/dom/html.ts +7 -10
- package/src/dom/observable.test.ts +43 -0
- package/src/dom/observable.ts +11 -0
- package/src/dom/router/router.ts +4 -4
- package/src/dom/test.ts +5 -1
- package/src/dom/xml.ts +1 -2
- package/src/index.html +6 -3
- package/src/observable/_notes +21 -8
- package/src/observable/event.ts +93 -0
- package/src/observable/observable.test.ts +73 -0
- package/src/observable/observable.ts +403 -0
- package/src/scope/describe.ts +14 -1
- package/src/scope/display/dom.ts +2 -2
- package/src/scope/execute.ts +2 -5
- package/src/server/http/css.ts +3 -1
- package/src/test_all.ts +10 -8
- package/src/observable/observable._js +0 -175
package/build/test.mjs
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { parse } from "./flags.js";
|
|
3
|
-
import { execute } from "./scope/execute.js";
|
|
4
|
-
import { asXML } from "./scope/display/junit.js";
|
|
5
|
-
import { onConsole } from "./scope/display/console.js";
|
|
6
|
-
await import("./test_all.js");
|
|
7
|
-
(async function () {
|
|
8
|
-
const results = await execute();
|
|
9
|
-
const FLAGS = parse(process.argv);
|
|
10
|
-
switch (FLAGS.asString("mode", "console")) {
|
|
11
|
-
case "junit":
|
|
12
|
-
const xml = asXML(results);
|
|
13
|
-
console.log(xml);
|
|
14
|
-
break;
|
|
15
|
-
case "console":
|
|
16
|
-
default:
|
|
17
|
-
onConsole(results);
|
|
18
|
-
break;
|
|
19
|
-
}
|
|
20
|
-
if (results.failed > 0) {
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
})();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parse } from "./flags.js";
|
|
3
|
+
import { execute } from "./scope/execute.js";
|
|
4
|
+
import { asXML } from "./scope/display/junit.js";
|
|
5
|
+
import { onConsole } from "./scope/display/console.js";
|
|
6
|
+
await import("./test_all.js");
|
|
7
|
+
(async function () {
|
|
8
|
+
const results = await execute();
|
|
9
|
+
const FLAGS = parse(process.argv);
|
|
10
|
+
switch (FLAGS.asString("mode", "console")) {
|
|
11
|
+
case "junit":
|
|
12
|
+
const xml = asXML(results);
|
|
13
|
+
console.log(xml);
|
|
14
|
+
break;
|
|
15
|
+
case "console":
|
|
16
|
+
default:
|
|
17
|
+
onConsole(results);
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
if (results.failed > 0) {
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
})();
|
package/build/test_all.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import "./context.test.js";
|
|
2
|
-
import "./equal.test.js";
|
|
3
|
-
import "./flags.test.js";
|
|
4
|
-
import "./fs.test.js";
|
|
5
|
-
import "./generator.test.js";
|
|
6
|
-
import "./lock.test.js";
|
|
7
|
-
import "./result.test.js";
|
|
1
|
+
import "./context.test.js";
|
|
2
|
+
import "./equal.test.js";
|
|
3
|
+
import "./flags.test.js";
|
|
4
|
+
import "./fs.test.js";
|
|
5
|
+
import "./generator.test.js";
|
|
6
|
+
import "./lock.test.js";
|
|
7
|
+
import "./result.test.js";
|
package/build/test_all.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
// This file must be .js for imports to run. Unused imports in .ts files are
|
|
2
|
-
// discarded during transpilation.
|
|
3
|
-
import { describe, expect, it } from "./scope/index.js";
|
|
4
|
-
import "./context.test.js";
|
|
5
|
-
import "./equal.test.js";
|
|
6
|
-
import "./flags.test.js";
|
|
7
|
-
import "./fs.test.js";
|
|
8
|
-
import "./generator.test.js";
|
|
9
|
-
import "./lock.test.js";
|
|
10
|
-
import "./result.test.js";
|
|
11
|
-
describe("Test executor", () => {
|
|
12
|
-
it("matches equality", () => {
|
|
13
|
-
expect(1).toBe(1);
|
|
14
|
-
});
|
|
15
|
-
it("fails on inequality", () => {
|
|
16
|
-
expect(() => expect(1).toBe(2)).toThrow();
|
|
17
|
-
});
|
|
18
|
-
});
|
|
1
|
+
// This file must be .js for imports to run. Unused imports in .ts files are
|
|
2
|
+
// discarded during transpilation.
|
|
3
|
+
import { describe, expect, it } from "./scope/index.js";
|
|
4
|
+
import "./context.test.js";
|
|
5
|
+
import "./equal.test.js";
|
|
6
|
+
import "./flags.test.js";
|
|
7
|
+
import "./fs.test.js";
|
|
8
|
+
import "./generator.test.js";
|
|
9
|
+
import "./lock.test.js";
|
|
10
|
+
import "./result.test.js";
|
|
11
|
+
describe("Test executor", () => {
|
|
12
|
+
it("matches equality", () => {
|
|
13
|
+
expect(1).toBe(1);
|
|
14
|
+
});
|
|
15
|
+
it("fails on inequality", () => {
|
|
16
|
+
expect(() => expect(1).toBe(2)).toThrow();
|
|
17
|
+
});
|
|
18
|
+
});
|
package/build/transpile.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export function transpile(url: string, get: () => Promise<{
|
|
2
|
-
toString(): string;
|
|
3
|
-
}>): Promise<any>;
|
|
1
|
+
export function transpile(url: string, get: () => Promise<{
|
|
2
|
+
toString(): string;
|
|
3
|
+
}>): Promise<any>;
|
package/build/transpile.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
// @ts-ignore
|
|
2
|
-
import ts from "typescript";
|
|
3
|
-
const compilerOptions = {
|
|
4
|
-
target: ts.ScriptTarget.ESNext,
|
|
5
|
-
module: ts.ModuleKind.ESNext,
|
|
6
|
-
inlineSourceMap: true,
|
|
7
|
-
inlineSources: true,
|
|
8
|
-
};
|
|
9
|
-
const tsmap = new Map();
|
|
10
|
-
export async function transpile(
|
|
11
|
-
/** @type string */ url,
|
|
12
|
-
/** @type {() => Promise<{toString(): string}>} */ get) {
|
|
13
|
-
if (!tsmap.has(url) || true) {
|
|
14
|
-
const source = ts.transpile((await get()).toString(), compilerOptions, url, undefined, url);
|
|
15
|
-
tsmap.set(url, source);
|
|
16
|
-
}
|
|
17
|
-
return tsmap.get(url);
|
|
18
|
-
}
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
const compilerOptions = {
|
|
4
|
+
target: ts.ScriptTarget.ESNext,
|
|
5
|
+
module: ts.ModuleKind.ESNext,
|
|
6
|
+
inlineSourceMap: true,
|
|
7
|
+
inlineSources: true,
|
|
8
|
+
};
|
|
9
|
+
const tsmap = new Map();
|
|
10
|
+
export async function transpile(
|
|
11
|
+
/** @type string */ url,
|
|
12
|
+
/** @type {() => Promise<{toString(): string}>} */ get) {
|
|
13
|
+
if (!tsmap.has(url) || true) {
|
|
14
|
+
const source = ts.transpile((await get()).toString(), compilerOptions, url, undefined, url);
|
|
15
|
+
tsmap.set(url, source);
|
|
16
|
+
}
|
|
17
|
+
return tsmap.get(url);
|
|
18
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davidsouther/jiffies",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"displayName": "JEFRi Jiffies",
|
|
6
6
|
"type": "module",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^17.0.24",
|
|
34
|
-
"typescript": "4.7.1
|
|
34
|
+
"typescript": "^4.7.1"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"sass": "^1.49.9"
|
|
38
38
|
}
|
|
39
|
-
}
|
|
39
|
+
}
|
package/src/components/logger.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { display, Display } from "../display.js";
|
|
2
|
-
import { DOMElement, Updatable } from "../dom/dom.js";
|
|
3
2
|
import { div, span, ul, li, pre, code } from "../dom/html.js";
|
|
4
3
|
import { LEVEL, Logger } from "../log.js";
|
|
5
4
|
|
|
6
5
|
export interface HTMLLogger extends Logger {
|
|
7
|
-
root:
|
|
6
|
+
root: Element;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
export function isHTMLLogger(logger: Logger): logger is HTMLLogger {
|
|
@@ -12,7 +11,7 @@ export function isHTMLLogger(logger: Logger): logger is HTMLLogger {
|
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
export function makeHTMLLogger(name: string): HTMLLogger {
|
|
15
|
-
let log:
|
|
14
|
+
let log: Element;
|
|
16
15
|
const root = div(div(span(name)), (log = ul()));
|
|
17
16
|
const logger: Partial<HTMLLogger> = { level: LEVEL.INFO, root };
|
|
18
17
|
|
|
@@ -15,13 +15,13 @@ describe("VirtualScroll", () => {
|
|
|
15
15
|
get: arrayAdapter(data),
|
|
16
16
|
row: (i) => div(`${i}`),
|
|
17
17
|
};
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
const scroll = VirtualScroll(props);
|
|
20
20
|
|
|
21
|
-
expect(scroll[State]
|
|
22
|
-
expect(scroll[State]
|
|
21
|
+
expect(scroll[State]?.bufferedItems).toBe(9);
|
|
22
|
+
expect(scroll[State]?.data).toEqual([0, 1, 2, 3, 4]);
|
|
23
23
|
//expect(scroll.state.topPaddingHeight).toBe(0);
|
|
24
|
-
expect(scroll[State]
|
|
24
|
+
expect(scroll[State]?.viewportHeight).toBe(60);
|
|
25
25
|
//expect(scroll.state.totalHeight).toBe(200);
|
|
26
26
|
});
|
|
27
27
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { debounce } from "../debounce.js";
|
|
2
2
|
import { FC, State } from "../dom/fc.js";
|
|
3
|
-
import { div
|
|
3
|
+
import { div } from "../dom/html.js";
|
|
4
4
|
|
|
5
5
|
export interface VirtualScrollSettings {
|
|
6
6
|
minIndex: number;
|
|
@@ -22,7 +22,7 @@ export function arrayAdapter<T>(data: T[]): VirtualScrollDataAdapter<T> {
|
|
|
22
22
|
export interface VirtualScrollProps<T, U extends HTMLElement> {
|
|
23
23
|
settings: Partial<VirtualScrollSettings>;
|
|
24
24
|
get: VirtualScrollDataAdapter<T>;
|
|
25
|
-
row: (t: T) =>
|
|
25
|
+
row: (t: T) => U;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function fillVirtualScrollSettings(
|
|
@@ -30,7 +30,7 @@ export function fillVirtualScrollSettings(
|
|
|
30
30
|
): VirtualScrollSettings {
|
|
31
31
|
const {
|
|
32
32
|
minIndex = 0,
|
|
33
|
-
maxIndex =
|
|
33
|
+
maxIndex = Number.MAX_SAFE_INTEGER,
|
|
34
34
|
startIndex = 0,
|
|
35
35
|
itemHeight = 20,
|
|
36
36
|
count = maxIndex - minIndex + 1,
|
|
@@ -121,7 +121,7 @@ interface VirtualScrollState<T, U extends HTMLElement = HTMLElement> {
|
|
|
121
121
|
bottomPaddingHeight: number; // px
|
|
122
122
|
toleranceHeight: number; // px
|
|
123
123
|
data: T[];
|
|
124
|
-
rows:
|
|
124
|
+
rows: U[];
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
// export interface VirtualScroll<T, U extends HTMLElement> {
|
|
@@ -130,8 +130,8 @@ interface VirtualScrollState<T, U extends HTMLElement = HTMLElement> {
|
|
|
130
130
|
// }
|
|
131
131
|
|
|
132
132
|
export const VirtualScroll = FC<
|
|
133
|
-
VirtualScrollProps<
|
|
134
|
-
VirtualScrollState<
|
|
133
|
+
VirtualScrollProps<any, HTMLElement>,
|
|
134
|
+
VirtualScrollState<any, HTMLElement>
|
|
135
135
|
>("virtual-scroll", (element, props) => {
|
|
136
136
|
const settings = fillVirtualScrollSettings(props.settings);
|
|
137
137
|
const state = (element[State] = {
|
|
@@ -163,14 +163,14 @@ export const VirtualScroll = FC<
|
|
|
163
163
|
state.topPaddingHeight = newState.topPaddingHeight;
|
|
164
164
|
state.bottomPaddingHeight = newState.bottomPaddingHeight;
|
|
165
165
|
state.data = newState.data;
|
|
166
|
-
|
|
166
|
+
state.rows = state.data.map(props.row);
|
|
167
167
|
|
|
168
168
|
viewportElement.update(
|
|
169
169
|
div({
|
|
170
170
|
class: "VirtualScroll__topPadding",
|
|
171
171
|
style: { height: `${state.topPaddingHeight}px` },
|
|
172
172
|
}),
|
|
173
|
-
...(
|
|
173
|
+
...(state.rows ?? []).map((row, i) =>
|
|
174
174
|
div(
|
|
175
175
|
{
|
|
176
176
|
class: `VirtualScroll__item_${i}`,
|
package/src/diff.test.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { diff } from "./diff.js";
|
|
2
|
+
import { describe, it } from "./scope/describe.js";
|
|
3
|
+
import { expect } from "./scope/expect.js";
|
|
4
|
+
|
|
5
|
+
describe("diff", () => {
|
|
6
|
+
it("diffs primitives", () => {
|
|
7
|
+
const diffed = diff(1, 2);
|
|
8
|
+
expect(diffed).toEqual([{ key: "", left: 1, right: 2 }]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("diffs objects", () => {
|
|
12
|
+
const diffed = diff({ a: 1, b: 2 }, { a: 2, b: 2 });
|
|
13
|
+
expect(diffed).toEqual([{ key: "a", left: 1, right: 2 }]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("diffs nested objects", () => {
|
|
17
|
+
const diffed = diff({ a: { c: 1 }, b: 2 }, { a: { c: 2 }, b: 2 });
|
|
18
|
+
expect(diffed).toEqual([
|
|
19
|
+
{ key: "a", children: [{ key: "c", left: 1, right: 2 }] },
|
|
20
|
+
]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("diffs missing sides", () => {
|
|
24
|
+
const diffed = diff<{ a?: number; b?: number }>({ a: 1 }, { b: 2 });
|
|
25
|
+
expect(diffed).toEqual([
|
|
26
|
+
{ key: "a", left: 1, right: undefined },
|
|
27
|
+
{ key: "b", left: undefined, right: 2 },
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("diffs arrays", () => {
|
|
32
|
+
const diffed = diff<number[]>([1, 2, 3], [1, 4, 3]);
|
|
33
|
+
expect(diffed).toEqual([{ key: 1, left: 2, right: 4 }]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("diffs objects in an array", () => {
|
|
37
|
+
const diffed = diff(
|
|
38
|
+
[{ a: { b: 1 } }, { a: { b: 2 } }, { a: { b: 3 } }],
|
|
39
|
+
[{ a: { b: 1 } }, { a: { b: 4 } }, { a: { b: 3 } }]
|
|
40
|
+
);
|
|
41
|
+
expect(diffed).toEqual([
|
|
42
|
+
{
|
|
43
|
+
key: 1,
|
|
44
|
+
children: [{ key: "a", children: [{ key: "b", left: 2, right: 4 }] }],
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
});
|
package/src/diff.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { range } from "./range.js";
|
|
2
|
+
import { isSome, None, Option, Some } from "./result.js";
|
|
3
|
+
|
|
4
|
+
export const DiffA = Symbol("A");
|
|
5
|
+
export const DiffB = Symbol("B");
|
|
6
|
+
|
|
7
|
+
export type DiffIndex = string | number;
|
|
8
|
+
export type DiffPrimitive = string | number | boolean | null | undefined;
|
|
9
|
+
|
|
10
|
+
interface DiffEntry {
|
|
11
|
+
key: DiffIndex;
|
|
12
|
+
left: DiffPrimitive;
|
|
13
|
+
right: DiffPrimitive;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface DiffList {
|
|
17
|
+
key: DiffIndex;
|
|
18
|
+
children: (DiffEntry | DiffList)[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function doDiff<T>(va: T, vb: T, k: DiffIndex): Option<DiffList | DiffEntry> {
|
|
22
|
+
if (Array.isArray(va)) {
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
return diffArray(va, vb, k);
|
|
25
|
+
}
|
|
26
|
+
if (typeof va == "object") {
|
|
27
|
+
const d = diffObject(va, vb, k);
|
|
28
|
+
if (d.children.length == 0) {
|
|
29
|
+
return None();
|
|
30
|
+
} else {
|
|
31
|
+
return Some(d);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (Object.is(va, vb)) {
|
|
35
|
+
return None();
|
|
36
|
+
} else {
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
return { key: k, left: va, right: vb };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function diffArray<T>(
|
|
43
|
+
a: Partial<T>[],
|
|
44
|
+
b: Partial<T>[],
|
|
45
|
+
key: DiffIndex
|
|
46
|
+
): Option<DiffList> {
|
|
47
|
+
const indexes = Math.max(a.length, b.length);
|
|
48
|
+
const children = range(0, indexes)
|
|
49
|
+
.map((i) => doDiff(a[i], b[i], i))
|
|
50
|
+
.filter(isSome);
|
|
51
|
+
return children.length > 0 ? { key, children } : None();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function diffObject<T>(
|
|
55
|
+
a: Partial<T>,
|
|
56
|
+
b: Partial<T>,
|
|
57
|
+
key: DiffIndex = ""
|
|
58
|
+
): DiffList {
|
|
59
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
60
|
+
const children = [...keys]
|
|
61
|
+
// @ts-ignore
|
|
62
|
+
.map((k) => doDiff(a[k], b[k], k))
|
|
63
|
+
.filter(isSome);
|
|
64
|
+
return {
|
|
65
|
+
key,
|
|
66
|
+
children,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function diff<T>(
|
|
71
|
+
a: Partial<T>,
|
|
72
|
+
b: Partial<T>
|
|
73
|
+
): (DiffEntry | DiffList)[] {
|
|
74
|
+
if (typeof a != "object" && !Object.is(a, b)) {
|
|
75
|
+
// @ts-ignore
|
|
76
|
+
return [{ key: "", left: a, right: b }];
|
|
77
|
+
}
|
|
78
|
+
return (
|
|
79
|
+
Array.isArray(a)
|
|
80
|
+
? // @ts-ignore
|
|
81
|
+
diffArray(a, b, "") ?? { children: [] }
|
|
82
|
+
: diffObject(a, b, "")
|
|
83
|
+
).children;
|
|
84
|
+
}
|
package/src/dom/dom.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Properties } from "./types/css.js";
|
|
2
2
|
|
|
3
3
|
const Events = Symbol("events");
|
|
4
4
|
export const CLEAR = Symbol("Clear children");
|
|
@@ -10,40 +10,44 @@ export type DOMElement = Element &
|
|
|
10
10
|
DocumentAndElementEventHandlers &
|
|
11
11
|
ElementCSSInlineStyle;
|
|
12
12
|
|
|
13
|
-
export type Updater<E extends DOMElement> = Omit<E, "style"> & {
|
|
14
|
-
[Events]?: Map<string, EventHandler>;
|
|
15
|
-
update?: (attrs?: DenormAttrs<E>, ...children: DenormChildren[]) => Node;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type Updatable<E extends Element> = Omit<E, "style"> & {
|
|
19
|
-
[Events]: Map<string, EventHandler>;
|
|
20
|
-
update: (attrs?: DenormAttrs<E>, ...children: DenormChildren[]) => Node;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
13
|
export type DomAttrs = {
|
|
24
|
-
class: string;
|
|
25
|
-
style: Partial<
|
|
14
|
+
class: string | string[];
|
|
15
|
+
style: Partial<Properties> | string;
|
|
16
|
+
role: "button" | "list" | "listbox";
|
|
26
17
|
events: Partial<{
|
|
27
|
-
[K in keyof HTMLElementEventMap]: EventHandler;
|
|
18
|
+
[K in keyof HTMLElementEventMap]: EventHandler | null;
|
|
28
19
|
}>;
|
|
29
20
|
};
|
|
30
21
|
|
|
31
|
-
export type Attrs<E extends Element, S = {}> = Partial<
|
|
22
|
+
export type Attrs<E extends Omit<Element, "update">, S = {}> = Partial<
|
|
23
|
+
Omit<E, "style"> & S & DomAttrs
|
|
24
|
+
>;
|
|
32
25
|
|
|
33
|
-
export type DenormAttrs<E extends Element, S = {}> =
|
|
26
|
+
export type DenormAttrs<E extends Omit<Element, "update">, S = {}> =
|
|
34
27
|
| Attrs<E, S>
|
|
35
28
|
| DenormChildren;
|
|
36
29
|
|
|
30
|
+
declare global {
|
|
31
|
+
interface Element {
|
|
32
|
+
[Events]: Map<string, EventHandler>;
|
|
33
|
+
update(attrs?: DenormAttrs<Element>, ...children: DenormChildren[]): this;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type DOMUpdates<E extends Element = Element> =
|
|
38
|
+
| [DenormAttrs<E>, ...DenormChildren[]]
|
|
39
|
+
| DenormChildren[];
|
|
40
|
+
|
|
37
41
|
function isAttrs<E extends Element>(
|
|
38
42
|
attrs: DenormAttrs<E> | undefined
|
|
39
43
|
): attrs is Attrs<E> {
|
|
40
44
|
if (!attrs) {
|
|
41
45
|
return false;
|
|
42
46
|
}
|
|
43
|
-
if (typeof attrs === "
|
|
44
|
-
return
|
|
47
|
+
if (typeof attrs === "object") {
|
|
48
|
+
return !(attrs as Node).nodeType;
|
|
45
49
|
}
|
|
46
|
-
return
|
|
50
|
+
return false;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
export function normalizeArguments<E extends Element>(
|
|
@@ -63,29 +67,31 @@ export function normalizeArguments<E extends Element>(
|
|
|
63
67
|
return [attributes, children.flat()];
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
export function up<E extends
|
|
67
|
-
element: E,
|
|
70
|
+
export function up<E extends Element>(
|
|
71
|
+
element: Omit<E, "update">,
|
|
68
72
|
attrs?: DenormAttrs<E>,
|
|
69
73
|
...children: DenormChildren[]
|
|
70
|
-
):
|
|
71
|
-
return update(element, ...normalizeArguments(attrs, children));
|
|
74
|
+
): E {
|
|
75
|
+
return update(element, ...normalizeArguments(attrs, children)) as E;
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
export function update
|
|
75
|
-
element:
|
|
76
|
-
attrs: Attrs<
|
|
78
|
+
export function update(
|
|
79
|
+
element: Omit<Element, "update">,
|
|
80
|
+
attrs: Attrs<Element>,
|
|
77
81
|
children: DenormChildren[]
|
|
78
|
-
):
|
|
82
|
+
): Element {
|
|
79
83
|
// Track events, to remove later
|
|
80
84
|
const $events = (element[Events] ??= new Map<string, EventHandler>());
|
|
81
85
|
const { style = {}, events = {}, ...rest } = attrs;
|
|
82
86
|
|
|
83
87
|
Object.entries(events as NonNullable<typeof attrs.events>).forEach(
|
|
84
88
|
([k, v]) => {
|
|
85
|
-
if (v === null
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
if (v === null) {
|
|
90
|
+
if ($events.has(k)) {
|
|
91
|
+
const listener = $events.get(k)!;
|
|
92
|
+
element.removeEventListener(k, listener);
|
|
93
|
+
}
|
|
94
|
+
} else if (v !== undefined) {
|
|
89
95
|
element.addEventListener(k as keyof ElementEventMap, v);
|
|
90
96
|
$events.set(k, v);
|
|
91
97
|
}
|
|
@@ -107,48 +113,54 @@ export function update<E extends DOMElement>(
|
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
Object.entries(rest).forEach(([k, v]) => {
|
|
110
|
-
if (k === "class"
|
|
111
|
-
v.
|
|
116
|
+
if (k === "class") {
|
|
117
|
+
v = Array.isArray(v)
|
|
118
|
+
? v
|
|
119
|
+
: (typeof v === "string" ? v : `${v}`).split(/\s+/m);
|
|
120
|
+
(v as string[])
|
|
112
121
|
.filter((s) => s !== "")
|
|
113
|
-
.forEach((c) =>
|
|
122
|
+
.forEach((c) => {
|
|
123
|
+
if (c.startsWith("!")) {
|
|
124
|
+
element.classList.remove(c.substring(1));
|
|
125
|
+
} else {
|
|
126
|
+
element.classList.add(c);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
114
130
|
}
|
|
115
131
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
k == "role" ||
|
|
132
|
+
const useNamespace =
|
|
133
|
+
element.namespaceURI &&
|
|
119
134
|
element.namespaceURI != "http://www.w3.org/1999/xhtml";
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
default:
|
|
130
|
-
if (v === "") {
|
|
131
|
-
element.removeAttributeNS(element.namespaceURI, k);
|
|
132
|
-
} else {
|
|
133
|
-
element.setAttributeNS(element.namespaceURI, k, v);
|
|
134
|
-
}
|
|
135
|
+
const remove = !v;
|
|
136
|
+
|
|
137
|
+
if (useNamespace) {
|
|
138
|
+
if (remove) {
|
|
139
|
+
element.removeAttributeNS(element.namespaceURI, k);
|
|
140
|
+
} else if (v === true) {
|
|
141
|
+
element.setAttributeNS(element.namespaceURI, k, k);
|
|
142
|
+
} else {
|
|
143
|
+
element.setAttributeNS(element.namespaceURI, k, v);
|
|
135
144
|
}
|
|
136
145
|
} else {
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
if (remove) {
|
|
147
|
+
element.removeAttribute(k);
|
|
148
|
+
} else if (v === true) {
|
|
149
|
+
element.setAttribute(k, k);
|
|
150
|
+
} else {
|
|
151
|
+
element.setAttribute(k, v);
|
|
152
|
+
}
|
|
139
153
|
}
|
|
140
154
|
});
|
|
141
155
|
|
|
142
156
|
if (children?.length > 0) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
element.replaceChildren(...(children as (string | Node)[]));
|
|
147
|
-
}
|
|
157
|
+
element.replaceChildren(
|
|
158
|
+
...(children[0] === CLEAR ? [] : (children as (string | Node)[]))
|
|
159
|
+
);
|
|
148
160
|
}
|
|
149
161
|
|
|
150
|
-
element.update ??= (attrs, ...children) =>
|
|
162
|
+
(element as Element).update ??= (attrs, ...children) =>
|
|
151
163
|
update(element, ...normalizeArguments(attrs, children));
|
|
152
164
|
|
|
153
|
-
return element as
|
|
165
|
+
return element as Element;
|
|
154
166
|
}
|
package/src/dom/fc.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
DenormChildren,
|
|
4
4
|
DomAttrs,
|
|
5
5
|
normalizeArguments,
|
|
6
|
-
Updatable,
|
|
7
6
|
update,
|
|
8
7
|
} from "./dom.js";
|
|
9
8
|
|
|
@@ -11,17 +10,17 @@ export type Attrs<S> = S & Partial<DomAttrs>;
|
|
|
11
10
|
|
|
12
11
|
export const State = Symbol();
|
|
13
12
|
export interface FCComponent<P extends object, S extends object>
|
|
14
|
-
extends
|
|
15
|
-
[State]
|
|
13
|
+
extends Element {
|
|
14
|
+
[State]?: Partial<S>;
|
|
16
15
|
update(
|
|
17
|
-
attrs?: Partial<Attrs<P
|
|
16
|
+
attrs?: Partial<Attrs<P> & DomAttrs> | DenormChildren,
|
|
18
17
|
...children: DenormChildren[]
|
|
19
|
-
):
|
|
18
|
+
): this;
|
|
20
19
|
}
|
|
21
20
|
export interface RenderFn<P extends object, S extends object> {
|
|
22
21
|
(el: FCComponent<P, S>, attrs: Attrs<P>, children: DenormChildren[]):
|
|
23
|
-
|
|
|
24
|
-
|
|
|
22
|
+
| Element
|
|
23
|
+
| Element[];
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
export interface FCComponentCtor<P extends object, S extends object> {
|
|
@@ -35,7 +34,7 @@ export function FC<P extends object, S extends object = {}>(
|
|
|
35
34
|
name: string,
|
|
36
35
|
component: RenderFn<P, S>
|
|
37
36
|
): FCComponentCtor<P, S> {
|
|
38
|
-
class FCImpl extends HTMLElement {
|
|
37
|
+
class FCImpl extends HTMLElement implements FCComponent<P, S> {
|
|
39
38
|
constructor() {
|
|
40
39
|
super();
|
|
41
40
|
}
|
|
@@ -55,12 +54,14 @@ export function FC<P extends object, S extends object = {}>(
|
|
|
55
54
|
this.#children = children;
|
|
56
55
|
}
|
|
57
56
|
this.#attrs = { ...this.#attrs, ...(attrs as Attrs<P>) };
|
|
57
|
+
|
|
58
58
|
// Apply updates from the attrs to the dom node itself
|
|
59
|
-
// @ts-ignore
|
|
60
59
|
update(this, this.#attrs, []);
|
|
60
|
+
|
|
61
61
|
// Re-run the component function using new element, attrs, and children.
|
|
62
62
|
const replace = [component(this, this.#attrs, this.#children)];
|
|
63
63
|
this.replaceChildren(...replace.flat());
|
|
64
|
+
return this;
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|