@lukekaalim/act-web 3.4.0-alpha.2 → 4.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @lukekaalim/act-web
2
2
 
3
+ ## 4.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 6658c01: Internal Refactor!
8
+ - afd247e: Another major refactor! So everything is broken. Good luck!
9
+
10
+ ### Minor Changes
11
+
12
+ - bd0a076: Add support for data-\* attributes on HTML elements
13
+ - b3f6c49: Added debug capabilities and protocol
14
+
15
+ ### Patch Changes
16
+
17
+ - 7597a8f: Added "Style" prop support to SVG
18
+ - ccb3900: Reconciler should apply all changes in the correct order, and not skip any.
19
+ - Reconciler does send out a Render command once it completes all pending renders (called a "ThreadStack")
20
+ Scheduler has been updated to perform some updates in Sync.
21
+ - Updated dependencies [6658c01]
22
+ - Updated dependencies [fdf1557]
23
+ - Updated dependencies [ccb3900]
24
+ - Updated dependencies [afd247e]
25
+ - Updated dependencies [2984273]
26
+ - Updated dependencies [b3f6c49]
27
+ - Updated dependencies [c5e8775]
28
+ - @lukekaalim/act-backstage@3.0.0
29
+ - @lukekaalim/act-recon@3.0.0
30
+ - @lukekaalim/act@4.0.0
31
+
32
+ ## 3.4.0-alpha.3
33
+
34
+ ### Patch Changes
35
+
36
+ - ccb3900: Reconciler should apply all changes in the correct order, and not skip any.
37
+ - Reconciler does send out a Render command once it completes all pending renders (called a "ThreadStack")
38
+ Scheduler has been updated to perform some updates in Sync.
39
+ - Updated dependencies [ccb3900]
40
+ - @lukekaalim/act-recon@3.0.0-alpha.3
41
+
3
42
  ## 3.4.0-alpha.2
4
43
 
5
44
  ### Minor Changes
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @lukekaalim/act-web
2
+
3
+ ```bash
4
+ npm i @lukekaalim/act-web
5
+ ```
6
+
7
+ ```ts
8
+ import { h, Component } from "@lukekaalim/act";
9
+ import { render } from "@lukekaalim/act-web";
10
+
11
+ const MyApp = () => {
12
+ return h('article', {}, [
13
+ h('h1', {}, "Hello, world!")
14
+ ])
15
+ }
16
+
17
+ render(h(MyApp), document.body)
18
+ ```
19
+
20
+ <TypeDoc project="@lukekaalim/act-web" name="render" />
21
+
22
+ ## Capabilities
23
+
24
+ - [x] Supports HTML elements and SVG elements
25
+ - [x] Add in the `web:html` or `web:svg` root pick the correct type
26
+ - [x] Prop support
27
+ - [x] **Attributes.** Most props will just be assigned directly to the element
28
+ - [x] **Event Listeners.** `onClick` adds an event listener for `click`
29
+ - [x] **Ref.** `ref` will attach a `HTMLElement` to `ref.current`
30
+ - [x] **Styles.** `style` will set styles directly on the element
31
+ - [x] **ClassList.** An array of strings or falseish values will be added as classes via `classList`
32
+
33
+ ## Builder
34
+
35
+ If you are composing another renderer and want to give
36
+ it web powers, use the builder:
37
+
38
+ <TypeDoc project="@lukekaalim/act-web" name="createWebNodeBuilder" />
package/docs/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { DocApp } from '@lukekaalim/grimoire';
2
+
3
+ const createWebDocs = (doc: DocApp) => {
4
+
5
+ }
package/mod.ts CHANGED
@@ -3,3 +3,4 @@ export * from './element.ts';
3
3
  export * from './props.ts';
4
4
  export * from './render.ts';
5
5
  export * from './scheduler.ts';
6
+ export * from './server.ts';
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "@lukekaalim/act-web",
3
- "version": "3.4.0-alpha.2",
3
+ "version": "4.0.0",
4
4
  "type": "module",
5
5
  "main": "mod.ts",
