@oxyhq/bloom 1.13.0 → 1.14.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.
Files changed (81) hide show
  1. package/README.md +1 -1
  2. package/docs/avatar.mdx +46 -5
  3. package/docs/index.mdx +1 -1
  4. package/docs/tab-bar.mdx +35 -0
  5. package/lib/commonjs/avatar/Avatar.js +24 -8
  6. package/lib/commonjs/avatar/Avatar.js.map +1 -1
  7. package/lib/commonjs/avatar/AvatarRing.js +13 -12
  8. package/lib/commonjs/avatar/AvatarRing.js.map +1 -1
  9. package/lib/commonjs/avatar/index.js +19 -0
  10. package/lib/commonjs/avatar/index.js.map +1 -1
  11. package/lib/commonjs/avatar/resolve-shape.js +43 -0
  12. package/lib/commonjs/avatar/resolve-shape.js.map +1 -0
  13. package/lib/commonjs/avatar/shape-paths.js +174 -0
  14. package/lib/commonjs/avatar/shape-paths.js.map +1 -0
  15. package/lib/commonjs/tab-bar/TabBarBase.js +53 -7
  16. package/lib/commonjs/tab-bar/TabBarBase.js.map +1 -1
  17. package/lib/commonjs/tab-bar/expo-router/RouterTabBar.js +5 -0
  18. package/lib/commonjs/tab-bar/expo-router/RouterTabBar.js.map +1 -1
  19. package/lib/module/avatar/Avatar.js +24 -8
  20. package/lib/module/avatar/Avatar.js.map +1 -1
  21. package/lib/module/avatar/AvatarRing.js +13 -12
  22. package/lib/module/avatar/AvatarRing.js.map +1 -1
  23. package/lib/module/avatar/index.js +1 -0
  24. package/lib/module/avatar/index.js.map +1 -1
  25. package/lib/module/avatar/resolve-shape.js +40 -0
  26. package/lib/module/avatar/resolve-shape.js.map +1 -0
  27. package/lib/module/avatar/shape-paths.js +170 -0
  28. package/lib/module/avatar/shape-paths.js.map +1 -0
  29. package/lib/module/tab-bar/TabBarBase.js +54 -8
  30. package/lib/module/tab-bar/TabBarBase.js.map +1 -1
  31. package/lib/module/tab-bar/expo-router/RouterTabBar.js +5 -0
  32. package/lib/module/tab-bar/expo-router/RouterTabBar.js.map +1 -1
  33. package/lib/typescript/commonjs/avatar/Avatar.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/avatar/AvatarRing.d.ts +4 -3
  35. package/lib/typescript/commonjs/avatar/AvatarRing.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/avatar/index.d.ts +3 -1
  37. package/lib/typescript/commonjs/avatar/index.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/avatar/resolve-shape.d.ts +17 -0
  39. package/lib/typescript/commonjs/avatar/resolve-shape.d.ts.map +1 -0
  40. package/lib/typescript/commonjs/avatar/shape-paths.d.ts +27 -0
  41. package/lib/typescript/commonjs/avatar/shape-paths.d.ts.map +1 -0
  42. package/lib/typescript/commonjs/avatar/types.d.ts +30 -3
  43. package/lib/typescript/commonjs/avatar/types.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/tab-bar/TabBarBase.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/tab-bar/expo-router/RouterTabBar.d.ts +5 -0
  46. package/lib/typescript/commonjs/tab-bar/expo-router/RouterTabBar.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/tab-bar/types.d.ts +36 -5
  48. package/lib/typescript/commonjs/tab-bar/types.d.ts.map +1 -1
  49. package/lib/typescript/module/avatar/Avatar.d.ts.map +1 -1
  50. package/lib/typescript/module/avatar/AvatarRing.d.ts +4 -3
  51. package/lib/typescript/module/avatar/AvatarRing.d.ts.map +1 -1
  52. package/lib/typescript/module/avatar/index.d.ts +3 -1
  53. package/lib/typescript/module/avatar/index.d.ts.map +1 -1
  54. package/lib/typescript/module/avatar/resolve-shape.d.ts +17 -0
  55. package/lib/typescript/module/avatar/resolve-shape.d.ts.map +1 -0
  56. package/lib/typescript/module/avatar/shape-paths.d.ts +27 -0
  57. package/lib/typescript/module/avatar/shape-paths.d.ts.map +1 -0
  58. package/lib/typescript/module/avatar/types.d.ts +30 -3
  59. package/lib/typescript/module/avatar/types.d.ts.map +1 -1
  60. package/lib/typescript/module/tab-bar/TabBarBase.d.ts.map +1 -1
  61. package/lib/typescript/module/tab-bar/expo-router/RouterTabBar.d.ts +5 -0
  62. package/lib/typescript/module/tab-bar/expo-router/RouterTabBar.d.ts.map +1 -1
  63. package/lib/typescript/module/tab-bar/types.d.ts +36 -5
  64. package/lib/typescript/module/tab-bar/types.d.ts.map +1 -1
  65. package/package.json +1 -1
  66. package/src/avatar/Avatar.tsx +27 -10
  67. package/src/avatar/AvatarRing.tsx +21 -14
  68. package/src/avatar/index.ts +7 -0
  69. package/src/avatar/resolve-shape.ts +33 -0
  70. package/src/avatar/shape-paths.ts +150 -0
  71. package/src/avatar/types.ts +32 -3
  72. package/src/tab-bar/TabBarBase.tsx +69 -10
  73. package/src/tab-bar/expo-router/RouterTabBar.tsx +5 -0
  74. package/src/tab-bar/types.ts +36 -5
  75. package/src/__tests__/support/collision-fixture-barrel.ts +0 -20
  76. package/src/__tests__/support/constructed-style-sheets.ts +0 -68
  77. package/src/__tests__/support/press-host.ts +0 -30
  78. package/src/__tests__/support/rendered-style.ts +0 -99
  79. package/src/__tests__/support/unread-hook-fixture.ts +0 -33
  80. package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +0 -7682
  81. package/src/theme/__tests__/fixtures/color-engine-golden.json +0 -1
