@srfnstack/fntags 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srfnstack/fntags",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "author": "Robert Kempton <r@snow87.com>",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/srfnstack/fntags",
@@ -1 +1 @@
1
- {"version":3,"file":"fntags.d.mts","sourceRoot":"","sources":["fntags.mjs"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,2DALW,MAAM,eACH,CAAC,IAAI,MAAO,CAAC,OA6C1B;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,iEAPgB,GAAG,cAoIlB;AAwWD;;;;GAIG;AACH,iCAHW,GAAG,GACD,IAAI,CAuBhB;AAqFD;;;;GAIG;AACH,6BAHW,GAAG,GACD,OAAO,CAInB;AAED;;;;GAIG;AACH,mCAHW,GAAG,GACF,MAAM,CAIjB;AAED;;;;;;;;;;GAUG;AACH,kEALW,MAAM,GAAC,MAAM,OACb,MAAM,YACN,MAAM,EAAE,GAAC,IAAI,EAAE,KAezB;AAOD;;;;;;;;;;;;;;;GAeG;AACH,qDAJkB,IAAI,iBACH,IAAI,CA+DtB;;;;;;;;;kCAzvBmC,CAAC,YAAY,CAAC,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;;2BAE/C,CAAC,MAAI,CAAC,IAAI,GAAC,GAAG,CAAC,CAAC,GAAC,GAAG,GAAC,IAAI,gCAAkC,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;qBAG9E,MAAM,KAAG,IAAI;;;;sCAEI,CAAC,YAAY,CAAC,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;mCACvC,CAAC,YAAY,CAAC,KAAG,MAAM,KAAK,MAAM;;;;yCAC7B,GAAG,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;+CACrB,GAAG,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;;;kBAC7C,GAAG,KAAG,IAAI;;;;cAGhB,MAAK,GAAG;;;;;qBACC,CAAC,KAAG,IAAI;;;;;;oBAEV,MAAM,KAAG,GAAG;;;;oBAGZ,MAAM,SAAS,GAAG,mBAAmB,OAAO,KAAG,IAAI;;;;uCAClC,CAAC,YAAY,CAAC,KAAG,IAAI,KAAK,IAAI;;;;eACtD,OAAO;;;;;sDAKqB,CAAC,KAAG,CAAC"}
1
+ {"version":3,"file":"fntags.d.mts","sourceRoot":"","sources":["fntags.mjs"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,2DALW,MAAM,eACH,CAAC,IAAI,MAAO,CAAC,OA6C1B;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,iEAPgB,GAAG,cAoIlB;AAgYD;;;;GAIG;AACH,iCAHW,GAAG,GACD,IAAI,CAuBhB;AAqFD;;;;GAIG;AACH,6BAHW,GAAG,GACD,OAAO,CAInB;AAED;;;;GAIG;AACH,mCAHW,GAAG,GACF,MAAM,CAIjB;AAED;;;;;;;;;;GAUG;AACH,kEALW,MAAM,GAAC,MAAM,OACb,MAAM,YACN,MAAM,EAAE,GAAC,IAAI,EAAE,KAezB;AAOD;;;;;;;;;;;;;;;GAeG;AACH,qDAJkB,IAAI,iBACH,IAAI,CA+DtB;;;;;;;;;kCAjxBmC,CAAC,YAAY,CAAC,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;;2BAE/C,CAAC,MAAI,CAAC,IAAI,GAAC,GAAG,CAAC,CAAC,GAAC,GAAG,GAAC,IAAI,gCAAkC,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;qBAG9E,MAAM,KAAG,IAAI;;;;sCAEI,CAAC,YAAY,CAAC,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;mCACvC,CAAC,YAAY,CAAC,KAAG,MAAM,KAAK,MAAM;;;;yCAC7B,GAAG,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;+CACrB,GAAG,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;;;kBAC7C,GAAG,KAAG,IAAI;;;;cAGhB,MAAK,GAAG;;;;;qBACC,CAAC,KAAG,IAAI;;;;;;oBAEV,MAAM,KAAG,GAAG;;;;oBAGZ,MAAM,SAAS,GAAG,mBAAmB,OAAO,KAAG,IAAI;;;;uCAClC,CAAC,YAAY,CAAC,KAAG,IAAI,KAAK,IAAI;;;;eACtD,OAAO;;;;;sDAKqB,CAAC,KAAG,CAAC"}
package/src/fntags.mjs CHANGED
@@ -393,7 +393,7 @@ function doBindChildren (parent, element) {
393
393
  if (!Array.isArray(ctx.currentValue)) {
394
394
  throw new Error('You can only use bindChildren with a state that contains an array. try myState([mystate]) before calling this function.')
395
395
  }
396
- ctx.currentValue = ctx.currentValue.map(v => v.isFnState ? v : fnstate(v))
396
+
397
397
  ctx.bindContexts.push({ element, parent })
398
398
  this.subscribe((_, oldState) => {
399
399
  if (!Array.isArray(ctx.currentValue)) {
@@ -490,60 +490,79 @@ function arrangeElements (ctx, bindContext, oldState) {
490
490
  if (!ctx?.currentValue?.length) {
491
491
  bindContext.parent.textContent = ''
492
492
  bindContext.boundElementByKey = {}
493
+ bindContext.elementStates = {}
493
494
  ctx.selectObservers = {}
494
495
  return
495
496
  }
496
497
 
498
+ // Initialize the element state map if it doesn't exist
499
+ if (!bindContext.elementStates) {
500
+ bindContext.elementStates = {}
501
+ }
502
+
497
503
  const keys = {}
498
504
  const keysArr = []
499
- let oldStateMap = null
500
- for (const i in ctx.currentValue) {
501
- let valueState = ctx.currentValue[i]
502
- // if the value is not a fnstate, we need to wrap it
503
- if (valueState === null || valueState === undefined || !valueState.isFnState) {
504
- if (oldStateMap === null) {
505
- oldStateMap = oldState && oldState.reduce((acc, v) => {
506
- const key = keyMapper(ctx.mapKey, v.isFnState ? v() : v)
507
- acc[key] = v
508
- return acc
509
- }, {})
510
- }
511
- // check if we have an old state for this key
512
- const key = keyMapper(ctx.mapKey, valueState)
513
- if (oldStateMap && oldStateMap[key]) {
514
- const newValue = valueState
515
- valueState = ctx.currentValue[i] = oldStateMap[key]
516
- valueState(newValue)
517
- } else {
518
- valueState = ctx.currentValue[i] = fnstate(valueState)
519
- }
520
- }
521
- const key = keyMapper(ctx.mapKey, valueState())
505
+
506
+ for (let i = 0; i < ctx.currentValue.length; i++) {
507
+ const rawValue = ctx.currentValue[i]
508
+ // Use the raw value to get the key
509
+ const key = keyMapper(ctx.mapKey, rawValue)
510
+
522
511
  if (keys[key]) {
523
512
  if (oldState) ctx.state(oldState)
524
513
  throw new Error('Duplicate keys in a bound array are not allowed, state reset to previous value.')
525
514
  }
515
+
526
516
  keys[key] = i
527
517
  keysArr[i] = key
518
+
519
+ // We look up the persistent fnstate for this key, or create it if new
520
+ let elementState = bindContext.elementStates[key]
521
+ if (!elementState) {
522
+ elementState = fnstate(rawValue)
523
+ // Ensure the parent state is set so child states can listen to selection changes
524
+ elementState.parentCtx = ctx
525
+ bindContext.elementStates[key] = elementState
526
+ // If the user replaces the value of this elementState (e.g. elementState(newObj)),
527
+ // we must update the original array to keep it in sync.
528
+ elementState.subscribe((newItem) => {
529
+ const currentArr = ctx.state()
530
+ // If the parent is no longer an array, abort
531
+ if (!Array.isArray(currentArr)) return
532
+
533
+ // find the element dynamically because sorting/filtering might have shifted the index
534
+ const idx = currentArr.findIndex(item => keyMapper(ctx.mapKey, item) === key)
535
+
536
+ if (idx > -1 && currentArr[idx] !== newItem) {
537
+ currentArr[idx] = newItem
538
+ }
539
+ })
540
+ } else {
541
+ // Update existing state with new raw value.
542
+ // fnstate handles the equality check (newValue !== oldValue) internally.
543
+ elementState(rawValue)
544
+ }
528
545
  }
529
546
 
530
547
  let prev = null
531
548
  const parent = bindContext.parent
532
549
 
550
+ // 2. Render Loop
533
551
  for (let i = ctx.currentValue.length - 1; i >= 0; i--) {
534
552
  const key = keysArr[i]
535
- const valueState = ctx.currentValue[i]
553
+ // Retrieve the element state to pass to the builder
554
+ const valueState = bindContext.elementStates[key]
555
+
536
556
  let current = bindContext.boundElementByKey[key]
537
557
  let isNew = false
538
- // ensure the parent state is always set and can be accessed by the child states to listen to the selection change and such
539
- if (valueState.parentCtx === undefined) {
540
- valueState.parentCtx = ctx
541
- }
558
+
542
559
  if (current === undefined) {
543
560
  isNew = true
561
+ // Pass the valueState (the fnstate wrapper) to the user's element builder
544
562
  current = bindContext.boundElementByKey[key] = renderNode(evaluateElement(bindContext.element, valueState))
545
563
  current.key = key
546
564
  }
565
+
547
566
  // place the element in the parent
548
567
  if (prev == null) {
549
568
  if (!parent.lastChild || parent.lastChild.key !== current.key) {
@@ -560,9 +579,12 @@ function arrangeElements (ctx, bindContext, oldState) {
560
579
  } else if (prev.previousSibling.key !== current.key) {
561
580
  // the previous was deleted all together, so we will delete it and replace the element
562
581
  if (keys[prev.previousSibling.key] === undefined) {
563
- delete bindContext.boundElementByKey[prev.previousSibling.key]
564
- if (ctx.selectObservers[prev.previousSibling.key] !== undefined && current.insertAdjacentElement !== undefined) {
565
- delete ctx.selectObservers[prev.previousSibling.key]
582
+ const keyToDelete = prev.previousSibling.key
583
+ delete bindContext.boundElementByKey[keyToDelete]
584
+ delete bindContext.elementStates[keyToDelete] // Cleanup element state
585
+
586
+ if (ctx.selectObservers[keyToDelete] !== undefined && current.insertAdjacentElement !== undefined) {
587
+ delete ctx.selectObservers[keyToDelete]
566
588
  }
567
589
  prev.previousSibling.replaceWith(current)
568
590
  } else if (isNew) {
@@ -581,11 +603,13 @@ function arrangeElements (ctx, bindContext, oldState) {
581
603
  prev = current
582
604
  }
583
605
 
584
- // catch any strays
606
+ // 3. Catch any strays (Cleanup)
585
607
  for (const key in bindContext.boundElementByKey) {
586
608
  if (keys[key] === undefined) {
587
609
  bindContext.boundElementByKey[key].remove()
588
610
  delete bindContext.boundElementByKey[key]
611
+ delete bindContext.elementStates[key] // Cleanup element state
612
+
589
613
  if (ctx.selectObservers[key] !== undefined) {
590
614
  delete ctx.selectObservers[key]
591
615
  }