ctrl-fx 0.1.0 → 0.1.2

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 (152) hide show
  1. package/README.md +13 -0
  2. package/dist/chunk-BDTTV33M.js +4050 -0
  3. package/dist/chunk-EKBLRHWK.js +916 -0
  4. package/dist/chunk-OSIRHKDB.js +1812 -0
  5. package/dist/chunk-PKBMQBKP.js +7 -0
  6. package/dist/chunk-XICUXW4T.js +252 -0
  7. package/dist/db/index.d.ts +218 -16
  8. package/dist/db/index.js +53 -19
  9. package/dist/dom/index.d.ts +2 -248
  10. package/dist/dom/index.js +30 -266
  11. package/dist/effects.d.ts +2 -580
  12. package/dist/effects.js +111 -1556
  13. package/dist/index-DeEjNrg-.d.ts +1399 -0
  14. package/dist/index.d.ts +2 -41
  15. package/dist/index.js +18 -78
  16. package/dist/router.d.ts +11 -12
  17. package/dist/router.js +57 -68
  18. package/dist/testing.d.ts +67 -60
  19. package/dist/testing.js +46 -158
  20. package/dist/webcomponent.d.ts +7 -4
  21. package/dist/webcomponent.js +48 -40
  22. package/package.json +4 -3
  23. package/dist/clipboard.d.ts +0 -7
  24. package/dist/clipboard.d.ts.map +0 -1
  25. package/dist/clipboard.js +0 -3
  26. package/dist/db/effects.d.ts +0 -78
  27. package/dist/db/effects.d.ts.map +0 -1
  28. package/dist/db/effects.js +0 -98
  29. package/dist/db/errors.d.ts +0 -21
  30. package/dist/db/errors.d.ts.map +0 -1
  31. package/dist/db/errors.js +0 -13
  32. package/dist/db/index.d.ts.map +0 -1
  33. package/dist/db/keypath.d.ts +0 -40
  34. package/dist/db/keypath.d.ts.map +0 -1
  35. package/dist/db/keypath.js +0 -20
  36. package/dist/db/setup.d.ts +0 -83
  37. package/dist/db/setup.d.ts.map +0 -1
  38. package/dist/db/setup.js +0 -100
  39. package/dist/db/testdb.d.ts +0 -19
  40. package/dist/db/testdb.d.ts.map +0 -1
  41. package/dist/db/testdb.js +0 -34
  42. package/dist/dom/attrs.d.ts +0 -67
  43. package/dist/dom/attrs.d.ts.map +0 -1
  44. package/dist/dom/attrs.js +0 -154
  45. package/dist/dom/components.d.ts +0 -43
  46. package/dist/dom/components.d.ts.map +0 -1
  47. package/dist/dom/components.js +0 -86
  48. package/dist/dom/elems.d.ts +0 -121
  49. package/dist/dom/elems.d.ts.map +0 -1
  50. package/dist/dom/elems.js +0 -52
  51. package/dist/dom/events.d.ts +0 -232
  52. package/dist/dom/events.d.ts.map +0 -1
  53. package/dist/dom/events.js +0 -289
  54. package/dist/dom/index.d.ts.map +0 -1
  55. package/dist/dom/nodeid.d.ts +0 -9
  56. package/dist/dom/nodeid.d.ts.map +0 -1
  57. package/dist/dom/nodeid.js +0 -27
  58. package/dist/dom/svg.d.ts +0 -23
  59. package/dist/dom/svg.d.ts.map +0 -1
  60. package/dist/dom/svg.js +0 -1
  61. package/dist/dom/views.d.ts +0 -60
  62. package/dist/dom/views.d.ts.map +0 -1
  63. package/dist/dom/views.js +0 -154
  64. package/dist/effects.d.ts.map +0 -1
  65. package/dist/index.d.ts.map +0 -1
  66. package/dist/internal/clipboard.d.ts +0 -5
  67. package/dist/internal/clipboard.d.ts.map +0 -1
  68. package/dist/internal/clipboard.js +0 -33
  69. package/dist/internal/cmpmgr.d.ts +0 -46
  70. package/dist/internal/cmpmgr.d.ts.map +0 -1
  71. package/dist/internal/cmpmgr.js +0 -561
  72. package/dist/internal/dbmgr.d.ts +0 -11
  73. package/dist/internal/dbmgr.d.ts.map +0 -1
  74. package/dist/internal/dbmgr.js +0 -200
  75. package/dist/internal/dominterpreter.d.ts +0 -5
  76. package/dist/internal/dominterpreter.d.ts.map +0 -1
  77. package/dist/internal/dominterpreter.js +0 -337
  78. package/dist/internal/eventmgr.d.ts +0 -41
  79. package/dist/internal/eventmgr.d.ts.map +0 -1
  80. package/dist/internal/eventmgr.js +0 -696
  81. package/dist/internal/index.d.ts +0 -19
  82. package/dist/internal/index.d.ts.map +0 -1
  83. package/dist/internal/index.js +0 -38
  84. package/dist/internal/interpreter.d.ts +0 -10
  85. package/dist/internal/interpreter.d.ts.map +0 -1
  86. package/dist/internal/interpreter.js +0 -1
  87. package/dist/internal/locutils.d.ts +0 -4
  88. package/dist/internal/locutils.d.ts.map +0 -1
  89. package/dist/internal/locutils.js +0 -22
  90. package/dist/internal/net.d.ts +0 -4
  91. package/dist/internal/net.d.ts.map +0 -1
  92. package/dist/internal/net.js +0 -64
  93. package/dist/internal/realdom.d.ts +0 -249
  94. package/dist/internal/realdom.d.ts.map +0 -1
  95. package/dist/internal/realdom.js +0 -1
  96. package/dist/internal/styleregistry.d.ts +0 -6
  97. package/dist/internal/styleregistry.d.ts.map +0 -1
  98. package/dist/internal/styleregistry.js +0 -18
  99. package/dist/internal/taskreg.d.ts +0 -9
  100. package/dist/internal/taskreg.d.ts.map +0 -1
  101. package/dist/internal/taskreg.js +0 -28
  102. package/dist/internal/testing.d.ts +0 -12
  103. package/dist/internal/testing.d.ts.map +0 -1
  104. package/dist/internal/testing.js +0 -974
  105. package/dist/internal/testinginterpreter.d.ts +0 -10
  106. package/dist/internal/testinginterpreter.d.ts.map +0 -1
  107. package/dist/internal/testinginterpreter.js +0 -646
  108. package/dist/internal/vdom.d.ts +0 -93
  109. package/dist/internal/vdom.d.ts.map +0 -1
  110. package/dist/internal/vdom.js +0 -470
  111. package/dist/json/index.d.ts +0 -5
  112. package/dist/json/index.d.ts.map +0 -1
  113. package/dist/json/index.js +0 -2
  114. package/dist/json/validation.d.ts +0 -28
  115. package/dist/json/validation.d.ts.map +0 -1
  116. package/dist/json/validation.js +0 -8
  117. package/dist/net/fragment.d.ts +0 -10
  118. package/dist/net/fragment.d.ts.map +0 -1
  119. package/dist/net/fragment.js +0 -27
  120. package/dist/net/index.d.ts +0 -42
  121. package/dist/net/index.d.ts.map +0 -1
  122. package/dist/net/index.js +0 -60
  123. package/dist/net/location.d.ts +0 -21
  124. package/dist/net/location.d.ts.map +0 -1
  125. package/dist/net/location.js +0 -41
  126. package/dist/net/path.d.ts +0 -12
  127. package/dist/net/path.d.ts.map +0 -1
  128. package/dist/net/path.js +0 -29
  129. package/dist/net/queryparam.d.ts +0 -10
  130. package/dist/net/queryparam.d.ts.map +0 -1
  131. package/dist/net/queryparam.js +0 -34
  132. package/dist/router.d.ts.map +0 -1
  133. package/dist/scroll.d.ts +0 -7
  134. package/dist/scroll.d.ts.map +0 -1
  135. package/dist/scroll.js +0 -3
  136. package/dist/testing.d.ts.map +0 -1
  137. package/dist/utils/apply.d.ts +0 -9
  138. package/dist/utils/apply.d.ts.map +0 -1
  139. package/dist/utils/apply.js +0 -10
  140. package/dist/utils/index.d.ts +0 -22
  141. package/dist/utils/index.d.ts.map +0 -1
  142. package/dist/utils/index.js +0 -138
  143. package/dist/utils/lens.d.ts +0 -18
  144. package/dist/utils/lens.d.ts.map +0 -1
  145. package/dist/utils/lens.js +0 -27
  146. package/dist/utils/match.d.ts +0 -17
  147. package/dist/utils/match.d.ts.map +0 -1
  148. package/dist/utils/match.js +0 -28
  149. package/dist/utils/result.d.ts +0 -47
  150. package/dist/utils/result.d.ts.map +0 -1
  151. package/dist/utils/result.js +0 -97
  152. package/dist/webcomponent.d.ts.map +0 -1
