scaling based on component size instead webview size

This commit is contained in:
Timo K.
2026-09-04 14:16:40 +02:00
parent f5458bb03e
commit 38c28a1c8f
11 changed files with 293 additions and 14 deletions
+104
View File
@@ -0,0 +1,104 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { observeElementSize$ } from "./elementSize";
/**
* A ResizeObserver whose notifications the test triggers itself, and which
* keeps track of what it is watching. jsdom does not ship one.
*/
class MockResizeObserver {
public static instances: MockResizeObserver[] = [];
public observed: Element[] = [];
public disconnected = false;
public constructor(private readonly callback: ResizeObserverCallback) {
MockResizeObserver.instances.push(this);
}
public observe(element: Element): void {
this.observed.push(element);
// Real observers report the initial size once observation starts
this.fire();
}
public unobserve(): void {}
public disconnect(): void {
this.disconnected = true;
}
public fire(): void {
this.callback([], this as unknown as ResizeObserver);
}
}
describe("observeElementSize$", () => {
const originalResizeObserver = window.ResizeObserver;
let element: HTMLDivElement;
let size: { width: number; height: number };
beforeEach(() => {
MockResizeObserver.instances = [];
window.ResizeObserver =
MockResizeObserver as unknown as typeof ResizeObserver;
// jsdom does no layout, so the element reports whatever we say it does
size = { width: 800, height: 600 };
element = document.createElement("div");
Object.defineProperty(element, "clientWidth", { get: () => size.width });
Object.defineProperty(element, "clientHeight", { get: () => size.height });
});
afterEach(() => {
window.ResizeObserver = originalResizeObserver;
});
it("emits the current size synchronously on subscription", () => {
const next = vi.fn();
observeElementSize$(element).subscribe(next);
expect(next).toHaveBeenCalledTimes(1);
expect(next).toHaveBeenCalledWith({ width: 800, height: 600 });
expect(MockResizeObserver.instances[0].observed).toEqual([element]);
});
it("emits again whenever the element is resized", () => {
const next = vi.fn();
observeElementSize$(element).subscribe(next);
const [observer] = MockResizeObserver.instances;
size = { width: 300, height: 300 };
observer.fire();
size = { width: 1000, height: 700 };
observer.fire();
expect(next.mock.calls.map(([s]) => s)).toEqual([
{ width: 800, height: 600 },
{ width: 300, height: 300 },
{ width: 1000, height: 700 },
]);
});
it("does not repeat a size that has not changed", () => {
const next = vi.fn();
observeElementSize$(element).subscribe(next);
// The initial report the observer makes on observe() carried the same size
// as the synchronous measurement, and so did not count
expect(next).toHaveBeenCalledTimes(1);
MockResizeObserver.instances[0].fire();
expect(next).toHaveBeenCalledTimes(1);
});
it("stops observing when unsubscribed", () => {
const subscription = observeElementSize$(element).subscribe();
const [observer] = MockResizeObserver.instances;
expect(observer.disconnected).toBe(false);
subscription.unsubscribe();
expect(observer.disconnected).toBe(true);
});
});
+42
View File
@@ -0,0 +1,42 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { Observable, distinctUntilChanged } from "rxjs";
export interface ElementSize {
width: number;
height: number;
}
/**
* Observes the size of an element, starting with the size it currently has and
* following it through every change for as long as the subscription lasts.
*
* Measured with `clientWidth`/`clientHeight`, so borders, scrollbars and any
* transforms a host might animate the element with are left out: this is the
* space there is to draw in, not where the element happens to be painted.
*/
export function observeElementSize$(element: Element): Observable<ElementSize> {
return new Observable<ElementSize>((subscriber) => {
const measure = (): void =>
subscriber.next({
width: element.clientWidth,
height: element.clientHeight,
});
// Synchronously, so that a Behavior built on this has an initial value
measure();
const observer = new ResizeObserver(measure);
observer.observe(element);
return (): void => observer.disconnect();
}).pipe(
// A ResizeObserver reports once on observe(), which repeats the first
// measurement above, and again for changes to dimensions we do not read
distinctUntilChanged(
(a, b) => a.width === b.width && a.height === b.height,
),
);
}
+1
View File
@@ -176,6 +176,7 @@ export function getBasicCallViewModelEnvironment(
}),
connectionState$: constant(ConnectionState.Connected),
matrixRTCMode$: constant(MatrixRTCMode.Compatibility),
windowSize$: constant({ width: 1000, height: 800 }),
...callViewModelOptions,
},
handRaisedSubject$,