@furystack/shades-nipple 9.0.2 โ 10.0.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/CHANGELOG.md +68 -0
- package/README.md +20 -18
- package/esm/nipple.d.ts +13 -8
- package/esm/nipple.d.ts.map +1 -1
- package/esm/nipple.js.map +1 -1
- package/esm/nipple.spec.js +59 -1
- package/esm/nipple.spec.js.map +1 -1
- package/package.json +6 -6
- package/src/nipple.spec.tsx +74 -6
- package/src/nipple.tsx +14 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [10.0.0] - 2026-03-27
|
|
4
|
+
|
|
5
|
+
### โจ Features
|
|
6
|
+
|
|
7
|
+
- Normalized joystick callbacks to receive a single `event` object with a `data` payload (`onStart`, `onMove`, `onDir`, `onEnd`), aligning the component API with the current runtime event shape.
|
|
8
|
+
|
|
9
|
+
### ๐ Documentation
|
|
10
|
+
|
|
11
|
+
- Updated README examples and prop documentation to use the new callback signature and `NippleManagerOptions` type.
|
|
12
|
+
|
|
13
|
+
### ๐งช Tests
|
|
14
|
+
|
|
15
|
+
- Updated component tests to use the exported `NippleManagerEventHandler` type for callback mocks.
|
|
16
|
+
|
|
17
|
+
### โฌ๏ธ Dependencies
|
|
18
|
+
|
|
19
|
+
- Upgraded `nipplejs` from `^0.10.2` to `^1.0.1` to use the latest upstream manager and event behavior.
|
|
20
|
+
- Updated `@furystack/shades` dependency
|
|
21
|
+
|
|
22
|
+
### ๐ฅ Breaking Changes
|
|
23
|
+
|
|
24
|
+
### Callback Signatures Now Receive A Single Event Object
|
|
25
|
+
|
|
26
|
+
Joystick callback props no longer receive positional `(evt, data)` arguments.
|
|
27
|
+
All callback props now receive a single `event` object, and joystick payload values are available under `event.data`.
|
|
28
|
+
|
|
29
|
+
**Before / After:**
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
// โ Before
|
|
33
|
+
<NippleComponent
|
|
34
|
+
managerOptions={{ mode: 'static', position: { left: '50%', top: '50%' } }}
|
|
35
|
+
onMove={(_evt, data) => {
|
|
36
|
+
console.log(data.direction?.angle)
|
|
37
|
+
console.log(data.force)
|
|
38
|
+
}}
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
// โ
After
|
|
42
|
+
<NippleComponent
|
|
43
|
+
managerOptions={{ mode: 'static', position: { left: '50%', top: '50%' } }}
|
|
44
|
+
onMove={(event) => {
|
|
45
|
+
console.log(event.data.direction?.angle)
|
|
46
|
+
console.log(event.data.force)
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Impact:** Consumers using `onStart`, `onMove`, `onDir`, or `onEnd` with `(evt, data)` parameters must update handlers to use a single `event` parameter.
|
|
52
|
+
|
|
53
|
+
**Migration steps:**
|
|
54
|
+
|
|
55
|
+
1. Find all `NippleComponent` callback usages in your app.
|
|
56
|
+
2. Replace handler signatures from `(evt, data)` (or similar two-argument forms) to `(event)`.
|
|
57
|
+
3. Replace direct `data` references with `event.data`.
|
|
58
|
+
4. Run type-check and tests to verify all joystick interactions still compile and behave as expected.
|
|
59
|
+
|
|
60
|
+
## [9.0.3] - 2026-03-25
|
|
61
|
+
|
|
62
|
+
### ๐ฆ Build
|
|
63
|
+
|
|
64
|
+
- Removed deprecated `baseUrl` from tsconfig.json for TypeScript 6 compatibility
|
|
65
|
+
|
|
66
|
+
### โฌ๏ธ Dependencies
|
|
67
|
+
|
|
68
|
+
- Upgraded `typescript` from ^5.9.3 to ^6.0.2
|
|
69
|
+
- Upgraded `vitest` from ^4.1.0 to ^4.1.1
|
|
70
|
+
|
|
3
71
|
## [9.0.2] - 2026-03-19
|
|
4
72
|
|
|
5
73
|
### โจ Features
|
package/README.md
CHANGED
|
@@ -28,20 +28,20 @@ const GameControls = Shade({
|
|
|
28
28
|
color: 'blue',
|
|
29
29
|
}}
|
|
30
30
|
style={{ width: '200px', height: '200px', position: 'relative' }}
|
|
31
|
-
onStart={(
|
|
32
|
-
console.log('Joystick started', data)
|
|
31
|
+
onStart={(event) => {
|
|
32
|
+
console.log('Joystick started', event.data)
|
|
33
33
|
}}
|
|
34
|
-
onMove={(
|
|
35
|
-
console.log('Direction:', data.direction?.angle)
|
|
36
|
-
console.log('Distance:', data.distance)
|
|
37
|
-
console.log('Force:', data.force)
|
|
34
|
+
onMove={(event) => {
|
|
35
|
+
console.log('Direction:', event.data.direction?.angle)
|
|
36
|
+
console.log('Distance:', event.data.distance)
|
|
37
|
+
console.log('Force:', event.data.force)
|
|
38
38
|
}}
|
|
39
|
-
onEnd={(
|
|
39
|
+
onEnd={() => {
|
|
40
40
|
console.log('Joystick released')
|
|
41
41
|
}}
|
|
42
|
-
onDir={(
|
|
42
|
+
onDir={(event) => {
|
|
43
43
|
// Fired when direction changes (up, down, left, right)
|
|
44
|
-
console.log('Direction changed:', data.direction?.angle)
|
|
44
|
+
console.log('Direction changed:', event.data.direction?.angle)
|
|
45
45
|
}}
|
|
46
46
|
/>
|
|
47
47
|
),
|
|
@@ -50,14 +50,14 @@ const GameControls = Shade({
|
|
|
50
50
|
|
|
51
51
|
## Props
|
|
52
52
|
|
|
53
|
-
| Prop | Type
|
|
54
|
-
| ---------------- |
|
|
55
|
-
| `managerOptions` | `
|
|
56
|
-
| `style` | `CSSStyleDeclaration`
|
|
57
|
-
| `onStart` | `(
|
|
58
|
-
| `onEnd` | `(
|
|
59
|
-
| `onMove` | `(
|
|
60
|
-
| `onDir` | `(
|
|
53
|
+
| Prop | Type | Description |
|
|
54
|
+
| ---------------- | ---------------------- | -------------------------------------------- |
|
|
55
|
+
| `managerOptions` | `NippleManagerOptions` | NippleJS options passed to `nipplejs.create` |
|
|
56
|
+
| `style` | `CSSStyleDeclaration` | Inline styles for the container |
|
|
57
|
+
| `onStart` | `(event) => void` | Called when the joystick is pressed |
|
|
58
|
+
| `onEnd` | `(event) => void` | Called when the joystick is released |
|
|
59
|
+
| `onMove` | `(event) => void` | Called while the joystick is being moved |
|
|
60
|
+
| `onDir` | `(event) => void` | Called when direction changes |
|
|
61
61
|
|
|
62
62
|
## Manager Options
|
|
63
63
|
|
|
@@ -72,4 +72,6 @@ Common `managerOptions` properties:
|
|
|
72
72
|
| `threshold` | `number` | Minimum distance before triggering events |
|
|
73
73
|
| `fadeTime` | `number` | Fade animation duration |
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
Event callback props receive a single object that contains at least a `data` field with the joystick payload.
|
|
76
|
+
|
|
77
|
+
See the [NippleJS documentation](https://yoannmoi.net/nipplejs/) for all available options and event payload details.
|
package/esm/nipple.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
export
|
|
3
|
-
|
|
1
|
+
import nipplejs from 'nipplejs';
|
|
2
|
+
export type NippleManagerOptions = Parameters<typeof nipplejs.create>[0];
|
|
3
|
+
export type NippleManagerEvent = {
|
|
4
|
+
data: unknown;
|
|
5
|
+
};
|
|
6
|
+
export type NippleManagerEventHandler = (evt: NippleManagerEvent) => void;
|
|
7
|
+
export type NippleComponentProps = {
|
|
8
|
+
managerOptions: NippleManagerOptions;
|
|
4
9
|
style?: Partial<CSSStyleDeclaration>;
|
|
5
|
-
onStart?:
|
|
6
|
-
onEnd?:
|
|
7
|
-
onDir?:
|
|
8
|
-
onMove?:
|
|
9
|
-
}
|
|
10
|
+
onStart?: NippleManagerEventHandler;
|
|
11
|
+
onEnd?: NippleManagerEventHandler;
|
|
12
|
+
onDir?: NippleManagerEventHandler;
|
|
13
|
+
onMove?: NippleManagerEventHandler;
|
|
14
|
+
};
|
|
10
15
|
export declare const NippleComponent: (props: NippleComponentProps & Omit<Partial<HTMLElement>, "style"> & {
|
|
11
16
|
style?: Partial<CSSStyleDeclaration>;
|
|
12
17
|
} & {
|
package/esm/nipple.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nipple.d.ts","sourceRoot":"","sources":["../src/nipple.tsx"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"nipple.d.ts","sourceRoot":"","sources":["../src/nipple.tsx"],"names":[],"mappings":"AACA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAG/B,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;AACxE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,OAAO,CAAA;CACd,CAAA;AACD,MAAM,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,kBAAkB,KAAK,IAAI,CAAA;AAEzE,MAAM,MAAM,oBAAoB,GAAG;IACjC,cAAc,EAAE,oBAAoB,CAAA;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAA;IACpC,OAAO,CAAC,EAAE,yBAAyB,CAAA;IACnC,KAAK,CAAC,EAAE,yBAAyB,CAAA;IACjC,KAAK,CAAC,EAAE,yBAAyB,CAAA;IACjC,MAAM,CAAC,EAAE,yBAAyB,CAAA;CACnC,CAAA;AAED,eAAO,MAAM,eAAe;;;;sEAkC1B,CAAA"}
|
package/esm/nipple.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nipple.js","sourceRoot":"","sources":["../src/nipple.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"nipple.js","sourceRoot":"","sources":["../src/nipple.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,QAAQ,MAAM,UAAU,CAAA;AAkB/B,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAuB;IACzD,iBAAiB,EAAE,cAAc;IACjC,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,EAAE;QACrD,MAAM,OAAO,GAAG,MAAM,CAAiB,MAAM,CAAC,CAAA;QAE9C,aAAa,CAAC,aAAa,EAAE,GAAG,EAAE;YAChC,IAAI,OAAkC,CAAA;YACtC,cAAc,CAAC,GAAG,EAAE;gBAClB,IAAI,CAAC,OAAO,CAAC,OAAO;oBAAE,OAAM;gBAC5B,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC;oBACxB,IAAI,EAAE,OAAO,CAAC,OAAO;oBACrB,GAAG,KAAK,CAAC,cAAc;iBACxB,CAAC,CAAA;gBACF,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;oBAClB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;gBACpC,CAAC;gBACD,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;oBAChB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;gBAChC,CAAC;gBACD,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;oBAChB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;gBAChC,CAAC;gBACD,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;oBACjB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;gBAClC,CAAC;YACH,CAAC,CAAC,CAAA;YACF,OAAO,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAA;QACvD,CAAC,CAAC,CAAA;QAEF,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,yBAAK,GAAG,EAAE,OAAO,IAAG,QAAQ,CAAO,CAAA;QAC5C,CAAC;QACD,OAAO,yBAAK,GAAG,EAAE,OAAO,GAAI,CAAA;IAC9B,CAAC;CACF,CAAC,CAAA"}
|
package/esm/nipple.spec.js
CHANGED
|
@@ -3,9 +3,36 @@ import { createComponent, initializeShadeRoot } from '@furystack/shades';
|
|
|
3
3
|
import { usingAsync } from '@furystack/utils';
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
import { NippleComponent } from './nipple.js';
|
|
6
|
+
const { createMock, managerMock, managerEventHandlers } = vi.hoisted(() => {
|
|
7
|
+
const handlers = {};
|
|
8
|
+
const manager = {
|
|
9
|
+
on: vi.fn((event, handler) => {
|
|
10
|
+
handlers[event] = handler;
|
|
11
|
+
return manager;
|
|
12
|
+
}),
|
|
13
|
+
destroy: vi.fn(),
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
createMock: vi.fn(() => manager),
|
|
17
|
+
managerMock: manager,
|
|
18
|
+
managerEventHandlers: handlers,
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
vi.mock('nipplejs', () => ({
|
|
22
|
+
default: {
|
|
23
|
+
create: createMock,
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
6
26
|
describe('Nipple', () => {
|
|
7
27
|
beforeEach(() => {
|
|
8
28
|
document.body.innerHTML = '<div id="root"></div>';
|
|
29
|
+
createMock.mockClear();
|
|
30
|
+
managerMock.on.mockClear();
|
|
31
|
+
managerMock.destroy.mockClear();
|
|
32
|
+
delete managerEventHandlers.start;
|
|
33
|
+
delete managerEventHandlers.end;
|
|
34
|
+
delete managerEventHandlers.dir;
|
|
35
|
+
delete managerEventHandlers.move;
|
|
9
36
|
});
|
|
10
37
|
afterEach(() => {
|
|
11
38
|
document.body.innerHTML = '';
|
|
@@ -39,7 +66,38 @@ describe('Nipple', () => {
|
|
|
39
66
|
expect(nipple.props.onEnd).toBe(onEnd);
|
|
40
67
|
expect(nipple.props.onMove).toBe(onMove);
|
|
41
68
|
expect(nipple.props.onStart).toBe(onStart);
|
|
42
|
-
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
it('Should pass runtime move events as single event payload object', async () => {
|
|
72
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
73
|
+
const rootElement = document.getElementById('root');
|
|
74
|
+
const onMoveSpy = vi.fn((_) => undefined);
|
|
75
|
+
const onMove = onMoveSpy;
|
|
76
|
+
initializeShadeRoot({
|
|
77
|
+
injector,
|
|
78
|
+
rootElement,
|
|
79
|
+
jsxElement: createComponent(NippleComponent, { managerOptions: {}, onMove: onMove }),
|
|
80
|
+
});
|
|
81
|
+
await Promise.resolve();
|
|
82
|
+
expect(createMock).toHaveBeenCalledTimes(1);
|
|
83
|
+
expect(managerMock.on).toHaveBeenCalledWith('move', onMove);
|
|
84
|
+
const moveHandler = managerEventHandlers.move;
|
|
85
|
+
expect(moveHandler).toBeTypeOf('function');
|
|
86
|
+
const runtimeEvent = {
|
|
87
|
+
data: {
|
|
88
|
+
direction: { angle: 'up' },
|
|
89
|
+
distance: 24,
|
|
90
|
+
force: 0.75,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
moveHandler?.(runtimeEvent);
|
|
94
|
+
expect(onMoveSpy).toHaveBeenCalledTimes(1);
|
|
95
|
+
expect(onMoveSpy).toHaveBeenCalledWith(runtimeEvent);
|
|
96
|
+
expect(onMoveSpy).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
97
|
+
data: expect.objectContaining({
|
|
98
|
+
force: 0.75,
|
|
99
|
+
}),
|
|
100
|
+
}));
|
|
43
101
|
});
|
|
44
102
|
});
|
|
45
103
|
});
|
package/esm/nipple.spec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nipple.spec.js","sourceRoot":"","sources":["../src/nipple.spec.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAExE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,UAAU,CAAC,GAAG,EAAE;QACd,QAAQ,CAAC,IAAI,CAAC,SAAS,GAAG,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"nipple.spec.js","sourceRoot":"","sources":["../src/nipple.spec.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furystack/shades-nipple",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"description": "A Nipple component wrapper for FuryStack Shades",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
},
|
|
39
39
|
"homepage": "https://github.com/furystack/furystack",
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@furystack/inject": "^12.0.
|
|
42
|
-
"typescript": "^
|
|
43
|
-
"vitest": "^4.1.
|
|
41
|
+
"@furystack/inject": "^12.0.35",
|
|
42
|
+
"typescript": "^6.0.2",
|
|
43
|
+
"vitest": "^4.1.2"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@furystack/shades": "^13.
|
|
47
|
-
"nipplejs": "^0.
|
|
46
|
+
"@furystack/shades": "^13.2.0",
|
|
47
|
+
"nipplejs": "^1.0.1"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=22.0.0"
|
package/src/nipple.spec.tsx
CHANGED
|
@@ -2,12 +2,41 @@ import { Injector } from '@furystack/inject'
|
|
|
2
2
|
import { createComponent, initializeShadeRoot } from '@furystack/shades'
|
|
3
3
|
import { usingAsync } from '@furystack/utils'
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
-
import type { NippleComponentProps } from './nipple.js'
|
|
5
|
+
import type { NippleComponentProps, NippleManagerEvent, NippleManagerEventHandler } from './nipple.js'
|
|
6
6
|
import { NippleComponent } from './nipple.js'
|
|
7
7
|
|
|
8
|
+
const { createMock, managerMock, managerEventHandlers } = vi.hoisted(() => {
|
|
9
|
+
const handlers: Partial<Record<'start' | 'end' | 'dir' | 'move', (event: unknown) => void>> = {}
|
|
10
|
+
const manager = {
|
|
11
|
+
on: vi.fn((event: 'start' | 'end' | 'dir' | 'move', handler: (event: unknown) => void) => {
|
|
12
|
+
handlers[event] = handler
|
|
13
|
+
return manager
|
|
14
|
+
}),
|
|
15
|
+
destroy: vi.fn(),
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
createMock: vi.fn(() => manager),
|
|
19
|
+
managerMock: manager,
|
|
20
|
+
managerEventHandlers: handlers,
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
vi.mock('nipplejs', () => ({
|
|
25
|
+
default: {
|
|
26
|
+
create: createMock,
|
|
27
|
+
},
|
|
28
|
+
}))
|
|
29
|
+
|
|
8
30
|
describe('Nipple', () => {
|
|
9
31
|
beforeEach(() => {
|
|
10
32
|
document.body.innerHTML = '<div id="root"></div>'
|
|
33
|
+
createMock.mockClear()
|
|
34
|
+
managerMock.on.mockClear()
|
|
35
|
+
managerMock.destroy.mockClear()
|
|
36
|
+
delete managerEventHandlers.start
|
|
37
|
+
delete managerEventHandlers.end
|
|
38
|
+
delete managerEventHandlers.dir
|
|
39
|
+
delete managerEventHandlers.move
|
|
11
40
|
})
|
|
12
41
|
afterEach(() => {
|
|
13
42
|
document.body.innerHTML = ''
|
|
@@ -29,10 +58,10 @@ describe('Nipple', () => {
|
|
|
29
58
|
await usingAsync(new Injector(), async (injector) => {
|
|
30
59
|
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
31
60
|
|
|
32
|
-
const onStart = vi.fn()
|
|
33
|
-
const onDir = vi.fn()
|
|
34
|
-
const onMove = vi.fn()
|
|
35
|
-
const onEnd = vi.fn()
|
|
61
|
+
const onStart: NippleManagerEventHandler = vi.fn()
|
|
62
|
+
const onDir: NippleManagerEventHandler = vi.fn()
|
|
63
|
+
const onMove: NippleManagerEventHandler = vi.fn()
|
|
64
|
+
const onEnd: NippleManagerEventHandler = vi.fn()
|
|
36
65
|
|
|
37
66
|
initializeShadeRoot({
|
|
38
67
|
injector,
|
|
@@ -48,8 +77,47 @@ describe('Nipple', () => {
|
|
|
48
77
|
expect(nipple.props.onEnd).toBe(onEnd)
|
|
49
78
|
expect(nipple.props.onMove).toBe(onMove)
|
|
50
79
|
expect(nipple.props.onStart).toBe(onStart)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('Should pass runtime move events as single event payload object', async () => {
|
|
84
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
85
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
86
|
+
const onMoveSpy = vi.fn((_: NippleManagerEvent) => undefined)
|
|
87
|
+
const onMove: NippleManagerEventHandler = onMoveSpy
|
|
88
|
+
|
|
89
|
+
initializeShadeRoot({
|
|
90
|
+
injector,
|
|
91
|
+
rootElement,
|
|
92
|
+
jsxElement: <NippleComponent managerOptions={{}} onMove={onMove} />,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
await Promise.resolve()
|
|
96
|
+
|
|
97
|
+
expect(createMock).toHaveBeenCalledTimes(1)
|
|
98
|
+
expect(managerMock.on).toHaveBeenCalledWith('move', onMove)
|
|
99
|
+
|
|
100
|
+
const moveHandler = managerEventHandlers.move
|
|
101
|
+
expect(moveHandler).toBeTypeOf('function')
|
|
102
|
+
|
|
103
|
+
const runtimeEvent = {
|
|
104
|
+
data: {
|
|
105
|
+
direction: { angle: 'up' },
|
|
106
|
+
distance: 24,
|
|
107
|
+
force: 0.75,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
moveHandler?.(runtimeEvent)
|
|
51
111
|
|
|
52
|
-
|
|
112
|
+
expect(onMoveSpy).toHaveBeenCalledTimes(1)
|
|
113
|
+
expect(onMoveSpy).toHaveBeenCalledWith(runtimeEvent)
|
|
114
|
+
expect(onMoveSpy).toHaveBeenLastCalledWith(
|
|
115
|
+
expect.objectContaining<NippleManagerEvent>({
|
|
116
|
+
data: expect.objectContaining({
|
|
117
|
+
force: 0.75,
|
|
118
|
+
}),
|
|
119
|
+
}),
|
|
120
|
+
)
|
|
53
121
|
})
|
|
54
122
|
})
|
|
55
123
|
})
|
package/src/nipple.tsx
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { Shade, createComponent } from '@furystack/shades'
|
|
2
|
-
import type { EventData, JoystickManagerOptions, JoystickOutputData } from 'nipplejs'
|
|
3
2
|
import nipplejs from 'nipplejs'
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
type NippleManager = ReturnType<typeof nipplejs.create>
|
|
5
|
+
export type NippleManagerOptions = Parameters<typeof nipplejs.create>[0]
|
|
6
|
+
export type NippleManagerEvent = {
|
|
7
|
+
data: unknown
|
|
8
|
+
}
|
|
9
|
+
export type NippleManagerEventHandler = (evt: NippleManagerEvent) => void
|
|
10
|
+
|
|
11
|
+
export type NippleComponentProps = {
|
|
12
|
+
managerOptions: NippleManagerOptions
|
|
7
13
|
style?: Partial<CSSStyleDeclaration>
|
|
8
|
-
onStart?:
|
|
9
|
-
onEnd?:
|
|
10
|
-
onDir?:
|
|
11
|
-
onMove?:
|
|
14
|
+
onStart?: NippleManagerEventHandler
|
|
15
|
+
onEnd?: NippleManagerEventHandler
|
|
16
|
+
onDir?: NippleManagerEventHandler
|
|
17
|
+
onMove?: NippleManagerEventHandler
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
export const NippleComponent = Shade<NippleComponentProps>({
|
|
@@ -17,7 +23,7 @@ export const NippleComponent = Shade<NippleComponentProps>({
|
|
|
17
23
|
const zoneRef = useRef<HTMLDivElement>('zone')
|
|
18
24
|
|
|
19
25
|
useDisposable('nipple-init', () => {
|
|
20
|
-
let manager:
|
|
26
|
+
let manager: NippleManager | undefined
|
|
21
27
|
queueMicrotask(() => {
|
|
22
28
|
if (!zoneRef.current) return
|
|
23
29
|
manager = nipplejs.create({
|