@fun-land/fun-web 0.2.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 (71) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +515 -0
  3. package/coverage/clover.xml +136 -0
  4. package/coverage/coverage-final.json +4 -0
  5. package/coverage/lcov-report/base.css +224 -0
  6. package/coverage/lcov-report/block-navigation.js +87 -0
  7. package/coverage/lcov-report/dom.ts.html +961 -0
  8. package/coverage/lcov-report/favicon.png +0 -0
  9. package/coverage/lcov-report/index.html +146 -0
  10. package/coverage/lcov-report/mount.ts.html +202 -0
  11. package/coverage/lcov-report/prettify.css +1 -0
  12. package/coverage/lcov-report/prettify.js +2 -0
  13. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  14. package/coverage/lcov-report/sorter.js +196 -0
  15. package/coverage/lcov-report/state.ts.html +91 -0
  16. package/coverage/lcov.info +260 -0
  17. package/dist/esm/src/dom.d.ts +85 -0
  18. package/dist/esm/src/dom.js +207 -0
  19. package/dist/esm/src/dom.js.map +1 -0
  20. package/dist/esm/src/index.d.ts +7 -0
  21. package/dist/esm/src/index.js +5 -0
  22. package/dist/esm/src/index.js.map +1 -0
  23. package/dist/esm/src/mount.d.ts +21 -0
  24. package/dist/esm/src/mount.js +27 -0
  25. package/dist/esm/src/mount.js.map +1 -0
  26. package/dist/esm/src/state.d.ts +2 -0
  27. package/dist/esm/src/state.js +3 -0
  28. package/dist/esm/src/state.js.map +1 -0
  29. package/dist/esm/src/types.d.ts +3 -0
  30. package/dist/esm/src/types.js +3 -0
  31. package/dist/esm/src/types.js.map +1 -0
  32. package/dist/esm/tsconfig.publish.tsbuildinfo +1 -0
  33. package/dist/src/dom.d.ts +85 -0
  34. package/dist/src/dom.js +224 -0
  35. package/dist/src/dom.js.map +1 -0
  36. package/dist/src/index.d.ts +7 -0
  37. package/dist/src/index.js +24 -0
  38. package/dist/src/index.js.map +1 -0
  39. package/dist/src/mount.d.ts +21 -0
  40. package/dist/src/mount.js +31 -0
  41. package/dist/src/mount.js.map +1 -0
  42. package/dist/src/state.d.ts +2 -0
  43. package/dist/src/state.js +7 -0
  44. package/dist/src/state.js.map +1 -0
  45. package/dist/src/types.d.ts +3 -0
  46. package/dist/src/types.js +4 -0
  47. package/dist/src/types.js.map +1 -0
  48. package/dist/tsconfig.publish.tsbuildinfo +1 -0
  49. package/eslint.config.js +54 -0
  50. package/examples/README.md +67 -0
  51. package/examples/counter/bundle.js +219 -0
  52. package/examples/counter/counter.ts +112 -0
  53. package/examples/counter/index.html +44 -0
  54. package/examples/todo-app/Todo.ts +79 -0
  55. package/examples/todo-app/index.html +142 -0
  56. package/examples/todo-app/todo-app.ts +120 -0
  57. package/examples/todo-app/todo-bundle.js +410 -0
  58. package/jest.config.js +5 -0
  59. package/package.json +49 -0
  60. package/src/dom.test.ts +768 -0
  61. package/src/dom.ts +296 -0
  62. package/src/index.ts +25 -0
  63. package/src/mount.test.ts +220 -0
  64. package/src/mount.ts +39 -0
  65. package/src/state.test.ts +225 -0
  66. package/src/state.ts +2 -0
  67. package/src/types.ts +9 -0
  68. package/tsconfig.json +16 -0
  69. package/tsconfig.publish.json +6 -0
  70. package/wip/hx-magic-properties-plan.md +575 -0
  71. package/wip/next.md +22 -0
