simplyflow 0.7.5 → 0.7.7

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.
package/src/dom.mjs ADDED
@@ -0,0 +1,99 @@
1
+ import { signals, signal as stateSignal, notifyGet, notifySet, makeContext } from './state.mjs'
2
+
3
+ const domSignalHandler = {
4
+ get: (target, property, receiver) => {
5
+ if (property===Symbol.xRay) {
6
+ return target // don't notifyGet here, this is only called by set
7
+ }
8
+ if (property===Symbol.Signal) {
9
+ return true
10
+ }
11
+ const value = target?.[property]
12
+ notifyGet(receiver, property)
13
+ if (typeof value === 'function') {
14
+ return value.bind(target) // make sure element functions are not linked to the proxy
15
+ }
16
+ if (value && typeof value == 'object') {
17
+ return stateSignal(value)
18
+ }
19
+ return value
20
+ },
21
+ has: (target, property) => {
22
+ let receiver = signals.get(target)
23
+ if (receiver) {
24
+ notifyGet(receiver, property)
25
+ }
26
+ return Object.hasOwn(target, property)
27
+ },
28
+ ownKeys: (target) => {
29
+ let receiver = signals.get(target) // receiver is not part of the trap arguments, so retrieve it here
30
+ if (receiver) {
31
+ notifyGet(receiver, iterate)
32
+ }
33
+ return Reflect.ownKeys(target)
34
+ }
35
+ }
36
+
37
+ export function signal(el) {
38
+ if (el[Symbol.xRay]) {
39
+ return el
40
+ }
41
+ if (!signals.has(el)) {
42
+ signals.set(el, new Proxy(el, domSignalHandler))
43
+ domListen(el, signals.get(el))
44
+ }
45
+ return signals.get(el)
46
+ }
47
+
48
+ const observers = new WeakMap()
49
+
50
+ function domListen(el, signal) {
51
+ let oldContentHTML = el.innerHTML
52
+ let oldContentText = el.innerText
53
+ if (!observers.has(el)) {
54
+ const observer = new MutationObserver((mutationList, observer) => {
55
+ // collect changes
56
+ const changes = {}
57
+ for (const mutation of mutationList) {
58
+ if (mutation.type==='attributes') {
59
+ // check if any listeners for each attribute
60
+ changes[mutation.attributeName] = mutation.attributeOldValue
61
+ } else if (mutation.type==='subtree' || mutation.type==='characterData') {
62
+ // change on innerHTML/innerText
63
+ if (el.innerHTML != oldContentHTML) {
64
+ changes.innerHTML = oldContentHTML
65
+ oldContentHTML = el.innerHTML
66
+ }
67
+ if (el.innerText != oldContentText) {
68
+ changes.innerText = oldContentText
69
+ oldContentText = el.innerText
70
+ }
71
+ }
72
+ }
73
+ for (const prop in changes) {
74
+ notifySet(signal, makeContext(prop, { was: changes[prop], now: el[prop] }))
75
+ }
76
+ })
77
+ observer.observe(el, {
78
+ characterData: true,
79
+ subtree: true,
80
+ attributes: true,
81
+ attributesOldValue: true
82
+ })
83
+ observers.set(el, observer)
84
+ //@TODO: unregister the observer when el is removed from the dom (after a timeout)
85
+ if (el.matches('input, textarea, select')) {
86
+ let prevValue = el.value
87
+ el.addEventListener('change', (evt) => {
88
+ notifySet(signal, makeContext('value', { was: prevValue, now: el.value }))
89
+ prevValue = el.value
90
+ })
91
+ if (el.matches('input, textarea')) {
92
+ el.addEventListener('input', (evt) => {
93
+ notifySet(signal, makeContext('value', { was: prevValue, now: el.value }))
94
+ prevValue = el.value
95
+ })
96
+ }
97
+ }
98
+ }
99
+ }
package/src/flow.mjs CHANGED
@@ -2,6 +2,7 @@ import { bind } from './bind.mjs'
2
2
  import * as model from './model.mjs'
3
3
  import * as state from './state.mjs'
4
4
  import './render.mjs'
5
+ import * as dom from './dom.mjs'
5
6
 