@@ -0,0 +1,4050 @@
1
+ import {
2
+ containerEventTypes,
3
+ eventTypes,
4
+ foldNode,
5
+ getId,
6
+ isNodeGroup,
7
+ nodeGroup,
8
+ nodeId
9
+ } from "./chunk-EKBLRHWK.js";
10
+ import {
11
+ ResponseBody,
12
+ emptyLocation,
13
+ emptyPath,
14
+ eq,
15
+ exhaustivenessCheck,
16
+ failure,
17
+ filterNot,
18
+ formatLocation,
19
+ internalLocationFromLocation,
20
+ isEffect,
21
+ makeEffects,
22
+ parseAllQueryParams,
23
+ parseFragment,
24
+ parseInternalLocation,
25
+ parsePath,
26
+ pure,
27
+ stringToInt,
28
+ success,
29
+ taskId
30
+ } from "./chunk-OSIRHKDB.js";
31
+ import {
32
+ dbVersion,
33
+ run,
34
+ run2
35
+ } from "./chunk-XICUXW4T.js";
36
+ import {
37
+ __publicField
38
+ } from "./chunk-PKBMQBKP.js";
39
+
40
+ // src/internal/locutils.ts
41
+ function currentLocation(window2) {
42
+ return {
43
+ protocol: filterNot(window2.location.protocol?.trim(), (p) => p.length <= 0),
44
+ hostname: filterNot(window2.location.hostname?.trim(), (h) => h.length <= 0),
45
+ port: window2.location.port ? stringToInt(window2.location.port.trim()) : void 0,
46
+ path: window2.location.pathname ? parsePath(window2.location.pathname) : emptyPath,
47
+ queryParams: window2.location.search?.trim() ? parseAllQueryParams(window2.location.search?.trim()) : [],
48
+ fragment: window2.location.hash ? parseFragment(window2.location.hash) : void 0
49
+ };
50
+ }
51
+
52
+ // src/internal/eventmgr.ts
53
+ function toNativeEventType(type) {
54
+ return type === "textinput" ? "input" : type;
55
+ }
56
+ function makeEventTypeKeyedRecord(eventTypes2, defaultValue) {
57
+ const record = {};
58
+ for (const eventType of eventTypes2) {
59
+ record[eventType] = defaultValue();
60
+ }
61
+ return record;
62
+ }
63
+ function makeListenerCounts(eventTypes2) {
64
+ return makeEventTypeKeyedRecord(eventTypes2, () => ({
65
+ regular: 0,
66
+ capture: 0
67
+ }));
68
+ }
69
+ function updateListenerChangeMap(map, eventType, capture) {
70
+ const existing = map.get(eventType);
71
+ if (existing) {
72
+ if (capture) {
73
+ existing.capture = true;
74
+ } else {
75
+ existing.regular = true;
76
+ }
77
+ } else {
78
+ map.set(eventType, {
79
+ capture,
80
+ regular: !capture
81
+ });
82
+ }
83
+ }
84
+ function adjustCountsAndGetListenerTypesToRemoveAndAdd(counts, oldListeners, newListeners) {
85
+ const countDiffs = {};
86
+ oldListeners.forEach((listener) => {
87
+ const capture = listener.options && listener.options.capture ? true : false;
88
+ const totals = countDiffs[listener._type];
89
+ const currDiff = capture ? totals?.capture : totals?.regular;
90
+ const newDiff = currDiff ? currDiff - 1 : -1;
91
+ if (totals) {
92
+ if (capture) {
93
+ totals.capture = newDiff;
94
+ } else {
95
+ totals.regular = newDiff;
96
+ }
97
+ } else {
98
+ countDiffs[listener._type] = {
99
+ regular: capture ? 0 : newDiff,
100
+ capture: capture ? newDiff : 0
101
+ };
102
+ }
103
+ });
104
+ const allNewEvents = /* @__PURE__ */ new Map();
105
+ newListeners.forEach((listener) => {
106
+ const capture = listener.options && listener.options.capture ? true : false;
107
+ const totals = countDiffs[listener._type];
108
+ const currDiff = capture ? totals?.capture : totals?.regular;
109
+ const newDiff = currDiff ? currDiff + 1 : 1;
110
+ if (totals) {
111
+ if (capture) {
112
+ totals.capture = newDiff;
113
+ } else {
114
+ totals.regular = newDiff;
115
+ }
116
+ } else {
117
+ countDiffs[listener._type] = {
118
+ regular: capture ? 0 : newDiff,
119
+ capture: capture ? newDiff : 0
120
+ };
121
+ }
122
+ updateListenerChangeMap(allNewEvents, listener._type, capture);
123
+ });
124
+ const toAdd = /* @__PURE__ */ new Map();
125
+ const toRemove = /* @__PURE__ */ new Map();
126
+ for (const [k, v] of Object.entries(countDiffs)) {
127
+ const tpe = k;
128
+ const diffTotals = v;
129
+ const currCount = counts[tpe];
130
+ currCount.capture = currCount.capture + diffTotals.capture;
131
+ currCount.regular = currCount.regular + diffTotals.regular;
132
+ if (currCount.capture === 0) {
133
+ if (diffTotals.capture < 0) {
134
+ updateListenerChangeMap(toRemove, tpe, true);
135
+ }
136
+ } else if (currCount.capture > 0) {
137
+ if (diffTotals.capture === currCount.capture) {
138
+ updateListenerChangeMap(toAdd, tpe, true);
139
+ }
140
+ } else {
141
+ throw new Error(
142
+ "EventListener counts out of sync (this indicates a ctrl-fx framework bug)"
143
+ );
144
+ }
145
+ if (currCount.regular === 0) {
146
+ if (diffTotals.regular < 0) {
147
+ updateListenerChangeMap(toRemove, tpe, false);
148
+ }
149
+ } else if (currCount.regular > 0) {
150
+ if (diffTotals.regular === currCount.regular) {
151
+ updateListenerChangeMap(toAdd, tpe, false);
152
+ }
153
+ } else {
154
+ throw new Error(
155
+ "EventListener counts out of sync (this indicates a ctrl-fx framework bug)"
156
+ );
157
+ }
158
+ }
159
+ return { toRemove, toAdd, allNewEvents };
160
+ }
161
+ var EventManagerDelegate = class {
162
+ constructor(window2, runEffect) {
163
+ __publicField(this, "window");
164
+ __publicField(this, "runEffect");
165
+ this.window = window2;
166
+ this.runEffect = runEffect;
167
+ }
168
+ runListeners(ev, listeners) {
169
+ if (listeners.length) {
170
+ const listener = listeners[0];
171
+ if (toNativeEventType(listener.listener._type) === ev.type.toLowerCase() && (ev.eventPhase !== 1 || listener.listener.options.capture)) {
172
+ this.runListener(listener, ev, (keepGoing) => {
173
+ if (keepGoing) {
174
+ this.runListeners(ev, listeners.slice(1));
175
+ } else {
176
+ return;
177
+ }
178
+ });
179
+ } else {
180
+ this.runListeners(ev, listeners.slice(1));
181
+ }
182
+ } else {
183
+ return;
184
+ }
185
+ }
186
+ runListener(listenerAndNode, ev, onComplete) {
187
+ let effect;
188
+ const listener = listenerAndNode.listener;
189
+ switch (listener._type) {
190
+ case "focus":
191
+ case "blur": {
192
+ effect = listener.effect;
193
+ break;
194
+ }
195
+ case "change": {
196
+ let value = "";
197
+ let checked = false;
198
+ if (ev.target && "tagName" in ev.target) {
199
+ switch (ev.target.tagName) {
200
+ case "INPUT": {
201
+ value = ev.target.value;
202
+ checked = ev.target.checked;
203
+ break;
204
+ }
205
+ case "SELECT":
206
+ case "TEXTAREA": {
207
+ value = ev.target.value;
208
+ break;
209
+ }
210
+ }
211
+ }
212
+ const changeData = { value, checked };
213
+ effect = listener.effect(changeData);
214
+ break;
215
+ }
216
+ case "click":
217
+ case "dblclick":
218
+ case "mouseenter":
219
+ case "mouseleave":
220
+ case "submit": {
221
+ effect = listener.effect;
222
+ break;
223
+ }
224
+ case "mousemove": {
225
+ assertMouseMoveEvent(ev);
226
+ const mouseMoveData = {
227
+ clientX: ev.clientX,
228
+ clientY: ev.clientY,
229
+ pageX: ev.pageX,
230
+ pageY: ev.pageY,
231
+ movementX: ev.movementX,
232
+ movementY: ev.movementY
233
+ };
234
+ effect = listener.effect(mouseMoveData);
235
+ break;
236
+ }
237
+ case "wheel": {
238
+ assertWheelEvent(ev);
239
+ const wheelData = {
240
+ deltaX: ev.deltaX,
241
+ deltaY: ev.deltaY,
242
+ deltaZ: ev.deltaZ,
243
+ deltaMode: ev.deltaMode
244
+ };
245
+ effect = listener.effect(wheelData);
246
+ break;
247
+ }
248
+ case "scroll":
249
+ case "scrollend": {
250
+ const owningNode = listenerAndNode.owningNode;
251
+ const scrollData = owningNode._type === "InternalElementNode" ? {
252
+ scrollTop: owningNode.realNode.scrollTop,
253
+ scrollLeft: owningNode.realNode.scrollLeft
254
+ } : { scrollTop: 0, scrollLeft: 0 };
255
+ effect = listener.effect(scrollData);
256
+ break;
257
+ }
258
+ //console.log(ev.target && 'tagName' in ev.target && ev.target.tagName === 'INPUT')
259
+ case "textinput": {
260
+ let textState;
261
+ if (ev.target && "tagName" in ev.target) {
262
+ switch (ev.target.tagName) {
263
+ case "INPUT":
264
+ case "TEXTAREA": {
265
+ textState = {
266
+ value: ev.target.value,
267
+ selectionStart: ev.target.selectionStart,
268
+ selectionEnd: ev.target.selectionEnd
269
+ };
270
+ break;
271
+ }
272
+ case "DIV": {
273
+ textState = {
274
+ value: ev.target.innerText,
275
+ selectionStart: 0,
276
+ selectionEnd: 0
277
+ };
278
+ break;
279
+ }
280
+ default: {
281
+ textState = {
282
+ value: "",
283
+ selectionStart: 0,
284
+ selectionEnd: 0
285
+ };
286
+ }
287
+ }
288
+ } else {
289
+ textState = {
290
+ value: "",
291
+ selectionStart: 0,
292
+ selectionEnd: 0
293
+ };
294
+ }
295
+ effect = listener.effect(textState);
296
+ break;
297
+ }
298
+ case "touchmove":
299
+ case "touchstart":
300
+ case "touchend":
301
+ case "touchcancel": {
302
+ assertTouchEvent(ev);
303
+ const touchData = [];
304
+ for (let i = 0; i < ev.touches.length; i++) {
305
+ touchData.push({
306
+ clientX: ev.touches[i].clientX,
307
+ clientY: ev.touches[i].clientY,
308
+ pageX: ev.touches[i].pageX,
309
+ pageY: ev.touches[i].pageY
310
+ });
311
+ }
312
+ effect = listener.effect(touchData);
313
+ break;
314
+ }
315
+ case "keydown":
316
+ case "keyup": {
317
+ assertKeyEvent(ev);
318
+ const keyData = { key: ev.key, code: ev.code };
319
+ effect = listener.effect(keyData);
320
+ break;
321
+ }
322
+ case "resize": {
323
+ const dimensions = {
324
+ width: this.window.innerWidth,
325
+ height: this.window.innerHeight
326
+ };
327
+ effect = listener.onChange(dimensions);
328
+ break;
329
+ }
330
+ case "popstate":
331
+ case "locationchange": {
332
+ const loc = internalLocationFromLocation(currentLocation(this.window));
333
+ effect = listener.onChange(loc);
334
+ break;
335
+ }
336
+ case "visibilitychange": {
337
+ const state = this.window.document.visibilityState;
338
+ effect = listener.onChange(state);
339
+ break;
340
+ }
341
+ case "online":
342
+ case "offline": {
343
+ effect = listener.effect;
344
+ break;
345
+ }
346
+ case "custom": {
347
+ effect = listener.effect(ev.detail);
348
+ break;
349
+ }
350
+ default:
351
+ exhaustivenessCheck(listener);
352
+ }
353
+ this.runEffect(effect, listenerAndNode.owningNode, (result) => {
354
+ if (result !== void 0) {
355
+ if (result.preventDefault) {
356
+ ev.preventDefault();
357
+ }
358
+ if (result.stopPropagation) {
359
+ ev.stopImmediatePropagation();
360
+ onComplete(false);
361
+ } else {
362
+ onComplete(true);
363
+ }
364
+ } else {
365
+ onComplete(true);
366
+ }
367
+ });
368
+ }
369
+ };
370
+ function assertMouseMoveEvent(event) {
371
+ if (!("clientX" in event)) {
372
+ throw new Error("Event is not a mousemove event");
373
+ }
374
+ }
375
+ function assertWheelEvent(event) {
376
+ if (!("deltaX" in event)) {
377
+ throw new Error("Event is not a wheel event");
378
+ }
379
+ }
380
+ function assertTouchEvent(event) {
381
+ if (!(event.type.toLowerCase().startsWith("touch") && "touches" in event)) {
382
+ throw new Error("Event is not a touch event");
383
+ }
384
+ }
385
+ function assertKeyEvent(event) {
386
+ if (!(event.type.toLowerCase().startsWith("key") && "key" in event && "code" in event)) {
387
+ throw new Error("Event is not a key event");
388
+ }
389
+ }
390
+ var ContainerEventManagerDelegate = class extends EventManagerDelegate {
391
+ constructor(window2, runEffect) {
392
+ super(window2, runEffect);
393
+ __publicField(this, "nodeLookupByEvent", makeEventTypeKeyedRecord(
394
+ containerEventTypes,
395
+ () => /* @__PURE__ */ new Set()
396
+ ));
397
+ __publicField(this, "eventListeners", /* @__PURE__ */ new Map());
398
+ __publicField(this, "windowEventListener", (event) => {
399
+ this.onEvent(event);
400
+ });
401
+ __publicField(this, "windowCaptureEventListener", (event) => {
402
+ this.onEvent(event);
403
+ });
404
+ __publicField(this, "documentEventListener", (event) => {
405
+ this.onEvent(event);
406
+ });
407
+ __publicField(this, "documentCaptureEventListener", (event) => {
408
+ this.onEvent(event);
409
+ });
410
+ __publicField(this, "counts");
411
+ this.counts = makeListenerCounts(containerEventTypes);
412
+ }
413
+ onEvent(event) {
414
+ const eventType = event.type;
415
+ const nodeSet = this.nodeLookupByEvent[eventType];
416
+ if (!nodeSet) {
417
+ return;
418
+ }
419
+ const listenersToRun = [];
420
+ for (const node of nodeSet) {
421
+ const listeners = this.eventListeners.get(node);
422
+ if (listeners) {
423
+ listeners.forEach((listener) => {
424
+ if (listener._type === eventType && (event.eventPhase !== 1 || listener.options.capture)) {
425
+ listenersToRun.push({ listener, owningNode: node });
426
+ }
427
+ });
428
+ }
429
+ }
430
+ this.runListeners(event, listenersToRun);
431
+ if (eventType === "popstate") {
432
+ this.window.dispatchEvent(new Event("locationchange"));
433
+ }
434
+ }
435
+ setContainerListeners(node) {
436
+ if (node.containerListeners.length === 0) {
437
+ this.removeContainerListeners(node);
438
+ return;
439
+ }
440
+ const existingListeners = this.eventListeners.get(node);
441
+ const passive = node.containerListeners.reduce(
442
+ (acc, listener) => acc && (listener.options.passive ? true : false),
443
+ true
444
+ );
445
+ const adjustments = adjustCountsAndGetListenerTypesToRemoveAndAdd(
446
+ this.counts,
447
+ existingListeners || [],
448
+ node.containerListeners
449
+ );
450
+ containerEventTypes.forEach((ev) => {
451
+ if (adjustments.allNewEvents.has(ev)) {
452
+ this.nodeLookupByEvent[ev].add(node);
453
+ } else {
454
+ this.nodeLookupByEvent[ev].delete(node);
455
+ }
456
+ });
457
+ if (node.containerListeners.length > 0) {
458
+ this.eventListeners.set(node, [...node.containerListeners]);
459
+ } else {
460
+ this.eventListeners.delete(node);
461
+ }
462
+ this.processAdjustments(adjustments, passive);
463
+ }
464
+ processAdjustments(adjustments, passive) {
465
+ for (const [ev, changes] of adjustments.toAdd) {
466
+ switch (ev) {
467
+ case "resize":
468
+ case "popstate":
469
+ case "locationchange":
470
+ case "online":
471
+ case "offline": {
472
+ if (changes.capture) {
473
+ this.window.addEventListener(ev, this.windowCaptureEventListener, {
474
+ capture: true,
475
+ passive
476
+ });
477
+ }
478
+ if (changes.regular) {
479
+ this.window.addEventListener(ev, this.windowEventListener, {
480
+ capture: false,
481
+ passive
482
+ });
483
+ }
484
+ break;
485
+ }
486
+ case "keydown":
487
+ case "keyup":
488
+ case "visibilitychange": {
489
+ if (changes.capture) {
490
+ this.window.document.addEventListener(
491
+ ev,
492
+ this.documentCaptureEventListener,
493
+ { capture: true, passive }
494
+ );
495
+ }
496
+ if (changes.regular) {
497
+ this.window.document.addEventListener(
498
+ ev,
499
+ this.documentEventListener,
500
+ { capture: false, passive }
501
+ );
502
+ }
503
+ break;
504
+ }
505
+ default: {
506
+ exhaustivenessCheck(ev);
507
+ }
508
+ }
509
+ }
510
+ }
511
+ removeContainerListeners(node) {
512
+ const existingListeners = this.eventListeners.get(node);
513
+ if (!existingListeners) {
514
+ return;
515
+ }
516
+ const adjustments = adjustCountsAndGetListenerTypesToRemoveAndAdd(
517
+ this.counts,
518
+ existingListeners,
519
+ []
520
+ );
521
+ this.processAdjustments(adjustments, false);
522
+ existingListeners.forEach((listener) => {
523
+ this.nodeLookupByEvent[listener._type].delete(node);
524
+ });
525
+ this.eventListeners.delete(node);
526
+ }
527
+ };
528
+ var ElementEventManagerDelegate = class extends EventManagerDelegate {
529
+ constructor(window2, runEffect) {
530
+ super(window2, runEffect);
531
+ __publicField(this, "listeners", /* @__PURE__ */ new Map());
532
+ __publicField(this, "customHandlers", /* @__PURE__ */ new Map());
533
+ }
534
+ setElementListeners(node) {
535
+ const standardListeners = node.eventListeners.filter(
536
+ (l) => l._type !== "custom"
537
+ );
538
+ const customListeners = node.eventListeners.filter(
539
+ (l) => l._type === "custom"
540
+ );
541
+ this.updateCustomListeners(node, customListeners);
542
+ if (standardListeners.length === 0) {
543
+ this.removeStandardListeners(node);
544
+ return;
545
+ }
546
+ let listenerInfo = this.listeners.get(node);
547
+ if (!listenerInfo) {
548
+ const that = this;
549
+ listenerInfo = {
550
+ counts: makeListenerCounts(eventTypes),
551
+ listeners: [],
552
+ realListener(event) {
553
+ const listeners = that.listeners.get(node)?.listeners;
554
+ if (listeners) {
555
+ that.runListeners(
556
+ event,
557
+ listeners.map((listener) => ({
558
+ listener,
559
+ owningNode: node
560
+ }))
561
+ );
562
+ }
563
+ }
564
+ };
565
+ this.listeners.set(node, listenerInfo);
566
+ }
567
+ const passive = standardListeners.reduce(
568
+ (acc, listener) => acc && (listener.options.passive ? true : false),
569
+ true
570
+ );
571
+ const adjustments = adjustCountsAndGetListenerTypesToRemoveAndAdd(
572
+ listenerInfo.counts,
573
+ listenerInfo.listeners,
574
+ standardListeners
575
+ );
576
+ listenerInfo.listeners = [...standardListeners];
577
+ for (const [tpe, info] of adjustments.toAdd) {
578
+ const domType = toNativeEventType(tpe);
579
+ if (info.regular) {
580
+ node.realNode.addEventListener(domType, listenerInfo.realListener, {
581
+ capture: false,
582
+ passive
583
+ });
584
+ }
585
+ if (info.capture) {
586
+ node.realNode.addEventListener(domType, listenerInfo.realListener, {
587
+ capture: true,
588
+ passive
589
+ });
590
+ }
591
+ }
592
+ for (const [tpe, info] of adjustments.toRemove) {
593
+ const domType = toNativeEventType(tpe);
594
+ if (info.regular) {
595
+ node.realNode.removeEventListener(domType, listenerInfo.realListener, false);
596
+ }
597
+ if (info.capture) {
598
+ node.realNode.removeEventListener(domType, listenerInfo.realListener, true);
599
+ }
600
+ }
601
+ }
602
+ updateCustomListeners(node, customListeners) {
603
+ const existingHandlers = this.customHandlers.get(node) ?? /* @__PURE__ */ new Map();
604
+ const byName = /* @__PURE__ */ new Map();
605
+ for (const l of customListeners) {
606
+ const arr = byName.get(l.eventName) ?? [];
607
+ arr.push(l);
608
+ byName.set(l.eventName, arr);
609
+ }
610
+ for (const [name, { realListener }] of existingHandlers) {
611
+ if (!byName.has(name)) {
612
+ node.realNode.removeEventListener(name, realListener, false);
613
+ existingHandlers.delete(name);
614
+ }
615
+ }
616
+ for (const [name, listeners] of byName) {
617
+ const existing = existingHandlers.get(name);
618
+ if (existing) {
619
+ existing.listeners = listeners;
620
+ } else {
621
+ const that = this;
622
+ const info = { listeners, realListener: null };
623
+ info.realListener = (ev) => {
624
+ const detail = ev.detail;
625
+ for (const l of info.listeners) {
626
+ that.runEffect(l.effect(detail), node, (result) => {
627
+ if (result?.stopPropagation) ev.stopImmediatePropagation();
628
+ if (result?.preventDefault) ev.preventDefault();
629
+ });
630
+ }
631
+ };
632
+ existingHandlers.set(name, info);
633
+ node.realNode.addEventListener(name, info.realListener, {
634
+ capture: false,
635
+ passive: false
636
+ });
637
+ }
638
+ }
639
+ if (existingHandlers.size > 0) {
640
+ this.customHandlers.set(node, existingHandlers);
641
+ } else {
642
+ this.customHandlers.delete(node);
643
+ }
644
+ }
645
+ removeStandardListeners(node) {
646
+ const listenerInfo = this.listeners.get(node);
647
+ if (!listenerInfo) {
648
+ return;
649
+ }
650
+ for (const [eventType, info] of Object.entries(listenerInfo.counts)) {
651
+ const domType = toNativeEventType(eventType);
652
+ if (info.regular) {
653
+ node.realNode.removeEventListener(
654
+ domType,
655
+ listenerInfo.realListener,
656
+ false
657
+ );
658
+ }
659
+ if (info.capture) {
660
+ node.realNode.removeEventListener(
661
+ domType,
662
+ listenerInfo.realListener,
663
+ true
664
+ );
665
+ }
666
+ }
667
+ this.listeners.delete(node);
668
+ }
669
+ };
670
+ var EventManager = class {
671
+ constructor(window2, runEffect) {
672
+ __publicField(this, "containerDelegate");
673
+ __publicField(this, "elementDelegate");
674
+ this.containerDelegate = new ContainerEventManagerDelegate(
675
+ window2,
676
+ runEffect
677
+ );
678
+ this.elementDelegate = new ElementEventManagerDelegate(window2, runEffect);
679
+ }
680
+ setContainerListeners(node) {
681
+ this.containerDelegate.setContainerListeners(node);
682
+ }
683
+ setElementListeners(node) {
684
+ this.elementDelegate.setElementListeners(node);
685
+ }
686
+ };
687
+
688
+ // src/internal/taskreg.ts
689
+ var TaskRegistry = class {
690
+ constructor() {
691
+ __publicField(this, "tasks", {});
692
+ }
693
+ set(taskId2, cleanup) {
694
+ this.tasks[taskId2] = cleanup;
695
+ }
696
+ cancel(taskId2) {
697
+ const cleanup = this.tasks[taskId2];
698
+ if (cleanup) cleanup();
699
+ delete this.tasks[taskId2];
700
+ }
701
+ remove(taskId2) {
702
+ delete this.tasks[taskId2];
703
+ }
704
+ cancelAll() {
705
+ for (const cleanup of Object.values(this.tasks)) {
706
+ cleanup();
707
+ }
708
+ this.tasks = {};
709
+ }
710
+ };
711
+
712
+ // src/internal/vdom.ts
713
+ function rootNode(realNode) {
714
+ return {
715
+ _type: "RootNode",
716
+ realNode,
717
+ prepend(node) {
718
+ addNewChild(this, node, "left");
719
+ },
720
+ append(node) {
721
+ addNewChild(this, node, "right");
722
+ }
723
+ };
724
+ }
725
+ function componentNode(component, nodeId2) {
726
+ return {
727
+ _type: "ComponentNode",
728
+ component,
729
+ nodeId: nodeId2,
730
+ state: { _type: "Uninitialized" },
731
+ containerListeners: [],
732
+ componentEventListeners: [],
733
+ before(node) {
734
+ moveNodeNextTo(node, this, "left");
735
+ },
736
+ after(node) {
737
+ moveNodeNextTo(node, this, "right");
738
+ },
739
+ prepend(node) {
740
+ addNewChild(this, node, "left");
741
+ },
742
+ append(node) {
743
+ addNewChild(this, node, "right");
744
+ }
745
+ };
746
+ }
747
+ function viewNode(view) {
748
+ return {
749
+ _type: "ViewNode",
750
+ nodeId: view.nodeId,
751
+ params: view.params,
752
+ containerListeners: [],
753
+ after(nodeToMove) {
754
+ moveNodeNextTo(nodeToMove, this, "right");
755
+ },
756
+ before(nodeToMove) {
757
+ moveNodeNextTo(nodeToMove, this, "left");
758
+ },
759
+ prepend(node) {
760
+ addNewChild(this, node, "left");
761
+ },
762
+ append(node) {
763
+ addNewChild(this, node, "right");
764
+ }
765
+ };
766
+ }
767
+ function internalTextNode(document, text) {
768
+ return {
769
+ _type: "InternalTextNode",
770
+ text,
771
+ realNode: document.createTextNode(text),
772
+ after(nodeToMove) {
773
+ moveNodeNextTo(nodeToMove, this, "right");
774
+ },
775
+ before(nodeToMove) {
776
+ moveNodeNextTo(nodeToMove, this, "left");
777
+ }
778
+ };
779
+ }
780
+ function internalElementNode(document, tag, nodeId2, attrs, eventListeners, props = []) {
781
+ const realNode = document.createElement(tag);
782
+ attrs.forEach((a) => {
783
+ realNode.setAttribute(a.name, a.value ? a.value : "");
784
+ });
785
+ props.forEach((p) => {
786
+ ;
787
+ realNode[p.name] = p.value;
788
+ });
789
+ return {
790
+ _type: "InternalElementNode",
791
+ tag,
792
+ nodeId: nodeId2,
793
+ attrs,
794
+ props,
795
+ eventListeners,
796
+ realNode,
797
+ after(nodeToMove) {
798
+ moveNodeNextTo(nodeToMove, this, "right");
799
+ },
800
+ before(nodeToMove) {
801
+ moveNodeNextTo(nodeToMove, this, "left");
802
+ },
803
+ prepend(node) {
804
+ addNewChild(this, node, "left");
805
+ },
806
+ append(node) {
807
+ addNewChild(this, node, "right");
808
+ }
809
+ };
810
+ }
811
+ function detach(node, leaveRealNodeAttached = false) {
812
+ const origPrevSibling = node.prevSibling;
813
+ const origNextSibling = node.nextSibling;
814
+ if (!origPrevSibling) {
815
+ if (node.parent) {
816
+ node.parent.firstChild = origNextSibling;
817
+ }
818
+ }
819
+ if (origPrevSibling) {
820
+ origPrevSibling.nextSibling = origNextSibling;
821
+ }
822
+ if (origNextSibling) {
823
+ origNextSibling.prevSibling = origPrevSibling;
824
+ }
825
+ if (!leaveRealNodeAttached) {
826
+ switch (node._type) {
827
+ case "InternalElementNode":
828
+ case "InternalTextNode": {
829
+ node.realNode.remove();
830
+ break;
831
+ }
832
+ case "ComponentNode":
833
+ case "ViewNode":
834
+ break;
835
+ default:
836
+ exhaustivenessCheck(node);
837
+ }
838
+ }
839
+ node.prevSibling = void 0;
840
+ node.nextSibling = void 0;
841
+ node.parent = void 0;
842
+ }
843
+ function moveNodeNextTo(nodeToMove, anchorNode, direction) {
844
+ detach(nodeToMove, true);
845
+ switch (direction) {
846
+ case "right": {
847
+ nodeToMove.nextSibling = anchorNode.nextSibling;
848
+ anchorNode.nextSibling = nodeToMove;
849
+ nodeToMove.prevSibling = anchorNode;
850
+ if (nodeToMove.nextSibling) {
851
+ nodeToMove.nextSibling.prevSibling = nodeToMove;
852
+ }
853
+ break;
854
+ }
855
+ case "left": {
856
+ nodeToMove.prevSibling = anchorNode.prevSibling;
857
+ anchorNode.prevSibling = nodeToMove;
858
+ nodeToMove.nextSibling = anchorNode;
859
+ if (nodeToMove.prevSibling) {
860
+ nodeToMove.prevSibling.nextSibling = nodeToMove;
861
+ } else if (anchorNode.parent) {
862
+ anchorNode.parent.firstChild = nodeToMove;
863
+ }
864
+ break;
865
+ }
866
+ default: {
867
+ exhaustivenessCheck(direction);
868
+ }
869
+ }
870
+ nodeToMove.parent = anchorNode.parent;
871
+ const realNodesToMove = realNodesFor(nodeToMove);
872
+ let realAnchorNode = getRealAnchorNode(anchorNode, direction);
873
+ realNodesToMove.forEach((node) => {
874
+ switch (direction) {
875
+ case "right": {
876
+ if (!realAnchorNode) {
877
+ const realParent = realParentNode(anchorNode);
878
+ if (realParent) {
879
+ realParent.append(node);
880
+ }
881
+ } else {
882
+ realAnchorNode.after(node);
883
+ }
884
+ realAnchorNode = node;
885
+ break;
886
+ }
887
+ case "left": {
888
+ if (!realAnchorNode) {
889
+ const realParent = realParentNode(anchorNode);
890
+ if (realParent) {
891
+ realParent.prepend(node);
892
+ }
893
+ } else {
894
+ realAnchorNode.before(node);
895
+ }
896
+ realAnchorNode = node;
897
+ break;
898
+ }
899
+ default: {
900
+ exhaustivenessCheck(direction);
901
+ }
902
+ }
903
+ });
904
+ }
905
+ function addNewChild(parent, child, position) {
906
+ detach(child, true);
907
+ child.parent = parent;
908
+ if (position === "left") {
909
+ child.nextSibling = parent.firstChild;
910
+ parent.firstChild = child;
911
+ } else {
912
+ if (parent.firstChild) {
913
+ const curLastChild = lastNode(parent.firstChild);
914
+ curLastChild.nextSibling = child;
915
+ child.prevSibling = curLastChild;
916
+ } else {
917
+ parent.firstChild = child;
918
+ }
919
+ }
920
+ const realChildNodes = realNodesFor(child);
921
+ if (parent._type === "RootNode") {
922
+ if (position === "left") {
923
+ parent.realNode.prepend(...realChildNodes);
924
+ } else {
925
+ parent.realNode.append(...realChildNodes);
926
+ }
927
+ return;
928
+ }
929
+ if (parent._type === "InternalElementNode") {
930
+ if (position === "left") {
931
+ parent.realNode.prepend(...realChildNodes);
932
+ } else {
933
+ parent.realNode.append(...realChildNodes);
934
+ }
935
+ return;
936
+ }
937
+ const realSibling = getRealSibling(parent, position);
938
+ if (realSibling) {
939
+ if (position === "left") {
940
+ realSibling.realNode.before(...realChildNodes);
941
+ } else {
942
+ realSibling.realNode.after(...realChildNodes);
943
+ }
944
+ } else {
945
+ const realParent = realParentNode(parent);
946
+ if (realParent) {
947
+ if (position === "left") {
948
+ realParent.prepend(...realChildNodes);
949
+ } else {
950
+ realParent.append(...realChildNodes);
951
+ }
952
+ }
953
+ }
954
+ }
955
+ function realNodesFor(node) {
956
+ switch (node._type) {
957
+ case "InternalElementNode":
958
+ case "InternalTextNode": {
959
+ return [node.realNode];
960
+ }
961
+ case "ComponentNode":
962
+ case "ViewNode": {
963
+ const realNodes = [];
964
+ let next = node.firstChild;
965
+ while (next) {
966
+ realNodes.push(...realNodesFor(next));
967
+ next = next.nextSibling;
968
+ }
969
+ return realNodes;
970
+ }
971
+ default: {
972
+ exhaustivenessCheck(node);
973
+ }
974
+ }
975
+ }
976
+ function getRealAnchorNode(anchorNode, insertionDirection) {
977
+ let curInitialNodeForLevel = anchorNode;
978
+ while (curInitialNodeForLevel) {
979
+ let curNode = curInitialNodeForLevel;
980
+ while (curNode) {
981
+ if (curNode._type === "InternalTextNode" || curNode._type === "InternalElementNode") {
982
+ return curNode.realNode;
983
+ }
984
+ let curChild = insertionDirection === "right" ? curNode.firstChild ? lastNode(curNode.firstChild) : null : curNode.firstChild;
985
+ while (curChild) {
986
+ if (curChild._type === "InternalElementNode" || curChild._type === "InternalTextNode") {
987
+ return curChild.realNode;
988
+ }
989
+ curChild = insertionDirection === "right" ? curChild.prevSibling : curChild.nextSibling;
990
+ }
991
+ curNode = insertionDirection === "right" ? curNode.prevSibling : curNode.nextSibling;
992
+ }
993
+ switch (curInitialNodeForLevel.parent?._type) {
994
+ case "InternalElementNode":
995
+ case "RootNode":
996
+ return void 0;
997
+ case "ComponentNode":
998
+ case "ViewNode": {
999
+ curInitialNodeForLevel = curInitialNodeForLevel.parent;
1000
+ }
1001
+ }
1002
+ }
1003
+ return void 0;
1004
+ }
1005
+ function lastNode(node) {
1006
+ let result = node;
1007
+ while (result.nextSibling) {
1008
+ result = result.nextSibling;
1009
+ }
1010
+ return result;
1011
+ }
1012
+ function realParentNode(node) {
1013
+ return realClosestAncestor(node)?.realNode;
1014
+ }
1015
+ function realClosestAncestor(node) {
1016
+ const parent = node.parent;
1017
+ if (!parent) {
1018
+ return void 0;
1019
+ }
1020
+ switch (parent._type) {
1021
+ case "InternalElementNode":
1022
+ return parent;
1023
+ case "ComponentNode":
1024
+ return realClosestAncestor(parent);
1025
+ case "RootNode":
1026
+ return parent;
1027
+ case "ViewNode":
1028
+ return realClosestAncestor(parent);
1029
+ }
1030
+ }
1031
+ function getRealSibling(node, position) {
1032
+ let startNodeForCurrentLevel = node;
1033
+ while (startNodeForCurrentLevel) {
1034
+ const sibling = position === "left" ? startNodeForCurrentLevel.prevSibling : startNodeForCurrentLevel.nextSibling;
1035
+ if (sibling) {
1036
+ const realSibling = getRealSiblingLookingLaterally(sibling, position);
1037
+ if (realSibling) {
1038
+ return realSibling;
1039
+ }
1040
+ }
1041
+ const parent = startNodeForCurrentLevel.parent;
1042
+ if (!parent) {
1043
+ return void 0;
1044
+ }
1045
+ switch (parent._type) {
1046
+ case "InternalElementNode":
1047
+ case "RootNode": {
1048
+ return void 0;
1049
+ }
1050
+ case "ComponentNode":
1051
+ case "ViewNode": {
1052
+ startNodeForCurrentLevel = parent;
1053
+ continue;
1054
+ }
1055
+ default:
1056
+ exhaustivenessCheck(parent);
1057
+ }
1058
+ }
1059
+ return void 0;
1060
+ }
1061
+ function getRealSiblingLookingLaterally(nextNode, position) {
1062
+ while (nextNode) {
1063
+ switch (nextNode._type) {
1064
+ case "InternalElementNode":
1065
+ case "InternalTextNode":
1066
+ return nextNode;
1067
+ case "ComponentNode":
1068
+ case "ViewNode":
1069
+ if (nextNode.firstChild) {
1070
+ const child = position === "left" ? lastNode(nextNode.firstChild) : nextNode.firstChild;
1071
+ const rslt = getRealSiblingLookingLaterally(child, position);
1072
+ if (rslt) {
1073
+ return rslt;
1074
+ }
1075
+ }
1076
+ nextNode = position === "left" ? nextNode.prevSibling : nextNode.nextSibling;
1077
+ continue;
1078
+ }
1079
+ }
1080
+ return void 0;
1081
+ }
1082
+ function updateText(node, newText) {
1083
+ if (node.text === newText) {
1084
+ return;
1085
+ }
1086
+ node.text = newText;
1087
+ node.realNode.nodeValue = newText;
1088
+ }
1089
+ function updateAttrs(node, newAttrs) {
1090
+ const newAttrsMap = new Map(newAttrs.map((a) => [a.name, a]));
1091
+ node.attrs.forEach((oldAttr) => {
1092
+ const newAttr = newAttrsMap.get(oldAttr.name);
1093
+ if (newAttr) {
1094
+ if (newAttr.value || oldAttr.value) {
1095
+ if (newAttr.value !== oldAttr.value) {
1096
+ node.realNode.setAttribute(
1097
+ newAttr.name,
1098
+ newAttr.value ? newAttr.value : ""
1099
+ );
1100
+ }
1101
+ }
1102
+ } else {
1103
+ node.realNode.removeAttribute(oldAttr.name);
1104
+ }
1105
+ });
1106
+ node.attrs = newAttrs;
1107
+ }
1108
+ function updateProps(node, newProps) {
1109
+ const newPropsMap = new Map(newProps.map((p) => [p.name, p.value]));
1110
+ node.props.forEach((oldProp) => {
1111
+ if (!newPropsMap.has(oldProp.name)) {
1112
+ ;
1113
+ node.realNode[oldProp.name] = void 0;
1114
+ }
1115
+ });
1116
+ newProps.forEach((p) => {
1117
+ ;
1118
+ node.realNode[p.name] = p.value;
1119
+ });
1120
+ node.props = newProps;
1121
+ }
1122
+ function closestComponentPath(node) {
1123
+ const closestForParent = node.parent && node.parent._type !== "RootNode" ? closestComponentPath(node.parent) : void 0;
1124
+ switch (node._type) {
1125
+ case "InternalElementNode":
1126
+ case "InternalTextNode":
1127
+ case "ViewNode":
1128
+ return closestForParent;
1129
+ case "ComponentNode":
1130
+ return componentPath(node.nodeId, closestForParent);
1131
+ }
1132
+ }
1133
+
1134
+ // src/internal/cmpmgr.ts
1135
+ function manageComponent(component, mountId, window2, interpreter, styleRegistry) {
1136
+ const mountNode = window2.document.getElementById(mountId);
1137
+ if (!mountNode) {
1138
+ throw new Error(
1139
+ `Cannot attach component tree. Root node with id ${mountId} not found`
1140
+ );
1141
+ }
1142
+ const mgr = new ComponentManager(
1143
+ component,
1144
+ mountNode,
1145
+ window2,
1146
+ interpreter,
1147
+ styleRegistry
1148
+ );
1149
+ return {
1150
+ dispatchEffect: (effect) => mgr.dispatchEffect(effect)
1151
+ };
1152
+ }
1153
+ var ComponentManager = class {
1154
+ constructor(component, mountNode, window2, interpreter, styleRegistry, onRootEvent, alreadyMountedPaths) {
1155
+ __publicField(this, "rootCompPath");
1156
+ __publicField(this, "components", {});
1157
+ __publicField(this, "removedComponents", {});
1158
+ // Mirrors `components` but for view()/fixedView() subtrees, which have no
1159
+ // state of their own but still need stable identity so their onRender
1160
+ // effects don't refire whenever an ancestor element is rebuilt (or, via
1161
+ // alreadyMountedPaths, whenever a fresh ComponentManager remounts them).
1162
+ // Keyed the same way as components: nodeId + enclosing *component* path
1163
+ // (nesting through another view doesn't add a path segment).
1164
+ __publicField(this, "views", {});
1165
+ __publicField(this, "addedComponents", []);
1166
+ __publicField(this, "pendingRenderEffects", []);
1167
+ __publicField(this, "interpreter");
1168
+ __publicField(this, "window");
1169
+ __publicField(this, "document");
1170
+ __publicField(this, "eventManager");
1171
+ __publicField(this, "taskRegistry", new TaskRegistry());
1172
+ __publicField(this, "styleRegistry");
1173
+ __publicField(this, "onRootEvent");
1174
+ // Paths that were already mounted (and had their onRender effects fired)
1175
+ // by some earlier, unrelated ComponentManager instance -- e.g. a prior
1176
+ // `.run()` call in ctrl-fx/testing, which mounts a fresh ComponentManager
1177
+ // each time but wants onRender to still behave as if the component tree
1178
+ // had persisted. Suppresses re-queueing onRender effects for those paths
1179
+ // without affecting real first-time mounts.
1180
+ __publicField(this, "alreadyMountedPaths");
1181
+ this.window = window2;
1182
+ this.document = this.window.document;
1183
+ this.interpreter = interpreter;
1184
+ this.styleRegistry = styleRegistry;
1185
+ this.onRootEvent = onRootEvent;
1186
+ this.alreadyMountedPaths = alreadyMountedPaths ?? /* @__PURE__ */ new Set();
1187
+ this.eventManager = new EventManager(
1188
+ this.window,
1189
+ (effect, node, onResult) => {
1190
+ const componentPath2 = closestComponentPath(node);
1191
+ if (componentPath2) {
1192
+ this.runEffect(effect, componentPath2, onResult);
1193
+ }
1194
+ }
1195
+ );
1196
+ const rootCompPath = componentPath(nodeId("__root_component"));
1197
+ this.rootCompPath = rootCompPath;
1198
+ const compNode = componentNode(component, rootCompPath.nodeId);
1199
+ const root = rootNode(mountNode);
1200
+ root.append(compNode);
1201
+ this.components[componentPath(compNode.nodeId, void 0).format()] = compNode;
1202
+ this.injectComponentCss(component);
1203
+ const initStateEffect = component.initialState(component.params);
1204
+ this.runEffect(initStateEffect, rootCompPath, (state) => {
1205
+ this.renderComponent(rootCompPath, state);
1206
+ });
1207
+ }
1208
+ injectComponentCss(component) {
1209
+ if (!component.css) return;
1210
+ const css = typeof component.css === "function" ? component.css(component.params) : component.css;
1211
+ this.styleRegistry.injectStyle(css);
1212
+ }
1213
+ getRootState() {
1214
+ return this.components[this.rootCompPath.format()]?.state ?? {
1215
+ _type: "Uninitialized"
1216
+ };
1217
+ }
1218
+ /** Paths of components (past their initial render) and views currently mounted. */
1219
+ getMountedPaths() {
1220
+ const componentPaths = Object.entries(this.components).filter(([, node]) => node.state._type === "Ready").map(([path]) => path);
1221
+ return /* @__PURE__ */ new Set([...componentPaths, ...Object.keys(this.views)]);
1222
+ }
1223
+ dispatchEffect(effect) {
1224
+ this.runEffect(effect, this.rootCompPath, () => {
1225
+ });
1226
+ }
1227
+ destroy() {
1228
+ this.taskRegistry.cancelAll();
1229
+ }
1230
+ runEffect(effect, componentPath2, f) {
1231
+ const that = this;
1232
+ this.interpreter(
1233
+ effect,
1234
+ {
1235
+ onComplete: f,
1236
+ onFireEvent: (event) => {
1237
+ const component = this.components[componentPath2.format()];
1238
+ if (component) {
1239
+ const parentCompPath = componentPath2.parentComponentPath;
1240
+ if (parentCompPath) {
1241
+ component.componentEventListeners.forEach((listener) => {
1242
+ const effect2 = listener(event);
1243
+ this.runEffect(effect2, parentCompPath, () => {
1244
+ });
1245
+ });
1246
+ } else {
1247
+ this.onRootEvent?.(event);
1248
+ }
1249
+ }
1250
+ },
1251
+ getState: () => {
1252
+ return that.components[componentPath2.format()].state.value;
1253
+ },
1254
+ setState: (state) => {
1255
+ that.renderComponent(componentPath2, state);
1256
+ }
1257
+ },
1258
+ this.taskRegistry
1259
+ );
1260
+ }
1261
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1262
+ renderComponent(componentPath2, state) {
1263
+ const component = this.components[componentPath2.format()];
1264
+ if (!component) {
1265
+ return;
1266
+ }
1267
+ if (component.state._type === "Ready" && eq(component.state.value, state)) {
1268
+ return;
1269
+ }
1270
+ const isInitialMount = component.state._type === "Uninitialized";
1271
+ const wasAlreadyMounted = this.alreadyMountedPaths.has(componentPath2.format());
1272
+ component.state = { _type: "Ready", value: state };
1273
+ const newNodeGroup = component.component.view(
1274
+ state,
1275
+ component.component.params
1276
+ );
1277
+ if (isInitialMount && !wasAlreadyMounted && newNodeGroup.renderEffects.length > 0) {
1278
+ this.pendingRenderEffects.push({
1279
+ path: componentPath2,
1280
+ effects: newNodeGroup.renderEffects
1281
+ });
1282
+ }
1283
+ component.containerListeners = newNodeGroup.containerListeners;
1284
+ this.eventManager.setContainerListeners(component);
1285
+ const oldNodes = component.firstChild;
1286
+ this.compareNodeSequences(
1287
+ oldNodes,
1288
+ newNodeGroup.nodes,
1289
+ component,
1290
+ componentPath2
1291
+ );
1292
+ const added = this.addedComponents.splice(0);
1293
+ added.forEach((childPath) => {
1294
+ const cNode = this.components[childPath.format()];
1295
+ if (cNode) {
1296
+ const initEffect = cNode.component.initialState(cNode.component.params);
1297
+ this.runEffect(initEffect, childPath, (state2) => {
1298
+ this.renderComponent(childPath, state2);
1299
+ });
1300
+ }
1301
+ });
1302
+ const pending = this.pendingRenderEffects.splice(0);
1303
+ pending.forEach(({ path, effects }) => {
1304
+ effects.forEach((effect) => this.runEffect(effect, path, () => {
1305
+ }));
1306
+ });
1307
+ }
1308
+ compareNodeSequences(oldNodes, newNodes, parent, ancestorComponentPath) {
1309
+ const mutNewNodes = [...newNodes];
1310
+ while (mutNewNodes.length > 0) {
1311
+ const newNode = mutNewNodes.shift();
1312
+ const correspondingOldNode = this.findCorrespondingOldNode(
1313
+ newNode,
1314
+ oldNodes,
1315
+ ancestorComponentPath
1316
+ );
1317
+ if (correspondingOldNode) {
1318
+ if (correspondingOldNode !== oldNodes) {
1319
+ oldNodes?.before(correspondingOldNode);
1320
+ }
1321
+ this.compareNodes(correspondingOldNode, newNode, ancestorComponentPath);
1322
+ oldNodes = correspondingOldNode.nextSibling;
1323
+ } else {
1324
+ const newInternalNode = this.convertNodeToInternalNode(
1325
+ newNode,
1326
+ ancestorComponentPath
1327
+ );
1328
+ if (oldNodes) {
1329
+ oldNodes.before(newInternalNode);
1330
+ } else {
1331
+ parent.append(newInternalNode);
1332
+ }
1333
+ }
1334
+ }
1335
+ let oldNode = oldNodes;
1336
+ while (oldNode) {
1337
+ const next = oldNode.nextSibling;
1338
+ this.removeNode(oldNode, ancestorComponentPath);
1339
+ oldNode = next;
1340
+ }
1341
+ }
1342
+ removeNode(oldNode, ancestorComponentPath) {
1343
+ detach(oldNode);
1344
+ const that = this;
1345
+ function removeNodeRecords(node, ancestorComponentPath2) {
1346
+ switch (node._type) {
1347
+ case "InternalTextNode":
1348
+ return;
1349
+ case "InternalElementNode": {
1350
+ that.eventManager.setElementListeners(node);
1351
+ return;
1352
+ }
1353
+ case "ViewNode": {
1354
+ that.eventManager.setContainerListeners(node);
1355
+ const viewPath = componentPath(
1356
+ node.nodeId,
1357
+ ancestorComponentPath2
1358
+ ).format();
1359
+ delete that.views[viewPath];
1360
+ return;
1361
+ }
1362
+ case "ComponentNode": {
1363
+ that.eventManager.setContainerListeners(node);
1364
+ const compPath = componentPath(
1365
+ node.nodeId,
1366
+ ancestorComponentPath2
1367
+ ).format();
1368
+ delete that.components[compPath];
1369
+ that.removedComponents[compPath] = node;
1370
+ return;
1371
+ }
1372
+ }
1373
+ }
1374
+ foldTree(
1375
+ oldNode,
1376
+ ancestorComponentPath,
1377
+ void 0,
1378
+ (node, ancestorComponentId) => {
1379
+ removeNodeRecords(node, ancestorComponentId);
1380
+ }
1381
+ );
1382
+ }
1383
+ compareNodes(oldNode, newNode, ancestorComponentPath) {
1384
+ const parent = oldNode.parent;
1385
+ const removeOldAndAddNew = () => {
1386
+ const nextSibling = oldNode.nextSibling;
1387
+ this.removeNode(oldNode, ancestorComponentPath);
1388
+ const newInternalNode = this.convertNodeToInternalNode(
1389
+ newNode,
1390
+ ancestorComponentPath
1391
+ );
1392
+ if (nextSibling) {
1393
+ nextSibling.after(newInternalNode);
1394
+ } else if (parent) {
1395
+ parent.append(newInternalNode);
1396
+ }
1397
+ };
1398
+ const that = this;
1399
+ foldNode(newNode, {
1400
+ onText(text) {
1401
+ if (oldNode._type !== "InternalTextNode") {
1402
+ removeOldAndAddNew();
1403
+ return;
1404
+ }
1405
+ updateText(oldNode, text);
1406
+ },
1407
+ onVoidElement(elem) {
1408
+ if (oldNode._type !== "InternalElementNode" || oldNode.tag !== elem.tag) {
1409
+ removeOldAndAddNew();
1410
+ return;
1411
+ }
1412
+ updateAttrs(oldNode, elem.attrs);
1413
+ updateProps(oldNode, elem.props);
1414
+ if (oldNode.eventListeners.length > 0 || elem.eventListeners.length > 0) {
1415
+ oldNode.eventListeners = elem.eventListeners;
1416
+ that.eventManager.setElementListeners(oldNode);
1417
+ }
1418
+ },
1419
+ onNonVoidElement(elem) {
1420
+ if (oldNode._type !== "InternalElementNode" || oldNode.tag !== elem.tag) {
1421
+ removeOldAndAddNew();
1422
+ return;
1423
+ }
1424
+ updateAttrs(oldNode, elem.attrs);
1425
+ updateProps(oldNode, elem.props);
1426
+ if (oldNode.eventListeners.length > 0) {
1427
+ oldNode.eventListeners = elem.eventListeners;
1428
+ that.eventManager.setElementListeners(oldNode);
1429
+ }
1430
+ that.compareNodeSequences(
1431
+ oldNode.firstChild,
1432
+ elem.children,
1433
+ oldNode,
1434
+ ancestorComponentPath
1435
+ );
1436
+ },
1437
+ onView(view) {
1438
+ if (oldNode._type !== "ViewNode" || !oldNode.nodeId.eq(view.nodeId)) {
1439
+ removeOldAndAddNew();
1440
+ return;
1441
+ }
1442
+ if (eq(oldNode.params, view.params)) {
1443
+ return;
1444
+ }
1445
+ const newNodeGroup = view.nodes(view.params);
1446
+ oldNode.params = view.params;
1447
+ oldNode.containerListeners = newNodeGroup.containerListeners;
1448
+ that.eventManager.setContainerListeners(oldNode);
1449
+ that.compareNodeSequences(
1450
+ oldNode.firstChild,
1451
+ newNodeGroup.nodes,
1452
+ oldNode,
1453
+ ancestorComponentPath
1454
+ );
1455
+ },
1456
+ onComponent(comp) {
1457
+ if (oldNode._type !== "ComponentNode" || !oldNode.nodeId.eq(comp.nodeId)) {
1458
+ removeOldAndAddNew();
1459
+ return;
1460
+ }
1461
+ if (oldNode.state._type === "Ready") {
1462
+ if (oldNode.componentEventListeners.length > 0 || comp.componentEventListeners.length > 0) {
1463
+ oldNode.componentEventListeners = comp.componentEventListeners;
1464
+ that.eventManager.setContainerListeners(oldNode);
1465
+ }
1466
+ if (eq(oldNode.component.params, comp.component.params)) {
1467
+ oldNode.component = comp.component;
1468
+ } else {
1469
+ that.injectComponentCss(comp.component);
1470
+ const newNodeGroup = comp.component.view(
1471
+ oldNode.state.value,
1472
+ comp.component.params
1473
+ );
1474
+ oldNode.containerListeners = newNodeGroup.containerListeners;
1475
+ that.eventManager.setContainerListeners(oldNode);
1476
+ oldNode.component = comp.component;
1477
+ that.compareNodeSequences(
1478
+ oldNode.firstChild,
1479
+ newNodeGroup.nodes,
1480
+ oldNode,
1481
+ componentPath(comp.nodeId, ancestorComponentPath)
1482
+ );
1483
+ }
1484
+ } else {
1485
+ oldNode.component = comp.component;
1486
+ }
1487
+ }
1488
+ });
1489
+ }
1490
+ convertNodeToInternalNode(node, ancestorComponentId) {
1491
+ const that = this;
1492
+ return foldNode(node, {
1493
+ onText(text) {
1494
+ return internalTextNode(that.document, text);
1495
+ },
1496
+ onVoidElement(elem) {
1497
+ const internalElem = internalElementNode(
1498
+ that.document,
1499
+ elem.tag,
1500
+ elem.nodeId,
1501
+ elem.attrs,
1502
+ elem.eventListeners,
1503
+ elem.props
1504
+ );
1505
+ if (elem.eventListeners.length > 0) {
1506
+ that.eventManager.setElementListeners(internalElem);
1507
+ }
1508
+ return internalElem;
1509
+ },
1510
+ onNonVoidElement(elem) {
1511
+ const internalElement = internalElementNode(
1512
+ that.document,
1513
+ elem.tag,
1514
+ elem.nodeId,
1515
+ elem.attrs,
1516
+ elem.eventListeners,
1517
+ elem.props
1518
+ );
1519
+ if (elem.eventListeners.length > 0) {
1520
+ that.eventManager.setElementListeners(internalElement);
1521
+ }
1522
+ elem.children.forEach((childNode) => {
1523
+ internalElement.append(
1524
+ that.convertNodeToInternalNode(childNode, ancestorComponentId)
1525
+ );
1526
+ });
1527
+ return internalElement;
1528
+ },
1529
+ onView(view) {
1530
+ const viewId = componentPath(view.nodeId, ancestorComponentId).format();
1531
+ const existingView = that.views[viewId];
1532
+ if (existingView) {
1533
+ detach(existingView);
1534
+ that.compareNodes(existingView, view, ancestorComponentId);
1535
+ return existingView;
1536
+ }
1537
+ const vNode = viewNode(view);
1538
+ const nodeGroup2 = view.nodes(view.params);
1539
+ vNode.params = view.params;
1540
+ vNode.containerListeners = nodeGroup2.containerListeners;
1541
+ nodeGroup2.nodes.forEach((childNode) => {
1542
+ vNode.append(
1543
+ that.convertNodeToInternalNode(childNode, ancestorComponentId)
1544
+ );
1545
+ });
1546
+ that.eventManager.setContainerListeners(vNode);
1547
+ that.views[viewId] = vNode;
1548
+ if (!that.alreadyMountedPaths.has(viewId) && nodeGroup2.renderEffects.length > 0) {
1549
+ that.pendingRenderEffects.push({
1550
+ path: ancestorComponentId,
1551
+ effects: nodeGroup2.renderEffects
1552
+ });
1553
+ }
1554
+ return vNode;
1555
+ },
1556
+ onComponent(comp) {
1557
+ const compId = componentPath(comp.nodeId, ancestorComponentId).format();
1558
+ const existingComp = that.components[compId];
1559
+ if (existingComp) {
1560
+ detach(existingComp);
1561
+ that.compareNodes(existingComp, comp, ancestorComponentId);
1562
+ return existingComp;
1563
+ }
1564
+ const cNode = componentNode(comp.component, comp.nodeId);
1565
+ if (comp.componentEventListeners.length > 0) {
1566
+ cNode.componentEventListeners = comp.componentEventListeners;
1567
+ that.eventManager.setContainerListeners(cNode);
1568
+ }
1569
+ that.injectComponentCss(comp.component);
1570
+ that.components[compId] = cNode;
1571
+ that.addedComponents.push(
1572
+ componentPath(comp.nodeId, ancestorComponentId)
1573
+ );
1574
+ return cNode;
1575
+ }
1576
+ });
1577
+ }
1578
+ // private compareNodes(
1579
+ // oldNode: InternalNode,
1580
+ // newNode: Node<unknown, unknown>,
1581
+ // parent: RealElement,
1582
+ // prevSibling: RealNode | null,
1583
+ // ancestorComponentId: ComponentId
1584
+ // ): RealNode | null {
1585
+ // if (typeof (newNode) === 'string') {
1586
+ // if (oldNode._type === 'InternalTextNode') {
1587
+ // if (oldNode.text !== newNode) {
1588
+ // oldNode.realNode.nodeValue = newNode
1589
+ // }
1590
+ // return oldNode.realNode
1591
+ // }
1592
+ // this.removeNode(oldNode)
1593
+ // return this.addNode1(newNode, parent, prevSibling, ancestorComponentId)
1594
+ // }
1595
+ // switch (newNode._type) {
1596
+ // case 'Element': {
1597
+ // if (
1598
+ // oldNode._type === 'InternalElementNode' &&
1599
+ // newNode.tag === oldNode.tag
1600
+ // ) {
1601
+ // this.compareElements(oldNode, newNode)
1602
+ // return oldNode.realNode
1603
+ // } else {
1604
+ // this.removeNode(oldNode)
1605
+ // return this.addNode1(newNode, parent, prevSibling, ancestorComponentId)
1606
+ // }
1607
+ // }
1608
+ // case 'Component': {
1609
+ // return todo()
1610
+ // }
1611
+ // case 'View': {
1612
+ // return todo()
1613
+ // }
1614
+ // }
1615
+ // }
1616
+ /**
1617
+ * Return an old node that can be compared to `newNode`.
1618
+ *
1619
+ * If the old node represent the same node as `newNode`, return it.
1620
+ *
1621
+ * If the same node isn't found, return the first node if it is `comparable`,
1622
+ * otherwise return undefined. (We don't move nodes if they are comparable but
1623
+ * not the same).
1624
+ */
1625
+ findCorrespondingOldNode(newNode, oldNodes, ancestorComponentPath) {
1626
+ if (typeof newNode !== "string" && newNode._type === "Component") {
1627
+ const registeredComponent = this.components[componentPath(newNode.nodeId, ancestorComponentPath).format()];
1628
+ if (registeredComponent) {
1629
+ return registeredComponent;
1630
+ }
1631
+ }
1632
+ if (typeof newNode !== "string" && newNode._type === "View") {
1633
+ const registeredView = this.views[componentPath(newNode.nodeId, ancestorComponentPath).format()];
1634
+ if (registeredView) {
1635
+ return registeredView;
1636
+ }
1637
+ }
1638
+ let next = oldNodes;
1639
+ while (next) {
1640
+ if (getNodesRelationship(next, newNode) === "same") {
1641
+ return next;
1642
+ }
1643
+ next = next.nextSibling;
1644
+ }
1645
+ if (oldNodes) {
1646
+ if (getNodesRelationship(oldNodes, newNode) === "comparable") {
1647
+ return oldNodes;
1648
+ }
1649
+ }
1650
+ return void 0;
1651
+ }
1652
+ };
1653
+ function getNodesRelationship(oldNode, newNode) {
1654
+ if (typeof newNode === "string") {
1655
+ return oldNode._type === "InternalTextNode" ? "comparable" : "unrelated";
1656
+ }
1657
+ if (oldNode._type === "InternalTextNode") {
1658
+ return "unrelated";
1659
+ }
1660
+ switch (newNode._type) {
1661
+ case "Element": {
1662
+ if (oldNode._type === "InternalElementNode") {
1663
+ if (newNode.nodeId) {
1664
+ if (oldNode.nodeId) {
1665
+ return newNode.nodeId.eq(oldNode.nodeId) ? "same" : "unrelated";
1666
+ }
1667
+ } else if (oldNode.nodeId) {
1668
+ return "unrelated";
1669
+ }
1670
+ const oldId = getId(oldNode.attrs);
1671
+ const newId = getId(newNode.attrs);
1672
+ if (newId) {
1673
+ if (oldId) {
1674
+ return oldId === newId ? "same" : "unrelated";
1675
+ } else {
1676
+ return "unrelated";
1677
+ }
1678
+ } else if (oldId) {
1679
+ return "unrelated";
1680
+ }
1681
+ return oldNode.tag === newNode.tag ? "comparable" : "unrelated";
1682
+ } else {
1683
+ return "unrelated";
1684
+ }
1685
+ }
1686
+ case "View":
1687
+ if (oldNode._type === "ViewNode") {
1688
+ return newNode.nodeId.eq(oldNode.nodeId) ? "same" : "unrelated";
1689
+ } else {
1690
+ return "unrelated";
1691
+ }
1692
+ case "Component":
1693
+ if (oldNode._type === "ComponentNode") {
1694
+ return newNode.nodeId.eq(oldNode.nodeId) ? "same" : "unrelated";
1695
+ } else {
1696
+ return "unrelated";
1697
+ }
1698
+ }
1699
+ }
1700
+ function foldTree(root, ancestorComponentPath, init, f) {
1701
+ let acc = init;
1702
+ function visit(n, ancestorComponentPath2, moveThroushSiblings) {
1703
+ acc = f(n, ancestorComponentPath2, acc);
1704
+ switch (n._type) {
1705
+ case "InternalTextNode": {
1706
+ break;
1707
+ }
1708
+ case "InternalElementNode":
1709
+ case "ViewNode": {
1710
+ if (n.firstChild) {
1711
+ visit(n.firstChild, ancestorComponentPath2, true);
1712
+ }
1713
+ break;
1714
+ }
1715
+ case "ComponentNode": {
1716
+ if (n.firstChild) {
1717
+ visit(
1718
+ n.firstChild,
1719
+ componentPath(n.nodeId, ancestorComponentPath2),
1720
+ true
1721
+ );
1722
+ }
1723
+ break;
1724
+ }
1725
+ default: {
1726
+ exhaustivenessCheck(n);
1727
+ }
1728
+ }
1729
+ if (moveThroushSiblings) {
1730
+ let next = n.nextSibling;
1731
+ while (next) {
1732
+ visit(next, ancestorComponentPath2, false);
1733
+ next = next.nextSibling;
1734
+ }
1735
+ }
1736
+ }
1737
+ visit(root, ancestorComponentPath, false);
1738
+ return acc;
1739
+ }
1740
+
1741
+ // src/internal/styleregistry.ts
1742
+ function realStyleRegistry() {
1743
+ const injected = /* @__PURE__ */ new Set();
1744
+ return {
1745
+ injectStyle(css) {
1746
+ if (injected.has(css)) return;
1747
+ injected.add(css);
1748
+ const el = globalThis.document.createElement("style");
1749
+ el.textContent = css;
1750
+ globalThis.document.head.appendChild(el);
1751
+ }
1752
+ };
1753
+ }
1754
+ function noopStyleRegistry() {
1755
+ return {
1756
+ injectStyle(_css) {
1757
+ }
1758
+ };
1759
+ }
1760
+
1761
+ // src/db/testdb.ts
1762
+ function serializeIdbKey(key) {
1763
+ if (key instanceof Date) return key.toISOString();
1764
+ return JSON.stringify(key);
1765
+ }
1766
+ var TestDatabaseData = class {
1767
+ constructor(databases) {
1768
+ __publicField(this, "_databases");
1769
+ this._databases = databases;
1770
+ }
1771
+ records(db, store) {
1772
+ return [...this._databases.get(db)?.get(store)?.records.values() ?? []];
1773
+ }
1774
+ getByKey(db, store, key) {
1775
+ return this._databases.get(db)?.get(store)?.records.get(serializeIdbKey(key));
1776
+ }
1777
+ size(db, store) {
1778
+ return this._databases.get(db)?.get(store)?.records.size ?? 0;
1779
+ }
1780
+ hasDatabase(db) {
1781
+ return this._databases.has(db);
1782
+ }
1783
+ hasStore(db, store) {
1784
+ return this._databases.get(db)?.has(store) ?? false;
1785
+ }
1786
+ };
1787
+
1788
+ // src/scroll.ts
1789
+ function scrollElementError(selector) {
1790
+ return { _type: "ScrollElementError", cause: "ElementNotFound", selector };
1791
+ }
1792
+
1793
+ // src/internal/testinginterpreter.ts
1794
+ function seededRandom(callIndex) {
1795
+ let h = callIndex + 1 | 0;
1796
+ h = Math.imul(h ^ h >>> 16, 73244475);
1797
+ h = Math.imul(h ^ h >>> 16, 73244475);
1798
+ h ^= h >>> 16;
1799
+ return (h >>> 0) / 4294967296;
1800
+ }
1801
+ function snapshotTestData(acc) {
1802
+ return {
1803
+ window: {
1804
+ alerts: [...acc.window.alerts],
1805
+ confirms: [...acc.window.confirms],
1806
+ confirmResponses: [...acc.window.confirmResponses],
1807
+ prompts: [...acc.window.prompts],
1808
+ promptResponses: [...acc.window.promptResponses]
1809
+ },
1810
+ navigator: { appBadgeOperations: [...acc.navigator.appBadgeOperations] },
1811
+ clipboard: {
1812
+ content: acc.clipboard.content,
1813
+ writes: [...acc.clipboard.writes]
1814
+ },
1815
+ document: { title: acc.document.title },
1816
+ http: { interactions: [...acc.http.interactions] },
1817
+ clock: { calls: [...acc.clock.calls] },
1818
+ random: { calls: [...acc.random.calls] },
1819
+ console: { logs: [...acc.console.logs] },
1820
+ db: new TestDatabaseData(
1821
+ new Map(
1822
+ [...acc.db.databases.entries()].map(([dbName, db]) => [
1823
+ dbName,
1824
+ new Map(
1825
+ [...db.stores.entries()].map(([storeName, store]) => [
1826
+ storeName,
1827
+ {
1828
+ keyPath: store.keyPath,
1829
+ records: new Map(store.records),
1830
+ autoIncrementCounter: store.autoIncrementCounter
1831
+ }
1832
+ ])
1833
+ )
1834
+ ])
1835
+ )
1836
+ ),
1837
+ storage: {
1838
+ local: new Map(acc.storage.local),
1839
+ session: new Map(acc.storage.session)
1840
+ },
1841
+ navigation: {
1842
+ locationHistory: [...acc.navigation.locationHistory]
1843
+ },
1844
+ scroll: {
1845
+ windowScrolls: [...acc.scroll.windowScrolls],
1846
+ elementScrolls: [...acc.scroll.elementScrolls]
1847
+ },
1848
+ downloads: [...acc.downloads],
1849
+ broadcasts: {
1850
+ sent: [...acc.broadcasts.sent]
1851
+ },
1852
+ custom: acc.custom
1853
+ };
1854
+ }
1855
+ var TestScheduler = class {
1856
+ constructor() {
1857
+ __publicField(this, "virtualNow", 0);
1858
+ __publicField(this, "pending", []);
1859
+ }
1860
+ schedule(task, taskCallbacks, tskId, initialDelayInMillis, repeatIntervalInMillis) {
1861
+ this.pending.push({
1862
+ task,
1863
+ taskCallbacks,
1864
+ taskId: tskId,
1865
+ nextFireAtMillis: this.virtualNow + initialDelayInMillis,
1866
+ repeatIntervalInMillis
1867
+ });
1868
+ }
1869
+ cancel(tskId) {
1870
+ this.pending = this.pending.filter((t) => t.taskId !== tskId);
1871
+ }
1872
+ advance(ms, acc, broadcastReg, window2, config) {
1873
+ const targetTime = this.virtualNow + ms;
1874
+ while (true) {
1875
+ const next = this.pending.filter((t) => t.nextFireAtMillis <= targetTime).sort((a, b) => a.nextFireAtMillis - b.nextFireAtMillis)[0];
1876
+ if (!next) break;
1877
+ this.virtualNow = next.nextFireAtMillis;
1878
+ if (next.repeatIntervalInMillis !== void 0) {
1879
+ next.nextFireAtMillis = this.virtualNow + next.repeatIntervalInMillis;
1880
+ } else {
1881
+ this.pending = this.pending.filter((t) => t !== next);
1882
+ }
1883
+ testingInterpreter(
1884
+ next.task,
1885
+ next.taskCallbacks,
1886
+ new TaskRegistry(),
1887
+ acc,
1888
+ broadcastReg,
1889
+ this,
1890
+ window2,
1891
+ config
1892
+ );
1893
+ }
1894
+ this.virtualNow = targetTime;
1895
+ }
1896
+ };
1897
+ var BroadcastChannelRegistry = class {
1898
+ constructor() {
1899
+ __publicField(this, "channels", /* @__PURE__ */ new Map());
1900
+ }
1901
+ subscribe(channel, handler, tskId, callbacks) {
1902
+ if (!this.channels.has(channel)) this.channels.set(channel, []);
1903
+ this.channels.get(channel).push({ handler, taskId: tskId, callbacks });
1904
+ }
1905
+ unsubscribe(tskId) {
1906
+ for (const [ch, subs] of this.channels) {
1907
+ this.channels.set(ch, subs.filter((s) => s.taskId !== tskId));
1908
+ }
1909
+ }
1910
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1911
+ deliver(channel, message, acc, broadcastReg, scheduler, window2, config) {
1912
+ const subs = this.channels.get(channel) ?? [];
1913
+ for (const sub of subs) {
1914
+ testingInterpreter(
1915
+ sub.handler(message),
1916
+ {
1917
+ ...sub.callbacks,
1918
+ onComplete() {
1919
+ }
1920
+ },
1921
+ new TaskRegistry(),
1922
+ acc,
1923
+ broadcastReg,
1924
+ scheduler,
1925
+ window2,
1926
+ config
1927
+ );
1928
+ }
1929
+ }
1930
+ };
1931
+ function makeTestingInterpreter(initialData, window2, config) {
1932
+ const acc = {
1933
+ window: {
1934
+ alerts: [...initialData.window.alerts],
1935
+ confirms: [...initialData.window.confirms],
1936
+ confirmResponses: [...initialData.window.confirmResponses],
1937
+ prompts: [...initialData.window.prompts],
1938
+ promptResponses: [...initialData.window.promptResponses]
1939
+ },
1940
+ navigator: {
1941
+ appBadgeOperations: [...initialData.navigator.appBadgeOperations]
1942
+ },
1943
+ clipboard: {
1944
+ content: initialData.clipboard.content,
1945
+ writes: [...initialData.clipboard.writes]
1946
+ },
1947
+ document: { title: initialData.document.title },
1948
+ http: { interactions: [...initialData.http.interactions] },
1949
+ clock: { calls: [...initialData.clock.calls] },
1950
+ random: { calls: [...initialData.random.calls] },
1951
+ console: { logs: [...initialData.console.logs] },
1952
+ storage: {
1953
+ local: new Map(initialData.storage.local),
1954
+ session: new Map(initialData.storage.session)
1955
+ },
1956
+ navigation: {
1957
+ locationHistory: [...initialData.navigation.locationHistory]
1958
+ },
1959
+ scroll: {
1960
+ windowScrolls: [...initialData.scroll.windowScrolls],
1961
+ elementScrolls: [...initialData.scroll.elementScrolls]
1962
+ },
1963
+ downloads: [...initialData.downloads],
1964
+ broadcasts: {
1965
+ sent: [...initialData.broadcasts.sent]
1966
+ },
1967
+ db: {
1968
+ databases: new Map(
1969
+ [...initialData.db._databases.entries()].map(([dbName, dbSnap]) => [
1970
+ dbName,
1971
+ {
1972
+ stores: new Map(
1973
+ [...dbSnap.entries()].map(([storeName, storeSnap]) => [
1974
+ storeName,
1975
+ {
1976
+ keyPath: storeSnap.keyPath,
1977
+ records: new Map(storeSnap.records),
1978
+ autoIncrementCounter: storeSnap.autoIncrementCounter
1979
+ }
1980
+ ])
1981
+ )
1982
+ }
1983
+ ])
1984
+ )
1985
+ },
1986
+ uuidSequence: [],
1987
+ custom: initialData.custom
1988
+ };
1989
+ const scheduler = new TestScheduler();
1990
+ const broadcastReg = new BroadcastChannelRegistry();
1991
+ const interpreter = (effect, callbacks, taskRegistry) => {
1992
+ testingInterpreter(effect, callbacks, taskRegistry, acc, broadcastReg, scheduler, window2, config);
1993
+ };
1994
+ return {
1995
+ interpreter,
1996
+ getData: () => snapshotTestData(acc),
1997
+ advanceTime: (ms) => scheduler.advance(ms, acc, broadcastReg, window2, config),
1998
+ fireBroadcast: (channel, message) => broadcastReg.deliver(channel, message, acc, broadcastReg, scheduler, window2, config)
1999
+ };
2000
+ }
2001
+ function testingInterpreter(effect, callbacks, taskRegistry, acc, broadcastReg, scheduler, window2, config) {
2002
+ switch (effect._type) {
2003
+ case "Return": {
2004
+ callbacks.onComplete(effect.value);
2005
+ return;
2006
+ }
2007
+ case "FlatMap": {
2008
+ testingInterpreter(
2009
+ effect.effect,
2010
+ {
2011
+ ...callbacks,
2012
+ onComplete(result) {
2013
+ testingInterpreter(
2014
+ effect.next(result),
2015
+ callbacks,
2016
+ taskRegistry,
2017
+ acc,
2018
+ broadcastReg,
2019
+ scheduler,
2020
+ window2,
2021
+ config
2022
+ );
2023
+ }
2024
+ },
2025
+ taskRegistry,
2026
+ acc,
2027
+ broadcastReg,
2028
+ scheduler,
2029
+ window2,
2030
+ config
2031
+ );
2032
+ return;
2033
+ }
2034
+ case "Suspend": {
2035
+ switch (effect.operation._type) {
2036
+ case "Alert": {
2037
+ acc.window.alerts.push(effect.operation.input);
2038
+ callbacks.onComplete(void 0);
2039
+ return;
2040
+ }
2041
+ case "Confirm": {
2042
+ const response = acc.window.confirmResponses.shift() ?? false;
2043
+ acc.window.confirms.push({
2044
+ message: effect.operation.input,
2045
+ response
2046
+ });
2047
+ callbacks.onComplete(response);
2048
+ return;
2049
+ }
2050
+ case "Prompt": {
2051
+ const response = acc.window.promptResponses.shift() ?? null;
2052
+ acc.window.prompts.push({
2053
+ message: effect.operation.input.message,
2054
+ default: effect.operation.input.default,
2055
+ response
2056
+ });
2057
+ callbacks.onComplete(response);
2058
+ return;
2059
+ }
2060
+ case "GetNotificationPermission": {
2061
+ callbacks.onComplete(permission("default"));
2062
+ return;
2063
+ }
2064
+ case "RequestNotificationPermission": {
2065
+ callbacks.onComplete(permission("granted"));
2066
+ return;
2067
+ }
2068
+ case "GetLocation": {
2069
+ const history2 = acc.navigation.locationHistory;
2070
+ callbacks.onComplete(
2071
+ history2.length > 0 ? history2[history2.length - 1] : emptyLocation
2072
+ );
2073
+ return;
2074
+ }
2075
+ case "Async": {
2076
+ testingInterpreter(
2077
+ effect.operation.input.effect,
2078
+ {
2079
+ ...callbacks,
2080
+ onComplete() {
2081
+ callbacks.onComplete(void 0);
2082
+ }
2083
+ },
2084
+ taskRegistry,
2085
+ acc,
2086
+ broadcastReg,
2087
+ scheduler,
2088
+ window2,
2089
+ config
2090
+ );
2091
+ return;
2092
+ }
2093
+ case "CancelTask": {
2094
+ scheduler.cancel(effect.operation.input);
2095
+ broadcastReg.unsubscribe(effect.operation.input);
2096
+ taskRegistry.remove(effect.operation.input);
2097
+ callbacks.onComplete(void 0);
2098
+ return;
2099
+ }
2100
+ case "ClearAppBadge": {
2101
+ acc.navigator.appBadgeOperations.push({ _type: "ClearAppBadge" });
2102
+ callbacks.onComplete(void 0);
2103
+ return;
2104
+ }
2105
+ case "SetAppBadge": {
2106
+ acc.navigator.appBadgeOperations.push({
2107
+ _type: "SetAppBadge",
2108
+ count: effect.operation.input
2109
+ });
2110
+ callbacks.onComplete(void 0);
2111
+ return;
2112
+ }
2113
+ case "SetDocumentTitle": {
2114
+ acc.document.title = effect.operation.input;
2115
+ callbacks.onComplete(void 0);
2116
+ return;
2117
+ }
2118
+ case "ReadClipboard": {
2119
+ callbacks.onComplete(success(acc.clipboard.content));
2120
+ return;
2121
+ }
2122
+ case "WriteClipboard": {
2123
+ acc.clipboard.content = effect.operation.input;
2124
+ acc.clipboard.writes.push(effect.operation.input);
2125
+ callbacks.onComplete(success(void 0));
2126
+ return;
2127
+ }
2128
+ case "Download": {
2129
+ acc.downloads.push(effect.operation.input);
2130
+ callbacks.onComplete(void 0);
2131
+ return;
2132
+ }
2133
+ case "PostBroadcastMessage": {
2134
+ acc.broadcasts.sent.push(effect.operation.input);
2135
+ callbacks.onComplete(void 0);
2136
+ return;
2137
+ }
2138
+ case "SubscribeToBroadcastChannel": {
2139
+ const { channel, handler, taskId: tskIdInput } = effect.operation.input;
2140
+ const tskId = tskIdInput ?? taskId(generateUuid());
2141
+ if (tskIdInput) {
2142
+ broadcastReg.unsubscribe(tskIdInput);
2143
+ }
2144
+ broadcastReg.subscribe(channel, handler, tskId, callbacks);
2145
+ callbacks.onComplete(tskId);
2146
+ return;
2147
+ }
2148
+ case "ScrollWindow": {
2149
+ acc.scroll.windowScrolls.push(effect.operation.input);
2150
+ callbacks.onComplete(void 0);
2151
+ return;
2152
+ }
2153
+ case "ScrollElement": {
2154
+ const { selector, options } = effect.operation.input;
2155
+ const el = window2.document.querySelector(selector);
2156
+ if (el === null) {
2157
+ callbacks.onComplete(failure(scrollElementError(selector)));
2158
+ } else {
2159
+ el.scrollTo(options);
2160
+ acc.scroll.elementScrolls.push({ selector, options });
2161
+ callbacks.onComplete(success(void 0));
2162
+ }
2163
+ return;
2164
+ }
2165
+ case "FireEvent": {
2166
+ callbacks.onFireEvent(effect.operation.input);
2167
+ callbacks.onComplete(void 0);
2168
+ return;
2169
+ }
2170
+ case "GetState": {
2171
+ callbacks.onComplete(callbacks.getState());
2172
+ return;
2173
+ }
2174
+ case "GetTime": {
2175
+ const callIndex = acc.clock.calls.length;
2176
+ const scripted = config?.clock.values ?? [];
2177
+ const value = callIndex < scripted.length ? scripted[callIndex] : /* @__PURE__ */ new Date(0);
2178
+ acc.clock.calls.push(value);
2179
+ callbacks.onComplete(value);
2180
+ return;
2181
+ }
2182
+ case "GetRandom": {
2183
+ const callIndex = acc.random.calls.length;
2184
+ const scripted = config?.random.values ?? [];
2185
+ const value = callIndex < scripted.length ? scripted[callIndex] : seededRandom(callIndex);
2186
+ acc.random.calls.push(value);
2187
+ callbacks.onComplete(value);
2188
+ return;
2189
+ }
2190
+ case "Log": {
2191
+ acc.console.logs.push(effect.operation.input);
2192
+ callbacks.onComplete(void 0);
2193
+ return;
2194
+ }
2195
+ case "MakeHttpRequest": {
2196
+ const request = effect.operation.input;
2197
+ const handler = config?.http.handler ?? (() => {
2198
+ throw new Error("No HTTP handler configured in TestConfig.");
2199
+ });
2200
+ let result;
2201
+ try {
2202
+ result = handler(request, acc.http.interactions);
2203
+ } catch (err) {
2204
+ result = failure({
2205
+ _type: "RequestError",
2206
+ cause: "UnexpectedError",
2207
+ message: err instanceof Error ? err.message : `Unexpected request error: ${err}`
2208
+ });
2209
+ }
2210
+ acc.http.interactions.push({ request, response: result });
2211
+ callbacks.onComplete(result);
2212
+ return;
2213
+ }
2214
+ case "Product": {
2215
+ let resultA = "__ctrl_fx_unset_result";
2216
+ let resultB = "__ctrl_fx_unset_result";
2217
+ testingInterpreter(
2218
+ effect.operation.input[0],
2219
+ {
2220
+ ...callbacks,
2221
+ onComplete(a) {
2222
+ resultA = a;
2223
+ if (resultB !== "__ctrl_fx_unset_result") {
2224
+ callbacks.onComplete([resultA, resultB]);
2225
+ }
2226
+ }
2227
+ },
2228
+ taskRegistry,
2229
+ acc,
2230
+ broadcastReg,
2231
+ scheduler,
2232
+ window2,
2233
+ config
2234
+ );
2235
+ testingInterpreter(
2236
+ effect.operation.input[1],
2237
+ {
2238
+ ...callbacks,
2239
+ onComplete(b) {
2240
+ resultB = b;
2241
+ if (resultA !== "__ctrl_fx_unset_result") {
2242
+ callbacks.onComplete([resultA, resultB]);
2243
+ }
2244
+ }
2245
+ },
2246
+ taskRegistry,
2247
+ acc,
2248
+ broadcastReg,
2249
+ scheduler,
2250
+ window2,
2251
+ config
2252
+ );
2253
+ return;
2254
+ }
2255
+ case "ScheduleTask": {
2256
+ const tskId = effect.operation.input.taskId ?? taskId(generateUuid());
2257
+ if (effect.operation.input.taskId) {
2258
+ scheduler.cancel(effect.operation.input.taskId);
2259
+ }
2260
+ scheduler.schedule(
2261
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2262
+ effect.operation.input.task,
2263
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2264
+ { ...callbacks, onComplete() {
2265
+ } },
2266
+ tskId,
2267
+ effect.operation.input.initialDelayInMillis,
2268
+ effect.operation.input.repeatIntervalInMillis
2269
+ );
2270
+ callbacks.onComplete(tskId);
2271
+ return;
2272
+ }
2273
+ case "SetTimeout": {
2274
+ callbacks.onComplete(void 0);
2275
+ return;
2276
+ }
2277
+ case "PushState": {
2278
+ acc.navigation.locationHistory.push(effect.operation.input);
2279
+ callbacks.onComplete(void 0);
2280
+ return;
2281
+ }
2282
+ case "ReplaceState": {
2283
+ if (acc.navigation.locationHistory.length > 0) {
2284
+ acc.navigation.locationHistory[acc.navigation.locationHistory.length - 1] = effect.operation.input;
2285
+ } else {
2286
+ acc.navigation.locationHistory.push(effect.operation.input);
2287
+ }
2288
+ callbacks.onComplete(void 0);
2289
+ return;
2290
+ }
2291
+ case "Go": {
2292
+ const delta = effect.operation.input;
2293
+ const newLen = acc.navigation.locationHistory.length + delta;
2294
+ if (newLen > 0) {
2295
+ acc.navigation.locationHistory.length = newLen;
2296
+ }
2297
+ callbacks.onComplete(void 0);
2298
+ return;
2299
+ }
2300
+ case "GenerateUuid": {
2301
+ const uuidLen = acc.uuidSequence.length;
2302
+ const nextUuid = uuidFromString(`${uuidLen}`);
2303
+ acc.uuidSequence.push(nextUuid);
2304
+ callbacks.onComplete(nextUuid);
2305
+ return;
2306
+ }
2307
+ case "GetStorageItem": {
2308
+ const store = acc.storage[effect.operation.input.storage];
2309
+ callbacks.onComplete(
2310
+ store.get(effect.operation.input.key) ?? null
2311
+ );
2312
+ return;
2313
+ }
2314
+ case "SetStorageItem": {
2315
+ acc.storage[effect.operation.input.storage].set(
2316
+ effect.operation.input.key,
2317
+ effect.operation.input.value
2318
+ );
2319
+ callbacks.onComplete(void 0);
2320
+ return;
2321
+ }
2322
+ case "RemoveStorageItem": {
2323
+ acc.storage[effect.operation.input.storage].delete(
2324
+ effect.operation.input.key
2325
+ );
2326
+ callbacks.onComplete(void 0);
2327
+ return;
2328
+ }
2329
+ case "ClearStorage": {
2330
+ acc.storage[effect.operation.input.storage].clear();
2331
+ callbacks.onComplete(void 0);
2332
+ return;
2333
+ }
2334
+ case "OpenDatabase": {
2335
+ const { db, setup } = effect.operation.input;
2336
+ if (!acc.db.databases.has(db)) {
2337
+ const mutableDb = { stores: /* @__PURE__ */ new Map() };
2338
+ acc.db.databases.set(db, mutableDb);
2339
+ run2(setup(dbVersion(0)), makeSetupInterpreter(mutableDb), (_r) => {
2340
+ callbacks.onComplete(success(void 0));
2341
+ });
2342
+ } else {
2343
+ callbacks.onComplete(success(void 0));
2344
+ }
2345
+ return;
2346
+ }
2347
+ case "RunDbTransaction": {
2348
+ const { db, effect: dbEffect } = effect.operation.input;
2349
+ const mutableDb = acc.db.databases.get(db);
2350
+ if (!mutableDb) {
2351
+ callbacks.onComplete(
2352
+ failure({
2353
+ _type: "UnexpectedError",
2354
+ message: `Database '${db}' is not open`
2355
+ })
2356
+ );
2357
+ return;
2358
+ }
2359
+ run(dbEffect, makeDbInterpreter(mutableDb), (_r) => {
2360
+ callbacks.onComplete(success(void 0));
2361
+ });
2362
+ return;
2363
+ }
2364
+ case "UpdateState": {
2365
+ callbacks.setState(effect.operation.input(callbacks.getState()));
2366
+ callbacks.onComplete(void 0);
2367
+ return;
2368
+ }
2369
+ case "CustomEffect": {
2370
+ const { args, test } = effect.operation.input;
2371
+ const result = test(args, acc.custom);
2372
+ acc.custom = result.data;
2373
+ callbacks.onComplete(result.out);
2374
+ return;
2375
+ }
2376
+ default: {
2377
+ exhaustivenessCheck(effect.operation);
2378
+ }
2379
+ }
2380
+ break;
2381
+ }
2382
+ default: {
2383
+ exhaustivenessCheck(effect);
2384
+ }
2385
+ }
2386
+ }
2387
+ function makeSetupInterpreter(mutableDb) {
2388
+ return (op, onComplete) => {
2389
+ switch (op._type) {
2390
+ case "CreateObjectStore": {
2391
+ mutableDb.stores.set(op.objectStore, {
2392
+ keyPath: op.keyPath,
2393
+ records: /* @__PURE__ */ new Map(),
2394
+ autoIncrementCounter: 1
2395
+ });
2396
+ onComplete(void 0);
2397
+ return;
2398
+ }
2399
+ case "CreateIndex": {
2400
+ onComplete(void 0);
2401
+ return;
2402
+ }
2403
+ case "DeleteObjectStore": {
2404
+ mutableDb.stores.delete(op.objectStore);
2405
+ onComplete(void 0);
2406
+ return;
2407
+ }
2408
+ case "DeleteIndex": {
2409
+ onComplete(void 0);
2410
+ return;
2411
+ }
2412
+ case "GetObjectStoreNames": {
2413
+ onComplete([...mutableDb.stores.keys()]);
2414
+ return;
2415
+ }
2416
+ case "GetIndexNames": {
2417
+ onComplete([]);
2418
+ return;
2419
+ }
2420
+ case "RunDbEffect": {
2421
+ run(
2422
+ op.effect,
2423
+ makeDbInterpreter(mutableDb),
2424
+ (_r) => onComplete(void 0)
2425
+ );
2426
+ return;
2427
+ }
2428
+ }
2429
+ };
2430
+ }
2431
+ function makeDbInterpreter(mutableDb) {
2432
+ return (op, onComplete) => {
2433
+ switch (op._type) {
2434
+ case "Add":
2435
+ case "Put": {
2436
+ const store = requireStore(mutableDb, op.objectStore);
2437
+ store.records.set(extractKey(op.value, store), op.value);
2438
+ onComplete(void 0);
2439
+ return;
2440
+ }
2441
+ case "Get": {
2442
+ const store = requireStore(mutableDb, op.objectStore);
2443
+ onComplete(store.records.get(serializeIdbKey(op.key)));
2444
+ return;
2445
+ }
2446
+ case "GetAll": {
2447
+ const store = requireStore(mutableDb, op.objectStore);
2448
+ onComplete([...store.records.values()]);
2449
+ return;
2450
+ }
2451
+ case "Delete": {
2452
+ const store = requireStore(mutableDb, op.objectStore);
2453
+ store.records.delete(serializeIdbKey(op.key));
2454
+ onComplete(void 0);
2455
+ return;
2456
+ }
2457
+ case "Count": {
2458
+ const store = requireStore(mutableDb, op.objectStore);
2459
+ onComplete(store.records.size);
2460
+ return;
2461
+ }
2462
+ }
2463
+ };
2464
+ }
2465
+ function requireStore(mutableDb, storeName) {
2466
+ const store = mutableDb.stores.get(storeName);
2467
+ if (!store) throw new Error(`Object store '${storeName}' not found`);
2468
+ return store;
2469
+ }
2470
+ function extractKey(value, store) {
2471
+ const { keyPath } = store;
2472
+ switch (keyPath._type) {
2473
+ case "AutoIncrement":
2474
+ return String(store.autoIncrementCounter++);
2475
+ case "Manual": {
2476
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2477
+ throw new Error(`Cannot extract key '${keyPath.value}' from non-object`);
2478
+ }
2479
+ return JSON.stringify(value[keyPath.value]);
2480
+ }
2481
+ case "Multi": {
2482
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2483
+ throw new Error("Cannot extract compound key from non-object");
2484
+ }
2485
+ const obj = value;
2486
+ return JSON.stringify(
2487
+ [keyPath.first, keyPath.second, ...keyPath.rest].map((f) => obj[f])
2488
+ );
2489
+ }
2490
+ }
2491
+ }
2492
+
2493
+ // src/internal/testing.ts
2494
+ var Node = class {
2495
+ constructor() {
2496
+ __publicField(this, "previousSibling", null);
2497
+ __publicField(this, "nextSibling", null);
2498
+ __publicField(this, "parentNode", null);
2499
+ }
2500
+ remove() {
2501
+ if (this.previousSibling) {
2502
+ this.previousSibling.nextSibling = this.nextSibling;
2503
+ } else {
2504
+ if (this.parentNode) {
2505
+ this.parentNode.firstChild = this.nextSibling;
2506
+ }
2507
+ }
2508
+ if (this.nextSibling) {
2509
+ this.nextSibling.previousSibling = this.previousSibling;
2510
+ }
2511
+ this.parentNode = null;
2512
+ }
2513
+ insertBefore(node) {
2514
+ const newNode = typeof node === "string" ? new TextNode(node) : node;
2515
+ newNode.remove();
2516
+ if (this.previousSibling) {
2517
+ this.previousSibling.nextSibling = newNode;
2518
+ newNode.previousSibling = this.previousSibling;
2519
+ } else {
2520
+ if (this.parentNode) {
2521
+ this.parentNode.firstChild = newNode;
2522
+ }
2523
+ }
2524
+ newNode.nextSibling = this;
2525
+ newNode.parentNode = this.parentNode;
2526
+ this.previousSibling = newNode;
2527
+ return newNode;
2528
+ }
2529
+ insertAfter(node) {
2530
+ const newNode = typeof node === "string" ? new TextNode(node) : node;
2531
+ newNode.remove();
2532
+ if (this.nextSibling) {
2533
+ this.nextSibling.previousSibling = newNode;
2534
+ newNode.nextSibling = this.nextSibling;
2535
+ }
2536
+ newNode.previousSibling = this;
2537
+ newNode.parentNode = this.parentNode;
2538
+ this.nextSibling = newNode;
2539
+ return newNode;
2540
+ }
2541
+ before(...nodes) {
2542
+ let current = this;
2543
+ for (let i = nodes.length - 1; i >= 0; i--) {
2544
+ current = current.insertBefore(nodes[i]);
2545
+ }
2546
+ }
2547
+ after(...nodes) {
2548
+ let current = this;
2549
+ for (let i = 0; i < nodes.length; i++) {
2550
+ current = current.insertAfter(nodes[i]);
2551
+ }
2552
+ }
2553
+ };
2554
+ var TextNode = class extends Node {
2555
+ constructor(nodeValue) {
2556
+ super();
2557
+ __publicField(this, "nodeValue");
2558
+ this.nodeValue = nodeValue;
2559
+ }
2560
+ };
2561
+ var ElementNode = class _ElementNode extends Node {
2562
+ constructor(tagName) {
2563
+ super();
2564
+ __publicField(this, "tagName");
2565
+ __publicField(this, "attributes", {});
2566
+ __publicField(this, "firstChild", null);
2567
+ __publicField(this, "scrollTop", 0);
2568
+ __publicField(this, "scrollLeft", 0);
2569
+ __publicField(this, "eventListeners", []);
2570
+ this.tagName = tagName;
2571
+ }
2572
+ scrollTo(options) {
2573
+ if (options.top !== void 0) this.scrollTop = options.top;
2574
+ if (options.left !== void 0) this.scrollLeft = options.left;
2575
+ }
2576
+ removeAttribute(name) {
2577
+ delete this.attributes[name];
2578
+ }
2579
+ setAttribute(name, value) {
2580
+ this.attributes[name] = value;
2581
+ }
2582
+ lastChild() {
2583
+ let child = this.firstChild;
2584
+ while (child && child.nextSibling) {
2585
+ child = child.nextSibling;
2586
+ }
2587
+ return child;
2588
+ }
2589
+ append(...nodes) {
2590
+ if (nodes.length === 0) {
2591
+ return;
2592
+ }
2593
+ let last = this.lastChild();
2594
+ if (!last) {
2595
+ this.firstChild = nodes[0];
2596
+ this.firstChild.parentNode = this;
2597
+ nodes = nodes.slice(1);
2598
+ last = this.firstChild;
2599
+ }
2600
+ last.after(...nodes);
2601
+ }
2602
+ prepend(...nodes) {
2603
+ if (this.firstChild) {
2604
+ this.firstChild.before(...nodes);
2605
+ } else {
2606
+ this.append(...nodes);
2607
+ }
2608
+ }
2609
+ addEventListener(type, listener, options) {
2610
+ this.eventListeners.push({
2611
+ type,
2612
+ listener,
2613
+ options
2614
+ });
2615
+ }
2616
+ removeEventListener(type, listener, useCapture) {
2617
+ this.eventListeners = this.eventListeners.filter((el) => {
2618
+ return !(el.type === type && el.listener === listener && (useCapture ? el.options.capture : !el.options.capture));
2619
+ });
2620
+ }
2621
+ matchesSelector(selector) {
2622
+ const parts = parseSelector(selector);
2623
+ return matchesSelectorSequence(this, parts);
2624
+ }
2625
+ traverseAndCollect(selector, result) {
2626
+ if (this.matchesSelector(selector)) {
2627
+ result.push(this);
2628
+ }
2629
+ let child = this.firstChild;
2630
+ while (child) {
2631
+ if (child instanceof _ElementNode) {
2632
+ child.traverseAndCollect(selector, result);
2633
+ }
2634
+ child = child.nextSibling;
2635
+ }
2636
+ }
2637
+ querySelector(selector) {
2638
+ const results = this.querySelectorAll(selector);
2639
+ return results.length > 0 ? results[0] : null;
2640
+ }
2641
+ querySelectorAll(selector) {
2642
+ const result = [];
2643
+ this.traverseAndCollect(selector, result);
2644
+ return result;
2645
+ }
2646
+ };
2647
+ var Document = class {
2648
+ constructor() {
2649
+ __publicField(this, "head", this.createElement("head"));
2650
+ __publicField(this, "body", this.createElement("body"));
2651
+ __publicField(this, "visibilityState", "visible");
2652
+ __publicField(this, "documentEventListeners", []);
2653
+ }
2654
+ createTextNode(text) {
2655
+ return new TextNode(text);
2656
+ }
2657
+ getElementById(id) {
2658
+ return this.body.querySelector("#" + id);
2659
+ }
2660
+ querySelector(selector) {
2661
+ return this.body.querySelector(selector);
2662
+ }
2663
+ createElement(tag) {
2664
+ return new ElementNode(tag);
2665
+ }
2666
+ addEventListener(type, listener, options) {
2667
+ this.documentEventListeners.push({ type, listener, options });
2668
+ }
2669
+ removeEventListener(type, listener, useCapture) {
2670
+ this.documentEventListeners = this.documentEventListeners.filter(
2671
+ (el) => !(el.type === type && el.listener === listener && (useCapture ? el.options.capture : !el.options.capture))
2672
+ );
2673
+ }
2674
+ };
2675
+ var Window = class {
2676
+ constructor() {
2677
+ __publicField(this, "document", new Document());
2678
+ __publicField(this, "innerWidth", 1024);
2679
+ __publicField(this, "innerHeight", 768);
2680
+ __publicField(this, "location", {
2681
+ protocol: "http",
2682
+ hostname: "localhost",
2683
+ port: "8080",
2684
+ pathname: "/",
2685
+ search: "",
2686
+ hash: ""
2687
+ });
2688
+ __publicField(this, "eventListeners", []);
2689
+ }
2690
+ addEventListener(type, listener, options) {
2691
+ this.eventListeners.push({
2692
+ type,
2693
+ listener,
2694
+ options
2695
+ });
2696
+ }
2697
+ removeEventListener(type, listener, useCapture) {
2698
+ this.eventListeners = this.eventListeners.filter((el) => {
2699
+ return !(el.type === type && el.listener === listener && (useCapture ? el.options.capture : !el.options.capture));
2700
+ });
2701
+ }
2702
+ };
2703
+ function serializeElement(elem) {
2704
+ const attrs = Object.entries(elem.attributes).map(
2705
+ ([name, value]) => ({ name, value })
2706
+ );
2707
+ const childNodes = [];
2708
+ let child = elem.firstChild;
2709
+ while (child) {
2710
+ if (child instanceof TextNode) {
2711
+ childNodes.push(child.nodeValue);
2712
+ } else if (child instanceof ElementNode) {
2713
+ childNodes.push(serializeElement(child));
2714
+ }
2715
+ child = child.nextSibling;
2716
+ }
2717
+ return { tag: elem.tagName, attrs, childNodes };
2718
+ }
2719
+ function serializeDom(doc) {
2720
+ return {
2721
+ head: serializeElement(doc.head),
2722
+ body: serializeElement(doc.body)
2723
+ };
2724
+ }
2725
+ function dispatchClick(element) {
2726
+ const syntheticEvent = {
2727
+ type: "click",
2728
+ target: null,
2729
+ eventPhase: 2,
2730
+ defaultPrevented: false,
2731
+ propagationStopped: false,
2732
+ preventDefault() {
2733
+ this.defaultPrevented = true;
2734
+ },
2735
+ stopImmediatePropagation() {
2736
+ this.propagationStopped = true;
2737
+ }
2738
+ };
2739
+ for (const entry of element.eventListeners) {
2740
+ if (entry.type === "click") {
2741
+ entry.listener(syntheticEvent);
2742
+ if (syntheticEvent.propagationStopped) {
2743
+ break;
2744
+ }
2745
+ }
2746
+ }
2747
+ }
2748
+ function dispatchSubmit(element) {
2749
+ const syntheticEvent = {
2750
+ type: "submit",
2751
+ target: null,
2752
+ eventPhase: 2,
2753
+ defaultPrevented: false,
2754
+ propagationStopped: false,
2755
+ preventDefault() {
2756
+ this.defaultPrevented = true;
2757
+ },
2758
+ stopImmediatePropagation() {
2759
+ this.propagationStopped = true;
2760
+ }
2761
+ };
2762
+ for (const entry of element.eventListeners) {
2763
+ if (entry.type === "submit") {
2764
+ entry.listener(syntheticEvent);
2765
+ if (syntheticEvent.propagationStopped) {
2766
+ break;
2767
+ }
2768
+ }
2769
+ }
2770
+ }
2771
+ function dispatchCustomEvent(element, eventName, detail) {
2772
+ const syntheticEvent = {
2773
+ type: eventName,
2774
+ detail,
2775
+ target: null,
2776
+ eventPhase: 2,
2777
+ defaultPrevented: false,
2778
+ propagationStopped: false,
2779
+ preventDefault() {
2780
+ this.defaultPrevented = true;
2781
+ },
2782
+ stopImmediatePropagation() {
2783
+ this.propagationStopped = true;
2784
+ }
2785
+ };
2786
+ for (const entry of element.eventListeners) {
2787
+ if (entry.type === eventName) {
2788
+ entry.listener(syntheticEvent);
2789
+ if (syntheticEvent.propagationStopped) {
2790
+ break;
2791
+ }
2792
+ }
2793
+ }
2794
+ }
2795
+ function dispatchTextInput(element, value) {
2796
+ const syntheticEvent = {
2797
+ type: "input",
2798
+ target: { tagName: "INPUT", value, selectionStart: value.length, selectionEnd: value.length },
2799
+ eventPhase: 2,
2800
+ defaultPrevented: false,
2801
+ propagationStopped: false,
2802
+ preventDefault() {
2803
+ this.defaultPrevented = true;
2804
+ },
2805
+ stopImmediatePropagation() {
2806
+ this.propagationStopped = true;
2807
+ }
2808
+ };
2809
+ for (const entry of element.eventListeners) {
2810
+ if (entry.type === "input") {
2811
+ entry.listener(syntheticEvent);
2812
+ if (syntheticEvent.propagationStopped) {
2813
+ break;
2814
+ }
2815
+ }
2816
+ }
2817
+ }
2818
+ function querySelectorAllInApp(window2, selector) {
2819
+ const appRoot = window2.document.getElementById("app");
2820
+ if (!appRoot) return [];
2821
+ const results = [];
2822
+ let child = appRoot.firstChild;
2823
+ while (child) {
2824
+ if (child instanceof ElementNode) {
2825
+ results.push(...child.querySelectorAll(selector));
2826
+ }
2827
+ child = child.nextSibling;
2828
+ }
2829
+ return results;
2830
+ }
2831
+ function makeWindow() {
2832
+ const win = new Window();
2833
+ const root = win.document.createElement("div");
2834
+ root.setAttribute("id", "app");
2835
+ win.document.body.append(root);
2836
+ return win;
2837
+ }
2838
+ var TestableComponentImpl = class _TestableComponentImpl {
2839
+ constructor(component, state, config, data, dom, window2, mountedPaths) {
2840
+ __publicField(this, "config");
2841
+ __publicField(this, "data");
2842
+ __publicField(this, "dom");
2843
+ __publicField(this, "_component");
2844
+ __publicField(this, "state");
2845
+ __publicField(this, "_window");
2846
+ // Component paths already mounted by a previous ComponentManager in this
2847
+ // chain of .run() calls. Each .run() mounts a fresh ComponentManager (the
2848
+ // DOM/scheduler/subscriptions don't persist), so without this, onRender
2849
+ // effects would fire again on every chained .run() even though, from the
2850
+ // test's perspective, the component was already mounted.
2851
+ __publicField(this, "_mountedPaths");
2852
+ this._component = component;
2853
+ this.state = state;
2854
+ this.config = config;
2855
+ this.data = data;
2856
+ this.dom = dom;
2857
+ this._window = window2;
2858
+ this._mountedPaths = mountedPaths;
2859
+ }
2860
+ withConfig(config) {
2861
+ return new _TestableComponentImpl(
2862
+ this._component,
2863
+ this.state,
2864
+ config,
2865
+ this.data,
2866
+ this.dom,
2867
+ this._window,
2868
+ this._mountedPaths
2869
+ );
2870
+ }
2871
+ withData(data) {
2872
+ return new _TestableComponentImpl(
2873
+ this._component,
2874
+ this.state,
2875
+ this.config,
2876
+ data,
2877
+ this.dom,
2878
+ this._window,
2879
+ this._mountedPaths
2880
+ );
2881
+ }
2882
+ run(...interactions) {
2883
+ const win = makeWindow();
2884
+ const { interpreter, getData, advanceTime: advanceTime2, fireBroadcast } = makeTestingInterpreter(
2885
+ this.data,
2886
+ win,
2887
+ this.config
2888
+ );
2889
+ const currentState = this.state;
2890
+ const wrappedComponent = {
2891
+ ...this._component,
2892
+ initialState: (_params) => pure(currentState)
2893
+ };
2894
+ const appNode = win.document.getElementById("app");
2895
+ const mgr = new ComponentManager(
2896
+ wrappedComponent,
2897
+ appNode,
2898
+ win,
2899
+ interpreter,
2900
+ noopStyleRegistry(),
2901
+ void 0,
2902
+ this._mountedPaths
2903
+ );
2904
+ for (const interaction of interactions) {
2905
+ if ("_type" in interaction) {
2906
+ switch (interaction._type) {
2907
+ case "AdvanceTime":
2908
+ advanceTime2(interaction.milliseconds);
2909
+ break;
2910
+ case "ReceiveBroadcast":
2911
+ fireBroadcast(interaction.channel, interaction.message);
2912
+ break;
2913
+ case "FireCustomEvent": {
2914
+ const qs = interaction.selector;
2915
+ const cssSelector = qs.selector;
2916
+ const results = win.document.body.querySelectorAll(cssSelector);
2917
+ results.forEach(
2918
+ (el) => dispatchCustomEvent(el, interaction.eventName, interaction.detail)
2919
+ );
2920
+ break;
2921
+ }
2922
+ case "Submit": {
2923
+ const qs = interaction.selector;
2924
+ const cssSelector = qs.selector;
2925
+ const results = win.document.body.querySelectorAll(cssSelector);
2926
+ results.forEach((el) => dispatchSubmit(el));
2927
+ break;
2928
+ }
2929
+ case "TextInput": {
2930
+ const qs = interaction.selector;
2931
+ const cssSelector = qs.selector;
2932
+ const results = win.document.body.querySelectorAll(cssSelector);
2933
+ results.forEach((el) => dispatchTextInput(el, interaction.value));
2934
+ break;
2935
+ }
2936
+ default:
2937
+ exhaustivenessCheck(interaction);
2938
+ }
2939
+ } else {
2940
+ switch (interaction.type) {
2941
+ case "Click": {
2942
+ const qs = interaction.selector;
2943
+ const cssSelector = qs.selector;
2944
+ const results = win.document.body.querySelectorAll(cssSelector);
2945
+ switch (qs._type) {
2946
+ case "ExactlyOne":
2947
+ if (results.length !== 1)
2948
+ throw new Error(
2949
+ `Expected exactly one element matching '${cssSelector}', found ${results.length}`
2950
+ );
2951
+ break;
2952
+ case "OneOrMore":
2953
+ if (results.length === 0)
2954
+ throw new Error(
2955
+ `Expected at least one element matching '${cssSelector}', found 0`
2956
+ );
2957
+ break;
2958
+ case "ZeroOrOne":
2959
+ if (results.length > 1)
2960
+ throw new Error(
2961
+ `Expected zero or one element matching '${cssSelector}', found ${results.length}`
2962
+ );
2963
+ break;
2964
+ case "ZeroOrMore":
2965
+ break;
2966
+ default:
2967
+ exhaustivenessCheck(qs);
2968
+ }
2969
+ results.forEach((el) => dispatchClick(el));
2970
+ break;
2971
+ }
2972
+ default:
2973
+ exhaustivenessCheck(interaction.type);
2974
+ }
2975
+ }
2976
+ }
2977
+ const stateAfter = mgr.getRootState();
2978
+ const newState = stateAfter._type === "Ready" ? stateAfter.value : currentState;
2979
+ return new _TestableComponentImpl(
2980
+ this._component,
2981
+ newState,
2982
+ this.config,
2983
+ getData(),
2984
+ serializeDom(win.document),
2985
+ win,
2986
+ mgr.getMountedPaths()
2987
+ );
2988
+ }
2989
+ find(selector) {
2990
+ return querySelectorAllInApp(this._window, selector).map(serializeElement);
2991
+ }
2992
+ findOne(selector) {
2993
+ const results = querySelectorAllInApp(this._window, selector);
2994
+ if (results.length !== 1)
2995
+ throw new Error(
2996
+ `findOne('${selector}'): expected 1 element, found ${results.length}`
2997
+ );
2998
+ return serializeElement(results[0]);
2999
+ }
3000
+ findOneOrMore(selector) {
3001
+ const results = querySelectorAllInApp(this._window, selector);
3002
+ if (results.length === 0)
3003
+ throw new Error(
3004
+ `findOneOrMore('${selector}'): expected at least 1 element, found 0`
3005
+ );
3006
+ return results.map(serializeElement);
3007
+ }
3008
+ findMaybeOne(selector) {
3009
+ const results = querySelectorAllInApp(this._window, selector);
3010
+ if (results.length > 1)
3011
+ throw new Error(
3012
+ `findMaybeOne('${selector}'): expected 0 or 1 elements, found ${results.length}`
3013
+ );
3014
+ return results.length === 0 ? void 0 : serializeElement(results[0]);
3015
+ }
3016
+ };
3017
+ function testableComponent(component, config, data) {
3018
+ const initialData = data || newTestData();
3019
+ const win = makeWindow();
3020
+ const { interpreter, getData } = makeTestingInterpreter(
3021
+ initialData,
3022
+ win,
3023
+ config
3024
+ );
3025
+ const appNode = win.document.getElementById("app");
3026
+ const mgr = new ComponentManager(
3027
+ component,
3028
+ appNode,
3029
+ win,
3030
+ interpreter,
3031
+ noopStyleRegistry()
3032
+ );
3033
+ const stateAfter = mgr.getRootState();
3034
+ const currentState = stateAfter._type === "Ready" ? stateAfter.value : void 0;
3035
+ return new TestableComponentImpl(
3036
+ component,
3037
+ currentState,
3038
+ config,
3039
+ getData(),
3040
+ serializeDom(win.document),
3041
+ win,
3042
+ mgr.getMountedPaths()
3043
+ );
3044
+ }
3045
+ function parseSelector(selector) {
3046
+ const parts = selector.trim().split(/\s+/);
3047
+ return parts.map(parsePart);
3048
+ }
3049
+ function parsePart(part) {
3050
+ const attrs = {};
3051
+ const classes = [];
3052
+ let tag = void 0;
3053
+ if (part.startsWith("#")) {
3054
+ attrs["id"] = part.substring(1);
3055
+ return { tag, classes, attrs };
3056
+ }
3057
+ if (part.startsWith(".")) {
3058
+ classes.push(part.substring(1));
3059
+ return { tag, classes, attrs };
3060
+ }
3061
+ const attrRegex = /\[([^\]=]+)=([^\]]+)\]/g;
3062
+ let match;
3063
+ while ((match = attrRegex.exec(part)) !== null) {
3064
+ attrs[match[1]] = match[2];
3065
+ }
3066
+ const withoutAttrs = part.replace(attrRegex, "").trim();
3067
+ const dotParts = withoutAttrs.split(".");
3068
+ tag = dotParts[0] || void 0;
3069
+ classes.push(...dotParts.slice(1));
3070
+ return { tag, classes, attrs };
3071
+ }
3072
+ function matchesPart(element, part) {
3073
+ if (part.tag && element.tagName.toLowerCase() !== part.tag.toLowerCase()) {
3074
+ return false;
3075
+ }
3076
+ for (const cls of part.classes) {
3077
+ const elementClasses = (element.attributes["class"] || "").split(/\s+/);
3078
+ if (!elementClasses.includes(cls)) {
3079
+ return false;
3080
+ }
3081
+ }
3082
+ for (const [attr2, value] of Object.entries(part.attrs)) {
3083
+ if (element.attributes[attr2] !== value) {
3084
+ return false;
3085
+ }
3086
+ }
3087
+ return true;
3088
+ }
3089
+ function matchesSelectorSequence(element, parts) {
3090
+ if (parts.length === 0) return true;
3091
+ if (!matchesPart(element, parts[parts.length - 1])) {
3092
+ return false;
3093
+ }
3094
+ if (parts.length === 1) return true;
3095
+ let ancestor = element.parentNode;
3096
+ for (let i = parts.length - 2; i >= 0; i--) {
3097
+ let found = false;
3098
+ while (ancestor) {
3099
+ if (matchesPart(ancestor, parts[i])) {
3100
+ found = true;
3101
+ ancestor = ancestor.parentNode;
3102
+ break;
3103
+ }
3104
+ ancestor = ancestor.parentNode;
3105
+ }
3106
+ if (!found) return false;
3107
+ }
3108
+ return true;
3109
+ }
3110
+
3111
+ // src/internal/index.ts
3112
+ function componentPath(id, parentComponentPath) {
3113
+ return {
3114
+ parentComponentPath,
3115
+ nodeId: typeof id === "string" ? nodeId(id) : id,
3116
+ format() {
3117
+ return formatComponentPath(this);
3118
+ }
3119
+ };
3120
+ }
3121
+ function formatComponentPath(path) {
3122
+ const parentStr = path.parentComponentPath ? formatComponentPath(path.parentComponentPath) : "";
3123
+ return parentStr + "/" + encodeURIComponent(path.nodeId.value);
3124
+ }
3125
+ function generateUuid() {
3126
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
3127
+ return crypto.randomUUID();
3128
+ } else {
3129
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
3130
+ const r = Math.random() * 16 | 0;
3131
+ const v = c == "x" ? r : r & 3 | 8;
3132
+ return v.toString(16);
3133
+ });
3134
+ }
3135
+ }
3136
+ function testComponent(component, config, data) {
3137
+ return testableComponent(component, config ?? defaultTestConfig(), data);
3138
+ }
3139
+
3140
+ // src/clipboard.ts
3141
+ function clipboardError(cause, message) {
3142
+ return { _type: "ClipboardError", cause, message };
3143
+ }
3144
+
3145
+ // src/internal/clipboard.ts
3146
+ function writeClipboard(text, callback) {
3147
+ if (!globalThis.navigator?.clipboard?.writeText) {
3148
+ callback(
3149
+ failure(clipboardError("NotSupported", "Clipboard API not available"))
3150
+ );
3151
+ return;
3152
+ }
3153
+ globalThis.navigator.clipboard.writeText(text).then(() => callback(success(void 0))).catch((err) => callback(failure(toClipboardError(err))));
3154
+ }
3155
+ function readClipboard(callback) {
3156
+ if (!globalThis.navigator?.clipboard?.readText) {
3157
+ callback(
3158
+ failure(clipboardError("NotSupported", "Clipboard API not available"))
3159
+ );
3160
+ return;
3161
+ }
3162
+ globalThis.navigator.clipboard.readText().then((text) => callback(success(text))).catch((err) => callback(failure(toClipboardError(err))));
3163
+ }
3164
+ function toClipboardError(err) {
3165
+ if (err instanceof DOMException && err.name === "NotAllowedError") {
3166
+ return clipboardError("NotAllowed", err.message);
3167
+ } else if (err instanceof Error) {
3168
+ return clipboardError("UnexpectedError", err.message);
3169
+ } else {
3170
+ return clipboardError("UnexpectedError", `Unexpected clipboard error: ${err}`);
3171
+ }
3172
+ }
3173
+
3174
+ // src/db/errors.ts
3175
+ function unexpectedError(params) {
3176
+ return {
3177
+ _type: "UnexpectedError",
3178
+ cause: params.cause,
3179
+ message: params.message
3180
+ };
3181
+ }
3182
+ function notFoundError(cause) {
3183
+ return { _type: "NotFoundError", cause };
3184
+ }
3185
+ function constraintError(cause) {
3186
+ return { _type: "ConstraintError", cause };
3187
+ }
3188
+
3189
+ // src/internal/dbmgr.ts
3190
+ var DbManager = class {
3191
+ constructor() {
3192
+ __publicField(this, "openDatabases", /* @__PURE__ */ new Map());
3193
+ }
3194
+ open(db, version, setup, callback) {
3195
+ const idbFactory = globalThis.indexedDB;
3196
+ if (!idbFactory) {
3197
+ return callback(
3198
+ failure(unexpectedError({ message: "IndexedDB not supported" }))
3199
+ );
3200
+ }
3201
+ const openRequest = idbFactory.open(db, version);
3202
+ openRequest.onblocked = (_event) => {
3203
+ window.alert(
3204
+ "A database upgrade is blocked. Please close other tabs with this app."
3205
+ );
3206
+ };
3207
+ let upgradeError = void 0;
3208
+ openRequest.onupgradeneeded = (versionChangeEvent) => {
3209
+ const idbDb = openRequest.result;
3210
+ const tx = openRequest.transaction;
3211
+ if (!tx) {
3212
+ console.warn("Expected idb transaction to be non-null during upgrade");
3213
+ return;
3214
+ }
3215
+ const oldVersion = dbVersion(versionChangeEvent.oldVersion);
3216
+ const effect = setup(oldVersion);
3217
+ try {
3218
+ run2(effect, setupInterpreter(idbDb, tx), (_result) => {
3219
+ });
3220
+ } catch (err) {
3221
+ if (err instanceof Error) upgradeError = err;
3222
+ throw err;
3223
+ }
3224
+ };
3225
+ openRequest.onerror = (event) => {
3226
+ if (upgradeError instanceof DOMException) {
3227
+ if (upgradeError.name === "ConstraintError") {
3228
+ return callback(failure(constraintError(upgradeError)));
3229
+ }
3230
+ if (upgradeError.name === "NotFoundError") {
3231
+ return callback(failure(notFoundError(upgradeError)));
3232
+ }
3233
+ }
3234
+ return callback(
3235
+ failure(
3236
+ unexpectedError({
3237
+ message: extractMessage(event),
3238
+ cause: upgradeError
3239
+ })
3240
+ )
3241
+ );
3242
+ };
3243
+ openRequest.onsuccess = (_event) => {
3244
+ this.openDatabases.set(db, openRequest.result);
3245
+ callback(success(void 0));
3246
+ };
3247
+ }
3248
+ runTransaction(db, objectStores, mode, effect, callback) {
3249
+ const idbDb = this.openDatabases.get(db);
3250
+ if (!idbDb) {
3251
+ return callback(
3252
+ failure(unexpectedError({ message: `Database '${db}' is not open` }))
3253
+ );
3254
+ }
3255
+ const tx = idbDb.transaction([...objectStores], mode);
3256
+ let txError = null;
3257
+ tx.onerror = (_event) => {
3258
+ txError = tx.error;
3259
+ };
3260
+ tx.onabort = (_event) => {
3261
+ if (txError !== null) {
3262
+ if (txError.name === "ConstraintError") {
3263
+ return callback(failure(constraintError(txError)));
3264
+ }
3265
+ if (txError.name === "NotFoundError") {
3266
+ return callback(failure(notFoundError(txError)));
3267
+ }
3268
+ return callback(failure(unexpectedError({ cause: txError })));
3269
+ }
3270
+ return callback(
3271
+ failure(unexpectedError({ message: "Transaction aborted" }))
3272
+ );
3273
+ };
3274
+ tx.oncomplete = (_event) => {
3275
+ callback(success(void 0));
3276
+ };
3277
+ run(effect, dbInterpreter(tx), (_result) => {
3278
+ });
3279
+ }
3280
+ };
3281
+ function setupInterpreter(db, tx) {
3282
+ return (op, onComplete) => {
3283
+ switch (op._type) {
3284
+ case "CreateObjectStore": {
3285
+ db.createObjectStore(op.objectStore, objectStoreKeyOptions(op.keyPath));
3286
+ onComplete(void 0);
3287
+ return;
3288
+ }
3289
+ case "CreateIndex": {
3290
+ const store = tx.objectStore(op.objectStore);
3291
+ store.createIndex(op.index, indexKeyPathValue(op.keyPath), {
3292
+ unique: op.unique
3293
+ });
3294
+ onComplete(void 0);
3295
+ return;
3296
+ }
3297
+ case "DeleteObjectStore": {
3298
+ db.deleteObjectStore(op.objectStore);
3299
+ onComplete(void 0);
3300
+ return;
3301
+ }
3302
+ case "DeleteIndex": {
3303
+ const store = tx.objectStore(op.objectStore);
3304
+ store.deleteIndex(op.index);
3305
+ onComplete(void 0);
3306
+ return;
3307
+ }
3308
+ case "GetObjectStoreNames": {
3309
+ onComplete(Array.from(db.objectStoreNames));
3310
+ return;
3311
+ }
3312
+ case "GetIndexNames": {
3313
+ const store = tx.objectStore(op.objectStore);
3314
+ onComplete(Array.from(store.indexNames));
3315
+ return;
3316
+ }
3317
+ case "RunDbEffect": {
3318
+ run(
3319
+ op.effect,
3320
+ dbInterpreter(tx),
3321
+ (_result) => onComplete(void 0)
3322
+ );
3323
+ return;
3324
+ }
3325
+ }
3326
+ };
3327
+ }
3328
+ function dbInterpreter(tx) {
3329
+ return (op, onComplete) => {
3330
+ switch (op._type) {
3331
+ case "Add": {
3332
+ const req = tx.objectStore(op.objectStore).add(op.value);
3333
+ req.onsuccess = () => onComplete(void 0);
3334
+ return;
3335
+ }
3336
+ case "Put": {
3337
+ const req = tx.objectStore(op.objectStore).put(op.value);
3338
+ req.onsuccess = () => onComplete(void 0);
3339
+ return;
3340
+ }
3341
+ case "Get": {
3342
+ const req = tx.objectStore(op.objectStore).get(op.key);
3343
+ req.onsuccess = () => onComplete(req.result ?? void 0);
3344
+ return;
3345
+ }
3346
+ case "GetAll": {
3347
+ const req = tx.objectStore(op.objectStore).getAll();
3348
+ req.onsuccess = () => onComplete(req.result);
3349
+ return;
3350
+ }
3351
+ case "Delete": {
3352
+ const req = tx.objectStore(op.objectStore).delete(op.key);
3353
+ req.onsuccess = () => onComplete(void 0);
3354
+ return;
3355
+ }
3356
+ case "Count": {
3357
+ const req = tx.objectStore(op.objectStore).count();
3358
+ req.onsuccess = () => onComplete(req.result);
3359
+ return;
3360
+ }
3361
+ }
3362
+ };
3363
+ }
3364
+ function objectStoreKeyOptions(keyPath) {
3365
+ switch (keyPath._type) {
3366
+ case "AutoIncrement":
3367
+ return { keyPath: keyPath.value, autoIncrement: true };
3368
+ case "Manual":
3369
+ return { keyPath: keyPath.value, autoIncrement: false };
3370
+ case "Multi":
3371
+ return {
3372
+ keyPath: [keyPath.first, keyPath.second, ...keyPath.rest],
3373
+ autoIncrement: false
3374
+ };
3375
+ }
3376
+ }
3377
+ function indexKeyPathValue(keyPath) {
3378
+ switch (keyPath._type) {
3379
+ case "SingleIndex":
3380
+ return keyPath.value;
3381
+ case "MultiIndex":
3382
+ return [keyPath.first, keyPath.second, ...keyPath.rest];
3383
+ }
3384
+ }
3385
+ function extractMessage(event) {
3386
+ const m = event.message;
3387
+ if (typeof m === "string") return m;
3388
+ if (m != null) return `${m}`;
3389
+ return void 0;
3390
+ }
3391
+
3392
+ // src/internal/net.ts
3393
+ function makeHttpRequest(request, callback) {
3394
+ console.log("About to fetch " + request.uri);
3395
+ globalThis.fetch(request.uri, {
3396
+ method: request.method,
3397
+ headers: request.headers,
3398
+ body: request.body
3399
+ }).catch((err) => handleError(err, callback)).then((response) => {
3400
+ if (response) {
3401
+ response.arrayBuffer().catch((err) => handleError(err, callback)).then((ab) => {
3402
+ if (ab) {
3403
+ callback(
3404
+ success({
3405
+ status: response.status,
3406
+ headers: convertHeaders(response.headers),
3407
+ body: new ResponseBody(ab)
3408
+ })
3409
+ );
3410
+ }
3411
+ });
3412
+ }
3413
+ });
3414
+ }
3415
+ function handleError(err, callback) {
3416
+ if (err instanceof DOMException && err.name === "AbortError") {
3417
+ callback(
3418
+ failure({
3419
+ _type: "RequestError",
3420
+ cause: "RequestAborted",
3421
+ message: err.message
3422
+ })
3423
+ );
3424
+ } else if (err instanceof TypeError) {
3425
+ callback(
3426
+ failure({
3427
+ _type: "RequestError",
3428
+ cause: "NetworkFailure",
3429
+ message: err.message
3430
+ })
3431
+ );
3432
+ } else if (err instanceof Error) {
3433
+ callback(
3434
+ failure({
3435
+ _type: "RequestError",
3436
+ cause: "UnexpectedError",
3437
+ message: err.message
3438
+ })
3439
+ );
3440
+ } else {
3441
+ callback(
3442
+ failure({
3443
+ _type: "RequestError",
3444
+ cause: "UnexpectedError",
3445
+ message: `Unexpected request error: ${err}`
3446
+ })
3447
+ );
3448
+ }
3449
+ }
3450
+ function convertHeaders(headers) {
3451
+ const result = {};
3452
+ for (const [key, value] of headers) {
3453
+ result[key] = value;
3454
+ }
3455
+ return result;
3456
+ }
3457
+
3458
+ // src/internal/dominterpreter.ts
3459
+ var dbManager = new DbManager();
3460
+ function domInterpreter(effect, callbacks, taskRegistry) {
3461
+ switch (effect._type) {
3462
+ case "Return": {
3463
+ callbacks.onComplete(effect.value);
3464
+ return;
3465
+ }
3466
+ case "FlatMap": {
3467
+ domInterpreter(
3468
+ effect.effect,
3469
+ {
3470
+ ...callbacks,
3471
+ onComplete(result) {
3472
+ domInterpreter(effect.next(result), callbacks, taskRegistry);
3473
+ }
3474
+ },
3475
+ taskRegistry
3476
+ );
3477
+ return;
3478
+ }
3479
+ case "Suspend": {
3480
+ switch (effect.operation._type) {
3481
+ case "Alert": {
3482
+ globalThis.window.alert(effect.operation.input);
3483
+ callbacks.onComplete(void 0);
3484
+ return;
3485
+ }
3486
+ case "Confirm": {
3487
+ callbacks.onComplete(
3488
+ globalThis.window.confirm(effect.operation.input)
3489
+ );
3490
+ return;
3491
+ }
3492
+ case "Prompt": {
3493
+ callbacks.onComplete(
3494
+ globalThis.window.prompt(
3495
+ effect.operation.input.message,
3496
+ effect.operation.input.default
3497
+ )
3498
+ );
3499
+ return;
3500
+ }
3501
+ case "GetNotificationPermission": {
3502
+ callbacks.onComplete(
3503
+ permission(globalThis.Notification.permission)
3504
+ );
3505
+ return;
3506
+ }
3507
+ case "RequestNotificationPermission": {
3508
+ globalThis.Notification.requestPermission().then((perm) => {
3509
+ callbacks.onComplete(permission(perm));
3510
+ });
3511
+ return;
3512
+ }
3513
+ case "GetLocation": {
3514
+ const loc = globalThis.window.location;
3515
+ callbacks.onComplete(
3516
+ parseInternalLocation(loc.pathname + loc.search + loc.hash)
3517
+ );
3518
+ return;
3519
+ }
3520
+ case "Async": {
3521
+ const { effect: innerEffect, delayInMillis } = effect.operation.input;
3522
+ setTimeout(() => {
3523
+ domInterpreter(
3524
+ innerEffect,
3525
+ { ...callbacks, onComplete() {
3526
+ } },
3527
+ taskRegistry
3528
+ );
3529
+ }, delayInMillis);
3530
+ callbacks.onComplete(void 0);
3531
+ return;
3532
+ }
3533
+ case "CancelTask": {
3534
+ taskRegistry.cancel(effect.operation.input);
3535
+ callbacks.onComplete(void 0);
3536
+ return;
3537
+ }
3538
+ case "ClearAppBadge": {
3539
+ if (globalThis.navigator.clearAppBadge) {
3540
+ globalThis.navigator.clearAppBadge();
3541
+ }
3542
+ callbacks.onComplete(void 0);
3543
+ return;
3544
+ }
3545
+ case "SetAppBadge": {
3546
+ if (globalThis.navigator.setAppBadge) {
3547
+ globalThis.navigator.setAppBadge(effect.operation.input);
3548
+ }
3549
+ callbacks.onComplete(void 0);
3550
+ return;
3551
+ }
3552
+ case "SetDocumentTitle": {
3553
+ globalThis.document.title = effect.operation.input;
3554
+ callbacks.onComplete(void 0);
3555
+ return;
3556
+ }
3557
+ case "FireEvent": {
3558
+ callbacks.onFireEvent(effect.operation.input);
3559
+ callbacks.onComplete(void 0);
3560
+ return;
3561
+ }
3562
+ case "GetState": {
3563
+ callbacks.onComplete(callbacks.getState());
3564
+ return;
3565
+ }
3566
+ case "GetTime": {
3567
+ callbacks.onComplete(/* @__PURE__ */ new Date());
3568
+ return;
3569
+ }
3570
+ case "GetRandom": {
3571
+ callbacks.onComplete(Math.random());
3572
+ return;
3573
+ }
3574
+ case "MakeHttpRequest": {
3575
+ makeHttpRequest(
3576
+ effect.operation.input,
3577
+ (result) => callbacks.onComplete(result)
3578
+ );
3579
+ return;
3580
+ }
3581
+ case "Log": {
3582
+ console.log(effect.operation.input);
3583
+ callbacks.onComplete(void 0);
3584
+ return;
3585
+ }
3586
+ case "Product": {
3587
+ let resultA = "__ctrl_fx_unset_result";
3588
+ let resultB = "__ctrl_fx_unset_result";
3589
+ domInterpreter(
3590
+ effect.operation.input[0],
3591
+ {
3592
+ ...callbacks,
3593
+ onComplete(a) {
3594
+ resultA = a;
3595
+ if (resultB !== "__ctrl_fx_unset_result") {
3596
+ callbacks.onComplete([resultA, resultB]);
3597
+ }
3598
+ }
3599
+ },
3600
+ taskRegistry
3601
+ );
3602
+ domInterpreter(
3603
+ effect.operation.input[1],
3604
+ {
3605
+ ...callbacks,
3606
+ onComplete(b) {
3607
+ resultB = b;
3608
+ if (resultA !== "__ctrl_fx_unset_result") {
3609
+ callbacks.onComplete([resultA, resultB]);
3610
+ }
3611
+ }
3612
+ },
3613
+ taskRegistry
3614
+ );
3615
+ return;
3616
+ }
3617
+ case "ScheduleTask": {
3618
+ let tskId;
3619
+ if (effect.operation.input.taskId) {
3620
+ taskRegistry.cancel(effect.operation.input.taskId);
3621
+ tskId = effect.operation.input.taskId;
3622
+ }
3623
+ if (effect.operation.input.repeatIntervalInMillis === void 0) {
3624
+ const timerId = globalThis.setTimeout(() => {
3625
+ domInterpreter(
3626
+ effect.operation.input.task,
3627
+ { ...callbacks, onComplete() {
3628
+ } },
3629
+ taskRegistry
3630
+ );
3631
+ }, effect.operation.input.initialDelayInMillis);
3632
+ tskId ||= taskId(generateUuid());
3633
+ taskRegistry.set(tskId, () => clearTimeout(timerId));
3634
+ } else if (effect.operation.input.initialDelayInMillis === effect.operation.input.repeatIntervalInMillis) {
3635
+ const timerId = globalThis.setInterval(() => {
3636
+ domInterpreter(
3637
+ effect.operation.input.task,
3638
+ { ...callbacks, onComplete() {
3639
+ } },
3640
+ taskRegistry
3641
+ );
3642
+ }, effect.operation.input.repeatIntervalInMillis);
3643
+ tskId ||= taskId(generateUuid());
3644
+ taskRegistry.set(tskId, () => clearInterval(timerId));
3645
+ } else {
3646
+ tskId ||= taskId(generateUuid());
3647
+ const timerId = globalThis.setTimeout(() => {
3648
+ domInterpreter(
3649
+ effect.operation.input.task,
3650
+ {
3651
+ ...callbacks,
3652
+ onComplete() {
3653
+ const newTimerId = globalThis.setInterval(() => {
3654
+ domInterpreter(
3655
+ effect.operation.input.task,
3656
+ { ...callbacks, onComplete() {
3657
+ } },
3658
+ taskRegistry
3659
+ );
3660
+ }, effect.operation.input.repeatIntervalInMillis);
3661
+ taskRegistry.set(tskId, () => clearInterval(newTimerId));
3662
+ }
3663
+ },
3664
+ taskRegistry
3665
+ );
3666
+ }, effect.operation.input.initialDelayInMillis);
3667
+ taskRegistry.set(tskId, () => clearTimeout(timerId));
3668
+ }
3669
+ callbacks.onComplete(tskId);
3670
+ return;
3671
+ }
3672
+ case "SetTimeout": {
3673
+ setTimeout(() => {
3674
+ callbacks.onComplete(void 0);
3675
+ }, effect.operation.input);
3676
+ return;
3677
+ }
3678
+ case "PushState": {
3679
+ const url = formatLocation(effect.operation.input);
3680
+ history.pushState(null, "", url);
3681
+ window.dispatchEvent(new Event("locationchange"));
3682
+ callbacks.onComplete(void 0);
3683
+ return;
3684
+ }
3685
+ case "ReplaceState": {
3686
+ const url = formatLocation(effect.operation.input);
3687
+ history.replaceState(null, "", url);
3688
+ window.dispatchEvent(new Event("locationchange"));
3689
+ callbacks.onComplete(void 0);
3690
+ return;
3691
+ }
3692
+ case "Go": {
3693
+ history.go(effect.operation.input);
3694
+ callbacks.onComplete(void 0);
3695
+ return;
3696
+ }
3697
+ case "GetStorageItem": {
3698
+ const store = effect.operation.input.storage === "local" ? localStorage : sessionStorage;
3699
+ callbacks.onComplete(store.getItem(effect.operation.input.key));
3700
+ return;
3701
+ }
3702
+ case "SetStorageItem": {
3703
+ const store = effect.operation.input.storage === "local" ? localStorage : sessionStorage;
3704
+ store.setItem(
3705
+ effect.operation.input.key,
3706
+ effect.operation.input.value
3707
+ );
3708
+ callbacks.onComplete(void 0);
3709
+ return;
3710
+ }
3711
+ case "RemoveStorageItem": {
3712
+ const store = effect.operation.input.storage === "local" ? localStorage : sessionStorage;
3713
+ store.removeItem(effect.operation.input.key);
3714
+ callbacks.onComplete(void 0);
3715
+ return;
3716
+ }
3717
+ case "ClearStorage": {
3718
+ const store = effect.operation.input.storage === "local" ? localStorage : sessionStorage;
3719
+ store.clear();
3720
+ callbacks.onComplete(void 0);
3721
+ return;
3722
+ }
3723
+ case "OpenDatabase": {
3724
+ const { db, version, setup } = effect.operation.input;
3725
+ dbManager.open(db, version, setup, (result) => {
3726
+ callbacks.onComplete(result);
3727
+ });
3728
+ return;
3729
+ }
3730
+ case "RunDbTransaction": {
3731
+ const {
3732
+ db,
3733
+ objectStores,
3734
+ mode,
3735
+ effect: dbEffect
3736
+ } = effect.operation.input;
3737
+ dbManager.runTransaction(db, objectStores, mode, dbEffect, (result) => {
3738
+ callbacks.onComplete(result);
3739
+ });
3740
+ return;
3741
+ }
3742
+ case "UpdateState": {
3743
+ callbacks.setState(effect.operation.input(callbacks.getState()));
3744
+ callbacks.onComplete(void 0);
3745
+ return;
3746
+ }
3747
+ case "GenerateUuid": {
3748
+ callbacks.onComplete(generateUuid());
3749
+ return;
3750
+ }
3751
+ case "ReadClipboard": {
3752
+ readClipboard((result) => callbacks.onComplete(result));
3753
+ return;
3754
+ }
3755
+ case "WriteClipboard": {
3756
+ writeClipboard(
3757
+ effect.operation.input,
3758
+ (result) => callbacks.onComplete(result)
3759
+ );
3760
+ return;
3761
+ }
3762
+ case "Download": {
3763
+ const { filename, content, contentType } = effect.operation.input;
3764
+ const blob = new Blob([content], { type: contentType });
3765
+ const url = URL.createObjectURL(blob);
3766
+ const a = globalThis.document.createElement("a");
3767
+ a.href = url;
3768
+ a.download = filename;
3769
+ globalThis.document.body.appendChild(a);
3770
+ a.click();
3771
+ globalThis.document.body.removeChild(a);
3772
+ URL.revokeObjectURL(url);
3773
+ callbacks.onComplete(void 0);
3774
+ return;
3775
+ }
3776
+ case "PostBroadcastMessage": {
3777
+ const bc = new BroadcastChannel(effect.operation.input.channel);
3778
+ bc.postMessage(effect.operation.input.message);
3779
+ bc.close();
3780
+ callbacks.onComplete(void 0);
3781
+ return;
3782
+ }
3783
+ case "SubscribeToBroadcastChannel": {
3784
+ const { channel, handler, taskId: tskIdInput } = effect.operation.input;
3785
+ const tskId = tskIdInput ?? taskId(generateUuid());
3786
+ const bc = new BroadcastChannel(channel);
3787
+ bc.onmessage = (event) => {
3788
+ domInterpreter(
3789
+ handler(event.data),
3790
+ { ...callbacks, onComplete() {
3791
+ } },
3792
+ taskRegistry
3793
+ );
3794
+ };
3795
+ taskRegistry.set(tskId, () => bc.close());
3796
+ callbacks.onComplete(tskId);
3797
+ return;
3798
+ }
3799
+ case "ScrollWindow": {
3800
+ globalThis.window.scrollTo(effect.operation.input);
3801
+ callbacks.onComplete(void 0);
3802
+ return;
3803
+ }
3804
+ case "ScrollElement": {
3805
+ const { selector, options } = effect.operation.input;
3806
+ const el = globalThis.document.querySelector(selector);
3807
+ if (el === null) {
3808
+ callbacks.onComplete(failure(scrollElementError(selector)));
3809
+ } else {
3810
+ el.scrollTo(options);
3811
+ callbacks.onComplete(success(void 0));
3812
+ }
3813
+ return;
3814
+ }
3815
+ case "CustomEffect": {
3816
+ const { args, f } = effect.operation.input;
3817
+ const result = f(args);
3818
+ if (result instanceof Promise) {
3819
+ result.then((out) => callbacks.onComplete(out));
3820
+ } else {
3821
+ callbacks.onComplete(result);
3822
+ }
3823
+ return;
3824
+ }
3825
+ default:
3826
+ exhaustivenessCheck(effect.operation);
3827
+ }
3828
+ break;
3829
+ }
3830
+ default:
3831
+ exhaustivenessCheck(effect);
3832
+ }
3833
+ }
3834
+
3835
+ // src/index.ts
3836
+ function isState(effectOrState) {
3837
+ return !isEffect(effectOrState);
3838
+ }
3839
+ function toComponent(app, initialState) {
3840
+ const { pure: pure2 } = makeEffects();
3841
+ const initialStateEffect = isState(initialState) ? pure2(initialState) : initialState;
3842
+ const adjustedApp = (state) => {
3843
+ const nodeOrNodeGroup = app(state);
3844
+ if (isNodeGroup(nodeOrNodeGroup)) {
3845
+ return nodeOrNodeGroup;
3846
+ } else {
3847
+ return nodeGroup(nodeOrNodeGroup);
3848
+ }
3849
+ };
3850
+ return {
3851
+ _type: "Component",
3852
+ params: void 0,
3853
+ view: adjustedApp,
3854
+ initialState() {
3855
+ return initialStateEffect;
3856
+ }
3857
+ };
3858
+ }
3859
+ function manageApplication(app, initialState, mountId = "app") {
3860
+ const component = toComponent(app, initialState);
3861
+ return manageComponent(
3862
+ component,
3863
+ mountId,
3864
+ globalThis.window,
3865
+ domInterpreter,
3866
+ realStyleRegistry()
3867
+ );
3868
+ }
3869
+ function permission(value) {
3870
+ return {
3871
+ value,
3872
+ isGranted() {
3873
+ return value === "granted";
3874
+ },
3875
+ isDefault() {
3876
+ return value === "default";
3877
+ },
3878
+ isDenied() {
3879
+ return value === "denied";
3880
+ }
3881
+ };
3882
+ }
3883
+ function uuidFromString(input) {
3884
+ let hash = 0;
3885
+ for (let i = 0; i < input.length; i++) {
3886
+ hash = (hash << 5) - hash + input.charCodeAt(i) | 0;
3887
+ }
3888
+ const hex = (hash >>> 0).toString(16).padStart(8, "0");
3889
+ const repeated = hex.repeat(4);
3890
+ return [
3891
+ repeated.slice(0, 8),
3892
+ repeated.slice(8, 12),
3893
+ repeated.slice(12, 16),
3894
+ repeated.slice(16, 20),
3895
+ repeated.slice(20, 32)
3896
+ ].join("-");
3897
+ }
3898
+
3899
+ // src/testing.ts
3900
+ var clearAppBadge = {
3901
+ _type: "ClearAppBadge"
3902
+ };
3903
+ function setAppBadge(count) {
3904
+ return { _type: "SetAppBadge", count };
3905
+ }
3906
+ function appBadgeCount(operations) {
3907
+ if (operations.length > 0) {
3908
+ const op = operations[operations.length - 1];
3909
+ switch (op._type) {
3910
+ case "ClearAppBadge":
3911
+ return 0;
3912
+ case "SetAppBadge":
3913
+ return op.count;
3914
+ }
3915
+ } else {
3916
+ return 0;
3917
+ }
3918
+ }
3919
+ function defaultTestConfig() {
3920
+ return {
3921
+ http: {
3922
+ handler: (_request, _prior) => {
3923
+ throw new Error("No HTTP handler configured in TestConfig.");
3924
+ }
3925
+ },
3926
+ clock: {
3927
+ values: []
3928
+ },
3929
+ random: {
3930
+ values: []
3931
+ }
3932
+ };
3933
+ }
3934
+ function newTestData(custom) {
3935
+ return {
3936
+ window: {
3937
+ alerts: [],
3938
+ confirms: [],
3939
+ confirmResponses: [],
3940
+ prompts: [],
3941
+ promptResponses: []
3942
+ },
3943
+ navigator: {
3944
+ appBadgeOperations: []
3945
+ },
3946
+ clipboard: {
3947
+ content: "",
3948
+ writes: []
3949
+ },
3950
+ document: {
3951
+ title: null
3952
+ },
3953
+ http: {
3954
+ interactions: []
3955
+ },
3956
+ clock: {
3957
+ calls: []
3958
+ },
3959
+ random: {
3960
+ calls: []
3961
+ },
3962
+ console: {
3963
+ logs: []
3964
+ },
3965
+ db: new TestDatabaseData(/* @__PURE__ */ new Map()),
3966
+ storage: {
3967
+ local: /* @__PURE__ */ new Map(),
3968
+ session: /* @__PURE__ */ new Map()
3969
+ },
3970
+ navigation: {
3971
+ locationHistory: []
3972
+ },
3973
+ scroll: {
3974
+ windowScrolls: [],
3975
+ elementScrolls: []
3976
+ },
3977
+ downloads: [],
3978
+ broadcasts: {
3979
+ sent: []
3980
+ },
3981
+ custom: custom ?? {}
3982
+ };
3983
+ }
3984
+ function click(selector) {
3985
+ return {
3986
+ type: "Click",
3987
+ selector
3988
+ };
3989
+ }
3990
+ function advanceTime(milliseconds) {
3991
+ return {
3992
+ _type: "AdvanceTime",
3993
+ milliseconds
3994
+ };
3995
+ }
3996
+ function receiveBroadcast(channel, message) {
3997
+ return { _type: "ReceiveBroadcast", channel, message };
3998
+ }
3999
+ function fireCustomEvent(selector, eventName, detail) {
4000
+ return { _type: "FireCustomEvent", selector, eventName, detail };
4001
+ }
4002
+ function submit(selector) {
4003
+ return { _type: "Submit", selector };
4004
+ }
4005
+ function textInput(selector, value) {
4006
+ return { _type: "TextInput", selector, value };
4007
+ }
4008
+ function attr(name, value) {
4009
+ return { name, value };
4010
+ }
4011
+ function testComponent2(component, config, data) {
4012
+ return testComponent(component, config, data);
4013
+ }
4014
+ function testApplication(app, initialState, config, data) {
4015
+ const component = toComponent(app, initialState);
4016
+ return testComponent2(component, config, data);
4017
+ }
4018
+ function one(selector) {
4019
+ return {
4020
+ _type: "OneOrMore",
4021
+ selector
4022
+ };
4023
+ }
4024
+
4025
+ export {
4026
+ TestDatabaseData,
4027
+ clearAppBadge,
4028
+ setAppBadge,
4029
+ appBadgeCount,
4030
+ defaultTestConfig,
4031
+ newTestData,
4032
+ click,
4033
+ advanceTime,
4034
+ receiveBroadcast,
4035
+ fireCustomEvent,
4036
+ submit,
4037
+ textInput,
4038
+ attr,
4039
+ testComponent2 as testComponent,
4040
+ testApplication,
4041
+ one,
4042
+ ComponentManager,
4043
+ realStyleRegistry,
4044
+ domInterpreter,
4045
+ isState,
4046
+ toComponent,
4047
+ manageApplication,
4048
+ permission,
4049
+ uuidFromString
4050
+ };