@astryxdesign/core 0.7.8-canary.fbb81c6 → 0.7.8

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/dist/ClickableCard/ClickableCard.d.ts +1 -1
  3. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  4. package/dist/ClickableCard/ClickableCard.js +1 -5
  5. package/dist/DateInput/PickerWheel.d.ts.map +1 -1
  6. package/dist/DateInput/PickerWheel.js +1 -7
  7. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -9
  8. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  9. package/dist/DropdownMenu/DropdownMenu.js +4 -9
  10. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  11. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +2 -3
  12. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  13. package/dist/DropdownMenu/DropdownMenuItem.js +2 -3
  14. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  15. package/dist/DropdownMenu/DropdownMenuRadioItem.js +2 -3
  16. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  17. package/dist/DropdownMenu/DropdownMenuSubMenu.js +2 -3
  18. package/dist/Item/Item.d.ts.map +1 -1
  19. package/dist/Item/Item.js +1 -6
  20. package/dist/Layer/preserveAnimations.d.ts +5 -1
  21. package/dist/Layer/preserveAnimations.d.ts.map +1 -1
  22. package/dist/Layer/preserveAnimations.js +214 -12
  23. package/dist/MoreMenu/MoreMenu.d.ts +3 -5
  24. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  25. package/dist/SelectableCard/SelectableCard.d.ts +1 -1
  26. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  27. package/dist/SelectableCard/SelectableCard.js +1 -5
  28. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  29. package/dist/TreeList/TreeListItem.js +1 -3
  30. package/dist/astryx.css +2 -6
  31. package/dist/hooks/menuPressGesture.d.ts +21 -132
  32. package/dist/hooks/menuPressGesture.d.ts.map +1 -1
  33. package/dist/hooks/menuPressGesture.js +30 -155
  34. package/dist/hooks/useClickableContainer.d.ts +5 -19
  35. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  36. package/dist/hooks/useClickableContainer.js +5 -28
  37. package/dist/hooks/useMenuPress.d.ts +1 -34
  38. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  39. package/dist/hooks/useMenuPress.js +5 -93
  40. package/dist/motion/transientCopy.d.ts +2 -4
  41. package/dist/motion/transientCopy.d.ts.map +1 -1
  42. package/dist/motion/transientCopy.js +5 -34
  43. package/dist/motion/uikit/gestures.d.ts +0 -41
  44. package/dist/motion/uikit/gestures.d.ts.map +1 -1
  45. package/dist/motion/uikit/gestures.js +0 -25
  46. package/dist/motion/uikit/index.d.ts +1 -1
  47. package/dist/motion/uikit/index.d.ts.map +1 -1
  48. package/dist/motion/uikit/index.js +1 -1
  49. package/dist/utils/pressFeedback.d.ts +0 -7
  50. package/dist/utils/pressFeedback.d.ts.map +1 -1
  51. package/dist/utils/pressFeedback.js +1 -16
  52. package/package.json +5 -5
  53. package/src/ClickableCard/ClickableCard.tsx +1 -8
  54. package/src/DateInput/PickerWheel.tsx +2 -7
  55. package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -1
  56. package/src/DropdownMenu/DropdownMenu.tsx +5 -29
  57. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +1 -5
  58. package/src/DropdownMenu/DropdownMenuItem.tsx +4 -8
  59. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +1 -5
  60. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +1 -5
  61. package/src/Item/Item.tsx +5 -15
  62. package/src/Layer/preserveAnimations.ts +275 -12
  63. package/src/MoreMenu/MoreMenu.doc.mjs +4 -2
  64. package/src/MoreMenu/MoreMenu.tsx +3 -5
  65. package/src/SelectableCard/SelectableCard.tsx +1 -8
  66. package/src/TreeList/TreeListItem.tsx +6 -8
  67. package/src/hooks/menuPressGesture.ts +38 -243
  68. package/src/hooks/useClickableContainer.doc.mjs +2 -8
  69. package/src/hooks/useClickableContainer.ts +6 -52
  70. package/src/hooks/useMenuPress.ts +11 -118
  71. package/src/motion/MEASUREMENTS.md +0 -11
  72. package/src/motion/transientCopy.ts +5 -45
  73. package/src/motion/uikit/gestures.ts +0 -22
  74. package/src/motion/uikit/index.ts +0 -1
  75. package/src/utils/pressFeedback.ts +1 -16
  76. package/dist/hooks/menuPressValues.d.ts +0 -16
  77. package/dist/hooks/menuPressValues.d.ts.map +0 -1
  78. package/dist/hooks/menuPressValues.js +0 -18
  79. package/dist/motion/carryAnimations.d.ts +0 -58
  80. package/dist/motion/carryAnimations.d.ts.map +0 -1
  81. package/dist/motion/carryAnimations.js +0 -305
  82. package/dist/privateAttributes/menuPressLit.stylex.d.ts +0 -52
  83. package/dist/privateAttributes/menuPressLit.stylex.d.ts.map +0 -1
  84. package/dist/privateAttributes/menuPressLit.stylex.js +0 -99
  85. package/src/hooks/menuPressValues.ts +0 -18
  86. package/src/motion/carryAnimations.ts +0 -389
  87. package/src/privateAttributes/menuPressLit.stylex.ts +0 -104
