-
Notifications
You must be signed in to change notification settings - Fork 219
Expand file tree
/
Copy pathutils.ts
More file actions
118 lines (97 loc) · 3.55 KB
/
utils.ts
File metadata and controls
118 lines (97 loc) · 3.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { NgZone, inject, numberAttribute } from '@angular/core'
import { Observable, filter, fromEvent, merge } from 'rxjs'
export interface ClientPoint {
x: number
y: number
}
/**
* Only supporting a single {@link TouchEvent} point
*/
export function getPointFromEvent(event: MouseEvent | TouchEvent | KeyboardEvent): ClientPoint {
// NOTE: In firefox TouchEvent is only defined for touch capable devices
const isTouchEvent = (e: typeof event): e is TouchEvent => window.TouchEvent && event instanceof TouchEvent
if (isTouchEvent(event)) {
if (event.changedTouches.length === 0) {
return undefined
}
const { clientX, clientY } = event.changedTouches[0]
return {
x: clientX,
y: clientY,
}
}
if (event instanceof KeyboardEvent) {
const target = event.target as HTMLElement
// Calculate element midpoint
return {
x: target.offsetLeft + target.offsetWidth / 2,
y: target.offsetTop + target.offsetHeight / 2,
}
}
return {
x: event.clientX,
y: event.clientY,
}
}
export function gutterEventsEqualWithDelta(
startEvent: MouseEvent | TouchEvent,
endEvent: MouseEvent | TouchEvent,
deltaInPx: number,
gutterElement: HTMLElement,
) {
if (
!gutterElement.contains(startEvent.target as HTMLElement) ||
!gutterElement.contains(endEvent.target as HTMLElement)
) {
return false
}
const startPoint = getPointFromEvent(startEvent)
const endPoint = getPointFromEvent(endEvent)
return Math.abs(endPoint.x - startPoint.x) <= deltaInPx && Math.abs(endPoint.y - startPoint.y) <= deltaInPx
}
export function fromMouseDownEvent(target: HTMLElement | Document) {
return merge(
fromEvent<MouseEvent>(target, 'mousedown').pipe(filter((e) => e.button === 0)),
// We must prevent default here so we declare it as non passive explicitly
fromEvent<TouchEvent>(target, 'touchstart', { passive: false }),
)
}
export function fromMouseMoveEvent(target: HTMLElement | Document) {
return merge(fromEvent<MouseEvent>(target, 'mousemove'), fromEvent<TouchEvent>(target, 'touchmove'))
}
export function fromMouseUpEvent(target: HTMLElement | Document, includeTouchCancel = false) {
const withoutTouchCancel = merge(fromEvent<MouseEvent>(target, 'mouseup'), fromEvent<TouchEvent>(target, 'touchend'))
return includeTouchCancel
? merge(withoutTouchCancel, fromEvent<TouchEvent>(target, 'touchcancel'))
: withoutTouchCancel
}
export function sum<T>(array: T[] | readonly T[], fn: (item: T) => number) {
return (array as T[]).reduce((sum, item) => sum + fn(item), 0)
}
export function toRecord<TItem, TKey extends string, TValue>(
array: TItem[] | readonly TItem[],
fn: (item: TItem, index: number) => [TKey, TValue],
): Record<TKey, TValue> {
return (array as TItem[]).reduce<Record<TKey, TValue>>(
(record, item, index) => {
const [key, value] = fn(item, index)
record[key] = value
return record
},
{} as Record<TKey, TValue>,
)
}
export function createClassesString(classesRecord: Record<string, boolean>) {
return Object.entries(classesRecord)
.filter(([, value]) => value)
.map(([key]) => key)
.join(' ')
}
export function leaveNgZone<T>() {
return (source: Observable<T>) =>
new Observable<T>((observer) => inject(NgZone).runOutsideAngular(() => source.subscribe(observer)))
}
export const numberAttributeWithFallback = (fallback: number) => (value: unknown) => numberAttribute(value, fallback)
export const assertUnreachable = (value: never, name: string) => {
throw new Error(`as-split: unknown value "${value}" for "${name}"`)
}