@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.
Files changed (172) hide show
  1. package/build/assert.d.ts +23 -23
  2. package/build/assert.js +33 -33
  3. package/build/case.d.ts +1 -1
  4. package/build/case.js +5 -5
  5. package/build/components/button_bar.d.ts +8 -8
  6. package/build/components/button_bar.js +27 -27
  7. package/build/components/inline_edit.d.ts +12 -12
  8. package/build/components/inline_edit.js +48 -48
  9. package/build/components/logger.d.ts +6 -7
  10. package/build/components/logger.js +22 -22
  11. package/build/components/select.d.ts +13 -10
  12. package/build/components/select.js +3 -3
  13. package/build/components/test.d.ts +1 -1
  14. package/build/components/test.js +2 -2
  15. package/build/components/virtual_scroll.d.ts +40 -41
  16. package/build/components/virtual_scroll.js +94 -94
  17. package/build/components/virtual_scroll.test.d.ts +1 -1
  18. package/build/components/virtual_scroll.test.js +21 -21
  19. package/build/context.d.ts +15 -15
  20. package/build/context.js +43 -43
  21. package/build/context.test.d.ts +1 -1
  22. package/build/context.test.js +46 -46
  23. package/build/debounce.d.ts +1 -1
  24. package/build/debounce.js +7 -7
  25. package/build/display.d.ts +5 -5
  26. package/build/display.js +11 -11
  27. package/build/dom/css/border.d.ts +11 -11
  28. package/build/dom/css/border.js +27 -27
  29. package/build/dom/css/constants.d.ts +31 -31
  30. package/build/dom/css/constants.js +28 -28
  31. package/build/dom/css/core.d.ts +5 -5
  32. package/build/dom/css/core.js +24 -24
  33. package/build/dom/css/fstyle.d.ts +5 -5
  34. package/build/dom/css/fstyle.js +32 -32
  35. package/build/dom/css/sizing.d.ts +5 -5
  36. package/build/dom/css/sizing.js +10 -10
  37. package/build/dom/dom.d.ts +26 -27
  38. package/build/dom/dom.js +95 -95
  39. package/build/dom/fc.d.ts +14 -14
  40. package/build/dom/fc.js +36 -35
  41. package/build/dom/fc.test.d.ts +1 -1
  42. package/build/dom/fc.test.js +21 -21
  43. package/build/dom/form/form.app.d.ts +1 -1
  44. package/build/dom/form/form.app.js +23 -23
  45. package/build/dom/form/form.d.ts +26 -26
  46. package/build/dom/form/form.js +34 -34
  47. package/build/dom/form/form.test.js +1 -1
  48. package/build/dom/html.d.ts +113 -117
  49. package/build/dom/html.js +114 -114
  50. package/build/dom/html.test.d.ts +1 -1
  51. package/build/dom/html.test.js +58 -58
  52. package/build/dom/provide.d.ts +3 -3
  53. package/build/dom/provide.js +7 -7
  54. package/build/dom/router/link.d.ts +6 -6
  55. package/build/dom/router/link.js +3 -3
  56. package/build/dom/router/router.d.ts +12 -12
  57. package/build/dom/router/router.js +49 -49
  58. package/build/dom/svg.d.ts +64 -64
  59. package/build/dom/svg.js +65 -65
  60. package/build/dom/test.d.ts +1 -1
  61. package/build/dom/test.js +2 -2
  62. package/build/dom/types/css.d.ts +6612 -6612
  63. package/build/dom/types/css.js +23 -23
  64. package/build/dom/types/dom.js +1 -1
  65. package/build/dom/types/html.d.ts +616 -616
  66. package/build/dom/types/html.js +1 -1
  67. package/build/dom/xml.d.ts +1 -1
  68. package/build/dom/xml.js +4 -5
  69. package/build/equal.d.ts +5 -5
  70. package/build/equal.js +37 -37
  71. package/build/equal.test.d.ts +1 -1
  72. package/build/equal.test.js +20 -20
  73. package/build/flags.d.ts +7 -7
  74. package/build/flags.js +48 -48
  75. package/build/flags.test.d.ts +1 -1
  76. package/build/flags.test.js +35 -35
  77. package/build/fs.d.ts +48 -48
  78. package/build/fs.js +144 -144
  79. package/build/fs.test.d.ts +1 -1
  80. package/build/fs.test.js +43 -43
  81. package/build/generator.d.ts +1 -1
  82. package/build/generator.js +10 -10
  83. package/build/generator.test.d.ts +1 -1
  84. package/build/generator.test.js +24 -24
  85. package/build/is_browser.d.ts +1 -1
  86. package/build/is_browser.js +1 -1
  87. package/build/loader.d.mts +22 -22
  88. package/build/loader.mjs +35 -35
  89. package/build/lock.d.ts +1 -1
  90. package/build/lock.js +23 -23
  91. package/build/lock.test.d.ts +1 -1
  92. package/build/lock.test.js +16 -16
  93. package/build/log.d.ts +26 -26
  94. package/build/log.js +46 -46
  95. package/build/observable/observable.d.ts +83 -0
  96. package/build/observable/observable.js +148 -0
  97. package/build/observable/observable.test.d.ts +1 -0
  98. package/build/observable/observable.test.js +21 -0
  99. package/build/range.d.ts +1 -1
  100. package/build/range.js +7 -7
  101. package/build/result.d.ts +31 -31
  102. package/build/result.js +65 -65
  103. package/build/result.test.d.ts +1 -1
  104. package/build/result.test.js +71 -71
  105. package/build/safe.d.ts +1 -1
  106. package/build/safe.js +10 -10
  107. package/build/scope/describe.d.ts +18 -14
  108. package/build/scope/describe.js +61 -52
  109. package/build/scope/display/console.d.ts +2 -2
  110. package/build/scope/display/console.js +21 -21
  111. package/build/scope/display/dom.d.ts +3 -3
  112. package/build/scope/display/dom.js +26 -26
  113. package/build/scope/display/junit.d.ts +2 -2
  114. package/build/scope/display/junit.js +17 -17
  115. package/build/scope/execute.d.ts +12 -12
  116. package/build/scope/execute.js +85 -85
  117. package/build/scope/expect.d.ts +23 -23
  118. package/build/scope/expect.js +108 -108
  119. package/build/scope/fix.d.ts +4 -4
  120. package/build/scope/fix.js +22 -22
  121. package/build/scope/index.d.ts +3 -3
  122. package/build/scope/index.js +3 -3
  123. package/build/scope/scope.d.ts +17 -17
  124. package/build/scope/scope.js +1 -1
  125. package/build/server/http/apps.d.ts +5 -5
  126. package/build/server/http/apps.js +23 -23
  127. package/build/server/http/css.d.ts +5 -5
  128. package/build/server/http/css.js +50 -47
  129. package/build/server/http/index.d.ts +21 -21
  130. package/build/server/http/index.js +73 -73
  131. package/build/server/http/response.d.ts +4 -4
  132. package/build/server/http/response.js +40 -40
  133. package/build/server/http/sitemap.d.ts +2 -2
  134. package/build/server/http/sitemap.js +42 -42
  135. package/build/server/http/static.d.ts +2 -2
  136. package/build/server/http/static.js +21 -21
  137. package/build/server/http/typescript.d.ts +5 -5
  138. package/build/server/http/typescript.js +40 -40
  139. package/build/server/main.d.ts +2 -2
  140. package/build/server/main.js +9 -9
  141. package/build/test.d.mts +2 -2
  142. package/build/test.mjs +23 -23
  143. package/build/test_all.d.ts +7 -7
  144. package/build/test_all.js +18 -18
  145. package/build/transpile.d.mts +3 -3
  146. package/build/transpile.mjs +18 -18
  147. package/package.json +3 -3
  148. package/src/components/logger.ts +2 -3
  149. package/src/components/virtual_scroll.test.ts +4 -4
  150. package/src/components/virtual_scroll.ts +8 -8
  151. package/src/diff.test.ts +48 -0
  152. package/src/diff.ts +84 -0
  153. package/src/dom/dom.ts +73 -61
  154. package/src/dom/fc.ts +10 -9
  155. package/src/dom/html.test.ts +5 -5
  156. package/src/dom/html.ts +7 -10
  157. package/src/dom/observable.test.ts +43 -0
  158. package/src/dom/observable.ts +11 -0
  159. package/src/dom/router/router.ts +4 -4
  160. package/src/dom/test.ts +5 -1
  161. package/src/dom/xml.ts +1 -2
  162. package/src/index.html +6 -3
  163. package/src/observable/_notes +21 -8
  164. package/src/observable/event.ts +93 -0
  165. package/src/observable/observable.test.ts +73 -0
  166. package/src/observable/observable.ts +403 -0
  167. package/src/scope/describe.ts +14 -1
  168. package/src/scope/display/dom.ts +2 -2
  169. package/src/scope/execute.ts +2 -5
  170. package/src/server/http/css.ts +3 -1
  171. package/src/test_all.ts +10 -8
  172. 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
