@object-ui/mobile 16.1.0 → 17.2.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 +127 -0
- package/dist/MobileProvider.d.ts +3 -3
- package/dist/MobileProvider.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/useGesture.d.ts +2 -2
- package/dist/useGesture.d.ts.map +1 -1
- package/dist/useGesture.js +57 -3
- package/dist/useSpecGesture.d.ts +27 -1
- package/dist/useSpecGesture.d.ts.map +1 -1
- package/dist/useSpecGesture.js +69 -17
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,132 @@
|
|
|
1
1
|
# @object-ui/mobile
|
|
2
2
|
|
|
3
|
+
## 17.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 726b89c: `@object-ui/types` stops declaring sixteen symbols under names `@objectstack/spec` owns (objectui#3156, objectstack#4115).
|
|
8
|
+
|
|
9
|
+
Seven are now **derived** from the spec, nine are **renamed** to the local
|
|
10
|
+
dialect they always were. Both halves remove the same hazard: a local
|
|
11
|
+
declaration under a spec export's name reads as the spec's own definition to
|
|
12
|
+
the next reader, so a copy that is merely _correct today_ is a planted premise
|
|
13
|
+
tomorrow.
|
|
14
|
+
|
|
15
|
+
**Derived** — the spec now supplies the keys, by reference:
|
|
16
|
+
|
|
17
|
+
| symbol | derivation |
|
|
18
|
+
| :----------------------- | :-------------------------------------------------------------------------------- |
|
|
19
|
+
| `ActionParam` | `z.input<typeof ActionParamSchema>`, `type` widened to the local legacy spellings |
|
|
20
|
+
| `CreateExportJobRequest` | `Omit<CreateExportJobInput, 'object'>` (`object` is the method argument) |
|
|
21
|
+
| `CreateExportJobResult` | re-export from `@objectstack/spec/contracts` |
|
|
22
|
+
| `ImportRowResult` | re-export from `@objectstack/spec/api` |
|
|
23
|
+
| `NavigationArea` | spec keys, with `navigation` / `visible` pinned locally |
|
|
24
|
+
| `NavigationAreaSchema` | `specFieldsExcept(NavigationAreaSchema.shape, …)` |
|
|
25
|
+
| `Theme` | re-export of the spec's `ThemeInput` (the authoring shape) |
|
|
26
|
+
| `ExportJobFormat` | re-export of the spec's `ExportFormat` |
|
|
27
|
+
|
|
28
|
+
Four of these close real gaps rather than tidy names. `ActionParam` never
|
|
29
|
+
declared `reference` — the key `resolveActionParams()` actually reads for an
|
|
30
|
+
inline lookup target — nor `defaultFromRow`, which the metadata designer's own
|
|
31
|
+
inspector writes; it also narrowed `visible` to a bare string although the
|
|
32
|
+
resolver has always accepted the `{ dialect, source }` envelope too.
|
|
33
|
+
`CreateExportJobResult.createdAt` and `ImportRowResult.action` were optional
|
|
34
|
+
here and required by the server, leaving every consumer a branch that could
|
|
35
|
+
never run. And `NavigationArea`'s `id` now carries the spec's own length rule
|
|
36
|
+
instead of accepting any string.
|
|
37
|
+
|
|
38
|
+
**Renamed** — same word, different concept:
|
|
39
|
+
|
|
40
|
+
| was | now | why |
|
|
41
|
+
| :----------------- | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `FileMetadata` | `UploadedFileMetadata` | field-VALUE payload (`url`, `original_name`), not the storage file record |
|
|
43
|
+
| `GestureType` | `TouchGestureType` | direction-fused (`swipe-left`), not the spec's type+direction pair |
|
|
44
|
+
| `GestureConfig` | `TouchGestureConfig` | gesture→`action` binding, not per-gesture tuning |
|
|
45
|
+
| `OfflineConfig` | `PWAOfflineConfig` | service-worker route caching, not the offline data/sync model |
|
|
46
|
+
| `PageRegion` | `PageNodeRegion` | region of the renderer page NODE, holding `SchemaNode`s |
|
|
47
|
+
| `PageRegionSchema` | `PageNodeRegionSchema` | zod twin of the above |
|
|
48
|
+
| `ResponsiveConfig` | `MobileResponsiveConfig` | mobile box config, not the spec's SDUI grid contract |
|
|
49
|
+
| `WidgetManifest` | `RuntimeWidgetManifest` | SDUI component manifest, not the field-widget plugin manifest |
|
|
50
|
+
| `WidgetSource` | `RuntimeWidgetSource` | `module`/`inline`/`registry` loader union — and its `inline` carries a resolved component where the spec's carries source code |
|
|
51
|
+
|
|
52
|
+
**Migration**: the old names are gone, not deprecated — an alias would preserve
|
|
53
|
+
exactly the ambiguity being removed. Import the new name; nothing about the
|
|
54
|
+
shapes changed. `@object-ui/types` already re-exports the spec's own
|
|
55
|
+
`SpecResponsiveConfig`, and `@object-ui/react`'s `useOffline` config remains the
|
|
56
|
+
spec-shaped `OfflineConfig`, so both concepts stay reachable under
|
|
57
|
+
distinguishable names.
|
|
58
|
+
|
|
59
|
+
Each rename carries a bidirectional tripwire
|
|
60
|
+
(`packages/types/src/__tests__/page-nav-misc-spec-parity.test.ts`): it fails if
|
|
61
|
+
the spec ever claims the new name, and also if the spec retires the old one —
|
|
62
|
+
at which point the natural name can be taken back rather than the workaround
|
|
63
|
+
outliving its reason.
|
|
64
|
+
|
|
65
|
+
### Patch Changes
|
|
66
|
+
|
|
67
|
+
- Updated dependencies [4ae0ac4]
|
|
68
|
+
- Updated dependencies [696e3c1]
|
|
69
|
+
- Updated dependencies [4bf612c]
|
|
70
|
+
- Updated dependencies [cb82705]
|
|
71
|
+
- Updated dependencies [f572849]
|
|
72
|
+
- Updated dependencies [444457c]
|
|
73
|
+
- Updated dependencies [022e4c3]
|
|
74
|
+
- Updated dependencies [009e25d]
|
|
75
|
+
- Updated dependencies [726b89c]
|
|
76
|
+
- @object-ui/types@17.2.0
|
|
77
|
+
|
|
78
|
+
## 17.1.0
|
|
79
|
+
|
|
80
|
+
### Patch Changes
|
|
81
|
+
|
|
82
|
+
- Updated dependencies [9e7349e]
|
|
83
|
+
- Updated dependencies [8864971]
|
|
84
|
+
- Updated dependencies [b41f401]
|
|
85
|
+
- Updated dependencies [19e9fa0]
|
|
86
|
+
- Updated dependencies [38ca8be]
|
|
87
|
+
- Updated dependencies [4952edf]
|
|
88
|
+
- Updated dependencies [7f0252e]
|
|
89
|
+
- Updated dependencies [7639a61]
|
|
90
|
+
- Updated dependencies [94e63ef]
|
|
91
|
+
- Updated dependencies [c4db402]
|
|
92
|
+
- Updated dependencies [5319bf1]
|
|
93
|
+
- Updated dependencies [49e5671]
|
|
94
|
+
- Updated dependencies [b5b97e2]
|
|
95
|
+
- Updated dependencies [f59f2c1]
|
|
96
|
+
- Updated dependencies [4874117]
|
|
97
|
+
- Updated dependencies [ce08d55]
|
|
98
|
+
- Updated dependencies [2374a49]
|
|
99
|
+
- Updated dependencies [ea7f477]
|
|
100
|
+
- Updated dependencies [7f23cd0]
|
|
101
|
+
- Updated dependencies [24e0e0a]
|
|
102
|
+
- Updated dependencies [3a6cf24]
|
|
103
|
+
- Updated dependencies [aa35561]
|
|
104
|
+
- Updated dependencies [03bd53b]
|
|
105
|
+
- Updated dependencies [3c1f321]
|
|
106
|
+
- Updated dependencies [a045a32]
|
|
107
|
+
- Updated dependencies [912496d]
|
|
108
|
+
- Updated dependencies [9867281]
|
|
109
|
+
- @object-ui/types@17.1.0
|
|
110
|
+
|
|
111
|
+
## 17.0.0
|
|
112
|
+
|
|
113
|
+
### Patch Changes
|
|
114
|
+
|
|
115
|
+
- Updated dependencies [1767124]
|
|
116
|
+
- Updated dependencies [8ecf5a6]
|
|
117
|
+
- Updated dependencies [dfd3705]
|
|
118
|
+
- Updated dependencies [6dee2cb]
|
|
119
|
+
- Updated dependencies [c7cff19]
|
|
120
|
+
- Updated dependencies [cd09a7b]
|
|
121
|
+
- Updated dependencies [f1abf0e]
|
|
122
|
+
- Updated dependencies [f05b84e]
|
|
123
|
+
- Updated dependencies [662bdf9]
|
|
124
|
+
- Updated dependencies [059a052]
|
|
125
|
+
- Updated dependencies [53642d4]
|
|
126
|
+
- Updated dependencies [8aae006]
|
|
127
|
+
- Updated dependencies [d147a13]
|
|
128
|
+
- @object-ui/types@17.0.0
|
|
129
|
+
|
|
3
130
|
## 16.1.0
|
|
4
131
|
|
|
5
132
|
### Patch Changes
|
package/dist/MobileProvider.d.ts
CHANGED
|
@@ -6,19 +6,19 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
import React from 'react';
|
|
9
|
-
import type { PWAConfig,
|
|
9
|
+
import type { PWAConfig, PWAOfflineConfig } from '@object-ui/types';
|
|
10
10
|
import { type BreakpointState } from './useBreakpoint';
|
|
11
11
|
export interface MobileContextValue extends BreakpointState {
|
|
12
12
|
/** PWA configuration */
|
|
13
13
|
pwa?: PWAConfig;
|
|
14
14
|
/** Offline configuration */
|
|
15
|
-
offline?:
|
|
15
|
+
offline?: PWAOfflineConfig;
|
|
16
16
|
}
|
|
17
17
|
export interface MobileProviderProps {
|
|
18
18
|
/** PWA configuration */
|
|
19
19
|
pwa?: PWAConfig;
|
|
20
20
|
/** Offline configuration */
|
|
21
|
-
offline?:
|
|
21
|
+
offline?: PWAOfflineConfig;
|
|
22
22
|
/** Children */
|
|
23
23
|
children: React.ReactNode;
|
|
24
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MobileProvider.d.ts","sourceRoot":"","sources":["../src/MobileProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA6C,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"MobileProvider.d.ts","sourceRoot":"","sources":["../src/MobileProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA6C,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEtE,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD,wBAAwB;IACxB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAKD,MAAM,WAAW,mBAAmB;IAClC,wBAAwB;IACxB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,eAAe;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,mBAAmB,qBAa7E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,kBAAkB,GAAG,IAAI,CAE5D"}
|
package/dist/index.d.ts
CHANGED
|
@@ -33,5 +33,5 @@ export { createOfflineDataSource, type OfflineDataSource, type OfflineDataSource
|
|
|
33
33
|
export { useOfflineSync, type OfflineSyncState } from './useOfflineSync';
|
|
34
34
|
export { getServiceWorkerSource, requestBackgroundSync, type ServiceWorkerSourceOptions, } from './serviceWorkerSource';
|
|
35
35
|
export { BREAKPOINTS, resolveResponsiveValue } from './breakpoints';
|
|
36
|
-
export type { BreakpointName, ResponsiveValue,
|
|
36
|
+
export type { BreakpointName, ResponsiveValue, MobileResponsiveConfig, MobileOverrides, PWAConfig, PWAIcon, FetchCacheStrategy, PWAOfflineConfig, OfflineRoute, TouchGestureType, TouchGestureConfig, GestureContext, MobileComponentConfig, SpecGestureConfig, SwipeGestureConfig, PinchGestureConfig, LongPressGestureConfig, TouchInteraction, TouchTargetConfig, } from '@object-ui/types';
|
|
37
37
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,KAAK,oBAAoB,EAAE,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACrH,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACtG,OAAO,EAAE,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,qBAAqB,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,GACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAGpE,YAAY,EACV,cAAc,EACd,eAAe,EACf,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,KAAK,oBAAoB,EAAE,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACrH,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,KAAK,qBAAqB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACtG,OAAO,EAAE,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,qBAAqB,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,GACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAGpE,YAAY,EACV,cAAc,EACd,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,SAAS,EACT,OAAO,EACP,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC"}
|
package/dist/useGesture.d.ts
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
-
import type {
|
|
8
|
+
import type { TouchGestureType, GestureContext } from '@object-ui/types';
|
|
9
9
|
export interface UseGestureOptions {
|
|
10
10
|
/** Gesture type to detect */
|
|
11
|
-
type:
|
|
11
|
+
type: TouchGestureType;
|
|
12
12
|
/** Callback when gesture is detected */
|
|
13
13
|
onGesture: (context: GestureContext) => void;
|
|
14
14
|
/** Minimum distance for swipe detection (pixels) */
|
package/dist/useGesture.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGesture.d.ts","sourceRoot":"","sources":["../src/useGesture.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"useGesture.d.ts","sourceRoot":"","sources":["../src/useGesture.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEzE,MAAM,WAAW,iBAAiB;IAChC,6BAA6B;IAC7B,IAAI,EAAE,gBAAgB,CAAC;IACvB,wCAAwC;IACxC,SAAS,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,2CAA2C;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAC5D,OAAO,EAAE,iBAAiB,uCAqK3B"}
|
package/dist/useGesture.js
CHANGED
|
@@ -23,12 +23,27 @@ export function useGesture(options) {
|
|
|
23
23
|
const ref = useRef(null);
|
|
24
24
|
const startRef = useRef(null);
|
|
25
25
|
const longPressTimerRef = useRef(undefined);
|
|
26
|
+
// Real double-tap needs the PREVIOUS tap's timestamp — the old
|
|
27
|
+
// implementation fired on every single qualifying tap (#2942).
|
|
28
|
+
const lastTapRef = useRef(0);
|
|
29
|
+
// Two-touch tracking for pinch/rotate: initial + latest distance/angle
|
|
30
|
+
// between the two touch points.
|
|
31
|
+
const twoTouchRef = useRef(null);
|
|
26
32
|
const { type, onGesture, threshold = 50, longPressDuration = 500, enabled = true } = options;
|
|
27
33
|
const handleTouchStart = useCallback((e) => {
|
|
28
34
|
if (!enabled)
|
|
29
35
|
return;
|
|
30
36
|
const touch = e.touches[0];
|
|
31
37
|
startRef.current = { x: touch.clientX, y: touch.clientY, time: Date.now() };
|
|
38
|
+
// Pinch/rotate baseline — distance and angle between the two touches.
|
|
39
|
+
if ((type === 'pinch' || type === 'rotate') && e.touches.length >= 2) {
|
|
40
|
+
const [t1, t2] = [e.touches[0], e.touches[1]];
|
|
41
|
+
const dx2 = t2.clientX - t1.clientX;
|
|
42
|
+
const dy2 = t2.clientY - t1.clientY;
|
|
43
|
+
const d = Math.hypot(dx2, dy2);
|
|
44
|
+
const a = (Math.atan2(dy2, dx2) * 180) / Math.PI;
|
|
45
|
+
twoTouchRef.current = { d0: d, a0: a, d1: d, a1: a };
|
|
46
|
+
}
|
|
32
47
|
if (type === 'long-press') {
|
|
33
48
|
longPressTimerRef.current = setTimeout(() => {
|
|
34
49
|
if (startRef.current) {
|
|
@@ -62,14 +77,45 @@ export function useGesture(options) {
|
|
|
62
77
|
duration,
|
|
63
78
|
velocity,
|
|
64
79
|
};
|
|
80
|
+
// Pinch / rotate resolve from the two-touch delta, not the single-touch
|
|
81
|
+
// path below. Fired once on release, like the other discrete gestures.
|
|
82
|
+
if (type === 'pinch' || type === 'rotate') {
|
|
83
|
+
const t2 = twoTouchRef.current;
|
|
84
|
+
twoTouchRef.current = null;
|
|
85
|
+
startRef.current = null;
|
|
86
|
+
if (!t2 || t2.d0 <= 0)
|
|
87
|
+
return;
|
|
88
|
+
const scale = t2.d1 / t2.d0;
|
|
89
|
+
// Normalize the angle delta into (-180, 180].
|
|
90
|
+
let rotation = t2.a1 - t2.a0;
|
|
91
|
+
if (rotation > 180)
|
|
92
|
+
rotation -= 360;
|
|
93
|
+
if (rotation <= -180)
|
|
94
|
+
rotation += 360;
|
|
95
|
+
if (type === 'pinch' && Math.abs(scale - 1) >= 0.1) {
|
|
96
|
+
onGesture({ ...context, scale });
|
|
97
|
+
}
|
|
98
|
+
else if (type === 'rotate' && Math.abs(rotation) >= 15) {
|
|
99
|
+
onGesture({ ...context, rotation });
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
65
103
|
// Detect gesture type
|
|
66
104
|
if (type === 'tap' && distance < 10 && duration < 300) {
|
|
67
105
|
onGesture(context);
|
|
68
106
|
}
|
|
69
107
|
else if (type === 'double-tap') {
|
|
70
|
-
//
|
|
108
|
+
// Two qualifying taps within 350ms — a single tap must NOT fire
|
|
109
|
+
// (it used to, collapsing double-tap into tap, #2942).
|
|
71
110
|
if (distance < 10 && duration < 300) {
|
|
72
|
-
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
if (now - lastTapRef.current <= 350) {
|
|
113
|
+
lastTapRef.current = 0;
|
|
114
|
+
onGesture(context);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
lastTapRef.current = now;
|
|
118
|
+
}
|
|
73
119
|
}
|
|
74
120
|
}
|
|
75
121
|
else if (distance >= threshold) {
|
|
@@ -93,11 +139,19 @@ export function useGesture(options) {
|
|
|
93
139
|
}
|
|
94
140
|
startRef.current = null;
|
|
95
141
|
}, [type, onGesture, threshold, enabled]);
|
|
96
|
-
const handleTouchMove = useCallback(() => {
|
|
142
|
+
const handleTouchMove = useCallback((e) => {
|
|
97
143
|
// Cancel long-press if finger moves
|
|
98
144
|
if (type === 'long-press') {
|
|
99
145
|
clearTimeout(longPressTimerRef.current);
|
|
100
146
|
}
|
|
147
|
+
// Track the latest two-touch distance/angle for pinch/rotate.
|
|
148
|
+
if ((type === 'pinch' || type === 'rotate') && twoTouchRef.current && e.touches.length >= 2) {
|
|
149
|
+
const [t1, t2] = [e.touches[0], e.touches[1]];
|
|
150
|
+
const dx2 = t2.clientX - t1.clientX;
|
|
151
|
+
const dy2 = t2.clientY - t1.clientY;
|
|
152
|
+
twoTouchRef.current.d1 = Math.hypot(dx2, dy2);
|
|
153
|
+
twoTouchRef.current.a1 = (Math.atan2(dy2, dx2) * 180) / Math.PI;
|
|
154
|
+
}
|
|
101
155
|
}, [type]);
|
|
102
156
|
useEffect(() => {
|
|
103
157
|
const el = ref.current;
|
package/dist/useSpecGesture.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
-
import type { SpecGestureConfig } from '@object-ui/types';
|
|
8
|
+
import type { TouchGestureType, SpecGestureConfig } from '@object-ui/types';
|
|
9
9
|
export interface UseSpecGestureOptions {
|
|
10
10
|
/** Spec gesture configuration */
|
|
11
11
|
config: SpecGestureConfig;
|
|
@@ -15,7 +15,33 @@ export interface UseSpecGestureOptions {
|
|
|
15
15
|
onPinch?: (scale: number) => void;
|
|
16
16
|
/** Callback when a long-press gesture is detected */
|
|
17
17
|
onLongPress?: () => void;
|
|
18
|
+
/** Callback when a double-tap gesture is detected */
|
|
19
|
+
onDoubleTap?: () => void;
|
|
20
|
+
/** Callback when a pan/drag gesture is detected (any-direction move) */
|
|
21
|
+
onPan?: (direction: string) => void;
|
|
22
|
+
/** Callback when a rotate gesture is detected (degrees, CW positive) */
|
|
23
|
+
onRotate?: (rotation: number) => void;
|
|
24
|
+
/** Fallback for gestures without a dedicated callback */
|
|
25
|
+
onGesture?: (context: {
|
|
26
|
+
type: string;
|
|
27
|
+
direction?: string;
|
|
28
|
+
scale?: number;
|
|
29
|
+
rotation?: number;
|
|
30
|
+
}) => void;
|
|
18
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Spec `GestureTypeSchema` (`ui/touch.zod.ts`) → the recognizer type
|
|
34
|
+
* `useGesture` implements. The spec's `drag` and `pan` are one recognizer
|
|
35
|
+
* (any-direction move past the threshold); `swipe` resolves per configured
|
|
36
|
+
* direction, so it maps through {@link SWIPE_DIRECTION_MAP} instead.
|
|
37
|
+
* Exported for the spec-parity test.
|
|
38
|
+
*
|
|
39
|
+
* Before #2942 the hook never read `config.type` at all — it branched on
|
|
40
|
+
* which SUB-OBJECT was present (`swipe` / `pinch` / `longPress`), so
|
|
41
|
+
* `pan` / `drag` / `rotate` / `double_tap` (types with no sub-object) all
|
|
42
|
+
* fell through to the `'tap'` initializer and fired on a tap.
|
|
43
|
+
*/
|
|
44
|
+
export declare const SPEC_GESTURE_TYPE_MAP: Record<string, TouchGestureType>;
|
|
19
45
|
/**
|
|
20
46
|
* Spec-aware gesture hook that maps an @objectstack/spec GestureConfig
|
|
21
47
|
* to the existing useGesture hook.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSpecGesture.d.ts","sourceRoot":"","sources":["../src/useSpecGesture.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"useSpecGesture.d.ts","sourceRoot":"","sources":["../src/useSpecGesture.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE5E,MAAM,WAAW,qBAAqB;IACpC,iCAAiC;IACjC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,gDAAgD;IAChD,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,gDAAgD;IAChD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,wEAAwE;IACxE,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,yDAAyD;IACzD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACxG;AASD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAQlE,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAChE,OAAO,EAAE,qBAAqB,uCAoE/B"}
|
package/dist/useSpecGesture.js
CHANGED
|
@@ -12,6 +12,27 @@ const SWIPE_DIRECTION_MAP = {
|
|
|
12
12
|
up: 'swipe-up',
|
|
13
13
|
down: 'swipe-down',
|
|
14
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Spec `GestureTypeSchema` (`ui/touch.zod.ts`) → the recognizer type
|
|
17
|
+
* `useGesture` implements. The spec's `drag` and `pan` are one recognizer
|
|
18
|
+
* (any-direction move past the threshold); `swipe` resolves per configured
|
|
19
|
+
* direction, so it maps through {@link SWIPE_DIRECTION_MAP} instead.
|
|
20
|
+
* Exported for the spec-parity test.
|
|
21
|
+
*
|
|
22
|
+
* Before #2942 the hook never read `config.type` at all — it branched on
|
|
23
|
+
* which SUB-OBJECT was present (`swipe` / `pinch` / `longPress`), so
|
|
24
|
+
* `pan` / `drag` / `rotate` / `double_tap` (types with no sub-object) all
|
|
25
|
+
* fell through to the `'tap'` initializer and fired on a tap.
|
|
26
|
+
*/
|
|
27
|
+
export const SPEC_GESTURE_TYPE_MAP = {
|
|
28
|
+
swipe: 'swipe-left', // per-direction; resolved via SWIPE_DIRECTION_MAP
|
|
29
|
+
pinch: 'pinch',
|
|
30
|
+
long_press: 'long-press',
|
|
31
|
+
double_tap: 'double-tap',
|
|
32
|
+
drag: 'pan',
|
|
33
|
+
pan: 'pan',
|
|
34
|
+
rotate: 'rotate',
|
|
35
|
+
};
|
|
15
36
|
/**
|
|
16
37
|
* Spec-aware gesture hook that maps an @objectstack/spec GestureConfig
|
|
17
38
|
* to the existing useGesture hook.
|
|
@@ -26,28 +47,59 @@ const SWIPE_DIRECTION_MAP = {
|
|
|
26
47
|
* ```
|
|
27
48
|
*/
|
|
28
49
|
export function useSpecGesture(options) {
|
|
29
|
-
const { config, onSwipe, onPinch, onLongPress } = options;
|
|
50
|
+
const { config, onSwipe, onPinch, onLongPress, onDoubleTap, onPan, onRotate, onGesture: onAny } = options;
|
|
30
51
|
const enabled = config.enabled ?? true;
|
|
52
|
+
// The DECLARED type drives recognition (#2942) — `config.type` is required
|
|
53
|
+
// by the spec's `GestureConfigSchema`. The sub-object presence checks below
|
|
54
|
+
// only back-fill legacy configs that predate the type field.
|
|
55
|
+
const declared = typeof config.type === 'string'
|
|
56
|
+
? config.type
|
|
57
|
+
: config.swipe
|
|
58
|
+
? 'swipe'
|
|
59
|
+
: config.longPress
|
|
60
|
+
? 'long_press'
|
|
61
|
+
: config.pinch
|
|
62
|
+
? 'pinch'
|
|
63
|
+
: undefined;
|
|
31
64
|
let gestureType = 'tap';
|
|
32
65
|
let threshold;
|
|
33
66
|
let longPressDuration;
|
|
34
67
|
let onGesture = () => { };
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
68
|
+
const fallback = (ctx) => onAny?.(ctx);
|
|
69
|
+
switch (declared) {
|
|
70
|
+
case 'swipe': {
|
|
71
|
+
const dir = Array.isArray(config.swipe?.direction)
|
|
72
|
+
? config.swipe?.direction[0]
|
|
73
|
+
: config.swipe?.direction;
|
|
74
|
+
gestureType = (dir ? SWIPE_DIRECTION_MAP[dir] : undefined) ?? 'swipe-left';
|
|
75
|
+
threshold = config.swipe?.threshold;
|
|
76
|
+
onGesture = (ctx) => (onSwipe ? onSwipe(ctx.direction ?? dir ?? 'left') : fallback({ type: 'swipe', ...ctx }));
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case 'long_press':
|
|
80
|
+
gestureType = 'long-press';
|
|
81
|
+
longPressDuration = config.longPress?.duration;
|
|
82
|
+
onGesture = () => (onLongPress ? onLongPress() : fallback({ type: 'long_press' }));
|
|
83
|
+
break;
|
|
84
|
+
case 'pinch':
|
|
85
|
+
gestureType = 'pinch';
|
|
86
|
+
onGesture = (ctx) => (onPinch ? onPinch(ctx.scale ?? 1) : fallback({ type: 'pinch', ...ctx }));
|
|
87
|
+
break;
|
|
88
|
+
case 'double_tap':
|
|
89
|
+
gestureType = 'double-tap';
|
|
90
|
+
onGesture = () => (onDoubleTap ? onDoubleTap() : fallback({ type: 'double_tap' }));
|
|
91
|
+
break;
|
|
92
|
+
case 'pan':
|
|
93
|
+
case 'drag':
|
|
94
|
+
gestureType = 'pan';
|
|
95
|
+
onGesture = (ctx) => (onPan ? onPan(ctx.direction ?? 'left') : fallback({ type: declared, ...ctx }));
|
|
96
|
+
break;
|
|
97
|
+
case 'rotate':
|
|
98
|
+
gestureType = 'rotate';
|
|
99
|
+
onGesture = (ctx) => (onRotate ? onRotate(ctx.rotation ?? 0) : fallback({ type: 'rotate', ...ctx }));
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
break;
|
|
51
103
|
}
|
|
52
104
|
return useGesture({
|
|
53
105
|
type: gestureType,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/mobile",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Mobile optimization for Object UI with responsive components, PWA support, and touch gesture handling.",
|
|
@@ -30,11 +30,12 @@
|
|
|
30
30
|
"react": "^18.0.0 || ^19.0.0"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@object-ui/types": "
|
|
33
|
+
"@object-ui/types": "17.2.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
+
"@objectstack/spec": "^17.0.0-rc.1",
|
|
36
37
|
"@types/react": "19.2.17",
|
|
37
|
-
"react": "19.2.
|
|
38
|
+
"react": "19.2.8",
|
|
38
39
|
"typescript": "^6.0.3",
|
|
39
40
|
"vitest": "^4.1.10"
|
|
40
41
|
},
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"build": "tsc",
|
|
64
65
|
"clean": "rm -rf dist",
|
|
65
66
|
"test": "vitest run",
|
|
66
|
-
"type-check": "tsc --noEmit",
|
|
67
|
+
"type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
67
68
|
"lint": "eslint ."
|
|
68
69
|
}
|
|
69
70
|
}
|