@derivesome/tree 0.1.1

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 (113) hide show
  1. package/.package.json.~undo-tree~ +4 -0
  2. package/.tsconfig.json.~undo-tree~ +4 -0
  3. package/dist/cjs/brands.d.ts +3 -0
  4. package/dist/cjs/brands.d.ts.map +1 -0
  5. package/dist/cjs/brands.js +5 -0
  6. package/dist/cjs/brands.js.map +1 -0
  7. package/dist/cjs/context.d.ts +28 -0
  8. package/dist/cjs/context.d.ts.map +1 -0
  9. package/dist/cjs/context.js +48 -0
  10. package/dist/cjs/context.js.map +1 -0
  11. package/dist/cjs/index.d.ts +6 -0
  12. package/dist/cjs/index.d.ts.map +1 -0
  13. package/dist/cjs/index.js +22 -0
  14. package/dist/cjs/index.js.map +1 -0
  15. package/dist/cjs/mount.d.ts +6 -0
  16. package/dist/cjs/mount.d.ts.map +1 -0
  17. package/dist/cjs/mount.js +209 -0
  18. package/dist/cjs/mount.js.map +1 -0
  19. package/dist/cjs/props.d.ts +12 -0
  20. package/dist/cjs/props.d.ts.map +1 -0
  21. package/dist/cjs/props.js +80 -0
  22. package/dist/cjs/props.js.map +1 -0
  23. package/dist/cjs/renderer.d.ts +29 -0
  24. package/dist/cjs/renderer.d.ts.map +1 -0
  25. package/dist/cjs/renderer.js +3 -0
  26. package/dist/cjs/renderer.js.map +1 -0
  27. package/dist/cjs/tree-node-like.d.ts +8 -0
  28. package/dist/cjs/tree-node-like.d.ts.map +1 -0
  29. package/dist/cjs/tree-node-like.js +4 -0
  30. package/dist/cjs/tree-node-like.js.map +1 -0
  31. package/dist/cjs/tree.d.ts +46 -0
  32. package/dist/cjs/tree.d.ts.map +1 -0
  33. package/dist/cjs/tree.js +154 -0
  34. package/dist/cjs/tree.js.map +1 -0
  35. package/dist/cjs/velement.d.ts +185 -0
  36. package/dist/cjs/velement.d.ts.map +1 -0
  37. package/dist/cjs/velement.js +874 -0
  38. package/dist/cjs/velement.js.map +1 -0
  39. package/dist/esm/brands.d.ts +3 -0
  40. package/dist/esm/brands.d.ts.map +1 -0
  41. package/dist/esm/brands.js +5 -0
  42. package/dist/esm/brands.js.map +1 -0
  43. package/dist/esm/context.d.ts +28 -0
  44. package/dist/esm/context.d.ts.map +1 -0
  45. package/dist/esm/context.js +48 -0
  46. package/dist/esm/context.js.map +1 -0
  47. package/dist/esm/index.d.ts +6 -0
  48. package/dist/esm/index.d.ts.map +1 -0
  49. package/dist/esm/index.js +22 -0
  50. package/dist/esm/index.js.map +1 -0
  51. package/dist/esm/mount.d.ts +6 -0
  52. package/dist/esm/mount.d.ts.map +1 -0
  53. package/dist/esm/mount.js +209 -0
  54. package/dist/esm/mount.js.map +1 -0
  55. package/dist/esm/props.d.ts +12 -0
  56. package/dist/esm/props.d.ts.map +1 -0
  57. package/dist/esm/props.js +80 -0
  58. package/dist/esm/props.js.map +1 -0
  59. package/dist/esm/renderer.d.ts +29 -0
  60. package/dist/esm/renderer.d.ts.map +1 -0
  61. package/dist/esm/renderer.js +3 -0
  62. package/dist/esm/renderer.js.map +1 -0
  63. package/dist/esm/tree-node-like.d.ts +8 -0
  64. package/dist/esm/tree-node-like.d.ts.map +1 -0
  65. package/dist/esm/tree-node-like.js +4 -0
  66. package/dist/esm/tree-node-like.js.map +1 -0
  67. package/dist/esm/tree.d.ts +46 -0
  68. package/dist/esm/tree.d.ts.map +1 -0
  69. package/dist/esm/tree.js +154 -0
  70. package/dist/esm/tree.js.map +1 -0
  71. package/dist/esm/velement.d.ts +185 -0
  72. package/dist/esm/velement.d.ts.map +1 -0
  73. package/dist/esm/velement.js +874 -0
  74. package/dist/esm/velement.js.map +1 -0
  75. package/package.json +46 -0
  76. package/package.json~ +52 -0
  77. package/src/#mount.test.ts# +372 -0
  78. package/src/.brands.ts.~undo-tree~ +6 -0
  79. package/src/.context.ts.~undo-tree~ +6 -0
  80. package/src/.index.ts.~undo-tree~ +11 -0
  81. package/src/.mount.test.ts.~undo-tree~ +438 -0
  82. package/src/.mount.ts.~undo-tree~ +70 -0
  83. package/src/.node-like.ts.~undo-tree~ +8 -0
  84. package/src/.props.ts.~undo-tree~ +125 -0
  85. package/src/.renderer.ts.~undo-tree~ +18 -0
  86. package/src/.tree-node-like.ts.~undo-tree~ +12 -0
  87. package/src/.tree.ts.~undo-tree~ +46 -0
  88. package/src/.velement.ts.~undo-tree~ +1739 -0
  89. package/src/brands.ts +2 -0
  90. package/src/brands.ts~ +0 -0
  91. package/src/context.ts +61 -0
  92. package/src/context.ts~ +0 -0
  93. package/src/index.ts +5 -0
  94. package/src/index.ts~ +4 -0
  95. package/src/mount.test.ts +405 -0
  96. package/src/mount.test.ts~ +375 -0
  97. package/src/mount.ts +332 -0
  98. package/src/mount.ts~ +306 -0
  99. package/src/node-like.ts~ +0 -0
  100. package/src/props.ts +99 -0
  101. package/src/props.ts~ +86 -0
  102. package/src/renderer.ts +37 -0
  103. package/src/renderer.ts~ +37 -0
  104. package/src/tree-node-like.ts +8 -0
  105. package/src/tree-node-like.ts~ +6 -0
  106. package/src/tree.ts +226 -0
  107. package/src/tree.ts~ +227 -0
  108. package/src/velement.ts +990 -0
  109. package/src/velement.ts~ +966 -0
  110. package/tsconfig.cjs.json +10 -0
  111. package/tsconfig.esm.json +10 -0
  112. package/tsconfig.json +23 -0
  113. package/tsconfig.json~ +23 -0