@@ -4,7 +4,7 @@
4
4
  * @file menuPressGesture.ts
5
5
  * @input A menu press gesture and one pointer event, both as plain data
6
6
  * @output The pure `menuPressStep` state machine, its state, event and effect
7
- * types, the settle constant, and the finger's handoff distance
7
+ * types, and the settle constant
8
8
  * @position Pure module under `useMenuPress`. It has no DOM and no React, so
9
9
  * a unit test can check each transition without a browser.
10
10
  *
@@ -13,24 +13,12 @@
13
13
  * - /packages/core/src/hooks/useMenuPress.ts
14
14
  */
15
15
 
16
- import {MENU_PRESS_HANDOFF_PX as UIKIT_MENU_PRESS_HANDOFF_PX} from './menuPressValues';
17
-
18
- /**
19
- * How long the opening press must last before its release may act, unless a
20
- * finger's handoff applies.
21
- */
16
+ /** How long the opening press must last before its release may act. */
22
17
  export const MENU_PRESS_SETTLE_MS = 300;
23
18
 
24
- /**
25
- * How far a finger that opened the menu travels, from the first sample the
26
- * menu sees, before it starts picking (UIMenu's scrub handoff).
27
- */
28
- export const MENU_PRESS_HANDOFF_PX: number = UIKIT_MENU_PRESS_HANDOFF_PX;
29
-
30
19
  export type MenuPressPointerType = 'mouse' | 'touch' | 'pen';
31
20
 
32
- /** @internal The machine's own shape, with the finger's handoff; not public contract. */
33
- export type MenuPressState<T> =
21
+ export type MenuPressGesture<T> =
34
22
  | {phase: 'idle'}
35
23
  | {
36
24
  phase: 'triggerPress';
@@ -41,12 +29,6 @@ export type MenuPressState<T> =
41
29
  phase: 'open';
42
30
  pointerType: MenuPressPointerType;
43
31
  openedAt: number;
44
- /**
45
- * A finger's handoff: the first position the menu saw after it opened,
46
- * which the scrub distance is counted from. Unset until that sample.
47
- * @internal Not public contract.
48
- */
49
- handoffFrom?: {x: number; y: number};
50
32
  }
51
33
  | {
52
34
  phase: 'tracking';
@@ -57,8 +39,7 @@ export type MenuPressState<T> =
57
39
  row: T | null;
58
40
  };
59
41
 
