@tamagui/web 1.92.1 → 1.93.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/dist/cjs/createComponent.js +156 -104
- package/dist/cjs/createComponent.js.map +2 -2
- package/dist/cjs/createComponent.native.js +139 -104
- package/dist/cjs/createComponent.native.js.map +2 -2
- package/dist/cjs/createVariable.js +7 -3
- package/dist/cjs/createVariable.js.map +1 -1
- package/dist/cjs/createVariable.native.js +7 -3
- package/dist/cjs/createVariable.native.js.map +2 -2
- package/dist/cjs/helpers/expandStyle.native.js +0 -5
- package/dist/cjs/helpers/expandStyle.native.js.map +2 -2
- package/dist/cjs/helpers/getSplitStyles.native.js +1 -15
- package/dist/cjs/helpers/getSplitStyles.native.js.map +1 -1
- package/dist/cjs/helpers/getThemeCSSRules.js +4 -1
- package/dist/cjs/helpers/getThemeCSSRules.js.map +1 -1
- package/dist/cjs/helpers/insertStyleRule.js +4 -1
- package/dist/cjs/helpers/insertStyleRule.js.map +1 -1
- package/dist/cjs/helpers/insertStyleRule.native.js +3 -1
- package/dist/cjs/helpers/insertStyleRule.native.js.map +2 -2
- package/dist/cjs/helpers/registerCSSVariable.js +4 -1
- package/dist/cjs/helpers/registerCSSVariable.js.map +1 -1
- package/dist/cjs/helpers/registerCSSVariable.native.js +1 -1
- package/dist/cjs/helpers/registerCSSVariable.native.js.map +2 -2
- package/dist/cjs/hooks/useProps.js +29 -5
- package/dist/cjs/hooks/useProps.js.map +1 -1
- package/dist/cjs/hooks/useProps.native.js +39 -4
- package/dist/cjs/hooks/useProps.native.js.map +2 -2
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.native.js +2 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/esm/createComponent.js +156 -104
- package/dist/esm/createComponent.js.map +2 -2
- package/dist/esm/createComponent.mjs +193 -135
- package/dist/esm/createComponent.native.js +135 -103
- package/dist/esm/createComponent.native.js.map +2 -2
- package/dist/esm/createVariable.js +7 -3
- package/dist/esm/createVariable.js.map +1 -1
- package/dist/esm/createVariable.mjs +19 -16
- package/dist/esm/createVariable.native.js +7 -3
- package/dist/esm/createVariable.native.js.map +2 -2
- package/dist/esm/helpers/expandStyle.native.js +0 -5
- package/dist/esm/helpers/expandStyle.native.js.map +2 -2
- package/dist/esm/helpers/getSplitStyles.native.js +1 -15
- package/dist/esm/helpers/getSplitStyles.native.js.map +1 -1
- package/dist/esm/helpers/getThemeCSSRules.js +4 -1
- package/dist/esm/helpers/getThemeCSSRules.js.map +1 -1
- package/dist/esm/helpers/getThemeCSSRules.mjs +1 -1
- package/dist/esm/helpers/insertStyleRule.js +4 -1
- package/dist/esm/helpers/insertStyleRule.js.map +1 -1
- package/dist/esm/helpers/insertStyleRule.mjs +4 -3
- package/dist/esm/helpers/insertStyleRule.native.js +3 -1
- package/dist/esm/helpers/insertStyleRule.native.js.map +2 -2
- package/dist/esm/helpers/registerCSSVariable.js +4 -1
- package/dist/esm/helpers/registerCSSVariable.js.map +1 -1
- package/dist/esm/helpers/registerCSSVariable.mjs +1 -1
- package/dist/esm/helpers/registerCSSVariable.native.js +1 -1
- package/dist/esm/helpers/registerCSSVariable.native.js.map +2 -2
- package/dist/esm/hooks/useProps.js +31 -6
- package/dist/esm/hooks/useProps.js.map +1 -1
- package/dist/esm/hooks/useProps.mjs +32 -5
- package/dist/esm/hooks/useProps.native.js +41 -5
- package/dist/esm/hooks/useProps.native.js.map +2 -2
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +1 -0
- package/dist/esm/index.native.js +1 -0
- package/dist/esm/index.native.js.map +1 -1
- package/package.json +12 -12
- package/reset.css +1 -0
- package/src/createComponent.tsx +301 -229
- package/src/createVariable.ts +11 -2
- package/src/helpers/getThemeCSSRules.ts +4 -1
- package/src/helpers/insertStyleRule.tsx +4 -1
- package/src/helpers/registerCSSVariable.ts +4 -3
- package/src/hooks/useProps.tsx +46 -14
- package/src/index.ts +1 -0
- package/types/createComponent.d.ts +31 -1
- package/types/createComponent.d.ts.map +1 -1
- package/types/createVariable.d.ts.map +1 -1
- package/types/helpers/getThemeCSSRules.d.ts.map +1 -1
- package/types/helpers/insertStyleRule.d.ts.map +1 -1
- package/types/helpers/registerCSSVariable.d.ts.map +1 -1
- package/types/hooks/useProps.d.ts +7 -4
- package/types/hooks/useProps.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
package/src/createComponent.tsx
CHANGED
|
@@ -132,6 +132,194 @@ if (typeof document !== 'undefined') {
|
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
export const useComponentState = (
|
|
136
|
+
props: StackProps | TextProps | Record<string, any>,
|
|
137
|
+
{ animationDriver, groups }: ComponentContextI,
|
|
138
|
+
staticConfig: StaticConfig,
|
|
139
|
+
config: TamaguiInternalConfig
|
|
140
|
+
) => {
|
|
141
|
+
const useAnimations = animationDriver?.useAnimations as UseAnimationHook | undefined
|
|
142
|
+
|
|
143
|
+
const stateRef = useRef<TamaguiComponentStateRef>({})
|
|
144
|
+
|
|
145
|
+
// after we get states mount we need to turn off isAnimated for server side
|
|
146
|
+
const hasAnimationProp = Boolean(
|
|
147
|
+
'animation' in props || (props.style && hasAnimatedStyleValue(props.style))
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
// disable for now still ssr issues
|
|
151
|
+
const supportsCSSVars = animationDriver?.supportsCSSVars
|
|
152
|
+
const curStateRef = stateRef.current
|
|
153
|
+
|
|
154
|
+
const willBeAnimatedClient = (() => {
|
|
155
|
+
const next = !!(hasAnimationProp && !staticConfig.isHOC && useAnimations)
|
|
156
|
+
return Boolean(next || curStateRef.hasAnimated)
|
|
157
|
+
})()
|
|
158
|
+
|
|
159
|
+
const willBeAnimated = !isServer && willBeAnimatedClient
|
|
160
|
+
|
|
161
|
+
// once animated, always animated to preserve hooks / vdom structure
|
|
162
|
+
if (willBeAnimated && !curStateRef.hasAnimated) {
|
|
163
|
+
curStateRef.hasAnimated = true
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// HOOK
|
|
167
|
+
const isHydrated = config?.disableSSR ? true : useDidFinishSSR()
|
|
168
|
+
|
|
169
|
+
// HOOK
|
|
170
|
+
const presence =
|
|
171
|
+
(willBeAnimated &&
|
|
172
|
+
props['animatePresence'] !== false &&
|
|
173
|
+
animationDriver?.usePresence?.()) ||
|
|
174
|
+
null
|
|
175
|
+
const presenceState = presence?.[2]
|
|
176
|
+
const isExiting = presenceState?.isPresent === false
|
|
177
|
+
const isEntering = presenceState?.isPresent === true && presenceState.initial !== false
|
|
178
|
+
|
|
179
|
+
const hasEnterStyle = !!props.enterStyle
|
|
180
|
+
// finish animated logic, avoid isAnimated when unmounted
|
|
181
|
+
const hasRNAnimation = hasAnimationProp && animationDriver?.isReactNative
|
|
182
|
+
const isReactNative = staticConfig.isReactNative
|
|
183
|
+
|
|
184
|
+
// only web server + initial client render run this when not hydrated:
|
|
185
|
+
let isAnimated = willBeAnimated
|
|
186
|
+
if (!isReactNative && hasRNAnimation && !staticConfig.isHOC && !isHydrated) {
|
|
187
|
+
isAnimated = false
|
|
188
|
+
curStateRef.willHydrate = true
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (process.env.NODE_ENV === 'development' && time) time`pre-use-state`
|
|
192
|
+
|
|
193
|
+
const hasEnterState = hasEnterStyle || isEntering
|
|
194
|
+
const needsToMount = !isHydrated || !curStateRef.host
|
|
195
|
+
|
|
196
|
+
const initialState = hasEnterState
|
|
197
|
+
? needsToMount
|
|
198
|
+
? defaultComponentStateShouldEnter
|
|
199
|
+
: defaultComponentState
|
|
200
|
+
: defaultComponentStateMounted
|
|
201
|
+
|
|
202
|
+
// will be nice to deprecate half of these:
|
|
203
|
+
const disabled = isDisabled(props)
|
|
204
|
+
|
|
205
|
+
if (disabled != null) {
|
|
206
|
+
initialState.disabled = disabled
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// HOOK
|
|
210
|
+
const states = useState<TamaguiComponentState>(initialState)
|
|
211
|
+
|
|
212
|
+
const state = props.forceStyle ? { ...states[0], [props.forceStyle]: true } : states[0]
|
|
213
|
+
const setState = states[1]
|
|
214
|
+
|
|
215
|
+
// immediately update disabled state and reset component state
|
|
216
|
+
if (disabled !== state.disabled) {
|
|
217
|
+
setState({
|
|
218
|
+
...state,
|
|
219
|
+
...defaultComponentState, // removes any stale press state etc
|
|
220
|
+
disabled,
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let setStateShallow = createShallowSetState(setState, disabled, props.debug)
|
|
225
|
+
|
|
226
|
+
if (isHydrated && state.unmounted === 'should-enter') {
|
|
227
|
+
state.unmounted = true
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// set enter/exit variants onto our new props object
|
|
231
|
+
if (presenceState && isAnimated && isHydrated && staticConfig.variants) {
|
|
232
|
+
if (process.env.NODE_ENV === 'development' && props.debug === 'verbose') {
|
|
233
|
+
console.warn(`has presenceState ${JSON.stringify(presenceState)}`)
|
|
234
|
+
}
|
|
235
|
+
const { enterVariant, exitVariant, enterExitVariant, custom } = presenceState
|
|
236
|
+
if (isObj(custom)) {
|
|
237
|
+
Object.assign(props, custom)
|
|
238
|
+
}
|
|
239
|
+
const exv = exitVariant ?? enterExitVariant
|
|
240
|
+
const env = enterVariant ?? enterExitVariant
|
|
241
|
+
if (state.unmounted && env && staticConfig.variants[env]) {
|
|
242
|
+
if (process.env.NODE_ENV === 'development' && props.debug === 'verbose') {
|
|
243
|
+
console.warn(`Animating presence ENTER "${env}"`)
|
|
244
|
+
}
|
|
245
|
+
props[env] = true
|
|
246
|
+
} else if (isExiting && exv) {
|
|
247
|
+
if (process.env.NODE_ENV === 'development' && props.debug === 'verbose') {
|
|
248
|
+
console.warn(`Animating presence EXIT "${exv}"`)
|
|
249
|
+
}
|
|
250
|
+
props[exv] = exitVariant === enterExitVariant ? false : true
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const shouldAvoidClasses = Boolean(
|
|
255
|
+
!isWeb ||
|
|
256
|
+
(isAnimated && !supportsCSSVars) ||
|
|
257
|
+
!staticConfig.acceptsClassName ||
|
|
258
|
+
// on server for SSR and animation compat added the && isHydrated but perhaps we want
|
|
259
|
+
// disableClassName="until-hydrated" to be more straightforward
|
|
260
|
+
// see issue if not, Button sets disableClassName to true <Button animation="" /> with
|
|
261
|
+
// the react-native driver errors because it tries to animate var(--color) to rbga(..)
|
|
262
|
+
(props.disableClassName && isHydrated)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
const groupName = props.group as any as string
|
|
266
|
+
|
|
267
|
+
if (groupName && !curStateRef.group) {
|
|
268
|
+
const listeners = new Set<GroupStateListener>()
|
|
269
|
+
curStateRef.group = {
|
|
270
|
+
listeners,
|
|
271
|
+
emit(name, state) {
|
|
272
|
+
listeners.forEach((l) => l(name, state))
|
|
273
|
+
},
|
|
274
|
+
subscribe(cb) {
|
|
275
|
+
listeners.add(cb)
|
|
276
|
+
return () => {
|
|
277
|
+
listeners.delete(cb)
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (groupName) {
|
|
284
|
+
// when we set state we also set our group state and emit an event for children listening:
|
|
285
|
+
const groupContextState = groups.state
|
|
286
|
+
const og = setStateShallow
|
|
287
|
+
setStateShallow = (state) => {
|
|
288
|
+
og(state)
|
|
289
|
+
curStateRef.group!.emit(groupName, {
|
|
290
|
+
pseudo: state,
|
|
291
|
+
})
|
|
292
|
+
// and mutate the current since its concurrent safe (children throw it in useState on mount)
|
|
293
|
+
const next = {
|
|
294
|
+
...groupContextState[groupName],
|
|
295
|
+
...state,
|
|
296
|
+
}
|
|
297
|
+
groupContextState[groupName] = next
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
curStateRef,
|
|
303
|
+
disabled,
|
|
304
|
+
groupName,
|
|
305
|
+
hasAnimationProp,
|
|
306
|
+
hasEnterStyle,
|
|
307
|
+
isAnimated,
|
|
308
|
+
isExiting,
|
|
309
|
+
isHydrated,
|
|
310
|
+
presence,
|
|
311
|
+
presenceState,
|
|
312
|
+
setState,
|
|
313
|
+
setStateShallow,
|
|
314
|
+
shouldAvoidClasses,
|
|
315
|
+
state,
|
|
316
|
+
stateRef,
|
|
317
|
+
supportsCSSVars,
|
|
318
|
+
willBeAnimated,
|
|
319
|
+
willBeAnimatedClient,
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
135
323
|
/**
|
|
136
324
|
* Only on native do we need the actual underlying View/Text
|
|
137
325
|
* On the web we avoid react-native dep altogether.
|
|
@@ -178,7 +366,6 @@ export function createComponent<
|
|
|
178
366
|
const {
|
|
179
367
|
Component,
|
|
180
368
|
isText,
|
|
181
|
-
isInput,
|
|
182
369
|
isZStack,
|
|
183
370
|
isHOC,
|
|
184
371
|
validStyles = {},
|
|
@@ -234,7 +421,7 @@ export function createComponent<
|
|
|
234
421
|
let styledContextProps: Object | undefined
|
|
235
422
|
let overriddenContextProps: Object | undefined
|
|
236
423
|
let contextValue: Object | null | undefined
|
|
237
|
-
const { context } = staticConfig
|
|
424
|
+
const { context, isReactNative } = staticConfig
|
|
238
425
|
|
|
239
426
|
if (context) {
|
|
240
427
|
// HOOK 3 (-1 if production)
|
|
@@ -286,7 +473,7 @@ export function createComponent<
|
|
|
286
473
|
let overlay: HTMLSpanElement | null = null
|
|
287
474
|
|
|
288
475
|
const debugVisualizerHandler = (show = false) => {
|
|
289
|
-
const node =
|
|
476
|
+
const node = curStateRef.host as HTMLElement
|
|
290
477
|
if (!node) return
|
|
291
478
|
|
|
292
479
|
if (show) {
|
|
@@ -346,183 +533,39 @@ export function createComponent<
|
|
|
346
533
|
// conditional but if ever true stays true
|
|
347
534
|
// [animated, inversed]
|
|
348
535
|
// HOOK
|
|
349
|
-
|
|
536
|
+
|
|
350
537
|
if (process.env.NODE_ENV === 'development' && time) time`stateref`
|
|
351
538
|
|
|
352
539
|
/**
|
|
353
540
|
* Component state for tracking animations, pseudos
|
|
354
541
|
*/
|
|
355
|
-
const
|
|
356
|
-
const useAnimations =
|
|
357
|
-
|
|
358
|
-
// after we get states mount we need to turn off isAnimated for server side
|
|
359
|
-
const hasAnimationProp = Boolean(
|
|
360
|
-
'animation' in props || (props.style && hasAnimatedStyleValue(props.style))
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
// disable for now still ssr issues
|
|
364
|
-
const supportsCSSVars = animationsConfig?.supportsCSSVars
|
|
365
|
-
const curState = stateRef.current
|
|
366
|
-
|
|
367
|
-
const willBeAnimatedClient = (() => {
|
|
368
|
-
const next = !!(hasAnimationProp && !isHOC && useAnimations)
|
|
369
|
-
return Boolean(next || curState.hasAnimated)
|
|
370
|
-
})()
|
|
371
|
-
|
|
372
|
-
const willBeAnimated = !isServer && willBeAnimatedClient
|
|
373
|
-
|
|
374
|
-
// once animated, always animated to preserve hooks / vdom structure
|
|
375
|
-
if (willBeAnimated && !curState.hasAnimated) {
|
|
376
|
-
curState.hasAnimated = true
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// HOOK
|
|
380
|
-
const isHydrated = config?.disableSSR ? true : useDidFinishSSR()
|
|
381
|
-
|
|
382
|
-
// HOOK
|
|
383
|
-
const presence =
|
|
384
|
-
(willBeAnimated &&
|
|
385
|
-
props['animatePresence'] !== false &&
|
|
386
|
-
animationsConfig?.usePresence?.()) ||
|
|
387
|
-
null
|
|
388
|
-
const presenceState = presence?.[2]
|
|
389
|
-
const isExiting = presenceState?.isPresent === false
|
|
390
|
-
const isEntering =
|
|
391
|
-
presenceState?.isPresent === true && presenceState.initial !== false
|
|
392
|
-
|
|
393
|
-
const hasEnterStyle = !!props.enterStyle
|
|
394
|
-
// finish animated logic, avoid isAnimated when unmounted
|
|
395
|
-
const hasRNAnimation = hasAnimationProp && animationsConfig?.isReactNative
|
|
396
|
-
const isReactNative = staticConfig.isReactNative
|
|
397
|
-
|
|
398
|
-
// only web server + initial client render run this when not hydrated:
|
|
399
|
-
let isAnimated = willBeAnimated
|
|
400
|
-
if (!isReactNative && hasRNAnimation && !isHOC && !isHydrated) {
|
|
401
|
-
isAnimated = false
|
|
402
|
-
curState.willHydrate = true
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (process.env.NODE_ENV === 'development' && time) time`pre-use-state`
|
|
406
|
-
|
|
407
|
-
const hasEnterState = hasEnterStyle || isEntering
|
|
408
|
-
const needsToMount = !isHydrated || !curState.host
|
|
409
|
-
|
|
410
|
-
const initialState = hasEnterState
|
|
411
|
-
? needsToMount
|
|
412
|
-
? defaultComponentStateShouldEnter
|
|
413
|
-
: defaultComponentState
|
|
414
|
-
: defaultComponentStateMounted
|
|
415
|
-
|
|
416
|
-
// will be nice to deprecate half of these:
|
|
417
|
-
const disabled =
|
|
418
|
-
props.disabled ||
|
|
419
|
-
props.accessibilityState?.disabled ||
|
|
420
|
-
props['aria-disabled'] ||
|
|
421
|
-
// @ts-expect-error (comes from core)
|
|
422
|
-
props.accessibilityDisabled ||
|
|
423
|
-
false
|
|
542
|
+
const animationDriver = componentContext.animationDriver
|
|
543
|
+
const useAnimations = animationDriver?.useAnimations as UseAnimationHook | undefined
|
|
424
544
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
let setStateShallow = createShallowSetState(setState, disabled, debugProp)
|
|
447
|
-
|
|
448
|
-
if (isHydrated && state.unmounted === 'should-enter') {
|
|
449
|
-
state.unmounted = true
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// set enter/exit variants onto our new props object
|
|
453
|
-
if (presenceState && isAnimated && isHydrated && staticConfig.variants) {
|
|
454
|
-
if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
|
|
455
|
-
console.warn(`has presenceState ${JSON.stringify(presenceState)}`)
|
|
456
|
-
}
|
|
457
|
-
const { enterVariant, exitVariant, enterExitVariant, custom } = presenceState
|
|
458
|
-
if (isObj(custom)) {
|
|
459
|
-
Object.assign(props, custom)
|
|
460
|
-
}
|
|
461
|
-
const exv = exitVariant ?? enterExitVariant
|
|
462
|
-
const env = enterVariant ?? enterExitVariant
|
|
463
|
-
if (state.unmounted && env && staticConfig.variants[env]) {
|
|
464
|
-
if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
|
|
465
|
-
console.warn(`Animating presence ENTER "${env}"`)
|
|
466
|
-
}
|
|
467
|
-
props[env] = true
|
|
468
|
-
} else if (isExiting && exv) {
|
|
469
|
-
if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
|
|
470
|
-
console.warn(`Animating presence EXIT "${exv}"`)
|
|
471
|
-
}
|
|
472
|
-
props[exv] = exitVariant === enterExitVariant ? false : true
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const shouldAvoidClasses = Boolean(
|
|
477
|
-
!isWeb ||
|
|
478
|
-
(isAnimated && !supportsCSSVars) ||
|
|
479
|
-
!staticConfig.acceptsClassName ||
|
|
480
|
-
// on server for SSR and animation compat added the && isHydrated but perhaps we want
|
|
481
|
-
// disableClassName="until-hydrated" to be more straightforward
|
|
482
|
-
// see issue if not, Button sets disableClassName to true <Button animation="" /> with
|
|
483
|
-
// the react-native driver errors because it tries to animate var(--color) to rbga(..)
|
|
484
|
-
(propsIn.disableClassName && isHydrated)
|
|
485
|
-
)
|
|
545
|
+
const {
|
|
546
|
+
curStateRef,
|
|
547
|
+
disabled,
|
|
548
|
+
groupName,
|
|
549
|
+
hasAnimationProp,
|
|
550
|
+
hasEnterStyle,
|
|
551
|
+
isAnimated,
|
|
552
|
+
isExiting,
|
|
553
|
+
isHydrated,
|
|
554
|
+
presence,
|
|
555
|
+
presenceState,
|
|
556
|
+
setState,
|
|
557
|
+
setStateShallow,
|
|
558
|
+
shouldAvoidClasses,
|
|
559
|
+
state,
|
|
560
|
+
stateRef,
|
|
561
|
+
supportsCSSVars,
|
|
562
|
+
willBeAnimated,
|
|
563
|
+
willBeAnimatedClient,
|
|
564
|
+
} = useComponentState(props, componentContext, staticConfig, config!)
|
|
486
565
|
|
|
487
566
|
const shouldForcePseudo = !!propsIn.forceStyle
|
|
488
567
|
const noClassNames = shouldAvoidClasses || shouldForcePseudo
|
|
489
568
|
|
|
490
|
-
const groupName = props.group as any as string
|
|
491
|
-
|
|
492
|
-
if (groupName && !curState.group) {
|
|
493
|
-
const listeners = new Set<GroupStateListener>()
|
|
494
|
-
curState.group = {
|
|
495
|
-
listeners,
|
|
496
|
-
emit(name, state) {
|
|
497
|
-
listeners.forEach((l) => l(name, state))
|
|
498
|
-
},
|
|
499
|
-
subscribe(cb) {
|
|
500
|
-
listeners.add(cb)
|
|
501
|
-
return () => {
|
|
502
|
-
listeners.delete(cb)
|
|
503
|
-
}
|
|
504
|
-
},
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (groupName) {
|
|
509
|
-
// when we set state we also set our group state and emit an event for children listening:
|
|
510
|
-
const groupContextState = componentContext.groups.state
|
|
511
|
-
const og = setStateShallow
|
|
512
|
-
setStateShallow = (state) => {
|
|
513
|
-
og(state)
|
|
514
|
-
curState.group!.emit(groupName, {
|
|
515
|
-
pseudo: state,
|
|
516
|
-
})
|
|
517
|
-
// and mutate the current since its concurrent safe (children throw it in useState on mount)
|
|
518
|
-
const next = {
|
|
519
|
-
...groupContextState[groupName],
|
|
520
|
-
...state,
|
|
521
|
-
}
|
|
522
|
-
groupContextState[groupName] = next
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
569
|
if (process.env.NODE_ENV === 'development' && time) time`use-state`
|
|
527
570
|
|
|
528
571
|
const hasTextAncestor = !!(isWeb && isText ? componentContext.inText : false)
|
|
@@ -539,8 +582,8 @@ export function createComponent<
|
|
|
539
582
|
|
|
540
583
|
let elementType = isText ? BaseTextComponent : BaseViewComponent
|
|
541
584
|
|
|
542
|
-
if (
|
|
543
|
-
elementType =
|
|
585
|
+
if (animationDriver && isAnimated) {
|
|
586
|
+
elementType = animationDriver[isText ? 'Text' : 'View'] || elementType
|
|
544
587
|
}
|
|
545
588
|
|
|
546
589
|
// internal use only
|
|
@@ -552,19 +595,19 @@ export function createComponent<
|
|
|
552
595
|
if (process.env.NODE_ENV === 'development' && time) time`theme-props`
|
|
553
596
|
|
|
554
597
|
if (props.themeShallow) {
|
|
555
|
-
|
|
598
|
+
curStateRef.themeShallow = true
|
|
556
599
|
}
|
|
557
600
|
|
|
558
601
|
const themeStateProps: UseThemeWithStateProps = {
|
|
559
602
|
name: props.theme,
|
|
560
603
|
componentName,
|
|
561
604
|
disable: disableTheme,
|
|
562
|
-
shallow:
|
|
605
|
+
shallow: curStateRef.themeShallow,
|
|
563
606
|
inverse: props.themeInverse,
|
|
564
607
|
debug: debugProp,
|
|
565
608
|
}
|
|
566
609
|
|
|
567
|
-
if (typeof
|
|
610
|
+
if (typeof curStateRef.isListeningToTheme === 'boolean') {
|
|
568
611
|
themeStateProps.shouldUpdate = () => stateRef.current.isListeningToTheme
|
|
569
612
|
}
|
|
570
613
|
|
|
@@ -668,14 +711,14 @@ export function createComponent<
|
|
|
668
711
|
)
|
|
669
712
|
|
|
670
713
|
// hide strategy will set this opacity = 0 until measured
|
|
671
|
-
if (props.group && props.untilMeasured === 'hide' && !
|
|
714
|
+
if (props.group && props.untilMeasured === 'hide' && !curStateRef.hasMeasured) {
|
|
672
715
|
splitStyles.style ||= {}
|
|
673
716
|
splitStyles.style.opacity = 0
|
|
674
717
|
}
|
|
675
718
|
|
|
676
719
|
if (process.env.NODE_ENV === 'development' && time) time`split-styles`
|
|
677
720
|
|
|
678
|
-
|
|
721
|
+
curStateRef.isListeningToTheme = splitStyles.dynamicThemeAccess
|
|
679
722
|
|
|
680
723
|
// only listen for changes if we are using raw theme values or media space, or dynamic media (native)
|
|
681
724
|
// array = space media breakpoints
|
|
@@ -805,18 +848,18 @@ export function createComponent<
|
|
|
805
848
|
elementType,
|
|
806
849
|
nonTamaguiProps,
|
|
807
850
|
stateRef,
|
|
808
|
-
|
|
851
|
+
curStateRef.willHydrate
|
|
809
852
|
) || nonTamaguiProps
|
|
810
853
|
|
|
811
854
|
// HOOK (1 more):
|
|
812
|
-
if (!
|
|
813
|
-
|
|
855
|
+
if (!curStateRef.composedRef) {
|
|
856
|
+
curStateRef.composedRef = composeRefs<TamaguiElement>(
|
|
814
857
|
(x) => (stateRef.current.host = x as TamaguiElement),
|
|
815
858
|
forwardedRef
|
|
816
859
|
)
|
|
817
860
|
}
|
|
818
861
|
|
|
819
|
-
viewProps.ref =
|
|
862
|
+
viewProps.ref = curStateRef.composedRef
|
|
820
863
|
|
|
821
864
|
if (process.env.NODE_ENV === 'development') {
|
|
822
865
|
if (!isReactNative && !isText && isWeb && !isHOC) {
|
|
@@ -840,70 +883,37 @@ export function createComponent<
|
|
|
840
883
|
const { pseudoGroups, mediaGroups } = splitStyles
|
|
841
884
|
|
|
842
885
|
// TODO if you add a group prop setStateShallow changes identity...
|
|
843
|
-
if (!
|
|
844
|
-
|
|
886
|
+
if (!curStateRef.unPress) {
|
|
887
|
+
curStateRef.unPress = () => setStateShallow({ press: false, pressIn: false })
|
|
845
888
|
}
|
|
846
889
|
|
|
847
|
-
const unPress =
|
|
848
|
-
const shouldEnter = state.unmounted
|
|
890
|
+
const unPress = curStateRef.unPress!
|
|
849
891
|
|
|
850
892
|
useEffect(() => {
|
|
851
893
|
if (disabled) {
|
|
852
894
|
return
|
|
853
895
|
}
|
|
854
896
|
|
|
855
|
-
if (
|
|
897
|
+
if (state.unmounted) {
|
|
856
898
|
setStateShallow({ unmounted: false })
|
|
857
899
|
return
|
|
858
900
|
}
|
|
859
901
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
if (process.env.NODE_ENV === 'development' && !componentContext.groups) {
|
|
869
|
-
console.debug(`No context group found`)
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
disposeGroupsListener = componentContext.groups?.subscribe(
|
|
873
|
-
(name, { layout, pseudo }) => {
|
|
874
|
-
if (pseudo && pseudoGroups?.has(name)) {
|
|
875
|
-
// we emit a partial so merge it + change reference so mergeIfNotShallowEqual runs
|
|
876
|
-
Object.assign(current.pseudo, pseudo)
|
|
877
|
-
persist()
|
|
878
|
-
} else if (layout && mediaGroups) {
|
|
879
|
-
const mediaState = getMediaState(mediaGroups, layout)
|
|
880
|
-
const next = mergeIfNotShallowEqual(current.media, mediaState)
|
|
881
|
-
if (next !== current.media) {
|
|
882
|
-
Object.assign(current.media, next)
|
|
883
|
-
persist()
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
function persist() {
|
|
887
|
-
// force it to be referentially different so it always updates
|
|
888
|
-
const group = {
|
|
889
|
-
...state.group,
|
|
890
|
-
[name]: current,
|
|
891
|
-
}
|
|
892
|
-
setStateShallow({
|
|
893
|
-
group,
|
|
894
|
-
})
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
)
|
|
898
|
-
}
|
|
902
|
+
const dispose = subscribeToContextGroup({
|
|
903
|
+
disabled,
|
|
904
|
+
componentContext,
|
|
905
|
+
setStateShallow,
|
|
906
|
+
state,
|
|
907
|
+
mediaGroups,
|
|
908
|
+
pseudoGroups,
|
|
909
|
+
})
|
|
899
910
|
|
|
900
911
|
return () => {
|
|
901
|
-
|
|
912
|
+
dispose?.()
|
|
902
913
|
mouseUps.delete(unPress)
|
|
903
914
|
}
|
|
904
915
|
}, [
|
|
905
916
|
disabled,
|
|
906
|
-
shouldEnter,
|
|
907
917
|
pseudoGroups ? Object.keys([...pseudoGroups]).join('') : 0,
|
|
908
918
|
mediaGroups ? Object.keys([...mediaGroups]).join('') : 0,
|
|
909
919
|
])
|
|
@@ -1161,7 +1171,7 @@ export function createComponent<
|
|
|
1161
1171
|
if (process.env.NODE_ENV === 'development' && time) time`create-element`
|
|
1162
1172
|
|
|
1163
1173
|
// must override context so siblings don't clobber initial state
|
|
1164
|
-
const groupState =
|
|
1174
|
+
const groupState = curStateRef.group
|
|
1165
1175
|
const subGroupContext = useMemo(() => {
|
|
1166
1176
|
if (!groupState || !groupName) return
|
|
1167
1177
|
groupState.listeners.clear()
|
|
@@ -1272,7 +1282,6 @@ export function createComponent<
|
|
|
1272
1282
|
defaultProps,
|
|
1273
1283
|
elementType,
|
|
1274
1284
|
events,
|
|
1275
|
-
initialState,
|
|
1276
1285
|
isAnimated,
|
|
1277
1286
|
isMediaArray,
|
|
1278
1287
|
isStringElement,
|
|
@@ -1628,3 +1637,66 @@ function getMediaState(
|
|
|
1628
1637
|
|
|
1629
1638
|
const fromPx = (val?: number | string) =>
|
|
1630
1639
|
typeof val !== 'string' ? val : +val.replace('px', '')
|
|
1640
|
+
|
|
1641
|
+
export const isDisabled = (props: any) => {
|
|
1642
|
+
return (
|
|
1643
|
+
props.disabled ||
|
|
1644
|
+
props.accessibilityState?.disabled ||
|
|
1645
|
+
props['aria-disabled'] ||
|
|
1646
|
+
props.accessibilityDisabled ||
|
|
1647
|
+
false
|
|
1648
|
+
)
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
export const subscribeToContextGroup = ({
|
|
1652
|
+
disabled = false,
|
|
1653
|
+
setStateShallow,
|
|
1654
|
+
pseudoGroups,
|
|
1655
|
+
mediaGroups,
|
|
1656
|
+
componentContext,
|
|
1657
|
+
state,
|
|
1658
|
+
}: {
|
|
1659
|
+
disabled?: boolean
|
|
1660
|
+
setStateShallow: (next?: Partial<TamaguiComponentState> | undefined) => void
|
|
1661
|
+
pseudoGroups?: Set<string>
|
|
1662
|
+
mediaGroups?: Set<string>
|
|
1663
|
+
componentContext: ComponentContextI
|
|
1664
|
+
state: TamaguiComponentState
|
|
1665
|
+
}) => {
|
|
1666
|
+
// parent group pseudo listening
|
|
1667
|
+
if (pseudoGroups || mediaGroups) {
|
|
1668
|
+
const current = {
|
|
1669
|
+
pseudo: {},
|
|
1670
|
+
media: {},
|
|
1671
|
+
} satisfies GroupState
|
|
1672
|
+
|
|
1673
|
+
if (process.env.NODE_ENV === 'development' && !componentContext.groups) {
|
|
1674
|
+
console.debug(`No context group found`)
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
return componentContext.groups?.subscribe((name, { layout, pseudo }) => {
|
|
1678
|
+
if (pseudo && pseudoGroups?.has(name)) {
|
|
1679
|
+
// we emit a partial so merge it + change reference so mergeIfNotShallowEqual runs
|
|
1680
|
+
Object.assign(current.pseudo, pseudo)
|
|
1681
|
+
persist()
|
|
1682
|
+
} else if (layout && mediaGroups) {
|
|
1683
|
+
const mediaState = getMediaState(mediaGroups, layout)
|
|
1684
|
+
const next = mergeIfNotShallowEqual(current.media, mediaState)
|
|
1685
|
+
if (next !== current.media) {
|
|
1686
|
+
Object.assign(current.media, next)
|
|
1687
|
+
persist()
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
function persist() {
|
|
1691
|
+
// force it to be referentially different so it always updates
|
|
1692
|
+
const group = {
|
|
1693
|
+
...state.group,
|
|
1694
|
+
[name]: current,
|
|
1695
|
+
}
|
|
1696
|
+
setStateShallow({
|
|
1697
|
+
group,
|
|
1698
|
+
})
|
|
1699
|
+
}
|
|
1700
|
+
})
|
|
1701
|
+
}
|
|
1702
|
+
}
|
package/src/createVariable.ts
CHANGED
|
@@ -20,19 +20,28 @@ export interface Variable<A = any> {
|
|
|
20
20
|
|
|
21
21
|
export type MakeVariable<A = any> = A extends string | number ? Variable<A> : A
|
|
22
22
|
|
|
23
|
+
function constructCSSVariableName(name: string) {
|
|
24
|
+
return `var(--${process.env.TAMAGUI_CSS_VARIABLE_PREFIX || ''}${name})`
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
type VariableIn<A = any> = Pick<Variable<A>, 'key' | 'name' | 'val'>
|
|
24
28
|
export const createVariable = <A extends string | number | Variable = any>(
|
|
25
29
|
props: VariableIn<A>,
|
|
26
30
|
skipHash = false
|
|
27
31
|
): Variable<A> => {
|
|
28
32
|
if (!skipHash && isVariable(props)) return props
|
|
33
|
+
|
|
29
34
|
const { key, name, val } = props
|
|
30
35
|
return {
|
|
31
36
|
[IS_VAR]: true,
|
|
32
37
|
key: key!,
|
|
33
38
|
name: skipHash ? '' : simpleHash(name, 40),
|
|
34
39
|
val: val as any,
|
|
35
|
-
variable: isWeb
|
|
40
|
+
variable: isWeb
|
|
41
|
+
? skipHash
|
|
42
|
+
? constructCSSVariableName(name)
|
|
43
|
+
: createCSSVariable(name)
|
|
44
|
+
: '',
|
|
36
45
|
}
|
|
37
46
|
}
|
|
38
47
|
|
|
@@ -90,5 +99,5 @@ export const createCSSVariable = (nameProp: string, includeVar = true) => {
|
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
const name = simpleHash(nameProp, 60)
|
|
93
|
-
return includeVar ?
|
|
102
|
+
return includeVar ? constructCSSVariableName(name) : name
|
|
94
103
|
}
|