package/src/tree.ts ADDED
@@ -0,0 +1,226 @@
1
+ import { derived, has, isReference, ref, Reference } from "@derivesome/core";
2
+ import { TreeNodeBrand } from "./brands";
3
+ import { TreeNodeProps } from "./props";
4
+
5
+ export type TreeNodeFn<T extends TreeNodeProps = TreeNodeProps> = (
6
+ props: TreeNodeProps & T,
7
+ ) => unknown;
8
+
9
+ export type Component<T extends TreeNodeProps = TreeNodeProps> = TreeNodeFn<T>;
10
+
11
+ export interface TreeNodeBase<Type extends string> {
12
+ type: Type;
13
+ props: TreeNodeProps;
14
+ [TreeNodeBrand]: true;
15
+ }
16
+
17
+ export interface TreeNodeElement extends TreeNodeBase<"element"> {
18
+ tag: string;
19
+ children: TreeNode[];
20
+ }
21
+
22
+ export interface TreeNodeValue extends TreeNodeBase<"value"> {
23
+ value: unknown;
24
+ }
25
+
26
+ export interface TreeNodeVoid extends TreeNodeBase<"void"> {}
27
+
28
+ export interface TreeNodeFunction<
29
+ P extends TreeNodeProps = TreeNodeProps,
30
+ > extends TreeNodeBase<"function"> {
31
+ fn: TreeNodeFn<P>;
32
+ }
33
+
34
+ export interface TreeNodeList<T = any> extends TreeNodeBase<"list"> {
35
+ props: Omit<TreeNodeProps, "children"> & {
36
+ children: Reference<T[]>;
37
+ };
38
+ items: Reference<TreeNode[]>;
39
+ map: (item: T, idx: number) => TreeNode;
40
+ }
41
+
42
+ export type TreeNode =
43
+ | TreeNodeElement
44
+ | TreeNodeValue
45
+ | TreeNodeVoid
46
+ | TreeNodeFunction
47
+ | TreeNodeList;
48
+
49
+ const makeBase = <Type extends string>(
50
+ t: Type,
51
+ ): { type: Type; [TreeNodeBrand]: true } => {
52
+ return {
53
+ type: t,
54
+ [TreeNodeBrand]: true,
55
+ };
56
+ };
57
+
58
+ export function isTreeNode(x: unknown): x is TreeNode {
59
+ return has(x, TreeNodeBrand) && x[TreeNodeBrand] === true;
60
+ }
61
+
62
+ // tn - short for (TreeNode)
63
+ export namespace tn {
64
+ export function none(): TreeNodeVoid {
65
+ return {
66
+ ...makeBase("void"),
67
+ props: {},
68
+ };
69
+ }
70
+
71
+ export function list<T>(
72
+ items: Reference<T[]> | T[],
73
+ map?: (item: T, idx: number) => TreeNode,
74
+ ): TreeNodeList {
75
+ const mapFn = map || ((x) => tnu.normalizeOne(x));
76
+ const its = isReference(items) ? items : ref(items);
77
+ return {
78
+ ...makeBase("list"),
79
+ props: {
80
+ children: its,
81
+ },
82
+ map: mapFn,
83
+ items: derived(() => its.get().map(mapFn)),
84
+ };
85
+ }
86
+
87
+ export function value(v: unknown): TreeNodeValue {
88
+ return {
89
+ ...makeBase("value"),
90
+ props: {},
91
+ value: v,
92
+ };
93
+ }
94
+
95
+ export function element(tag: string, props: TreeNodeProps): TreeNodeElement {
96
+ return {
97
+ ...makeBase("element"),
98
+ tag,
99
+ props,
100
+ children: tnu.normalizeChildren(props.children),
101
+ };
102
+ }
103
+
104
+ export function fn(
105
+ fun: TreeNodeFn,
106
+ props: TreeNodeProps = {},
107
+ ): TreeNodeFunction {
108
+ return {
109
+ ...makeBase("function"),
110
+ fn: fun,
111
+ props: props,
112
+ };
113
+ }
114
+ }
115
+
116
+ // tnu - short for (TreeNode Utils)
117
+ export namespace tnu {
118
+ export const normalizeOne = (x: unknown): TreeNode => {
119
+ if (isTreeNode(x)) return x;
120
+ if (typeof x === "undefined" || x === null || x === false) return tn.none();
121
+ if (
122
+ typeof x === "string" ||
123
+ typeof x === "number" ||
124
+ typeof x === "boolean"
125
+ )
126
+ return tn.value(x);
127
+ if (Array.isArray(x)) {
128
+ if (x.length === 1) {
129
+ const first = x[0]!;
130
+ if (isTreeNode(first) && first.type === "list") return first;
131
+ }
132
+ return tn.list(x.map((v) => normalizeOne(v)));
133
+ }
134
+ if (typeof x === "function")
135
+ return tn.fn((props) => normalizeOne((x as TreeNodeFn)(props)));
136
+ throw new Error(`normalizeOne: unhandled ${typeof x}`);
137
+ };
138
+
139
+ export const normalizeChildren = (x: unknown): TreeNode[] => {
140
+ if (isTreeNode(x)) return [x];
141
+ if (typeof x === "function") return [tn.value(x)];
142
+ if (Array.isArray(x)) return x.flatMap((v) => normalizeChildren(v));
143
+
144
+ if (isReference(x)) {
145
+ // Try to understand the intent by peeking at the value
146
+ const val = x.peek();
147
+
148
+ if (Array.isArray(val)) {
149
+ // assume we're trying to render a list
150
+ return [
151
+ tn.list(
152
+ derived(() =>
153
+ ((x.get() || []) as Array<unknown>).flatMap((v) =>
154
+ tnu.normalizeChildren(v),
155
+ ),
156
+ ),
157
+ ),
158
+ ];
159
+ } else if (isTreeNode(val)) {
160
+ /*
161
+ we turn this into a list so that we can allow syntax like:
162
+ ```
163
+ <div>
164
+ {derived(() => <span/>)}
165
+ </div>
166
+ ```
167
+ instead of
168
+ ```
169
+ <div>
170
+ {derived(() => [<span/>])}
171
+ </div>
172
+ ```
173
+ */
174
+ return [
175
+ tn.list(
176
+ derived(() => {
177
+ return normalizeChildren(x.get());
178
+ }),
179
+ ),
180
+ ];
181
+ }
182
+ return [tn.value(x)];
183
+ }
184
+ return [normalizeOne(x)];
185
+ };
186
+
187
+ export const assertGetKey = (x: TreeNode): string | number => {
188
+ const k = x.props.key;
189
+ if (!(typeof k === "string" || typeof k === "number"))
190
+ throw new Error(`Each item in a list must have a key`);
191
+ return k;
192
+ };
193
+ }
194
+
195
+ export function createNode(
196
+ tag: string,
197
+ props?: TreeNodeProps | null,
198
+ ...children: unknown[]
199
+ ): TreeNodeElement;
200
+ export function createNode<P extends TreeNodeProps>(
201
+ tag: TreeNodeFn<P>,
202
+ props?: P | null,
203
+ ...children: unknown[]
204
+ ): TreeNodeFunction;
205
+ export function createNode(
206
+ tag: string | TreeNodeFn,
207
+ props?: TreeNodeProps | null,
208
+ ...children: unknown[]
209
+ ): TreeNode {
210
+ const normalizedProps: TreeNodeProps = props ?? {};
211
+ const normalizedChildren = tnu.normalizeChildren(children);
212
+
213
+ if (typeof tag === "function") {
214
+ return tn.fn(tag, {
215
+ ...normalizedProps,
216
+ ...(normalizedChildren.length > 0
217
+ ? { children: normalizedChildren }
218
+ : {}),
219
+ });
220
+ }
221
+
222
+ return tn.element(tag, {
223
+ ...props,
224
+ children: normalizedChildren,
225
+ });
226
+ }
package/src/tree.ts~ ADDED
@@ -0,0 +1,227 @@
1
+ import { derived, has, isReference, ref, Reference } from "@derivesome/core";
2
+ import { TreeNodeBrand } from "./brands";
3
+ import { TreeNodeProps } from "./props";
4
+
5
+ export type TreeNodeFn<T extends TreeNodeProps = TreeNodeProps> = (
6
+ props: TreeNodeProps & T,
7
+ ) => unknown;
8
+
9
+ export type Component<T extends TreeNodeProps = TreeNodeProps> = TreeNodeFn<T>;
10
+
11
+ export interface TreeNodeBase<Type extends string> {
12
+ type: Type;
13
+ props: TreeNodeProps;
14
+ [TreeNodeBrand]: true;
15
+ }
16
+
17
+ export interface TreeNodeElement extends TreeNodeBase<"element"> {
18
+ tag: string;
19
+ children: TreeNode[];
20
+ }
21
+
22
+ export interface TreeNodeValue extends TreeNodeBase<"value"> {
23
+ value: unknown;
24
+ }
25
+
26
+ export interface TreeNodeVoid extends TreeNodeBase<"void"> {}
27
+
28
+ export interface TreeNodeFunction<
29
+ P extends TreeNodeProps = TreeNodeProps,
30
+ > extends TreeNodeBase<"function"> {
31
+ fn: TreeNodeFn<P>;
32
+ }
33
+
34
+ export interface TreeNodeList<T = any> extends TreeNodeBase<"list"> {
35
+ props: Omit<TreeNodeProps, "children"> & {
36
+ children: Reference<T[]>;
37
+ };
38
+ items: Reference<TreeNode[]>;
39
+ map: (item: T, idx: number) => TreeNode;
40
+ }
41
+
42
+ export type TreeNode =
43
+ | TreeNodeElement
44
+ | TreeNodeValue
45
+ | TreeNodeVoid
46
+ | TreeNodeFunction
47
+ | TreeNodeList;
48
+
49
+ const makeBase = <Type extends string>(
50
+ t: Type,
51
+ ): { type: Type; [TreeNodeBrand]: true } => {
52
+ return {
53
+ type: t,
54
+ [TreeNodeBrand]: true,
55
+ };
56
+ };
57
+
58
+ export function isTreeNode(x: unknown): x is TreeNode {
59
+ return has(x, TreeNodeBrand) && x[TreeNodeBrand] === true;
60
+ }
61
+
62
+ // tn - short for (TreeNode)
63
+ export namespace tn {
64
+ export function none(): TreeNodeVoid {
65
+ return {
66
+ ...makeBase("void"),
67
+ props: {},
68
+ };
69
+ }
70
+
71
+ export function list<T>(
72
+ items: Reference<T[]> | T[],
73
+ map?: (item: T, idx: number) => TreeNode,
74
+ ): TreeNodeList {
75
+ const mapFn = map || ((x) => tnu.normalizeOne(x));
76
+ const its = isReference(items) ? items : ref(items);
77
+ return {
78
+ ...makeBase("list"),
79
+ props: {
80
+ children: its,
81
+ },
82
+ map: mapFn,
83
+ items: derived(() => its.get().map(mapFn)),
84
+ };
85
+ }
86
+
87
+ export function value(v: unknown): TreeNodeValue {
88
+ return {
89
+ ...makeBase("value"),
90
+ props: {},
91
+ value: v,
92
+ };
93
+ }
94
+
95
+ export function element(tag: string, props: TreeNodeProps): TreeNodeElement {
96
+ return {
97
+ ...makeBase("element"),
98
+ tag,
99
+ props,
100
+ children: tnu.normalizeChildren(props.children),
101
+ };
102
+ }
103
+
104
+ export function fn(
105
+ fun: TreeNodeFn,
106
+ props: TreeNodeProps = {},
107
+ ): TreeNodeFunction {
108
+ return {
109
+ ...makeBase("function"),
110
+ fn: fun,
111
+ props: props,
112
+ };
113
+ }
114
+ }
115
+
116
+ // tnu - short for (TreeNode Utils)
117
+ export namespace tnu {
118
+ export const normalizeOne = (x: unknown): TreeNode => {
119
+ if (isTreeNode(x)) return x;
120
+ if (typeof x === "undefined" || x === null || x === false) return tn.none();
121
+ if (
122
+ typeof x === "string" ||
123
+ typeof x === "number" ||
124
+ typeof x === "boolean"
125
+ )
126
+ return tn.value(x);
127
+ if (Array.isArray(x)) {
128
+ if (x.length === 1) {
129
+ const first = x[0]!;
130
+ if (isTreeNode(first) && first.type === "list") return first;
131
+ }
132
+ return tn.list(x.map((v) => normalizeOne(v)));
133
+ }
134
+ if (typeof x === "function")
135
+ return tn.fn((props) => normalizeOne((x as TreeNodeFn)(props)));
136
+ throw new Error(`normalizeOne: unhandled ${typeof x}`);
137
+ };
138
+
139
+ export const normalizeChildren = (x: unknown): TreeNode[] => {
140
+ if (isTreeNode(x)) return [x];
141
+ if (typeof x === "function") return [tn.value(x)];
142
+ if (Array.isArray(x)) return x.flatMap((v) => normalizeChildren(v));
143
+
144
+ if (isReference(x)) {
145
+ // Try to understand the intent by peeking at the value
146
+ const val = x.peek();
147
+
148
+ if (Array.isArray(val)) {
149
+ // assume we're trying to render a list
150
+
151
+ return [
152
+ tn.list(
153
+ derived(() =>
154
+ ((x.get() || []) as Array<unknown>).flatMap((v) =>
155
+ tnu.normalizeChildren(v),
156
+ ),
157
+ ),
158
+ ),
159
+ ];
160
+ } else if (isTreeNode(val)) {
161
+ /*
162
+ we turn this into a list so that we can allow syntax like:
163
+ ```
164
+ <div>
165
+ {derived(() => <span/>)}
166
+ </div>
167
+ ```
168
+ instead of
169
+ ```
170
+ <div>
171
+ {derived(() => [<span/>])}
172
+ </div>
173
+ ```
174
+ */
175
+ return [
176
+ tn.list(
177
+ derived(() => {
178
+ return normalizeChildren(x.get())
179
+ }),
180
+ ),
181
+ ];
182
+ }
183
+ return [tn.value(x)];
184
+ }
185
+ return [normalizeOne(x)];
186
+ };
187
+
188
+ export const assertGetKey = (x: TreeNode): string | number => {
189
+ const k = x.props.key;
190
+ if (!(typeof k === "string" || typeof k === "number"))
191
+ throw new Error(`Each item in a list must have a key`);
192
+ return k;
193
+ };
194
+ }
195
+
196
+ export function createNode(
197
+ tag: string,
198
+ props?: TreeNodeProps | null,
199
+ ...children: unknown[]
200
+ ): TreeNodeElement;
201
+ export function createNode<P extends TreeNodeProps>(
202
+ tag: TreeNodeFn<P>,
203
+ props?: P | null,
204
+ ...children: unknown[]
205
+ ): TreeNodeFunction;
206
+ export function createNode(
207
+ tag: string | TreeNodeFn,
208
+ props?: TreeNodeProps | null,
209
+ ...children: unknown[]
210
+ ): TreeNode {
211
+ const normalizedProps: TreeNodeProps = props ?? {};
212
+ const normalizedChildren = tnu.normalizeChildren(children);
213
+
214
+ if (typeof tag === "function") {
215
+ return tn.fn(tag, {
216
+ ...normalizedProps,
217
+ ...(normalizedChildren.length > 0
218
+ ? { children: normalizedChildren }
219
+ : {}),
220
+ });
221
+ }
222
+
223
+ return tn.element(tag, {
224
+ ...props,
225
+ children: normalizedChildren,
226
+ });
227
+ }