@@ -1,6 +1,27 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { ImageSourcePropType, StyleProp, ViewStyle, ImageStyle } from 'react-native';
3
- export type AvatarShape = 'circle' | 'squircle';
3
+ import type { AvatarShapeName } from './shape-paths';
4
+ /**
5
+ * Built-in avatar outlines: the two originals plus the named registry in
6
+ * `shape-paths.ts`. Any of these can be passed as a plain string.
7
+ */
8
+ export type AvatarShape = 'circle' | 'squircle' | AvatarShapeName;
9
+ /**
10
+ * A caller-supplied outline, for shapes that are not in the built-in registry.
11
+ *
12
+ * The path is clipped against the avatar box, so it should fill its coordinate
13
+ * space edge to edge; anything outside is cut off rather than scaled to fit.
14
+ */
15
+ export interface AvatarShapePath {
16
+ /** SVG path data (the `d` attribute) describing a closed outline. */
17
+ d: string;
18
+ /**
19
+ * Side length of the square coordinate space `d` is drawn in — 100 for a
20
+ * path written over a 0–100 grid, 1 for one written over a 0–1 grid.
21
+ * Defaults to 1.
22
+ */
23
+ viewBox?: number;
24
+ }
4
25
  /** Gradient sweep direction for a multi-color {@link AvatarRingConfig}. */
5
26
  export type AvatarRingGradientDirection = 'diagonal' | 'horizontal' | 'vertical';