60
- /** @internal The machine's own shape, with the finger's handoff; not public contract. */
61
- export type MenuPressInput<T> =
42
+ export type MenuPressEvent<T> =
62
43
  | {
63
44
  type: 'down';
64
45
  target: 'trigger' | 'menu';
@@ -69,38 +50,18 @@ export type MenuPressInput<T> =
69
50
  }
70
51
  /** The menu opened while the trigger press is still held. */
71
52
  | {type: 'opened'; time: number}
72
- | {
73
- type: 'move';
74
- row: T | null;
75
- isInMenu: boolean;
76
- time: number;
77
- /**
78
- * Viewport position, for a finger's handoff distance.
79
- * @internal Not public contract.
80
- */
81
- x?: number;
82
- /** @internal See `x`. */
83
- y?: number;
84
- }
53
+ | {type: 'move'; row: T | null; isInMenu: boolean; time: number}
85
54
  | {
86
55
  type: 'up';
87
56
  row: T | null;
88
57
  isInMenu: boolean;
89
58
  isOnTrigger: boolean;
90
59
  time: number;
91
- /**
92
- * Viewport position, for a finger's handoff distance.
93
- * @internal Not public contract.
94
- */
95
- x?: number;
96
- /** @internal See `x`. */
97
- y?: number;
98
60
  }
99
61
  /** The browser took the gesture: pointercancel, a second pointer, a scroll. */
100
62
  | {type: 'cancel'};
101
63
 
102
- /** @internal The machine's own shape, with the finger's handoff; not public contract. */
103
- export type MenuPressOutcome<T> =
64
+ export type MenuPressEffect<T> =
104
65
  | {type: 'none'}
105
66
  /** A mouse pressed the trigger: open the menu now, under the held pointer. */
106
67
  | {type: 'open'}
@@ -110,52 +71,27 @@ export type MenuPressOutcome<T> =
110
71
  /**
111
72
  * The gesture ended without a row acting. `stray` says whether the browser
112
73
  * will still report a `click` for this gesture that must not act; `dismiss`
113
- * says the menu should close: a mouse released outside it.
74
+ * says the menu should close (a mouse released outside it).
114
75
  */
115
76
  | {type: 'settle'; stray: boolean; dismiss: boolean};
116
77
 
