Hacky demo changes
This commit is contained in:
parent
7cba369308
commit
ea7afd618c
39 changed files with 2041 additions and 745 deletions
58
frontend/src/lib/dom-scroll.test.ts
Normal file
58
frontend/src/lib/dom-scroll.test.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { canWheelScrollInsideTarget } from './dom-scroll';
|
||||
|
||||
function setElementMetrics(
|
||||
element: HTMLElement,
|
||||
metrics: Partial<{
|
||||
clientHeight: number;
|
||||
clientWidth: number;
|
||||
scrollHeight: number;
|
||||
scrollWidth: number;
|
||||
}>
|
||||
) {
|
||||
for (const [key, value] of Object.entries(metrics)) {
|
||||
Object.defineProperty(element, key, { configurable: true, value });
|
||||
}
|
||||
}
|
||||
|
||||
describe('canWheelScrollInsideTarget', () => {
|
||||
it('allows horizontal wheel gestures inside a horizontal scroller', () => {
|
||||
const scroller = document.createElement('div');
|
||||
scroller.style.overflowX = 'auto';
|
||||
scroller.scrollLeft = 20;
|
||||
setElementMetrics(scroller, { clientWidth: 100, scrollWidth: 240 });
|
||||
|
||||
const child = document.createElement('button');
|
||||
scroller.appendChild(child);
|
||||
document.body.appendChild(scroller);
|
||||
|
||||
expect(canWheelScrollInsideTarget(child, 40, 0)).toBe(true);
|
||||
scroller.remove();
|
||||
});
|
||||
|
||||
it('allows vertical wheel gestures inside a vertical scroller', () => {
|
||||
const scroller = document.createElement('div');
|
||||
scroller.style.overflowY = 'auto';
|
||||
scroller.scrollTop = 20;
|
||||
setElementMetrics(scroller, { clientHeight: 100, scrollHeight: 240 });
|
||||
|
||||
const child = document.createElement('button');
|
||||
scroller.appendChild(child);
|
||||
document.body.appendChild(scroller);
|
||||
|
||||
expect(canWheelScrollInsideTarget(child, 60, 20)).toBe(true);
|
||||
scroller.remove();
|
||||
});
|
||||
|
||||
it('does not allow horizontal gestures at the edge of a scroller', () => {
|
||||
const scroller = document.createElement('div');
|
||||
scroller.style.overflowX = 'auto';
|
||||
scroller.scrollLeft = 0;
|
||||
setElementMetrics(scroller, { clientWidth: 100, scrollWidth: 240 });
|
||||
|
||||
document.body.appendChild(scroller);
|
||||
|
||||
expect(canWheelScrollInsideTarget(scroller, -40, 0)).toBe(false);
|
||||
scroller.remove();
|
||||
});
|
||||
});
|
||||
45
frontend/src/lib/dom-scroll.ts
Normal file
45
frontend/src/lib/dom-scroll.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
const SCROLLABLE_OVERFLOW = new Set(['auto', 'scroll', 'overlay']);
|
||||
|
||||
function canScrollHorizontally(element: HTMLElement, deltaX: number): boolean {
|
||||
if (deltaX === 0) return false;
|
||||
|
||||
const style = window.getComputedStyle(element);
|
||||
if (!SCROLLABLE_OVERFLOW.has(style.overflowX)) return false;
|
||||
if (element.scrollWidth <= element.clientWidth) return false;
|
||||
|
||||
const maxScrollLeft = element.scrollWidth - element.clientWidth;
|
||||
if (deltaX < 0) return element.scrollLeft > 0;
|
||||
return element.scrollLeft < maxScrollLeft - 1;
|
||||
}
|
||||
|
||||
function canScrollVertically(element: HTMLElement, deltaY: number): boolean {
|
||||
if (deltaY === 0) return false;
|
||||
|
||||
const style = window.getComputedStyle(element);
|
||||
if (!SCROLLABLE_OVERFLOW.has(style.overflowY)) return false;
|
||||
if (element.scrollHeight <= element.clientHeight) return false;
|
||||
|
||||
const maxScrollTop = element.scrollHeight - element.clientHeight;
|
||||
if (deltaY < 0) return element.scrollTop > 0;
|
||||
return element.scrollTop < maxScrollTop - 1;
|
||||
}
|
||||
|
||||
export function canWheelScrollInsideTarget(
|
||||
target: EventTarget | null,
|
||||
deltaX: number,
|
||||
deltaY: number
|
||||
): boolean {
|
||||
let element = target instanceof Element ? target : null;
|
||||
|
||||
while (element && element !== document.body && element !== document.documentElement) {
|
||||
if (
|
||||
element instanceof HTMLElement &&
|
||||
(canScrollHorizontally(element, deltaX) || canScrollVertically(element, deltaY))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
element = element.parentElement;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -19,6 +19,12 @@ const FEATURE_ICON_PATHS: Record<string, ReactNode> = {
|
|||
<polyline points="15 6 21 6 21 12" />
|
||||
</>
|
||||
),
|
||||
'Estimated price': (
|
||||
<>
|
||||
<polyline points="4 16 8 12 13 15 20 6" />
|
||||
<polyline points="15 6 21 6 21 12" />
|
||||
</>
|
||||
),
|
||||
'Price per sqm': (
|
||||
<>
|
||||
<rect x="3" y="3" width="7" height="7" />
|
||||
|
|
@ -109,6 +115,18 @@ const FEATURE_ICON_PATHS: Record<string, ReactNode> = {
|
|||
<line x1="12" y1="18" x2="12" y2="15" />
|
||||
</>
|
||||
),
|
||||
'Travel time to nearest train or tube station (min)': (
|
||||
<>
|
||||
<rect x="5" y="3" width="14" height="10" rx="2" />
|
||||
<path d="M8 17h8" />
|
||||
<path d="M9 13l-2 4" />
|
||||
<path d="M15 13l2 4" />
|
||||
<circle cx="8.5" cy="8" r="1" fill="currentColor" />
|
||||
<circle cx="15.5" cy="8" r="1" fill="currentColor" />
|
||||
<path d="M12 18v3" />
|
||||
<circle cx="12" cy="21" r="1.5" />
|
||||
</>
|
||||
),
|
||||
|
||||
// ── Education ────────────────────────────────
|
||||
'Education, Skills and Training Score': (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue