@zakkster/lite-observe 1.1.0 → 1.1.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 +20 -0
- package/README.md +1 -1
- package/llms.txt +3 -1
- package/package.json +1 -1
- package/src/Resize.js +52 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.1 -- Late mixed-box joiner reads the last delivered size, not a stale zero
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A late `observeResize` mixed-box joiner no longer reads 0 until the next
|
|
8
|
+
resize (B-4).** 1.1.0's lazy box-pair mint was correct for cost but wrong for
|
|
9
|
+
value: when a second consumer asked for the other box on an element already
|
|
10
|
+
under observation, its pair was minted fresh at `signal(0)` and nothing wrote
|
|
11
|
+
it until the element next resized -- on a settled layout, potentially never, so
|
|
12
|
+
a border-box reader mounted into a stable layout saw a zero-size element with
|
|
13
|
+
no error and no warning. `drainResize` now parks the last delivered box
|
|
14
|
+
dimensions on the slot as four primitives (`lastCW`/`lastCH`/`lastBW`/`lastBH`)
|
|
15
|
+
-- numbers only, no browser object retained, no allocation added to the hot
|
|
16
|
+
path -- and a lazily minted pair is seeded from them (`=== null ? 0 : x`, so a
|
|
17
|
+
parked 0 survives and a never-delivered box still reads 0). Before any delivery
|
|
18
|
+
the parked fields are null and a freshly minted pair reads 0, unchanged. The
|
|
19
|
+
slot literal births the four fields as `null` for hidden-class stability. No
|
|
20
|
+
API change; the drain path stays zero-allocation (Phase A `maxMajor=0` holds)
|
|
21
|
+
and retention stays 0 per cycle.
|
|
22
|
+
|
|
3
23
|
## 1.1.0 -- Lazy resize box-pair allocation + pooled-node census docs
|
|
4
24
|
|
|
5
25
|
### Changed
|
package/README.md
CHANGED
|
@@ -146,7 +146,7 @@ interface ResizeHandle {
|
|
|
146
146
|
|
|
147
147
|
Width and height are independent fine-grained signals. N consumers of the same element share one underlying `ResizeObserver.observe()` call; the element is unobserved when the last handle is disposed.
|
|
148
148
|
|
|
149
|
-
`options.box` selects which box the `width` / `height` signals surface: `'content'` (default) mirrors `entry.contentRect`; `'border'` mirrors `entry.borderBoxSize` (includes padding + border, matching `getBoundingClientRect()`). Mixed-box consumers of the *same* element still share a single underlying observation -- one `ResizeObserver` fires both, and each handle reads the pair it asked for. On legacy runtimes without `borderBoxSize`, a border-box reader sees content-box values until the next layout.
|
|
149
|
+
`options.box` selects which box the `width` / `height` signals surface: `'content'` (default) mirrors `entry.contentRect`; `'border'` mirrors `entry.borderBoxSize` (includes padding + border, matching `getBoundingClientRect()`). Mixed-box consumers of the *same* element still share a single underlying observation -- one `ResizeObserver` fires both, and each handle reads the pair it asked for. A consumer that joins an already-observed element after a delivery has happened is seeded from that element's last delivered size for its box (the slot parks those numbers), so it reads the real size at once rather than a zero that would persist until the next resize; before any delivery has arrived, a freshly minted pair reads 0. On legacy runtimes without `borderBoxSize`, a border-box reader sees content-box values until the next layout.
|
|
150
150
|
|
|
151
151
|
Pooled signal nodes: 2 while observed (the requested box pair; 4 if both content- and border-box consumers observe the same element). Freed on last dispose.
|
|
152
152
|
|
package/llms.txt
CHANGED
|
@@ -12,7 +12,9 @@ registry: N consumers of the same element share one underlying observer.
|
|
|
12
12
|
independent signals (Object.is-gated). First emission async. `options.box`
|
|
13
13
|
selects `'content'` (default, contentRect) or `'border'` (borderBoxSize,
|
|
14
14
|
matches getBoundingClientRect). Mixed-box consumers of one element still
|
|
15
|
-
share a single observation
|
|
15
|
+
share a single observation; a late joiner is seeded from the element's
|
|
16
|
+
last delivered size for its box (0 if nothing has been delivered yet),
|
|
17
|
+
not a stale zero until the next resize.
|
|
16
18
|
Pooled nodes: 2 while observed (the requested box pair; 4 if both content-
|
|
17
19
|
and border-box consumers observe the same element). Freed on last dispose.
|
|
18
20
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zakkster/lite-observe",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
|
|
5
5
|
"description": "Zero-GC reactive bridge for the DOM observer APIs. ResizeObserver, IntersectionObserver, MutationObserver, matchMedia, and Page Visibility collapsed to fine-grained lite-signal signals. Shared registry: N components observing the same element pay one observer cost.",
|
|
6
6
|
"type": "module",
|
package/src/Resize.js
CHANGED
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
* source. ResizeObserver fires with sub-pixel precision; we forward the raw
|
|
13
13
|
* `contentRect.width` / `contentRect.height` values.
|
|
14
14
|
*
|
|
15
|
+
* Each slot also parks the last delivered box dimensions as four primitives
|
|
16
|
+
* (no browser object retained, no allocation on the drain path). A late
|
|
17
|
+
* mixed-box joiner that mints the other pair after delivery has already
|
|
18
|
+
* happened reads those last numbers immediately, not a stale zero that would
|
|
19
|
+
* persist until the element next resizes; before any delivery the parked
|
|
20
|
+
* fields are null and a freshly minted pair reads 0.
|
|
21
|
+
*
|
|
15
22
|
* SSR-safe. In a Node environment without `ResizeObserver`, signals are
|
|
16
23
|
* created and stay at zero; observe is a no-op.
|
|
17
24
|
*/
|
|
@@ -59,6 +66,24 @@ function drainResize() {
|
|
|
59
66
|
// rect is a field read, not an allocation. Hoisted here because the
|
|
60
67
|
// border fallback reads it too; content-box writes use it directly.
|
|
61
68
|
const rect = entry.contentRect;
|
|
69
|
+
// Border box: includes padding + border. Per spec the field is an
|
|
70
|
+
// array (to support multi-column layout); we read the first entry.
|
|
71
|
+
// Older browsers without borderBoxSize fall back to contentRect; for
|
|
72
|
+
// zero-alloc cleanliness we just mirror content here when it is absent,
|
|
73
|
+
// so a legacy-runtime border consumer sees content-box values until the
|
|
74
|
+
// next layout pass. Resolve both dimensions into locals once (numbers,
|
|
75
|
+
// not references) so the parking and the guarded write below reuse them.
|
|
76
|
+
const bbs = entry.borderBoxSize;
|
|
77
|
+
const bw = (bbs && bbs.length > 0) ? bbs[0].inlineSize : rect.width;
|
|
78
|
+
const bh = (bbs && bbs.length > 0) ? bbs[0].blockSize : rect.height;
|
|
79
|
+
// Park the last delivered numbers on the slot unconditionally so a late
|
|
80
|
+
// mixed-box joiner can seed its lazily-minted pair from the element's
|
|
81
|
+
// real size instead of a stale zero (B-4). Four primitives: zero
|
|
82
|
+
// allocation, no browser object retained.
|
|
83
|
+
slot.lastCW = rect.width;
|
|
84
|
+
slot.lastCH = rect.height;
|
|
85
|
+
slot.lastBW = bw;
|
|
86
|
+
slot.lastBH = bh;
|
|
62
87
|
// Content box: the historical default. Matches the rect a consumer
|
|
63
88
|
// would derive from `getBoundingClientRect` after subtracting
|
|
64
89
|
// padding + border. Written only if a content-box consumer minted it.
|
|
@@ -66,24 +91,10 @@ function drainResize() {
|
|
|
66
91
|
slot.contentWidth.set(rect.width);
|
|
67
92
|
slot.contentHeight.set(rect.height);
|
|
68
93
|
}
|
|
69
|
-
// Border box:
|
|
70
|
-
// array (to support multi-column layout); we read the first entry.
|
|
71
|
-
// Older browsers without borderBoxSize fall back to contentRect
|
|
72
|
-
// plus the differential measured via getBoundingClientRect at the
|
|
73
|
-
// next layout. For zero-alloc cleanliness we just mirror content
|
|
74
|
-
// here when borderBoxSize is absent; consumers who specifically
|
|
75
|
-
// requested border-box on a legacy runtime will see content-box
|
|
76
|
-
// values until the next layout pass. Written only if a border-box
|
|
77
|
-
// consumer minted the pair.
|
|
94
|
+
// Border box: written only if a border-box consumer minted the pair.
|
|
78
95
|
if (slot.borderWidth !== null) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
slot.borderWidth.set(bbs[0].inlineSize);
|
|
82
|
-
slot.borderHeight.set(bbs[0].blockSize);
|
|
83
|
-
} else {
|
|
84
|
-
slot.borderWidth.set(rect.width);
|
|
85
|
-
slot.borderHeight.set(rect.height);
|
|
86
|
-
}
|
|
96
|
+
slot.borderWidth.set(bw);
|
|
97
|
+
slot.borderHeight.set(bh);
|
|
87
98
|
}
|
|
88
99
|
}
|
|
89
100
|
}
|
|
@@ -127,7 +138,9 @@ function ensureObserver() {
|
|
|
127
138
|
* its own dispose. The element stays observed until every handle has been
|
|
128
139
|
* disposed. Consumers requesting different boxes for the same element
|
|
129
140
|
* cooperate transparently: a single ResizeObserver fires both, and each
|
|
130
|
-
* handle surfaces the pair it asked for.
|
|
141
|
+
* handle surfaces the pair it asked for. A handle that joins an already-
|
|
142
|
+
* observed element after a delivery is seeded from that element's last
|
|
143
|
+
* delivered size for its box; before any delivery it reads 0.
|
|
131
144
|
*
|
|
132
145
|
* @param {Element} element
|
|
133
146
|
* @param {ResizeOptions} [options]
|
|
@@ -144,6 +157,14 @@ export function observeResize(element, options) {
|
|
|
144
157
|
contentHeight: null,
|
|
145
158
|
borderWidth: null,
|
|
146
159
|
borderHeight: null,
|
|
160
|
+
// Last delivered box dimensions, parked as primitives by
|
|
161
|
+
// drainResize so a late mixed-box joiner seeds its pair from the
|
|
162
|
+
// element's real size, not a stale zero (B-4). null until the first
|
|
163
|
+
// delivery; a null-check (not `??`) reads that as 0 at mint time.
|
|
164
|
+
lastCW: null,
|
|
165
|
+
lastCH: null,
|
|
166
|
+
lastBW: null,
|
|
167
|
+
lastBH: null,
|
|
147
168
|
refCount: 0
|
|
148
169
|
};
|
|
149
170
|
slots.set(element, slot);
|
|
@@ -152,11 +173,21 @@ export function observeResize(element, options) {
|
|
|
152
173
|
}
|
|
153
174
|
// Lazily mint the requested box pair if absent. Placed before refCount++
|
|
154
175
|
// so it also covers a later mixed-box caller joining an existing slot that
|
|
155
|
-
// only holds the other pair.
|
|
176
|
+
// only holds the other pair. Seed from the last delivered numbers so a late
|
|
177
|
+
// joiner reads the element's real size at once, not a stale zero until the
|
|
178
|
+
// next resize (B-4). Explicit `=== null ? 0 : x` rather than `?? 0`: only
|
|
179
|
+
// null (never delivered) means "no size yet" and reads 0; any delivered
|
|
180
|
+
// number, 0 included, passes through untouched. Law: null is not zero.
|
|
156
181
|
if (box === 'border') {
|
|
157
|
-
if (slot.borderWidth === null) {
|
|
182
|
+
if (slot.borderWidth === null) {
|
|
183
|
+
slot.borderWidth = signal(slot.lastBW === null ? 0 : slot.lastBW);
|
|
184
|
+
slot.borderHeight = signal(slot.lastBH === null ? 0 : slot.lastBH);
|
|
185
|
+
}
|
|
158
186
|
} else {
|
|
159
|
-
if (slot.contentWidth === null) {
|
|
187
|
+
if (slot.contentWidth === null) {
|
|
188
|
+
slot.contentWidth = signal(slot.lastCW === null ? 0 : slot.lastCW);
|
|
189
|
+
slot.contentHeight = signal(slot.lastCH === null ? 0 : slot.lastCH);
|
|
190
|
+
}
|
|
160
191
|
}
|
|
161
192
|
slot.refCount++;
|
|
162
193
|
|