react-msaview 5.0.13 → 5.1.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.
Files changed (89) hide show
  1. package/bundle/index.js +135 -35
  2. package/bundle/index.js.LICENSE.txt +1 -1
  3. package/bundle/index.js.map +1 -1
  4. package/dist/clustalX.d.ts +5 -0
  5. package/dist/clustalX.js +88 -0
  6. package/dist/clustalX.js.map +1 -0
  7. package/dist/components/SequenceTextArea.js +6 -6
  8. package/dist/components/SequenceTextArea.js.map +1 -1
  9. package/dist/components/TextTrack.js +3 -5
  10. package/dist/components/TextTrack.js.map +1 -1
  11. package/dist/components/Track.d.ts +2 -1
  12. package/dist/components/Track.js +4 -3
  13. package/dist/components/Track.js.map +1 -1
  14. package/dist/components/VerticalScrollbar.js +6 -31
  15. package/dist/components/VerticalScrollbar.js.map +1 -1
  16. package/dist/components/dialogs/InterProScanDialog.js +5 -3
  17. package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
  18. package/dist/components/header/HeaderStatusArea.js +4 -2
  19. package/dist/components/header/HeaderStatusArea.js.map +1 -1
  20. package/dist/components/header/MSASettingsMenu.js +2 -27
  21. package/dist/components/header/MSASettingsMenu.js.map +1 -1
  22. package/dist/components/header/SettingsMenu.js +4 -69
  23. package/dist/components/header/SettingsMenu.js.map +1 -1
  24. package/dist/components/header/TreeSettingsMenu.js +3 -41
  25. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  26. package/dist/components/header/settingsMenuItems.d.ts +4 -0
  27. package/dist/components/header/settingsMenuItems.js +59 -0
  28. package/dist/components/header/settingsMenuItems.js.map +1 -0
  29. package/dist/components/import/ImportFormExamples.js +1 -1
  30. package/dist/components/import/ImportFormExamples.js.map +1 -1
  31. package/dist/components/minimap/Minimap.js +6 -31
  32. package/dist/components/minimap/Minimap.js.map +1 -1
  33. package/dist/components/msa/MSACanvasBlock.js +31 -33
  34. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  35. package/dist/components/msa/renderMSABlock.js +15 -9
  36. package/dist/components/msa/renderMSABlock.js.map +1 -1
  37. package/dist/components/tree/TreeCanvas.js +12 -14
  38. package/dist/components/tree/TreeCanvas.js.map +1 -1
  39. package/dist/components/tree/renderTreeCanvas.d.ts +9 -3
  40. package/dist/components/tree/renderTreeCanvas.js +29 -14
  41. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  42. package/dist/fetchUtils.d.ts +2 -1
  43. package/dist/fetchUtils.js +16 -2
  44. package/dist/fetchUtils.js.map +1 -1
  45. package/dist/hierarchy.d.ts +3 -0
  46. package/dist/hierarchy.js +25 -0
  47. package/dist/hierarchy.js.map +1 -1
  48. package/dist/launchInterProScan.d.ts +2 -1
  49. package/dist/launchInterProScan.js +31 -15
  50. package/dist/launchInterProScan.js.map +1 -1
  51. package/dist/model.d.ts +150 -494
  52. package/dist/model.js +24 -116
  53. package/dist/model.js.map +1 -1
  54. package/dist/useColorContrast.d.ts +10 -0
  55. package/dist/useColorContrast.js +13 -0
  56. package/dist/useColorContrast.js.map +1 -0
  57. package/dist/useDragScroll.d.ts +12 -0
  58. package/dist/useDragScroll.js +47 -0
  59. package/dist/useDragScroll.js.map +1 -0
  60. package/dist/version.d.ts +1 -1
  61. package/dist/version.js +1 -1
  62. package/dist/version.js.map +1 -1
  63. package/package.json +6 -6
  64. package/src/clustalX.test.ts +76 -0
  65. package/src/clustalX.ts +131 -0
  66. package/src/components/SequenceTextArea.tsx +6 -6
  67. package/src/components/TextTrack.tsx +3 -8
  68. package/src/components/Track.tsx +6 -9
  69. package/src/components/VerticalScrollbar.tsx +13 -40
  70. package/src/components/dialogs/InterProScanDialog.tsx +7 -1
  71. package/src/components/header/HeaderStatusArea.tsx +6 -1
  72. package/src/components/header/MSASettingsMenu.tsx +3 -27
  73. package/src/components/header/SettingsMenu.tsx +7 -79
  74. package/src/components/header/TreeSettingsMenu.tsx +4 -48
  75. package/src/components/header/settingsMenuItems.ts +68 -0
  76. package/src/components/import/ImportFormExamples.tsx +1 -1
  77. package/src/components/minimap/Minimap.tsx +13 -38
  78. package/src/components/msa/MSACanvasBlock.tsx +53 -36
  79. package/src/components/msa/renderMSABlock.ts +16 -9
  80. package/src/components/tree/TreeCanvas.tsx +13 -35
  81. package/src/components/tree/renderTreeCanvas.test.ts +205 -0
  82. package/src/components/tree/renderTreeCanvas.ts +46 -11
  83. package/src/fetchUtils.ts +16 -2
  84. package/src/hierarchy.ts +28 -0
  85. package/src/launchInterProScan.ts +34 -10
  86. package/src/model.ts +33 -143
  87. package/src/useColorContrast.ts +18 -0
  88. package/src/useDragScroll.ts +55 -0
  89. package/src/version.ts +1 -1
