Skip to content

Commit 5b8b9f5

Browse files
committed
fix(slide-potentiometer): not functional in Firefox #87
1 parent 44bd671 commit 5b8b9f5

File tree

3 files changed

+57
-40
lines changed

3 files changed

+57
-40
lines changed

src/potentiometer-element.ts

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { customElement, property } from 'lit/decorators.js';
33
import { styleMap } from 'lit/directives/style-map.js';
44
import { analog, ElementPin } from './pin';
55
import { clamp } from './utils/clamp';
6-
import { calculateBoundingRect } from './utils/geometry';
6+
import { getScreenCTM } from './utils/ctm-workaround';
77

88
const knobCenter = {
99
x: 9.91,
@@ -193,42 +193,11 @@ export class PotentiometerElement extends LitElement {
193193

194194
private updateKnobMatrix() {
195195
const knob = this.shadowRoot?.querySelector<SVGRectElement>('#knob');
196-
this.pageToKnobMatrix = knob?.getScreenCTM()?.inverse() ?? null;
197-
198-
const { userAgent } = navigator;
199-
200-
if (userAgent.indexOf('Firefox') >= 0 || userAgent.indexOf('Epiphany') >= 0) {
201-
// Firefox's getScreenCTM() is broken: https://bugzilla.mozilla.org/show_bug.cgi?id=1610093
202-
const firefoxWorkaround =
203-
this.shadowRoot?.querySelector<SVGRectElement>('#firefox-workaround');
204-
const boundingRect = firefoxWorkaround?.getBoundingClientRect();
205-
const svgRect = firefoxWorkaround?.ownerSVGElement?.getBoundingClientRect();
206-
if (!boundingRect || !svgRect) {
207-
return;
208-
}
209-
210-
const cx = svgRect.x + svgRect.width / 2;
211-
const cy = svgRect.y + svgRect.height / 2;
212-
const deltaX = cx - (boundingRect.x + boundingRect.width / 2);
213-
const deltaY = cy - (boundingRect.y + boundingRect.height / 2);
214-
const angle = (Math.atan2(deltaY, deltaX) / Math.PI) * 180;
215-
const rotation = new DOMMatrix().rotate(angle);
216-
const rotatedRect = calculateBoundingRect(new DOMRect(0, 9.5, 1, 1), rotation);
217-
const sx = rotatedRect.width / boundingRect.width;
218-
const sy = rotatedRect.height / boundingRect.height;
219-
this.pageToKnobMatrix = rotation
220-
.inverse()
221-
.multiply(
222-
new DOMMatrix([
223-
sx,
224-
0,
225-
0,
226-
sy,
227-
rotatedRect.left - boundingRect.left * sx,
228-
rotatedRect.top - boundingRect.top * sy,
229-
])
230-
);
231-
}
196+
const workaroundElement = this.shadowRoot?.querySelector<SVGRectElement>('#firefox-workaround');
197+
this.pageToKnobMatrix =
198+
knob && workaroundElement
199+
? getScreenCTM(knob, workaroundElement, new DOMRect(0, 9.5, 1, 1))
200+
: null;
232201
}
233202

234203
private rotateHandler(event: MouseEvent | TouchEvent) {

src/slide-potentiometer-element.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { html, LitElement, css } from 'lit';
1+
import { css, html, LitElement } from 'lit';
22
import { customElement, property } from 'lit/decorators.js';
33
import { analog, ElementPin } from './pin';
44
import { clamp } from './utils/clamp';
5+
import { getScreenCTM } from './utils/ctm-workaround';
56
import { mmToPix } from './utils/units';
67

78
@customElement('wokwi-slide-potentiometer')
@@ -171,6 +172,7 @@ export class SlidePotentiometerElement extends LitElement {
171172
<rect x="22.2" y="0" width=".6" height="19" fill="#efefef" />
172173
</g>
173174
</g>
175+
<rect x="0" y="14" width="1" height="1" fill="none" id="firefox-workaround" />
174176
</svg>
175177
`;
176178
}
@@ -210,8 +212,14 @@ export class SlidePotentiometerElement extends LitElement {
210212

211213
private updateCaseDisplayProperties(): void {
212214
const element = this.shadowRoot?.querySelector<SVGRectElement>('#sliderCase');
213-
if (element) {
214-
this.pageToLocalTransformationMatrix = element.getScreenCTM()?.inverse() || null;
215+
const firefoxWorkaround = this.shadowRoot?.querySelector<SVGRectElement>('#firefox-workaround');
216+
217+
if (element && firefoxWorkaround) {
218+
this.pageToLocalTransformationMatrix = getScreenCTM(
219+
element,
220+
firefoxWorkaround,
221+
new DOMRect(0, 14, 1, 1)
222+
);
215223
}
216224

217225
// Handle zooming in the storybook

src/utils/ctm-workaround.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { calculateBoundingRect } from './geometry';
2+
3+
export function getScreenCTM(
4+
target: SVGGraphicsElement,
5+
workaroundElement: SVGGraphicsElement,
6+
workaroundRect: DOMRectReadOnly
7+
) {
8+
const { userAgent } = navigator;
9+
const workaroundNeeded = userAgent.indexOf('Firefox') >= 0 || userAgent.indexOf('Epiphany') >= 0;
10+
11+
if (workaroundNeeded) {
12+
// Firefox's getScreenCTM() is broken: https://bugzilla.mozilla.org/show_bug.cgi?id=1610093
13+
const targetCTM = target.getCTM();
14+
const workaroundCTM = workaroundElement?.getCTM();
15+
const boundingRect = workaroundElement?.getBoundingClientRect();
16+
const svgRect = workaroundElement?.ownerSVGElement?.getBoundingClientRect();
17+
if (!boundingRect || !svgRect || !workaroundCTM || !targetCTM) {
18+
return null;
19+
}
20+
21+
const centerX = svgRect.x + svgRect.width / 2;
22+
const centerY = svgRect.y + svgRect.height / 2;
23+
const deltaX = centerX - (boundingRect.x + boundingRect.width / 2);
24+
const deltaY = centerY - (boundingRect.y + boundingRect.height / 2);
25+
const angle = (Math.atan2(deltaY, deltaX) / Math.PI) * 180;
26+
const rotation = new DOMMatrix().rotate(angle);
27+
const rotatedRect = calculateBoundingRect(workaroundRect, rotation);
28+
const scaleX = rotatedRect.width / boundingRect.width;
29+
const scaleY = rotatedRect.height / boundingRect.height;
30+
const localCTM = workaroundCTM.inverse().multiply(targetCTM);
31+
return rotation
32+
.inverse()
33+
.translate(rotatedRect.left, rotatedRect.top)
34+
.multiply(localCTM.inverse())
35+
.scale(scaleX, scaleY)
36+
.translate(-boundingRect.left, -boundingRect.top);
37+
} else {
38+
return target.getScreenCTM()?.inverse() || null;
39+
}
40+
}

0 commit comments

Comments
 (0)