@muze-labs/simplyflow 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3780 @@
1
+ (() => {
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, { get: all[name], enumerable: true });
6
+ };
7
+
8
+ // src/state.mjs
9
+ var state_exports = {};
10
+ __export(state_exports, {
11
+ addTracer: () => addTracer,
12
+ batch: () => batch,
13
+ clockEffect: () => clockEffect,
14
+ clone: () => clone,
15
+ createSignal: () => createSignal,
16
+ destroy: () => destroy,
17
+ effect: () => effect,
18
+ getSignal: () => getSignal,
19
+ isSignal: () => isSignal,
20
+ makeContext: () => makeContext,
21
+ notifyGet: () => notifyGet,
22
+ notifySet: () => notifySet,
23
+ raw: () => raw,
24
+ registerSignal: () => registerSignal,
25
+ signal: () => signal,
26
+ signals: () => signals,
27
+ throttledEffect: () => throttledEffect,
28
+ trace: () => trace,
29
+ untracked: () => untracked
30
+ });
31
+
32
+ // src/symbols.mjs
33
+ var DEP = {
34
+ ITERATE: Symbol.for("@simplyedit/simplyflow.iterate"),
35
+ XRAY: Symbol.for("@simplyedit/simplyflow.xRay"),
36
+ SIGNAL: Symbol.for("@simplyedit/simplyflow.Signal"),
37
+ TEMPLATE: Symbol.for("@simplyedit/simplyflow.bindTemplate"),
38
+ VALUE: Symbol.for("@simplyedit/simplyflow.bindValue"),
39
+ LENGTH: "length",
40
+ SIZE: "size"
41
+ };
42
+
43
+ // src/state.mjs
44
+ var MAP_READS_KEY = /* @__PURE__ */ new Set(["get", "has"]);
45
+ var MAP_READS_ITERATION = /* @__PURE__ */ new Set(["keys", "values", "entries", "forEach", Symbol.iterator]);
46
+ var MAP_WRITES = /* @__PURE__ */ new Set(["set", "delete", "clear"]);
47
+ var SET_WRITES = /* @__PURE__ */ new Set(["add", "delete", "clear"]);
48
+ var SET_ITERATION_PROPERTIES = {
49
+ entries: {},
50
+ forEach: {},
51
+ has: {},
52
+ keys: {},
53
+ values: {},
54
+ [Symbol.iterator]: {}
55
+ };
56
+ function isObjectLike(value) {
57
+ return value !== null && (typeof value === "object" || typeof value === "function");
58
+ }
59
+ function isSignal(value) {
60
+ return Boolean(isObjectLike(value) && value[DEP.SIGNAL]);
61
+ }
62
+ function raw(value) {
63
+ return isSignal(value) ? value[DEP.XRAY] : value;
64
+ }
65
+ function getSignal(value) {
66
+ return isSignal(value) ? value : signals.get(value);
67
+ }
68
+ function targetSignal(target) {
69
+ return signals.get(target);
70
+ }
71
+ function readTarget(target, property) {
72
+ return target?.[property];
73
+ }
74
+ function bindMethod(target, receiver, value) {
75
+ if (target instanceof HTMLElement || target instanceof Number || target instanceof String || target instanceof Boolean) {
76
+ return value.bind(target);
77
+ }
78
+ return value.bind(receiver);
79
+ }
80
+ function collectRemovedArrayValues(target, nextLength) {
81
+ const values = /* @__PURE__ */ new Map();
82
+ if (!Array.isArray(target) || nextLength >= target.length) {
83
+ return values;
84
+ }
85
+ for (let index = nextLength; index < target.length; index++) {
86
+ if (Object.hasOwn(target, index)) {
87
+ values.set(index, target[index]);
88
+ }
89
+ }
90
+ return values;
91
+ }
92
+ function addArrayLengthChanges(context, target, oldLength, removedValues = /* @__PURE__ */ new Map()) {
93
+ if (!Array.isArray(target) || oldLength === target.length) {
94
+ return;
95
+ }
96
+ context.set(DEP.LENGTH, { was: oldLength, now: target.length });
97
+ context.set(DEP.ITERATE, {});
98
+ for (const [index, oldValue] of removedValues) {
99
+ context.set(String(index), { delete: true, was: oldValue, now: void 0 });
100
+ }
101
+ }
102
+ function notifyContext(receiver, context) {
103
+ if (context.size) {
104
+ notifySet(receiver, context);
105
+ }
106
+ }
107
+ function wrapArrayMethod(target, property, receiver, value) {
108
+ return (...args) => {
109
+ const oldLength = target.length;
110
+ const result = value.apply(receiver, args);
111
+ if (oldLength !== target.length) {
112
+ notifySet(receiver, makeContext(DEP.LENGTH, { was: oldLength, now: target.length }));
113
+ }
114
+ return result;
115
+ };
116
+ }
117
+ function addMapWriteChanges(context, target, property, args, oldSize) {
118
+ if (property === "set") {
119
+ const [key, nextValue] = args;
120
+ const hadKey = target.has(key);
121
+ const oldValue = target.get(key);
122
+ return () => {
123
+ if (!hadKey || !Object.is(oldValue, nextValue)) {
124
+ context.set(key, { was: oldValue, now: nextValue });
125
+ context.set(DEP.ITERATE, {});
126
+ }
127
+ if (!hadKey) {
128
+ context.set(DEP.SIZE, { was: oldSize, now: target.size });
129
+ }
130
+ };
131
+ }
132
+ if (property === "delete") {
133
+ const [key] = args;
134
+ const hadKey = target.has(key);
135
+ const oldValue = target.get(key);
136
+ return () => {
137
+ if (hadKey) {
138
+ context.set(key, { delete: true, was: oldValue, now: void 0 });
139
+ context.set(DEP.SIZE, { was: oldSize, now: target.size });
140
+ context.set(DEP.ITERATE, {});
141
+ }
142
+ };
143
+ }
144
+ if (property === "clear") {
145
+ const oldEntries = oldSize ? Array.from(target.entries()) : [];
146
+ return () => {
147
+ if (oldEntries.length) {
148
+ for (const [key, oldValue] of oldEntries) {
149
+ context.set(key, { delete: true, was: oldValue, now: void 0 });
150
+ }
151
+ context.set(DEP.SIZE, { was: oldSize, now: target.size });
152
+ context.set(DEP.ITERATE, {});
153
+ }
154
+ };
155
+ }
156
+ return () => {
157
+ };
158
+ }
159
+ function wrapMapMethod(target, property, receiver, value) {
160
+ return (...args) => {
161
+ if (MAP_READS_KEY.has(property)) {
162
+ notifyGet(receiver, args[0]);
163
+ }
164
+ if (MAP_READS_ITERATION.has(property)) {
165
+ notifyGet(receiver, DEP.ITERATE);
166
+ }
167
+ const oldSize = target.size;
168
+ const context = /* @__PURE__ */ new Map();
169
+ const addChanges = MAP_WRITES.has(property) ? addMapWriteChanges(context, target, property, args, oldSize) : () => {
170
+ };
171
+ const result = value.apply(target, args);
172
+ addChanges();
173
+ notifyContext(receiver, context);
174
+ return result;
175
+ };
176
+ }
177
+ function addSetWriteChanges(context, target, property, args, oldSize) {
178
+ const [value] = args;
179
+ const hadValue = property === "add" || property === "delete" ? target.has(value) : false;
180
+ return () => {
181
+ const changed = property === "clear" ? oldSize > 0 : target.size !== oldSize || property === "delete" && hadValue;
182
+ if (!changed) {
183
+ return;
184
+ }
185
+ context.set(DEP.SIZE, { was: oldSize, now: target.size });
186
+ for (const prop of Reflect.ownKeys(SET_ITERATION_PROPERTIES)) {
187
+ context.set(prop, {});
188
+ }
189
+ };
190
+ }
191
+ function wrapSetMethod(target, property, receiver, value) {
192
+ return (...args) => {
193
+ const oldSize = target.size;
194
+ const context = /* @__PURE__ */ new Map();
195
+ const addChanges = SET_WRITES.has(property) ? addSetWriteChanges(context, target, property, args, oldSize) : () => {
196
+ };
197
+ const result = value.apply(target, args);
198
+ addChanges();
199
+ notifyContext(receiver, context);
200
+ return result;
201
+ };
202
+ }
203
+ function propertyValueChanged(descriptor, oldDescriptor, oldValue, newDescriptor, newValue) {
204
+ return Object.hasOwn(descriptor, "value") && !Object.is(oldValue, newValue) || Object.hasOwn(descriptor, "get") && oldDescriptor?.get !== newDescriptor?.get || Object.hasOwn(descriptor, "set") && oldDescriptor?.set !== newDescriptor?.set;
205
+ }
206
+ var signalHandler = {
207
+ get(target, property, receiver) {
208
+ const value = readTarget(target, property);
209
+ notifyGet(receiver, property);
210
+ if (typeof value === "function") {
211
+ if (Array.isArray(target)) {
212
+ return wrapArrayMethod(target, property, receiver, value);
213
+ }
214
+ if (target instanceof Map) {
215
+ return wrapMapMethod(target, property, receiver, value);
216
+ }
217
+ if (target instanceof Set) {
218
+ return wrapSetMethod(target, property, receiver, value);
219
+ }
220
+ return bindMethod(target, receiver, value);
221
+ }
222
+ return isObjectLike(value) ? signal(value) : value;
223
+ },
224
+ set(target, property, value, receiver) {
225
+ const hadOwn = Object.hasOwn(target, property);
226
+ const oldLength = Array.isArray(target) ? target.length : void 0;
227
+ const removedValues = property === DEP.LENGTH ? collectRemovedArrayValues(target, Number(value)) : /* @__PURE__ */ new Map();
228
+ const oldValue = target[property];
229
+ target[property] = value;
230
+ const hasOwn = Object.hasOwn(target, property);
231
+ const newValue = target[property];
232
+ const context = /* @__PURE__ */ new Map();
233
+ if (!Object.is(oldValue, newValue) || !hadOwn && hasOwn) {
234
+ context.set(property, { was: oldValue, now: newValue });
235
+ }
236
+ if (!hadOwn && hasOwn) {
237
+ context.set(DEP.ITERATE, {});
238
+ }
239
+ addArrayLengthChanges(context, target, oldLength, removedValues);
240
+ notifyContext(receiver, context);
241
+ return true;
242
+ },
243
+ has(target, property) {
244
+ const receiver = targetSignal(target);
245
+ if (receiver) {
246
+ notifyGet(receiver, property);
247
+ }
248
+ return Reflect.has(target, property);
249
+ },
250
+ deleteProperty(target, property) {
251
+ const hadOwn = Object.hasOwn(target, property);
252
+ if (!hadOwn) {
253
+ return true;
254
+ }
255
+ const oldValue = target[property];
256
+ const oldLength = Array.isArray(target) ? target.length : void 0;
257
+ const result = Reflect.deleteProperty(target, property);
258
+ if (!result) {
259
+ return result;
260
+ }
261
+ const receiver = targetSignal(target);
262
+ const context = makeContext(property, { delete: true, was: oldValue, now: void 0 });
263
+ context.set(DEP.ITERATE, { delete: true, property });
264
+ addArrayLengthChanges(context, target, oldLength);
265
+ notifySet(receiver, context);
266
+ return result;
267
+ },
268
+ defineProperty(target, property, descriptor) {
269
+ const hadOwn = Object.hasOwn(target, property);
270
+ const oldDescriptor = Object.getOwnPropertyDescriptor(target, property);
271
+ const oldValue = target[property];
272
+ const oldLength = Array.isArray(target) ? target.length : void 0;
273
+ const removedValues = property === DEP.LENGTH && Object.hasOwn(descriptor, "value") ? collectRemovedArrayValues(target, Number(descriptor.value)) : /* @__PURE__ */ new Map();
274
+ const result = Reflect.defineProperty(target, property, descriptor);
275
+ if (!result) {
276
+ return result;
277
+ }
278
+ const hasOwn = Object.hasOwn(target, property);
279
+ const newDescriptor = Object.getOwnPropertyDescriptor(target, property);
280
+ const newValue = target[property];
281
+ const context = /* @__PURE__ */ new Map();
282
+ if (!hadOwn && hasOwn) {
283
+ context.set(property, { was: oldValue, now: newValue });
284
+ context.set(DEP.ITERATE, {});
285
+ } else if (hadOwn && hasOwn) {
286
+ if (propertyValueChanged(descriptor, oldDescriptor, oldValue, newDescriptor, newValue)) {
287
+ context.set(property, { was: oldValue, now: newValue });
288
+ }
289
+ if (oldDescriptor?.enumerable !== newDescriptor?.enumerable) {
290
+ context.set(DEP.ITERATE, {});
291
+ }
292
+ }
293
+ addArrayLengthChanges(context, target, oldLength, removedValues);
294
+ notifyContext(targetSignal(target), context);
295
+ return result;
296
+ },
297
+ ownKeys(target) {
298
+ const receiver = targetSignal(target);
299
+ notifyGet(receiver, DEP.ITERATE);
300
+ return Reflect.ownKeys(target);
301
+ }
302
+ };
303
+ var signals = /* @__PURE__ */ new WeakMap();
304
+ function assertSignalTarget(value, name) {
305
+ if (!isObjectLike(value)) {
306
+ throw new TypeError(
307
+ `simplyflow/state: ${name}() expects an object, array, Map, Set, class instance, function, or DOM node; received ${typeof value}`
308
+ );
309
+ }
310
+ }
311
+ function assertProxyHandler(handler, name) {
312
+ if (!handler || typeof handler !== "object") {
313
+ throw new TypeError(`simplyflow/state: ${name}() expects a Proxy handler object`);
314
+ }
315
+ }
316
+ function signalProxyHandler(handler) {
317
+ return {
318
+ ...handler,
319
+ get(target, property, receiver) {
320
+ if (property === DEP.XRAY) {
321
+ return target;
322
+ }
323
+ if (property === DEP.SIGNAL) {
324
+ return true;
325
+ }
326
+ if (handler.get) {
327
+ return handler.get(target, property, receiver);
328
+ }
329
+ return readTarget(target, property);
330
+ }
331
+ };
332
+ }
333
+ function registerSignal(target, proxy) {
334
+ const rawTarget = raw(target);
335
+ assertSignalTarget(rawTarget, "registerSignal");
336
+ if (!isSignal(proxy)) {
337
+ throw new TypeError("simplyflow/state: registerSignal() expects a signal proxy");
338
+ }
339
+ const existing = signals.get(rawTarget);
340
+ if (existing && existing !== proxy) {
341
+ throw new Error("simplyflow/state: registerSignal() target already has a different signal");
342
+ }
343
+ signals.set(rawTarget, proxy);
344
+ return proxy;
345
+ }
346
+ function createSignal(target, handler = {}, init) {
347
+ assertSignalTarget(target, "createSignal");
348
+ assertProxyHandler(handler, "createSignal");
349
+ if (init !== void 0 && typeof init !== "function") {
350
+ throw new TypeError("simplyflow/state: createSignal() expects init to be a function");
351
+ }
352
+ if (isSignal(target)) {
353
+ return target;
354
+ }
355
+ const existing = getSignal(target);
356
+ if (existing) {
357
+ return existing;
358
+ }
359
+ const proxy = new Proxy(target, signalProxyHandler(handler));
360
+ registerSignal(target, proxy);
361
+ init?.(target, proxy);
362
+ return proxy;
363
+ }
364
+ function signal(value = {}) {
365
+ if (!isObjectLike(value)) {
366
+ throw new TypeError(
367
+ `simplyflow/state: signal() expects an object, array, Map, Set, class instance, or function; received ${typeof value}`
368
+ );
369
+ }
370
+ return createSignal(value, signalHandler);
371
+ }
372
+ var tracers = [];
373
+ var tracing = false;
374
+ function trace(target, prop) {
375
+ if (typeof target === "function") {
376
+ tracing = true;
377
+ try {
378
+ return target();
379
+ } finally {
380
+ tracing = false;
381
+ }
382
+ }
383
+ if (!isSignal(target)) {
384
+ throw new TypeError("simplyflow/state: trace() expects either a function or a signal");
385
+ }
386
+ return getListeners(target, prop).map((listener) => ({
387
+ effect: listener.effectType,
388
+ fn: listener.effectFunction,
389
+ signal: signals.get(listener.effectFunction)
390
+ }));
391
+ }
392
+ function addTracer(tracer) {
393
+ if (!tracer || typeof tracer !== "object") {
394
+ throw new TypeError("simplyflow/state: addTracer() expects a tracer object");
395
+ }
396
+ if (!tracer.get && !tracer.set) {
397
+ throw new Error('simplyflow/state: addTracer: missing "get" or "set" property in tracer');
398
+ }
399
+ if (tracer.get && typeof tracer.get !== "function") {
400
+ throw new Error('simplyflow/state: addTracer: "get" is not a function');
401
+ }
402
+ if (tracer.set && typeof tracer.set !== "function") {
403
+ throw new Error('simplyflow/state: addTracer: "set" is not a function');
404
+ }
405
+ tracers.push(tracer);
406
+ }
407
+ function callTracers(kind, ...params) {
408
+ for (const tracer of tracers) {
409
+ tracer[kind]?.(...params);
410
+ }
411
+ }
412
+ var batchedListeners = /* @__PURE__ */ new Set();
413
+ var batchDepth = 0;
414
+ function notifySet(self, context = /* @__PURE__ */ new Map()) {
415
+ if (!isSignal(self)) {
416
+ throw new TypeError("simplyflow/state: notifySet() expects a signal as first argument");
417
+ }
418
+ if (!(context instanceof Map)) {
419
+ throw new TypeError("simplyflow/state: notifySet() expects context to be a Map; use makeContext()");
420
+ }
421
+ const listeners = /* @__PURE__ */ new Set();
422
+ context.forEach((change, property) => {
423
+ for (const listener of listenersFor(self, property)) {
424
+ addContextChange(listener, property, change);
425
+ listeners.add(listener);
426
+ }
427
+ });
428
+ if (!listeners.size) {
429
+ return;
430
+ }
431
+ if (batchDepth) {
432
+ for (const listener of listeners) {
433
+ batchedListeners.add(listener);
434
+ }
435
+ return;
436
+ }
437
+ runListeners(listeners, self, context);
438
+ }
439
+ function makeContext(property, change) {
440
+ const context = /* @__PURE__ */ new Map();
441
+ if (property instanceof Map) {
442
+ property.forEach((change2, prop) => context.set(prop, change2));
443
+ return context;
444
+ }
445
+ if (property !== null && typeof property === "object") {
446
+ for (const prop of Reflect.ownKeys(property)) {
447
+ context.set(prop, property[prop]);
448
+ }
449
+ } else {
450
+ context.set(property, change);
451
+ }
452
+ return context;
453
+ }
454
+ function addContextChange(listener, property, change) {
455
+ if (!listener.context) {
456
+ listener.context = /* @__PURE__ */ new Map();
457
+ }
458
+ listener.context.set(property, change);
459
+ listener.needsUpdate = true;
460
+ }
461
+ function clearContext(listener) {
462
+ delete listener.context;
463
+ delete listener.needsUpdate;
464
+ }
465
+ function notifyGet(self, property) {
466
+ const currentCompute = computeStack[computeStack.length - 1];
467
+ if (!currentCompute || currentCompute.skipDependency?.(self, property)) {
468
+ return;
469
+ }
470
+ if (tracing && tracers.length) {
471
+ callTracers("get", self, property);
472
+ }
473
+ setListeners(self, property, currentCompute);
474
+ }
475
+ var listenersMap = /* @__PURE__ */ new WeakMap();
476
+ var computeMap = /* @__PURE__ */ new WeakMap();
477
+ var emptyListeners = /* @__PURE__ */ new Set();
478
+ function listenersFor(self, property) {
479
+ return listenersMap.get(self)?.get(property) || emptyListeners;
480
+ }
481
+ function getListeners(self, property) {
482
+ return Array.from(listenersFor(self, property));
483
+ }
484
+ function setListeners(self, property, compute) {
485
+ if (!listenersMap.has(self)) {
486
+ listenersMap.set(self, /* @__PURE__ */ new Map());
487
+ }
488
+ const listeners = listenersMap.get(self);
489
+ if (!listeners.has(property)) {
490
+ listeners.set(property, /* @__PURE__ */ new Set());
491
+ }
492
+ listeners.get(property).add(compute);
493
+ if (!computeMap.has(compute)) {
494
+ computeMap.set(compute, /* @__PURE__ */ new Map());
495
+ }
496
+ const dependencies = computeMap.get(compute);
497
+ if (!dependencies.has(property)) {
498
+ dependencies.set(property, /* @__PURE__ */ new Set());
499
+ }
500
+ dependencies.get(property).add(self);
501
+ }
502
+ function clearListeners(compute) {
503
+ const dependencies = computeMap.get(compute);
504
+ if (!dependencies) {
505
+ return;
506
+ }
507
+ dependencies.forEach((signals2, property) => {
508
+ signals2.forEach((signal3) => {
509
+ const listeners = listenersMap.get(signal3);
510
+ listeners?.get(property)?.delete(compute);
511
+ });
512
+ });
513
+ computeMap.delete(compute);
514
+ }
515
+ var computeStack = [];
516
+ var effectStack = [];
517
+ var signalStack = [];
518
+ var effectMap = /* @__PURE__ */ new WeakMap();
519
+ function assertFunction(fn, name) {
520
+ if (typeof fn !== "function") {
521
+ throw new TypeError(`simplyflow/state: ${name}() expects a function`);
522
+ }
523
+ }
524
+ function assertNotRecursive(fn) {
525
+ if (effectStack.includes(fn)) {
526
+ throw new Error("Recursive update() call", { cause: fn });
527
+ }
528
+ }
529
+ function effectSignal(fn) {
530
+ let connectedSignal = signals.get(fn);
531
+ if (!connectedSignal) {
532
+ connectedSignal = signal({ current: null });
533
+ signals.set(fn, connectedSignal);
534
+ }
535
+ return connectedSignal;
536
+ }
537
+ function setEffectResult(connectedSignal, result) {
538
+ if (result instanceof Promise) {
539
+ result.then((value) => {
540
+ connectedSignal.current = value;
541
+ });
542
+ } else {
543
+ connectedSignal.current = result;
544
+ }
545
+ }
546
+ function runTracked(compute, connectedSignal, fn, effectType, args = [compute, computeStack, signalStack]) {
547
+ if (signalStack.includes(connectedSignal)) {
548
+ throw new Error("Cyclical dependency in update() call", { cause: fn });
549
+ }
550
+ clearListeners(compute);
551
+ compute.effectFunction = fn;
552
+ compute.effectType = effectType;
553
+ computeStack.push(compute);
554
+ signalStack.push(connectedSignal);
555
+ let result;
556
+ try {
557
+ result = fn(...args);
558
+ } finally {
559
+ computeStack.pop();
560
+ signalStack.pop();
561
+ setEffectResult(connectedSignal, result);
562
+ }
563
+ }
564
+ function runListeners(listeners, signal3, context) {
565
+ const currentEffect = computeStack[computeStack.length - 1];
566
+ for (const listener of listeners) {
567
+ if (listener !== currentEffect && listener?.needsUpdate) {
568
+ if (listener.scheduleClock) {
569
+ listener.scheduleClock();
570
+ } else {
571
+ if (signal3 && tracing && tracers.length) {
572
+ callTracers("set", signal3, context, listener);
573
+ }
574
+ listener();
575
+ }
576
+ }
577
+ clearContext(listener);
578
+ }
579
+ }
580
+ function effect(fn) {
581
+ assertFunction(fn, "effect");
582
+ assertNotRecursive(fn);
583
+ effectStack.push(fn);
584
+ const connectedSignal = effectSignal(fn);
585
+ const compute = function computeEffect() {
586
+ runTracked(compute, connectedSignal, fn, effect);
587
+ };
588
+ compute.fn = fn;
589
+ effectMap.set(connectedSignal, compute);
590
+ compute();
591
+ return connectedSignal;
592
+ }
593
+ function destroy(connectedSignal) {
594
+ if (!isSignal(connectedSignal)) {
595
+ throw new TypeError("simplyflow/state: destroy() expects an effect signal");
596
+ }
597
+ const compute = effectMap.get(connectedSignal);
598
+ if (!compute) {
599
+ return;
600
+ }
601
+ compute.destroy?.();
602
+ clearListeners(compute);
603
+ if (compute.fn) {
604
+ signals.delete(compute.fn);
605
+ const index = effectStack.findIndex((fn) => fn === compute.fn);
606
+ if (index !== -1) {
607
+ effectStack.splice(index, 1);
608
+ }
609
+ }
610
+ effectMap.delete(connectedSignal);
611
+ }
612
+ function batch(fn) {
613
+ assertFunction(fn, "batch");
614
+ batchDepth++;
615
+ let result;
616
+ try {
617
+ result = fn();
618
+ } finally {
619
+ const finish = () => {
620
+ batchDepth--;
621
+ if (!batchDepth) {
622
+ runBatchedListeners();
623
+ }
624
+ };
625
+ if (result instanceof Promise) {
626
+ result.then(finish, finish);
627
+ } else {
628
+ finish();
629
+ }
630
+ }
631
+ return result;
632
+ }
633
+ function runBatchedListeners() {
634
+ const listeners = batchedListeners;
635
+ batchedListeners = /* @__PURE__ */ new Set();
636
+ const clocked = /* @__PURE__ */ new Set();
637
+ const ready = /* @__PURE__ */ new Set();
638
+ for (const listener of listeners) {
639
+ if (listener.scheduleClock) {
640
+ clocked.add(listener);
641
+ } else {
642
+ ready.add(listener);
643
+ }
644
+ }
645
+ runListeners(clocked);
646
+ runListeners(ready);
647
+ }
648
+ function throttledEffect(fn, throttleTime) {
649
+ assertFunction(fn, "throttledEffect");
650
+ if (!Number.isFinite(throttleTime) || throttleTime < 0) {
651
+ throw new TypeError("simplyflow/state: throttledEffect() expects throttleTime to be a non-negative number");
652
+ }
653
+ assertNotRecursive(fn);
654
+ effectStack.push(fn);
655
+ const connectedSignal = effectSignal(fn);
656
+ let throttledUntil = 0;
657
+ let hasChange = true;
658
+ let timeout = null;
659
+ const compute = function computeEffect() {
660
+ const now = Date.now();
661
+ if (throttledUntil > now) {
662
+ hasChange = true;
663
+ schedule();
664
+ return;
665
+ }
666
+ runTracked(compute, connectedSignal, fn, throttledEffect);
667
+ hasChange = false;
668
+ throttledUntil = Date.now() + throttleTime;
669
+ schedule();
670
+ };
671
+ function schedule() {
672
+ if (timeout) {
673
+ return;
674
+ }
675
+ const delay = Math.max(0, throttledUntil - Date.now());
676
+ timeout = globalThis.setTimeout(() => {
677
+ timeout = null;
678
+ if (hasChange) {
679
+ compute();
680
+ }
681
+ }, delay);
682
+ }
683
+ compute.fn = fn;
684
+ compute.destroy = () => {
685
+ if (timeout) {
686
+ globalThis.clearTimeout(timeout);
687
+ timeout = null;
688
+ }
689
+ hasChange = false;
690
+ };
691
+ effectMap.set(connectedSignal, compute);
692
+ compute();
693
+ return connectedSignal;
694
+ }
695
+ var clockQueues = /* @__PURE__ */ new WeakMap();
696
+ function readClockTime(clock) {
697
+ return raw(clock).time;
698
+ }
699
+ function getClockQueue(clock) {
700
+ if (!clockQueues.has(clock)) {
701
+ const queue = {
702
+ clock,
703
+ effects: /* @__PURE__ */ new Set(),
704
+ pending: /* @__PURE__ */ new Set(),
705
+ time: readClockTime(clock)
706
+ };
707
+ queue.tick = function tickClockEffects() {
708
+ const time = readClockTime(clock);
709
+ if (time <= queue.time) {
710
+ return;
711
+ }
712
+ queue.time = time;
713
+ const pending = Array.from(queue.pending);
714
+ queue.pending.clear();
715
+ for (const compute of pending) {
716
+ compute.clockPending = false;
717
+ if (queue.effects.has(compute)) {
718
+ compute();
719
+ }
720
+ }
721
+ };
722
+ queue.tick.effectFunction = queue.tick;
723
+ queue.tick.effectType = clockEffect;
724
+ setListeners(clock, "time", queue.tick);
725
+ clockQueues.set(clock, queue);
726
+ }
727
+ return clockQueues.get(clock);
728
+ }
729
+ function detachClockEffect(compute) {
730
+ const queue = compute.clockQueue;
731
+ if (!queue) {
732
+ return;
733
+ }
734
+ queue.pending.delete(compute);
735
+ queue.effects.delete(compute);
736
+ if (!queue.effects.size) {
737
+ clearListeners(queue.tick);
738
+ clockQueues.delete(queue.clock);
739
+ }
740
+ }
741
+ function clockEffect(fn, clock) {
742
+ assertFunction(fn, "clockEffect");
743
+ if (!clock || typeof clock !== "object" || typeof raw(clock).time !== "number") {
744
+ throw new TypeError("simplyflow/state: clockEffect() expects a clock object with a numeric .time property");
745
+ }
746
+ const clockSignal = isSignal(clock) ? clock : signal(raw(clock));
747
+ const connectedSignal = effectSignal(fn);
748
+ const queue = getClockQueue(clockSignal);
749
+ const compute = function computeEffect() {
750
+ clearListeners(compute);
751
+ compute.effectFunction = fn;
752
+ compute.effectType = clockEffect;
753
+ computeStack.push(compute);
754
+ let result;
755
+ try {
756
+ result = fn(compute, computeStack);
757
+ } finally {
758
+ computeStack.pop();
759
+ setEffectResult(connectedSignal, result);
760
+ }
761
+ };
762
+ compute.fn = fn;
763
+ compute.clockQueue = queue;
764
+ compute.skipDependency = (self, property) => self === clockSignal && property === "time";
765
+ compute.scheduleClock = () => {
766
+ if (!compute.clockPending) {
767
+ compute.clockPending = true;
768
+ queue.pending.add(compute);
769
+ }
770
+ };
771
+ compute.destroy = () => detachClockEffect(compute);
772
+ queue.effects.add(compute);
773
+ effectMap.set(connectedSignal, compute);
774
+ compute();
775
+ return connectedSignal;
776
+ }
777
+ function untracked(fn) {
778
+ assertFunction(fn, "untracked");
779
+ const index = computeStack.length - 1;
780
+ const current = computeStack[index];
781
+ computeStack[index] = false;
782
+ try {
783
+ return fn();
784
+ } finally {
785
+ computeStack[index] = current;
786
+ }
787
+ }
788
+ function cloneOptions(options) {
789
+ if (typeof options === "boolean") {
790
+ return { deep: options };
791
+ }
792
+ if (options === void 0) {
793
+ return { deep: true };
794
+ }
795
+ if (!options || typeof options !== "object") {
796
+ throw new TypeError("simplyflow/state: clone() expects options to be a boolean or object");
797
+ }
798
+ return { deep: options.deep !== false };
799
+ }
800
+ function typeName(value) {
801
+ return value?.constructor?.name || Object.prototype.toString.call(value).slice(8, -1);
802
+ }
803
+ function isPlainObject(value) {
804
+ const prototype = Object.getPrototypeOf(value);
805
+ return prototype === Object.prototype || prototype === null;
806
+ }
807
+ function isTypedArray(value) {
808
+ return ArrayBuffer.isView(value) && !(value instanceof DataView);
809
+ }
810
+ function isIntegerKey(property) {
811
+ if (typeof property !== "string" || property === "") {
812
+ return false;
813
+ }
814
+ const index = Number(property);
815
+ return Number.isInteger(index) && index >= 0 && String(index) === property;
816
+ }
817
+ function hasToClone(value) {
818
+ return typeof value.toClone === "function";
819
+ }
820
+ function cannotClone(value, path2) {
821
+ throw new TypeError(
822
+ `simplyflow/state: clone() cannot clone ${typeName(value)} at ${path2}; add a toClone() method for custom objects`
823
+ );
824
+ }
825
+ function cloneDescriptorProperties(source, result, cloneValue, skip = () => false) {
826
+ const descriptors = Object.getOwnPropertyDescriptors(source);
827
+ for (const key of Reflect.ownKeys(descriptors)) {
828
+ if (skip(key)) {
829
+ delete descriptors[key];
830
+ continue;
831
+ }
832
+ const descriptor = descriptors[key];
833
+ if (!Object.hasOwn(descriptor, "value")) {
834
+ cannotClone(source, String(key));
835
+ }
836
+ descriptor.value = cloneValue(descriptor.value, String(key));
837
+ }
838
+ Object.defineProperties(result, descriptors);
839
+ return result;
840
+ }
841
+ function cloneArrayBuffer(value) {
842
+ return value.slice(0);
843
+ }
844
+ function cloneSharedArrayBuffer(value) {
845
+ const result = new SharedArrayBuffer(value.byteLength);
846
+ new Uint8Array(result).set(new Uint8Array(value));
847
+ return result;
848
+ }
849
+ function cloneErrorObject(value, cloneValue, path2) {
850
+ const standardErrors = /* @__PURE__ */ new Set([
851
+ Error,
852
+ EvalError,
853
+ RangeError,
854
+ ReferenceError,
855
+ SyntaxError,
856
+ TypeError,
857
+ URIError,
858
+ typeof AggregateError === "undefined" ? void 0 : AggregateError
859
+ ]);
860
+ if (!standardErrors.has(value.constructor)) {
861
+ cannotClone(value, path2);
862
+ }
863
+ const options = Object.hasOwn(value, "cause") ? { cause: cloneValue(value.cause, "cause") } : void 0;
864
+ if (typeof AggregateError !== "undefined" && value instanceof AggregateError) {
865
+ const errors = Array.from(value.errors || [], (error, index) => cloneValue(error, `errors.${index}`));
866
+ return new AggregateError(errors, value.message, options);
867
+ }
868
+ return new value.constructor(value.message, options);
869
+ }
870
+ function clone(value, options) {
871
+ const { deep } = cloneOptions(options);
872
+ const seen = /* @__PURE__ */ new Map();
873
+ function cloneChild(value2, path2) {
874
+ return deep ? cloneValue(value2, path2) : raw(value2);
875
+ }
876
+ function cloneValue(value2, path2 = "value") {
877
+ const source = raw(value2);
878
+ if (!isObjectLike(source)) {
879
+ return source;
880
+ }
881
+ if (seen.has(source)) {
882
+ return seen.get(source);
883
+ }
884
+ if (hasToClone(source)) {
885
+ const result = raw(source.toClone());
886
+ if (Object.is(result, source)) {
887
+ throw new TypeError(`simplyflow/state: clone() toClone() returned the original object at ${path2}`);
888
+ }
889
+ seen.set(source, result);
890
+ return result;
891
+ }
892
+ if (Array.isArray(source)) {
893
+ const result = new Array(source.length);
894
+ seen.set(source, result);
895
+ return cloneDescriptorProperties(source, result, cloneChild, (key) => key === "length");
896
+ }
897
+ if (isPlainObject(source)) {
898
+ const result = Object.create(Object.getPrototypeOf(source));
899
+ seen.set(source, result);
900
+ return cloneDescriptorProperties(source, result, cloneChild);
901
+ }
902
+ if (source instanceof Map) {
903
+ const result = /* @__PURE__ */ new Map();
904
+ seen.set(source, result);
905
+ source.forEach((mapValue, mapKey) => {
906
+ result.set(cloneChild(mapKey, "map key"), cloneChild(mapValue, "map value"));
907
+ });
908
+ return cloneDescriptorProperties(source, result, cloneChild);
909
+ }
910
+ if (source instanceof Set) {
911
+ const result = /* @__PURE__ */ new Set();
912
+ seen.set(source, result);
913
+ source.forEach((setValue) => result.add(cloneChild(setValue, "set value")));
914
+ return cloneDescriptorProperties(source, result, cloneChild);
915
+ }
916
+ if (source instanceof Date) {
917
+ const result = new Date(source.getTime());
918
+ seen.set(source, result);
919
+ return cloneDescriptorProperties(source, result, cloneChild);
920
+ }
921
+ if (source instanceof RegExp) {
922
+ const result = new RegExp(source.source, source.flags);
923
+ result.lastIndex = source.lastIndex;
924
+ seen.set(source, result);
925
+ return cloneDescriptorProperties(source, result, cloneChild, (key) => key === "lastIndex");
926
+ }
927
+ if (source instanceof ArrayBuffer) {
928
+ const result = cloneArrayBuffer(source);
929
+ seen.set(source, result);
930
+ return cloneDescriptorProperties(source, result, cloneChild);
931
+ }
932
+ if (typeof SharedArrayBuffer !== "undefined" && source instanceof SharedArrayBuffer) {
933
+ const result = cloneSharedArrayBuffer(source);
934
+ seen.set(source, result);
935
+ return cloneDescriptorProperties(source, result, cloneChild);
936
+ }
937
+ if (source instanceof DataView) {
938
+ const buffer = source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
939
+ const result = new DataView(buffer);
940
+ seen.set(source, result);
941
+ return cloneDescriptorProperties(source, result, cloneChild);
942
+ }
943
+ if (isTypedArray(source)) {
944
+ const result = new source.constructor(source);
945
+ seen.set(source, result);
946
+ return cloneDescriptorProperties(source, result, cloneChild, isIntegerKey);
947
+ }
948
+ if (typeof URL !== "undefined" && source instanceof URL) {
949
+ const result = new URL(source.href);
950
+ seen.set(source, result);
951
+ return result;
952
+ }
953
+ if (typeof URLSearchParams !== "undefined" && source instanceof URLSearchParams) {
954
+ const result = new URLSearchParams(source);
955
+ seen.set(source, result);
956
+ return result;
957
+ }
958
+ if (typeof File !== "undefined" && source instanceof File) {
959
+ const result = new File([source], source.name, {
960
+ type: source.type,
961
+ lastModified: source.lastModified
962
+ });
963
+ seen.set(source, result);
964
+ return result;
965
+ }
966
+ if (typeof Blob !== "undefined" && source instanceof Blob) {
967
+ const result = source.slice(0, source.size, source.type);
968
+ seen.set(source, result);
969
+ return result;
970
+ }
971
+ if (source instanceof Error) {
972
+ const result = cloneErrorObject(source, cloneChild, path2);
973
+ seen.set(source, result);
974
+ return cloneDescriptorProperties(source, result, cloneChild, (key) => key === "message" || key === "cause" || key === "errors" || key === "stack");
975
+ }
976
+ if (typeof Node !== "undefined" && source instanceof Node && typeof source.cloneNode === "function") {
977
+ const result = source.cloneNode(deep);
978
+ seen.set(source, result);
979
+ return result;
980
+ }
981
+ cannotClone(source, path2);
982
+ }
983
+ return cloneValue(value);
984
+ }
985
+
986
+ // src/bind.transformers.mjs
987
+ function escape_html(context, next) {
988
+ let content = context.value?.innerHTML;
989
+ if (typeof context.value == "string") {
990
+ content = context.value;
991
+ context.value = { innerHTML: content };
992
+ }
993
+ if (content) {
994
+ content = content.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
995
+ context.value.innerHTML = content;
996
+ }
997
+ next(context);
998
+ }
999
+ function fixed_content(context, next) {
1000
+ if (typeof context.value == "string") {
1001
+ context.value = {};
1002
+ } else {
1003
+ delete context.value?.innerHTML;
1004
+ }
1005
+ next(context);
1006
+ }
1007
+
1008
+ // src/dom.mjs
1009
+ var dom_exports = {};
1010
+ __export(dom_exports, {
1011
+ findAttribute: () => findAttribute,
1012
+ signal: () => signal2,
1013
+ trackDomField: () => trackDomField,
1014
+ trackDomList: () => trackDomList
1015
+ });
1016
+ var domSignals = /* @__PURE__ */ new WeakMap();
1017
+ var observers = /* @__PURE__ */ new WeakMap();
1018
+ var domSignalHandler = {
1019
+ get: (target, property, receiver) => {
1020
+ const value = target?.[property];
1021
+ notifyGet(receiver, property);
1022
+ if (typeof value === "function") {
1023
+ return value.bind(target);
1024
+ }
1025
+ if (value && typeof value == "object") {
1026
+ return signal(value);
1027
+ }
1028
+ return value;
1029
+ },
1030
+ set: (target, property, value, receiver) => {
1031
+ const current = target[property];
1032
+ target[property] = value;
1033
+ const now = target[property];
1034
+ if (!Object.is(current, now)) {
1035
+ notifySet(receiver, makeContext(property, { was: current, now }));
1036
+ }
1037
+ return true;
1038
+ },
1039
+ has: (target, property) => {
1040
+ const receiver = getSignal(target);
1041
+ if (receiver) {
1042
+ notifyGet(receiver, property);
1043
+ }
1044
+ return Reflect.has(target, property);
1045
+ },
1046
+ ownKeys: (target) => {
1047
+ const receiver = getSignal(target);
1048
+ if (receiver) {
1049
+ notifyGet(receiver, DEP.ITERATE);
1050
+ }
1051
+ return Reflect.ownKeys(target);
1052
+ }
1053
+ };
1054
+ function signal2(el, options) {
1055
+ if (isSignal(el)) {
1056
+ return el;
1057
+ }
1058
+ const existing = getSignal(el);
1059
+ if (existing) {
1060
+ return existing;
1061
+ }
1062
+ return createSignal(el, domSignalHandler, (target, proxy) => {
1063
+ domListen(target, proxy, options);
1064
+ });
1065
+ }
1066
+ function domListen(el, signal3, options) {
1067
+ const defaultOptions = {
1068
+ characterData: true,
1069
+ subtree: true,
1070
+ attributes: true,
1071
+ attributesOldValue: true,
1072
+ childList: true
1073
+ };
1074
+ if (!options) {
1075
+ options = defaultOptions;
1076
+ }
1077
+ let oldContentHTML = el.innerHTML;
1078
+ let oldContentText = el.innerText;
1079
+ if (!observers.has(el)) {
1080
+ const observer = new MutationObserver((mutationList, observer2) => {
1081
+ const changes = {};
1082
+ for (const mutation of mutationList) {
1083
+ if (mutation.type === "attributes") {
1084
+ changes[mutation.attributeName] = mutation.attributeOldValue;
1085
+ } else if (mutation.type === "subtree" || mutation.type === "characterData") {
1086
+ if (el.innerHTML != oldContentHTML) {
1087
+ changes.innerHTML = oldContentHTML;
1088
+ oldContentHTML = el.innerHTML;
1089
+ }
1090
+ if (el.innerText != oldContentText) {
1091
+ changes.innerText = oldContentText;
1092
+ oldContentText = el.innerText;
1093
+ }
1094
+ } else if (mutation.type === "childList") {
1095
+ changes.children = {
1096
+ //FIXME: overwrites changes in this list path if list is rendered multiple times
1097
+ was: Array.from(el.children)
1098
+ //FIXME; fill in 'now'
1099
+ };
1100
+ changes.length = -1;
1101
+ if (el.innerHTML != oldContentHTML) {
1102
+ changes.innerHTML = oldContentHTML;
1103
+ oldContentHTML = el.innerHTML;
1104
+ }
1105
+ if (el.innerText != oldContentText) {
1106
+ changes.innerText = oldContentText;
1107
+ oldContentText = el.innerText;
1108
+ }
1109
+ } else {
1110
+ console.log("nothing to do for", el, mutation.type);
1111
+ }
1112
+ }
1113
+ for (const prop in changes) {
1114
+ notifySet(signal3, makeContext(prop, { was: changes[prop], now: el[prop] }));
1115
+ }
1116
+ });
1117
+ observer.observe(el, options);
1118
+ observers.set(el, observer);
1119
+ if (el.matches("input, textarea, select")) {
1120
+ let prevValue = el.value;
1121
+ let prevChecked = el.checked;
1122
+ const notifyFormValue = () => {
1123
+ notifySet(signal3, makeContext("value", { was: prevValue, now: el.value }));
1124
+ prevValue = el.value;
1125
+ if ("checked" in el) {
1126
+ notifySet(signal3, makeContext("checked", { was: prevChecked, now: el.checked }));
1127
+ prevChecked = el.checked;
1128
+ }
1129
+ };
1130
+ el.addEventListener("change", notifyFormValue);
1131
+ if (el.matches("input, textarea")) {
1132
+ el.addEventListener("input", notifyFormValue);
1133
+ }
1134
+ }
1135
+ }
1136
+ }
1137
+ function trackDomList(element2) {
1138
+ const path2 = this.getBindingPath(element2);
1139
+ if (!path2) {
1140
+ throw new Error("Could not find binding path for element", { cause: element2 });
1141
+ }
1142
+ const s = signal2(element2, {
1143
+ childList: true
1144
+ });
1145
+ throttledEffect(() => {
1146
+ const children = Array.from(s.children);
1147
+ untracked(() => {
1148
+ batch(() => {
1149
+ let key = 0;
1150
+ const currentList = getValueByPath(this.options.root, path2);
1151
+ const source = currentList.slice();
1152
+ for (const item of children) {
1153
+ if (item.tagName === "TEMPLATE") {
1154
+ continue;
1155
+ }
1156
+ if (item.dataset.flowKey) {
1157
+ if (item.dataset.flowKey != key) {
1158
+ setValueByPath(
1159
+ this.options.root,
1160
+ path2 + "." + key,
1161
+ source[item.dataset.flowKey]
1162
+ );
1163
+ }
1164
+ key++;
1165
+ }
1166
+ }
1167
+ if (currentList.length > key) {
1168
+ currentList.length = key;
1169
+ }
1170
+ });
1171
+ });
1172
+ }, 50);
1173
+ return s;
1174
+ }
1175
+ function trackDomField(element2, props, valueIsString, stringProperty = "innerHTML", getUpdateValue) {
1176
+ if (domSignals.has(element2)) {
1177
+ return;
1178
+ }
1179
+ const path2 = this.getBindingPath(element2);
1180
+ if (!path2) {
1181
+ throw new Error("Could not find binding path for element", { cause: element2 });
1182
+ }
1183
+ const s = signal2(element2);
1184
+ domSignals.set(element2, s);
1185
+ batch(() => {
1186
+ throttledEffect(() => {
1187
+ let updateValue;
1188
+ if (getUpdateValue) {
1189
+ for (const prop of props) {
1190
+ s[prop];
1191
+ }
1192
+ } else {
1193
+ updateValue = s[stringProperty];
1194
+ if (!valueIsString) {
1195
+ updateValue = getProperties(s, ...props);
1196
+ }
1197
+ }
1198
+ untracked(() => {
1199
+ const currentValue = getValueByPath(this.options.root, path2);
1200
+ if (getUpdateValue) {
1201
+ updateValue = getUpdateValue.call(this, s, currentValue);
1202
+ }
1203
+ if (typeof updateValue === "undefined") {
1204
+ return;
1205
+ }
1206
+ if (valueIsString && !Object.is(currentValue, updateValue) && String(currentValue) === updateValue) {
1207
+ return;
1208
+ }
1209
+ setValueByPath(this.options.root, path2, updateValue);
1210
+ });
1211
+ }, 50);
1212
+ });
1213
+ return s;
1214
+ }
1215
+ function findAttribute(el, attr) {
1216
+ return el.closest("[" + attr + "]")?.getAttribute(attr);
1217
+ }
1218
+
1219
+ // src/bind.render.mjs
1220
+ function writesFromDom(binding, context) {
1221
+ return binding.options.twoway || context.edit;
1222
+ }
1223
+ function field(context) {
1224
+ if (context.templates?.length) {
1225
+ fieldByTemplates.call(this, context);
1226
+ } else if (Object.hasOwnProperty.call(this.options.renderers, context.element.tagName)) {
1227
+ const renderer = this.options.renderers[context.element.tagName];
1228
+ if (renderer) {
1229
+ renderer.call(this, context);
1230
+ }
1231
+ } else if (this.options.renderers["*"]) {
1232
+ this.options.renderers["*"].call(this, context);
1233
+ }
1234
+ return context;
1235
+ }
1236
+ function list(context) {
1237
+ if (!Array.isArray(context.value)) {
1238
+ context.value = [context.value];
1239
+ }
1240
+ const length = context.value.length;
1241
+ if (!context.templates?.length) {
1242
+ console.error("No templates found in", context.element);
1243
+ } else {
1244
+ arrayByTemplates.call(this, context);
1245
+ }
1246
+ return context;
1247
+ }
1248
+ function map(context) {
1249
+ if (typeof context.value != "object" || !context.value) {
1250
+ console.error("Value is not an object.", context.element, context.path, context.value);
1251
+ } else if (!context.templates?.length) {
1252
+ console.error("No templates found in", context.element);
1253
+ } else {
1254
+ objectByTemplates.call(this, context);
1255
+ }
1256
+ return context;
1257
+ }
1258
+ function isInt(s) {
1259
+ if (parseInt(s) == s) {
1260
+ return true;
1261
+ }
1262
+ }
1263
+ function setValueByPath(root, path2, value) {
1264
+ batch(() => {
1265
+ let parts = path2.split(".");
1266
+ let curr = root;
1267
+ let part;
1268
+ part = parts.shift();
1269
+ let prev = null;
1270
+ let prevPart = null;
1271
+ let prevCurr = curr;
1272
+ while (part && curr) {
1273
+ prevCurr = curr;
1274
+ part = decodeURIComponent(part);
1275
+ if (part == "0" && !Array.isArray(curr)) {
1276
+ } else if (part == ":key") {
1277
+ throw new Error("setting key not yet supported");
1278
+ curr = prevPart;
1279
+ } else if (part == ":value") {
1280
+ } else if (Array.isArray(curr) && !isInt(part) && typeof curr[part] == "undefined") {
1281
+ prev = curr[0];
1282
+ curr = curr[0][part];
1283
+ } else {
1284
+ prev = curr;
1285
+ curr = curr[part];
1286
+ }
1287
+ prevPart = part;
1288
+ part = parts.shift();
1289
+ if (part && !curr) {
1290
+ const intKey = parseInt(part);
1291
+ if (intKey >= 0 && part === "" + intKey) {
1292
+ prevCurr[prevPart] = [];
1293
+ } else {
1294
+ prevCurr[prevPart] = {};
1295
+ }
1296
+ curr = prevCurr[prevPart];
1297
+ }
1298
+ }
1299
+ if (prev && prevPart && prev[prevPart] !== value) {
1300
+ if (Array.isArray(value)) {
1301
+ prev[prevPart] = value;
1302
+ } else if (value && typeof value == "object") {
1303
+ curr = prev[prevPart];
1304
+ if (!curr) {
1305
+ prev[prevPart] = {};
1306
+ curr = prev[prevPart];
1307
+ }
1308
+ for (const prop in value) {
1309
+ if (curr[prop] !== value[prop]) {
1310
+ curr[prop] = value[prop];
1311
+ }
1312
+ }
1313
+ } else {
1314
+ prev[prevPart] = value;
1315
+ }
1316
+ }
1317
+ });
1318
+ }
1319
+ function arrayByTemplates(context) {
1320
+ const attribute = this.options.attribute;
1321
+ const attributes = [attribute + "-field", attribute + "-edit", attribute + "-list", attribute + "-map", attribute + "-value-path"];
1322
+ const attrQuery = "[" + attributes.join("],[") + "]";
1323
+ const keyAttribute = attribute + "-key";
1324
+ const items = Array.from(context.element.querySelectorAll(":scope > [" + keyAttribute + "]"));
1325
+ const usedItems = /* @__PURE__ */ new Set();
1326
+ let cursor = 0;
1327
+ context.list = context.value;
1328
+ for (let index = 0; index < context.value.length; index++) {
1329
+ context.index = index;
1330
+ const value = context.list[index];
1331
+ let item = nextUnusedItem(items, usedItems, cursor);
1332
+ if (!item) {
1333
+ context.element.appendChild(this.applyTemplate(context));
1334
+ continue;
1335
+ }
1336
+ const newTemplate = this.findTemplate(context.templates, value);
1337
+ const currentValueMatches = item[DEP.VALUE] === value;
1338
+ let reusableItem = currentValueMatches ? item : findReusableItem(items, usedItems, value, newTemplate, cursor + 1);
1339
+ if (reusableItem) {
1340
+ if (newTemplate != reusableItem[DEP.TEMPLATE]) {
1341
+ context.element.replaceChild(this.applyTemplate(context), reusableItem);
1342
+ } else {
1343
+ context.element.insertBefore(reusableItem, item);
1344
+ updateItemKey(reusableItem, index, context.path, keyAttribute, attributes, attrQuery);
1345
+ reusableItem[DEP.VALUE] = value;
1346
+ }
1347
+ usedItems.add(reusableItem);
1348
+ if (reusableItem === item) {
1349
+ cursor++;
1350
+ }
1351
+ continue;
1352
+ }
1353
+ context.element.insertBefore(this.applyTemplate(context), item);
1354
+ }
1355
+ for (let item of items) {
1356
+ if (!usedItems.has(item)) {
1357
+ item.remove();
1358
+ }
1359
+ }
1360
+ if (this.options.twoway) {
1361
+ trackDomList.call(this, context.element);
1362
+ }
1363
+ }
1364
+ function nextUnusedItem(items, usedItems, start) {
1365
+ while (start < items.length) {
1366
+ const item = items[start];
1367
+ if (!usedItems.has(item)) {
1368
+ return item;
1369
+ }
1370
+ start++;
1371
+ }
1372
+ }
1373
+ function findReusableItem(items, usedItems, value, template, start) {
1374
+ for (let i = start; i < items.length; i++) {
1375
+ const item = items[i];
1376
+ if (!usedItems.has(item) && item[DEP.VALUE] === value && item[DEP.TEMPLATE] === template) {
1377
+ return item;
1378
+ }
1379
+ }
1380
+ }
1381
+ function updateItemKey(item, key, path2, keyAttribute, attributes, attrQuery) {
1382
+ const oldKey = item.getAttribute(keyAttribute);
1383
+ const newKey = "" + key;
1384
+ if (oldKey === newKey) {
1385
+ return;
1386
+ }
1387
+ item.setAttribute(keyAttribute, newKey);
1388
+ const oldPrefix = path2 + "." + oldKey;
1389
+ const newPrefix = path2 + "." + newKey;
1390
+ const bindings = Array.from(item.querySelectorAll(attrQuery));
1391
+ if (item.matches(attrQuery)) {
1392
+ bindings.unshift(item);
1393
+ }
1394
+ for (let binding of bindings) {
1395
+ for (let attr of attributes) {
1396
+ const bindPath = binding.getAttribute(attr);
1397
+ if (!bindPath || bindPath.substr(0, 5) === ":root") {
1398
+ continue;
1399
+ }
1400
+ if (bindPath === oldPrefix) {
1401
+ binding.setAttribute(attr, newPrefix);
1402
+ } else if (bindPath.startsWith(oldPrefix + ".")) {
1403
+ binding.setAttribute(attr, newPrefix + bindPath.substr(oldPrefix.length));
1404
+ }
1405
+ }
1406
+ }
1407
+ }
1408
+ function objectByTemplates(context) {
1409
+ const attribute = this.options.attribute;
1410
+ const attributes = [attribute + "-field", attribute + "-edit", attribute + "-list", attribute + "-map", attribute + "-value-path"];
1411
+ const attrQuery = "[" + attributes.join("],[") + "]";
1412
+ const keyAttribute = attribute + "-key";
1413
+ const items = Array.from(context.element.querySelectorAll(":scope > [" + keyAttribute + "]"));
1414
+ const usedItems = /* @__PURE__ */ new Set();
1415
+ let cursor = 0;
1416
+ context.list = context.value;
1417
+ for (let key in context.list) {
1418
+ context.index = key;
1419
+ const value = context.list[key];
1420
+ let item = nextUnusedItem(items, usedItems, cursor);
1421
+ if (!item) {
1422
+ context.element.appendChild(this.applyTemplate(context));
1423
+ continue;
1424
+ }
1425
+ const newTemplate = this.findTemplate(context.templates, value);
1426
+ let reusableItem;
1427
+ if (item.getAttribute(keyAttribute) === key) {
1428
+ reusableItem = item;
1429
+ } else {
1430
+ reusableItem = findItemByKey(items, usedItems, key, keyAttribute) || findReusableItem(items, usedItems, value, newTemplate, cursor);
1431
+ }
1432
+ if (reusableItem) {
1433
+ if (newTemplate != reusableItem[DEP.TEMPLATE]) {
1434
+ context.element.replaceChild(this.applyTemplate(context), reusableItem);
1435
+ } else {
1436
+ context.element.insertBefore(reusableItem, item);
1437
+ updateItemKey(reusableItem, key, context.path, keyAttribute, attributes, attrQuery);
1438
+ reusableItem[DEP.VALUE] = value;
1439
+ }
1440
+ usedItems.add(reusableItem);
1441
+ if (reusableItem === item) {
1442
+ cursor++;
1443
+ }
1444
+ continue;
1445
+ }
1446
+ context.element.insertBefore(this.applyTemplate(context), item);
1447
+ }
1448
+ for (let item of items) {
1449
+ if (!usedItems.has(item)) {
1450
+ item.remove();
1451
+ }
1452
+ }
1453
+ }
1454
+ function findItemByKey(items, usedItems, key, keyAttribute) {
1455
+ const stringKey = "" + key;
1456
+ for (let item of items) {
1457
+ if (!usedItems.has(item) && item.getAttribute(keyAttribute) === stringKey) {
1458
+ return item;
1459
+ }
1460
+ }
1461
+ }
1462
+ function fieldByTemplates(context) {
1463
+ const rendered = context.element.querySelector(":scope > :not(template)");
1464
+ const template = this.findTemplate(context.templates, context.value);
1465
+ context.parent = getParentPath(context.element);
1466
+ if (rendered) {
1467
+ if (template) {
1468
+ if (rendered?.[DEP.TEMPLATE] != template) {
1469
+ const clone2 = this.applyTemplate(context);
1470
+ context.element.replaceChild(clone2, rendered);
1471
+ }
1472
+ } else {
1473
+ context.element.removeChild(rendered);
1474
+ }
1475
+ } else if (template) {
1476
+ const clone2 = this.applyTemplate(context);
1477
+ context.element.appendChild(clone2);
1478
+ }
1479
+ }
1480
+ function getParentPath(el, attribute) {
1481
+ const parentEl = el.parentElement?.closest(`[${attribute}-list],[${attribute}-map]`);
1482
+ if (!parentEl) {
1483
+ return "";
1484
+ }
1485
+ if (parentEl.hasAttribute(`${attribute}-list`)) {
1486
+ return parentEl.getAttribute(`${attribute}-list`) + ".";
1487
+ }
1488
+ return parentEl.getAttribute(`${attribute}-map`) + ".";
1489
+ }
1490
+ function input(context) {
1491
+ const el = context.element;
1492
+ let value = context.value;
1493
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1494
+ setProperties(el, value, "title", "id", "className", "value", "checked");
1495
+ value = value.value;
1496
+ }
1497
+ if (typeof value == "undefined") {
1498
+ value = "";
1499
+ }
1500
+ if (el.type == "checkbox") {
1501
+ el.checked = checkboxIsChecked(el, value);
1502
+ } else if (el.type == "radio") {
1503
+ el.checked = matchValue(el.value, value);
1504
+ } else if (!matchValue(el.value, value)) {
1505
+ el.value = "" + value;
1506
+ }
1507
+ if (writesFromDom(this, context)) {
1508
+ if (el.type == "checkbox") {
1509
+ trackDomField.call(this, context.element, ["checked"], true, "checked", checkboxEditValue);
1510
+ } else if (el.type == "radio") {
1511
+ trackDomField.call(this, context.element, ["checked"], true, "checked", radioEditValue);
1512
+ } else {
1513
+ trackDomField.call(this, context.element, ["value"], true, "value");
1514
+ }
1515
+ }
1516
+ }
1517
+ function checkboxIsChecked(el, value) {
1518
+ if (Array.isArray(value)) {
1519
+ return value.some((selected) => matchValue(el.value, selected));
1520
+ }
1521
+ if (typeof value === "boolean") {
1522
+ return value;
1523
+ }
1524
+ return matchValue(el.value, value);
1525
+ }
1526
+ function checkboxEditValue(el, currentValue) {
1527
+ if (Array.isArray(currentValue)) {
1528
+ const value = el.value;
1529
+ const values = currentValue.filter((item) => !matchValue(item, value));
1530
+ if (el.checked) {
1531
+ values.push(value);
1532
+ }
1533
+ return values;
1534
+ }
1535
+ if (typeof currentValue === "boolean") {
1536
+ return el.checked;
1537
+ }
1538
+ if (el.checked && matchValue(el.value, currentValue)) {
1539
+ return currentValue;
1540
+ }
1541
+ return el.checked;
1542
+ }
1543
+ function radioEditValue(el, currentValue) {
1544
+ if (!el.checked) {
1545
+ return void 0;
1546
+ }
1547
+ return el.value;
1548
+ }
1549
+ function button(context) {
1550
+ element.call(this, context, "value");
1551
+ }
1552
+ function select(context) {
1553
+ const el = context.element;
1554
+ let value = context.value;
1555
+ if (value === null) {
1556
+ value = "";
1557
+ }
1558
+ if (Array.isArray(value)) {
1559
+ for (let option of el.options) {
1560
+ option.selected = value.some((selected) => matchValue(option.value, selected));
1561
+ if (option.selected) {
1562
+ option.setAttribute("selected", true);
1563
+ } else {
1564
+ option.removeAttribute("selected");
1565
+ }
1566
+ }
1567
+ } else if (typeof value != "object") {
1568
+ let option = Array.from(el.options).find((o) => matchValue(o.value, value));
1569
+ if (option) {
1570
+ option.selected = true;
1571
+ option.setAttribute("selected", true);
1572
+ }
1573
+ } else {
1574
+ if (value.options) {
1575
+ setSelectOptions(el, value.options);
1576
+ }
1577
+ if (typeof value.selected !== "undefined") {
1578
+ select.call(this, Object.assign({}, context, { value: value.selected }));
1579
+ }
1580
+ setProperties(el, value, "name", "id", "selectedIndex", "className");
1581
+ }
1582
+ if (writesFromDom(this, context)) {
1583
+ if (el.multiple) {
1584
+ trackDomField.call(this, context.element, ["value"], true, "value", selectMultipleEditValue);
1585
+ } else {
1586
+ trackDomField.call(this, context.element, ["value"], true, "value");
1587
+ }
1588
+ }
1589
+ }
1590
+ function selectMultipleEditValue(el) {
1591
+ const value = el.value;
1592
+ return Array.from(el.options).filter((option) => option.selected).map((option) => option.value);
1593
+ }
1594
+ function addOption(select2, option) {
1595
+ if (!option) {
1596
+ return;
1597
+ }
1598
+ if (typeof option !== "object") {
1599
+ select2.options.add(new Option("" + option));
1600
+ } else if (option.text) {
1601
+ select2.options.add(new Option(option.text, option.value, option.defaultSelected, option.selected));
1602
+ } else if (typeof option.value != "undefined") {
1603
+ select2.options.add(new Option("" + option.value, option.value, option.defaultSelected, option.selected));
1604
+ }
1605
+ }
1606
+ function setSelectOptions(select2, options) {
1607
+ select2.innerHTML = "";
1608
+ if (Array.isArray(options)) {
1609
+ for (const option of options) {
1610
+ addOption(select2, option);
1611
+ }
1612
+ } else if (options && typeof options == "object") {
1613
+ for (const option in options) {
1614
+ addOption(select2, { text: options[option], value: option });
1615
+ }
1616
+ }
1617
+ }
1618
+ function anchor(context) {
1619
+ element.call(this, context, "target", "href", "name", "newwindow", "nofollow");
1620
+ if (writesFromDom(this, context)) {
1621
+ batch(() => {
1622
+ updateProperties.call(this, context, ["target", "href", "name", "newwindow", "nofollow"]);
1623
+ });
1624
+ }
1625
+ }
1626
+ function image(context) {
1627
+ setProperties(context.element, context.value, "title", "alt", "src", "id");
1628
+ if (writesFromDom(this, context)) {
1629
+ batch(() => {
1630
+ updateProperties.call(this, context, ["title", "alt", "src", "id"]);
1631
+ });
1632
+ }
1633
+ }
1634
+ function iframe(context) {
1635
+ setProperties(context.element, context.value, "title", "src", "id");
1636
+ if (writesFromDom(this, context)) {
1637
+ batch(() => {
1638
+ updateProperties.call(this, context, ["title", "src", "id"]);
1639
+ });
1640
+ }
1641
+ }
1642
+ function meta(context) {
1643
+ setProperties(context.element, context.value, "content", "id");
1644
+ if (writesFromDom(this, context)) {
1645
+ batch(() => {
1646
+ updateProperties.call(this, context, ["content", "id"]);
1647
+ });
1648
+ }
1649
+ }
1650
+ function element(context, ...extraprops) {
1651
+ const el = context.element;
1652
+ let value = context.value;
1653
+ let valueIsString = false;
1654
+ if (typeof value != "undefined" && value !== null) {
1655
+ let strValue = "" + value;
1656
+ if (typeof value != "object" || strValue.substring(0, 8) != "[object ") {
1657
+ value = { innerHTML: value };
1658
+ valueIsString = true;
1659
+ }
1660
+ }
1661
+ const props = ["innerHTML", "title", "id", "className"].concat(extraprops);
1662
+ setProperties(el, value, ...props);
1663
+ if (writesFromDom(this, context)) {
1664
+ trackDomField.call(this, context.element, props, valueIsString);
1665
+ }
1666
+ }
1667
+ function setProperties(el, data, ...properties) {
1668
+ if (!data || typeof data !== "object") {
1669
+ return;
1670
+ }
1671
+ for (const property of properties) {
1672
+ if (typeof data[property] === "undefined") {
1673
+ continue;
1674
+ }
1675
+ if (matchValue(el[property], data[property])) {
1676
+ continue;
1677
+ }
1678
+ if (data[property] === null) {
1679
+ el[property] = "";
1680
+ } else {
1681
+ el[property] = "" + data[property];
1682
+ }
1683
+ }
1684
+ }
1685
+ function updateProperties(context, properties) {
1686
+ trackDomField.call(this, context.element, properties, false);
1687
+ }
1688
+ function getProperties(el, ...properties) {
1689
+ const result = {};
1690
+ for (const property of properties) {
1691
+ switch (property) {
1692
+ default:
1693
+ result[property] = el[property];
1694
+ break;
1695
+ }
1696
+ }
1697
+ return result;
1698
+ }
1699
+ function matchValue(a, b) {
1700
+ if (a == ":empty" && !b) {
1701
+ return true;
1702
+ }
1703
+ if (b == ":empty" && !a) {
1704
+ return true;
1705
+ }
1706
+ if ("" + a == "" + b) {
1707
+ return true;
1708
+ }
1709
+ return false;
1710
+ }
1711
+
1712
+ // src/bind.mjs
1713
+ var SimplyBind = class {
1714
+ /**
1715
+ * @param Object options - a set of options for this instance, options may include:
1716
+ * - root (signal) (required) - the root data object that contains al signals that can be bound
1717
+ * - container (HTMLElement) - the dom element to use as the root for all bindings
1718
+ * - attribute (string) - the prefix for the field, edit, list and map attributes, e.g. 'data-bind'
1719
+ * - transformers (object name:function) - a map of transformer names and functions
1720
+ * - render (object with field, list and map properties); edit uses field renderers
1721
+ */
1722
+ constructor(options) {
1723
+ this.bindings = /* @__PURE__ */ new Map();
1724
+ const defaultTransformers = {
1725
+ escape_html,
1726
+ fixed_content
1727
+ };
1728
+ const defaultOptions = {
1729
+ container: document.body,
1730
+ attribute: "data-flow",
1731
+ transformers: defaultTransformers,
1732
+ render: {
1733
+ field: [field],
1734
+ list: [list],
1735
+ map: [map]
1736
+ },
1737
+ renderers: {
1738
+ "INPUT": input,
1739
+ "TEXTAREA": input,
1740
+ "BUTTON": button,
1741
+ "SELECT": select,
1742
+ "A": anchor,
1743
+ "IMG": image,
1744
+ "IFRAME": iframe,
1745
+ "META": meta,
1746
+ "TEMPLATE": null,
1747
+ "*": element
1748
+ },
1749
+ twoway: false
1750
+ };
1751
+ if (!options?.root) {
1752
+ throw new Error("bind needs at least options.root set");
1753
+ }
1754
+ this.options = Object.assign({}, defaultOptions, options);
1755
+ if (options.transformers) {
1756
+ this.options.transformers = Object.assign({}, defaultTransformers, options?.transformers);
1757
+ }
1758
+ const attribute = this.options.attribute;
1759
+ const bindAttributes = [attribute + "-field", attribute + "-edit", attribute + "-list", attribute + "-map"];
1760
+ const transformAttribute = attribute + "-transform";
1761
+ const getBindingAttribute = (el) => {
1762
+ const foundAttribute = bindAttributes.find((attr) => el.hasAttribute(attr));
1763
+ if (!foundAttribute) {
1764
+ console.error("No matching attribute found", el, bindAttributes);
1765
+ }
1766
+ return foundAttribute;
1767
+ };
1768
+ const renderElement = (el) => {
1769
+ this.bindings.set(el, throttledEffect(() => {
1770
+ if (!el.isConnected) {
1771
+ untrack(el, this.getBindingPath(el));
1772
+ const binding = this.bindings.get(el);
1773
+ if (binding) {
1774
+ destroy(binding);
1775
+ this.bindings.delete(el);
1776
+ }
1777
+ return;
1778
+ }
1779
+ let context = {
1780
+ templates: el.querySelectorAll(":scope > template"),
1781
+ attribute: getBindingAttribute(el)
1782
+ };
1783
+ context.edit = context.attribute === this.options.attribute + "-edit";
1784
+ context.path = this.getBindingPath(el);
1785
+ context.value = getValueByPath(this.options.root, context.path);
1786
+ context.element = el;
1787
+ track(el, context);
1788
+ runTransformers(context);
1789
+ }, 50));
1790
+ };
1791
+ const runTransformers = (context) => {
1792
+ let transformers;
1793
+ switch (context.attribute) {
1794
+ case this.options.attribute + "-field":
1795
+ case this.options.attribute + "-edit":
1796
+ transformers = Array.from(this.options.render.field);
1797
+ break;
1798
+ case this.options.attribute + "-list":
1799
+ transformers = Array.from(this.options.render.list);
1800
+ break;
1801
+ case this.options.attribute + "-map":
1802
+ transformers = Array.from(this.options.render.map);
1803
+ break;
1804
+ default:
1805
+ throw new Error("no valid context attribute specified", context);
1806
+ break;
1807
+ }
1808
+ if (context.element.hasAttribute(transformAttribute)) {
1809
+ context.element.getAttribute(transformAttribute).split(" ").filter(Boolean).forEach((t) => {
1810
+ if (this.options.transformers[t]) {
1811
+ transformers.push(this.options.transformers[t]);
1812
+ } else {
1813
+ console.warn("No transformer with name " + t + " configured", { cause: context.element });
1814
+ }
1815
+ });
1816
+ }
1817
+ let next;
1818
+ for (let transformer of transformers) {
1819
+ next = /* @__PURE__ */ ((next2, transformer2) => {
1820
+ return (context2) => {
1821
+ return transformer2.call(this, context2, next2);
1822
+ };
1823
+ })(next, transformer);
1824
+ }
1825
+ next(context);
1826
+ };
1827
+ const applyBindings = (bindings2) => {
1828
+ for (let bindingEl of bindings2) {
1829
+ if (!this.bindings.get(bindingEl)) {
1830
+ renderElement(bindingEl);
1831
+ }
1832
+ }
1833
+ };
1834
+ const updateBindings = (changes) => {
1835
+ const selector = `[${attribute}-field],[${attribute}-edit],[${attribute}-list],[${attribute}-map]`;
1836
+ for (const change of changes) {
1837
+ if (change.type == "childList" && change.addedNodes) {
1838
+ for (let node of change.addedNodes) {
1839
+ if (node instanceof HTMLElement) {
1840
+ let bindings2 = Array.from(node.querySelectorAll(selector));
1841
+ if (node.matches(selector)) {
1842
+ bindings2.unshift(node);
1843
+ }
1844
+ if (bindings2.length) {
1845
+ applyBindings(bindings2);
1846
+ }
1847
+ }
1848
+ }
1849
+ }
1850
+ }
1851
+ };
1852
+ this.observer = new MutationObserver((changes) => {
1853
+ updateBindings(changes);
1854
+ });
1855
+ this.observer.observe(this.options.container, {
1856
+ subtree: true,
1857
+ childList: true
1858
+ });
1859
+ const bindings = this.options.container.querySelectorAll(
1860
+ ":is([" + this.options.attribute + "-field],[" + this.options.attribute + "-edit],[" + this.options.attribute + "-list],[" + this.options.attribute + "-map]):not(template)"
1861
+ );
1862
+ try {
1863
+ if (bindings.length) {
1864
+ applyBindings(bindings);
1865
+ }
1866
+ } catch (error) {
1867
+ this.destroy();
1868
+ throw error;
1869
+ }
1870
+ }
1871
+ /**
1872
+ * Finds the first matching template and creates a new DocumentFragment
1873
+ * with the correct data bind attributes in it (prepends the current path)
1874
+ * @param Context context
1875
+ * @return DocumentFragment
1876
+ */
1877
+ applyTemplate(context) {
1878
+ const path2 = context.path;
1879
+ const parent = context.parent;
1880
+ const templates = context.templates;
1881
+ const list2 = context.list;
1882
+ const index = context.index;
1883
+ const value = list2 ? list2[index] : context.value;
1884
+ let template = this.findTemplate(templates, value);
1885
+ if (!template) {
1886
+ let result = new DocumentFragment();
1887
+ result.innerHTML = "<!-- no matching template -->";
1888
+ return result;
1889
+ }
1890
+ let clone2 = template.content.cloneNode(true);
1891
+ if (!clone2.children?.length) {
1892
+ return clone2;
1893
+ }
1894
+ if (clone2.children.length > 1) {
1895
+ throw new Error("template must contain a single root node", { cause: template });
1896
+ }
1897
+ const attribute = this.options.attribute;
1898
+ const attributes = [attribute + "-field", attribute + "-edit", attribute + "-list", attribute + "-map"];
1899
+ const bindings = clone2.querySelectorAll(`[${attribute}-field],[${attribute}-edit],[${attribute}-list],[${attribute}-map]`);
1900
+ for (let binding of bindings) {
1901
+ if (binding.tagName == "TEMPLATE") {
1902
+ continue;
1903
+ }
1904
+ const attr = attributes.find((attr2) => binding.hasAttribute(attr2));
1905
+ let bind2 = binding.getAttribute(attr);
1906
+ bind2 = this.applyLinks(template.links, bind2);
1907
+ if (bind2.substring(0, ":root.".length) == ":root.") {
1908
+ binding.setAttribute(attr, bind2.substring(":root.".length));
1909
+ } else if (bind2 == ":value" && index != null) {
1910
+ binding.setAttribute(attr, path2 + "." + index);
1911
+ } else if (index != null) {
1912
+ binding.setAttribute(attr, path2 + "." + index + "." + bind2);
1913
+ } else {
1914
+ binding.setAttribute(attr, parent + bind2);
1915
+ }
1916
+ }
1917
+ this.applyTemplateCommandValues(clone2, template.links, path2, index);
1918
+ if (typeof index !== "undefined") {
1919
+ clone2.children[0].setAttribute(attribute + "-key", index);
1920
+ }
1921
+ clone2.children[0][DEP.TEMPLATE] = template;
1922
+ clone2.children[0][DEP.VALUE] = value;
1923
+ return clone2;
1924
+ }
1925
+ applyTemplateCommandValues(fragment, links, path2, index) {
1926
+ const valueAttribute = this.options.attribute + "-value";
1927
+ const valuePathAttribute = this.options.attribute + "-value-path";
1928
+ const valueSelector = "[" + valueAttribute + "]";
1929
+ const elements = Array.from(fragment.querySelectorAll(valueSelector));
1930
+ for (const element2 of elements) {
1931
+ let value = element2.getAttribute(valueAttribute);
1932
+ value = this.applyLinks(links, value);
1933
+ const resolved = templateCommandValue(value, path2, index);
1934
+ if (!resolved) {
1935
+ continue;
1936
+ }
1937
+ if (Object.hasOwn(resolved, "path")) {
1938
+ element2.setAttribute(valuePathAttribute, resolved.path);
1939
+ } else {
1940
+ element2.setAttribute(valueAttribute, resolved.value);
1941
+ element2.removeAttribute(valuePathAttribute);
1942
+ }
1943
+ }
1944
+ }
1945
+ parseLinks(links) {
1946
+ let result = {};
1947
+ links = links.split(";").map((link) => link.trim());
1948
+ for (let link of links) {
1949
+ link = link.split("=");
1950
+ result[link[0].trim()] = link[1].trim();
1951
+ }
1952
+ return result;
1953
+ }
1954
+ applyLinks(links, value) {
1955
+ for (let link in links) {
1956
+ if (value.startsWith(link + ".")) {
1957
+ return links[link] + value.substr(link.length);
1958
+ } else if (value == link) {
1959
+ return links[link];
1960
+ }
1961
+ }
1962
+ return value;
1963
+ }
1964
+ /**
1965
+ * Returns the path referenced in either the field, list or map attribute
1966
+ * @param HTMLElement el
1967
+ * @return string The path referenced, or void
1968
+ */
1969
+ getBindingPath(el) {
1970
+ const attributes = [
1971
+ this.options.attribute + "-field",
1972
+ this.options.attribute + "-edit",
1973
+ this.options.attribute + "-list",
1974
+ this.options.attribute + "-map"
1975
+ ];
1976
+ for (let attr of attributes) {
1977
+ if (el.hasAttribute(attr)) {
1978
+ return el.getAttribute(attr);
1979
+ }
1980
+ }
1981
+ }
1982
+ /**
1983
+ * Finds the first template from an array of templates that
1984
+ * matches the given value.
1985
+ */
1986
+ findTemplate(templates, value) {
1987
+ const templateMatches = (t) => {
1988
+ let path2 = this.getBindingPath(t);
1989
+ let currentItem;
1990
+ if (path2) {
1991
+ if (path2.substr(0, 6) == ":root.") {
1992
+ currentItem = getValueByPath(this.options.root, path2.substring(6));
1993
+ } else {
1994
+ currentItem = getValueByPath(value, path2);
1995
+ }
1996
+ } else {
1997
+ currentItem = value;
1998
+ }
1999
+ const strItem = "" + currentItem;
2000
+ let matches = t.getAttribute(this.options.attribute + "-match");
2001
+ if (matches) {
2002
+ if (matches === ":empty" && !currentItem) {
2003
+ return t;
2004
+ } else if (matches === ":notempty" && currentItem) {
2005
+ return t;
2006
+ }
2007
+ if (strItem == matches) {
2008
+ return t;
2009
+ }
2010
+ }
2011
+ if (!matches) {
2012
+ return t;
2013
+ }
2014
+ };
2015
+ let template = Array.from(templates).find(templateMatches);
2016
+ let links = null;
2017
+ if (template?.hasAttribute(this.options.attribute + "-link")) {
2018
+ links = this.parseLinks(template.getAttribute(this.options.attribute + "-link"));
2019
+ }
2020
+ let rel = template?.getAttribute("rel");
2021
+ if (rel) {
2022
+ let replacement = document.querySelector("template#" + rel);
2023
+ if (!replacement) {
2024
+ throw new Error("Could not find template with id " + rel);
2025
+ }
2026
+ template = replacement;
2027
+ }
2028
+ if (template) {
2029
+ template.links = links;
2030
+ }
2031
+ return template;
2032
+ }
2033
+ destroy() {
2034
+ this.bindings.forEach((binding, element2) => {
2035
+ untrack(element2, this.getBindingPath(element2));
2036
+ destroy(binding);
2037
+ });
2038
+ this.bindings = /* @__PURE__ */ new Map();
2039
+ this.observer.disconnect();
2040
+ }
2041
+ };
2042
+ function bind(options) {
2043
+ return new SimplyBind(options);
2044
+ }
2045
+ var tracking = /* @__PURE__ */ new Map();
2046
+ function track(el, context) {
2047
+ untrack(el);
2048
+ if (!tracking.has(context.path)) {
2049
+ tracking.set(context.path, [context]);
2050
+ } else {
2051
+ tracking.get(context.path).push(context);
2052
+ }
2053
+ }
2054
+ function untrack(el, path2) {
2055
+ if (path2) {
2056
+ let list2 = tracking.get(path2);
2057
+ if (list2) {
2058
+ list2 = list2.filter((context) => context.element !== el);
2059
+ tracking.set(path2, list2);
2060
+ }
2061
+ return;
2062
+ }
2063
+ tracking.forEach((list2, trackedPath) => {
2064
+ list2 = list2.filter((context) => context.element !== el);
2065
+ tracking.set(trackedPath, list2);
2066
+ });
2067
+ }
2068
+ function templateCommandValue(value, path2, index) {
2069
+ if (!value || value[0] !== ":") {
2070
+ return null;
2071
+ }
2072
+ if (value === ":key") {
2073
+ return { value: "" + index };
2074
+ }
2075
+ if (value === ":value") {
2076
+ return { path: templateItemPath(path2, index) };
2077
+ }
2078
+ if (value.startsWith(":value.")) {
2079
+ return { path: joinPath(templateItemPath(path2, index), value.substring(":value".length)) };
2080
+ }
2081
+ if (value.startsWith(":root.")) {
2082
+ return { path: value.substring(":root.".length) };
2083
+ }
2084
+ return null;
2085
+ }
2086
+ function templateItemPath(path2, index) {
2087
+ if (typeof index === "undefined") {
2088
+ return path2;
2089
+ }
2090
+ return joinPath(path2, "." + index);
2091
+ }
2092
+ function joinPath(path2, suffix) {
2093
+ if (!path2) {
2094
+ return suffix.replace(/^\./, "");
2095
+ }
2096
+ return path2 + suffix;
2097
+ }
2098
+ function getValueByPath(root, path2) {
2099
+ let parts = path2.split(".");
2100
+ let curr = root;
2101
+ let part;
2102
+ part = parts.shift();
2103
+ let prevPart = null;
2104
+ while (part && curr) {
2105
+ part = decodeURIComponent(part);
2106
+ if (part == "0" && !Array.isArray(curr)) {
2107
+ } else if (part == ":key") {
2108
+ curr = prevPart;
2109
+ } else if (part == ":value") {
2110
+ } else if (Array.isArray(curr) && typeof curr[part] == "undefined" && curr[0]) {
2111
+ curr = curr[0][part];
2112
+ } else {
2113
+ curr = curr[part];
2114
+ }
2115
+ prevPart = part;
2116
+ part = parts.shift();
2117
+ }
2118
+ return curr;
2119
+ }
2120
+
2121
+ // src/model.mjs
2122
+ var model_exports = {};
2123
+ __export(model_exports, {
2124
+ columns: () => columns,
2125
+ filter: () => filter,
2126
+ model: () => model,
2127
+ paging: () => paging,
2128
+ scroll: () => scroll,
2129
+ sort: () => sort
2130
+ });
2131
+ var SimplyFlowModel = class {
2132
+ /**
2133
+ * Creates a new datamodel, with a state property that contains
2134
+ * all the data passed to this constructor
2135
+ * @param state Object with all the data for this model
2136
+ * @throws Error if state is not set
2137
+ */
2138
+ constructor(state) {
2139
+ if (!state) {
2140
+ throw new Error("no options set");
2141
+ }
2142
+ if (state.data == null || typeof state.data[Symbol.iterator] !== "function") {
2143
+ console.warn("SimplyFlowModel: options.data is not iterable");
2144
+ }
2145
+ this.state = signal(state);
2146
+ if (!this.state.options) {
2147
+ this.state.options = {};
2148
+ }
2149
+ this.effects = [{ current: this.state.data }];
2150
+ this.view = {
2151
+ current: this.state.data
2152
+ };
2153
+ }
2154
+ /**
2155
+ * Adds an effect to run whenever a signal it depends on
2156
+ * changes. this.state is the usual signal.
2157
+ * The `fn` function param is not itself an effect, but must return
2158
+ * and effect function. `fn` takes one param, which is the data signal.
2159
+ * This signal will always have at least a `current` property.
2160
+ * The result of the effect function is pushed on to the this.effects
2161
+ * list. And the last effect added is set as this.view
2162
+ */
2163
+ addEffect(fn) {
2164
+ if (!fn || typeof fn !== "function") {
2165
+ throw new Error("addEffect requires an effect function as its parameter", { cause: fn });
2166
+ }
2167
+ const dataSignal = this.effects[this.effects.length - 1];
2168
+ const connectedSignal = fn.call(this, dataSignal);
2169
+ if (!isSignal(connectedSignal)) {
2170
+ throw new Error("addEffect function parameter must return a Signal", { cause: fn });
2171
+ }
2172
+ this.view = connectedSignal;
2173
+ this.effects.push(this.view);
2174
+ }
2175
+ };
2176
+ function model(options) {
2177
+ return new SimplyFlowModel(options);
2178
+ }
2179
+ function sort(options = {}) {
2180
+ return function(data) {
2181
+ this.state.options.sort = Object.assign({
2182
+ direction: "asc",
2183
+ sortBy: null,
2184
+ sortFn: (a, b) => {
2185
+ const sort2 = this.state.options.sort;
2186
+ const sortBy = sort2.sortBy;
2187
+ if (!sort2.sortBy) {
2188
+ return 0;
2189
+ }
2190
+ const direction = sort2.sortDirection || sort2.direction || "asc";
2191
+ const larger = direction == "asc" ? 1 : -1;
2192
+ const smaller = direction == "asc" ? -1 : 1;
2193
+ if (typeof a?.[sortBy] === "undefined") {
2194
+ if (typeof b?.[sortBy] === "undefined") {
2195
+ return 0;
2196
+ }
2197
+ return larger;
2198
+ }
2199
+ if (typeof b?.[sortBy] === "undefined") {
2200
+ return smaller;
2201
+ }
2202
+ if (a[sortBy] < b[sortBy]) {
2203
+ return smaller;
2204
+ } else if (a[sortBy] > b[sortBy]) {
2205
+ return larger;
2206
+ } else {
2207
+ return 0;
2208
+ }
2209
+ }
2210
+ }, options);
2211
+ return throttledEffect(() => {
2212
+ const sort2 = this.state.options.sort;
2213
+ const direction = sort2?.sortDirection || sort2?.direction;
2214
+ if (sort2?.sortBy && direction) {
2215
+ const trackedSortFn = sort2.sortFn;
2216
+ const sortFn = raw(sort2).sortFn || trackedSortFn;
2217
+ return data.current.toSorted((a, b) => sortFn.call(this, a, b));
2218
+ }
2219
+ return data.current;
2220
+ }, 50);
2221
+ };
2222
+ }
2223
+ function paging(options = {}) {
2224
+ return function(data) {
2225
+ this.state.options.paging = Object.assign({
2226
+ page: 1,
2227
+ pageSize: 20,
2228
+ max: 1
2229
+ }, options);
2230
+ return throttledEffect(() => {
2231
+ return batch(() => {
2232
+ const paging2 = this.state.options.paging;
2233
+ if (!paging2.pageSize) {
2234
+ paging2.pageSize = 20;
2235
+ }
2236
+ paging2.max = Math.ceil(data.current.length / paging2.pageSize);
2237
+ paging2.page = Math.max(1, Math.min(paging2.max, paging2.page));
2238
+ const start = (paging2.page - 1) * paging2.pageSize;
2239
+ const end = start + paging2.pageSize;
2240
+ return data.current.slice(start, end);
2241
+ });
2242
+ }, 50);
2243
+ };
2244
+ }
2245
+ function filter(options) {
2246
+ if (!options?.name || typeof options.name !== "string") {
2247
+ throw new Error("filter requires options.name to be a string");
2248
+ }
2249
+ if (!options.matches || typeof options.matches !== "function") {
2250
+ throw new Error("filter requires options.matches to be a function");
2251
+ }
2252
+ return function(data) {
2253
+ if (this.state.options[options.name]) {
2254
+ throw new Error("a filter with this name already exists on this model");
2255
+ }
2256
+ this.state.options[options.name] = options;
2257
+ return throttledEffect(() => {
2258
+ const filterOptions = this.state.options[options.name];
2259
+ if (filterOptions.enabled) {
2260
+ const trackedMatches = filterOptions.matches;
2261
+ const matches = raw(filterOptions).matches || trackedMatches;
2262
+ return data.current.filter((row) => matches.call(this, row));
2263
+ }
2264
+ return data.current;
2265
+ }, 50);
2266
+ };
2267
+ }
2268
+ function columns(options = {}) {
2269
+ const columnOptions = options?.columns && typeof options.columns === "object" ? options.columns : options;
2270
+ if (!columnOptions || typeof columnOptions !== "object" || Object.keys(columnOptions).length === 0) {
2271
+ throw new Error("columns requires options to be an object with at least one property");
2272
+ }
2273
+ return function(data) {
2274
+ this.state.options.columns = columnOptions;
2275
+ return throttledEffect(() => {
2276
+ return data.current.map((input2) => {
2277
+ let result = {};
2278
+ for (let key of Object.keys(this.state.options.columns)) {
2279
+ if (!this.state.options.columns[key]?.hidden) {
2280
+ result[key] = input2[key] ?? null;
2281
+ }
2282
+ }
2283
+ return result;
2284
+ });
2285
+ }, 50);
2286
+ };
2287
+ }
2288
+ function scroll(options) {
2289
+ return function(data) {
2290
+ this.state.options.scroll = Object.assign({
2291
+ offset: 0,
2292
+ rowHeight: 26,
2293
+ rowCount: 20,
2294
+ itemsPerRow: 1,
2295
+ size: data.current.length
2296
+ }, options);
2297
+ const scrollOptions = this.state.options.scroll;
2298
+ const scrollbar = scrollOptions.scrollbar || scrollOptions.container?.querySelector("[data-flow-scrollbar]");
2299
+ if (scrollbar) {
2300
+ if (scrollOptions.container) {
2301
+ scrollOptions.container.addEventListener("scroll", (evt) => {
2302
+ scrollOptions.offset = Math.floor(
2303
+ scrollOptions.container.scrollTop / (scrollOptions.rowHeight * scrollOptions.itemsPerRow)
2304
+ );
2305
+ });
2306
+ }
2307
+ throttledEffect(() => {
2308
+ scrollOptions.size = data.current.length * scrollOptions.rowHeight;
2309
+ scrollbar.style.height = scrollOptions.size + "px";
2310
+ }, 50);
2311
+ }
2312
+ return throttledEffect(() => {
2313
+ if (scrollOptions.container) {
2314
+ scrollOptions.rowCount = Math.ceil(
2315
+ scrollOptions.container.getBoundingClientRect().height / scrollOptions.rowHeight
2316
+ );
2317
+ }
2318
+ scrollOptions.data = data.current;
2319
+ let start = Math.min(scrollOptions.offset, data.current.length - 1);
2320
+ let end = start + scrollOptions.rowCount;
2321
+ if (end > data.current.length) {
2322
+ end = data.current.length;
2323
+ start = end - scrollOptions.rowCount;
2324
+ }
2325
+ return data.current.slice(start, end);
2326
+ }, 50);
2327
+ };
2328
+ }
2329
+
2330
+ // src/render.mjs
2331
+ var SimplyRender = class extends HTMLElement {
2332
+ constructor() {
2333
+ super();
2334
+ }
2335
+ connectedCallback() {
2336
+ let templateId = this.getAttribute("rel");
2337
+ let template = document.getElementById(templateId);
2338
+ if (template) {
2339
+ let content = template.content.cloneNode(true);
2340
+ for (const node of content.childNodes) {
2341
+ const clone2 = node.cloneNode(true);
2342
+ if (clone2.nodeType == document.ELEMENT_NODE) {
2343
+ clone2.querySelectorAll("template").forEach(function(t) {
2344
+ t.setAttribute("simply-render", "");
2345
+ });
2346
+ if (this.attributes) {
2347
+ for (const attr of this.attributes) {
2348
+ if (attr.name != "rel") {
2349
+ clone2.setAttribute(attr.name, attr.value);
2350
+ }
2351
+ }
2352
+ }
2353
+ }
2354
+ this.parentNode.insertBefore(clone2, this);
2355
+ }
2356
+ this.parentNode.removeChild(this);
2357
+ } else {
2358
+ const observe = () => {
2359
+ const observer = new MutationObserver(() => {
2360
+ template = document.getElementById(templateId);
2361
+ if (template) {
2362
+ observer.disconnect();
2363
+ this.replaceWith(this);
2364
+ }
2365
+ });
2366
+ observer.observe(globalThis.document, {
2367
+ subtree: true,
2368
+ childList: true
2369
+ });
2370
+ };
2371
+ observe();
2372
+ }
2373
+ }
2374
+ };
2375
+ if (!customElements.get("simply-render")) {
2376
+ customElements.define("simply-render", SimplyRender);
2377
+ }
2378
+
2379
+ // src/suggest.mjs
2380
+ function closest(name, options, { maxDistance = 2, minLength = 4 } = {}) {
2381
+ if (name.length < minLength) {
2382
+ return;
2383
+ }
2384
+ let result;
2385
+ let resultDistance = Infinity;
2386
+ for (const option of options) {
2387
+ const distance = editDistance(name, option, maxDistance);
2388
+ if (distance < resultDistance) {
2389
+ result = option;
2390
+ resultDistance = distance;
2391
+ }
2392
+ }
2393
+ return resultDistance <= maxDistance ? result : void 0;
2394
+ }
2395
+ function editDistance(a, b, maxDistance = 2) {
2396
+ const tooFar = maxDistance + 1;
2397
+ if (Math.abs(a.length - b.length) > maxDistance) {
2398
+ return tooFar;
2399
+ }
2400
+ const previous = Array.from({ length: b.length + 1 }, (_, index) => index);
2401
+ const current = new Array(b.length + 1);
2402
+ for (let ai = 1; ai <= a.length; ai++) {
2403
+ current[0] = ai;
2404
+ for (let bi = 1; bi <= b.length; bi++) {
2405
+ const cost = a[ai - 1] === b[bi - 1] ? 0 : 1;
2406
+ current[bi] = Math.min(
2407
+ previous[bi] + 1,
2408
+ current[bi - 1] + 1,
2409
+ previous[bi - 1] + cost
2410
+ );
2411
+ }
2412
+ previous.splice(0, previous.length, ...current);
2413
+ }
2414
+ return previous[b.length];
2415
+ }
2416
+
2417
+ // src/route.mjs
2418
+ function routes(options) {
2419
+ return new SimplyRoute(options);
2420
+ }
2421
+ var SimplyRoute = class {
2422
+ constructor(options = {}) {
2423
+ this.options = options;
2424
+ this.baseURL = options.baseURL || "/";
2425
+ this.app = options.app || {};
2426
+ this.addMissingSlash = !!options.addMissingSlash;
2427
+ this.matchExact = !!options.matchExact;
2428
+ this.hijackLinks = !!options.hijackLinks;
2429
+ this.clear();
2430
+ if (options.routes) {
2431
+ this.load(options.routes);
2432
+ }
2433
+ }
2434
+ load(routes2) {
2435
+ parseRoutes(routes2, this.routeInfo, this.matchExact);
2436
+ }
2437
+ clear() {
2438
+ this.routeInfo = [];
2439
+ this.listeners = {
2440
+ match: {},
2441
+ call: {},
2442
+ goto: {},
2443
+ finish: {}
2444
+ };
2445
+ }
2446
+ match(path2, options) {
2447
+ let args = {
2448
+ path: path2,
2449
+ options
2450
+ };
2451
+ args = this.runListeners("match", args);
2452
+ path2 = args.path ? args.path : path2;
2453
+ let searchParams;
2454
+ if (!path2) {
2455
+ const currentPath = document.location.pathname + document.location.hash;
2456
+ if (this.has(currentPath)) {
2457
+ path2 = currentPath;
2458
+ } else {
2459
+ path2 = document.location.pathname;
2460
+ }
2461
+ searchParams = new URLSearchParams(document.location.search);
2462
+ } else {
2463
+ searchParams = searchParamsForPath(path2);
2464
+ }
2465
+ path2 = getPath(routePath(path2), this.baseURL);
2466
+ for (let route of this.routeInfo) {
2467
+ let params = route.pattern.match(path2);
2468
+ if (this.addMissingSlash && !params) {
2469
+ if (path2 && path2[path2.length - 1] != "/") {
2470
+ const pathWithSlash = path2 + "/";
2471
+ params = route.pattern.match(pathWithSlash);
2472
+ if (params) {
2473
+ path2 = pathWithSlash;
2474
+ history.replaceState({}, "", getURL(path2, this.baseURL));
2475
+ }
2476
+ }
2477
+ }
2478
+ if (params) {
2479
+ Object.assign(params, options);
2480
+ args.route = route;
2481
+ args.params = params;
2482
+ args = this.runListeners("call", args);
2483
+ params = args.params ? args.params : params;
2484
+ args.searchParams = searchParams;
2485
+ args.result = callRouteAction(this.app, route, params, searchParams);
2486
+ this.runListeners("finish", args);
2487
+ return args.result;
2488
+ }
2489
+ }
2490
+ return false;
2491
+ }
2492
+ runListeners(action, params) {
2493
+ if (!this.listeners[action] || !Object.keys(this.listeners[action])) {
2494
+ return;
2495
+ }
2496
+ Object.keys(this.listeners[action]).forEach((route) => {
2497
+ const pattern = compileRoutePattern(route);
2498
+ if (pattern.match(routePath(params.path))) {
2499
+ var result;
2500
+ for (let callback of this.listeners[action][route]) {
2501
+ result = callback.call(this.app, params);
2502
+ if (result) {
2503
+ params = result;
2504
+ }
2505
+ }
2506
+ }
2507
+ });
2508
+ return params;
2509
+ }
2510
+ handleEvents() {
2511
+ this.removeEvents();
2512
+ const popstateHandler = () => {
2513
+ this.match();
2514
+ };
2515
+ const clickHandler = (evt) => {
2516
+ if (evt.ctrlKey) {
2517
+ return;
2518
+ }
2519
+ if (evt.which != 1) {
2520
+ return;
2521
+ }
2522
+ var link = evt.target;
2523
+ while (link && link.tagName != "A") {
2524
+ link = link.parentElement;
2525
+ }
2526
+ if (link && link.pathname && link.hostname == globalThis.location.hostname && !link.link && !link.dataset.simplyCommand) {
2527
+ let check = [
2528
+ { match: link.hash, goto: link.hash },
2529
+ { match: link.pathname + link.hash, goto: link.pathname + link.search + link.hash },
2530
+ { match: link.pathname, goto: link.pathname + link.search }
2531
+ ];
2532
+ let target;
2533
+ do {
2534
+ target = check.shift();
2535
+ target.match = getPath(target.match, this.baseURL);
2536
+ } while (check.length && !this.has(target.match));
2537
+ if (this.has(target.match)) {
2538
+ let params = this.runListeners("goto", { path: target.goto });
2539
+ if (params.path) {
2540
+ const followLink = this.goto(params.path);
2541
+ if (!followLink || this.options.hijackLinks && followLink !== false) {
2542
+ evt.preventDefault();
2543
+ return false;
2544
+ }
2545
+ }
2546
+ }
2547
+ }
2548
+ };
2549
+ globalThis.addEventListener("popstate", popstateHandler);
2550
+ this.app.container.addEventListener("click", clickHandler);
2551
+ this.eventHandlers = {
2552
+ container: this.app.container,
2553
+ popstateHandler,
2554
+ clickHandler
2555
+ };
2556
+ }
2557
+ removeEvents() {
2558
+ if (!this.eventHandlers) {
2559
+ return;
2560
+ }
2561
+ globalThis.removeEventListener("popstate", this.eventHandlers.popstateHandler);
2562
+ this.eventHandlers.container.removeEventListener("click", this.eventHandlers.clickHandler);
2563
+ this.eventHandlers = void 0;
2564
+ }
2565
+ destroy() {
2566
+ this.removeEvents();
2567
+ }
2568
+ goto(path2) {
2569
+ history.pushState({}, "", getURL(path2, this.baseURL));
2570
+ return this.match(path2);
2571
+ }
2572
+ has(path2) {
2573
+ path2 = getPath(routePath(path2), this.baseURL);
2574
+ for (let route of this.routeInfo) {
2575
+ if (route.pattern.match(path2)) {
2576
+ return true;
2577
+ }
2578
+ }
2579
+ return false;
2580
+ }
2581
+ addListener(action, route, callback) {
2582
+ if (["goto", "match", "call", "finish"].indexOf(action) == -1) {
2583
+ throw new TypeError(`simplyflow/route: unknown listener type "${action}"`);
2584
+ }
2585
+ if (!this.listeners[action][route]) {
2586
+ this.listeners[action][route] = [];
2587
+ }
2588
+ this.listeners[action][route].push(callback);
2589
+ }
2590
+ removeListener(action, route, callback) {
2591
+ if (["goto", "match", "call", "finish"].indexOf(action) == -1) {
2592
+ throw new TypeError(`simplyflow/route: unknown listener type "${action}"`);
2593
+ }
2594
+ if (!this.listeners[action][route]) {
2595
+ return;
2596
+ }
2597
+ this.listeners[action][route] = this.listeners[action][route].filter((listener) => {
2598
+ return listener != callback;
2599
+ });
2600
+ }
2601
+ init(options) {
2602
+ if (options.baseURL) {
2603
+ this.baseURL = options.baseURL;
2604
+ }
2605
+ }
2606
+ };
2607
+ function callRouteAction(app2, route, params, searchParams) {
2608
+ if (typeof route.action === "function") {
2609
+ return route.action.call(app2, params, searchParams);
2610
+ }
2611
+ if (typeof route.action === "string") {
2612
+ const action = app2.actions?.[route.action];
2613
+ if (typeof action === "function") {
2614
+ return action.call(app2, routeActionParams(route, params, searchParams));
2615
+ }
2616
+ throw unknownRouteActionError(route, app2.actions);
2617
+ }
2618
+ throw new TypeError(`simplyflow/route: route "${route.path}" must use a function or action name`);
2619
+ }
2620
+ var warnedRouteQueryConflicts = /* @__PURE__ */ new Set();
2621
+ function routeActionParams(route, params, searchParams) {
2622
+ const query = queryParams(searchParams);
2623
+ for (const key of Object.keys(query)) {
2624
+ if (Object.hasOwn(params, key)) {
2625
+ warnRouteQueryConflict(route, key);
2626
+ }
2627
+ }
2628
+ return Object.assign(query, params);
2629
+ }
2630
+ function queryParams(searchParams) {
2631
+ const params = {};
2632
+ for (const [key, value] of searchParams.entries()) {
2633
+ if (!Object.hasOwn(params, key)) {
2634
+ params[key] = value;
2635
+ } else if (Array.isArray(params[key])) {
2636
+ params[key].push(value);
2637
+ } else {
2638
+ params[key] = [params[key], value];
2639
+ }
2640
+ }
2641
+ return params;
2642
+ }
2643
+ function warnRouteQueryConflict(route, key) {
2644
+ const warningKey = `${route.path}\0${key}`;
2645
+ if (warnedRouteQueryConflicts.has(warningKey)) {
2646
+ return;
2647
+ }
2648
+ warnedRouteQueryConflicts.add(warningKey);
2649
+ console.warn(`simplyflow/route: query parameter "${key}" was ignored because route "${route.path}" already provides a route parameter with that name.`);
2650
+ }
2651
+ function unknownRouteActionError(route, actions2) {
2652
+ const suggestion = closest(route.action, Object.keys(actions2 || {}));
2653
+ const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
2654
+ return new TypeError(`simplyflow/route: route "${route.path}" uses unknown action "${route.action}".${hint}`);
2655
+ }
2656
+ function searchParamsForPath(path2) {
2657
+ const index = typeof path2 === "string" ? path2.indexOf("?") : -1;
2658
+ if (index === -1) {
2659
+ return new URLSearchParams();
2660
+ }
2661
+ const hashIndex = path2.indexOf("#", index);
2662
+ const search = hashIndex === -1 ? path2.substring(index) : path2.substring(index, hashIndex);
2663
+ return new URLSearchParams(search);
2664
+ }
2665
+ function routePath(path2) {
2666
+ const index = typeof path2 === "string" ? path2.indexOf("?") : -1;
2667
+ if (index === -1) {
2668
+ return path2;
2669
+ }
2670
+ const hashIndex = path2.indexOf("#", index);
2671
+ if (hashIndex === -1) {
2672
+ return path2.substring(0, index);
2673
+ }
2674
+ return path2.substring(0, index) + path2.substring(hashIndex);
2675
+ }
2676
+ function getPath(path2, baseURL = "/") {
2677
+ if (path2.substring(0, baseURL.length) == baseURL || baseURL[baseURL.length - 1] == "/" && path2.length == baseURL.length - 1 && path2 == baseURL.substring(0, path2.length)) {
2678
+ path2 = path2.substring(baseURL.length);
2679
+ }
2680
+ if (path2[0] != "/") {
2681
+ path2 = "/" + path2;
2682
+ }
2683
+ return path2;
2684
+ }
2685
+ function getURL(path2, baseURL) {
2686
+ path2 = getPath(path2, baseURL);
2687
+ if (baseURL[baseURL.length - 1] === "/" && path2[0] === "/") {
2688
+ path2 = path2.substring(1);
2689
+ }
2690
+ if (path2[0] == "#") {
2691
+ return path2;
2692
+ }
2693
+ return baseURL + path2;
2694
+ }
2695
+ function compileRoutePattern(path2, exact = false) {
2696
+ const params = [];
2697
+ const regexp = routeRegexp(path2, exact, params);
2698
+ return {
2699
+ path: path2,
2700
+ params,
2701
+ regexp,
2702
+ match(value) {
2703
+ const matches = regexp.exec(value);
2704
+ if (!matches) {
2705
+ return null;
2706
+ }
2707
+ const result = {};
2708
+ params.forEach((name, i) => {
2709
+ result[name] = matches[i + 1];
2710
+ });
2711
+ return result;
2712
+ }
2713
+ };
2714
+ }
2715
+ function routeRegexp(route, exact = false, params = []) {
2716
+ if (route.includes(":*")) {
2717
+ throw new TypeError(`simplyflow/route: route "${route}" uses the old wildcard syntax ":*". Use a named wildcard like ":path*" instead.`);
2718
+ }
2719
+ const prefix = route[0] === "#" ? "" : "^";
2720
+ const suffix = exact ? "$" : "";
2721
+ return new RegExp(prefix + routeRegexpSource(route, params) + suffix);
2722
+ }
2723
+ function routeRegexpSource(route, params) {
2724
+ let source = "";
2725
+ let index = 0;
2726
+ while (index < route.length) {
2727
+ if (route[index] === ":") {
2728
+ const match = /^:([A-Za-z_][A-Za-z0-9_]*)(\*)?/.exec(route.substring(index));
2729
+ if (!match) {
2730
+ throw new TypeError(`simplyflow/route: invalid route parameter in "${route}"`);
2731
+ }
2732
+ params.push(match[1]);
2733
+ source += match[2] ? "(.*)" : "([^/]+)";
2734
+ index += match[0].length;
2735
+ continue;
2736
+ }
2737
+ if (route[index] === "*") {
2738
+ source += ".*";
2739
+ index++;
2740
+ continue;
2741
+ }
2742
+ source += escapeRegexp(route[index]);
2743
+ index++;
2744
+ }
2745
+ return source;
2746
+ }
2747
+ function escapeRegexp(value) {
2748
+ return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
2749
+ }
2750
+ function parseRoutes(routes2, routeInfo, exact = false) {
2751
+ const paths = Object.keys(routes2);
2752
+ for (let path2 of paths) {
2753
+ routeInfo.push({
2754
+ path: path2,
2755
+ pattern: compileRoutePattern(path2, exact),
2756
+ action: routes2[path2]
2757
+ });
2758
+ }
2759
+ return routeInfo;
2760
+ }
2761
+
2762
+ // src/path.mjs
2763
+ var path = {
2764
+ get(dataset, pointer) {
2765
+ if (typeof pointer !== "string") {
2766
+ return pointer;
2767
+ }
2768
+ if (!pointer) {
2769
+ return dataset;
2770
+ }
2771
+ return pointer.split(".").reduce(function(acc, name) {
2772
+ if (acc == null) {
2773
+ return null;
2774
+ }
2775
+ if (!Reflect.has(Object(acc), name)) {
2776
+ return null;
2777
+ }
2778
+ return acc[name];
2779
+ }, dataset);
2780
+ },
2781
+ set: function(dataset, pointer, value) {
2782
+ const parent = path.get(dataset, path.parent(pointer));
2783
+ if (parent == null) {
2784
+ throw new TypeError(`simplyflow/path: cannot set "${pointer}" because its parent path does not exist`);
2785
+ }
2786
+ parent[path.pop(pointer)] = value;
2787
+ },
2788
+ pop: function(pointer) {
2789
+ return pointer.split(".").pop();
2790
+ },
2791
+ push: function(pointer, name) {
2792
+ return (pointer ? pointer + "." : "") + name;
2793
+ },
2794
+ parent: function(pointer) {
2795
+ const names = pointer.split(".");
2796
+ names.pop();
2797
+ return names.join(".");
2798
+ },
2799
+ parents: function(dataset, pointer) {
2800
+ let result = [];
2801
+ while (pointer) {
2802
+ pointer = path.parent(pointer);
2803
+ result.unshift(pointer);
2804
+ }
2805
+ return result;
2806
+ }
2807
+ };
2808
+ var path_default = path;
2809
+
2810
+ // src/command.mjs
2811
+ var commandState = /* @__PURE__ */ new WeakMap();
2812
+ var COMMAND_OPTIONS = [
2813
+ "commands",
2814
+ "handlers",
2815
+ "app",
2816
+ "container"
2817
+ ];
2818
+ var SimplyCommands = class {
2819
+ constructor(options = {}) {
2820
+ if (!options.app) {
2821
+ options.app = {};
2822
+ }
2823
+ if (!options.app.container) {
2824
+ options.app.container = document.body;
2825
+ }
2826
+ this.app = options.app;
2827
+ this.$handlers = options.handlers || defaultHandlers;
2828
+ if (options.commands) {
2829
+ Object.assign(this, options.commands);
2830
+ }
2831
+ const commandHandler = (evt) => {
2832
+ const command = getCommand(evt, this.$handlers, this.app);
2833
+ if (!command) {
2834
+ return;
2835
+ }
2836
+ if (!this[command.name]) {
2837
+ warnUnknownCommand(this, command.name, command.source);
2838
+ return;
2839
+ }
2840
+ const shouldContinue = this[command.name].call(options.app, command.source, command.value, evt);
2841
+ if (shouldContinue !== true) {
2842
+ evt.preventDefault();
2843
+ evt.stopPropagation();
2844
+ return false;
2845
+ }
2846
+ };
2847
+ const container = options.app.container;
2848
+ container.addEventListener("click", commandHandler);
2849
+ container.addEventListener("submit", commandHandler);
2850
+ container.addEventListener("change", commandHandler);
2851
+ container.addEventListener("input", commandHandler);
2852
+ commandState.set(this, { container, commandHandler });
2853
+ }
2854
+ call(command, el, value, event) {
2855
+ if (!this[command]) {
2856
+ warnUnknownCommand(this, command, el);
2857
+ return;
2858
+ }
2859
+ return this[command].call(this.app, el, value, event);
2860
+ }
2861
+ appendHandler(handler) {
2862
+ this.$handlers.push(handler);
2863
+ }
2864
+ prependHandler(handler) {
2865
+ this.$handlers.unshift(handler);
2866
+ }
2867
+ };
2868
+ function commands(options = {}) {
2869
+ return new SimplyCommands(options);
2870
+ }
2871
+ function destroyCommands(commandApi) {
2872
+ const state = commandState.get(commandApi);
2873
+ if (!state) {
2874
+ return;
2875
+ }
2876
+ state.container.removeEventListener("click", state.commandHandler);
2877
+ state.container.removeEventListener("submit", state.commandHandler);
2878
+ state.container.removeEventListener("change", state.commandHandler);
2879
+ state.container.removeEventListener("input", state.commandHandler);
2880
+ commandState.delete(commandApi);
2881
+ }
2882
+ function getCommand(evt, handlers, app2) {
2883
+ var el = evt.target.closest("[data-simply-command]");
2884
+ if (el) {
2885
+ for (let handler of handlers) {
2886
+ if (el.matches(handler.match)) {
2887
+ if (handler.check(el, evt)) {
2888
+ return {
2889
+ name: el.dataset.simplyCommand,
2890
+ source: el,
2891
+ value: handler.get(el, app2)
2892
+ };
2893
+ }
2894
+ return null;
2895
+ }
2896
+ }
2897
+ }
2898
+ return null;
2899
+ }
2900
+ function getConfiguredCommandValue(el, app2) {
2901
+ const pathAttribute = "simplyValuePath";
2902
+ if (Object.hasOwn(el.dataset, pathAttribute)) {
2903
+ return {
2904
+ found: true,
2905
+ value: path_default.get(app2?.data, el.dataset[pathAttribute])
2906
+ };
2907
+ }
2908
+ if (Object.hasOwn(el.dataset, "simplyValue")) {
2909
+ return { found: true, value: el.dataset.simplyValue };
2910
+ }
2911
+ return { found: false, value: void 0 };
2912
+ }
2913
+ var defaultHandlers = [
2914
+ {
2915
+ match: "input,select,textarea",
2916
+ get: function(el, app2) {
2917
+ const configuredValue = getConfiguredCommandValue(el, app2);
2918
+ if (configuredValue.found) {
2919
+ return configuredValue.value;
2920
+ }
2921
+ if (el.tagName === "SELECT" && el.multiple) {
2922
+ let values = [];
2923
+ for (let option of el.options) {
2924
+ if (option.selected) {
2925
+ values.push(option.value);
2926
+ }
2927
+ }
2928
+ return values;
2929
+ }
2930
+ return el.value;
2931
+ },
2932
+ check: function(el, evt) {
2933
+ return evt.type == "change" || el.dataset.simplyImmediate && evt.type == "input";
2934
+ }
2935
+ },
2936
+ {
2937
+ match: "a,button",
2938
+ get: function(el, app2) {
2939
+ const configuredValue = getConfiguredCommandValue(el, app2);
2940
+ if (configuredValue.found) {
2941
+ return configuredValue.value;
2942
+ }
2943
+ return el.href || el.value;
2944
+ },
2945
+ check: function(el, evt) {
2946
+ return evt.type == "click" && evt.ctrlKey == false && evt.button == 0;
2947
+ }
2948
+ },
2949
+ {
2950
+ match: "form",
2951
+ get: function(el) {
2952
+ let data = {};
2953
+ for (let input2 of Array.from(el.elements)) {
2954
+ if (input2.tagName == "INPUT" && (input2.type == "checkbox" || input2.type == "radio")) {
2955
+ if (!input2.checked) {
2956
+ return;
2957
+ }
2958
+ }
2959
+ if (data[input2.name] && !Array.isArray(data[input2.name])) {
2960
+ data[input2.name] = [data[input2.name]];
2961
+ }
2962
+ if (Array.isArray(data[input2.name])) {
2963
+ data[input2.name].push(input2.value);
2964
+ } else {
2965
+ data[input2.name] = input2.value;
2966
+ }
2967
+ }
2968
+ return data;
2969
+ },
2970
+ check: function(el, evt) {
2971
+ return evt.type == "submit";
2972
+ }
2973
+ },
2974
+ {
2975
+ match: "*",
2976
+ get: function(el, app2) {
2977
+ return getConfiguredCommandValue(el, app2).value;
2978
+ },
2979
+ check: function(el, evt) {
2980
+ return evt.type == "click" && evt.ctrlKey == false && evt.button == 0;
2981
+ }
2982
+ }
2983
+ ];
2984
+ var unknownCommandWarnings = /* @__PURE__ */ new WeakMap();
2985
+ function warnUnknownCommand(commands2, command, source) {
2986
+ let warned = unknownCommandWarnings.get(commands2);
2987
+ if (!warned) {
2988
+ warned = /* @__PURE__ */ new Set();
2989
+ unknownCommandWarnings.set(commands2, warned);
2990
+ }
2991
+ if (warned.has(command)) {
2992
+ return;
2993
+ }
2994
+ warned.add(command);
2995
+ const suggestion = closest(command, commandNames(commands2));
2996
+ const suffix = suggestion ? `. Did you mean "${suggestion}"?` : "";
2997
+ if (source) {
2998
+ console.warn(`simplyflow/command: unknown command "${command}"${suffix}`, { cause: source });
2999
+ } else {
3000
+ console.warn(`simplyflow/command: unknown command "${command}"${suffix}`);
3001
+ }
3002
+ }
3003
+ function commandNames(commands2) {
3004
+ return Object.keys(commands2).filter((command) => {
3005
+ return !command.startsWith("$") && !COMMAND_OPTIONS.includes(command) && typeof commands2[command] === "function";
3006
+ });
3007
+ }
3008
+
3009
+ // src/action.mjs
3010
+ var warnedUnknownActions = /* @__PURE__ */ new WeakMap();
3011
+ function actions(options) {
3012
+ if (options.app) {
3013
+ const functionHandler = {
3014
+ apply(target, thisArg, argumentsList) {
3015
+ try {
3016
+ const result = target(...argumentsList);
3017
+ if (result instanceof Promise) {
3018
+ return result.catch((err) => {
3019
+ return options.app.onError.call(this, err, target);
3020
+ });
3021
+ }
3022
+ return result;
3023
+ } catch (err) {
3024
+ return options.app.onError.call(this, err, target);
3025
+ }
3026
+ }
3027
+ };
3028
+ const actionHandler = {
3029
+ get(target, property) {
3030
+ if (!Object.hasOwn(target, property)) {
3031
+ warnUnknownAction(target, property);
3032
+ return void 0;
3033
+ }
3034
+ if (options.app.onError) {
3035
+ return new Proxy(target[property].bind(options.app), functionHandler);
3036
+ } else {
3037
+ return target[property].bind(options.app);
3038
+ }
3039
+ }
3040
+ };
3041
+ return new Proxy(options.actions, actionHandler);
3042
+ } else {
3043
+ return options;
3044
+ }
3045
+ }
3046
+ function warnUnknownAction(actions2, property) {
3047
+ if (typeof property !== "string") {
3048
+ return;
3049
+ }
3050
+ let warned = warnedUnknownActions.get(actions2);
3051
+ if (!warned) {
3052
+ warned = /* @__PURE__ */ new Set();
3053
+ warnedUnknownActions.set(actions2, warned);
3054
+ }
3055
+ if (warned.has(property)) {
3056
+ return;
3057
+ }
3058
+ warned.add(property);
3059
+ const suggestion = closest(property, Object.keys(actions2));
3060
+ const suffix = suggestion ? `. Did you mean "${suggestion}"?` : "";
3061
+ console.warn(`simplyflow/action: unknown action "${property}"${suffix}`);
3062
+ }
3063
+
3064
+ // src/shortcut.mjs
3065
+ var shortcutState = /* @__PURE__ */ new WeakMap();
3066
+ var accesskeyState = /* @__PURE__ */ new WeakMap();
3067
+ var KEY = Object.freeze({
3068
+ Compose: 229,
3069
+ Control: 17,
3070
+ Meta: 224,
3071
+ Alt: 18,
3072
+ Shift: 16
3073
+ });
3074
+ var SimplyShortcuts = class {
3075
+ constructor(options = {}) {
3076
+ if (!options.app) {
3077
+ options.app = {};
3078
+ }
3079
+ if (!options.app.container) {
3080
+ options.app.container = document.body;
3081
+ }
3082
+ Object.assign(this, options.shortcuts);
3083
+ const keyHandler = (e) => {
3084
+ let shortcutScopes = [];
3085
+ let shortcutElement = e.target.closest("[data-simply-shortcuts]");
3086
+ while (shortcutElement) {
3087
+ shortcutScopes.push(shortcutElement.dataset.simplyShortcuts);
3088
+ shortcutElement = shortcutElement.parentNode.closest("[data-simply-shortcuts]");
3089
+ }
3090
+ if (shortcutScopes[shortcutScopes.length - 1] != "default") {
3091
+ shortcutScopes.push("default");
3092
+ }
3093
+ let shortcutScope;
3094
+ let separators = ["+", "-"];
3095
+ for (let separator of separators) {
3096
+ const keyString = getKeyString(e, separator);
3097
+ for (let i in shortcutScopes) {
3098
+ shortcutScope = shortcutScopes[i];
3099
+ if (this[shortcutScope] && typeof this[shortcutScope][keyString] == "function") {
3100
+ let _continue = this[shortcutScope][keyString].call(options.app, e);
3101
+ if (!_continue) {
3102
+ e.preventDefault();
3103
+ return;
3104
+ }
3105
+ }
3106
+ if (typeof this[shortcutScope + "." + keyString] == "function") {
3107
+ let _continue = this[shortcutScope + "." + keyString].call(options.app, e);
3108
+ if (!_continue) {
3109
+ e.preventDefault();
3110
+ return;
3111
+ }
3112
+ }
3113
+ if (typeof this[keyString] == "function") {
3114
+ let _continue = this[keyString].call(options.app, e);
3115
+ if (!_continue) {
3116
+ e.preventDefault();
3117
+ return;
3118
+ }
3119
+ }
3120
+ }
3121
+ }
3122
+ };
3123
+ const container = options.app.container;
3124
+ container.addEventListener("keydown", keyHandler);
3125
+ shortcutState.set(this, { container, keyHandler });
3126
+ }
3127
+ };
3128
+ function getKeyString(e, separator = "+") {
3129
+ if (e.isComposing || e.keyCode === KEY.Compose) {
3130
+ return;
3131
+ }
3132
+ if (e.defaultPrevented) {
3133
+ return;
3134
+ }
3135
+ if (!e.target) {
3136
+ return;
3137
+ }
3138
+ let keyCombination = [];
3139
+ if (e.ctrlKey && e.keyCode != KEY.Control) {
3140
+ keyCombination.push("Control");
3141
+ }
3142
+ if (e.metaKey && e.keyCode != KEY.Meta) {
3143
+ keyCombination.push("Meta");
3144
+ }
3145
+ if (e.altKey && e.keyCode != KEY.Alt) {
3146
+ keyCombination.push("Alt");
3147
+ }
3148
+ if (e.shiftKey && e.keyCode != KEY.Shift) {
3149
+ keyCombination.push("Shift");
3150
+ }
3151
+ keyCombination.push(e.key.toLowerCase());
3152
+ return keyCombination.join(separator);
3153
+ }
3154
+ function shortcuts(options = {}) {
3155
+ return new SimplyShortcuts(options);
3156
+ }
3157
+ function destroyShortcuts(shortcutApi) {
3158
+ const state = shortcutState.get(shortcutApi);
3159
+ if (!state) {
3160
+ return;
3161
+ }
3162
+ state.container.removeEventListener("keydown", state.keyHandler);
3163
+ shortcutState.delete(shortcutApi);
3164
+ }
3165
+ function accesskeys(options = {}) {
3166
+ const container = options.container || options.app?.container || document.body;
3167
+ const keyHandler = (e) => {
3168
+ const separators = ["+", "-"];
3169
+ for (const separator of separators) {
3170
+ const keyString = getKeyString(e, separator);
3171
+ const selector = "[data-simply-accesskey='" + keyString + "']";
3172
+ const targets = container.querySelectorAll(selector);
3173
+ if (targets.length) {
3174
+ targets.forEach(function(target) {
3175
+ target.click();
3176
+ });
3177
+ }
3178
+ }
3179
+ };
3180
+ container.addEventListener("keydown", keyHandler);
3181
+ const controller = {};
3182
+ accesskeyState.set(controller, { container, keyHandler });
3183
+ return controller;
3184
+ }
3185
+ function destroyAccesskeys(accesskeyApi) {
3186
+ const state = accesskeyState.get(accesskeyApi);
3187
+ if (!state) {
3188
+ return;
3189
+ }
3190
+ state.container.removeEventListener("keydown", state.keyHandler);
3191
+ accesskeyState.delete(accesskeyApi);
3192
+ }
3193
+
3194
+ // src/behavior.mjs
3195
+ var BEHAVIOR_SELECTOR = "[data-simply-behavior]";
3196
+ var SimplyBehaviors = class {
3197
+ constructor(options = {}) {
3198
+ this.app = options.app;
3199
+ this.container = options.container || document.body;
3200
+ this.behaviors = options.behaviors || {};
3201
+ this.active = /* @__PURE__ */ new Set();
3202
+ this.cleanups = /* @__PURE__ */ new WeakMap();
3203
+ this.unknown = /* @__PURE__ */ new Set();
3204
+ this.observer = new MutationObserver((changes) => this.handleChanges(changes));
3205
+ this.observer.observe(this.container, {
3206
+ subtree: true,
3207
+ childList: true
3208
+ });
3209
+ for (const node of behaviorNodes(this.container)) {
3210
+ this.start(node);
3211
+ }
3212
+ }
3213
+ start(node) {
3214
+ if (this.active.has(node)) {
3215
+ return;
3216
+ }
3217
+ const name = node?.dataset?.simplyBehavior;
3218
+ const behavior = this.behaviors[name];
3219
+ if (!name || typeof behavior !== "function") {
3220
+ this.warnUnknown(name, node);
3221
+ return;
3222
+ }
3223
+ this.active.add(node);
3224
+ const cleanup = behavior.call(this.app || node, node);
3225
+ if (typeof cleanup === "function") {
3226
+ this.cleanups.set(node, cleanup);
3227
+ } else if (typeof cleanup !== "undefined") {
3228
+ console.warn("simplyflow/behavior: behavior may only return a cleanup function", { cause: cleanup });
3229
+ }
3230
+ }
3231
+ stop(node) {
3232
+ if (!this.active.has(node)) {
3233
+ return;
3234
+ }
3235
+ this.active.delete(node);
3236
+ const cleanup = this.cleanups.get(node);
3237
+ this.cleanups.delete(node);
3238
+ if (cleanup) {
3239
+ cleanup.call(this.app || node, node);
3240
+ }
3241
+ }
3242
+ handleChanges(changes) {
3243
+ const added = [];
3244
+ for (const change of changes) {
3245
+ if (change.type !== "childList") {
3246
+ continue;
3247
+ }
3248
+ for (const node of change.removedNodes) {
3249
+ for (const behaviorNode of behaviorNodes(node)) {
3250
+ this.stop(behaviorNode);
3251
+ }
3252
+ }
3253
+ for (const node of change.addedNodes) {
3254
+ added.push(...behaviorNodes(node));
3255
+ }
3256
+ }
3257
+ for (const node of added) {
3258
+ this.start(node);
3259
+ }
3260
+ }
3261
+ warnUnknown(name, node) {
3262
+ if (!name || this.unknown.has(name)) {
3263
+ return;
3264
+ }
3265
+ this.unknown.add(name);
3266
+ const suggestion = closest(name, Object.keys(this.behaviors));
3267
+ const suffix = suggestion ? `. Did you mean "${suggestion}"?` : "";
3268
+ console.warn(`simplyflow/behavior: unknown behavior "${name}"${suffix}`, { cause: node });
3269
+ }
3270
+ destroy() {
3271
+ this.observer.disconnect();
3272
+ for (const node of Array.from(this.active)) {
3273
+ this.stop(node);
3274
+ }
3275
+ }
3276
+ };
3277
+ function behaviors(options = {}) {
3278
+ return new SimplyBehaviors(options);
3279
+ }
3280
+ function behaviorNodes(root) {
3281
+ if (!root?.querySelectorAll) {
3282
+ return [];
3283
+ }
3284
+ const nodes = Array.from(root.querySelectorAll(BEHAVIOR_SELECTOR));
3285
+ if (root.matches?.(BEHAVIOR_SELECTOR)) {
3286
+ nodes.unshift(root);
3287
+ }
3288
+ return nodes;
3289
+ }
3290
+
3291
+ // src/include.mjs
3292
+ function throttle(callbackFunction, intervalTime) {
3293
+ let eventId = 0;
3294
+ return function throttledCallback(...params) {
3295
+ if (eventId) {
3296
+ return;
3297
+ }
3298
+ eventId = globalThis.setTimeout(() => {
3299
+ eventId = 0;
3300
+ callbackFunction.apply(this, params);
3301
+ }, intervalTime);
3302
+ };
3303
+ }
3304
+ var runWhenIdle = (() => {
3305
+ if (globalThis.requestIdleCallback) {
3306
+ return (callback) => {
3307
+ globalThis.requestIdleCallback(callback, { timeout: 500 });
3308
+ };
3309
+ }
3310
+ return globalThis.requestAnimationFrame || ((callback) => globalThis.setTimeout(callback, 0));
3311
+ })();
3312
+ function rebaseHref(relative, base, cacheBuster) {
3313
+ const url = new URL(relative, base);
3314
+ if (cacheBuster) {
3315
+ url.searchParams.set("cb", cacheBuster);
3316
+ }
3317
+ return url.href;
3318
+ }
3319
+ function cloneScript(script, base, cacheBuster) {
3320
+ const clone2 = globalThis.document.createElement("script");
3321
+ for (const attr of script.attributes) {
3322
+ clone2.setAttribute(attr.name, attr.value);
3323
+ }
3324
+ clone2.removeAttribute("data-simply-location");
3325
+ if (clone2.hasAttribute("src")) {
3326
+ clone2.src = rebaseHref(clone2.getAttribute("src"), base, cacheBuster);
3327
+ } else {
3328
+ clone2.textContent = script.textContent;
3329
+ }
3330
+ return clone2;
3331
+ }
3332
+ function insertScript(script, placeholder) {
3333
+ placeholder.parentNode.insertBefore(script, placeholder);
3334
+ placeholder.parentNode.removeChild(placeholder);
3335
+ }
3336
+ function shouldWaitForScript(script) {
3337
+ return script.hasAttribute("src") && !script.hasAttribute("async");
3338
+ }
3339
+ function insertAndWaitForScript(script, placeholder) {
3340
+ return new Promise((resolve) => {
3341
+ const done = () => {
3342
+ script.removeEventListener("load", done);
3343
+ script.removeEventListener("error", done);
3344
+ resolve();
3345
+ };
3346
+ script.addEventListener("load", done);
3347
+ script.addEventListener("error", done);
3348
+ insertScript(script, placeholder);
3349
+ });
3350
+ }
3351
+ function findIncludeLinks(container) {
3352
+ const selector = 'link[rel="simply-include"],link[rel="simply-include-once"]';
3353
+ const links = Array.from(container.querySelectorAll(selector));
3354
+ if (container.matches?.(selector)) {
3355
+ links.unshift(container);
3356
+ }
3357
+ return links;
3358
+ }
3359
+ var SimplyIncludes = class {
3360
+ constructor(options = {}) {
3361
+ this.container = options.container || globalThis.document;
3362
+ this.cacheBuster = options.cacheBuster ?? defaultCacheBuster;
3363
+ this.included = /* @__PURE__ */ Object.create(null);
3364
+ this.scriptLocations = [];
3365
+ this.destroyed = false;
3366
+ this.handleChanges = throttle(() => {
3367
+ runWhenIdle(() => {
3368
+ if (!this.destroyed) {
3369
+ this.includeLinks(findIncludeLinks(this.container));
3370
+ }
3371
+ });
3372
+ }, 10);
3373
+ if (options.observe !== false) {
3374
+ this.observer = new MutationObserver(this.handleChanges);
3375
+ this.observer.observe(this.container, {
3376
+ subtree: true,
3377
+ childList: true
3378
+ });
3379
+ this.handleChanges();
3380
+ }
3381
+ }
3382
+ async scripts(scripts, base) {
3383
+ const arr = scripts.slice();
3384
+ for (const script of arr) {
3385
+ if (this.destroyed) {
3386
+ return;
3387
+ }
3388
+ const clone2 = cloneScript(script, base, this.cacheBuster);
3389
+ const node = this.scriptLocations[script.dataset.simplyLocation];
3390
+ if (!node?.parentNode) {
3391
+ continue;
3392
+ }
3393
+ const waitForLoad = shouldWaitForScript(clone2);
3394
+ if (waitForLoad) {
3395
+ clone2.async = false;
3396
+ await insertAndWaitForScript(clone2, node);
3397
+ } else {
3398
+ insertScript(clone2, node);
3399
+ }
3400
+ }
3401
+ }
3402
+ html(html2, link) {
3403
+ const fragment = globalThis.document.createRange().createContextualFragment(html2);
3404
+ const stylesheets = fragment.querySelectorAll('link[rel="stylesheet"],style');
3405
+ for (const stylesheet of stylesheets) {
3406
+ const href = stylesheet.getAttribute("href");
3407
+ if (href) {
3408
+ stylesheet.href = rebaseHref(href, link.href, this.cacheBuster);
3409
+ }
3410
+ globalThis.document.head.appendChild(stylesheet);
3411
+ }
3412
+ const scriptsFragment = globalThis.document.createDocumentFragment();
3413
+ const scripts = fragment.querySelectorAll("script");
3414
+ if (scripts.length) {
3415
+ for (const script of scripts) {
3416
+ const placeholder = globalThis.document.createComment(script.src || "inline script");
3417
+ script.parentNode.insertBefore(placeholder, script);
3418
+ script.dataset.simplyLocation = this.scriptLocations.length;
3419
+ this.scriptLocations.push(placeholder);
3420
+ scriptsFragment.appendChild(script);
3421
+ }
3422
+ globalThis.setTimeout(() => {
3423
+ this.scripts(Array.from(scriptsFragment.children), link ? link.href : globalThis.location.href);
3424
+ }, 10);
3425
+ }
3426
+ link.parentNode.insertBefore(fragment, link);
3427
+ }
3428
+ async includeLinks(links) {
3429
+ const remainingLinks = links.reduce((remainder, link) => {
3430
+ if (link.rel === "simply-include-once" && this.included[link.href]) {
3431
+ link.parentNode.removeChild(link);
3432
+ } else {
3433
+ this.included[link.href] = true;
3434
+ link.rel = "simply-include-loading";
3435
+ remainder.push(link);
3436
+ }
3437
+ return remainder;
3438
+ }, []);
3439
+ for (const link of remainingLinks) {
3440
+ if (this.destroyed || !link.href) {
3441
+ continue;
3442
+ }
3443
+ try {
3444
+ const response = await fetch(link.href);
3445
+ if (!response.ok) {
3446
+ console.warn(`simplyflow/include: failed to load "${link.href}" (${response.status})`);
3447
+ link.rel = "simply-include-error";
3448
+ continue;
3449
+ }
3450
+ const html2 = await response.text();
3451
+ if (this.destroyed || !link.parentNode) {
3452
+ continue;
3453
+ }
3454
+ this.html(html2, link);
3455
+ link.parentNode?.removeChild(link);
3456
+ } catch (error) {
3457
+ console.warn(`simplyflow/include: failed to load "${link.href}"`, { cause: error });
3458
+ link.rel = "simply-include-error";
3459
+ }
3460
+ }
3461
+ }
3462
+ destroy() {
3463
+ this.destroyed = true;
3464
+ this.observer?.disconnect();
3465
+ this.observer = void 0;
3466
+ }
3467
+ };
3468
+ function includes(options = {}) {
3469
+ return new SimplyIncludes(options);
3470
+ }
3471
+ var defaultCacheBuster = null;
3472
+ var defaultInclude = () => new SimplyIncludes({
3473
+ container: globalThis.document,
3474
+ cacheBuster: defaultCacheBuster,
3475
+ observe: false
3476
+ });
3477
+ var include = {
3478
+ get cacheBuster() {
3479
+ return defaultCacheBuster;
3480
+ },
3481
+ set cacheBuster(value) {
3482
+ defaultCacheBuster = value;
3483
+ },
3484
+ scripts: (scripts, base) => defaultInclude().scripts(scripts, base),
3485
+ html: (html2, link) => defaultInclude().html(html2, link),
3486
+ links: (links) => defaultInclude().includeLinks(Array.from(links))
3487
+ };
3488
+
3489
+ // src/app.mjs
3490
+ var APP_OPTIONS = [
3491
+ "container",
3492
+ "data",
3493
+ "templates",
3494
+ "styles",
3495
+ "start",
3496
+ "onError",
3497
+ "components",
3498
+ "behaviors",
3499
+ "baseURL",
3500
+ "commands",
3501
+ "shortcuts",
3502
+ "routes",
3503
+ "actions"
3504
+ ];
3505
+ var SimplyApp = class {
3506
+ constructor(options = {}) {
3507
+ if (options.components) {
3508
+ const mergedOptions = {};
3509
+ mergeComponents(mergedOptions, options.components);
3510
+ mergeOptions(mergedOptions, options);
3511
+ options = mergedOptions;
3512
+ }
3513
+ this.container = options.container || document.body;
3514
+ this.destroyed = false;
3515
+ this.data = signal(options.data || {});
3516
+ this.start = options.start;
3517
+ this.onError = options.onError;
3518
+ this.components = options.components;
3519
+ this.baseURL = options.baseURL;
3520
+ installTemplates(this.container, options.templates);
3521
+ installStyles(this.container, options.styles);
3522
+ for (const key of Object.keys(options)) {
3523
+ switch (key) {
3524
+ case "container":
3525
+ case "data":
3526
+ case "templates":
3527
+ case "styles":
3528
+ case "start":
3529
+ case "onError":
3530
+ case "components":
3531
+ case "baseURL":
3532
+ break;
3533
+ case "commands":
3534
+ this.commands = commands({ app: this, container: this.container, commands: options.commands });
3535
+ break;
3536
+ case "shortcuts":
3537
+ this.shortcuts = shortcuts({ app: this, shortcuts: options.shortcuts });
3538
+ break;
3539
+ case "behaviors":
3540
+ this.behaviors = behaviors({ app: this, container: this.container, behaviors: options.behaviors });
3541
+ break;
3542
+ case "routes":
3543
+ this.routes = routes({ app: this, routes: options.routes });
3544
+ break;
3545
+ case "actions":
3546
+ this.actions = actions({ app: this, actions: options.actions });
3547
+ break;
3548
+ case "prototype":
3549
+ case "__proto__":
3550
+ break;
3551
+ default:
3552
+ warnLikelyOptionTypo(key);
3553
+ this[key] = options[key];
3554
+ break;
3555
+ }
3556
+ }
3557
+ this.binding = bind({
3558
+ root: this.data,
3559
+ container: this.container,
3560
+ attribute: "data-simply"
3561
+ });
3562
+ this.includes = includes({ container: this.container });
3563
+ this.accesskeys = accesskeys({ app: this, container: this.container });
3564
+ }
3565
+ get app() {
3566
+ return this;
3567
+ }
3568
+ destroy() {
3569
+ this.destroyed = true;
3570
+ if (this.binding) {
3571
+ this.binding.destroy();
3572
+ this.binding = void 0;
3573
+ }
3574
+ if (this.commands) {
3575
+ destroyCommands(this.commands);
3576
+ }
3577
+ if (this.shortcuts) {
3578
+ destroyShortcuts(this.shortcuts);
3579
+ }
3580
+ if (this.accesskeys) {
3581
+ destroyAccesskeys(this.accesskeys);
3582
+ this.accesskeys = void 0;
3583
+ }
3584
+ if (this.routes) {
3585
+ this.routes.destroy();
3586
+ this.routes = void 0;
3587
+ }
3588
+ if (this.behaviors) {
3589
+ this.behaviors.destroy();
3590
+ this.behaviors = void 0;
3591
+ }
3592
+ if (this.includes) {
3593
+ this.includes.destroy();
3594
+ this.includes = void 0;
3595
+ }
3596
+ }
3597
+ };
3598
+ function installTemplates(container, templates) {
3599
+ if (!templates) {
3600
+ return;
3601
+ }
3602
+ for (const name of Object.keys(templates)) {
3603
+ const element2 = document.createElement("div");
3604
+ element2.innerHTML = templates[name];
3605
+ let template = container.querySelector("template#" + name);
3606
+ if (!template) {
3607
+ template = document.createElement("template");
3608
+ template.id = name;
3609
+ template.content.append(...element2.children);
3610
+ container.appendChild(template);
3611
+ } else {
3612
+ template.content.replaceChildren(...element2.children);
3613
+ }
3614
+ }
3615
+ }
3616
+ function installStyles(container, styles) {
3617
+ if (!styles) {
3618
+ return;
3619
+ }
3620
+ for (const name of Object.keys(styles)) {
3621
+ let style = container.querySelector("style#" + name + ".css");
3622
+ if (!style) {
3623
+ style = document.createElement("style");
3624
+ style.id = name + ".css";
3625
+ container.appendChild(style);
3626
+ }
3627
+ style.innerHTML = styles[name];
3628
+ }
3629
+ }
3630
+ function warnLikelyOptionTypo(key) {
3631
+ const suggestion = closest(key, APP_OPTIONS);
3632
+ if (suggestion) {
3633
+ console.warn(`simplyflow/app: unknown option "${key}". Did you mean "${suggestion}"? The option was still added to the app as "app.${key}".`);
3634
+ }
3635
+ }
3636
+ function initRoutes(app2) {
3637
+ if (app2.destroyed) {
3638
+ return;
3639
+ }
3640
+ if (app2.routes) {
3641
+ if (app2.baseURL) {
3642
+ app2.routes.init({ baseURL: app2.baseURL });
3643
+ }
3644
+ app2.routes.handleEvents();
3645
+ globalThis.setTimeout(() => {
3646
+ if (app2.destroyed || !app2.routes) {
3647
+ return;
3648
+ }
3649
+ if (app2.routes.has(globalThis.location?.hash)) {
3650
+ app2.routes.match(globalThis.location.hash);
3651
+ } else {
3652
+ app2.routes.match(globalThis.location?.pathname + globalThis.location?.hash);
3653
+ }
3654
+ });
3655
+ }
3656
+ }
3657
+ function handleAppError(app2, error, context) {
3658
+ if (app2.onError) {
3659
+ return app2.onError.call(app2, error, context);
3660
+ }
3661
+ throw error;
3662
+ }
3663
+ function app(options = {}) {
3664
+ const app2 = new SimplyApp(options);
3665
+ if (!app2.start) {
3666
+ initRoutes(app2);
3667
+ return app2;
3668
+ }
3669
+ try {
3670
+ const result = app2.start.call(app2);
3671
+ if (result instanceof Promise) {
3672
+ result.then(() => initRoutes(app2)).catch((error) => handleAppError(app2, error, app2.start));
3673
+ } else {
3674
+ initRoutes(app2);
3675
+ }
3676
+ } catch (error) {
3677
+ handleAppError(app2, error, app2.start);
3678
+ }
3679
+ return app2;
3680
+ }
3681
+ function mergeOptions(options, otherOptions) {
3682
+ for (const key of Object.keys(otherOptions)) {
3683
+ switch (typeof otherOptions[key]) {
3684
+ case "object":
3685
+ if (!otherOptions[key]) {
3686
+ continue;
3687
+ }
3688
+ if (!options[key]) {
3689
+ options[key] = otherOptions[key];
3690
+ } else {
3691
+ mergeOptions(options[key], otherOptions[key]);
3692
+ }
3693
+ break;
3694
+ default:
3695
+ options[key] = otherOptions[key];
3696
+ }
3697
+ }
3698
+ }
3699
+ function mergeComponents(options, components) {
3700
+ for (const name of Object.keys(components)) {
3701
+ const component = components[name];
3702
+ if (component.components) {
3703
+ mergeComponents(options, component.components);
3704
+ }
3705
+ if (!options.components) {
3706
+ options.components = {};
3707
+ }
3708
+ options.components[name] = component;
3709
+ for (const key of Object.keys(component)) {
3710
+ switch (key) {
3711
+ case "start":
3712
+ case "onError":
3713
+ // App lifecycle functions are app-level behavior, not merged component state.
3714
+ case "components":
3715
+ break;
3716
+ default:
3717
+ if (!options[key]) {
3718
+ options[key] = /* @__PURE__ */ Object.create(null);
3719
+ }
3720
+ mergeOptions(options[key], component[key]);
3721
+ break;
3722
+ }
3723
+ }
3724
+ }
3725
+ }
3726
+
3727
+ // src/highlight.mjs
3728
+ function html(strings, ...values) {
3729
+ const outputArray = values.map(
3730
+ (value, index) => `${strings[index]}${value}`
3731
+ );
3732
+ return outputArray.join("") + strings[strings.length - 1];
3733
+ }
3734
+ function css(strings, ...values) {
3735
+ return html(strings, ...values);
3736
+ }
3737
+
3738
+ // src/flow.mjs
3739
+ if (!globalThis.simply) {
3740
+ globalThis.simply = {};
3741
+ }
3742
+ globalThis.html = html;
3743
+ globalThis.css = css;
3744
+ var modelApi = Object.assign(model, {
3745
+ model,
3746
+ sort,
3747
+ paging,
3748
+ filter,
3749
+ columns,
3750
+ scroll
3751
+ });
3752
+ Object.assign(globalThis.simply, {
3753
+ app,
3754
+ bind,
3755
+ model: modelApi,
3756
+ state: state_exports,
3757
+ signal,
3758
+ effect,
3759
+ batch,
3760
+ clone,
3761
+ destroy,
3762
+ untracked,
3763
+ throttledEffect,
3764
+ clockEffect,
3765
+ createSignal,
3766
+ isSignal,
3767
+ raw,
3768
+ dom: dom_exports,
3769
+ behaviors,
3770
+ actions,
3771
+ commands,
3772
+ include,
3773
+ includes,
3774
+ shortcuts,
3775
+ path: path_default,
3776
+ routes
3777
+ });
3778
+ delete globalThis.simply.advanced;
3779
+ var flow_default = globalThis.simply;
3780
+ })();