simplyflow 0.8.2 → 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.
@@ -23,47 +23,83 @@
23
23
  trace: () => trace,
24
24
  untracked: () => untracked
25
25
  });
26
- if (!Symbol.iterate) {
27
- Symbol.iterate = Symbol("iterate");
26
+
27
+ // src/symbols.mjs
28
+ var DEP = {
29
+ ITERATE: Symbol.for("@simplyedit/simplyflow.iterate"),
30
+ XRAY: Symbol.for("@simplyedit/simplyflow.xRay"),
31
+ SIGNAL: Symbol.for("@simplyedit/simplyflow.Signal"),
32
+ TEMPLATE: Symbol.for("@simplyedit/simplyflow.bindTemplate"),
33
+ LENGTH: "length",
34
+ SIZE: "size"
35
+ };
36
+
37
+ // src/state.mjs
38
+ function wrapMapMethod(target, property, receiver, value) {
39
+ return (...args) => {
40
+ if (property === "get" || property === "has") {
41
+ notifyGet(receiver, args[0]);
42
+ }
43
+ if (["keys", "values", "entries", "forEach", Symbol.iterator].includes(property)) {
44
+ notifyGet(receiver, DEP.ITERATE);
45
+ }
46
+ const oldSize = target.size;
47
+ const result = value.apply(target, args);
48
+ if (property === "set") {
49
+ notifySet(receiver, makeContext(args[0], { now: args[1] }));
50
+ }
51
+ if (property === "delete") {
52
+ notifySet(receiver, makeContext(args[0], { delete: true }));
53
+ }
54
+ if (oldSize !== target.size) {
55
+ notifySet(receiver, makeContext(DEP.SIZE, {}));
56
+ }
57
+ if (["set", "delete", "clear"].includes(property) || oldSize !== target.size) {
58
+ notifySet(receiver, makeContext(DEP.ITERATE, {}));
59
+ }
60
+ return result;
61
+ };
28
62
  }
29
- if (!Symbol.xRay) {
30
- Symbol.xRay = Symbol("xRay");
63
+ function wrapArrayMethod(target, property, receiver, value) {
64
+ return (...args) => {
65
+ let l = target.length;
66
+ let result = value.apply(receiver, args);
67
+ if (l != target.length) {
68
+ notifySet(receiver, makeContext(DEP.LENGTH, { was: l, now: target.length }));
69
+ }
70
+ return result;
71
+ };
31
72
  }
32
- if (!Symbol.Signal) {
33
- Symbol.Signal = Symbol("Signal");
73
+ function wrapSetMethod(target, property, receiver, value) {
74
+ return (...args) => {
75
+ let s = target.size;
76
+ let result = value.apply(target, args);
77
+ if (s != target.size) {
78
+ notifySet(receiver, makeContext(DEP.SIZE, { was: s, now: target.size }));
79
+ }
80
+ if (["set", "add", "clear", "delete"].includes(property)) {
81
+ notifySet(receiver, makeContext({ entries: {}, forEach: {}, has: {}, keys: {}, values: {}, [Symbol.iterator]: {} }));
82
+ }
83
+ return result;
84
+ };
34
85
  }
35
86
  var signalHandler = {
36
87
  get: (target, property, receiver) => {
37
- if (property === Symbol.xRay) {
88
+ if (property === DEP.XRAY) {
38
89
  return target;
39
90
  }
40
- if (property === Symbol.Signal) {
91
+ if (property === DEP.SIGNAL) {
41
92
  return true;
42
93
  }
43
94
  const value = target?.[property];
44
95
  notifyGet(receiver, property);
45
96
  if (typeof value === "function") {
46
97
  if (Array.isArray(target)) {
47
- return (...args) => {
48
- let l = target.length;
49
- let result = value.apply(receiver, args);
50
- if (l != target.length) {
51
- notifySet(receiver, makeContext("length", { was: l, now: target.length }));
52
- }
53
- return result;
54
- };
55
- } else if (target instanceof Set || target instanceof Map) {
56
- return (...args) => {
57
- let s = target.size;
58
- let result = value.apply(target, args);
59
- if (s != target.size) {
60
- notifySet(receiver, makeContext("size", { was: s, now: target.size }));
61
- }
62
- if (["set", "add", "clear", "delete"].includes(property)) {
63
- notifySet(receiver, makeContext({ entries: {}, forEach: {}, has: {}, keys: {}, values: {}, [Symbol.iterator]: {} }));
64
- }
65
- return result;
66
- };
98
+ return wrapArrayMethod(target, property, receiver, value);
99
+ } else if (target instanceof Map) {
100
+ return wrapMapMethod(target, property, receiver, value);
101
+ } else if (target instanceof Set) {
102
+ return wrapSetMethod(target, property, receiver, value);
67
103
  } else if (target instanceof HTMLElement || target instanceof Number || target instanceof String || target instanceof Boolean) {
68
104
  return value.bind(target);
69
105
  } else {
@@ -82,8 +118,8 @@
82
118
  notifySet(receiver, makeContext(property, { was: current, now: value }));
83
119
  }
84
120
  if (typeof current === "undefined") {
85
- notifySet(receiver, makeContext(Symbol.iterate, {}));
86
- notifySet(receiver, makeContext("length", {}));
121
+ notifySet(receiver, makeContext(DEP.ITERATE, {}));
122
+ notifySet(receiver, makeContext(DEP.LENGTH, {}));
87
123
  }
88
124
  return true;
89
125
  },
@@ -100,28 +136,34 @@
100
136
  delete target[property];
101
137
  let receiver = signals.get(target);
102
138
  notifySet(receiver, makeContext(property, { delete: true, was: current }));
139
+ notifySet(
140
+ receiver,
141
+ makeContext(DEP.ITERATE, { delete: true, property })
142
+ );
103
143
  }
104
144
  return true;
105
145
  },
106
146
  defineProperty: (target, property, descriptor) => {
107
147
  if (typeof target[property] === "undefined") {
108
148
  let receiver = signals.get(target);
109
- notifySet(receiver, makeContext(Symbol.iterate, {}));
149
+ notifySet(receiver, makeContext(DEP.ITERATE, {}));
110
150
  }
111
151
  return Object.defineProperty(target, property, descriptor);
112
152
  },
113
153
  ownKeys: (target) => {
114
154
  let receiver = signals.get(target);
115
- notifyGet(receiver, Symbol.iterate);
155
+ notifyGet(receiver, DEP.ITERATE);
116
156
  return Reflect.ownKeys(target);
117
157
  }
118
158
  };
119
159
  var signals = /* @__PURE__ */ new WeakMap();
120
- function signal(v) {
121
- if (!v) {
122
- v = {};
160
+ function signal(v = {}) {
161
+ if (v === null || typeof v !== "object" && typeof v !== "function") {
162
+ throw new TypeError(
163
+ `simplyflow/state: signal() expects an object, array, Map, Set, class instance, or function; received ${typeof v}`
164
+ );
123
165
  }
124
- if (v[Symbol.Signal]) {
166
+ if (v[DEP.SIGNAL]) {
125
167
  return v;
126
168
  }
127
169
  if (!signals.has(v)) {
@@ -131,23 +173,33 @@
131
173
  }
132
174
  var tracers = [];
133
175
  var tracing = false;
134
- function trace(signal3, prop) {
135
- if (typeof signal3 === "function") {
176
+ function trace(target, prop) {
177
+ if (typeof target === "function") {
136
178
  tracing = true;
137
- signal3();
138
- tracing = false;
139
- } else {
140
- const listeners = getListeners(signal3, prop);
141
- return listeners.map((listener) => {
142
- return {
143
- effect: listener.effectType,
144
- fn: listener.effectFunction,
145
- signal: signals.get(listener.effectFunction)
146
- };
147
- });
179
+ try {
180
+ return target();
181
+ } finally {
182
+ tracing = false;
183
+ }
184
+ }
185
+ if (!target || !target[DEP.SIGNAL]) {
186
+ throw new TypeError(
187
+ "simplyflow/state: trace() expects either a function or a signal"
188
+ );
148
189
  }
190
+ const listeners = getListeners(target, prop);
191
+ return listeners.map((listener) => {
192
+ return {
193
+ effect: listener.effectType,
194
+ fn: listener.effectFunction,
195
+ signal: signals.get(listener.effectFunction)
196
+ };
197
+ });
149
198
  }
150
199
  function addTracer(tracer) {
200
+ if (!tracer || typeof tracer !== "object") {
201
+ throw new TypeError("simplyflow/state: addTracer() expects a tracer object");
202
+ }
151
203
  if (!tracer.get && !tracer.set) {
152
204
  throw new Error('simply.state: addTracer: missing "get" or "set" property in tracer', tracer);
153
205
  }
@@ -168,7 +220,13 @@
168
220
  }
169
221
  var batchedListeners = /* @__PURE__ */ new Set();
170
222
  var batchMode = 0;
171
- function notifySet(self, context = {}) {
223
+ function notifySet(self, context = /* @__PURE__ */ new Map()) {
224
+ if (!self || !self[DEP.SIGNAL]) {
225
+ throw new TypeError("simplyflow/state: notifySet() expects a signal as first argument");
226
+ }
227
+ if (!(context instanceof Map)) {
228
+ throw new TypeError("simplyflow/state: notifySet() expects context to be a Map; use makeContext()");
229
+ }
172
230
  let listeners = [];
173
231
  context.forEach((change, property) => {
174
232
  let propListeners = getListeners(self, property);
@@ -199,8 +257,14 @@
199
257
  }
200
258
  function makeContext(property, change) {
201
259
  let context = /* @__PURE__ */ new Map();
202
- if (typeof property === "object") {
203
- for (let prop in property) {
260
+ if (property instanceof Map) {
261
+ property.forEach((change2, prop) => {
262
+ context.set(prop, change2);
263
+ });
264
+ return context;
265
+ }
266
+ if (property !== null && typeof property === "object") {
267
+ for (const prop of Reflect.ownKeys(property)) {
204
268
  context.set(prop, property[prop]);
205
269
  }
206
270
  } else {
@@ -256,23 +320,31 @@
256
320
  connectedSignals.get(property).add(self);
257
321
  }
258
322
  function clearListeners(compute) {
259
- let connectedSignals = computeMap.get(compute);
260
- if (connectedSignals) {
261
- connectedSignals.forEach((property) => {
262
- property.forEach((s) => {
263
- let listeners = listenersMap.get(s);
264
- if (listeners.has(property)) {
265
- listeners.get(property).delete(compute);
266
- }
267
- });
268
- });
323
+ const connectedSignals = computeMap.get(compute);
324
+ if (!connectedSignals) {
325
+ return;
269
326
  }
327
+ connectedSignals.forEach((signals2, property) => {
328
+ signals2.forEach((signal3) => {
329
+ const listeners = listenersMap.get(signal3);
330
+ if (listeners?.has(property)) {
331
+ listeners.get(property).delete(compute);
332
+ }
333
+ });
334
+ });
335
+ computeMap.delete(compute);
270
336
  }
271
337
  var computeStack = [];
272
338
  var effectStack = [];
273
339
  var effectMap = /* @__PURE__ */ new WeakMap();
274
340
  var signalStack = [];
341
+ function assertFunction(fn, name) {
342
+ if (typeof fn !== "function") {
343
+ throw new TypeError(`simplyflow/state: ${name}() expects a function`);
344
+ }
345
+ }
275
346
  function effect(fn) {
347
+ assertFunction(fn, "effect");
276
348
  if (effectStack.findIndex((f) => fn == f) !== -1) {
277
349
  throw new Error("Recursive update() call", { cause: fn });
278
350
  }
@@ -314,16 +386,21 @@
314
386
  return connectedSignal;
315
387
  }
316
388
  function destroy(connectedSignal) {
317
- const computeEffect = effectMap.get(connectedSignal)?.deref();
389
+ if (!connectedSignal || !connectedSignal[DEP.SIGNAL]) {
390
+ throw new TypeError("simplyflow/state: destroy() expects an effect signal");
391
+ }
392
+ const computeEffect = effectMap.get(connectedSignal);
318
393
  if (!computeEffect) {
319
394
  return;
320
395
  }
321
396
  clearListeners(computeEffect);
322
- let fn = computeEffect.fn;
323
- signals.remove(fn);
397
+ if (computeEffect.fn) {
398
+ signals.delete(computeEffect.fn);
399
+ }
324
400
  effectMap.delete(connectedSignal);
325
401
  }
326
402
  function batch(fn) {
403
+ assertFunction(fn, "batch");
327
404
  batchMode++;
328
405
  let result;
329
406
  try {
@@ -357,6 +434,12 @@
357
434
  }
358
435
  }
359
436
  function throttledEffect(fn, throttleTime) {
437
+ assertFunction(fn, "throttledEffect");
438
+ if (!Number.isFinite(throttleTime) || throttleTime < 0) {
439
+ throw new TypeError(
440
+ `simplyflow/state: throttledEffect() expects throttleTime to be a non-negative number`
441
+ );
442
+ }
360
443
  if (effectStack.findIndex((f) => fn == f) !== -1) {
361
444
  throw new Error("Recursive update() call", { cause: fn });
362
445
  }
@@ -368,16 +451,31 @@
368
451
  });
369
452
  signals.set(fn, connectedSignal);
370
453
  }
371
- let throttled = false;
454
+ let throttledUntil = 0;
372
455
  let hasChange = true;
373
- const computeEffect = function computeEffect2() {
374
- if (signalStack.findIndex((s) => s == connectedSignal) !== -1) {
375
- throw new Error("Cyclical dependency in update() call", { cause: fn });
456
+ let timeout = null;
457
+ function schedule() {
458
+ if (timeout) {
459
+ return;
376
460
  }
377
- if (throttled && throttled > Date.now()) {
461
+ const delay = Math.max(0, throttledUntil - Date.now());
462
+ timeout = globalThis.setTimeout(() => {
463
+ timeout = null;
464
+ if (hasChange) {
465
+ computeEffect();
466
+ }
467
+ }, delay);
468
+ }
469
+ const computeEffect = function computeEffect2() {
470
+ const now = Date.now();
471
+ if (throttledUntil > now) {
378
472
  hasChange = true;
473
+ schedule();
379
474
  return;
380
475
  }
476
+ if (signalStack.findIndex((s) => s == connectedSignal) !== -1) {
477
+ throw new Error("Cyclical dependency in update() call", { cause: fn });
478
+ }
381
479
  clearListeners(computeEffect2);
382
480
  computeEffect2.effectFunction = fn;
383
481
  computeEffect2.effectType = throttledEffect;
@@ -398,17 +496,19 @@
398
496
  connectedSignal.current = result;
399
497
  }
400
498
  }
401
- throttled = Date.now() + throttleTime;
402
- globalThis.setTimeout(() => {
403
- if (hasChange) {
404
- computeEffect2();
405
- }
406
- }, throttleTime);
499
+ throttledUntil = Date.now() + throttleTime;
500
+ schedule();
407
501
  };
408
502
  computeEffect();
409
503
  return connectedSignal;
410
504
  }
411
505
  function clockEffect(fn, clock) {
506
+ assertFunction(fn, "clockEffect");
507
+ if (!clock || typeof clock !== "object" || typeof clock.time !== "number") {
508
+ throw new TypeError(
509
+ `simplyflow/state: clockEffect() expects a clock object with a numeric .time property`
510
+ );
511
+ }
412
512
  let connectedSignal = signals.get(fn);
413
513
  if (!connectedSignal) {
414
514
  connectedSignal = signal({
@@ -451,6 +551,7 @@
451
551
  return connectedSignal;
452
552
  }
453
553
  function untracked(fn) {
554
+ assertFunction(fn, "untracked");
454
555
  const pos = computeStack.length - 1;
455
556
  const remember = computeStack[pos];
456
557
  computeStack[pos] = false;
@@ -472,16 +573,15 @@
472
573
  return value2;
473
574
  }
474
575
  if (Array.isArray(value2)) {
475
- let result = [];
576
+ const result = [];
476
577
  if (!deep) {
477
- result = value2.slice();
478
- seen.set(value2, result);
479
- return result;
578
+ return value2.slice();
480
579
  }
481
580
  seen.set(value2, result);
482
- for (const key of value2) {
483
- result[key] = innerClone(value2[key]);
581
+ for (let i = 0; i < value2.length; i++) {
582
+ result[i] = innerClone(value2[i]);
484
583
  }
584
+ return result;
485
585
  } else if (!value2.constructor || value2.constructor === Object) {
486
586
  let result = {};
487
587
  if (!value2.constructor) {
@@ -497,7 +597,7 @@
497
597
  }
498
598
  break;
499
599
  default:
500
- return null;
600
+ return value2;
501
601
  break;
502
602
  }
503
603
  };
@@ -534,12 +634,13 @@
534
634
  trackDomList: () => trackDomList
535
635
  });
536
636
  var domSignals = /* @__PURE__ */ new WeakMap();
637
+ var observers = /* @__PURE__ */ new WeakMap();
537
638
  var domSignalHandler = {
538
639
  get: (target, property, receiver) => {
539
- if (property === Symbol.xRay) {
640
+ if (property === DEP.XRAY) {
540
641
  return target;
541
642
  }
542
- if (property === Symbol.Signal) {
643
+ if (property === DEP.SIGNAL) {
543
644
  return true;
544
645
  }
545
646
  const value = target?.[property];
@@ -568,7 +669,7 @@
568
669
  }
569
670
  };
570
671
  function signal2(el, options) {
571
- if (el[Symbol.xRay]) {
672
+ if (el[DEP.XRAY]) {
572
673
  return el;
573
674
  }
574
675
  if (!signals.has(el)) {
@@ -577,7 +678,6 @@
577
678
  }
578
679
  return signals.get(el);
579
680
  }
580
- var observers = /* @__PURE__ */ new WeakMap();
581
681
  function domListen(el, signal3, options) {
582
682
  const defaultOptions = {
583
683
  characterData: true,
@@ -673,6 +773,7 @@
673
773
  });
674
774
  });
675
775
  });
776
+ return s;
676
777
  }
677
778
  function trackDomField(element2, props, valueIsString) {
678
779
  if (domSignals.has(element2)) {
@@ -695,6 +796,7 @@
695
796
  });
696
797
  });
697
798
  });
799
+ return s;
698
800
  }
699
801
 
700
802
  // src/bind.render.mjs
@@ -794,6 +896,8 @@
794
896
  }
795
897
  function arrayByTemplates(context) {
796
898
  const attribute = this.options.attribute;
899
+ const attributes = [attribute + "-field", attribute + "-list", attribute + "-map"];
900
+ const attrQuery = "[" + attributes.join("],[") + "]";
797
901
  let items = context.element.querySelectorAll(":scope > [" + attribute + "-key]");
798
902
  let lastKey = 0;
799
903
  let skipped = 0;
@@ -806,18 +910,23 @@
806
910
  } else if (currentKey < lastKey) {
807
911
  item.remove();
808
912
  } else {
809
- let bindings = Array.from(item.querySelectorAll(`[${attribute}]`));
810
- if (item.matches(`[${attribute}]`)) {
913
+ let bindings = Array.from(item.querySelectorAll(attrQuery));
914
+ if (item.matches(attrQuery)) {
811
915
  bindings.unshift(item);
812
916
  }
813
917
  let needsReplacement = bindings.find((b) => {
814
- let databind = b.getAttribute(attribute);
815
- return databind.substr(0, 5) !== ":root" && databind.substr(0, context.path.length) !== context.path;
918
+ for (let attr of attributes) {
919
+ let databind = b.getAttribute(attr);
920
+ if (databind && databind.substr(0, 5) !== ":root" && databind.substr(0, context.path.length) !== context.path) {
921
+ return true;
922
+ }
923
+ }
924
+ return false;
816
925
  });
817
926
  if (!needsReplacement) {
818
- if (item[Symbol.bindTemplate]) {
927
+ if (item[DEP.TEMPLATE]) {
819
928
  let newTemplate = this.findTemplate(context.templates, context.list[lastKey]);
820
- if (newTemplate != item[Symbol.bindTemplate]) {
929
+ if (newTemplate != item[DEP.TEMPLATE]) {
821
930
  needsReplacement = true;
822
931
  if (!newTemplate) {
823
932
  skipped++;
@@ -880,7 +989,7 @@
880
989
  }
881
990
  }
882
991
  let newTemplate = this.findTemplate(context.templates, context.list[context.index]);
883
- if (newTemplate != item[Symbol.bindTemplate]) {
992
+ if (newTemplate != item[DEP.TEMPLATE]) {
884
993
  let clone2 = this.applyTemplate(context);
885
994
  context.element.replaceChild(clone2, item);
886
995
  }
@@ -896,7 +1005,7 @@
896
1005
  context.parent = getParentPath(context.element);
897
1006
  if (rendered) {
898
1007
  if (template) {
899
- if (rendered?.[Symbol.bindTemplate] != template) {
1008
+ if (rendered?.[DEP.TEMPLATE] != template) {
900
1009
  const clone2 = this.applyTemplate(context);
901
1010
  context.element.replaceChild(clone2, rendered);
902
1011
  }
@@ -1088,9 +1197,6 @@
1088
1197
  }
1089
1198
 
1090
1199
  // src/bind.mjs
1091
- if (!Symbol.bindTemplate) {
1092
- Symbol.bindTemplate = Symbol("bindTemplate");
1093
- }
1094
1200
  var SimplyBind = class {
1095
1201
  /**
1096
1202
  * @param Object options - a set of options for this instance, options may include:
@@ -1117,6 +1223,7 @@
1117
1223
  },
1118
1224
  renderers: {
1119
1225
  "INPUT": input,
1226
+ "TEXTAREA": input,
1120
1227
  "BUTTON": button,
1121
1228
  "SELECT": select,
1122
1229
  "A": anchor,
@@ -1286,7 +1393,7 @@
1286
1393
  if (typeof index !== "undefined") {
1287
1394
  clone2.children[0].setAttribute(attribute + "-key", index);
1288
1395
  }
1289
- clone2.children[0][Symbol.bindTemplate] = template;
1396
+ clone2.children[0][DEP.TEMPLATE] = template;
1290
1397
  return clone2;
1291
1398
  }
1292
1399
  parseLinks(links) {
@@ -1473,7 +1580,7 @@
1473
1580
  }
1474
1581
  const dataSignal = this.effects[this.effects.length - 1];
1475
1582
  const connectedSignal = fn.call(this, dataSignal);
1476
- if (!connectedSignal || !connectedSignal[Symbol.Signal]) {
1583
+ if (!connectedSignal || !connectedSignal[DEP.SIGNAL]) {
1477
1584
  throw new Error("addEffect function parameter must return a Signal", { cause: fn });
1478
1585
  }
1479
1586
  this.view = connectedSignal;