@object-ui/mobile 17.5.0 → 17.7.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.
@@ -21,7 +21,10 @@ export interface UseSpecGestureOptions {
21
21
  onPan?: (direction: string) => void;
22
22
  /** Callback when a rotate gesture is detected (degrees, CW positive) */
23
23
  onRotate?: (rotation: number) => void;
24
- /** Fallback for gestures without a dedicated callback */
24
+ /**
25
+ * Fallback for gestures without a dedicated callback. Its `type` is the
26
+ * DECLARED spec gesture, never the recognizer's own name for the same move.
27
+ */
25
28
  onGesture?: (context: {
26
29
  type: string;
27
30
  direction?: string;
@@ -35,8 +38,10 @@ export interface UseSpecGestureOptions {
35
38
  * {@link GestureType} `useGesture` implements. Note the two sides are
36
39
  * different vocabularies, which is why this map exists at all: the retired
37
40
  * spec's `drag` and `pan` are one recognizer
38
- * (any-direction move past the threshold); `swipe` resolves per configured
39
- * direction, so it maps through {@link SWIPE_DIRECTION_MAP} instead.
41
+ * (any-direction move past the threshold); `swipe` declares a SET of
42
+ * directions, so it recognizes on that same any-direction move and fires only
43
+ * when the DETECTED direction is a member of the declared set — no single
44
+ * recognizer name carries that, which is why its entry below is a placeholder.
40
45
  * Exported for the spec-parity test.
41
46
  *
42
47
  * Before #2942 the hook never read `config.type` at all — it branched on
@@ -47,13 +52,14 @@ export interface UseSpecGestureOptions {
47
52
  export declare const SPEC_GESTURE_TYPE_MAP: Record<string, GestureType>;
48
53
  /**
49
54
  * Spec-aware gesture hook that maps a {@link SpecGestureConfig} — the retired
50
- * `@objectstack/spec` `ui/touch` shape, not this package's own
51
- * `GestureConfig` — onto the existing useGesture hook.
55
+ * `@objectstack/spec` `ui/touch` tuning shape, a different contract from the
56
+ * direction-fused {@link GestureType} vocabulary `useGesture` speaks — onto
57
+ * the existing useGesture hook.
52
58
  *
53
59
  * @example
54
60
  * ```tsx
55
61
  * const ref = useSpecGesture({
56
- * config: { type: 'swipe', enabled: true, swipe: { direction: 'left', threshold: 80 } },
62
+ * config: { type: 'swipe', enabled: true, swipe: { direction: ['left'], threshold: 80 } },
57
63
  * onSwipe: (dir) => console.log('Swiped', dir),
58
64
  * });
59
65
  * return <div ref={ref}>Swipe me</div>;
@@ -1 +1 @@
1
- {"version":3,"file":"useSpecGesture.d.ts","sourceRoot":"","sources":["../src/useSpecGesture.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEvE,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;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAQ7D,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAChE,OAAO,EAAE,qBAAqB,uCAoE/B"}
1
+ {"version":3,"file":"useSpecGesture.d.ts","sourceRoot":"","sources":["../src/useSpecGesture.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEvE,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;;;OAGG;IACH,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;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAQ7D,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EAChE,OAAO,EAAE,qBAAqB,uCAoF/B"}
@@ -5,21 +5,17 @@
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 { useGesture } from './useGesture';
9
- const SWIPE_DIRECTION_MAP = {
10
- left: 'swipe-left',
11
- right: 'swipe-right',
12
- up: 'swipe-up',
13
- down: 'swipe-down',
14
- };
8
+ import { useGesture } from './useGesture.js';
15
9
  /**
16
10
  * `SPEC_GESTURE_TYPES` (the retired `ui/touch` vocabulary, owned by
17
11
  * `@object-ui/types` since objectstack#4988) → the recognizer
18
12
  * {@link GestureType} `useGesture` implements. Note the two sides are
19
13
  * different vocabularies, which is why this map exists at all: the retired
20
14
  * spec's `drag` and `pan` are one recognizer
21
- * (any-direction move past the threshold); `swipe` resolves per configured
22
- * direction, so it maps through {@link SWIPE_DIRECTION_MAP} instead.
15
+ * (any-direction move past the threshold); `swipe` declares a SET of
16
+ * directions, so it recognizes on that same any-direction move and fires only
17
+ * when the DETECTED direction is a member of the declared set — no single
18
+ * recognizer name carries that, which is why its entry below is a placeholder.
23
19
  * Exported for the spec-parity test.
24
20
  *
25
21
  * Before #2942 the hook never read `config.type` at all — it branched on
@@ -28,7 +24,7 @@ const SWIPE_DIRECTION_MAP = {
28
24
  * fell through to the `'tap'` initializer and fired on a tap.
29
25
  */
30
26
  export const SPEC_GESTURE_TYPE_MAP = {
31
- swipe: 'swipe-left', // per-direction; resolved via SWIPE_DIRECTION_MAP
27
+ swipe: 'swipe-left', // placeholder; the hook recognizes any direction and filters by the declared set
32
28
  pinch: 'pinch',
33
29
  long_press: 'long-press',
34
30
  double_tap: 'double-tap',
@@ -38,13 +34,14 @@ export const SPEC_GESTURE_TYPE_MAP = {
38
34
  };
39
35
  /**
40
36
  * Spec-aware gesture hook that maps a {@link SpecGestureConfig} — the retired
41
- * `@objectstack/spec` `ui/touch` shape, not this package's own
42
- * `GestureConfig` — onto the existing useGesture hook.
37
+ * `@objectstack/spec` `ui/touch` tuning shape, a different contract from the
38
+ * direction-fused {@link GestureType} vocabulary `useGesture` speaks — onto
39
+ * the existing useGesture hook.
43
40
  *
44
41
  * @example
45
42
  * ```tsx
46
43
  * const ref = useSpecGesture({
47
- * config: { type: 'swipe', enabled: true, swipe: { direction: 'left', threshold: 80 } },
44
+ * config: { type: 'swipe', enabled: true, swipe: { direction: ['left'], threshold: 80 } },
48
45
  * onSwipe: (dir) => console.log('Swiped', dir),
49
46
  * });
50
47
  * return <div ref={ref}>Swipe me</div>;
@@ -69,15 +66,34 @@ export function useSpecGesture(options) {
69
66
  let threshold;
70
67
  let longPressDuration;
71
68
  let onGesture = () => { };
69
+ // `type` goes LAST in every payload below. The recognizer's own `type`
70
+ // travels inside `ctx` at runtime (`GestureContext.type`), so a `type`
71
+ // written BEFORE the spread is overwritten by it and the callback reports the
72
+ // recognizer instead of the declared spec gesture (objectui#9691). The two
73
+ // are different vocabularies — see `SPEC_GESTURE_TYPE_MAP` — so they agree
74
+ // only by coincidence, arm by arm.
72
75
  const fallback = (ctx) => onAny?.(ctx);
73
76
  switch (declared) {
74
77
  case 'swipe': {
75
- const dir = Array.isArray(config.swipe?.direction)
76
- ? config.swipe?.direction[0]
77
- : config.swipe?.direction;
78
- gestureType = (dir ? SWIPE_DIRECTION_MAP[dir] : undefined) ?? 'swipe-left';
78
+ // `SwipeGestureConfig.direction` is declared as a SET
79
+ // (`SpecSwipeDirection[]`), so recognition is the any-direction move past
80
+ // the threshold and the swipe fires only when the DETECTED direction is a
81
+ // MEMBER of that set. `direction[0]` would honour one element of a declared
82
+ // many; a scalar `direction` is rejected by the declared type and nothing
83
+ // re-admits it here (AGENTS.md #0.1). An empty or absent set declares no
84
+ // direction, so it fires for none.
85
+ const declaredDirections = config.swipe?.direction ?? [];
86
+ gestureType = 'pan';
79
87
  threshold = config.swipe?.threshold;
80
- onGesture = (ctx) => (onSwipe ? onSwipe(ctx.direction ?? dir ?? 'left') : fallback({ type: 'swipe', ...ctx }));
88
+ onGesture = (ctx) => {
89
+ const detected = ctx.direction;
90
+ if (detected === undefined || !declaredDirections.includes(detected))
91
+ return;
92
+ if (onSwipe)
93
+ onSwipe(detected);
94
+ else
95
+ fallback({ ...ctx, type: 'swipe' });
96
+ };
81
97
  break;
82
98
  }
83
99
  case 'long_press':
@@ -87,7 +103,7 @@ export function useSpecGesture(options) {
87
103
  break;
88
104
  case 'pinch':
89
105
  gestureType = 'pinch';
90
- onGesture = (ctx) => (onPinch ? onPinch(ctx.scale ?? 1) : fallback({ type: 'pinch', ...ctx }));
106
+ onGesture = (ctx) => (onPinch ? onPinch(ctx.scale ?? 1) : fallback({ ...ctx, type: 'pinch' }));
91
107
  break;
92
108
  case 'double_tap':
93
109
  gestureType = 'double-tap';
@@ -96,11 +112,11 @@ export function useSpecGesture(options) {
96
112
  case 'pan':
97
113
  case 'drag':
98
114
  gestureType = 'pan';
99
- onGesture = (ctx) => (onPan ? onPan(ctx.direction ?? 'left') : fallback({ type: declared, ...ctx }));
115
+ onGesture = (ctx) => (onPan ? onPan(ctx.direction ?? 'left') : fallback({ ...ctx, type: declared }));
100
116
  break;
101
117
  case 'rotate':
102
118
  gestureType = 'rotate';
103
- onGesture = (ctx) => (onRotate ? onRotate(ctx.rotation ?? 0) : fallback({ type: 'rotate', ...ctx }));
119
+ onGesture = (ctx) => (onRotate ? onRotate(ctx.rotation ?? 0) : fallback({ ...ctx, type: 'rotate' }));
104
120
  break;
105
121
  default:
106
122
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/mobile",
3
- "version": "17.5.0",
3
+ "version": "17.7.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,10 +30,10 @@
30
30
  "react": "^18.0.0 || ^19.0.0"
31
31
  },
32
32
  "dependencies": {
33
- "@object-ui/types": "17.5.0"
33
+ "@object-ui/types": "17.7.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@objectstack/spec": "^17.0.0-rc.6",
36
+ "@objectstack/spec": "^17.0.0",
37
37
  "@types/react": "19.2.18",
38
38
  "react": "19.2.8",
39
39
  "typescript": "^6.0.3",
@@ -61,9 +61,9 @@
61
61
  "access": "public"
62
62
  },
63
63
  "scripts": {
64
- "build": "tsc",
65
- "clean": "rm -rf dist",
66
- "test": "vitest run",
64
+ "build": "tsc && node ../../scripts/check-dist-completeness.mjs",
65
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
66
+ "test": "vitest run --root ../.. packages/mobile/",
67
67
  "type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
68
68
  "lint": "eslint ."
69
69
  }
@@ -1,71 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
- import type { BreakpointName, SpecResponsiveConfig } from '@object-ui/types';
9
- /**
10
- * The responsive layout config this hook consumes — re-exported, not re-declared
11
- * (objectui#4598).
12
- *
13
- * This used to be a hand-written interface over the same four keys, under a
14
- * comment asserting it mirrored the schema. The assertion was true on the day it
15
- * was written and maintained by nobody after that: the interface named the
16
- * schema without ever referring to it, so a key added or retired upstream would
17
- * have moved the two apart in silence. `ViewNavigationConfig` (objectui#4588)
18
- * read the same way until it had drifted on `mode`.
19
- *
20
- * `@object-ui/types` — already this package's only runtime dependency — publishes
21
- * the schema's own type at `index.ts` under this exact name, imported from
22
- * `@objectstack/spec/ui` rather than copied. Re-exporting it costs no new
23
- * dependency edge and leaves the published name unchanged, so the four keys are
24
- * now whatever the schema says they are rather than whatever this file last
25
- * remembered. `@object-ui/core`'s `ResponsiveProtocol` already binds through the
26
- * same re-export.
27
- *
28
- * `responsive-config-spec-parity.test.ts` pins the chain to the schema itself,
29
- * because the one link this file cannot see is `@object-ui/types` re-growing a
30
- * hand copy of its own.
31
- *
32
- * @example
33
- * ```ts
34
- * const config: SpecResponsiveConfig = {
35
- * columns: { xs: 12, sm: 6, lg: 4 },
36
- * hiddenOn: ['xs'],
37
- * order: { xs: 2, lg: 1 },
38
- * };
39
- * ```
40
- */
41
- export type { SpecResponsiveConfig };
42
- /**
43
- * Resolved responsive state from a SpecResponsiveConfig.
44
- */
45
- export interface ResolvedResponsiveState {
46
- /** Whether the component is hidden at the current breakpoint */
47
- hidden: boolean;
48
- /** Resolved column count for the current breakpoint */
49
- columns: number | undefined;
50
- /** Resolved display order for the current breakpoint */
51
- order: number | undefined;
52
- /** Current active breakpoint name */
53
- breakpoint: BreakpointName;
54
- }
55
- /**
56
- * Hook that consumes @objectstack/spec ResponsiveConfigSchema and
57
- * resolves breakpoint-aware layout state.
58
- *
59
- * @example
60
- * ```tsx
61
- * const { hidden, columns, order } = useResponsiveConfig({
62
- * columns: { xs: 12, sm: 6, lg: 4 },
63
- * hiddenOn: ['xs'],
64
- * order: { xs: 2, lg: 1 },
65
- * });
66
- * if (hidden) return null;
67
- * return <div style={{ gridColumn: `span ${columns}`, order }}>...</div>;
68
- * ```
69
- */
70
- export declare function useResponsiveConfig(config?: SpecResponsiveConfig): ResolvedResponsiveState;
71
- //# sourceMappingURL=useResponsiveConfig.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useResponsiveConfig.d.ts","sourceRoot":"","sources":["../src/useResponsiveConfig.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,YAAY,EAAE,oBAAoB,EAAE,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,gEAAgE;IAChE,MAAM,EAAE,OAAO,CAAC;IAChB,uDAAuD;IACvD,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,wDAAwD;IACxD,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,qCAAqC;IACrC,UAAU,EAAE,cAAc,CAAC;CAC5B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,oBAAoB,GAAG,uBAAuB,CAoB1F"}
@@ -1,41 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
- import { useMemo } from 'react';
9
- import { useBreakpoint } from './useBreakpoint';
10
- import { resolveResponsiveValue } from './breakpoints';
11
- /**
12
- * Hook that consumes @objectstack/spec ResponsiveConfigSchema and
13
- * resolves breakpoint-aware layout state.
14
- *
15
- * @example
16
- * ```tsx
17
- * const { hidden, columns, order } = useResponsiveConfig({
18
- * columns: { xs: 12, sm: 6, lg: 4 },
19
- * hiddenOn: ['xs'],
20
- * order: { xs: 2, lg: 1 },
21
- * });
22
- * if (hidden) return null;
23
- * return <div style={{ gridColumn: `span ${columns}`, order }}>...</div>;
24
- * ```
25
- */
26
- export function useResponsiveConfig(config) {
27
- const { breakpoint } = useBreakpoint();
28
- return useMemo(() => {
29
- if (!config) {
30
- return { hidden: false, columns: undefined, order: undefined, breakpoint };
31
- }
32
- const hidden = config.hiddenOn?.includes(breakpoint) ?? false;
33
- const columns = config.columns
34
- ? resolveResponsiveValue(config.columns, breakpoint)
35
- : undefined;
36
- const order = config.order
37
- ? resolveResponsiveValue(config.order, breakpoint)
38
- : undefined;
39
- return { hidden, columns, order, breakpoint };
40
- }, [config, breakpoint]);
41
- }