@tamagui/adapt 1.115.4 → 1.116.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Adapt.tsx CHANGED
@@ -1,4 +1,3 @@
1
- import React from 'react'
2
1
  import {
3
2
  isAndroid,
4
3
  isIos,
@@ -6,116 +5,252 @@ import {
6
5
  isWeb,
7
6
  useIsomorphicLayoutEffect,
8
7
  } from '@tamagui/constants'
9
- import type { MediaQueryKey, UseMediaState } from '@tamagui/core'
10
- import { useMedia } from '@tamagui/core'
8
+ import type { AllPlatforms, MediaQueryKey } from '@tamagui/core'
9
+ import { createStyledContext, useMedia } from '@tamagui/core'
11
10
  import { withStaticProperties } from '@tamagui/helpers'
11
+ import { PortalHost, PortalItem } from '@tamagui/portal'
12
+ import React, { createContext, useContext, useEffect, useId } from 'react'
13
+
14
+ /**
15
+ * Interfaces
16
+ */
17
+
18
+ export type AdaptWhen = MediaQueryKeyString | boolean | null
19
+ export type AdaptPlatform = AllPlatforms | 'touch' | null
20
+
21
+ type AdaptParentContextI = {
22
+ Contents: Component
23
+ scopeName: string
24
+ platform: AdaptPlatform
25
+ setPlatform: (when: AdaptPlatform) => any
26
+ when: AdaptWhen
27
+ setWhen: (when: AdaptWhen) => any
28
+ setChildren: (children: any) => any
29
+ portalName?: string
30
+ }
12
31
 
13
32
  type MediaQueryKeyString = MediaQueryKey extends string ? MediaQueryKey : never
14
33
 
15
34
  export type AdaptProps = {
16
- when?: MediaQueryKeyString | ((state: { media: UseMediaState }) => boolean)
17
- platform?: 'native' | 'web' | 'touch' | 'ios' | 'android'
18
- children:
19
- | JSX.Element
20
- | ((state: { enabled: boolean; media: UseMediaState }) => JSX.Element)
35
+ scope?: string
36
+ when?: AdaptWhen
37
+ platform?: AdaptPlatform
38
+ children: JSX.Element | ((children: React.ReactNode) => React.ReactNode)
21
39
  }
22
40
 
23
- type When = MediaQueryKeyString | boolean | null
24
-
25
41
  type Component = (props: any) => any
26
- type AdaptParentContextI = {
27
- Contents: Component
28
- setWhen: (when: When) => any
42
+
43
+ /**
44
+ * Contexts
45
+ */
46
+
47
+ const CurrentAdaptContextScope = createContext('')
48
+
49
+ export const AdaptContext = createStyledContext<AdaptParentContextI>({
50
+ Contents: null as any,
51
+ scopeName: '',
52
+ portalName: '',
53
+ platform: null as any,
54
+ setPlatform: null as any,
55
+ when: null as any,
56
+ setChildren: null as any,
57
+ setWhen: null as any,
58
+ })
59
+
60
+ const ProvideAdaptContext = ({
61
+ children,
62
+ ...context
63
+ }: AdaptParentContextI & { children: any }) => {
64
+ const scope = context.scopeName || ''
65
+
66
+ return (
67
+ <CurrentAdaptContextScope.Provider value={scope}>
68
+ <AdaptContext.Provider scope={scope} {...context}>
69
+ {children}
70
+ </AdaptContext.Provider>
71
+ </CurrentAdaptContextScope.Provider>
72
+ )
29
73
  }
30
74
 
31
- export const AdaptParentContext = React.createContext<AdaptParentContextI | null>(null)
75
+ export const useAdaptContext = (scope = '') => {
76
+ const contextScope = useContext(CurrentAdaptContextScope)
77
+ const context = AdaptContext.useStyledContext(
78
+ scope === '' ? contextScope || scope : scope
79
+ )
80
+ return context
81
+ }
32
82
 
33
- // forward props
34
- export const AdaptContents = (props: any) => {
35
- const context = React.useContext(AdaptParentContext)
36
- if (!context?.Contents) {
37
- throw new Error(
38
- process.env.NODE_ENV === 'production'
39
- ? `tamagui.dev/docs/intro/errors#warning-002`
40
- : `You're rendering a Tamagui <Adapt /> component without nesting it inside a parent that is able to adapt.`
41
- )
42
- }
43
- return React.createElement(context.Contents, props)
83
+ /**
84
+ * Hooks
85
+ */
86
+
87
+ type AdaptParentProps = {
88
+ children?: React.ReactNode
89
+ scope: string
90
+ Contents?: AdaptParentContextI['Contents']
91
+ portal?:
92
+ | boolean
93
+ | {
94
+ forwardProps?: any
95
+ }
44
96
  }
45
97
 
46
- AdaptContents.shouldForwardSpace = true
98
+ const AdaptPortals = new Map()
47
99
 
48
- export const useAdaptParent = ({
49
- Contents,
50
- }: { Contents: AdaptParentContextI['Contents'] }) => {
51
- const [when, setWhen] = React.useState<When>(null)
100
+ export const AdaptParent = ({ children, Contents, scope, portal }: AdaptParentProps) => {
101
+ const portalName = `AdaptPortal${scope}`
102
+ const id = useId()
52
103
 
53
- const AdaptProvider = React.useMemo(() => {
54
- const context: AdaptParentContextI = {
55
- Contents,
56
- setWhen,
57
- }
104
+ let FinalContents = Contents || AdaptPortals.get(id)
58
105
 
59
- function AdaptProviderView(props: { children?: any }) {
106
+ if (!FinalContents) {
107
+ FinalContents = () => {
60
108
  return (
61
- <AdaptParentContext.Provider value={context}>
62
- {props.children}
63
- </AdaptParentContext.Provider>
109
+ <PortalHost
110
+ name={portalName}
111
+ forwardProps={typeof portal === 'boolean' ? undefined : portal?.forwardProps}
112
+ />
64
113
  )
65
114
  }
115
+ AdaptPortals.set(id, FinalContents)
116
+ }
117
+
118
+ useEffect(() => {
119
+ return () => {
120
+ AdaptPortals.delete(id)
121
+ }
122
+ }, [])
66
123
 
67
- return AdaptProviderView
68
- }, [Contents])
124
+ const [when, setWhen] = React.useState<AdaptWhen>(null)
125
+ const [platform, setPlatform] = React.useState<AdaptPlatform>(null)
126
+ const [children2, setChildren] = React.useState(null)
69
127
 
70
- return {
71
- AdaptProvider,
72
- when,
73
- }
128
+ return (
129
+ <ProvideAdaptContext
130
+ Contents={FinalContents}
131
+ when={when}
132
+ platform={platform}
133
+ setPlatform={setPlatform}
134
+ setWhen={setWhen}
135
+ setChildren={setChildren}
136
+ portalName={portalName}
137
+ scopeName={scope}
138
+ >
139
+ {children}
140
+ </ProvideAdaptContext>
141
+ )
74
142
  }
75
143
 
76
- export const Adapt = withStaticProperties(
77
- function Adapt({ platform, when, children }: AdaptProps) {
78
- const context = React.useContext(AdaptParentContext)
79
- const media = useMedia()
144
+ /**
145
+ * Components
146
+ */
80
147
 
81
- let enabled = false
148
+ export const AdaptContents = ({ scope, ...rest }: { scope?: string }) => {
149
+ const context = useAdaptContext(scope)
82
150
 
83
- if (typeof when === 'function') {
84
- enabled = when({ media })
85
- } else {
86
- enabled = !platform
151
+ if (!context?.Contents) {
152
+ throw new Error(
153
+ process.env.NODE_ENV === 'production'
154
+ ? `tamagui.dev/docs/intro/errors#warning-002`
155
+ : `You're rendering a Tamagui <Adapt /> component without nesting it inside a parent that is able to adapt.`
156
+ )
157
+ }
158
+
159
+ // forwards props - see shouldForwardSpace
160
+ return React.createElement(context.Contents, { ...rest, key: `stable` })
161
+ }
87
162
 
88
- if (platform === 'touch') enabled = isTouchable
89
- if (platform === 'native') enabled = !isWeb
90
- if (platform === 'web') enabled = isWeb
91
- if (platform === 'ios') enabled = isIos
92
- if (platform === 'android') enabled = isAndroid
163
+ AdaptContents.shouldForwardSpace = true
93
164
 
94
- if (when && !media[when]) {
95
- enabled = false
96
- }
97
- }
165
+ export const Adapt = withStaticProperties(
166
+ function Adapt(props: AdaptProps) {
167
+ const { platform, when, children, scope } = props
168
+ const context = useAdaptContext(scope)
169
+ const scopeName = scope ?? context.scopeName
170
+ const enabled = useAdaptIsActiveGiven(props)
98
171
 
99
172
  useIsomorphicLayoutEffect(() => {
100
- if (!enabled) return
101
- context?.setWhen((when || enabled) as When)
173
+ context?.setWhen((when || enabled) as AdaptWhen)
174
+ context?.setPlatform(platform || null)
175
+ }, [when, platform, context, enabled])
102
176
 
177
+ useIsomorphicLayoutEffect(() => {
103
178
  return () => {
104
179
  context?.setWhen(null)
105
180
  }
106
- }, [when, context, enabled])
181
+ }, [])
107
182
 
108
- if (!enabled) {
109
- return null
110
- }
183
+ let output: React.ReactNode
111
184
 
112
185
  if (typeof children === 'function') {
113
- return children({ enabled, media })
186
+ const Component = context?.Contents
187
+ output = children(Component ? <Component /> : null)
188
+ } else {
189
+ output = children
114
190
  }
115
191
 
116
- return children
192
+ // TODO this isn't ideal using an effect to set children, will cause double-renders
193
+ // on every change
194
+ useEffect(() => {
195
+ if (typeof children === 'function' && output !== undefined) {
196
+ context?.setChildren(output)
197
+ }
198
+ }, [output])
199
+
200
+ return (
201
+ <CurrentAdaptContextScope.Provider value={scopeName}>
202
+ {!enabled ? null : output}
203
+ </CurrentAdaptContextScope.Provider>
204
+ )
117
205
  },
118
206
  {
119
207
  Contents: AdaptContents,
120
208
  }
121
209
  )
210
+
211
+ export const AdaptPortalContents = (props: {
212
+ children: React.ReactNode
213
+ scope?: string
214
+ }) => {
215
+ // const isActive = useAdaptIsActive(props.scope)
216
+ const { portalName } = useAdaptContext(props.scope)
217
+
218
+ // if (!isActive) {
219
+ // return null
220
+ // }
221
+
222
+ return (
223
+ <PortalItem
224
+ // passthrough={!isWeb && !isActive}
225
+ hostName={portalName}
226
+ >
227
+ {props.children}
228
+ </PortalItem>
229
+ )
230
+ }
231
+
232
+ const useAdaptIsActiveGiven = ({
233
+ when,
234
+ platform,
235
+ }: Pick<AdaptProps, 'when' | 'platform'>) => {
236
+ const media = useMedia()
237
+
238
+ let enabled = false
239
+
240
+ if (platform === 'touch') enabled = isTouchable
241
+ if (platform === 'native') enabled = !isWeb
242
+ if (platform === 'web') enabled = isWeb
243
+ if (platform === 'ios') enabled = isIos
244
+ if (platform === 'android') enabled = isAndroid
245
+
246
+ if (when && typeof when === 'string' && !media[when]) {
247
+ enabled = false
248
+ }
249
+
250
+ return enabled
251
+ }
252
+
253
+ export const useAdaptIsActive = (scope?: string) => {
254
+ const props = useAdaptContext(scope)
255
+ return useAdaptIsActiveGiven(props)
256
+ }
package/types/Adapt.d.ts CHANGED
@@ -1,40 +1,63 @@
1
+ import type { AllPlatforms, MediaQueryKey } from '@tamagui/core';
1
2
  import React from 'react';
2
- import type { MediaQueryKey, UseMediaState } from '@tamagui/core';
3
+ /**
4
+ * Interfaces
5
+ */
6
+ export type AdaptWhen = MediaQueryKeyString | boolean | null;
7
+ export type AdaptPlatform = AllPlatforms | 'touch' | null;
8
+ type AdaptParentContextI = {
9
+ Contents: Component;
10
+ scopeName: string;
11
+ platform: AdaptPlatform;
12
+ setPlatform: (when: AdaptPlatform) => any;
13
+ when: AdaptWhen;
14
+ setWhen: (when: AdaptWhen) => any;
15
+ setChildren: (children: any) => any;
16
+ portalName?: string;
17
+ };
3
18
  type MediaQueryKeyString = MediaQueryKey extends string ? MediaQueryKey : never;
4
19
  export type AdaptProps = {
5
- when?: MediaQueryKeyString | ((state: {
6
- media: UseMediaState;
7
- }) => boolean);
8
- platform?: 'native' | 'web' | 'touch' | 'ios' | 'android';
9
- children: JSX.Element | ((state: {
10
- enabled: boolean;
11
- media: UseMediaState;
12
- }) => JSX.Element);
20
+ scope?: string;
21
+ when?: AdaptWhen;
22
+ platform?: AdaptPlatform;
23
+ children: JSX.Element | ((children: React.ReactNode) => React.ReactNode);
13
24
  };
14
- type When = MediaQueryKeyString | boolean | null;
15
25
  type Component = (props: any) => any;
16
- type AdaptParentContextI = {
17
- Contents: Component;
18
- setWhen: (when: When) => any;
26
+ export declare const AdaptContext: import("@tamagui/core").StyledContext<AdaptParentContextI>;
27
+ export declare const useAdaptContext: (scope?: string) => AdaptParentContextI;
28
+ /**
29
+ * Hooks
30
+ */
31
+ type AdaptParentProps = {
32
+ children?: React.ReactNode;
33
+ scope: string;
34
+ Contents?: AdaptParentContextI['Contents'];
35
+ portal?: boolean | {
36
+ forwardProps?: any;
37
+ };
19
38
  };
20
- export declare const AdaptParentContext: React.Context<AdaptParentContextI | null>;
39
+ export declare const AdaptParent: ({ children, Contents, scope, portal }: AdaptParentProps) => import("react/jsx-runtime").JSX.Element;
40
+ /**
41
+ * Components
42
+ */
21
43
  export declare const AdaptContents: {
22
- (props: any): React.FunctionComponentElement<any>;
44
+ ({ scope, ...rest }: {
45
+ scope?: string;
46
+ }): React.FunctionComponentElement<any>;
23
47
  shouldForwardSpace: boolean;
24
48
  };
25
- export declare const useAdaptParent: ({ Contents, }: {
26
- Contents: AdaptParentContextI["Contents"];
27
- }) => {
28
- AdaptProvider: (props: {
29
- children?: any;
30
- }) => import("react/jsx-runtime").JSX.Element;
31
- when: When;
32
- };
33
- export declare const Adapt: (({ platform, when, children }: AdaptProps) => JSX.Element | null) & {
49
+ export declare const Adapt: ((props: AdaptProps) => import("react/jsx-runtime").JSX.Element) & {
34
50
  Contents: {
35
- (props: any): React.FunctionComponentElement<any>;
51
+ ({ scope, ...rest }: {
52
+ scope?: string;
53
+ }): React.FunctionComponentElement<any>;
36
54
  shouldForwardSpace: boolean;
37
55
  };
38
56
  };
57
+ export declare const AdaptPortalContents: (props: {
58
+ children: React.ReactNode;
59
+ scope?: string;
60
+ }) => import("react/jsx-runtime").JSX.Element;
61
+ export declare const useAdaptIsActive: (scope?: string) => boolean;
39
62
  export {};
40
63
  //# sourceMappingURL=Adapt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Adapt.d.ts","sourceRoot":"","sources":["../src/Adapt.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAQzB,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAIjE,KAAK,mBAAmB,GAAG,aAAa,SAAS,MAAM,GAAG,aAAa,GAAG,KAAK,CAAA;AAE/E,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,CAAC,EAAE,mBAAmB,GAAG,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,aAAa,CAAA;KAAE,KAAK,OAAO,CAAC,CAAA;IAC3E,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,KAAK,GAAG,SAAS,CAAA;IACzD,QAAQ,EACJ,GAAG,CAAC,OAAO,GACX,CAAC,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,aAAa,CAAA;KAAE,KAAK,GAAG,CAAC,OAAO,CAAC,CAAA;CACzE,CAAA;AAED,KAAK,IAAI,GAAG,mBAAmB,GAAG,OAAO,GAAG,IAAI,CAAA;AAEhD,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAA;AACpC,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,SAAS,CAAA;IACnB,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,GAAG,CAAA;CAC7B,CAAA;AAED,eAAO,MAAM,kBAAkB,2CAAwD,CAAA;AAGvF,eAAO,MAAM,aAAa;YAAW,GAAG;;CAUvC,CAAA;AAID,eAAO,MAAM,cAAc,kBAExB;IAAE,QAAQ,EAAE,mBAAmB,CAAC,UAAU,CAAC,CAAA;CAAE;2BASV;QAAE,QAAQ,CAAC,EAAE,GAAG,CAAA;KAAE;;CAevD,CAAA;AAED,eAAO,MAAM,KAAK,kCAC6B,UAAU;;gBA3CpB,GAAG;;;CAuFvC,CAAA"}
1
+ {"version":3,"file":"Adapt.d.ts","sourceRoot":"","sources":["../src/Adapt.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAIhE,OAAO,KAAsD,MAAM,OAAO,CAAA;AAE1E;;GAEG;AAEH,MAAM,MAAM,SAAS,GAAG,mBAAmB,GAAG,OAAO,GAAG,IAAI,CAAA;AAC5D,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,OAAO,GAAG,IAAI,CAAA;AAEzD,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,aAAa,CAAA;IACvB,WAAW,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,GAAG,CAAA;IACzC,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,GAAG,CAAA;IACjC,WAAW,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,GAAG,CAAA;IACnC,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,KAAK,mBAAmB,GAAG,aAAa,SAAS,MAAM,GAAG,aAAa,GAAG,KAAK,CAAA;AAE/E,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,QAAQ,CAAC,EAAE,aAAa,CAAA;IACxB,QAAQ,EAAE,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,CAAC,CAAA;CACzE,CAAA;AAED,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAA;AAQpC,eAAO,MAAM,YAAY,4DASvB,CAAA;AAiBF,eAAO,MAAM,eAAe,yCAM3B,CAAA;AAED;;GAEG;AAEH,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,mBAAmB,CAAC,UAAU,CAAC,CAAA;IAC1C,MAAM,CAAC,EACH,OAAO,GACP;QACE,YAAY,CAAC,EAAE,GAAG,CAAA;KACnB,CAAA;CACN,CAAA;AAID,eAAO,MAAM,WAAW,0CAA2C,gBAAgB,4CA0ClF,CAAA;AAED;;GAEG;AAEH,eAAO,MAAM,aAAa;yBAAwB;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;;CAanE,CAAA;AAID,eAAO,MAAM,KAAK,WACM,UAAU;;6BAlBgB;YAAE,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE;;;CA6DnE,CAAA;AAED,eAAO,MAAM,mBAAmB,UAAW;IACzC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,4CAgBA,CAAA;AAuBD,eAAO,MAAM,gBAAgB,WAAY,MAAM,YAG9C,CAAA"}