@oxyhq/services 22.12.1 → 22.12.3

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 (85) hide show
  1. package/lib/commonjs/ui/components/FollowButton.js +5 -8
  2. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  3. package/lib/commonjs/ui/hooks/useSurfaceHeader.js +44 -7
  4. package/lib/commonjs/ui/hooks/useSurfaceHeader.js.map +1 -1
  5. package/lib/commonjs/ui/navigation/surfaceNavStack.js +8 -7
  6. package/lib/commonjs/ui/navigation/surfaceNavStack.js.map +1 -1
  7. package/lib/commonjs/ui/navigation/surfaces.js +8 -3
  8. package/lib/commonjs/ui/navigation/surfaces.js.map +1 -1
  9. package/lib/commonjs/ui/screens/ChangeAvatarScreen.js +48 -42
  10. package/lib/commonjs/ui/screens/ChangeAvatarScreen.js.map +1 -1
  11. package/lib/commonjs/ui/screens/CreateAccountScreen.js +60 -98
  12. package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
  13. package/lib/commonjs/ui/screens/FileManagementScreen.js +22 -8
  14. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  15. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js +10 -7
  16. package/lib/commonjs/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  17. package/lib/commonjs/ui/screens/fileManagement/UploadBar.js +19 -11
  18. package/lib/commonjs/ui/screens/fileManagement/UploadBar.js.map +1 -1
  19. package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js +42 -9
  20. package/lib/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
  21. package/lib/module/ui/components/FollowButton.js +5 -8
  22. package/lib/module/ui/components/FollowButton.js.map +1 -1
  23. package/lib/module/ui/hooks/useSurfaceHeader.js +45 -8
  24. package/lib/module/ui/hooks/useSurfaceHeader.js.map +1 -1
  25. package/lib/module/ui/navigation/surfaceNavStack.js +8 -7
  26. package/lib/module/ui/navigation/surfaceNavStack.js.map +1 -1
  27. package/lib/module/ui/navigation/surfaces.js +7 -1
  28. package/lib/module/ui/navigation/surfaces.js.map +1 -1
  29. package/lib/module/ui/screens/ChangeAvatarScreen.js +48 -42
  30. package/lib/module/ui/screens/ChangeAvatarScreen.js.map +1 -1
  31. package/lib/module/ui/screens/CreateAccountScreen.js +62 -100
  32. package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
  33. package/lib/module/ui/screens/FileManagementScreen.js +22 -8
  34. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  35. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js +10 -7
  36. package/lib/module/ui/screens/fileManagement/PhotoPickerSection.js.map +1 -1
  37. package/lib/module/ui/screens/fileManagement/UploadBar.js +20 -12
  38. package/lib/module/ui/screens/fileManagement/UploadBar.js.map +1 -1
  39. package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js +43 -10
  40. package/lib/module/ui/screens/fileManagement/hooks/useFileUploadState.js.map +1 -1
  41. package/lib/typescript/commonjs/ui/components/FollowButton.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts +7 -0
  43. package/lib/typescript/commonjs/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts +7 -4
  45. package/lib/typescript/commonjs/ui/navigation/surfaceNavStack.d.ts.map +1 -1
  46. package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts +9 -5
  48. package/lib/typescript/commonjs/ui/screens/ChangeAvatarScreen.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/ui/screens/FileManagementScreen.d.ts.map +1 -1
  51. package/lib/typescript/commonjs/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/ui/screens/fileManagement/UploadBar.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +5 -1
  54. package/lib/typescript/commonjs/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/ui/types/fileManagement.d.ts +10 -0
  56. package/lib/typescript/commonjs/ui/types/fileManagement.d.ts.map +1 -1
  57. package/lib/typescript/module/ui/components/FollowButton.d.ts.map +1 -1
  58. package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts +7 -0
  59. package/lib/typescript/module/ui/hooks/useSurfaceHeader.d.ts.map +1 -1
  60. package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts +7 -4
  61. package/lib/typescript/module/ui/navigation/surfaceNavStack.d.ts.map +1 -1
  62. package/lib/typescript/module/ui/navigation/surfaces.d.ts.map +1 -1
  63. package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts +9 -5
  64. package/lib/typescript/module/ui/screens/ChangeAvatarScreen.d.ts.map +1 -1
  65. package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  66. package/lib/typescript/module/ui/screens/FileManagementScreen.d.ts.map +1 -1
  67. package/lib/typescript/module/ui/screens/fileManagement/PhotoPickerSection.d.ts.map +1 -1
  68. package/lib/typescript/module/ui/screens/fileManagement/UploadBar.d.ts.map +1 -1
  69. package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts +5 -1
  70. package/lib/typescript/module/ui/screens/fileManagement/hooks/useFileUploadState.d.ts.map +1 -1
  71. package/lib/typescript/module/ui/types/fileManagement.d.ts +10 -0
  72. package/lib/typescript/module/ui/types/fileManagement.d.ts.map +1 -1
  73. package/package.json +4 -4
  74. package/src/ui/components/FollowButton.tsx +5 -8
  75. package/src/ui/hooks/useSurfaceHeader.ts +65 -8
  76. package/src/ui/navigation/__tests__/surfaceNavStack.test.ts +49 -17
  77. package/src/ui/navigation/surfaceNavStack.ts +15 -11
  78. package/src/ui/navigation/surfaces.ts +9 -1
  79. package/src/ui/screens/ChangeAvatarScreen.tsx +46 -39
  80. package/src/ui/screens/CreateAccountScreen.tsx +128 -166
  81. package/src/ui/screens/FileManagementScreen.tsx +22 -8
  82. package/src/ui/screens/fileManagement/PhotoPickerSection.tsx +4 -5
  83. package/src/ui/screens/fileManagement/UploadBar.tsx +17 -10
  84. package/src/ui/screens/fileManagement/hooks/useFileUploadState.ts +51 -10
  85. package/src/ui/types/fileManagement.ts +10 -0
