@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 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={(evt, data) => {
32
- console.log('Joystick started', data)
31
+ onStart={(event) => {
32
+ console.log('Joystick started', event.data)
33
33
  }}
34
- onMove={(evt, data) => {
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={(evt, data) => {
39
+ onEnd={() => {
40
40
  console.log('Joystick released')
41
41
  }}
42
- onDir={(evt, data) => {
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 | Description |
54
- | ---------------- | ------------------------ | -------------------------------------------- |
55
- | `managerOptions` | `JoystickManagerOptions` | NippleJS manager options (see NippleJS docs) |
56
- | `style` | `CSSStyleDeclaration` | Inline styles for the container |
57
- | `onStart` | `(evt, data) => void` | Called when the joystick is pressed |
58
- | `onEnd` | `(evt, data) => void` | Called when the joystick is released |
59
- | `onMove` | `(evt, data) => void` | Called while the joystick is being moved |
60
- | `onDir` | `(evt, data) => void` | Called when direction changes |
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
- See the [NippleJS documentation](https://yoannmoi.net/nipplejs/) for all available options.
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 type { EventData, JoystickManagerOptions, JoystickOutputData } from 'nipplejs';
2
- export interface NippleComponentProps {
3
- managerOptions: JoystickManagerOptions;
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?: (evt: EventData, data: JoystickOutputData) => void;
6
- onEnd?: (evt: EventData, data: JoystickOutputData) => void;
7
- onDir?: (evt: EventData, data: JoystickOutputData) => void;
8
- onMove?: (evt: EventData, data: JoystickOutputData) => void;
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
  } & {
@@ -1 +1 @@
1
- {"version":3,"file":"nipple.d.ts","sourceRoot":"","sources":["../src/nipple.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAGrF,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,sBAAsB,CAAA;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAA;IACpC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC5D,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC1D,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAA;IAC1D,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAA;CAC5D;AAED,eAAO,MAAM,eAAe;;;;sEAkC1B,CAAA"}
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;AAE1D,OAAO,QAAQ,MAAM,UAAU,CAAA;AAW/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,OAAuD,CAAA;YAC3D,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"}
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"}
@@ -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
- // TODO: Check for pointer events
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
  });
@@ -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;IACnD,CAAC,CAAC,CAAA;IACF,SAAS,CAAC,GAAG,EAAE;QACb,QAAQ,CAAC,IAAI,CAAC,SAAS,GAAG,EAAE,CAAA;IAC9B,CAAC,CAAC,CAAA;IACF,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;YAClD,MAAM,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAmB,CAAA;YAErE,mBAAmB,CAAC;gBAClB,QAAQ;gBACR,WAAW;gBACX,UAAU,EAAE,gBAAC,eAAe,IAAC,cAAc,EAAE,EAAE,GAAI;aACpD,CAAC,CAAA;YACF,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAA;QACvG,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;YAClD,MAAM,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAmB,CAAA;YAErE,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;YACvB,MAAM,KAAK,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;YACrB,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;YACtB,MAAM,KAAK,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;YAErB,mBAAmB,CAAC;gBAClB,QAAQ;gBACR,WAAW;gBACX,UAAU,EAAE,CACV,gBAAC,eAAe,IAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAI,CACtG;aACF,CAAC,CAAA;YACF,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAA;YACrG,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,cAAc,CAAsC,CAAA;YAE1F,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACtC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YACtC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YACxC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAE1C,iCAAiC;QACnC,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,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": "9.0.2",
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.33",
42
- "typescript": "^5.9.3",
43
- "vitest": "^4.1.0"
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.1.1",
47
- "nipplejs": "^0.10.2"
46
+ "@furystack/shades": "^13.2.0",
47
+ "nipplejs": "^1.0.1"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=22.0.0"
@@ -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
- // TODO: Check for pointer events
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
- export interface NippleComponentProps {
6
- managerOptions: JoystickManagerOptions
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?: (evt: EventData, data: JoystickOutputData) => void
9
- onEnd?: (evt: EventData, data: JoystickOutputData) => void
10
- onDir?: (evt: EventData, data: JoystickOutputData) => void
11
- onMove?: (evt: EventData, data: JoystickOutputData) => void
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: ReturnType<typeof nipplejs.create> | undefined
26
+ let manager: NippleManager | undefined
21
27
  queueMicrotask(() => {
22
28
  if (!zoneRef.current) return
23
29
  manager = nipplejs.create({