6
27
  export interface AvatarRingConfig {
@@ -39,8 +60,14 @@ export interface AvatarProps {
39
60
  verified?: boolean;
40
61
  /** Custom verified badge icon (rendered at bottom-right) */
41
62
  verifiedIcon?: ReactNode;
42
- /** Shape of the avatar (defaults to 'circle'). 'squircle' requires react-native-svg. */
43
- shape?: AvatarShape;
63
+ /**
64
+ * Shape of the avatar (defaults to `'circle'`). Accepts a built-in name —
65
+ * `'squircle'`, `'heart'`, `'pentagon'`, … see {@link AvatarShapeName} — or an
66
+ * {@link AvatarShapePath} with your own outline. Everything except `'circle'`
67
+ * clips through `react-native-svg`; a name that does not resolve falls back to
68
+ * a circle.
69
+ */
70
+ shape?: AvatarShape | AvatarShapePath;
44
71
  /** Container style */
45
72
  style?: StyleProp<ViewStyle>;
46
73
  /** Image style (circle shape only) */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/avatar/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI1F,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEhD,2EAA2E;AAC3E,MAAM,MAAM,2BAA2B,GAAG,UAAU,GAAG,YAAY,GAAG,UAAU,CAAC;AAEjF,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,2BAA2B,CAAC;CACjD;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAAC;IAC7C,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,wFAAwF;IACxF,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,sBAAsB;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,sCAAsC;IACtC,UAAU,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACnC,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oFAAoF;IACpF,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/avatar/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI1F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,eAAe,CAAC;AAElE;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,CAAC,EAAE,MAAM,CAAC;IACV;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,2EAA2E;AAC3E,MAAM,MAAM,2BAA2B,GAAG,UAAU,GAAG,YAAY,GAAG,UAAU,CAAC;AAEjF,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,2BAA2B,CAAC;CACjD;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAAC;IAC7C,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,WAAW,GAAG,eAAe,CAAC;IACtC,sBAAsB;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,sCAAsC;IACtC,UAAU,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACnC,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oFAAoF;IACpF,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"TabBarBase.d.ts","sourceRoot":"","sources":["../../../../src/tab-bar/TabBarBase.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,OAAO,CAAC;AAmBf,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,OAAO,EAiBL,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAujB3E;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAC1C,IAAI,EAAE,aAAa,CAAC,oBAAoB,CAAC;YAElB,WAAW;;EAGnC;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,CAAC,gBAAgB,CAAC;YAC1C,iBAAiB;;EAG/C"}
1
+ {"version":3,"file":"TabBarBase.d.ts","sourceRoot":"","sources":["../../../../src/tab-bar/TabBarBase.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,OAAO,CAAC;AAoBf,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,OAAO,EAiBL,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAinB3E;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAC1C,IAAI,EAAE,aAAa,CAAC,oBAAoB,CAAC;YAElB,WAAW;;EAGnC;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,CAAC,gBAAgB,CAAC;YAC1C,iBAAiB;;EAG/C"}
@@ -16,6 +16,11 @@ import type { TabBarItem, TabBarProps } from '../types';
16
16
  * by each trigger's own focus (see `RouterTabBarButton`), which is what keeps it
17
17
  * correct through deep links, back gestures and any other programmatic
18
18
  * navigation. Setting both would put two writers on one shared value.
19
+ *
20
+ * `activeProgress` is NOT omitted, and that is not an oversight. It answers a
21
+ * different question — where the highlight is, not whether there is one — so a
22
+ * router-focused bar whose screens are PAGED can pass it and keep the focus
23
+ * path for selection. See `TabBarProps.activeProgress`.
19
24
  */
20
25
  export type RouterTabBarProps = TabListProps & Omit<TabBarProps, 'activeIndex'>;
21
26
  export type RouterTabBarButtonProps = TabTriggerSlotProps & {
@@ -1 +1 @@
1
- {"version":3,"file":"RouterTabBar.d.ts","sourceRoot":"","sources":["../../../../../src/tab-bar/expo-router/RouterTabBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGxE,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAExD;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;AAEhF,MAAM,MAAM,uBAAuB,GAAG,mBAAmB,GAAG;IAC1D,IAAI,EAAE,UAAU,CAAC;IACjB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,EAI3B,OAAO,EAAE,QAAQ,EACjB,GAAG,KAAK,EACT,EAAE,iBAAiB,2CAEnB;yBARe,YAAY;;;AAY5B;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,KAAK,EAIL,IAAI,EAAE,KAAK,EACX,GAAG,KAAK,EACT,EAAE,uBAAuB,2CAEzB;yBAVe,kBAAkB"}
1
+ {"version":3,"file":"RouterTabBar.d.ts","sourceRoot":"","sources":["../../../../../src/tab-bar/expo-router/RouterTabBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGxE,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAExD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;AAEhF,MAAM,MAAM,uBAAuB,GAAG,mBAAmB,GAAG;IAC1D,IAAI,EAAE,UAAU,CAAC;IACjB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,EAI3B,OAAO,EAAE,QAAQ,EACjB,GAAG,KAAK,EACT,EAAE,iBAAiB,2CAEnB;yBARe,YAAY;;;AAY5B;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,KAAK,EAIL,IAAI,EAAE,KAAK,EACX,GAAG,KAAK,EACT,EAAE,uBAAuB,2CAEzB;yBAVe,kBAAkB"}
@@ -4,14 +4,21 @@
4
4
  *
5
5
  * Public API types for `@oxyhq/bloom/tab-bar`.
6
6
  *
7
- * This module imports from `react` and `react-native` ONLY. It is resolved by a
8
- * consumer's `tsc`, by Metro, and by every web bundler alike, so a platform
9
- * import here (expo-glass-effect / expo-symbols / expo-router) would break all
10
- * three at once. Platform behaviour lives in `surface.native.tsx` /
11
- * `glyph.native.tsx` / `surface.web.tsx` and the `expo-router/` adapter.
7
+ * This module names no PLATFORM package. It is resolved by a consumer's `tsc`,
8
+ * by Metro, and by every web bundler alike, so an import here of
9
+ * expo-glass-effect / expo-symbols / expo-router / react-native-screens would
10
+ * break all three at once. Platform behaviour lives in `surface.native.tsx` /
11
+ * `glyph.native.tsx` / `surface.web.tsx` and the `expo-router/` adapter; the
12
+ * gate is `src/__tests__/TabBarWebFork.test.ts`.
13
+ *
14
+ * `react-native-reanimated` is exempt and always was: it is a REQUIRED peer that
15
+ * every neutral module in this family already imports at runtime
16
+ * (`TabBarBase.tsx`), and the one reference below is `import type`, erased
17
+ * before any bundler sees it.
12
18
  */
13
19
  import type { ReactNode } from 'react';
14
20
  import type { PressableProps, ViewProps } from 'react-native';
21
+ import type { SharedValue } from 'react-native-reanimated';
15
22
  export type TabBarItem = {
16
23
  /** Stable identity for the tab (a route name in router-driven usage). */
17
24
  name: string;
@@ -89,6 +96,30 @@ export type TabBarProps = ViewProps & {
89
96
  * highlight from its own `isFocused`.
90
97
  */
91
98
  activeIndex?: number;
99
+ /**
100
+ * DRIVEN path — the highlight's live position, in TAB UNITS, on the UI thread.
101
+ * A pager writes its own page position here (`1.4` is 40% of the way from the
102
+ * second tab to the third) and the highlight tracks the finger 1:1 instead of
103
+ * springing to a settled index after the navigation commits.
104
+ *
105
+ * It says WHERE the highlight is, never WHETHER there is one. Selection stays
106
+ * with the path already in play: `activeIndex` when the bar is controlled (an
107
+ * index naming no tab still fades the highlight out — see above), each
108
+ * button's `isFocused` when it is not. Passing this alone, with neither, is a
109
+ * bar that is always selected, which is what the focus-driven path already
110
+ * means.
111
+ *
112
+ * PRECEDENCE, because a shared value with two writers is a race: the SCRUB
113
+ * always wins — a finger on the bar is a direct manipulation of the bar, and
114
+ * releasing it reports through `onIndexChange` like any other selection. Below
115
+ * that, this value wins over every discrete writer: while it is supplied, the
116
+ * controlled effect, the focus effect and a `TabBarButton`'s own press all
117
+ * stop writing the position and leave it to the driver. So a consumer that
118
+ * passes this must keep it truthful — spring it to the settled index after a
119
+ * commit, and after any navigation it did not cause (a deep link, a back
120
+ * gesture), or the highlight stays where the last gesture left it.
121
+ */
122
+ activeProgress?: SharedValue<number>;
92
123
  /**
93
124
  * Called with the chosen index when a tab is picked by tap, by scrub release,
94
125
  * or by keyboard/assistive-technology activation of a `TabBarButton`.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/tab-bar/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9D,MAAM,MAAM,UAAU,GAAG;IACvB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;;OAUG;IACH,IAAI,EAAE,SAAS,CAAC;IAChB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG;IACpC;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAC/C,IAAI,EAAE,UAAU,CAAC;IACjB,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/tab-bar/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,MAAM,MAAM,UAAU,GAAG;IACvB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;;OAUG;IACH,IAAI,EAAE,SAAS,CAAC;IAChB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG;IACpC;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAC/C,IAAI,EAAE,UAAU,CAAC;IACjB,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Avatar.d.ts","sourceRoot":"","sources":["../../../../src/avatar/Avatar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAa/D,OAAO,KAAK,EAAE,WAAW,EAAoB,MAAM,SAAS,CAAC;AAsY7D,eAAO,MAAM,MAAM,yCAAwB,CAAC"}
1
+ {"version":3,"file":"Avatar.d.ts","sourceRoot":"","sources":["../../../../src/avatar/Avatar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAa/D,OAAO,KAAK,EAAE,WAAW,EAAqC,MAAM,SAAS,CAAC;AAuZ9E,eAAO,MAAM,MAAM,yCAAwB,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
- import type { AvatarRingGradientDirection, AvatarShape } from './types';
2
+ import type { ResolvedAvatarShape } from './resolve-shape';
3
+ import type { AvatarRingGradientDirection } from './types';
3
4
  /**
4
5
  * Size of the box the ring occupies. With `gap === 0` the ring overlays the
5
6
  * avatar edge and the box equals the avatar size (footprint unchanged). With
@@ -11,8 +12,8 @@ export declare function getRingOuterSize(size: number, width: number, gap: numbe
11
12
  interface AvatarRingProps {
12
13
  /** Avatar diameter in px (the inner content size, before any gap). */
13
14
  size: number;
14
- /** Matches the avatar shape. */
15
- shape: AvatarShape;
15
+ /** The avatar's resolved outline, or `null` for a circle. */
16
+ shape: ResolvedAvatarShape;
16
17
  /** Solid ring: one color. Gradient ring: 2+ colors. */
17
18
  colors: string | string[];
18
19
  /** Ring stroke width in px. */
@@ -1 +1 @@
1
- {"version":3,"file":"AvatarRing.d.ts","sourceRoot":"","sources":["../../../../src/avatar/AvatarRing.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwB,MAAM,OAAO,CAAC;AAM7C,OAAO,KAAK,EAAE,2BAA2B,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAOxE;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED,UAAU,eAAe;IACvB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,KAAK,EAAE,WAAW,CAAC;IACnB,uDAAuD;IACvD,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,sDAAsD;IACtD,iBAAiB,EAAE,2BAA2B,CAAC;CAChD;AA2HD,eAAO,MAAM,UAAU,6CAA4B,CAAC"}
1
+ {"version":3,"file":"AvatarRing.d.ts","sourceRoot":"","sources":["../../../../src/avatar/AvatarRing.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwB,MAAM,OAAO,CAAC;AAK7C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAO3D;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED,UAAU,eAAe;IACvB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,EAAE,mBAAmB,CAAC;IAC3B,uDAAuD;IACvD,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,sDAAsD;IACtD,iBAAiB,EAAE,2BAA2B,CAAC;CAChD;AAkID,eAAO,MAAM,UAAU,6CAA4B,CAAC"}
@@ -1,6 +1,8 @@
1
1
  export { Avatar } from './Avatar';
2
2
  export { AvatarPlaceholderProvider } from './context';
3
3
  export type { AvatarPlaceholderConfig } from './context';
4
- export type { AvatarProps, AvatarShape, AvatarRingConfig, AvatarRingGradientDirection, } from './types';
4
+ export type { AvatarProps, AvatarShape, AvatarShapePath, AvatarRingConfig, AvatarRingGradientDirection, } from './types';
5
5
  export { defaultAvatarSource } from './default-avatar';
6
+ export { AVATAR_SHAPE_PATHS, AVATAR_SHAPE_LABELS, NAMED_SHAPE_VIEW_BOX, } from './shape-paths';
7
+ export type { AvatarShapeName } from './shape-paths';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/avatar/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AACtD,YAAY,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACzD,YAAY,EACV,WAAW,EACX,WAAW,EACX,gBAAgB,EAChB,2BAA2B,GAC5B,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/avatar/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AACtD,YAAY,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACzD,YAAY,EACV,WAAW,EACX,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,2BAA2B,GAC5B,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { AvatarShape, AvatarShapePath } from './types';
2
+ /**
3
+ * A shape reduced to what the renderers need: either nothing (a circle, drawn
4
+ * with `borderRadius` and no SVG at all) or one outline plus the size of the
5
+ * square space it is drawn in.
6
+ */
7
+ export type ResolvedAvatarShape = AvatarShapePath | null;
8
+ /**
9
+ * Turns the public `shape` prop into an outline.
10
+ *
11
+ * Returns `null` for a circle so callers can keep the cheap `borderRadius`
12
+ * path — the common case never touches `react-native-svg`. An unknown string
13
+ * also returns `null`: a shape name that does not resolve should degrade to a
14
+ * plain circular avatar, never to a blank hole where a face should be.
15
+ */
16
+ export declare function resolveAvatarShape(shape: AvatarShape | AvatarShapePath | undefined): ResolvedAvatarShape;
17
+ //# sourceMappingURL=resolve-shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-shape.d.ts","sourceRoot":"","sources":["../../../../src/avatar/resolve-shape.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE5D;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAAG,IAAI,CAAC;AAEzD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,eAAe,GAAG,SAAS,GAAG,mBAAmB,CAaxG"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Named avatar outlines.
3
+ *
4
+ * Each entry is an SVG path drawn in a 100×100 square coordinate space, scaled
5
+ * at render time to the avatar's pixel size. That space is declared once in
6
+ * {@link NAMED_SHAPE_VIEW_BOX} rather than baked into the numbers, so the paths
7
+ * can be read and edited at human-friendly magnitudes. The built-in squircle
8
+ * lives in `squircle-path.ts` and uses a 0–1 space; both are supported because
9
+ * {@link AvatarShapePath} carries its own `viewBox`.
10
+ *
11
+ * `circle` is deliberately absent: a circular avatar is rendered with
12
+ * `borderRadius` and never reaches the SVG renderer.
13
+ */
14
+ /** Side length of the coordinate space every path below is drawn in. */
15
+ export declare const NAMED_SHAPE_VIEW_BOX = 100;
16
+ export type AvatarShapeName = 'square' | 'slanted' | 'arch' | 'semicircle' | 'oval' | 'pill' | 'triangle' | 'arrow' | 'fan' | 'diamond' | 'clamshell' | 'pentagon' | 'gem' | 'very-sunny' | 'sunny' | '4-sided-cookie' | '6-sided-cookie' | '7-sided-cookie' | '9-sided-cookie' | '12-sided-cookie' | '4-leaf-clover' | '8-leaf-clover' | 'burst' | 'soft-burst' | 'boom' | 'soft-boom' | 'flower' | 'puffy' | 'puffy-diamond' | 'ghost-ish' | 'pixel-circle' | 'pixel-triangle' | 'bun' | 'heart';
17
+ export declare const AVATAR_SHAPE_PATHS: Record<AvatarShapeName, string>;
18
+ /**
19
+ * Display labels for the named shapes, in the order a picker should show them.
20
+ * Exported so consumers building a shape picker do not re-derive the order (or
21
+ * the capitalisation) themselves.
22
+ */
23
+ export declare const AVATAR_SHAPE_LABELS: {
24
+ name: AvatarShapeName;
25
+ label: string;
26
+ }[];
27
+ //# sourceMappingURL=shape-paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shape-paths.d.ts","sourceRoot":"","sources":["../../../../src/avatar/shape-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,wEAAwE;AACxE,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,MAAM,MAAM,eAAe,GACvB,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,GAC5C,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,KAAK,GAC9C,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,KAAK,GAAG,YAAY,GAC3D,OAAO,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,gBAAgB,GAChE,gBAAgB,GAAG,iBAAiB,GAAG,eAAe,GACtD,eAAe,GAAG,OAAO,GAAG,YAAY,GAAG,MAAM,GAAG,WAAW,GAC/D,QAAQ,GAAG,OAAO,GAAG,eAAe,GAAG,WAAW,GAClD,cAAc,GAAG,gBAAgB,GAAG,KAAK,GAAG,OAAO,CAAC;AAExD,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAgF9D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAmCzE,CAAC"}
@@ -1,6 +1,27 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { ImageSourcePropType, StyleProp, ViewStyle, ImageStyle } from 'react-native';
3
- export type AvatarShape = 'circle' | 'squircle';
3
+ import type { AvatarShapeName } from './shape-paths';
4
+ /**
5
+ * Built-in avatar outlines: the two originals plus the named registry in
6
+ * `shape-paths.ts`. Any of these can be passed as a plain string.
7
+ */
8
+ export type AvatarShape = 'circle' | 'squircle' | AvatarShapeName;
9
+ /**
10
+ * A caller-supplied outline, for shapes that are not in the built-in registry.
11
+ *
12
+ * The path is clipped against the avatar box, so it should fill its coordinate
13
+ * space edge to edge; anything outside is cut off rather than scaled to fit.
14
+ */
15
+ export interface AvatarShapePath {
16
+ /** SVG path data (the `d` attribute) describing a closed outline. */
17
+ d: string;
18
+ /**
19
+ * Side length of the square coordinate space `d` is drawn in — 100 for a
20
+ * path written over a 0–100 grid, 1 for one written over a 0–1 grid.
21
+ * Defaults to 1.
22
+ */
23
+ viewBox?: number;
24
+ }
4
25
  /** Gradient sweep direction for a multi-color {@link AvatarRingConfig}. */
5
26
  export type AvatarRingGradientDirection = 'diagonal' | 'horizontal' | 'vertical';
6
27
  export interface AvatarRingConfig {
@@ -39,8 +60,14 @@ export interface AvatarProps {
39
60
  verified?: boolean;
40
61
  /** Custom verified badge icon (rendered at bottom-right) */
41
62
  verifiedIcon?: ReactNode;
42
- /** Shape of the avatar (defaults to 'circle'). 'squircle' requires react-native-svg. */
43
- shape?: AvatarShape;
63
+ /**
64
+ * Shape of the avatar (defaults to `'circle'`). Accepts a built-in name —
65
+ * `'squircle'`, `'heart'`, `'pentagon'`, … see {@link AvatarShapeName} — or an
66
+ * {@link AvatarShapePath} with your own outline. Everything except `'circle'`
67
+ * clips through `react-native-svg`; a name that does not resolve falls back to
68
+ * a circle.
69
+ */
70
+ shape?: AvatarShape | AvatarShapePath;
44
71
  /** Container style */
45
72
  style?: StyleProp<ViewStyle>;
46
73
  /** Image style (circle shape only) */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/avatar/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI1F,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEhD,2EAA2E;AAC3E,MAAM,MAAM,2BAA2B,GAAG,UAAU,GAAG,YAAY,GAAG,UAAU,CAAC;AAEjF,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,2BAA2B,CAAC;CACjD;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAAC;IAC7C,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,wFAAwF;IACxF,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,sBAAsB;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,sCAAsC;IACtC,UAAU,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACnC,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oFAAoF;IACpF,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/avatar/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI1F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,eAAe,CAAC;AAElE;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,CAAC,EAAE,MAAM,CAAC;IACV;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,2EAA2E;AAC3E,MAAM,MAAM,2BAA2B,GAAG,UAAU,GAAG,YAAY,GAAG,UAAU,CAAC;AAEjF,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,2BAA2B,CAAC;CACjD;AAED,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAAC;IAC7C,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,WAAW,GAAG,eAAe,CAAC;IACtC,sBAAsB;IACtB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,sCAAsC;IACtC,UAAU,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACnC,mFAAmF;IACnF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oFAAoF;IACpF,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"TabBarBase.d.ts","sourceRoot":"","sources":["../../../../src/tab-bar/TabBarBase.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,OAAO,CAAC;AAmBf,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,OAAO,EAiBL,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAujB3E;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAC1C,IAAI,EAAE,aAAa,CAAC,oBAAoB,CAAC;YAElB,WAAW;;EAGnC;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,CAAC,gBAAgB,CAAC;YAC1C,iBAAiB;;EAG/C"}
1
+ {"version":3,"file":"TabBarBase.d.ts","sourceRoot":"","sources":["../../../../src/tab-bar/TabBarBase.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,OAAO,CAAC;AAoBf,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,OAAO,EAiBL,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAinB3E;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAC1C,IAAI,EAAE,aAAa,CAAC,oBAAoB,CAAC;YAElB,WAAW;;EAGnC;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,CAAC,gBAAgB,CAAC;YAC1C,iBAAiB;;EAG/C"}
@@ -16,6 +16,11 @@ import type { TabBarItem, TabBarProps } from '../types';
16
16
  * by each trigger's own focus (see `RouterTabBarButton`), which is what keeps it
17
17
  * correct through deep links, back gestures and any other programmatic
18
18
  * navigation. Setting both would put two writers on one shared value.
19
+ *
20
+ * `activeProgress` is NOT omitted, and that is not an oversight. It answers a
21
+ * different question — where the highlight is, not whether there is one — so a
22
+ * router-focused bar whose screens are PAGED can pass it and keep the focus
23
+ * path for selection. See `TabBarProps.activeProgress`.
19
24
  */
20
25
  export type RouterTabBarProps = TabListProps & Omit<TabBarProps, 'activeIndex'>;
21
26
  export type RouterTabBarButtonProps = TabTriggerSlotProps & {
@@ -1 +1 @@
1
- {"version":3,"file":"RouterTabBar.d.ts","sourceRoot":"","sources":["../../../../../src/tab-bar/expo-router/RouterTabBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGxE,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAExD;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;AAEhF,MAAM,MAAM,uBAAuB,GAAG,mBAAmB,GAAG;IAC1D,IAAI,EAAE,UAAU,CAAC;IACjB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,EAI3B,OAAO,EAAE,QAAQ,EACjB,GAAG,KAAK,EACT,EAAE,iBAAiB,2CAEnB;yBARe,YAAY;;;AAY5B;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,KAAK,EAIL,IAAI,EAAE,KAAK,EACX,GAAG,KAAK,EACT,EAAE,uBAAuB,2CAEzB;yBAVe,kBAAkB"}
1
+ {"version":3,"file":"RouterTabBar.d.ts","sourceRoot":"","sources":["../../../../../src/tab-bar/expo-router/RouterTabBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGxE,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAExD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;AAEhF,MAAM,MAAM,uBAAuB,GAAG,mBAAmB,GAAG;IAC1D,IAAI,EAAE,UAAU,CAAC;IACjB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,EAI3B,OAAO,EAAE,QAAQ,EACjB,GAAG,KAAK,EACT,EAAE,iBAAiB,2CAEnB;yBARe,YAAY;;;AAY5B;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,KAAK,EAIL,IAAI,EAAE,KAAK,EACX,GAAG,KAAK,EACT,EAAE,uBAAuB,2CAEzB;yBAVe,kBAAkB"}
@@ -4,14 +4,21 @@
4
4
  *
5
5
  * Public API types for `@oxyhq/bloom/tab-bar`.
6
6
  *
7
- * This module imports from `react` and `react-native` ONLY. It is resolved by a
8
- * consumer's `tsc`, by Metro, and by every web bundler alike, so a platform
9
- * import here (expo-glass-effect / expo-symbols / expo-router) would break all
10
- * three at once. Platform behaviour lives in `surface.native.tsx` /
11
- * `glyph.native.tsx` / `surface.web.tsx` and the `expo-router/` adapter.
7
+ * This module names no PLATFORM package. It is resolved by a consumer's `tsc`,
8
+ * by Metro, and by every web bundler alike, so an import here of
9
+ * expo-glass-effect / expo-symbols / expo-router / react-native-screens would
10
+ * break all three at once. Platform behaviour lives in `surface.native.tsx` /
11
+ * `glyph.native.tsx` / `surface.web.tsx` and the `expo-router/` adapter; the
12
+ * gate is `src/__tests__/TabBarWebFork.test.ts`.
13
+ *
14
+ * `react-native-reanimated` is exempt and always was: it is a REQUIRED peer that
15
+ * every neutral module in this family already imports at runtime
16
+ * (`TabBarBase.tsx`), and the one reference below is `import type`, erased
17
+ * before any bundler sees it.
12
18
  */
13
19
  import type { ReactNode } from 'react';
14
20
  import type { PressableProps, ViewProps } from 'react-native';
21
+ import type { SharedValue } from 'react-native-reanimated';
15
22
  export type TabBarItem = {
16
23
  /** Stable identity for the tab (a route name in router-driven usage). */
17
24
  name: string;
@@ -89,6 +96,30 @@ export type TabBarProps = ViewProps & {
89
96
  * highlight from its own `isFocused`.
90
97
  */
91
98
  activeIndex?: number;
99
+ /**
100
+ * DRIVEN path — the highlight's live position, in TAB UNITS, on the UI thread.
101
+ * A pager writes its own page position here (`1.4` is 40% of the way from the
102
+ * second tab to the third) and the highlight tracks the finger 1:1 instead of
103
+ * springing to a settled index after the navigation commits.
104
+ *
105
+ * It says WHERE the highlight is, never WHETHER there is one. Selection stays
106
+ * with the path already in play: `activeIndex` when the bar is controlled (an
107
+ * index naming no tab still fades the highlight out — see above), each
108
+ * button's `isFocused` when it is not. Passing this alone, with neither, is a
109
+ * bar that is always selected, which is what the focus-driven path already
110
+ * means.
111
+ *
112
+ * PRECEDENCE, because a shared value with two writers is a race: the SCRUB
113
+ * always wins — a finger on the bar is a direct manipulation of the bar, and
114
+ * releasing it reports through `onIndexChange` like any other selection. Below
115
+ * that, this value wins over every discrete writer: while it is supplied, the
116
+ * controlled effect, the focus effect and a `TabBarButton`'s own press all
117
+ * stop writing the position and leave it to the driver. So a consumer that
118
+ * passes this must keep it truthful — spring it to the settled index after a
119
+ * commit, and after any navigation it did not cause (a deep link, a back
120
+ * gesture), or the highlight stays where the last gesture left it.
121
+ */
122
+ activeProgress?: SharedValue<number>;
92
123
  /**
93
124
  * Called with the chosen index when a tab is picked by tap, by scrub release,
94
125
  * or by keyboard/assistive-technology activation of a `TabBarButton`.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/tab-bar/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9D,MAAM,MAAM,UAAU,GAAG;IACvB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;;OAUG;IACH,IAAI,EAAE,SAAS,CAAC;IAChB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG;IACpC;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAC/C,IAAI,EAAE,UAAU,CAAC;IACjB,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/tab-bar/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,MAAM,MAAM,UAAU,GAAG;IACvB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;;OAUG;IACH,IAAI,EAAE,SAAS,CAAC;IAChB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG;IACpC;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAC/C,IAAI,EAAE,UAAU,CAAC;IACjB,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/bloom",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "packageManager": "bun@1.3.14",
5
5
  "description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
6
6
  "main": "lib/commonjs/index.js",
@@ -10,8 +10,8 @@ import { Z_INDEX } from '../styles/z-index';
10
10
  import { useAvatarPlaceholder } from './context';
11
11
  import { LiveBadge } from './LiveBadge';
12
12
  import { AvatarRing, getRingOuterSize } from './AvatarRing';
13
- import { SQUIRCLE_PATH } from './squircle-path';
14
- import type { AvatarProps, AvatarRingConfig } from './types';
13
+ import { resolveAvatarShape } from './resolve-shape';
14
+ import type { AvatarProps, AvatarRingConfig, AvatarShapePath } from './types';
15
15
 
16
16
  // Google Contacts-inspired palette used to pick a deterministic background
17
17
  // color for name-based placeholder avatars.
@@ -45,10 +45,19 @@ import DEFAULT_AVATAR_IMAGE from './default-avatar';
45
45
 
46
46
  let clipIdCounter = 0;
47
47
 
48
- function SquircleImage({
48
+ /**
49
+ * Renders the avatar image clipped to an arbitrary outline.
50
+ *
51
+ * The path and the SVG viewport share one coordinate space, so a 0–1 squircle
52
+ * and a 0–100 named shape both work without rewriting their numbers: the
53
+ * viewport is declared at the path's own scale and the whole thing is drawn at
54
+ * `size` pixels.
55
+ */
56
+ function ClippedImage({
49
57
  uri,
50
58
  fallbackSource,
51
59
  size,
60
+ shape,
52
61
  fallbackColor,
53
62
  placeholderIcon,
54
63
  name,
@@ -57,6 +66,7 @@ function SquircleImage({
57
66
  uri?: string;
58
67
  fallbackSource?: AvatarProps['fallbackSource'];
59
68
  size: number;
69
+ shape: AvatarShapePath;
60
70
  fallbackColor: string;
61
71
  placeholderIcon?: React.ReactNode;
62
72
  name?: string;
@@ -71,6 +81,8 @@ function SquircleImage({
71
81
  );
72
82
  }
73
83
 
84
+ const span = shape.viewBox ?? 1;
85
+
74
86
  return (
75
87
  <>
76
88
  {/* Hidden RN Image for error detection on remote URIs */}
@@ -81,16 +93,16 @@ function SquircleImage({
81
93
  onError={onError}
82
94
  />
83
95
  )}
84
- <Svg width={size} height={size} viewBox="0 0 1 1">
96
+ <Svg width={size} height={size} viewBox={`0 0 ${span} ${span}`}>
85
97
  <Defs>
86
98
  <ClipPath id={clipId}>
87
- <Path d={SQUIRCLE_PATH} />
99
+ <Path d={shape.d} />
88
100
  </ClipPath>
89
101
  </Defs>
90
102
  <SvgImage
91
103
  href={href}
92
- width={1}
93
- height={1}
104
+ width={span}
105
+ height={span}
94
106
  preserveAspectRatio="xMidYMid slice"
95
107
  clipPath={`url(#${clipId})`}
96
108
  />
@@ -183,6 +195,10 @@ const AvatarComponent: React.FC<AvatarProps> = ({
183
195
  const theme = useTheme();
184
196
  const placeholderConfig = useAvatarPlaceholder();
185
197
  const radius = size / 2;
198
+ // `null` means "a circle", which is drawn with borderRadius and never reaches
199
+ // the SVG renderer. Memoised so an inline `shape={{ d: … }}` object literal
200
+ // does not produce a new clip descriptor on every render.
201
+ const clipShape = useMemo(() => resolveAvatarShape(shape), [shape]);
186
202
  const hasName = typeof name === 'string' && name.trim().length > 0;
187
203
  // Priority: explicit placeholderColor > deterministic color from name > theme default.
188
204
  const fallbackColor =
@@ -265,7 +281,7 @@ const AvatarComponent: React.FC<AvatarProps> = ({
265
281
  const ringElement = resolvedRing ? (
266
282
  <AvatarRing
267
283
  size={size}
268
- shape={shape}
284
+ shape={clipShape}
269
285
  colors={resolvedRing.colors}
270
286
  width={ringWidth}
271
287
  gap={ringGap}
@@ -278,11 +294,12 @@ const AvatarComponent: React.FC<AvatarProps> = ({
278
294
  // fills the container exactly.
279
295
  const avatarBox = (
280
296
  <View style={[styles.avatarBox, { width: size, height: size }]}>
281
- {shape === 'squircle' ? (
282
- <SquircleImage
297
+ {clipShape ? (
298
+ <ClippedImage
283
299
  uri={effectiveUri}
284
300
  fallbackSource={fallbackSource}
285
301
  size={size}
302
+ shape={clipShape}
286
303
  fallbackColor={fallbackColor}
287
304
  placeholderIcon={resolvedPlaceholderIcon}
288
305
  name={name}
@@ -3,8 +3,8 @@ import { View, StyleSheet } from 'react-native';
3
3
  import Svg, { Circle, Defs, LinearGradient, Path, Stop } from 'react-native-svg';
4
4
 
5
5
  import { Z_INDEX } from '../styles/z-index';
6
- import { SQUIRCLE_PATH } from './squircle-path';
7
- import type { AvatarRingGradientDirection, AvatarShape } from './types';
6
+ import type { ResolvedAvatarShape } from './resolve-shape';
7
+ import type { AvatarRingGradientDirection } from './types';
8
8
 
9
9
  // Module counter for unique gradient element ids — mirrors the `clipIdCounter`
10
10
  // pattern in `Avatar.tsx`. Deterministic (no `Math.random()`) so ids are stable
@@ -25,8 +25,8 @@ export function getRingOuterSize(size: number, width: number, gap: number): numb
25
25
  interface AvatarRingProps {
26
26
  /** Avatar diameter in px (the inner content size, before any gap). */
27
27
  size: number;
28
- /** Matches the avatar shape. */
29
- shape: AvatarShape;
28
+ /** The avatar's resolved outline, or `null` for a circle. */
29
+ shape: ResolvedAvatarShape;
30
30
  /** Solid ring: one color. Gradient ring: 2+ colors. */
31
31
  colors: string | string[];
32
32
  /** Ring stroke width in px. */
@@ -79,10 +79,10 @@ const AvatarRingComponent: React.FC<AvatarRingProps> = ({
79
79
  // box; drawn as a plain border it is a full circle → radius `outer / 2`.
80
80
  const ringRadius = outer / 2;
81
81
 
82
- // SVG is only needed for gradients (both shapes) and for the squircle
83
- // outline. A solid circle ring is a plain bordered View — cheaper, and it
84
- // keeps the common case off the SVG renderer entirely.
85
- const needsSvg = wantsGradient || shape === 'squircle';
82
+ // SVG is only needed for gradients and for non-circular outlines. A solid
83
+ // circle ring is a plain bordered View — cheaper, and it keeps the common
84
+ // case off the SVG renderer entirely.
85
+ const needsSvg = wantsGradient || shape !== null;
86
86
 
87
87
  if (!needsSvg) {
88
88
  return (
@@ -127,15 +127,22 @@ const AvatarRingComponent: React.FC<AvatarRingProps> = ({
127
127
  { zIndex: gap > 0 ? Z_INDEX.base : Z_INDEX.raised },
128
128
  ];
129
129
 
130
- if (shape === 'squircle') {
131
- // The path lives in 0–1 space, so the stroke width is expressed in the same
132
- // units; center-stroking it and letting the viewport clip the outer half
133
- // yields a ~`width`-px band on the inner edge of the outer box.
130
+ if (shape !== null) {
131
+ // The path lives in its own square space, so the stroke width has to be
132
+ // expressed in those units too: one unit is `outer / span` px. Center-
133
+ // stroking and letting the viewport clip the outer half yields a
134
+ // ~`width`-px band on the inner edge of the outer box, hence the doubling.
135
+ const span = shape.viewBox ?? 1;
134
136
  return (
135
137
  <View pointerEvents="none" style={overlayStyle}>
136
- <Svg width={outer} height={outer} viewBox="0 0 1 1">
138
+ <Svg width={outer} height={outer} viewBox={`0 0 ${span} ${span}`}>
137
139
  {gradientDefs}
138
- <Path d={SQUIRCLE_PATH} fill="none" stroke={stroke} strokeWidth={(width / outer) * 2} />
140
+ <Path
141
+ d={shape.d}
142
+ fill="none"
143
+ stroke={stroke}
144
+ strokeWidth={(width / outer) * 2 * span}
145
+ />
139
146
  </Svg>
140
147
  </View>
141
148
  );
@@ -4,7 +4,14 @@ export type { AvatarPlaceholderConfig } from './context';
4
4
  export type {
5
5
  AvatarProps,
6
6
  AvatarShape,
7
+ AvatarShapePath,
7
8
  AvatarRingConfig,
8
9
  AvatarRingGradientDirection,
9
10
  } from './types';
10
11
  export { defaultAvatarSource } from './default-avatar';
12
+ export {
13
+ AVATAR_SHAPE_PATHS,
14
+ AVATAR_SHAPE_LABELS,
15
+ NAMED_SHAPE_VIEW_BOX,
16
+ } from './shape-paths';
17
+ export type { AvatarShapeName } from './shape-paths';