6
6
  "dependencies": {
7
- "@lukekaalim/act": "^3.1.0",
8
- "@lukekaalim/act-recon": "^3.0.0-alpha.0",
9
- "@lukekaalim/act-backstage": "^3.0.0-alpha.0"
7
+ "@lukekaalim/act": "^4.0.0",
8
+ "@lukekaalim/act-backstage": "^3.0.0",
9
+ "@lukekaalim/act-recon": "^3.0.0",
10
+ "@types/hast": "^3.0.4",
11
+ "hast-util-to-html": "^9.0.5",
12
+ "hastscript": "^9.0.1",
13
+ "rehype": "^13.0.2",
14
+ "rehype-stringify": "^10.0.1"
10
15
  }
11
16
  }
package/render.ts CHANGED
@@ -1,8 +1,32 @@
1
- import { h } from "@lukekaalim/act";
2
- import { createWebSpace, HTML } from "./space";
3
- import { createRenderFunction, RenderFunction } from "@lukekaalim/act-backstage";
1
+ import { h, Node } from "@lukekaalim/act";
2
+ import { createWebNodeBuilder, HTML } from "./space";
3
+ import { RenderSpace2 } from "@lukekaalim/act-backstage";
4
4
  import { createDOMScheduler } from "./scheduler";
5
+ import { Reconciler2 } from "@lukekaalim/act-recon";
5
6
 
7
+ /**
8
+ * Custom options for web.render
9
+ */
10
+ type Options = {
11
+ window?: Window;
12
+ }
6
13
 
7
- export const render: RenderFunction<HTMLElement> = (node, root) =>
8
- createRenderFunction(createDOMScheduler(), createWebSpace)(h(HTML, {}, node), root);
14
+ /**
15
+ * Render a node into the page.
16
+ *
17
+ *
18
+ * @param node
19
+ * @param root
20
+ * @param options
21
+ * @returns
22
+ */
23
+ export const render = (node: Node, root: HTMLElement, options: Options = {}) => {
24
+ const scheduler = createDOMScheduler();
25
+ const reconciler = new Reconciler2(scheduler);
26
+ const space = new RenderSpace2(reconciler.tree, createWebNodeBuilder(root, options.window));
27
+
28
+ reconciler.bus = space.bus;
29
+ const ref = reconciler.mount(h(HTML, {}, node));
30
+
31
+ return {reconciler, space,ref};
32
+ }
package/scheduler.ts CHANGED
@@ -1,26 +1,53 @@
1
- import { createId } from "@lukekaalim/act";
2
1
  import { Scheduler } from "@lukekaalim/act-recon";
3
2
 