117
- /** @internal The machine's own shape, with the finger's handoff; not public contract. */
118
- export interface MenuPressTransition<T> {
119
- gesture: MenuPressState<T>;
120
- effect: MenuPressOutcome<T>;
78
+ export interface MenuPressStep<T> {
79
+ gesture: MenuPressGesture<T>;
80
+ effect: MenuPressEffect<T>;
121
81
  }
122
82
 
123
- export const IDLE_MENU_PRESS: MenuPressState<never> = {phase: 'idle'};
83
+ export const IDLE_MENU_PRESS: MenuPressGesture<never> = {phase: 'idle'};
124
84
 
125
- const NONE: MenuPressOutcome<never> = {type: 'none'};
85
+ const NONE: MenuPressEffect<never> = {type: 'none'};
126
86
 
127
- function idle<T>(effect: MenuPressOutcome<T>): MenuPressTransition<T> {
87
+ function idle<T>(effect: MenuPressEffect<T>): MenuPressStep<T> {
128
88
  return {gesture: {phase: 'idle'}, effect};
129
89
  }
130
90
 
131
- function highlightEffect<T>(row: T | null): MenuPressOutcome<T> {
91
+ function highlightEffect<T>(row: T | null): MenuPressEffect<T> {
132
92
  return row == null ? {type: 'clear'} : {type: 'highlight', row};
133
93
  }
134
94
 
135
- /** A finger or a pen: UIKit's touch rules apply. */
136
- function isFinger(pointerType: MenuPressPointerType): boolean {
137
- return pointerType !== 'mouse';
138
- }
139
-
140
- function hasPosition(event: {
141
- x?: number;
142
- y?: number;
143
- }): event is {x: number; y: number} {
144
- return event.x != null && event.y != null;
145
- }
146
-
147
- /**
148
- * Whether a finger has travelled the handoff distance from `from`. The
149
- * distance is a straight line, as UIMenu measures it: a diagonal 7 pt each
150
- * way does not hand off, 9 pt each way does.
151
- */
152
- function hasPassedHandoff(
153
- from: {x: number; y: number},
154
- to: {x: number; y: number},
155
- ): boolean {
156
- return Math.hypot(to.x - from.x, to.y - from.y) >= MENU_PRESS_HANDOFF_PX;
157
- }
158
-
159
95
  /**
160
96
  * Resolve a release while the highlight is tracking the pointer: over an
161
97
  * enabled row it acts, over the menu's chrome it acts on nothing, outside the
@@ -164,8 +100,8 @@ function hasPassedHandoff(
164
100
  */
165
101
  function release<T>(
166
102
  pointerType: MenuPressPointerType,
167
- event: Extract<MenuPressInput<T>, {type: 'up'}>,
168
- ): MenuPressTransition<T> {
103
+ event: Extract<MenuPressEvent<T>, {type: 'up'}>,
104
+ ): MenuPressStep<T> {
169
105
  if (event.row != null) {
170
106
  return idle({type: 'act', row: event.row});
171
107
  }
@@ -183,15 +119,10 @@ function release<T>(
183
119
  * - `idle`: no gesture is live.
184
120
  * - `triggerPress`: a finger rests on the trigger, and the menu is not open
185
121
  * yet. A mouse press on the trigger skips this state and opens at once.
186
- * - `open`: the menu opened under a held pointer, and the pointer does not
187
- * pick yet.
122
+ * - `open`: the menu opened under a held pointer, and the pointer has not
123
+ * entered it yet.
188
124
  * - `tracking`: the pointer is held, and the highlight follows it.
189
125
  *
190
- * A finger or a pen that opened the menu picks only after it travels
191
- * `MENU_PRESS_HANDOFF_PX` from the first position the menu sees. UIMenu
192
- * hands off the same way. Until then nothing lights, and its lift acts on
193
- * nothing. A host that reports no positions keeps the settle rule.
194
- *
195
126
  * The settle rule: the release of the opening gesture acts only after the
196
127
  * pointer entered the menu or the press lasted `MENU_PRESS_SETTLE_MS`. Else a
197
128
  * menu that opens under the pointer would pick a row that nobody chose.
@@ -199,14 +130,11 @@ function release<T>(
199
130
  * Rows are opaque (`T`). The hook feeds in the enabled row under the
200
131
  * pointer, or `null` over a divider, a heading, a disabled row, or outside
201
132
  * the menu.
202
- *
203
- * @internal The machine's own shape, with the finger's handoff; not public
204
- * contract.
205
133
  */
206
- export function menuPressTransition<T>(
207
- gesture: MenuPressState<T>,
208
- event: MenuPressInput<T>,
209
- ): MenuPressTransition<T> {
134
+ export function menuPressStep<T>(
135
+ gesture: MenuPressGesture<T>,
136
+ event: MenuPressEvent<T>,
137
+ ): MenuPressStep<T> {
210
138
  switch (gesture.phase) {
211
139
  case 'idle':
212
140
  return stepIdle(gesture, event);
@@ -220,9 +148,9 @@ export function menuPressTransition<T>(
220
148
  }
221
149
 
222
150
  function stepIdle<T>(
223
- gesture: Extract<MenuPressState<T>, {phase: 'idle'}>,
224
- event: MenuPressInput<T>,
225
- ): MenuPressTransition<T> {
151
+ gesture: Extract<MenuPressGesture<T>, {phase: 'idle'}>,
152
+ event: MenuPressEvent<T>,
153
+ ): MenuPressStep<T> {
226
154
  if (event.type !== 'down') {
227
155
  return {gesture, effect: NONE};
228
156
  }
@@ -258,9 +186,9 @@ function stepIdle<T>(
258
186
  }
259
187
 
260
188
  function stepTriggerPress<T>(
261
- gesture: Extract<MenuPressState<T>, {phase: 'triggerPress'}>,
262
- event: MenuPressInput<T>,
263
- ): MenuPressTransition<T> {
189
+ gesture: Extract<MenuPressGesture<T>, {phase: 'triggerPress'}>,
190
+ event: MenuPressEvent<T>,
191
+ ): MenuPressStep<T> {
264
192
  switch (event.type) {
265
193
  case 'opened':
266
194
  return {
@@ -288,34 +216,11 @@ function stepTriggerPress<T>(
288
216
  }
289
217
 
290
218
  function stepOpen<T>(
291
- gesture: Extract<MenuPressState<T>, {phase: 'open'}>,
292
- event: MenuPressInput<T>,
293
- ): MenuPressTransition<T> {
219
+ gesture: Extract<MenuPressGesture<T>, {phase: 'open'}>,
220
+ event: MenuPressEvent<T>,
221
+ ): MenuPressStep<T> {
294
222
  switch (event.type) {
295
- case 'move': {
296
- if (isFinger(gesture.pointerType) && hasPosition(event)) {
297
- // UIMenu counts from the first sample after the open, not from the
298
- // open point.
299
- const from = gesture.handoffFrom;
300
- if (from == null) {
301
- return {
302
- gesture: {...gesture, handoffFrom: {x: event.x, y: event.y}},
303
- effect: NONE,
304
- };
305
- }
306
- if (!hasPassedHandoff(from, event)) {
307
- return {gesture, effect: NONE};
308
- }
309
- return {
310
- gesture: {
311
- phase: 'tracking',
312
- pointerType: gesture.pointerType,
313
- origin: 'trigger',
314
- row: event.row,
315
- },
316
- effect: highlightEffect(event.row),
317
- };
318
- }
223
+ case 'move':
319
224
  if (!event.isInMenu) {
320
225
  return {gesture, effect: NONE};
321
226
  }
@@ -328,14 +233,7 @@ function stepOpen<T>(
328
233
  },
329
234
  effect: highlightEffect(event.row),
330
235
  };
331
- }
332
236
  case 'up': {
333
- if (isFinger(gesture.pointerType) && hasPosition(event)) {
334
- // The finger never travelled the handoff distance, so the row under it
335
- // appeared under a press nobody moved. Nothing acts, the menu stays,
336
- // and the trigger's click for this press is spent.
337
- return idle({type: 'settle', stray: true, dismiss: false});
338
- }
339
237
  const hasSettled = event.time - gesture.openedAt >= MENU_PRESS_SETTLE_MS;
340
238
  if (!hasSettled) {
341
239
  // The pointer never entered the menu and the press was short: the
@@ -347,7 +245,7 @@ function stepOpen<T>(
347
245
  }
348
246
  case 'cancel':
349
247
  // As above: the gesture ends, so its document listeners come off. The
350
- // menu stays open: a cancelled pointer did not ask to close it.
248
+ // menu stays open — a cancelled pointer did not ask to close it.
351
249
  return idle({type: 'settle', stray: false, dismiss: false});
352
250
  case 'down':
353
251
  case 'opened':
@@ -356,11 +254,11 @@ function stepOpen<T>(
356
254
  }
357
255
 
358
256
  function stepTracking<T>(
359
- gesture: Extract<MenuPressState<T>, {phase: 'tracking'}>,
360
- event: MenuPressInput<T>,
361
- ): MenuPressTransition<T> {
257
+ gesture: Extract<MenuPressGesture<T>, {phase: 'tracking'}>,
258
+ event: MenuPressEvent<T>,
259
+ ): MenuPressStep<T> {
362
260
  switch (event.type) {
363
- case 'move':
261
+ case 'move': {
364
262
  if (event.row === gesture.row) {
365
263
  return {gesture, effect: NONE};
366
264
  }
@@ -368,6 +266,7 @@ function stepTracking<T>(
368
266
  gesture: {...gesture, row: event.row},
369
267
  effect: highlightEffect(event.row),
370
268
  };
269
+ }
371
270
  case 'up':
372
271
  return release(gesture.pointerType, event);
373
272
  case 'cancel':
@@ -379,107 +278,3 @@ function stepTracking<T>(
379
278
  return {gesture, effect: NONE};
380
279
  }
381
280
  }
382
-
383
- // ---------------------------------------------------------------------------
384
- // The released public shape (`@astryxdesign/core/hooks`). The handoff
385
- // positions and `handoffFrom` stay internal: a caller of
386
- // `menuPressStep` keeps the settle rule, as released.
387
- // ---------------------------------------------------------------------------
388
-
389
- export type MenuPressGesture<T> =
390
- | {phase: 'idle'}
391
- | {
392
- phase: 'triggerPress';
393
- pointerType: MenuPressPointerType;
394
- startedAt: number;
395
- }
396
- | {
397
- phase: 'open';
398
- pointerType: MenuPressPointerType;
399
- openedAt: number;
400
- }
401
- | {
402
- phase: 'tracking';
403
- pointerType: MenuPressPointerType;
404
- /** Where the press began. */
405
- origin: 'trigger' | 'menu';
406
- /** The enabled row currently under the pointer, if any. */
407
- row: T | null;
408
- };
409
-
410
- export type MenuPressEvent<T> =
411
- | {
412
- type: 'down';
413
- target: 'trigger' | 'menu';
414
- pointerType: MenuPressPointerType;
415
- /** The enabled row under the press when it lands inside the menu. */
416
- row: T | null;
417
- time: number;
418
- }
419
- /** The menu opened while the trigger press is still held. */
420
- | {type: 'opened'; time: number}
421
- | {type: 'move'; row: T | null; isInMenu: boolean; time: number}
422
- | {
423
- type: 'up';
424
- row: T | null;
425
- isInMenu: boolean;
426
- isOnTrigger: boolean;
427
- time: number;
428
- }
429
- /** The browser took the gesture: pointercancel, a second pointer, a scroll. */
430
- | {type: 'cancel'};
431
-
432
- export type MenuPressEffect<T> =
433
- | {type: 'none'}
434
- /** A mouse pressed the trigger: open the menu now, under the held pointer. */
435
- | {type: 'open'}
436
- | {type: 'highlight'; row: T}
437
- | {type: 'clear'}
438
- | {type: 'act'; row: T}
439
- /**
440
- * The gesture ended without a row acting. `stray` says whether the browser
441
- * will still report a `click` for this gesture that must not act; `dismiss`
442
- * says the menu should close (a mouse released outside it).
443
- */
444
- | {type: 'settle'; stray: boolean; dismiss: boolean};
445
-
446
- export interface MenuPressStep<T> {
447
- gesture: MenuPressGesture<T>;
448
- effect: MenuPressEffect<T>;
449
- }
450
-
451
- /**
452
- * The menu press machine, as released: one step from a gesture and an event
453
- * to the next gesture and the effect to apply. Pure.
454
- */
455
- export function menuPressStep<T>(
456
- gesture: MenuPressGesture<T>,
457
- event: MenuPressEvent<T>,
458
- ): MenuPressStep<T> {
459
- // The released machine keeps the settle rule. Positions start the handoff,
460
- // so a caller's event reaches the transition without them.
461
- const {
462
- x: _x,
463
- y: _y,
464
- ...released
465
- } = event as MenuPressEvent<T> & {x?: number; y?: number};
466
- const {gesture: next, effect} = menuPressTransition(
467
- gesture,
468
- released as MenuPressEvent<T>,
469
- );
470
- return {
471
- gesture: publicGesture(next),
472
- effect,
473
- };
474
- }
475
-
476
- function publicGesture<T>(gesture: MenuPressState<T>): MenuPressGesture<T> {
477
- if (gesture.phase !== 'open') {
478
- return gesture;
479
- }
480
- return {
481
- phase: 'open',
482
- pointerType: gesture.pointerType,
483
- openedAt: gesture.openedAt,
484
- };
485
- }
@@ -61,17 +61,12 @@ export const docs = {
61
61
  type: '(event: MouseEvent<HTMLElement>) => void',
62
62
  description: 'Mouse up handler to attach to the container (handles middle-click navigation for href).',
63
63
  },
64
- {
65
- name: 'onPointerDown',
66
- type: '(event: PointerEvent<HTMLElement>) => void',
67
- description: 'Pointer down handler to attach to the container. On a container that paints its press with usePressFeedback, it drops a touch press that starts on a nested interactive element, which does not activate the container. A mouse :active press paint is unchanged. Without it, the container keeps its click rules and its pressed state for every press.',
68
- },
69
64
  ],
70
65
  usage: {
71
66
  description:
72
67
  'Makes a container element clickable while preserving nested interactive element behavior. Solves the "nested interactive elements" problem: when a card is clickable but contains buttons/links, clicking those should NOT trigger the card\'s action. Detects interactive ancestors between the click target and the container, and ignores text selections. Supports href navigation (including middle-click and Ctrl/Cmd+click for new tabs).',
73
68
  bestPractices: [
74
- { guidance: true, description: 'Attach onClick, onMouseUp and onPointerDown to the container element: full click handling including middle-click, and no touch pressed state for a press on a nested control.' },
69
+ { guidance: true, description: 'Attach both onClick and onMouseUp to the container element for full click handling including middle-click.' },
75
70
  { guidance: true, description: 'Use inside ClickableCard or SelectableCard for the standard card interaction pattern.' },
76
71
  { guidance: false, description: 'Use when the entire container is a single interactive element; just use a <button> or <a> directly.' },
77
72
  ],
@@ -98,13 +93,12 @@ export const docsDense = {
98
93
  returnDescriptions: {
99
94
  onClick: 'click handler for container element.',
100
95
  onMouseUp: 'mouse up handler for container (handles middle-click navigation for href).',
101
- onPointerDown: 'pointer down handler for container: drops usePressFeedback touch press starting on nested interactive element; mouse :active paint unchanged.',
102
96
  },
103
97
  usage: {
104
98
  description:
105
99
  'Makes container element clickable while preserving nested interactive element behavior. Solves "nested interactive elements" problem: when card is clickable but contains buttons/links, clicking those must NOT trigger card\'s action. Detects interactive ancestors between click target + container, ignores text selections. Supports href navigation (incl. middle-click + Ctrl/Cmd+click for new tabs).',
106
100
  bestPractices: [
107
- { guidance: true, description: 'Attach onClick + onMouseUp + onPointerDown to container element: full click handling incl. middle-click, no touch pressed state for press on nested control.' },
101
+ { guidance: true, description: 'Attach both onClick + onMouseUp to container element for full click handling incl. middle-click.' },
108
102
  { guidance: true, description: 'Use inside ClickableCard / SelectableCard for standard card interaction pattern.' },
109
103
  { guidance: false, description: 'Use when entire container is single interactive element; just use <button> / <a> directly.' },
110
104
  ],
@@ -5,28 +5,19 @@
5
5
  /**
6
6
  * @file useClickableContainer.ts
7
7
  * @input Container ref, interactive element ref, click/href handlers
8
- * @output onClick, onMouseUp and onPointerDown handlers for the container;
9
- * INTERACTIVE_SELECTORS list;
8
+ * @output onClick and onMouseUp handlers for the container; INTERACTIVE_SELECTORS list;
10
9
  * hasInteractiveAncestor and the shared innermostInteractive walk
11
10
  * @position Core hook for clickable containers with nested interactive
12
11
  * elements. A click on a nested button or link does not run the card's
13
- * action, and a finger press there shows no press on the card. Each navigation
14
- * passes the rule in utils/safeUrl.ts first.
12
+ * action. Each navigation passes the rule in utils/safeUrl.ts first.
15
13
  *
16
14
  * SYNC: When modified, update:
17
15
  * - /packages/core/src/hooks/index.ts (export)
18
16
  */
19
17
 
20
- import {
21
- useCallback,
22
- useEffect,
23
- type RefObject,
24
- type MouseEvent,
25
- type PointerEvent,
26
- } from 'react';
18
+ import {useCallback, useEffect, type RefObject, type MouseEvent} from 'react';
27
19
  import {isLayerSurface} from '../Layer/layerSurfacePress';
28
20
  import {isSafeUrl} from '../utils/safeUrl';
29
- import {cancelPress} from '../utils/pressFeedback';
30
21
 
31
22
  /**
32
23
  * The attribute this hook sets on the container it makes clickable. A click
@@ -168,14 +159,6 @@ export interface UseClickableContainerOptions {
168
159
  export interface ClickableContainerResult {
169
160
  onClick: (event: MouseEvent<HTMLElement>) => void;
170
161
  onMouseUp: (event: MouseEvent<HTMLElement>) => void;
171
- /**
172
- * Spread on the container with `onClick`. A press that starts on a nested
173
- * interactive element does not activate the container. On a container that
174
- * paints its press with `usePressFeedback`, this drops that touch press.
175
- * A mouse `:active` press paint is unchanged. A container without it keeps
176
- * its activation rule and shows its pressed state for every press.
177
- */
178
- onPointerDown: (event: PointerEvent<HTMLElement>) => void;
179
162
  }
180
163
 
181
164
  /**
@@ -193,16 +176,12 @@ export interface ClickableContainerResult {
193
176
  * @example
194
177
  * ```
195
178
  * const containerRef = useRef<HTMLDivElement>(null);
196
- * const { onClick, onMouseUp, onPointerDown } = useClickableContainer({
179
+ * const { onClick, onMouseUp } = useClickableContainer({
197
180
  * containerRef,
198
181
  * onClick: () => console.log('card clicked'),
199
182
  * });
200
183
  * return (
201
- * <div
202
- * ref={containerRef}
203
- * onClick={onClick}
204
- * onMouseUp={onMouseUp}
205
- * onPointerDown={onPointerDown}>
184
+ * <div ref={containerRef} onClick={onClick} onMouseUp={onMouseUp}>
206
185
  * <p>Click anywhere on this card</p>
207
186
  * <button onClick={() => alert('button')}>Nested button</button>
208
187
  * </div>
@@ -326,30 +305,5 @@ export function useClickableContainer({
326
305
  [containerRef, href, disabled],
327
306
  );
328
307
 
329
- // The press-state counterpart of onClick's rule, for touch: a finger press
330
- // that would not activate the container shows no press on it. A mouse or
331
- // pen press keeps its `:active` paint.
332
- const onPointerDown = useCallback(
333
- (event: PointerEvent<HTMLElement>) => {
334
- const containerEl = containerRef.current;
335
- const eventTarget = event.target;
336
- if (
337
- event.pointerType !== 'touch' ||
338
- disabled ||
339
- containerEl == null ||
340
- !(eventTarget instanceof Element) ||
341
- eventTarget === event.currentTarget ||
342
- !hasInteractiveAncestor(eventTarget, containerEl) ||
343
- // The control the container delegates to is the container's own
344
- // action: a press on it activates the container.
345
- interactiveRef?.current?.contains(eventTarget) === true
346
- ) {
347
- return;
348
- }
349
- cancelPress(containerEl);
350
- },
351
- [containerRef, interactiveRef, disabled],
352
- );
353
-
354
- return {onClick, onMouseUp, onPointerDown};
308
+ return {onClick, onMouseUp};
355
309
  }