@abide/abide 0.105.0 → 0.105.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @abide/abide
2
2
 
3
+ ## 0.105.1
4
+
5
+ ### Patch Changes
6
+
7
+ - fdfe14c: an emptied list clears its region in one write ([`b190cd1`](https://github.com/briancray/abide/commit/b190cd19cd5cf0bda5797a6308eddb5ada1af13b))
8
+
3
9
  ## 0.105.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abide/abide",
3
- "version": "0.105.0",
3
+ "version": "0.105.1",
4
4
  "type": "module",
5
5
  "description": "Isomorphic type-safe framework for reactive async interfaces for humans and machines",
6
6
  "license": "MIT",
package/src/ui/leave.ts CHANGED
@@ -23,6 +23,11 @@ export function holdWhileLeaving(
23
23
  settling.then(done, done)
24
24
  }
25
25
 
26
+ /** Whether any element is leaving, so a removal has to ask each node whether it holds one. */
27
+ export function anyLeaving(): boolean {
28
+ return LEAVING.size !== 0
29
+ }
30
+
26
31
  /** Removes `node`, or holds it where it is while an element it holds is leaving. */
27
32
  export function leave(node: ChildNode): void {
28
33
  if (LEAVING.size !== 0)
@@ -3,6 +3,7 @@ import { LAYER_RECONCILE } from './LAYER_NAMES.ts'
3
3
  import type { List } from './List.ts'
4
4
  import { HYDRATE_LOG } from './Mismatch.ts'
5
5
  import { beginLayer, endLayer } from './measureLayer.ts'
6
+ import { emptyRegion } from './regions.ts'
6
7
  import { dropRow, makeRow, moveRow, type Row } from './rows.ts'
7
8
 
8
9
  // 41.8 — a KEYED list, reconciled by moving the nodes that are already there rather than by
@@ -21,6 +22,10 @@ import { dropRow, makeRow, moveRow, type Row } from './rows.ts'
21
22
  * survive are MOVED, never remade.
22
23
  */
23
24
  export function reconcile(list: List, data: unknown[]): void {
25
+ if (data.length === 0) {
26
+ clear(list)
27
+ return
28
+ }
24
29
  const region = list.region
25
30
  const current = list.rows
26
31
  const wanted = data.length
@@ -182,6 +187,26 @@ export function reconcile(list: List, data: unknown[]): void {
182
187
  }
183
188
  }
184
189
 
190
+ /**
191
+ * Drops every row, emptying the region in one write rather than one removal a row.
192
+ *
193
+ * NOTHING WANTED IS ITS OWN SHAPE: there is no key to match and no order to keep, so the map,
194
+ * the survivor walk and the placement walk all answer nothing. Every row is still STOPPED first,
195
+ * as `dropRow` would, so a binding cannot wake over a node that is already gone.
196
+ */
197
+ function clear(list: List): void {
198
+ const parent = list.region.close.parentNode
199
+ if (parent === null) return
200
+ beginLayer(LAYER_RECONCILE)
201
+ try {
202
+ for (const row of list.rows) row.scope.stop()
203
+ list.rows = []
204
+ emptyRegion(list.region, parent)
205
+ } finally {
206
+ endLayer(LAYER_RECONCILE)
207
+ }
208
+ }
209
+
185
210
  /**
186
211
  * Whether a patch wrote to the row's item since the row was made.
187
212
  *
package/src/ui/regions.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { REGION_CLOSE, REGION_OPEN } from '#shared/ANCHORS.ts'
2
2
  import { LAYER_FILL } from './LAYER_NAMES.ts'
3
- import { leave } from './leave.ts'
3
+ import { anyLeaving, leave } from './leave.ts'
4
4
  import { Mismatch } from './Mismatch.ts'
5
5
  import { beginLayer, endLayer } from './measureLayer.ts'
6
6
 
@@ -97,7 +97,7 @@ export function fillRegion(
97
97
  throw new Mismatch(
98
98
  `abide: this region is not in a document, so the client and the server disagree about this region's shape (41.10).`,
99
99
  )
100
- removeUntil(region.open.nextSibling, region.close)
100
+ emptyRegion(region, parent)
101
101
  if (build === undefined) return
102
102
  beginLayer(LAYER_FILL)
103
103
  try {
@@ -126,6 +126,31 @@ export function fillEmpty(
126
126
  return true
127
127
  }
128
128
 
129
+ /**
130
+ * Removes everything between a region's anchors, which stay.
131
+ *
132
+ * ONE WRITE WHERE THE REGION IS ALL ITS PARENT HOLDS — a `<tbody>` or a `<ul>` around one
133
+ * `{#for}`, the common shape of a list emptied — and the anchors put back after it. A removal per
134
+ * node is a crossing into the engine per node; the engine's own clear is one.
135
+ *
136
+ * NODE BY NODE WHILE ANYTHING IS LEAVING (32.38), because a node holding a leaving element has to
137
+ * stay where it stands, and only `leave` asks.
138
+ */
139
+ export function emptyRegion(region: Region, parent: ParentNode): void {
140
+ const { open, close } = region
141
+ if (
142
+ anyLeaving() ||
143
+ parent.firstChild !== open ||
144
+ parent.lastChild !== close
145
+ ) {
146
+ removeUntil(open.nextSibling, close)
147
+ return
148
+ }
149
+ if (open.nextSibling === close) return
150
+ parent.textContent = ''
151
+ parent.append(open, close)
152
+ }
153
+
129
154
  /** Removes `from` and every sibling after it, up to and not including `end`. */
130
155
  export function removeUntil(from: ChildNode | null, end: Node | null): void {
131
156
  let node = from