@@ -0,0 +1,91 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for state.ts</title>
7
+ <meta charset="utf-8" />
8
+ <link rel="stylesheet" href="prettify.css" />
9
+ <link rel="stylesheet" href="base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <style type='text/css'>
13
+ .coverage-summary .sorter {
14
+ background-image: url(sort-arrow-sprite.png);
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <div class='wrapper'>
21
+ <div class='pad1'>
22
+ <h1><a href="index.html">All files</a> state.ts</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">100% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>2/2</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">100% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>0/0</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">100% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>1/1</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">100% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>1/1</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line high'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
69
+ <span class="cline-any cline-yes">31x</span>
70
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/** Re-export FunState and funState from fun-state with subscribe support */
71
+ export { funState, type FunState } from "@fun-land/fun-state";
72
+ &nbsp;</pre></td></tr></table></pre>
73
+
74
+ <div class='push'></div><!-- for sticky footer -->
75
+ </div><!-- /wrapper -->
76
+ <div class='footer quiet pad2 space-top1 center small'>
77
+ Code coverage generated by
78
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
79
+ at 2026-01-13T02:46:51.482Z
80
+ </div>
81
+ <script src="prettify.js"></script>
82
+ <script>
83
+ window.onload = function () {
84
+ prettyPrint();
85
+ };
86
+ </script>
87
+ <script src="sorter.js"></script>
88
+ <script src="block-navigation.js"></script>
89
+ </body>
90
+ </html>
91
+
@@ -0,0 +1,260 @@
1
+ TN:
2
+ SF:src/dom.ts
3
+ FN:18,(anonymous_0)
4
+ FN:55,(anonymous_1)
5
+ FN:60,(anonymous_2)
6
+ FN:74,(anonymous_3)
7
+ FN:75,(anonymous_4)
8
+ FN:84,(anonymous_5)
9
+ FN:85,(anonymous_6)
10
+ FN:94,(anonymous_7)
11
+ FN:95,(anonymous_8)
12
+ FN:96,(anonymous_9)
13
+ FN:102,bindProperty
14
+ FN:112,(anonymous_11)
15
+ FN:122,(anonymous_12)
16
+ FN:123,(anonymous_13)
17
+ FN:132,(anonymous_14)
18
+ FN:133,(anonymous_15)
19
+ FN:142,(anonymous_16)
20
+ FN:143,(anonymous_17)
21
+ FN:152,(anonymous_18)
22
+ FN:153,(anonymous_19)
23
+ FN:154,(anonymous_20)
24
+ FN:162,(anonymous_21)
25
+ FN:176,(anonymous_22)
26
+ FN:177,(anonymous_23)
27
+ FN:178,(anonymous_24)
28
+ FN:208,keyedChildren
29
+ FN:220,(anonymous_26)
30
+ FN:230,(anonymous_27)
31
+ FN:255,(anonymous_28)
32
+ FN:259,(anonymous_29)
33
+ FN:259,(anonymous_30)
34
+ FN:259,(anonymous_31)
35
+ FN:289,(anonymous_32)
36
+ FN:292,(anonymous_33)
37
+ FNF:34
38
+ FNH:29
39
+ FNDA:32,(anonymous_0)
40
+ FNDA:29,(anonymous_1)
41
+ FNDA:12,(anonymous_2)
42
+ FNDA:4,(anonymous_3)
43
+ FNDA:4,(anonymous_4)
44
+ FNDA:3,(anonymous_5)
45
+ FNDA:3,(anonymous_6)
46
+ FNDA:2,(anonymous_7)
47
+ FNDA:2,(anonymous_8)
48
+ FNDA:3,(anonymous_9)
49
+ FNDA:4,bindProperty
50
+ FNDA:1,(anonymous_11)
51
+ FNDA:4,(anonymous_12)
52
+ FNDA:4,(anonymous_13)
53
+ FNDA:2,(anonymous_14)
54
+ FNDA:2,(anonymous_15)
55
+ FNDA:6,(anonymous_16)
56
+ FNDA:6,(anonymous_17)
57
+ FNDA:3,(anonymous_18)
58
+ FNDA:3,(anonymous_19)
59
+ FNDA:4,(anonymous_20)
60
+ FNDA:3,(anonymous_21)
61
+ FNDA:1,(anonymous_22)
62
+ FNDA:1,(anonymous_23)
63
+ FNDA:3,(anonymous_24)
64
+ FNDA:5,keyedChildren
65
+ FNDA:5,(anonymous_26)
66
+ FNDA:8,(anonymous_27)
67
+ FNDA:21,(anonymous_28)
68
+ FNDA:0,(anonymous_29)
69
+ FNDA:0,(anonymous_30)
70
+ FNDA:0,(anonymous_31)
71
+ FNDA:0,(anonymous_32)
72
+ FNDA:0,(anonymous_33)
73
+ DA:4,2
74
+ DA:18,2
75
+ DA:23,32
76
+ DA:26,32
77
+ DA:27,13
78
+ DA:28,18
79
+ DA:30,16
80
+ DA:32,3
81
+ DA:33,3
82
+ DA:34,13
83
+ DA:36,2
84
+ DA:39,11
85
+ DA:45,32
86
+ DA:46,17
87
+ DA:49,32
88
+ DA:55,2
89
+ DA:59,29
90
+ DA:60,12
91
+ DA:61,24
92
+ DA:62,22
93
+ DA:63,12
94
+ DA:65,10
95
+ DA:73,2
96
+ DA:74,2
97
+ DA:75,4
98
+ DA:76,4
99
+ DA:77,4
100
+ DA:83,2
101
+ DA:84,2
102
+ DA:85,3
103
+ DA:86,3
104
+ DA:87,3
105
+ DA:93,2
106
+ DA:94,2
107
+ DA:95,2
108
+ DA:96,2
109
+ DA:97,3
110
+ DA:99,2
111
+ DA:102,2
112
+ DA:109,4
113
+ DA:112,4
114
+ DA:113,1
115
+ DA:115,4
116
+ DA:121,2
117
+ DA:122,2
118
+ DA:123,4
119
+ DA:124,4
120
+ DA:125,4
121
+ DA:131,2
122
+ DA:132,2
123
+ DA:133,2
124
+ DA:134,2
125
+ DA:135,2
126
+ DA:141,2
127
+ DA:142,2
128
+ DA:143,6
129
+ DA:144,6
130
+ DA:145,6
131
+ DA:151,2
132
+ DA:152,2
133
+ DA:153,3
134
+ DA:154,4
135
+ DA:155,3
136
+ DA:162,2
137
+ DA:168,3
138
+ DA:169,3
139
+ DA:175,2
140
+ DA:176,2
141
+ DA:177,1
142
+ DA:178,3
143
+ DA:208,2
144
+ DA:218,5
145
+ DA:220,5
146
+ DA:221,5
147
+ DA:223,8
148
+ DA:225,8
149
+ DA:227,5
150
+ DA:230,5
151
+ DA:231,8
152
+ DA:233,8
153
+ DA:234,8
154
+ DA:235,8
155
+ DA:236,17
156
+ DA:237,17
157
+ DA:238,16
158
+ DA:239,16
159
+ DA:243,7
160
+ DA:244,7
161
+ DA:245,1
162
+ DA:246,1
163
+ DA:247,1
164
+ DA:252,7
165
+ DA:253,15
166
+ DA:254,9
167
+ DA:255,21
168
+ DA:256,9
169
+ DA:259,0
170
+ DA:261,9
171
+ DA:266,7
172
+ DA:267,7
173
+ DA:268,15
174
+ DA:269,15
175
+ DA:270,15
176
+ DA:271,15
177
+ DA:272,10
178
+ DA:278,5
179
+ DA:281,5
180
+ DA:284,5
181
+ DA:286,4
182
+ DA:289,2
183
+ DA:290,0
184
+ DA:292,2
185
+ DA:293,0
186
+ LF:113
187
+ LH:110
188
+ BRDA:26,0,0,13
189
+ BRDA:26,0,1,19
190
+ BRDA:28,1,0,2
191
+ BRDA:28,1,1,16
192
+ BRDA:30,2,0,3
193
+ BRDA:30,2,1,13
194
+ BRDA:30,3,0,16
195
+ BRDA:30,3,1,3
196
+ BRDA:34,4,0,2
197
+ BRDA:34,4,1,11
198
+ BRDA:34,5,0,13
199
+ BRDA:34,5,1,11
200
+ BRDA:45,6,0,17
201
+ BRDA:45,6,1,15
202
+ BRDA:59,7,0,5
203
+ BRDA:59,7,1,24
204
+ BRDA:61,8,0,22
205
+ BRDA:61,8,1,2
206
+ BRDA:62,9,0,12
207
+ BRDA:62,9,1,10
208
+ BRDA:62,10,0,22
209
+ BRDA:62,10,1,11
210
+ BRDA:237,11,0,1
211
+ BRDA:237,11,1,16
212
+ BRDA:244,12,0,1
213
+ BRDA:244,12,1,6
214
+ BRDA:253,13,0,9
215
+ BRDA:253,13,1,6
216
+ BRDA:271,14,0,10
217
+ BRDA:271,14,1,5
218
+ BRDA:272,15,0,1
219
+ BRDA:272,15,1,9
220
+ BRDA:272,16,0,10
221
+ BRDA:272,16,1,10
222
+ BRDA:290,17,0,0
223
+ BRDA:290,17,1,0
224
+ BRDA:290,18,0,0
225
+ BRDA:290,18,1,0
226
+ BRF:38
227
+ BRH:34
228
+ end_of_record
229
+ TN:
230
+ SF:src/mount.ts
231
+ FN:23,(anonymous_0)
232
+ FN:34,(anonymous_1)
233
+ FNF:2
234
+ FNH:2
235
+ FNDA:9,(anonymous_0)
236
+ FNDA:3,(anonymous_1)
237
+ DA:23,1
238
+ DA:28,9
239
+ DA:29,9
240
+ DA:30,9
241
+ DA:32,9
242
+ DA:35,3
243
+ DA:36,3
244
+ LF:7
245
+ LH:7
246
+ BRF:0
247
+ BRH:0
248
+ end_of_record
249
+ TN:
250
+ SF:src/state.ts
251
+ FN:2,(anonymous_0)
252
+ FNF:1
253
+ FNH:1
254
+ FNDA:28,(anonymous_0)
255
+ DA:2,31
256
+ LF:1
257
+ LH:1
258
+ BRF:0
259
+ BRH:0
260
+ end_of_record
@@ -0,0 +1,85 @@
1
+ /** DOM utilities for functional element creation and manipulation */
2
+ import { FunState } from "./state";
3
+ import type { ElementChild } from "./types";
4
+ /**
5
+ * Create an HTML element with attributes and children
6
+ *
7
+ * Convention:
8
+ * - Properties with dashes (data-*, aria-*) become attributes
9
+ * - Properties starting with 'on' become event listeners
10
+ * - Everything else becomes element properties
11
+ *
12
+ * @example
13
+ * h('button', {className: 'btn', onclick: handler}, 'Click me')
14
+ * h('div', {id: 'app', 'data-test': 'foo'}, [child1, child2])
15
+ */
16
+ export declare const h: <Tag extends keyof HTMLElementTagNameMap>(tag: Tag, attrs?: Record<string, any> | null, children?: ElementChild | ElementChild[]) => HTMLElementTagNameMap[Tag];
17
+ /**
18
+ * Set text content of an element (returns element for chaining)
19
+ */
20
+ export declare const text: (content: string | number) => (el: Element) => Element;
21
+ /**
22
+ * Set an attribute on an element (returns element for chaining)
23
+ */
24
+ export declare const attr: (name: string, value: string) => (el: Element) => Element;
25
+ /**
26
+ * Set multiple attributes on an element (returns element for chaining)
27
+ */
28
+ export declare const attrs: (obj: Record<string, string>) => (el: Element) => Element;
29
+ export declare function bindProperty<E extends Element, K extends keyof E>(el: E, key: K, fs: FunState<E[K]>, signal: AbortSignal): E;
30
+ /**
31
+ * Add CSS classes to an element (returns element for chaining)
32
+ */
33
+ export declare const addClass: (...classes: string[]) => (el: Element) => Element;
34
+ /**
35
+ * Remove CSS classes from an element (returns element for chaining)
36
+ */
37
+ export declare const removeClass: (...classes: string[]) => (el: Element) => Element;
38
+ /**
39
+ * Toggle a CSS class on an element (returns element for chaining)
40
+ */
41
+ export declare const toggleClass: (className: string, force?: boolean) => (el: Element) => Element;
42
+ /**
43
+ * Append children to an element (returns parent for chaining)
44
+ */
45
+ export declare const append: (...children: Element[]) => (el: Element) => Element;
46
+ /**
47
+ * Add event listener with required AbortSignal (returns element for chaining)
48
+ * Signal is required to prevent forgetting cleanup
49
+ */
50
+ export declare const on: <E extends Element, K extends keyof HTMLElementEventMap>(el: E, type: K, handler: (ev: HTMLElementEventMap[K] & {
51
+ currentTarget: E;
52
+ }) => void, signal: AbortSignal) => E;
53
+ /**
54
+ * Functional composition - apply endomorphic functions (`<T>(x: T) => T`) left to right
55
+ */
56
+ export declare const pipeEndo: <T>(...fns: Array<(x: T) => T>) => (x: T) => T;
57
+ /**
58
+ *
59
+ */
60
+ type Keyed = {
61
+ key: string;
62
+ };
63
+ export type KeyedChildren = {
64
+ /** Reconcile DOM children to match current list state */
65
+ reconcile: () => void;
66
+ /** Abort + remove all mounted children */
67
+ dispose: () => void;
68
+ };
69
+ /**
70
+ * Keep a DOM container's children in sync with a FunState<Array<T>> using stable `t.key`.
71
+ *
72
+ * - No VDOM
73
+ * - Preserves existing row elements across updates
74
+ * - Creates one AbortController per row (cleaned up on removal or parent abort)
75
+ * - Reorders by DOM moves (appendChild)
76
+ * - Only remounts if order changes
77
+ */
78
+ export declare function keyedChildren<T extends Keyed>(parent: Element, signal: AbortSignal, list: FunState<T[]>, renderRow: (row: {
79
+ signal: AbortSignal;
80
+ state: FunState<T>;
81
+ remove: () => void;
82
+ }) => Element): KeyedChildren;
83
+ export declare const $: <T extends Element>(selector: string) => T | undefined;
84
+ export declare const $$: <T extends Element>(selector: string) => T[];
85
+ export {};
@@ -0,0 +1,207 @@
1
+ import { filter } from "@fun-land/accessor";
2
+ /**
3
+ * Create an HTML element with attributes and children
4
+ *
5
+ * Convention:
6
+ * - Properties with dashes (data-*, aria-*) become attributes
7
+ * - Properties starting with 'on' become event listeners
8
+ * - Everything else becomes element properties
9
+ *
10
+ * @example
11
+ * h('button', {className: 'btn', onclick: handler}, 'Click me')
12
+ * h('div', {id: 'app', 'data-test': 'foo'}, [child1, child2])
13
+ */
14
+ export const h = (tag,
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ attrs, children) => {
17
+ const element = document.createElement(tag);
18
+ // Apply attributes/properties/events
19
+ if (attrs) {
20
+ for (const [key, value] of Object.entries(attrs)) {
21
+ if (value == null)
22
+ continue;
23
+ if (key.startsWith("on") && typeof value === "function") {
24
+ // Event listener: onclick, onchange, etc.
25
+ const eventName = key.slice(2).toLowerCase();
26
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
27
+ element.addEventListener(eventName, value);
28
+ }
29
+ else if (key.includes("-") || key === "role") {
30
+ // Attribute: data-*, aria-*, role, etc.
31
+ element.setAttribute(key, String(value));
32
+ }
33
+ else {
34
+ // Property: className, id, textContent, etc.
35
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
36
+ element[key] = value;
37
+ }
38
+ }
39
+ }
40
+ // Append children
41
+ if (children != null) {
42
+ appendChildren(element, children);
43
+ }
44
+ return element;
45
+ };
46
+ /**
47
+ * Append children to an element, flattening arrays and converting primitives to text nodes
48
+ */
49
+ const appendChildren = (parent, children) => {
50
+ if (Array.isArray(children)) {
51
+ children.forEach((child) => appendChildren(parent, child));
52
+ }
53
+ else if (children != null) {
54
+ if (typeof children === "string" || typeof children === "number") {
55
+ parent.appendChild(document.createTextNode(String(children)));
56
+ }
57
+ else {
58
+ parent.appendChild(children);
59
+ }
60
+ }
61
+ };
62
+ /**
63
+ * Set text content of an element (returns element for chaining)
64
+ */
65
+ export const text = (content) => (el) => {
66
+ el.textContent = String(content);
67
+ return el;
68
+ };
69
+ /**
70
+ * Set an attribute on an element (returns element for chaining)
71
+ */
72
+ export const attr = (name, value) => (el) => {
73
+ el.setAttribute(name, value);
74
+ return el;
75
+ };
76
+ /**
77
+ * Set multiple attributes on an element (returns element for chaining)
78
+ */
79
+ export const attrs = (obj) => (el) => {
80
+ Object.entries(obj).forEach(([name, value]) => {
81
+ el.setAttribute(name, value);
82
+ });
83
+ return el;
84
+ };
85
+ export function bindProperty(el, key, fs, signal) {
86
+ // initial sync
87
+ el[key] = fs.get();
88
+ // reactive sync
89
+ fs.subscribe(signal, (v) => {
90
+ el[key] = v;
91
+ });
92
+ return el;
93
+ }
94
+ /**
95
+ * Add CSS classes to an element (returns element for chaining)
96
+ */
97
+ export const addClass = (...classes) => (el) => {
98
+ el.classList.add(...classes);
99
+ return el;
100
+ };
101
+ /**
102
+ * Remove CSS classes from an element (returns element for chaining)
103
+ */
104
+ export const removeClass = (...classes) => (el) => {
105
+ el.classList.remove(...classes);
106
+ return el;
107
+ };
108
+ /**
109
+ * Toggle a CSS class on an element (returns element for chaining)
110
+ */
111
+ export const toggleClass = (className, force) => (el) => {
112
+ el.classList.toggle(className, force);
113
+ return el;
114
+ };
115
+ /**
116
+ * Append children to an element (returns parent for chaining)
117
+ */
118
+ export const append = (...children) => (el) => {
119
+ children.forEach((child) => el.appendChild(child));
120
+ return el;
121
+ };
122
+ /**
123
+ * Add event listener with required AbortSignal (returns element for chaining)
124
+ * Signal is required to prevent forgetting cleanup
125
+ */
126
+ export const on = (el, type, handler, signal) => {
127
+ el.addEventListener(type, handler, { signal });
128
+ return el;
129
+ };
130
+ /**
131
+ * Functional composition - apply endomorphic functions (`<T>(x: T) => T`) left to right
132
+ */
133
+ export const pipeEndo = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);
134
+ /**
135
+ * Keep a DOM container's children in sync with a FunState<Array<T>> using stable `t.key`.
136
+ *
137
+ * - No VDOM
138
+ * - Preserves existing row elements across updates
139
+ * - Creates one AbortController per row (cleaned up on removal or parent abort)
140
+ * - Reorders by DOM moves (appendChild)
141
+ * - Only remounts if order changes
142
+ */
143
+ export function keyedChildren(parent, signal, list, renderRow) {
144
+ const rows = new Map();
145
+ const dispose = () => {
146
+ for (const row of rows.values()) {
147
+ // Abort first so listeners/subscriptions clean up
148
+ row.ctrl.abort();
149
+ // Remove from DOM (safe even if already removed)
150
+ row.el.remove();
151
+ }
152
+ rows.clear();
153
+ };
154
+ const reconcile = () => {
155
+ const items = list.get();
156
+ const nextKeys = [];
157
+ const seen = new Set();
158
+ for (const it of items) {
159
+ const k = it.key;
160
+ if (seen.has(k))
161
+ throw new Error(`keyedChildren: duplicate key "${k}"`);
162
+ seen.add(k);
163
+ nextKeys.push(k);
164
+ }
165
+ // Remove missing
166
+ for (const [k, row] of rows) {
167
+ if (!seen.has(k)) {
168
+ row.ctrl.abort();
169
+ row.el.remove();
170
+ rows.delete(k);
171
+ }
172
+ }
173
+ // Ensure present
174
+ for (const k of nextKeys) {
175
+ if (!rows.has(k)) {
176
+ const ctrl = new AbortController();
177
+ const itemState = list.focus(filter((t) => t.key === k));
178
+ const el = renderRow({
179
+ signal: ctrl.signal,
180
+ state: itemState,
181
+ remove: () => list.mod((list) => list.filter((t) => t.key !== k)),
182
+ });
183
+ rows.set(k, { key: k, el, ctrl });
184
+ }
185
+ }
186
+ // Reorder with minimal DOM movement (prevents focus loss)
187
+ const children = parent.children; // live
188
+ for (let i = 0; i < nextKeys.length; i++) {
189
+ const k = nextKeys[i];
190
+ const row = rows.get(k);
191
+ const currentAtI = children[i];
192
+ if (currentAtI !== row.el) {
193
+ parent.insertBefore(row.el, currentAtI !== null && currentAtI !== void 0 ? currentAtI : null);
194
+ }
195
+ }
196
+ };
197
+ // Reconcile whenever the list changes; `subscribe` will unsubscribe on abort (per your fix).
198
+ list.subscribe(signal, reconcile);
199
+ // Ensure all children clean up when parent aborts
200
+ signal.addEventListener("abort", dispose, { once: true });
201
+ // Initial mount
202
+ reconcile();
203
+ return { reconcile, dispose };
204
+ }
205
+ export const $ = (selector) => { var _a; return (_a = document.querySelector(selector)) !== null && _a !== void 0 ? _a : undefined; };
206
+ export const $$ = (selector) => Array.from(document.querySelectorAll(selector));
207
+ //# sourceMappingURL=dom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.js","sourceRoot":"","sources":["../../../src/dom.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type { Component, ElementChild } from "./types";
2
+ export type { FunState } from "./state";
3
+ export type { MountedComponent } from "./mount";
4
+ export type { KeyedChildren } from "./dom";
5
+ export { funState } from "./state";
6
+ export { h, text, attr, attrs, addClass, removeClass, toggleClass, append, on, bindProperty, keyedChildren, pipeEndo, $, $$, } from "./dom";
7
+ export { mount } from "./mount";
@@ -0,0 +1,5 @@
1
+ // @fun-land/fun-web - Web component library for fun-land
2
+ export { funState } from "./state";
3
+ export { h, text, attr, attrs, addClass, removeClass, toggleClass, append, on, bindProperty, keyedChildren, pipeEndo, $, $$, } from "./dom";
4
+ export { mount } from "./mount";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AAOzD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EACL,CAAC,EACD,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,WAAW,EACX,WAAW,EACX,MAAM,EACN,EAAE,EACF,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,CAAC,EACD,EAAE,GACH,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,21 @@
1
+ /** Component mounting utilities */
2
+ import type { Component } from "./types";
3
+ export interface MountedComponent {
4
+ element: Element;
5
+ unmount: () => void;
6
+ }
7
+ /**
8
+ * Mount a component to the DOM
9
+ * Creates an AbortController to manage component lifecycle
10
+ *
11
+ * @param component - Component function to mount
12
+ * @param props - Props passed to component (including any state)
13
+ * @param container - DOM element to mount into
14
+ * @returns Object with element reference and unmount function
15
+ *
16
+ * @example
17
+ * const mounted = mount(Counter, {label: 'Count', state: counterState}, document.body)
18
+ * // Later:
19
+ * mounted.unmount() // cleanup all subscriptions and listeners
20
+ */
21
+ export declare const mount: <Props>(component: Component<Props>, props: Props, container: Element) => MountedComponent;