sliftutils 1.9.1 → 1.9.3

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/index.d.ts CHANGED
@@ -554,6 +554,7 @@ declare module "sliftutils/render-utils/GenericFormat" {
554
554
 
555
555
  declare module "sliftutils/render-utils/Input" {
556
556
  import preact from "preact";
557
+ export declare function roundToDecimals(value: number, decimals: number): number;
557
558
  export type InputProps = (preact.JSX.HTMLAttributes<HTMLInputElement> & {
558
559
  /** ONLY throttles onChangeValue */
559
560
  throttle?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -1,4 +1,5 @@
1
1
  import preact from "preact";
2
+ export declare function roundToDecimals(value: number, decimals: number): number;
2
3
  export type InputProps = (preact.JSX.HTMLAttributes<HTMLInputElement> & {
3
4
  /** ONLY throttles onChangeValue */
4
5
  throttle?: number;
@@ -3,6 +3,22 @@ import { css } from "typesafecss";
3
3
  import { observer } from "./observer";
4
4
  import { throttleFunction } from "socket-function/src/misc";
5
5
 
6
+ const ARROW_KEY_SHIFT_FACTOR = 10;
7
+
8
+ export function roundToDecimals(value: number, decimals: number) {
9
+ return Math.round(value * 10 ** decimals) / 10 ** decimals;
10
+ }
11
+
12
+ function countDecimals(value: number) {
13
+ let text = String(value);
14
+ if (text.includes("e-")) {
15
+ return Number(text.split("e-")[1]);
16
+ }
17
+ let dot = text.indexOf(".");
18
+ if (dot < 0) return 0;
19
+ return text.length - dot - 1;
20
+ }
21
+
6
22
  // TODO: Autogrow mode while typing
7
23
 
8
24
  // NOTE: "value" is optional. If you don't pass "value", we will preserve the value.
@@ -201,13 +217,11 @@ export class Input extends preact.Component<InputProps> {
201
217
  }
202
218
  if (this.elem && props.type === "number") {
203
219
  let delta = 0;
204
- let magnitude = 1;
220
+ let magnitude = Number(props.step) || 1;
205
221
  if (e.shiftKey) {
206
- if (props.integer) {
207
- magnitude = 10;
208
- } else {
209
- magnitude = 0.1;
210
- }
222
+ magnitude *= ARROW_KEY_SHIFT_FACTOR;
223
+ } else if (e.ctrlKey && !props.integer) {
224
+ magnitude /= ARROW_KEY_SHIFT_FACTOR;
211
225
  }
212
226
  if (e.code === "ArrowUp") {
213
227
  delta = magnitude;
@@ -219,7 +233,8 @@ export class Input extends preact.Component<InputProps> {
219
233
  }
220
234
  if (delta !== 0) {
221
235
  e.preventDefault();
222
- let newValue = Math.round(((+this.elem.value || 0) + delta) * 100) / 100;
236
+ let decimals = Math.max(countDecimals(magnitude), countDecimals(+this.elem.value || 0));
237
+ let newValue = roundToDecimals((+this.elem.value || 0) + delta, decimals);
223
238
  e.currentTarget.value = newValue.toString();
224
239
  callback?.(e as unknown as preact.JSX.TargetedInputEvent<HTMLInputElement>);
225
240
  }
@@ -1,5 +1,5 @@
1
1
  import preact from "preact";
2
- import { Input } from "./Input";
2
+ import { Input, roundToDecimals } from "./Input";
3
3
  import { css } from "typesafecss";
4
4
  import { lazy } from "socket-function/src/caching";
5
5
  import { observer } from "./observer";
@@ -68,10 +68,6 @@ export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
68
68
  useDateUI?: boolean;
69
69
  };
70
70
 
71
- function roundToDecimals(value: number, decimals: number) {
72
- return Math.round(value * 10 ** decimals) / 10 ** decimals;
73
- }
74
-
75
71
  export const startGuessDateRange = +new Date(2010, 0, 1).getTime();
76
72
  export const endGuessDateRange = +new Date(2050, 0, 1).getTime();
77
73
 
@@ -3,6 +3,16 @@ import { runInAction } from "./mobxTyped";
3
3
 
4
4
  export let dragCount = 0;
5
5
 
6
+ const ARROW_STEP_PER_FRAME = 1;
7
+ const ARROW_STEP_PER_FRAME_SHIFT = 10;
8
+ const ARROW_STEP_PER_FRAME_CTRL = 0.1;
9
+ const ARROW_DIRECTIONS: { [code: string]: DragOffset } = {
10
+ ArrowUp: { x: 0, y: -1 },
11
+ ArrowDown: { x: 0, y: 1 },
12
+ ArrowLeft: { x: -1, y: 0 },
13
+ ArrowRight: { x: 1, y: 0 },
14
+ };
15
+
6
16
  type DragOffset = { x: number; y: number };
7
17
 
8
18
  function throttleFunctionFast<Args extends unknown[]>(
@@ -60,12 +70,30 @@ export function performDrag2(
60
70
  let startMouseY = e.clientY;
61
71
  let lastMouseX = e.clientX;
62
72
  let lastMouseY = e.clientY;
73
+ let keyX = 0;
74
+ let keyY = 0;
75
+ let heldArrows = new Set<string>();
76
+ let shiftHeld = e.shiftKey;
77
+ let ctrlHeld = e.ctrlKey;
78
+ let arrowRepeatRunning = false;
63
79
 
64
80
  let passedSlop = !slop;
65
81
 
82
+ function getDelta(): DragOffset {
83
+ let deltaX = lastMouseX - startMouseX + keyX;
84
+ let deltaY = lastMouseY - startMouseY + keyY;
85
+ if (slop) {
86
+ let dist = Math.sqrt(deltaX ** 2 + deltaY ** 2);
87
+ if (dist > slop) passedSlop = true;
88
+ }
89
+ return { x: deltaX, y: deltaY };
90
+ }
91
+
66
92
  function cancel() {
67
93
  lastMouseX = startMouseX;
68
94
  lastMouseY = startMouseY;
95
+ keyX = 0;
96
+ keyY = 0;
69
97
  finish();
70
98
  }
71
99
 
@@ -74,17 +102,41 @@ export function performDrag2(
74
102
 
75
103
  const triggerMove = throttleFunctionFast(async () => {
76
104
  if (disposed) return;
77
- let deltaX = lastMouseX - startMouseX;
78
- let deltaY = lastMouseY - startMouseY;
79
- if (slop) {
80
- let dist = Math.sqrt(deltaX ** 2 + deltaY ** 2);
81
- if (dist > slop) passedSlop = true;
82
- if (!passedSlop) return;
83
- }
84
- onMove({ x: deltaX, y: deltaY });
105
+ let delta = getDelta();
106
+ if (!passedSlop) return;
107
+ onMove(delta);
85
108
  await new Promise(r => requestAnimationFrame(r));
86
109
  });
87
110
 
111
+ function applyArrowStep() {
112
+ let step = ARROW_STEP_PER_FRAME;
113
+ if (shiftHeld) {
114
+ step = ARROW_STEP_PER_FRAME_SHIFT;
115
+ } else if (ctrlHeld) {
116
+ step = ARROW_STEP_PER_FRAME_CTRL;
117
+ }
118
+ for (let code of heldArrows) {
119
+ let direction = ARROW_DIRECTIONS[code];
120
+ keyX += direction.x * step;
121
+ keyY += direction.y * step;
122
+ }
123
+ triggerMove();
124
+ }
125
+
126
+ function runArrowRepeat() {
127
+ if (arrowRepeatRunning) return;
128
+ arrowRepeatRunning = true;
129
+ const frame = () => {
130
+ if (finished || !heldArrows.size) {
131
+ arrowRepeatRunning = false;
132
+ return;
133
+ }
134
+ applyArrowStep();
135
+ requestAnimationFrame(frame);
136
+ };
137
+ requestAnimationFrame(frame);
138
+ }
139
+
88
140
  const onMouseMove = (e: MouseEvent) => {
89
141
  if (finished) return;
90
142
  if (e.buttons === 0) {
@@ -106,16 +158,12 @@ export function performDrag2(
106
158
  window.removeEventListener("mouseup", finish, { capture: true });
107
159
  window.removeEventListener("blur", onBlur);
108
160
  document.removeEventListener("keydown", keyDown);
161
+ document.removeEventListener("keyup", keyUp);
109
162
  triggerMove();
110
163
  if (onDone) {
111
- let deltaX = lastMouseX - startMouseX;
112
- let deltaY = lastMouseY - startMouseY;
113
- if (slop) {
114
- let dist = Math.sqrt(deltaX ** 2 + deltaY ** 2);
115
- if (dist > slop) passedSlop = true;
116
- if (!passedSlop) return;
117
- }
118
- onDone({ x: deltaX, y: deltaY });
164
+ let delta = getDelta();
165
+ if (!passedSlop) return;
166
+ onDone(delta);
119
167
  }
120
168
  } finally {
121
169
  setTimeout(() => {
@@ -127,9 +175,25 @@ export function performDrag2(
127
175
 
128
176
  const keyDown = (e: KeyboardEvent) => {
129
177
  if (finished) return;
178
+ shiftHeld = e.shiftKey;
179
+ ctrlHeld = e.ctrlKey;
130
180
  if (e.code === "Escape") {
131
181
  cancel();
182
+ return;
132
183
  }
184
+ if (!ARROW_DIRECTIONS[e.code]) return;
185
+ e.preventDefault();
186
+ if (e.repeat || heldArrows.has(e.code)) return;
187
+ heldArrows.add(e.code);
188
+ applyArrowStep();
189
+ runArrowRepeat();
190
+ };
191
+
192
+ const keyUp = (e: KeyboardEvent) => {
193
+ if (finished) return;
194
+ shiftHeld = e.shiftKey;
195
+ ctrlHeld = e.ctrlKey;
196
+ heldArrows.delete(e.code);
133
197
  };
134
198
 
135
199
  const onBlur = () => {
@@ -141,6 +205,7 @@ export function performDrag2(
141
205
  window.addEventListener("mouseup", finish, { capture: true });
142
206
  window.addEventListener("blur", onBlur);
143
207
  document.addEventListener("keydown", keyDown);
208
+ document.addEventListener("keyup", keyUp);
144
209
  selfCancelCallback.add(cancel);
145
210
 
146
211
  if (!slop) {