@adia-ai/web-modules 0.6.27 → 0.6.28
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
|
# Changelog — @adia-ai/web-modules
|
|
2
2
|
|
|
3
|
+
## [0.6.28] — 2026-05-23
|
|
4
|
+
|
|
5
|
+
### Fixed — `editor-sidebar` rAF debounce for wide→narrow display-flip transient (§FB-50)
|
|
6
|
+
|
|
7
|
+
- **`editor-sidebar` — wide→narrow viewport transition latches `[collapsed]` even after v0.6.27's `getComputedStyle` guard.** The v0.6.27 guard only protects the narrow→wide direction. During wide→narrow, the CSS Grid track collapses the pane to 48px before `display:none` propagates to `getComputedStyle`; the guard sees `display:flex` and runs `#syncCollapsed()` against the transient 48px sample, latching `collapsed=true`. Fix: `#syncCollapsed()` now defers `collapsed=true` writes by one `requestAnimationFrame`. Expand signals (`rect.width > SNAP_THRESHOLD`) are applied synchronously; collapse signals wait one frame — by which point `display:none` has propagated and the `getComputedStyle` guard catches it. Pending rAF is cancelled on expand signals and on `disconnected()`. Intentional drag-to-collapse fires correctly after the drag settles. Added regression tests: (1) small-width RO sample does not immediately latch `collapsed`; (2) `display:none` before the rAF prevents the latch. Reported by color-app (FEEDBACK-50). Files: `editor/editor-sidebar/editor-sidebar.js`, `editor/editor-sidebar/editor-sidebar.test.js`.
|
|
8
|
+
|
|
3
9
|
## [0.6.27] — 2026-05-22
|
|
4
10
|
|
|
5
11
|
### Fixed — `editor-sidebar` computedStyle guard for small-nonzero display-flip transients (§FB-49)
|
|
@@ -67,6 +67,7 @@ class EditorSidebar extends UIElement {
|
|
|
67
67
|
#storageKey = null;
|
|
68
68
|
#onPointerDown = null;
|
|
69
69
|
#onPointerUp = null;
|
|
70
|
+
#pendingCollapseRaf = null;
|
|
70
71
|
|
|
71
72
|
connected() {
|
|
72
73
|
this.#pane = this.querySelector('pane-ui');
|
|
@@ -128,6 +129,10 @@ class EditorSidebar extends UIElement {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
disconnected() {
|
|
132
|
+
if (this.#pendingCollapseRaf !== null) {
|
|
133
|
+
cancelAnimationFrame(this.#pendingCollapseRaf);
|
|
134
|
+
this.#pendingCollapseRaf = null;
|
|
135
|
+
}
|
|
131
136
|
this.#ro?.disconnect();
|
|
132
137
|
this.#ro = null;
|
|
133
138
|
if (this.#onPointerDown && this.#pane) {
|
|
@@ -174,14 +179,37 @@ class EditorSidebar extends UIElement {
|
|
|
174
179
|
|
|
175
180
|
#syncCollapsed() {
|
|
176
181
|
if (!this.#pane) return;
|
|
177
|
-
// §FB-49: guard before reading rect — catches explicit display:none AND
|
|
178
|
-
// small-nonzero transients during display-flip (e.g. pane briefly at 48px
|
|
179
|
-
// while re-entering layout). getComputedStyle resolves CSS rules, not just
|
|
180
|
-
// inline style, covering both direct assignment and @media rules.
|
|
181
182
|
if (getComputedStyle(this).display === 'none') return;
|
|
182
183
|
const rect = this.#pane.getBoundingClientRect();
|
|
183
|
-
if (rect.width === 0) return; // §FB-42 belt-and-braces
|
|
184
|
-
|
|
184
|
+
if (rect.width === 0) return; // §FB-42 belt-and-braces
|
|
185
|
+
|
|
186
|
+
if (rect.width > SNAP_THRESHOLD) {
|
|
187
|
+
// Expand: apply immediately and cancel any pending collapse.
|
|
188
|
+
if (this.#pendingCollapseRaf !== null) {
|
|
189
|
+
cancelAnimationFrame(this.#pendingCollapseRaf);
|
|
190
|
+
this.#pendingCollapseRaf = null;
|
|
191
|
+
}
|
|
192
|
+
this.collapsed = false;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// §FB-50: small sample — may be a CSS-grid collapse transient during
|
|
197
|
+
// the wide→narrow display-flip (pane shrinks before display:none
|
|
198
|
+
// propagates to getComputedStyle). Defer one animation frame; if
|
|
199
|
+
// display:none takes effect before the rAF fires the guard above
|
|
200
|
+
// catches it. Intentional drag-to-collapse also goes through this
|
|
201
|
+
// path and fires after the drag settles, which is correct.
|
|
202
|
+
if (this.#pendingCollapseRaf === null) {
|
|
203
|
+
this.#pendingCollapseRaf = requestAnimationFrame(() => {
|
|
204
|
+
this.#pendingCollapseRaf = null;
|
|
205
|
+
if (!this.#pane) return;
|
|
206
|
+
if (getComputedStyle(this).display === 'none') return;
|
|
207
|
+
const r = this.#pane.getBoundingClientRect();
|
|
208
|
+
if (r.width > 0 && r.width <= SNAP_THRESHOLD) {
|
|
209
|
+
this.collapsed = true;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
185
213
|
}
|
|
186
214
|
|
|
187
215
|
#emitToggle() {
|
|
@@ -148,6 +148,65 @@ describe('editor-sidebar', () => {
|
|
|
148
148
|
globalThis.getComputedStyle = origGCS;
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
+
it('defers [collapsed]=true by one rAF for small-transient samples (§FB-50)', async () => {
|
|
152
|
+
let roCallback;
|
|
153
|
+
const savedRO = globalThis.ResizeObserver;
|
|
154
|
+
globalThis.ResizeObserver = class {
|
|
155
|
+
constructor(cb) { roCallback = cb; }
|
|
156
|
+
observe() {} unobserve() {} disconnect() {}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const sb = mount('<editor-sidebar slot="leading" collapsible><pane-ui></pane-ui></editor-sidebar>');
|
|
160
|
+
const pane = sb.querySelector('pane-ui');
|
|
161
|
+
// Simulate a small width on the pane (CSS grid collapse transient or drag).
|
|
162
|
+
pane.style.width = '48px';
|
|
163
|
+
|
|
164
|
+
if (roCallback) roCallback([{ target: pane }]);
|
|
165
|
+
// Immediately after RO callback — collapsed must NOT be set yet (rAF pending).
|
|
166
|
+
expect(sb.collapsed).toBe(false);
|
|
167
|
+
|
|
168
|
+
// After the rAF fires — pane still 48px, host visible → collapse is applied.
|
|
169
|
+
await new Promise((r) => requestAnimationFrame(r));
|
|
170
|
+
await tick();
|
|
171
|
+
expect(sb.collapsed).toBe(true);
|
|
172
|
+
|
|
173
|
+
globalThis.ResizeObserver = savedRO;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('rAF check skips collapse if display:none applied before frame fires (§FB-50 wide→narrow race)', async () => {
|
|
177
|
+
let roCallback;
|
|
178
|
+
const savedRO = globalThis.ResizeObserver;
|
|
179
|
+
globalThis.ResizeObserver = class {
|
|
180
|
+
constructor(cb) { roCallback = cb; }
|
|
181
|
+
observe() {} unobserve() {} disconnect() {}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const sb = mount('<editor-sidebar slot="leading" collapsible><pane-ui></pane-ui></editor-sidebar>');
|
|
185
|
+
const pane = sb.querySelector('pane-ui');
|
|
186
|
+
// Transient 48px — CSS grid collapsed the track before display:none applied.
|
|
187
|
+
pane.style.width = '48px';
|
|
188
|
+
|
|
189
|
+
if (roCallback) roCallback([{ target: pane }]);
|
|
190
|
+
expect(sb.collapsed).toBe(false); // deferred, not set yet
|
|
191
|
+
|
|
192
|
+
// Simulate display:none propagating before the rAF executes (the race window).
|
|
193
|
+
sb.style.display = 'none';
|
|
194
|
+
const origGCS = globalThis.getComputedStyle;
|
|
195
|
+
globalThis.getComputedStyle = (el, ps) => {
|
|
196
|
+
if (el === sb && el.style.display === 'none') return { display: 'none' };
|
|
197
|
+
return origGCS(el, ps);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// rAF fires — guard sees display:none → collapsed stays false.
|
|
201
|
+
await new Promise((r) => requestAnimationFrame(r));
|
|
202
|
+
await tick();
|
|
203
|
+
expect(sb.collapsed).toBe(false);
|
|
204
|
+
expect(sb.hasAttribute('collapsed')).toBe(false);
|
|
205
|
+
|
|
206
|
+
globalThis.ResizeObserver = savedRO;
|
|
207
|
+
globalThis.getComputedStyle = origGCS;
|
|
208
|
+
});
|
|
209
|
+
|
|
151
210
|
it('handles missing inner <pane-ui> gracefully (no crash)', () => {
|
|
152
211
|
const sb = mount('<editor-sidebar slot="leading"></editor-sidebar>');
|
|
153
212
|
// Should not throw
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/web-modules",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.28",
|
|
4
4
|
"description": "AdiaUI composite custom elements \u2014 shell, chat, editor, runtime clusters built from @adia-ai/web-components primitives. Subpath exports per cluster.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|