@@ -27,6 +27,13 @@ export declare const SurfaceHeaderContext: import("react").Context<SurfaceHeader
27
27
  * title/subtitle and any action slot. Merges over nothing (the host owns
28
28
  * back/close), replaces on change, and clears on unmount. Call it unconditionally;
29
29
  * it is a no-op outside a header-mode surface.
30
+ *
31
+ * Screens pass an INLINE `content` object (a fresh reference every render), so the
32
+ * push is guarded by {@link surfaceHeaderContentEqual}: the host's `setContent` is
33
+ * called only when the VALUE actually changes. Without that guard the setState
34
+ * would fire every render → the screen re-renders → the effect runs again → an
35
+ * infinite update loop (React error #185) in any consumer that does not memoize
36
+ * the object for us (i.e. anything not compiled by the React Compiler).
30
37
  */
31
38
  export declare function useSurfaceHeader(content: SurfaceHeaderContent | null | undefined): void;
32
39
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"useSurfaceHeader.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useSurfaceHeader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAAG,IAAI,CACrC,kBAAkB,EAChB,OAAO,GACP,cAAc,GACd,UAAU,GACV,YAAY,GACZ,MAAM,GACN,OAAO,GACP,QAAQ,GACR,eAAe,GACf,SAAS,GACT,QAAQ,GACR,UAAU,GACV,MAAM,GACN,UAAU,CACb,CAAC;AAEF,UAAU,yBAAyB;IACjC,UAAU,EAAE,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,KAAK,IAAI,CAAC;CAC5D;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,2DAAwD,CAAC;AAE1F;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAWvF"}
1
+ {"version":3,"file":"useSurfaceHeader.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useSurfaceHeader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,oBAAoB,GAAG,IAAI,CACrC,kBAAkB,EAChB,OAAO,GACP,cAAc,GACd,UAAU,GACV,YAAY,GACZ,MAAM,GACN,OAAO,GACP,QAAQ,GACR,eAAe,GACf,SAAS,GACT,QAAQ,GACR,UAAU,GACV,MAAM,GACN,UAAU,CACb,CAAC;AAEF,UAAU,yBAAyB;IACjC,UAAU,EAAE,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,KAAK,IAAI,CAAC;CAC5D;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,2DAAwD,CAAC;AAoC1F;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CA2BvF"}
@@ -50,10 +50,13 @@ export interface SurfaceNavStack {
50
50
  * `dismiss(result)` resolves it with `result`, backing out of the flow's entry
51
51
  * frame resolves it with `undefined` (cancelled) — popping the surface back to
52
52
  * the frame that started the flow WITHOUT dismissing it. This is how a screen
53
- * already living inside a surface (EditProfile, ManageAccount, ChangeAvatar)
54
- * opens the avatar picker / media selector as a MORPH instead of a stacked
55
- * surface while still awaiting its result. Flows NEST (ChangeAvatar the "My
56
- * Oxy files" selector → back), maintained as a stack.
53
+ * already living inside a surface (EditProfile, ManageAccount) opens the avatar
54
+ * flow as a MORPH instead of a stacked surface while still awaiting its result.
55
+ * The avatar flow is ONE flow (`ChangeAvatar`) with plain forward frames pushed
56
+ * on top (the "My Oxy files" `FileManagement` picker, then `AvatarCrop`); the
57
+ * cropper's `dismiss(result)` resolves THIS flow. Flows are kept as a STACK so
58
+ * they can nest generically, even though no current caller opens a flow inside
59
+ * another.
57
60
  */
58
61
  beginFlow(route: RouteName, props?: Record<string, unknown>): Promise<unknown>;
59
62
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"surfaceNavStack.d.ts","sourceRoot":"","sources":["../../../../../src/ui/navigation/surfaceNavStack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAU,CAAC;AAE1C;;;;;;;;;;;;;;GAcG;AAEH,8CAA8C;AAC9C,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;IAC/B,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAClE,sDAAsD;IACtD,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACjE,qEAAqE;IACrE,MAAM,IAAI,OAAO,CAAC;IAClB,0EAA0E;IAC1E,SAAS,IAAI,OAAO,CAAC;IACrB,2CAA2C;IAC3C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mFAAmF;IACnF,cAAc,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/E;;;;;OAKG;IACH,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7C,wFAAwF;IACxF,iBAAiB,IAAI,IAAI,CAAC;IAC1B,+BAA+B;IAC/B,MAAM,IAAI,QAAQ,CAAC;CACpB;AAQD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,EAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,eAAe,CAwFjB"}
1
+ {"version":3,"file":"surfaceNavStack.d.ts","sourceRoot":"","sources":["../../../../../src/ui/navigation/surfaceNavStack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAU,CAAC;AAE1C;;;;;;;;;;;;;;GAcG;AAEH,8CAA8C;AAC9C,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,SAAS,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC;IAC/B,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAClE,sDAAsD;IACtD,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACjE,qEAAqE;IACrE,MAAM,IAAI,OAAO,CAAC;IAClB,0EAA0E;IAC1E,SAAS,IAAI,OAAO,CAAC;IACrB,2CAA2C;IAC3C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mFAAmF;IACnF,cAAc,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/E;;;;;OAKG;IACH,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7C,wFAAwF;IACxF,iBAAiB,IAAI,IAAI,CAAC;IAC1B,+BAA+B;IAC/B,MAAM,IAAI,QAAQ,CAAC;CACpB;AAQD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,EAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,eAAe,CAyFjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"surfaces.d.ts","sourceRoot":"","sources":["../../../../../src/ui/navigation/surfaces.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"surfaces.d.ts","sourceRoot":"","sources":["../../../../../src/ui/navigation/surfaces.ts"],"names":[],"mappings":"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"}
@@ -4,11 +4,15 @@
4
4
  * Shows the current avatar large, then the four sources the user can pick from:
5
5
  * device gallery, camera, their existing Oxy files, and removing the current
6
6
  * photo. Every source that yields an image NAVIGATES WITHIN THIS SURFACE to
7
- * `AvatarCrop` (never presents it on top), so the panel morphs from this list's
8
- * size to the crop editor's and back Cancel on the cropper returns here to
9
- * re-pick. One container reshaping, not two sheets. The forward morph is
10
- * flash-free because only the top frame renders and the cropper does all its own
11
- * async work (URL resolution + measurement), so this list is never left on screen
7
+ * `AvatarCrop` (never presents it on top), so the panel morphs from frame to
8
+ * frame instead of stacking sheets. The device / camera sources navigate straight
9
+ * to the cropper; the "My Oxy files" source first navigates to the `FileManagement`
10
+ * picker frame, which then forward-navigates to the cropper on selection a real
11
+ * three-frame stack (this list picker cropper). So Cancel on the cropper
12
+ * returns to whichever frame opened it (the picker when re-picking from Oxy files,
13
+ * this list otherwise), and Cancel on the picker returns here. The forward morph
14
+ * is flash-free because only the top frame renders and the cropper does all its
15
+ * own async work (URL resolution + measurement), so no frame is left on screen
12
16
  * waiting.
13
17
  *
14
18
  * The surface resolves with an {@link AvatarCropResult} (the crop confirmed) or
@@ -1 +1 @@
1
- {"version":3,"file":"ChangeAvatarScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/ChangeAvatarScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAsB/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAW3D;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAChC,OAAO,EAAE,IAAI,CAAC;CACjB;AAmED,QAAA,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAmOjD,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"ChangeAvatarScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/ChangeAvatarScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAsB/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAW3D;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAChC,OAAO,EAAE,IAAI,CAAC;CACjB;AAmED,QAAA,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAsOjD,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"CreateAccountScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/CreateAccountScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAU/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAgF3D;;;;;GAKG;AACH,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA+SlD,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"CreateAccountScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/CreateAccountScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAiF3D;;;;;GAKG;AACH,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA4QlD,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"FileManagementScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/FileManagementScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAAyB,CAAC;AAiEzE,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA4yC7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"FileManagementScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/FileManagementScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAe/B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAAyB,CAAC;AAiEzE,QAAA,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA0zC7D,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"PhotoPickerSection.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/fileManagement/PhotoPickerSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAgBtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA6EhD;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,SAAS,EAAE,OAAO,CAAC;IACnB,iEAAiE;IACjE,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC7C,cAAc,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC9C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;CACtE;AA+MD,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAsSnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"PhotoPickerSection.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/fileManagement/PhotoPickerSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAgBtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA6EhD;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,SAAS,EAAE,OAAO,CAAC;IACnB,iEAAiE;IACjE,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC7C,cAAc,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IAC9C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;CACtE;AA8MD,QAAA,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAsSnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"UploadBar.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/fileManagement/UploadBar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,cAAc;IAC3B,6DAA6D;IAC7D,cAAc,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1D,2DAA2D;IAC3D,MAAM,EAAE,OAAO,CAAC;IAChB,uCAAuC;IACvC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;CACtE;AAcD;;;;GAIG;AACH,QAAA,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA6BvC,CAAC;AAEF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"UploadBar.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/screens/fileManagement/UploadBar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,cAAc;IAC3B,6DAA6D;IAC7D,cAAc,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1D,2DAA2D;IAC3D,MAAM,EAAE,OAAO,CAAC;IAChB,uCAAuC;IACvC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;CACtE;AAkBD;;;;GAIG;AACH,QAAA,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAgCvC,CAAC;AAEF,eAAe,SAAS,CAAC"}
@@ -16,8 +16,12 @@ export interface UseFileUploadStateParams {
16
16
  multiSelect: boolean;
17
17
  afterSelect: 'close' | 'back' | 'none';
18
18
  onSelect?: (file: FileMetadata) => void;
19
+ /** Forward-navigation picker: see {@link FileManagementScreenProps.onPicked}. */
20
+ onPicked?: (file: FileMetadata) => void;
19
21
  goBack?: () => void;
20
22
  onClose?: () => void;
23
+ /** When true, the document picker and pre-upload validation accept images only. */
24
+ imagesOnly?: boolean;
21
25
  selectedIds: Set<string>;
22
26
  setSelectedIds: React.Dispatch<React.SetStateAction<Set<string>>>;
23
27
  t: (key: string, vars?: Record<string, string | number>) => string;
@@ -42,6 +46,6 @@ export interface UseFileUploadStateResult {
42
46
  * orchestrator simply threads in the values these handlers depend on and
43
47
  * consumes the returned state/handlers.
44
48
  */
45
- export declare const useFileUploadState: ({ targetUserId, uploadFileMutation, defaultVisibility, selectMode, multiSelect, afterSelect, onSelect, goBack, onClose, selectedIds, setSelectedIds, t, }: UseFileUploadStateParams) => UseFileUploadStateResult;
49
+ export declare const useFileUploadState: ({ targetUserId, uploadFileMutation, defaultVisibility, selectMode, multiSelect, afterSelect, onSelect, onPicked, goBack, onClose, imagesOnly, selectedIds, setSelectedIds, t, }: UseFileUploadStateParams) => UseFileUploadStateResult;
46
50
  export default useFileUploadState;
47
51
  //# sourceMappingURL=useFileUploadState.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFileUploadState.d.ts","sourceRoot":"","sources":["../../../../../../../src/ui/screens/fileManagement/hooks/useFileUploadState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iDAA8C,CAAC;AAQlF,OAAO,EAEH,KAAK,iBAAiB,EAQzB,MAAM,cAAW,CAAC;AAEnB,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACjB;AAsBD,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;IACrD,iBAAiB,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IACrD,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACvC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,cAAc,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAClE,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;CACtE;AAED,mDAAmD;AACnD,MAAM,WAAW,wBAAwB;IACrC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,uEAAuE;IACvE,SAAS,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,cAAc,EAAE,2BAA2B,GAAG,IAAI,CAAC;IACnD,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,2JAahC,wBAAwB,KAAG,wBA+Y7B,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"useFileUploadState.d.ts","sourceRoot":"","sources":["../../../../../../../src/ui/screens/fileManagement/hooks/useFileUploadState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iDAA8C,CAAC;AAQlF,OAAO,EAEH,KAAK,iBAAiB,EAQzB,MAAM,cAAW,CAAC;AAEnB,qDAAqD;AACrD,MAAM,WAAW,2BAA2B;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACjB;AAsBD,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;IACrD,iBAAiB,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IACrD,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACvC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,iFAAiF;IACjF,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,mFAAmF;IACnF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,cAAc,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAClE,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC;CACtE;AAED,mDAAmD;AACnD,MAAM,WAAW,wBAAwB;IACrC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,uEAAuE;IACvE,SAAS,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,cAAc,EAAE,2BAA2B,GAAG,IAAI,CAAC;IACnD,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,iLAehC,wBAAwB,KAAG,wBAkb7B,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -7,6 +7,16 @@ export interface FileManagementScreenProps extends BaseScreenProps {
7
7
  selectMode?: boolean;
8
8
  multiSelect?: boolean;
9
9
  onSelect?: (file: FileMetadata) => void;
10
+ /**
11
+ * Forward-navigation picker: called with the picked file in single-select
12
+ * mode when the picker is one FRAME of a larger in-surface flow. Unlike
13
+ * {@link onSelect}, the caller is expected to NAVIGATE the surface onward
14
+ * (e.g. the avatar flow pushes the cropper on top of this picker) — so on
15
+ * select the picker neither dismisses nor closes the session, and its
16
+ * Cancel/back pops THIS frame (returning to the frame that opened the picker)
17
+ * rather than closing. Takes precedence over `onSelect`/`dismiss` when set.
18
+ */
19
+ onPicked?: (file: FileMetadata) => void;
10
20
  onConfirmSelection?: OnConfirmFileSelection;
11
21
  initialSelectedIds?: string[];
12
22
  maxSelection?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"fileManagement.d.ts","sourceRoot":"","sources":["../../../../../src/ui/types/fileManagement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAc,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;AAErE,MAAM,WAAW,yBAA0B,SAAQ,eAAe;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAExC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAE5C,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtD;;;OAGG;IACH,WAAW,CAAC,EAAE;QACV,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACL;AAED,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AAC3E,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AACvD,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC"}
1
+ {"version":3,"file":"fileManagement.d.ts","sourceRoot":"","sources":["../../../../../src/ui/types/fileManagement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAc,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;AAErE,MAAM,WAAW,yBAA0B,SAAQ,eAAe;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAExC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;IAE5C,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtD;;;OAGG;IACH,WAAW,CAAC,EAAE;QACV,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACL;AAED,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AAC3E,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AACvD,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "22.12.1",
3
+ "version": "22.12.3",
4
4
  "description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -127,7 +127,7 @@
127
127
  },
128
128
  "dependencies": {
129
129
  "@oxyhq/contracts": "0.17.0",
130
- "@oxyhq/core": "^12.10.3",
130
+ "@oxyhq/core": "^12.10.4",
131
131
  "@simplewebauthn/browser": "^13.3.0",
132
132
  "color": "^4.2.3"
133
133
  },
@@ -136,7 +136,7 @@
136
136
  "@commitlint/cli": "^17.6.5",
137
137
  "@commitlint/config-conventional": "^17.6.5",
138
138
  "@expo/vector-icons": "^15.0.3",
139
- "@oxyhq/core": "12.10.3",
139
+ "@oxyhq/core": "12.10.4",
140
140
  "@react-native-async-storage/async-storage": "^2.0.0",
141
141
  "@react-native-community/netinfo": "^11.4.1",
142
142
  "@react-native/babel-preset": "^0.86.0",
@@ -176,7 +176,7 @@
176
176
  },
177
177
  "peerDependencies": {
178
178
  "@expo/vector-icons": "^15.0.3",
179
- "@oxyhq/bloom": "^0.50.0",
179
+ "@oxyhq/bloom": "^0.50.1",
180
180
  "@oxyhq/core": "^12.10.0",
181
181
  "@react-native-async-storage/async-storage": "^2.0.0",
182
182
  "@react-native-community/netinfo": "^11.4.1",
@@ -178,17 +178,14 @@ const FollowButtonMultiInner = memo(function FollowButtonMultiInner({
178
178
 
179
179
  const isLoading = isSubmitting || isAnyLoading;
180
180
 
181
- // Populate the store with each member's follow status once on mount and again
182
- // only when the SET of target users changes. `fetchAllStatuses` is referentially
183
- // stable (its useFollow useCallback deps are [canUsePrivateApi, userIds,
184
- // oxyServices]; `userIds` inside useFollow is memoized, and the outer wrapper
185
- // memoizes `multiUserIds`), so depending on it plus the stable joined-string
186
- // key cannot self-retrigger — `allFollowing`/loading are intentionally NOT in
181
+ // Populate the store with each member's follow status on mount and whenever
182
+ // the target set changes. `fetchAllStatuses` is recreated when `userIds`
183
+ // changes inside `useFollow`, so it is the sole effect dependency — no need
184
+ // for a joined-string key. `allFollowing`/loading are intentionally NOT in
187
185
  // the deps.
188
- const userIdsKey = useMemo(() => userIds.join(','), [userIds]);
189
186
  useEffect(() => {
190
187
  fetchAllStatuses?.();
191
- }, [userIdsKey, fetchAllStatuses]);
188
+ }, [fetchAllStatuses]);
192
189
 
193
190
  const handlePress = useCallback(async (event?: { preventDefault?: () => void; stopPropagation?: () => void }) => {
194
191
  if (preventParentActions && event?.preventDefault) {
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, useLayoutEffect } from 'react';
1
+ import { createContext, useContext, useLayoutEffect, useRef } from 'react';
2
2
  import type { DialogHeaderConfig } from '@oxyhq/bloom/dialog';
3
3
 
4
4
  /**
@@ -42,21 +42,78 @@ interface SurfaceHeaderContextValue {
42
42
  */
43
43
  export const SurfaceHeaderContext = createContext<SurfaceHeaderContextValue | null>(null);
44
44
 
45
+ /**
46
+ * Shallow value-equality for a surface's header contribution.
47
+ *
48
+ * Scalars (`title`/`subtitle`/`largeTitle`/`tone`) compare by value; the object
49
+ * and node fields (`titleContent`/`left`/`right`/`primaryAction`/`actions`/
50
+ * `search`/`segments`/`progress`) compare by identity — callers memoize them, per
51
+ * this hook's contract. Function-typed affordances compare by PRESENCE, never
52
+ * identity: `onBack` legitimately gets a fresh closure each render, and its
53
+ * identity never changes what the header renders, so it must not count as a
54
+ * change (that is exactly what would drive the update loop).
55
+ */
56
+ function surfaceHeaderContentEqual(
57
+ a: SurfaceHeaderContent | null,
58
+ b: SurfaceHeaderContent | null,
59
+ ): boolean {
60
+ if (a === b) return true;
61
+ if (!a || !b) return false;
62
+ return (
63
+ a.title === b.title &&
64
+ a.titleContent === b.titleContent &&
65
+ a.subtitle === b.subtitle &&
66
+ a.largeTitle === b.largeTitle &&
67
+ a.left === b.left &&
68
+ a.right === b.right &&
69
+ !!a.onBack === !!b.onBack &&
70
+ a.primaryAction === b.primaryAction &&
71
+ a.actions === b.actions &&
72
+ a.search === b.search &&
73
+ a.segments === b.segments &&
74
+ a.tone === b.tone &&
75
+ a.progress === b.progress
76
+ );
77
+ }
78
+
45
79
  /**
46
80
  * Declare the Dialog nav header's content from within a surface screen — its
47
81
  * title/subtitle and any action slot. Merges over nothing (the host owns
48
82
  * back/close), replaces on change, and clears on unmount. Call it unconditionally;
49
83
  * it is a no-op outside a header-mode surface.
84
+ *
85
+ * Screens pass an INLINE `content` object (a fresh reference every render), so the
86
+ * push is guarded by {@link surfaceHeaderContentEqual}: the host's `setContent` is
87
+ * called only when the VALUE actually changes. Without that guard the setState
88
+ * would fire every render → the screen re-renders → the effect runs again → an
89
+ * infinite update loop (React error #185) in any consumer that does not memoize
90
+ * the object for us (i.e. anything not compiled by the React Compiler).
50
91
  */
51
92
  export function useSurfaceHeader(content: SurfaceHeaderContent | null | undefined): void {
52
93
  const ctx = useContext(SurfaceHeaderContext);
53
94
  const set = ctx?.setContent;
54
- // Set synchronously in the commit's layout phase so the bar/title fill in
55
- // BEFORE the browser paints — no first-frame flash of an empty bar.
95
+ const setRef = useRef(set);
96
+ setRef.current = set;
97
+ // The last value pushed to the host; `undefined` until the first push.
98
+ const lastRef = useRef<SurfaceHeaderContent | null | undefined>(undefined);
99
+
100
+ // Runs after EVERY commit (no deps array) so the bar/title fill in the same
101
+ // layout phase — but only pushes on a real value change, never on reference churn.
56
102
  useLayoutEffect(() => {
57
- if (!set) return;
58
- set(content ?? null);
59
- return () => set(null);
60
- // Callers must memoize `content` (especially slot nodes) so the header does not thrash.
61
- }, [set, content]);
103
+ const s = setRef.current;
104
+ if (!s) return;
105
+ const next = content ?? null;
106
+ if (lastRef.current !== undefined && surfaceHeaderContentEqual(lastRef.current, next)) return;
107
+ lastRef.current = next;
108
+ s(next);
109
+ });
110
+
111
+ // Clear this surface's contribution when the screen unmounts.
112
+ useLayoutEffect(
113
+ () => () => {
114
+ lastRef.current = undefined;
115
+ setRef.current?.(null);
116
+ },
117
+ [],
118
+ );
62
119
  }
@@ -45,8 +45,9 @@ describe('createSurfaceNavStack', () => {
45
45
  expect(stack.store.getState().closeResult).toBe('done');
46
46
  });
47
47
 
48
- describe('result-bearing sub-flow (morphed-in avatar picker)', () => {
48
+ describe('result-bearing sub-flow (morphed-in avatar flow)', () => {
49
49
  it('resolves with the descendant dismiss result and pops back to the caller frame', async () => {
50
+ // Device / camera path: ChangeAvatar navigates straight to the cropper.
50
51
  const stack = createSurfaceNavStack('EditProfile');
51
52
  const flow = stack.beginFlow('ChangeAvatar');
52
53
  expect(stack.getTop().route).toBe('ChangeAvatar');
@@ -84,39 +85,70 @@ describe('createSurfaceNavStack', () => {
84
85
  await expect(flow).resolves.toBeUndefined();
85
86
  });
86
87
 
87
- it('NESTS flows: the inner selector resolves first, then the outer avatar flow', async () => {
88
- // ChangeAvatar (outer) → "My Oxy files" FileManagement (inner)back → crop.
88
+ it('My Oxy files: the picker and cropper are FORWARD frames; the cropper confirm resolves the flow', async () => {
89
+ // EditProfile (flow) ChangeAvatar → FileManagement pickerAvatarCrop, all
90
+ // plain forward frames in the ONE avatar flow. Selecting a file forward-
91
+ // navigates to the cropper (it does NOT resolve the flow); the cropper's
92
+ // confirm resolves the flow and pops all the way back to EditProfile.
89
93
  const stack = createSurfaceNavStack('EditProfile');
90
- const avatarFlow = stack.beginFlow('ChangeAvatar');
91
- const fileFlow = stack.beginFlow('FileManagement');
94
+ const flow = stack.beginFlow('ChangeAvatar');
95
+ stack.navigate('FileManagement', { selectMode: true }); // "My Oxy files" picker
96
+ stack.navigate('AvatarCrop', { imageFileId: 'file-1' }); // forward-nav on pick
97
+ stack.resolveFlowOrDismiss({ uri: 'file:///cropped.jpg' }); // cropper confirm
98
+ await expect(flow).resolves.toEqual({ uri: 'file:///cropped.jpg' });
99
+ expect(stack.getTop().route).toBe('EditProfile');
100
+ expect(stack.store.getState().closing).toBe(false);
101
+ });
102
+
103
+ it('My Oxy files: Back unwinds cropper → picker → list, then cancels the flow', async () => {
104
+ // The bug this fixes: Back from the cropper must return to the PICKER (re-pick),
105
+ // not straight to the ChangeAvatar list.
106
+ const stack = createSurfaceNavStack('EditProfile');
107
+ const flow = stack.beginFlow('ChangeAvatar');
108
+ stack.navigate('FileManagement', { selectMode: true });
109
+ stack.navigate('AvatarCrop', { imageFileId: 'file-1' });
110
+ expect(stack.goBack()).toBe(true); // crop -> picker (re-pick)
111
+ expect(stack.getTop().route).toBe('FileManagement');
112
+ expect(stack.goBack()).toBe(true); // picker -> ChangeAvatar
113
+ expect(stack.getTop().route).toBe('ChangeAvatar');
114
+ expect(stack.goBack()).toBe(true); // ChangeAvatar -> EditProfile (cancels flow)
115
+ await expect(flow).resolves.toBeUndefined();
116
+ expect(stack.getTop().route).toBe('EditProfile');
117
+ expect(stack.store.getState().closing).toBe(false);
118
+ });
119
+
120
+ // The stack still supports NESTED flows generically, even though no current
121
+ // caller opens a flow inside another (the avatar flow uses one flow + forward
122
+ // frames). These guard that capability.
123
+ it('supports NESTED flows: the inner flow resolves first, then the outer one', async () => {
124
+ const stack = createSurfaceNavStack('EditProfile');
125
+ const outer = stack.beginFlow('ChangeAvatar');
126
+ const inner = stack.beginFlow('FileManagement');
92
127
  expect(stack.getTop().route).toBe('FileManagement');
93
- // Pick a file → inner flow resolves, pops back to ChangeAvatar (outer still open).
94
128
  stack.resolveFlowOrDismiss({ id: 'file-1' });
95
- await expect(fileFlow).resolves.toEqual({ id: 'file-1' });
129
+ await expect(inner).resolves.toEqual({ id: 'file-1' });
96
130
  expect(stack.getTop().route).toBe('ChangeAvatar');
97
131
  expect(stack.store.getState().closing).toBe(false);
98
- // Now drill to crop and confirm → outer flow resolves, pops to EditProfile.
99
132
  stack.navigate('AvatarCrop');
100
133
  stack.resolveFlowOrDismiss({ uri: 'file:///c.jpg' });
101
- await expect(avatarFlow).resolves.toEqual({ uri: 'file:///c.jpg' });
134
+ await expect(outer).resolves.toEqual({ uri: 'file:///c.jpg' });
102
135
  expect(stack.getTop().route).toBe('EditProfile');
103
136
  });
104
137
 
105
- it('cancelling the inner selector returns to the outer flow, leaving it open', async () => {
138
+ it('backing out of a NESTED inner flow leaves the outer flow pending', async () => {
106
139
  const stack = createSurfaceNavStack('EditProfile');
107
- const avatarFlow = stack.beginFlow('ChangeAvatar');
108
- const fileFlow = stack.beginFlow('FileManagement');
109
- expect(stack.goBack()).toBe(true); // FileManagement -> ChangeAvatar (cancels inner)
110
- await expect(fileFlow).resolves.toBeUndefined();
140
+ const outer = stack.beginFlow('ChangeAvatar');
141
+ const inner = stack.beginFlow('FileManagement');
142
+ expect(stack.goBack()).toBe(true); // inner entry backed out -> cancels inner only
143
+ await expect(inner).resolves.toBeUndefined();
111
144
  expect(stack.getTop().route).toBe('ChangeAvatar');
112
- // The outer avatar flow is still pending.
113
145
  let outerSettled = false;
114
- void avatarFlow.then(() => { outerSettled = true; });
146
+ void outer.then(() => { outerSettled = true; });
115
147
  await Promise.resolve();
116
148
  expect(outerSettled).toBe(false);
117
149
  });
118
150
 
119
- it('tearing down the surface abandons ALL nested flows (undefined)', async () => {
151
+ it('tearing down the surface abandons ALL pending flows (undefined)', async () => {
120
152
  const stack = createSurfaceNavStack('EditProfile');
121
153
  const outer = stack.beginFlow('ChangeAvatar');
122
154
  const inner = stack.beginFlow('FileManagement');
@@ -54,10 +54,13 @@ export interface SurfaceNavStack {
54
54
  * `dismiss(result)` resolves it with `result`, backing out of the flow's entry
55
55
  * frame resolves it with `undefined` (cancelled) — popping the surface back to
56
56
  * the frame that started the flow WITHOUT dismissing it. This is how a screen
57
- * already living inside a surface (EditProfile, ManageAccount, ChangeAvatar)
58
- * opens the avatar picker / media selector as a MORPH instead of a stacked
59
- * surface while still awaiting its result. Flows NEST (ChangeAvatar the "My
60
- * Oxy files" selector → back), maintained as a stack.
57
+ * already living inside a surface (EditProfile, ManageAccount) opens the avatar
58
+ * flow as a MORPH instead of a stacked surface while still awaiting its result.
59
+ * The avatar flow is ONE flow (`ChangeAvatar`) with plain forward frames pushed
60
+ * on top (the "My Oxy files" `FileManagement` picker, then `AvatarCrop`); the
61
+ * cropper's `dismiss(result)` resolves THIS flow. Flows are kept as a STACK so
62
+ * they can nest generically, even though no current caller opens a flow inside
63
+ * another.
61
64
  */
62
65
  beginFlow(route: RouteName, props?: Record<string, unknown>): Promise<unknown>;
63
66
  /**
@@ -94,13 +97,14 @@ export function createSurfaceNavStack(
94
97
  return frames[frames.length - 1];
95
98
  };
96
99
 
97
- // Result-bearing sub-flows currently running in this surface (the morphed-in
98
- // avatar picker, and NESTED within it the "My Oxy files" media selector).
99
- // A STACK: flows nest (ChangeAvatar FileManagement back to ChangeAvatar).
100
- // Held OUTSIDE the store (resolvers are functions; flow presence must not
101
- // re-render frames). Each flow's `returnLength` is the frame count BEFORE its
102
- // entry frame was pushed, so settling pops back to exactly the frame that
103
- // started it. `flowStack` is ordered outer → inner; the top is the innermost.
100
+ // Result-bearing sub-flows currently running in this surface (e.g. the morphed-
101
+ // in avatar flow, whose `ChangeAvatar FileManagement picker AvatarCrop`
102
+ // frames are plain forward pushes ON TOP of the ONE flow entry frame). Held as a
103
+ // STACK so flows can nest generically, and OUTSIDE the store (resolvers are
104
+ // functions; flow presence must not re-render frames). Each flow's `returnLength`
105
+ // is the frame count BEFORE its entry frame was pushed, so settling pops back to
106
+ // exactly the frame that started it. `flowStack` is ordered outer → inner; the
107
+ // top is the innermost.
104
108
  const flowStack: { returnLength: number; resolve: (result: unknown) => void }[] = [];
105
109
 
106
110
  const requestDismiss = (result?: unknown): void => {
@@ -5,7 +5,6 @@ import {
5
5
  type PresentOptions,
6
6
  type SurfaceControls,
7
7
  } from '@oxyhq/bloom/surfaces';
8
- import SurfaceScreen from '../components/SurfaceScreen';
9
8
  import type { RouteName } from './routes';
10
9
  import {
11
10
  getSurfaceConfig,
@@ -154,6 +153,15 @@ function presentInternal<K extends RouteName>(
154
153
  const config = getSurfaceConfig(route, props);
155
154
  const bloomOpts: PresentOptions = { label: route, ...bloomOptionsFor(config), ...opts };
156
155
 
156
+ // Lazy-resolved to break the surfaces.ts <-> SurfaceScreen module cycle: a
157
+ // mounted SurfaceScreen presents further surfaces (it imports these presenters),
158
+ // while presenting one renders a SurfaceScreen. Deferring the component to call
159
+ // time (both modules are initialized by then) makes the import graph one-way —
160
+ // the same idiom `routes.ts` uses to lazy-load screen components.
161
+ const SurfaceScreen = (
162
+ require('../components/SurfaceScreen') as typeof import('../components/SurfaceScreen')
163
+ ).default;
164
+
157
165
  const result = bloomSurfaces.present<SurfaceResult<K>>((surface: SurfaceControls) =>
158
166
  createElement(SurfaceScreen, {
159
167
  navStack,