package/src/fetchUtils.ts CHANGED
@@ -20,6 +20,20 @@ export async function jsonfetch<T>(url: string, args?: RequestInit) {
20
20
  return response.json() as T
21
21
  }
22
22
 
23
- export function timeout(time: number) {
24
- return new Promise(res => setTimeout(res, time))
23
+ export function timeout(time: number, signal?: AbortSignal) {
24
+ return new Promise<void>((resolve, reject) => {
25
+ if (signal?.aborted) {
26
+ reject(new DOMException('Aborted', 'AbortError'))
27
+ } else {
28
+ const id = setTimeout(resolve, time)
29
+ signal?.addEventListener('abort', () => {
30
+ clearTimeout(id)
31
+ reject(new DOMException('Aborted', 'AbortError'))
32
+ })
33
+ }
34
+ })
35
+ }
36
+
37
+ export function isAbortError(e: unknown) {
38
+ return e instanceof DOMException && e.name === 'AbortError'
25
39
  }
package/src/hierarchy.ts CHANGED
@@ -10,6 +10,7 @@ export interface HierarchyNode<T = NodeWithIds> {
10
10
  x?: number
11
11
  y?: number
12
12
  len?: number
13
+ depthToLeaf?: number
13
14
  _children?: HierarchyNode<T>[] | null
14
15
  }
15
16
 
@@ -203,6 +204,33 @@ export function collapse<T>(node: HierarchyNode<T>) {
203
204
  }
204
205
  }
205
206
 