+ })();
@@ -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
+ });
@@ -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>;
@@ -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.0.0",
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-rc"
34
+ "typescript": "^4.7.1"
35
35
  },
36
36
  "dependencies": {
37
37
  "sass": "^1.49.9"
38
38
  }
39
- }
39
+ }
@@ -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: Updatable<DOMElement>;
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: Updatable<DOMElement>;
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
- // @ts-ignore TODO(TFC)
18
+
19
19
  const scroll = VirtualScroll(props);
20
20
 
21
- expect(scroll[State].bufferedItems).toBe(9);
22
- expect(scroll[State].data).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8]);
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].viewportHeight).toBe(60);
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, UHTMLElement } from "../dom/html.js";
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) => UHTMLElement<U>;
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 = 1,
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: UHTMLElement<U>[];
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<unknown, HTMLElement>,
134
- VirtualScrollState<unknown, HTMLElement>
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
- element[State].rows = state.data.map(props.row);
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
- ...(element[State].rows ?? []).map((row, i) =>
173
+ ...(state.rows ?? []).map((row, i) =>
174
174
  div(
175
175
  {
176
176
  class: `VirtualScroll__item_${i}`,
@@ -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 * as CSS from "./types/css";
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<CSS.Properties>;
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<E & S & DomAttrs>;
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 === "string") {
44
- return false;
47
+ if (typeof attrs === "object") {
48
+ return !(attrs as Node).nodeType;
45
49
  }
46
- return !(attrs as Node).nodeType;
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 DOMElement>(
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
- ): Updatable<E> {
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<E extends DOMElement>(
75
- element: Updater<E>,
76
- attrs: Attrs<E>,
78
+ export function update(
79
+ element: Omit<Element, "update">,
80
+ attrs: Attrs<Element>,
77
81
  children: DenormChildren[]
78
- ): Updatable<E> {
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 && $events.has(k)) {
86
- const listener = $events.get(k)!;
87
- element.removeEventListener(k, listener);
88
- } else if (!$events.has(k)) {
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" && typeof v === "string") {
111
- v.split(/\s+/m)
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) => element.classList.add(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
- let useAttributes =
117
- k.startsWith("aria-") ||
118
- k == "role" ||
132
+ const useNamespace =
133
+ element.namespaceURI &&
119
134
  element.namespaceURI != "http://www.w3.org/1999/xhtml";
120
-
121
- if (useAttributes) {
122
- switch (v) {
123
- case false:
124
- element.removeAttributeNS(element.namespaceURI, k);
125
- break;
126
- case true:
127
- element.setAttributeNS(element.namespaceURI, k, k);
128
- break;
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
- // @ts-ignore Object.entries is unable to statically look into args
138
- element[k] = v;
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
- if (children[0] === CLEAR) {
144
- element.replaceChildren();
145
- } else {
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 Updatable<E>;
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 HTMLElement {
15
- [State]: Partial<S>;
13
+ extends Element {
14
+ [State]?: Partial<S>;
16
15
  update(
17
- attrs?: Partial<Attrs<P>> | DenormChildren,
16
+ attrs?: Partial<Attrs<P> & DomAttrs> | DenormChildren,
18
17
  ...children: DenormChildren[]
19
- ): void;
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
- | Updatable<Element>
24
- | Updatable<Element>[];
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