4
3
  export const createDOMScheduler = (): Scheduler => {
5
- const workMap = new Map();
6
4
  let id: number | null = null;
7
- const work = () => {
5
+ let callbackFunc = () => console.error(`DOMScheduler got callback before callback function was configured`)
6
+ let synccall_available = false;
7
+ let synccall_requested = false;
8
+ const time_budget = 60;
9
+
10
+ const onTimeout = () => {
11
+ const start = performance.now();
8
12
  id = null;
9
- for (const [,callback] of workMap)
10
- callback();
11
13
 
12
- workMap.clear();
13
- };
14
+ synccall_available = true;
15
+ // at least 1 call
16
+ callbackFunc();
17
+
18
+ // if callback func re-requested a call,
19
+ // do the rest in sync
20
+ while (synccall_requested) {
21
+ synccall_requested = false;
22
+ const now = performance.now();
23
+
24
+ if (now - start >= time_budget) {
25
+ synccall_available = false;
26
+ }
27
+
28
+ callbackFunc();
29
+ }
30
+ synccall_available = false;
31
+ }
32
+
14
33
  return {
15
- requestWork(callback) {
16
- const workId = createId<'WorkID'>();
17
- workMap.set(workId, callback);
18
- if (!id)
19
- id = setTimeout(work, 1) as any;
20
- return workId;
34
+ setCallbackFunc(newCallbackFunc) {
35
+ callbackFunc = newCallbackFunc;
36
+ },
37
+ isCallbackPending() {
38
+ return id !== null;
39
+ },
40
+ requestCallback() {
41
+ if (synccall_available) {
42
+ synccall_requested = true;
43
+ }
44
+ else if (!id) {
45
+ id = window.setTimeout(onTimeout, 0);
46
+ }
21
47
  },
22
- cancelWork(workId) {
23
- workMap.delete(workId);
48
+ cancelCallback() {
49
+ if (id !== null)
50
+ window.clearTimeout(id);
24
51
  },
25
52
  }
26
53
  }
package/server.ts ADDED
@@ -0,0 +1,91 @@
1
+ import { h, s } from 'hastscript';
2
+ import { Element, Nodes as HNode, Root } from 'hast';
3
+ import { NodeBuilder, RenderSpace2 } from "@lukekaalim/act-backstage";
4
+ import { primitiveNodeTypes } from '@lukekaalim/act';
5
+ import { CommitTree2 } from '@lukekaalim/act-recon';
6
+
7
+ export const createHASTBuilder = (root: Root): NodeBuilder<HNode, 'web:html' | 'web:svg'> => ({
8
+ roots: new Set(['web:html', 'web:svg']),
9
+
10
+ create(element, root): HNode | null {
11
+ switch (element.type) {
12
+ case primitiveNodeTypes.string:
13
+ case primitiveNodeTypes.string:
14
+ case primitiveNodeTypes.number:
15
+ return { type: 'text', value: '' };
16
+ default: {
17
+ switch (typeof element.type) {
18
+ case 'string':
19
+ switch (root) {
20
+ case 'web:html':
21
+ return h(element.type);
22
+ case 'web:svg':
23
+ return s(element.type);
24
+ }
25
+ default:
26
+ return null;
27
+ }
28
+ }
29
+ }
30
+ },
31
+ link(child, parent) {
32
+ switch (child.type) {
33
+ case 'element':
34
+ case 'comment':
35
+ case 'text':
36
+ switch (parent.type) {
37
+ case 'element':
38
+ case 'root':
39
+ parent.children.push(child);
40
+ }
41
+ return;
42
+ case 'doctype':
43
+ switch (parent.type) {
44
+ case 'root':
45
+ parent.children.push(child);
46
+ }
47
+ return;
48
+ default:
49
+ }
50
+ },
51
+ linkRoot(child) {
52
+ switch (child.type) {
53
+ case 'doctype':
54
+ case 'element':
55
+ case 'comment':
56
+ case 'text':
57
+ root.children.push(child);
58
+ }
59
+ },
60
+ update(el, next, prev) {
61
+ switch (el.type) {
62
+ case 'text':
63
+ el.value = (next.props.value as number | string | boolean).toString()
64
+ return;
65
+ case 'element':
66
+
67
+ }
68
+ },
69
+ unlink(child, parent) {
70
+ if ("children" in parent) {
71
+ parent.children = parent.children.filter(c => c !== child);
72
+ }
73
+ },
74
+ sort(el, children) {
75
+ if ("children" in el) {
76
+ el.children = children as Element[];
77
+ }
78
+ },
79
+ })
80
+
81
+ type DehydratedRender = {
82
+
83
+ }
84
+
85
+ export const dehydrate = (tree: CommitTree2, renderer: RenderSpace2<HNode, string | symbol>) => {
86
+
87
+ }
88
+
89
+ export const rehydrate = () => {
90
+
91
+ }
package/space.ts CHANGED
@@ -1,69 +1,77 @@
1
1
  import * as act from '@lukekaalim/act';
2
- import * as recon from '@lukekaalim/act-recon';
3
-
4
- import { createSimpleRenderSpace } from '@lukekaalim/act-backstage/mod.ts';
5
2
 
6
3
  import { setProps } from './props.ts';
4
+ import { NodeBuilder } from '@lukekaalim/act-backstage';
7
5
 
8
6
  export const HTML: act.Component = ({ children }) => act.h(act.renderNodeType, { type: 'web:html' }, children);
9
7
  export const SVG: act.Component = ({ children }) => act.h(act.renderNodeType, { type: 'web:svg' }, children);
10
8
 
11
9
  const defaultWindow = window;
12
10
 
13
- export const createWebSpace = (
14
- tree: recon.CommitTree,
11
+ export const createWebNodeBuilder = (
15
12
  root: HTMLElement,
16
13
  window: Window = defaultWindow
17
- ) => {
18
- return createSimpleRenderSpace(tree, {
19
- rootTypes: new Set(['web:html', 'web:svg']),
20
- create(element, rootType) {
21
- const tag = element.type;
22
-
23
- switch (typeof tag) {
24
- case 'symbol': {
25
- switch (tag) {
26
- case act.primitiveNodeTypes.string:
27
- case act.primitiveNodeTypes.number:
28
- return window.document.createTextNode("<empty text>");
29
- default:
30
- return null;
31
- }
14
+ ): NodeBuilder<HTMLElement | SVGElement | Text, 'web:html' | 'web:svg'> => ({
15
+ roots: new Set(['web:html', 'web:svg'] as const),
16
+
17
+ create(element, rootType) {
18
+ const tag = element.type;
19
+
20
+ switch (typeof tag) {
21
+ case 'symbol': {
22
+ switch (tag) {
23
+ case act.primitiveNodeTypes.string:
24
+ case act.primitiveNodeTypes.number:
25
+ return window.document.createTextNode("<empty text>");
26
+ default:
27
+ return null;
32
28
  }
33
- case 'string': {
34
- switch (rootType) {
35
- case 'web:html':
36
- return window.document.createElementNS('http://www.w3.org/1999/xhtml', tag);
37
- case 'web:svg':
38
- return window.document.createElementNS('http://www.w3.org/2000/svg', tag);
39
- }
29
+ }
30
+ case 'string': {
31
+ switch (rootType) {
32
+ case 'web:html':
33
+ return window.document.createElementNS('http://www.w3.org/1999/xhtml', tag);
34
+ case 'web:svg':
35
+ return window.document.createElementNS('http://www.w3.org/2000/svg', tag);
40
36
  }
41
- default:
42
- return null;
43
37
  }
44
- },
45
- update(el, next, prev) {
46
- setProps(window, el, next, prev);
47
- },
48
- link(el, parent) {
49
- (parent || root).appendChild(el);
50
- },
51
- unlink(el, parent) {
52
- (parent || root).removeChild(el);
53
- },
54
- sort(el, newChildren) {
55
- if (el instanceof Text)
56
- return;
57
- if (newChildren.length < 2)
58
- return;
38
+ default:
39
+ return null;
40
+ }
41
+ },
42
+ update(el, next, prev) {
43
+ setProps(window, el, next, prev);
44
+ },
45
+ link(el, parent) {
46
+ parent.appendChild(el);
47
+ },
48
+ linkRoot(child) {
49
+ root.appendChild(child);
50
+ },
51
+ unlink(el, parent) {
52
+ if (el.parentNode === parent)
53
+ parent.removeChild(el);
54
+ },
55
+ destroy(el) {
56
+ if (el.parentNode)
57
+ el.parentNode.removeChild(el)
58
+ },
59
+ suspend(el, parent) {
60
+ if (el instanceof HTMLElement)
61
+ el.style.opacity = 0.5.toString();
62
+ },
63
+ unsuspend(el, parent) {
64
+ if (el instanceof HTMLElement)
65
+ el.style.opacity = (1).toString();
66
+ },
67
+ sort(el, newChildren) {
68
+ if (el instanceof Text)
69
+ return;
70
+ if (newChildren.length < 2)
71
+ return;
59
72
 
60
- for (let i = 0; i < newChildren.length; i++)
61
- if (el.children[i] !== newChildren[i])
62
- el.insertBefore(newChildren[i], el.children[i])
63
- },
64
- destroy(prev) {
65
- if (prev.parentNode)
66
- prev.parentNode.removeChild(prev);
67
- },
68
- });
69
- };
73
+ for (let i = 0; i < newChildren.length; i++)
74
+ if (el.children[i] !== newChildren[i])
75
+ el.insertBefore(newChildren[i], el.children[i])
76
+ },
77
+ })