207
+ // Cladogram positioning based on ape's plot.phylo: uses topological depth (max
208
+ // steps to a tip) instead of branch length so all leaves align at the rightmost
209
+ // x. Memoizes onto node.depthToLeaf since the layout walks the tree repeatedly.
210
+ // See https://github.com/emmanuelparadis/ape/blob/master/R/plot.phylo.R
211
+ export function calcDepthToLeaf(node: HierarchyNode): number {
212
+ if (node.depthToLeaf === undefined) {
213
+ let maxDepth = 0
214
+ if (node.children) {
215
+ for (const child of node.children) {
216
+ maxDepth = Math.max(maxDepth, 1 + calcDepthToLeaf(child))
217
+ }
218
+ }
219
+ node.depthToLeaf = maxDepth
220
+ }
221
+ return node.depthToLeaf
222
+ }
223
+
224
+ export function findMaxBranchLen(node: HierarchyNode): number {
225
+ let maxLen = node.len || 0
226
+ if (node.children) {
227
+ for (const child of node.children) {
228
+ maxLen = Math.max(maxLen, findMaxBranchLen(child))
229
+ }
230
+ }
231
+ return maxLen
232
+ }
233
+
206
234
  export function maxLength(d: HierarchyNode): number {
207
235
  return (
208
236
  (d.data.length || 0) +
@@ -1,6 +1,6 @@
1
1
  import { getSession } from '@jbrowse/core/util'
2
2
 
3
- import { jsonfetch, textfetch, timeout } from './fetchUtils.ts'
3
+ import { isAbortError, jsonfetch, textfetch, timeout } from './fetchUtils.ts'
4
4
 
5
5
  import type { MsaViewModel } from './model.ts'
6
6
 
@@ -29,12 +29,14 @@ async function runInterProScan({
29
29
  onJobId,
30
30
  programs,
31
31
  model,
32
+ signal,
32
33
  }: {
33
34
  seq: string
34
35
  programs: string[]
35
36
  onProgress: (arg?: { msg: string; url?: string }) => void
36
37
  onJobId?: (arg: string) => void
37
38
  model: MsaViewModel
39
+ signal?: AbortSignal
38
40
  }) {
39
41
  const jobId = await textfetch(`${base}/iprscan5/run`, {
40
42
  method: 'POST',
@@ -43,37 +45,42 @@ async function runInterProScan({
43
45
  sequence: seq,
44
46
  programs: programs.join(','),
45
47
  }),
48
+ signal,
46
49
  })
47
50
  onJobId?.(jobId)
48
51
  await wait({
49
52
  jobId,
50
53
  onProgress,
54
+ signal,
51
55
  })
52
- await loadInterProScanResultsWithStatus({ jobId, model })
56
+ await loadInterProScanResultsWithStatus({ jobId, model, onProgress, signal })
53
57
  }
54
58
 
55
- function loadInterProScanResults(jobId: string) {
59
+ function loadInterProScanResults(jobId: string, signal?: AbortSignal) {
56
60
  return jsonfetch<InterProScanResponse>(
57
61
  `${base}/iprscan5/result/${jobId}/json`,
62
+ { signal },
58
63
  )
59
64
  }
60
65
 
61
66
  async function wait({
62
67
  onProgress,
63
68
  jobId,
69
+ signal,
64
70
  }: {
65
71
  jobId: string
66
72
  onProgress: (arg?: { msg: string; url?: string }) => void
73
+ signal?: AbortSignal
67
74
  }) {
68
75
  const url = `${base}/iprscan5/status/${jobId}`
69
76
  try {
70
77
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
71
78
  while (true) {
72
79
  for (let i = 0; i < 10; i++) {
73
- await timeout(1000)
80
+ await timeout(1000, signal)
74
81
  onProgress({ msg: `Checking status ${10 - i}`, url })
75
82
  }
76
- const result = await textfetch(url)
83
+ const result = await textfetch(url, { signal })
77
84
  if (result.includes('FINISHED')) {
78
85
  break
79
86
  }
@@ -93,6 +100,7 @@ export async function launchInterProScan({
93
100
  onJobId,
94
101
  onProgress,
95
102
  model,
103
+ signal,
96
104
  }: {
97
105
  algorithm: string
98
106
  seq: string
@@ -100,6 +108,7 @@ export async function launchInterProScan({
100
108
  onProgress: (arg?: { msg: string; url?: string }) => void
101
109
  onJobId?: (arg: string) => void
102
110
  model: MsaViewModel
111
+ signal?: AbortSignal
103
112
  }) {
104
113
  try {
105
114
  onProgress({ msg: `Launching ${algorithm} MSA` })
@@ -110,10 +119,17 @@ export async function launchInterProScan({
110
119
  onProgress,
111
120
  programs,
112
121
  model,
122
+ signal,
113
123
  })
114
124
  } else {
115
125
  throw new Error('unknown algorithm')
116
126
  }
127
+ } catch (e) {
128
+ if (isAbortError(e)) {
129
+ getSession(model).notify('Cancelled InterProScan query', 'info')
130
+ } else {
131
+ throw e
132
+ }
117
133
  } finally {
118
134
  onProgress()
119
135
  }
@@ -122,25 +138,33 @@ export async function launchInterProScan({
122
138
  async function loadInterProScanResultsWithStatus({
123
139
  jobId,
124
140
  model,
141
+ onProgress,
142
+ signal,
125
143
  }: {
126
144
  jobId: string
127
145
  model: MsaViewModel
146
+ onProgress: (arg?: { msg: string; url?: string }) => void
147
+ signal?: AbortSignal
128
148
  }) {
129
149
  try {
130
- model.setStatus({
150
+ onProgress({
131
151
  msg: `Downloading results of ${jobId} (for larger sequences this can be slow, click status to download and upload in the manual tab)`,
132
152
  url: `https://www.ebi.ac.uk/Tools/services/rest/iprscan5/result/${jobId}/json`,
133
153
  })
134
- const ret = await loadInterProScanResults(jobId)
154
+ const ret = await loadInterProScanResults(jobId, signal)
135
155
  model.setInterProAnnotations(
136
156
  Object.fromEntries(ret.results.map(r => [r.xref[0]!.id, r])),
137
157
  )
138
158
  model.setShowDomains(true)
139
159
  getSession(model).notify(`Loaded interproscan ${jobId} results`, 'success')
140
160
  } catch (e) {
141
- console.error(e)
142
- getSession(model).notifyError(`${e}`, e)
161
+ if (isAbortError(e)) {
162
+ getSession(model).notify('Cancelled InterProScan query', 'info')
163
+ } else {
164
+ console.error(e)
165
+ getSession(model).notifyError(`${e}`, e)
166
+ }
143
167
  } finally {
144
- model.setStatus()
168
+ onProgress()
145
169
  }
146
170
  }
package/src/model.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  } from 'msa-parsers'
21
21
 
22
22
  import { blocksX, blocksY } from './calculateBlocks.ts'
23
+ import { clustalXColumnColors } from './clustalX.ts'
23
24
  import colorSchemes from './colorSchemes.ts'
24
25
  import ConservationTrack from './components/ConservationTrack.tsx'
25
26
  import TextTrack from './components/TextTrack.tsx'
@@ -48,9 +49,11 @@ import {
48
49
  import { createPaletteMap } from './createPaletteMap.ts'
49
50
  import { flatToTree } from './flatToTree.ts'
50
51
  import {
52
+ calcDepthToLeaf,
51
53
  clusterLayout,
52
54
  collapse,
53
55
  find,
56
+ findMaxBranchLen,
54
57
  hierarchy,
55
58
  leaves,
56
59
  links,
@@ -90,9 +93,9 @@ import type { Theme } from '@mui/material'
90
93
  /**
91
94
  * #stateModel MsaView
92
95
  * extends
93
- * - DialogQueueSessionMixin
94
- * - MSAModel
95
- * - Tree
96
+ * - [DialogQueueSessionMixin](../dialogqueuesessionmixin)
97
+ * - [MSAModel](../msamodel)
98
+ * - [Tree](../tree)
96
99
  */
97
100
  function stateModelFactory() {
98
101
  return types
@@ -242,7 +245,9 @@ function stateModelFactory() {
242
245
  /**
243
246
  * #volatile
244
247
  */
245
- status: undefined as { msg: string; url?: string } | undefined,
248
+ status: undefined as
249
+ | { msg: string; url?: string; onCancel?: () => void }
250
+ | undefined,
246
251
  /**
247
252
  * #volatile
248
253
  * high resolution scale factor, helps make canvas look better on hi-dpi
@@ -337,6 +342,7 @@ function stateModelFactory() {
337
342
  /**
338
343
  * #volatile
339
344
  */
345
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
340
346
  error: undefined as unknown,
341
347
 
342
348
  /**
@@ -921,13 +927,9 @@ function stateModelFactory() {
921
927
  const columns = this.columns2d
922
928
  for (const column of columns) {
923
929
  for (let j = 0; j < column.length; j++) {
924
- const l = r[j] || {}
930
+ const l = (r[j] ??= {})
925
931
  const cj = column[j]!
926
- if (!l[cj]) {
927
- l[cj] = 0
928
- }
929
- l[cj]++
930
- r[j] = l
932
+ l[cj] = (l[cj] ?? 0) + 1
931
933
  }
932
934
  }
933
935
  return r
@@ -1005,136 +1007,9 @@ function stateModelFactory() {
1005
1007
  */
1006
1008
  get colClustalX() {
1007
1009
  const { colStats, colStatsSums } = this
1008
- return colStats.map((stats, i) => {
1009
- const total = colStatsSums[i]!
1010
- const colors: Record<string, string> = {}
1011
-
1012
- const W = stats.W ?? 0
1013
- const L = stats.L ?? 0
1014
- const V = stats.V ?? 0
1015
- const I = stats.I ?? 0
1016
- const M = stats.M ?? 0
1017
- const A = stats.A ?? 0
1018
- const F = stats.F ?? 0
1019
- const C = stats.C ?? 0
1020
- const H = stats.H ?? 0
1021
- const P = stats.P ?? 0
1022
- const R = stats.R ?? 0
1023
- const K = stats.K ?? 0
1024
- const Q = stats.Q ?? 0
1025
- const E = stats.E ?? 0
1026
- const D = stats.D ?? 0
1027
- const T = stats.T ?? 0
1028
- const S = stats.S ?? 0
1029
- const G = stats.G ?? 0
1030
- const Y = stats.Y ?? 0
1031
- const N = stats.N ?? 0
1032
-
1033
- const WLVIMAFCHPY = W + L + V + I + M + A + F + C + H + P + Y
1034
- const KR = K + R
1035
- const QE = Q + E
1036
- const ED = E + D
1037
- const TS = T + S
1038
-
1039
- if (WLVIMAFCHPY / total > 0.6) {
1040
- colors.W = 'rgb(128,179,230)'
1041
- colors.L = 'rgb(128,179,230)'
1042
- colors.V = 'rgb(128,179,230)'
1043
- colors.A = 'rgb(128,179,230)'
1044
- colors.I = 'rgb(128,179,230)'
1045
- colors.M = 'rgb(128,179,230)'
1046
- colors.F = 'rgb(128,179,230)'
1047
- colors.C = 'rgb(128,179,230)'
1048
- }
1049
-
1050
- if (
1051
- KR / total > 0.6 ||
1052
- K / total > 0.8 ||
1053
- R / total > 0.8 ||
1054
- Q / total > 0.8
1055
- ) {
1056
- colors.K = '#d88'
1057
- colors.R = '#d88'
1058
- }
1059
-
1060
- if (
1061
- KR / total > 0.6 ||
1062
- QE / total > 0.5 ||
1063
- E / total > 0.8 ||
1064
- Q / total > 0.8 ||
1065
- D / total > 0.8
1066
- ) {
1067
- colors.E = 'rgb(192, 72, 192)'
1068
- }
1069
-
1070
- if (
1071
- KR / total > 0.6 ||
1072
- ED / total > 0.5 ||
1073
- K / total > 0.8 ||
1074
- R / total > 0.8 ||
1075
- Q / total > 0.8
1076
- ) {
1077
- colors.D = 'rgb(204, 77, 204)'
1078
- }
1079
-
1080
- if (N / total > 0.5 || Y / total > 0.85) {
1081
- colors.N = '#8f8'
1082
- }
1083
-
1084
- if (
1085
- KR / total > 0.6 ||
1086
- QE / total > 0.6 ||
1087
- Q / total > 0.85 ||
1088
- E / total > 0.85 ||
1089
- K / total > 0.85 ||
1090
- R / total > 0.85
1091
- ) {
1092
- colors.Q = '#8f8'
1093
- }
1094
-
1095
- if (
1096
- WLVIMAFCHPY / total > 0.6 ||
1097
- TS / total > 0.5 ||
1098
- S / total > 0.85 ||
1099
- T / total > 0.85
1100
- ) {
1101
- colors.S = 'rgb(26,204,26)'
1102
- colors.T = 'rgb(26,204,26)'
1103
- }
1104
-
1105
- if (C / total > 0.85) {
1106
- colors.C = 'rgb(240, 128, 128)'
1107
- }
1108
-
1109
- if (G / total > 0) {
1110
- colors.G = 'rgb(240, 144, 72)'
1111
- }
1112
-
1113
- if (P / total > 0) {
1114
- colors.P = 'rgb(204, 204, 0)'
1115
- }
1116
-
1117
- if (
1118
- WLVIMAFCHPY / total > 0.6 ||
1119
- W / total > 0.85 ||
1120
- Y / total > 0.85 ||
1121
- A / total > 0.85 ||
1122
- C / total > 0.85 ||
1123
- P / total > 0.85 ||
1124
- Q / total > 0.85 ||
1125
- F / total > 0.85 ||
1126
- H / total > 0.85 ||
1127
- I / total > 0.85 ||
1128
- L / total > 0.85 ||
1129
- M / total > 0.85 ||
1130
- V / total > 0.85
1131
- ) {
1132
- colors.H = 'rgb(26, 179, 179)'
1133
- colors.Y = 'rgb(26, 179, 179)'
1134
- }
1135
-
1136
- return colors
1137
- })
1010
+ return colStats.map((stats, i) =>
1011
+ clustalXColumnColors(stats, colStatsSums[i]!),
1012
+ )
1138
1013
  },
1139
1014
 
1140
1015
  /**
@@ -1208,6 +1083,22 @@ function stateModelFactory() {
1208
1083
  return leaves(this.hierarchy)
1209
1084
  },
1210
1085
 
1086
+ /**
1087
+ * #getter
1088
+ * max branch length across the tree, used to scale phylogram x-positions
1089
+ */
1090
+ get maxBranchLength() {
1091
+ return findMaxBranchLen(this.hierarchy)
1092
+ },
1093
+
1094
+ /**
1095
+ * #getter
1096
+ * max topological depth to a tip, used to scale cladogram x-positions
1097
+ */
1098
+ get maxDepthToLeaf() {
1099
+ return calcDepthToLeaf(this.hierarchy)
1100
+ },
1101
+
1211
1102
  /**
1212
1103
  * #getter
1213
1104
  */
@@ -1419,7 +1310,7 @@ function stateModelFactory() {
1419
1310
  /**
1420
1311
  * #action
1421
1312
  */
1422
- setStatus(status?: { msg: string; url?: string }) {
1313
+ setStatus(status?: { msg: string; url?: string; onCancel?: () => void }) {
1423
1314
  self.status = status
1424
1315
  },
1425
1316
  }))
@@ -1785,8 +1676,7 @@ function stateModelFactory() {
1785
1676
  },
1786
1677
 
1787
1678
  initFilter(arg: string) {
1788
- const ret = self.featureFilters.get(arg)
1789
- if (ret === undefined) {
1679
+ if (!self.featureFilters.has(arg)) {
1790
1680
  self.featureFilters.set(arg, true)
1791
1681
  }
1792
1682
  },
@@ -0,0 +1,18 @@
1
+ import { useMemo } from 'react'
2
+
3
+ import { useTheme } from '@mui/material'
4
+
5
+ import { colorContrast } from './util.ts'
6
+
7
+ /**
8
+ * Resolves the current theme and a memoized contrast-color scheme for a given
9
+ * color scheme. Shared by the MSA and text-track canvas blocks.
10
+ */
11
+ export function useColorContrast(colorScheme: Record<string, string>) {
12
+ const theme = useTheme()
13
+ const contrastScheme = useMemo(
14
+ () => colorContrast(colorScheme, theme),
15
+ [colorScheme, theme],
16
+ )
17
+ return { theme, contrastScheme }
18
+ }
@@ -0,0 +1,55 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+
3
+ /**
4
+ * Tracks a mouse drag along a single axis and reports the scroll delta on each
5
+ * animation frame. Shared by the horizontal Minimap and the VerticalScrollbar.
6
+ *
7
+ * `onScroll` is invoked with the pixel delta since drag start and the scroll
8
+ * offset captured when the drag began. Memoize it (useCallback) so the document
9
+ * listeners are not re-attached on every render.
10
+ */
11
+ export function useDragScroll(
12
+ axis: 'x' | 'y',
13
+ onScroll: (delta: number, startScroll: number) => void,
14
+ ) {
15
+ const scheduled = useRef(false)
16
+ const [mouseDown, setMouseDown] = useState<{
17
+ client: number
18
+ startScroll: number
19
+ }>()
20
+
21
+ useEffect(() => {
22
+ if (mouseDown !== undefined) {
23
+ const { client: startClient, startScroll } = mouseDown
24
+ function onMove(event: MouseEvent) {
25
+ if (!scheduled.current) {
26
+ scheduled.current = true
27
+ requestAnimationFrame(() => {
28
+ const client = axis === 'x' ? event.clientX : event.clientY
29
+ onScroll(client - startClient, startScroll)
30
+ scheduled.current = false
31
+ })
32
+ }
33
+ }
34
+ function onUp() {
35
+ setMouseDown(undefined)
36
+ }
37
+ document.addEventListener('mousemove', onMove)
38
+ document.addEventListener('mouseup', onUp)
39
+ return () => {
40
+ document.removeEventListener('mousemove', onMove)
41
+ document.removeEventListener('mouseup', onUp)
42
+ }
43
+ }
44
+ }, [axis, onScroll, mouseDown])
45
+
46
+ return {
47
+ dragging: mouseDown !== undefined,
48
+ startDrag: (event: React.MouseEvent, startScroll: number) => {
49
+ setMouseDown({
50
+ client: axis === 'x' ? event.clientX : event.clientY,
51
+ startScroll,
52
+ })
53
+ },
54
+ }
55
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '5.0.13'
1
+ export const version = '5.1.0'