6
7
  if (!globalThis.simply) {
7
8
  globalThis.simply = {}
@@ -9,7 +10,8 @@ if (!globalThis.simply) {
9
10
  Object.assign(globalThis.simply, {
10
11
  bind,
11
12
  flow: model,
12
- state
13
+ state,
14
+ dom
13
15
  })
14
16
 
15
17
  export default globalThis.simply
package/src/model.mjs CHANGED
@@ -198,7 +198,7 @@ export function columns(options={}) {
198
198
  let result = {}
199
199
  for (let key of Object.keys(this.state.options.columns)) {
200
200
  if (!this.state.options.columns[key]?.hidden) {
201
- result[key] = input[key]
201
+ result[key] = input[key] ?? null
202
202
  }
203
203
  }
204
204
  return result
package/src/state.mjs CHANGED
@@ -68,7 +68,7 @@ const signalHandler = {
68
68
  set: (target, property, value, receiver) => {
69
69
  value = value?.[Symbol.xRay] || value // unwraps signal
70
70
  //FIXME: if value contains child objects, these may be signals as well... so do this recursively
71
- unwrap(value)
71
+ //unwrap(value)
72
72
  let current = target[property]
73
73
  if (current!==value) {
74
74
  target[property] = value
@@ -116,14 +116,14 @@ const signalHandler = {
116
116
  * Makes sure that a given object or function always uses the same
117
117
  * signal
118
118
  */
119
- const signals = new WeakMap()
119
+ export const signals = new WeakMap()
120
120
 
121
121
  /**
122
122
  * Creates a new signal proxy of the given object, that intercepts get/has and set/delete
123
123
  * to allow reactive functions to be triggered when signal values change.
124
124
  */
125
125
  export function signal(v) {
126
- unwrap(v)
126
+ //unwrap(v)
127
127
  if (v[Symbol.Signal]) { // avoid wrapping a Signal inside a Signal
128
128
  let target = v[Symbol.xRay]
129
129
  if (!signals.has(target)) {
@@ -136,52 +136,6 @@ export function signal(v) {
136
136
  return signals.get(v)
137
137
  }
138
138
 
139
- const domSignalHandler = {
140
- get: (target, property, receiver) => {
141
- if (property===Symbol.xRay) {
142
- return target // don't notifyGet here, this is only called by set
143
- }
144
- if (property===Symbol.Signal) {
145
- return true
146
- }
147
- const value = target?.[property]
148
- domListen(target, receiver)
149
- notifyGet(receiver, property)
150
- if (typeof value === 'function') {
151
- return value.bind(target) // make sure element functions are not linked to the proxy
152
- }
153
- if (value && typeof value == 'object') {
154
- return signal(value)
155
- }
156
- return value
157
- },
158
- has: (target, property) => {
159
- let receiver = signals.get(target)
160
- if (receiver) {
161
- domListen(target, receiver)
162
- notifyGet(receiver, property)
163
- }
164
- return Object.hasOwn(target, property)
165
- },
166
- ownKeys: (target) => {
167
- let receiver = signals.get(target) // receiver is not part of the trap arguments, so retrieve it here
168
- if (receiver) {
169
- domListen(target, receiver)
170
- notifyGet(receiver, iterate)
171
- }
172
- return Reflect.ownKeys(target)
173
- }
174
- }
175
-
176
- export function domSignal(el) {
177
- if (el[Symbol.xRay]) {
178
- return el
179
- }
180
- if (!signals.has(el)) {
181
- signals.set(el, new Proxy(el, domSignalHandler))
182
- }
183
- return signals.get(el)
184
- }
185
139
 
186
140
  let tracers = []
187
141
  let tracing = false
@@ -254,7 +208,7 @@ let batchMode = 0
254
208
  * Triggers any reactor function that depends on this signal
255
209
  * to re-compute its values
256
210
  */
257
- function notifySet(self, context={}) {
211
+ export function notifySet(self, context={}) {
258
212
  if (disableTracking) {
259
213
  return
260
214
  }
@@ -287,60 +241,8 @@ function notifySet(self, context={}) {
287
241
  }
288
242
  }
289
243
 
290
- const observers = new WeakMap()
291
-
292
- function domListen(el, signal) {
293
- let oldContentHTML = el.innerHTML
294
- let oldContentText = el.innerText
295
- if (!observers.has(el)) {
296
- const observer = new MutationObserver((mutationList, observer) => {
297
- // collect changes
298
- const changes = {}
299
- for (const mutation of mutationList) {
300
- if (mutation.type==='attributes') {
301
- // check if any listeners for each attribute
302
- changes[mutation.attributeName] = mutation.attributeOldValue
303
- } else if (mutation.type==='subtree' || mutation.type==='characterData') {
304
- // change on innerHTML/innerText
305
- if (el.innerHTML != oldContentHTML) {
306
- changes.innerHTML = oldContentHTML
307
- oldContentHTML = el.innerHTML
308
- }
309
- if (el.innerText != oldContentText) {
310
- changes.innerText = oldContentText
311
- oldContentText = el.innerText
312
- }
313
- }
314
- }
315
- for (const prop in changes) {
316
- notifySet(signal, makeContext(prop, { was: changes[prop], now: el[prop] }))
317
- }
318
- })
319
- observer.observe(el, {
320
- characterData: true,
321
- subtree: true,
322
- attributes: true,
323
- attributesOldValue: true
324
- })
325
- observers.set(el, observer)
326
- //@TODO: unregister the observer when el is removed from the dom (after a timeout)
327
- if (el.matches('input, textarea, select')) {
328
- let prevValue = el.value
329
- el.addEventListener('change', (evt) => {
330
- notifySet(signal, makeContext('value', { was: prevValue, now: el.value }))
331
- prevValue = el.value
332
- })
333
- if (el.matches('input, textarea')) {
334
- el.addEventListener('input', (evt) => {
335
- notifySet(signal, makeContext('value', { was: prevValue, now: el.value }))
336
- prevValue = el.value
337
- })
338
- }
339
- }
340
- }
341
- }
342
244
 
343
- function makeContext(property, change) {
245
+ export function makeContext(property, change) {
344
246
  let context = new Map()
345
247
  if (typeof property === 'object') {
346
248
  for (let prop in property) {
@@ -374,7 +276,7 @@ function clearContext(listener) {
374
276
  * then it adds the current reactor (top of this stack) to its
375
277
  * listeners. These are later called if this property changes
376
278
  */
377
- function notifyGet(self, property) {
279
+ export function notifyGet(self, property) {
378
280
  if (disableTracking) {
379
281
  return
380
282
  }
@@ -734,7 +636,7 @@ export function untracked(fn) {
734
636
  let seen = new WeakMap()
735
637
 
736
638
  function innerUnwrap(ob) {
737
- if (!ob || typeof ob!=='object' || seen.has(ob)) {
639
+ if (!ob || typeof ob!=='object' || ob instanceof HTMLElement || seen.has(ob)) {
738
640
  return
739
641
  }
740
642
  seen.set(ob, true)