@rpcbase/client 0.180.0 → 0.181.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/client",
3
- "version": "0.180.0",
3
+ "version": "0.181.0",
4
4
  "scripts": {
5
5
  "test": "../../node_modules/.bin/wireit"
6
6
  },
@@ -1,5 +1,5 @@
1
1
  /* @flow */
2
- import withSuspense from "../withSuspense"
2
+ import {withSuspense} from "../helpers/withSuspense"
3
3
 
4
4
 
5
5
  export default withSuspense(
@@ -0,0 +1,158 @@
1
+ /* @flow */
2
+ import React, {useState, useEffect, useRef} from "react"
3
+ import _isEmpty from "lodash/isEmpty"
4
+
5
+ import stopEventPropagation from "../helpers/stopEventPropagation"
6
+ import useThrottledMeasure from "..//helpers/useThrottledMeasure"
7
+
8
+ import "./tabs.scss"
9
+
10
+
11
+ type Props = {
12
+ active: boolean,
13
+ onChange: Function,
14
+ }
15
+
16
+ export const Tabs = (props: Props) => {
17
+ const els = useRef({})
18
+
19
+ // tracks drag movement, to prevent activating a tab if the user was just dragging the view arround
20
+ const dragRef = useRef(null)
21
+
22
+ const root = useRef(null)
23
+ const [containerRef, {width: rootWidth}] = useThrottledMeasure()
24
+
25
+ const [sizes, setSizes] = useState({})
26
+ const [isFirstRender, setIsFirstRender] = useState(true)
27
+
28
+ useEffect(() => {
29
+ const updateSizes = () => {
30
+ if (!root.current) {
31
+ return
32
+ }
33
+
34
+ const rootBounds = root.current.getBoundingClientRect()
35
+
36
+ const newSizes = {}
37
+
38
+ Object.keys(els.current).forEach((key) => {
39
+ const el = els.current[key]
40
+ const bounds = el.getBoundingClientRect()
41
+
42
+ const left = bounds.left - rootBounds.left
43
+ const right = rootBounds.right - bounds.right
44
+
45
+ newSizes[key] = {left, right}
46
+ })
47
+
48
+ setSizes(newSizes)
49
+ return newSizes
50
+ }
51
+
52
+ requestAnimationFrame(() => {
53
+ updateSizes()
54
+ })
55
+
56
+ const handleResize = () => {
57
+ updateSizes()
58
+ }
59
+
60
+ window.addEventListener("resize", handleResize)
61
+
62
+ return () => {
63
+ window.removeEventListener("resize", handleResize)
64
+ }
65
+ }, [rootWidth])
66
+
67
+ useEffect(() => {
68
+ if (!_isEmpty(sizes) && isFirstRender) {
69
+ requestAnimationFrame(() => {
70
+ setIsFirstRender(false)
71
+ })
72
+ }
73
+ }, [sizes])
74
+
75
+ const onMouseDown = (event) => {
76
+ dragRef.current = {
77
+ x: event.clientX,
78
+ y: event.clientY,
79
+ }
80
+ }
81
+
82
+ const onMouseMove = (event) => {
83
+ if (dragRef.current) {
84
+ const distance = Math.sqrt(
85
+ Math.pow(event.clientX - dragRef.current.x, 2) +
86
+ Math.pow(event.clientY - dragRef.current.y, 2),
87
+ )
88
+
89
+ if (distance > 10) {
90
+ dragRef.current.hasDragged = true
91
+ }
92
+ }
93
+ }
94
+
95
+ const getOnClickHandler = (child) => (event) => {
96
+ if (dragRef.current?.hasDragged) {
97
+ event.preventDefault()
98
+ } else {
99
+ const id = `tab-${child.key}`
100
+ const el = document.getElementById(id)
101
+ el?.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"})
102
+
103
+ props.onChange(child.key)
104
+ }
105
+
106
+ dragRef.current = null
107
+ }
108
+
109
+ const getTransitionTime = () => (isFirstRender ? 0 : 200)
110
+
111
+ const getUnderlineStyle = () => {
112
+ if (props.active === null || Object.keys(sizes).length === 0) {
113
+ return {left: "0", right: "100%"}
114
+ }
115
+
116
+ const size = sizes[props.active]
117
+
118
+ if (!size) return {}
119
+
120
+ const transitionTime = getTransitionTime()
121
+
122
+ return {
123
+ left: `${size.left}px`,
124
+ right: `${size.right}px`,
125
+ transition: `left ${transitionTime}ms, right ${transitionTime}ms`,
126
+ }
127
+ }
128
+
129
+ return (
130
+ <div ref={containerRef} className="u-tabs-container">
131
+ <div
132
+ className={cx("u-tabs", props.className)}
133
+ onMouseDown={stopEventPropagation}
134
+ ref={root}
135
+ style={props.style}
136
+ >
137
+ {React.Children.map(props.children, (child, i) => {
138
+ if (!child) return null
139
+
140
+ return (
141
+ <div
142
+ id={`tab-${child.key}`}
143
+ className={cx([`u-tabs__Tab`, {active: child.key === props.active}])}
144
+ onMouseDown={onMouseDown}
145
+ onMouseMove={onMouseMove}
146
+ onClick={getOnClickHandler(child)}
147
+ ref={(el) => (els.current[child.key] = el)}
148
+ >
149
+ {child}
150
+ </div>
151
+ )
152
+ })}
153
+
154
+ <div className="u-tabs__Underline" style={getUnderlineStyle()} />
155
+ </div>
156
+ </div>
157
+ )
158
+ }
@@ -0,0 +1,53 @@
1
+ @import "helpers";
2
+
3
+ $border-height: 3px;
4
+
5
+ /* stylelint-disable */
6
+ .u-tabs {
7
+ position: relative;
8
+ display: flex;
9
+ flex-direction: row;
10
+ margin-bottom: $border-height;
11
+
12
+ // overflow-x: scroll;
13
+ // overflow-y: visible;
14
+ }
15
+
16
+ .u-tabs__Tab {
17
+ display: inline-block;
18
+ width: 100%;
19
+ padding: 4px 8px;
20
+ color: $gray-900;
21
+ cursor: pointer;
22
+
23
+ & > div {
24
+ display: flex;
25
+ flex-direction: row;
26
+ justify-content: center;
27
+ }
28
+
29
+ &.active {
30
+ background-color: lighten($blue, 45%) !important;
31
+ font-weight: 600;
32
+
33
+ // &:first-child {
34
+ // border-top-left-radius: 3px;
35
+ // }
36
+ }
37
+
38
+ &:hover:not(.active) {
39
+ color: $primary;
40
+
41
+ svg path {
42
+ fill: $primary;
43
+ }
44
+ }
45
+ }
46
+
47
+ .u-tabs__Underline {
48
+ position: absolute;
49
+ top: 100%;
50
+ left: 0;
51
+ right: 0;
52
+ border-bottom: $border-height solid $primary;
53
+ }
@@ -0,0 +1,5 @@
1
+ /* @flow */
2
+
3
+ export default (e: SyntheticMouseEvent<HTMLElement>) => {
4
+ e.stopPropagation()
5
+ }
@@ -0,0 +1,49 @@
1
+ import {useState, useEffect, useRef, useCallback} from "react"
2
+ import useMeasure from "react-use/lib/useMeasure"
3
+ import _throttle from "lodash/throttle"
4
+ import isEqual from "fast-deep-equal/react"
5
+
6
+
7
+ const DEFAULT_THROTTLE_TIME = 16
8
+
9
+ const useThrottledMeasure = (throttleDuration = DEFAULT_THROTTLE_TIME) => {
10
+ const hasInitialMeasure = useRef(false)
11
+
12
+ const [ref, measuredRect] = useMeasure()
13
+ const [rect, setRect] = useState(() => {
14
+ return {x: 0, y: 0, width: 0, height: 0, top: 0, left: 0, bottom: 0, right: 0}
15
+ })
16
+
17
+ const throttledSetRect = useCallback(
18
+ _throttle(
19
+ (newRect) => {
20
+ setRect((current) => {
21
+ return isEqual(current, newRect) ? current : newRect
22
+ })
23
+ },
24
+ throttleDuration,
25
+ {leading: true, trailing: true},
26
+ ),
27
+ [throttleDuration],
28
+ )
29
+
30
+ useEffect(() => {
31
+ if (measuredRect.width > 0 && !hasInitialMeasure.current) {
32
+ hasInitialMeasure.current = true
33
+ setRect((current) => {
34
+ return isEqual(current, measuredRect) ? current : measuredRect
35
+ })
36
+ return
37
+ }
38
+
39
+ throttledSetRect(measuredRect)
40
+
41
+ return () => {
42
+ throttledSetRect.cancel()
43
+ }
44
+ }, [measuredRect, throttledSetRect])
45
+
46
+ return [ref, rect]
47
+ }
48
+
49
+ export default useThrottledMeasure
@@ -1,12 +1,12 @@
1
1
  /* @flow */
2
2
  import React, {Suspense, useEffect, useState} from "react"
3
3
 
4
- import ActivityIndicator from "../ActivityIndicator"
4
+ import ActivityIndicator from "../../ActivityIndicator"
5
5
 
6
6
 
7
7
  const DELAY_BEFORE_LOADER = 200
8
8
 
9
- const withSuspense = (loadFn, opts = {hideLoader: false}) => {
9
+ export const withSuspense = (loadFn, opts = {hideLoader: false}) => {
10
10
  const Component = React.lazy(loadFn)
11
11
 
12
12
  const Loader = () => {
@@ -36,5 +36,3 @@ const withSuspense = (loadFn, opts = {hideLoader: false}) => {
36
36
 
37
37
  return Wrapper
38
38
  }
39
-
40
- export default withSuspense
@@ -0,0 +1,76 @@
1
+ /* @flow */
2
+ export default class AutoScroller {
3
+ constructor(container, onScrollCallback) {
4
+ this.container = container
5
+ this.onScrollCallback = onScrollCallback
6
+ }
7
+
8
+ clear() {
9
+ if (this.interval == null) {
10
+ return
11
+ }
12
+
13
+ clearInterval(this.interval)
14
+ this.interval = null
15
+ }
16
+
17
+ update({translate, minTranslate, maxTranslate, width, height}) {
18
+ const direction = {
19
+ x: 0,
20
+ y: 0,
21
+ }
22
+ const speed = {
23
+ x: 1,
24
+ y: 1,
25
+ }
26
+ const acceleration = {
27
+ x: 10,
28
+ y: 10,
29
+ }
30
+
31
+ const {scrollTop, scrollLeft, scrollHeight, scrollWidth, clientHeight, clientWidth} =
32
+ this.container
33
+
34
+ const isTop = scrollTop === 0
35
+ const isBottom = scrollHeight - scrollTop - clientHeight === 0
36
+ const isLeft = scrollLeft === 0
37
+ const isRight = scrollWidth - scrollLeft - clientWidth === 0
38
+
39
+ if (translate.y >= maxTranslate.y - height / 2 && !isBottom) {
40
+ // Scroll Down
41
+ direction.y = 1
42
+ speed.y = acceleration.y * Math.abs((maxTranslate.y - height / 2 - translate.y) / height)
43
+ } else if (translate.x >= maxTranslate.x - width / 2 && !isRight) {
44
+ // Scroll Right
45
+ direction.x = 1
46
+ speed.x = acceleration.x * Math.abs((maxTranslate.x - width / 2 - translate.x) / width)
47
+ } else if (translate.y <= minTranslate.y + height / 2 && !isTop) {
48
+ // Scroll Up
49
+ direction.y = -1
50
+ speed.y = acceleration.y * Math.abs((translate.y - height / 2 - minTranslate.y) / height)
51
+ } else if (translate.x <= minTranslate.x + width / 2 && !isLeft) {
52
+ // Scroll Left
53
+ direction.x = -1
54
+ speed.x = acceleration.x * Math.abs((translate.x - width / 2 - minTranslate.x) / width)
55
+ }
56
+
57
+ if (this.interval) {
58
+ this.clear()
59
+ this.isAutoScrolling = false
60
+ }
61
+
62
+ if (direction.x !== 0 || direction.y !== 0) {
63
+ this.interval = setInterval(() => {
64
+ this.isAutoScrolling = true
65
+ const offset = {
66
+ left: speed.x * direction.x,
67
+ top: speed.y * direction.y,
68
+ }
69
+ this.container.scrollTop += offset.top
70
+ this.container.scrollLeft += offset.left
71
+
72
+ this.onScrollCallback(offset)
73
+ }, 5)
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,31 @@
1
+ /* @flow */
2
+ import SortableHandle from "./SortableHandle"
3
+
4
+ import "./drag-handle.scss"
5
+
6
+ const DragHandle = SortableHandle(({ref, variant = "bars", className = ""}) => {
7
+ let icon
8
+ if (variant === "bars") {
9
+ icon = (
10
+ <svg width={18} height={18} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
11
+ <path
12
+ fill={"currentColor"}
13
+ d="M 25 0 C 24.746094 0 24.476563 0.0859375 24.28125 0.28125 L 17.28125 7.28125 C 16.996094 7.566406 16.90625 8 17.0625 8.375 C 17.21875 8.746094 17.597656 9 18 9 L 32.03125 9 C 32.582031 9 33.03125 8.550781 33.03125 8 C 33.03125 7.664063 32.847656 7.367188 32.59375 7.1875 L 25.71875 0.28125 C 25.523438 0.0859375 25.253906 0 25 0 Z M 2 13 L 2 17 L 48 17 L 48 13 Z M 2 23 L 2 27 L 48 27 L 48 23 Z M 2 33 L 2 37 L 48 37 L 48 33 Z M 18 41 C 17.597656 41 17.21875 41.25 17.0625 41.625 C 16.90625 42 16.996094 42.433594 17.28125 42.71875 L 24.28125 49.71875 C 24.476563 49.914063 24.742188 50 25 50 C 25.257813 50 25.523438 49.914063 25.71875 49.71875 L 32.71875 42.71875 C 33.003906 42.433594 33.09375 42 32.9375 41.625 C 32.785156 41.25 32.402344 41 32 41 Z"
14
+ />
15
+ </svg>
16
+ )
17
+ } else if (variant === "dots") {
18
+ icon = (
19
+ <svg width={18} height={18} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
20
+ <path
21
+ fill={"currentColor"}
22
+ d="M 9 2 A 3 3 0 0 0 6 5 A 3 3 0 0 0 9 8 A 3 3 0 0 0 12 5 A 3 3 0 0 0 9 2 z M 21 2 A 3 3 0 0 0 18 5 A 3 3 0 0 0 21 8 A 3 3 0 0 0 24 5 A 3 3 0 0 0 21 2 z M 9 12 A 3 3 0 0 0 6 15 A 3 3 0 0 0 9 18 A 3 3 0 0 0 12 15 A 3 3 0 0 0 9 12 z M 21 12 A 3 3 0 0 0 18 15 A 3 3 0 0 0 21 18 A 3 3 0 0 0 24 15 A 3 3 0 0 0 21 12 z M 9 22 A 3 3 0 0 0 6 25 A 3 3 0 0 0 9 28 A 3 3 0 0 0 12 25 A 3 3 0 0 0 9 22 z M 21 22 A 3 3 0 0 0 18 25 A 3 3 0 0 0 21 28 A 3 3 0 0 0 24 25 A 3 3 0 0 0 21 22 z"
23
+ />
24
+ </svg>
25
+ )
26
+ } else throw new Error("DragHandle unknown variant")
27
+
28
+ return <div ref={ref} className={cx("sortable-drag-handle", className)}>{icon}</div>
29
+ })
30
+
31
+ export default DragHandle
@@ -0,0 +1,54 @@
1
+ /* @flow */
2
+ export default class Manager {
3
+ refs = {}
4
+
5
+ add(collection, ref) {
6
+ if (!this.refs[collection]) {
7
+ this.refs[collection] = []
8
+ }
9
+
10
+ this.refs[collection].push(ref)
11
+ }
12
+
13
+ remove(collection, ref) {
14
+ const index = this.getIndex(collection, ref)
15
+
16
+ if (index !== -1) {
17
+ this.refs[collection].splice(index, 1)
18
+ }
19
+ }
20
+
21
+ isActive() {
22
+ return this.active
23
+ }
24
+
25
+ getActive() {
26
+ return this.refs[this.active.collection].find(
27
+ // eslint-disable-next-line eqeqeq
28
+ ({node}) => node.sortableInfo.index == this.active.index,
29
+ )
30
+ }
31
+
32
+ getIndex(collection, ref) {
33
+ return this.refs[collection].indexOf(ref)
34
+ }
35
+
36
+ getOrderedRefs(collection = this.active.collection) {
37
+ return this.refs[collection].sort(sortByIndex)
38
+ }
39
+ }
40
+
41
+ function sortByIndex(
42
+ {
43
+ node: {
44
+ sortableInfo: {index: index1},
45
+ },
46
+ },
47
+ {
48
+ node: {
49
+ sortableInfo: {index: index2},
50
+ },
51
+ },
52
+ ) {
53
+ return index1 - index2
54
+ }
@@ -0,0 +1 @@
1
+ forked from https://github.com/clauderic/react-sortable-hoc
@@ -0,0 +1,7 @@
1
+ /* @flow */
2
+ export default function defaultGetHelperDimensions({node}) {
3
+ return {
4
+ height: node.offsetHeight,
5
+ width: node.offsetWidth,
6
+ }
7
+ }
@@ -0,0 +1,24 @@
1
+ /* @flow */
2
+ import {NodeType, closest} from "../utils"
3
+
4
+ export default function defaultShouldCancelStart(event) {
5
+ // Cancel sorting if the event target is an `input`, `textarea`, `select` or `option`
6
+ const interactiveElements = [
7
+ NodeType.Input,
8
+ NodeType.Textarea,
9
+ NodeType.Select,
10
+ NodeType.Option,
11
+ NodeType.Button,
12
+ ]
13
+
14
+ if (interactiveElements.indexOf(event.target.tagName) !== -1) {
15
+ // Return true to cancel sorting
16
+ return true
17
+ }
18
+
19
+ if (closest(event.target, (el) => el.contentEditable === "true")) {
20
+ return true
21
+ }
22
+
23
+ return false
24
+ }