@srfnstack/fntags 1.2.2 → 1.2.3

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.2.2",
3
+ "version": "1.2.3",
4
4
  "author": "Robert Kempton <r@snow87.com>",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/srfnstack/fntags",
@@ -50,10 +50,10 @@
50
50
  "typescript": "^5.3.3"
51
51
  },
52
52
  "scripts": {
53
- "test": "cp src/*.mjs docs/lib/ && npm run lint && cypress run --spec \"test/**\" --headless -b chrome",
54
- "cypress": "cypress run --headed --spec test/** -b chrome",
55
- "lint": "standard --env browser src && standard --env browser --env jest --global Prism --global cy test",
56
- "lint:fix": "standard --env browser --fix src && standard --env browser --env jest --global Prism --global cy --fix test",
53
+ "test": "cp src/*.mjs docs/lib/ && npm run lint && npm test --prefix packages/fntags-testing && cypress run --spec \"test/docs/**\" --headless -b chrome",
54
+ "cypress": "cypress run --headed --spec test/docs/** -b chrome",
55
+ "lint": "standard --env browser src && standard --env browser --env jest --global Prism --global cy test/docs",
56
+ "lint:fix": "standard --env browser --fix src && standard --env browser --env jest --global Prism --global cy --fix test/docs",
57
57
  "typedef": "rm -rf src/*.mts* && tsc",
58
58
  "docs": "typedoc --plugin typedoc-plugin-markdown --out docs/types --json docs/types.json ./src/*.mjs && node scripts/generateApi.js",
59
59
  "generate-api": "node scripts/generateApi.js",
@@ -1 +1 @@
1
- {"version":3,"file":"fntags.d.mts","sourceRoot":"","sources":["fntags.mjs"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,2DALW,MAAM,eACH,CAAC,IAAI,MAAO,CAAC,OA6C1B;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,iEAPgB,GAAG,cAoIlB;AA8cD;;;;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;;;;;;;;;kCA/1BmC,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":"AAcA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,2DALW,MAAM,eACH,CAAC,IAAI,MAAO,CAAC,OA6C1B;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,iEAPgB,GAAG,cAqIlB;AAqfD;;;;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;;;;;;;;;kCAv4BmC,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
@@ -141,7 +141,8 @@ export function fnstate (initialValue, mapKey) {
141
141
  } else {
142
142
  const oldState = ctx.currentValue
143
143
  ctx.currentValue = newState
144
- for (const observer of ctx.observers) {
144
+ const snapshot = ctx.observers.slice()
145
+ for (const observer of snapshot) {
145
146
  observer.fn(newState, oldState)
146
147
  }
147
148
  }
@@ -282,6 +283,24 @@ const subscribeSelect = (ctx, callback) => {
282
283
  parentCtx.selectObservers[key] = []
283
284
  }
284
285
  parentCtx.selectObservers[key].push(callback)
286
+
287
+ // Return an unsub that removes this callback, and register it in
288
+ // activeRenderCleanups so parent bindAs re-renders clean it up.
289
+ let active = true
290
+ const unsub = () => {
291
+ if (!active) return
292
+ active = false
293
+ const arr = parentCtx.selectObservers[key]
294
+ if (arr) {
295
+ const idx = arr.indexOf(callback)
296
+ if (idx !== -1) arr.splice(idx, 1)
297
+ if (arr.length === 0) delete parentCtx.selectObservers[key]
298
+ }
299
+ }
300
+ if (activeRenderCleanups !== null) {
301
+ activeRenderCleanups.push(unsub)
302
+ }
303
+ return unsub
285
304
  }
286
305
 
287
306
  function doBindSelectAttr (attribute) {
@@ -335,10 +354,10 @@ function doSelect (key) {
335
354
  const currentSelected = ctx.selected
336
355
  ctx.selected = key
337
356
  if (ctx.selectObservers[currentSelected] !== undefined) {
338
- for (const obs of ctx.selectObservers[currentSelected]) obs(ctx.selected)
357
+ for (const obs of ctx.selectObservers[currentSelected].slice()) obs(ctx.selected)
339
358
  }
340
359
  if (ctx.selectObservers[ctx.selected] !== undefined) {
341
- for (const obs of ctx.selectObservers[ctx.selected]) obs(ctx.selected)
360
+ for (const obs of ctx.selectObservers[ctx.selected].slice()) obs(ctx.selected)
342
361
  }
343
362
  }
344
363
 
@@ -417,7 +436,27 @@ function doBindChildren (parent, element) {
417
436
  throw new Error('You can only use bindChildren with a state that contains an array. try myState([mystate]) before calling this function.')
418
437
  }
419
438
 
420
- ctx.bindContexts.push({ element, parent })
439
+ const bindContext = { element, parent }
440
+ ctx.bindContexts.push(bindContext)
441
+
442
+ // If inside a parent bindAs render, register a cleanup that tears down all
443
+ // per-item subscriptions when the parent re-renders or is itself torn down.
444
+ // Without this, nested bindAttr/bindStyle/bindAs subscriptions created for
445
+ // each array item would be orphaned when the parent replaces this tree.
446
+ if (activeRenderCleanups !== null) {
447
+ activeRenderCleanups.push(() => {
448
+ if (bindContext.elementCleanups) {
449
+ for (const key in bindContext.elementCleanups) {
450
+ for (const unsub of bindContext.elementCleanups[key]) unsub()
451
+ }
452
+ bindContext.elementCleanups = {}
453
+ }
454
+ // Remove this bindContext so reconcile doesn't operate on stale state
455
+ const idx = ctx.bindContexts.indexOf(bindContext)
456
+ if (idx !== -1) ctx.bindContexts.splice(idx, 1)
457
+ })
458
+ }
459
+
421
460
  this.subscribe((_, oldState) => {
422
461
  if (!Array.isArray(ctx.currentValue)) {
423
462
  console.warn('A state used with bindChildren was updated to a non array value. This will be converted to an array of 1 and the state will be updated.')
@@ -460,9 +499,10 @@ const doBind = function (ctx, element, handleReplace) {
460
499
  // If inside a parent bindAs render, register a cascading cleanup that tears
461
500
  // down both the driving subscription and all subscriptions created inside
462
501
  // this bindAs's render, so they don't outlive the parent's re-render.
463
- if (prevRenderCleanups !== null && elCtx.drivingUnsub) {
502
+ if (prevRenderCleanups !== null && (elCtx.drivingUnsub || elCtx.selectUnsub)) {
464
503
  prevRenderCleanups.push(() => {
465
- elCtx.drivingUnsub()
504
+ if (elCtx.drivingUnsub) elCtx.drivingUnsub()
505
+ if (elCtx.selectUnsub) elCtx.selectUnsub()
466
506
  runCleanups(elCtx)
467
507
  })
468
508
  }
@@ -517,7 +557,9 @@ const updateReplacer = (ctx, element, elCtx) => (_, oldValue) => {
517
557
  function doBindSelect (element) {
518
558
  element = element ?? this
519
559
  const ctx = this._ctx
520
- return doBind(ctx, element, (elCtx) => subscribeSelect(ctx, updateReplacer(ctx, element, elCtx)))
560
+ return doBind(ctx, element, (elCtx) => {
561
+ elCtx.selectUnsub = subscribeSelect(ctx, updateReplacer(ctx, element, elCtx))
562
+ })
521
563
  }
522
564
 
523
565
  function doBindAs (element) {
@@ -671,9 +713,7 @@ function arrangeElements (ctx, bindContext, oldState) {
671
713
  delete bindContext.boundElementByKey[keyToDelete]
672
714
  delete bindContext.elementStates[keyToDelete] // Cleanup element state
673
715
 
674
- if (ctx.selectObservers[keyToDelete] !== undefined && current.insertAdjacentElement !== undefined) {
675
- delete ctx.selectObservers[keyToDelete]
676
- }
716
+ delete ctx.selectObservers[keyToDelete]
677
717
  prev.previousSibling.replaceWith(current)
678
718
  } else if (isNew) {
679
719
  // insertAdjacentElement is faster, but some nodes don't have it (lookin' at you text)