@tamagui/core 1.0.1-beta.216 → 1.0.1-beta.218

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 (50) hide show
  1. package/dist/cjs/createComponent.js +43 -90
  2. package/dist/cjs/createComponent.js.map +2 -2
  3. package/dist/cjs/createTamagui.js +2 -2
  4. package/dist/cjs/createTamagui.js.map +1 -1
  5. package/dist/cjs/helpers/getSplitStyles.js +48 -38
  6. package/dist/cjs/helpers/getSplitStyles.js.map +3 -3
  7. package/dist/cjs/helpers/getStylesAtomic.js +2 -4
  8. package/dist/cjs/helpers/getStylesAtomic.js.map +2 -2
  9. package/dist/cjs/helpers/mergeProps.js.map +2 -2
  10. package/dist/cjs/helpers/normalizeValueWithProperty.js +7 -2
  11. package/dist/cjs/helpers/normalizeValueWithProperty.js.map +2 -2
  12. package/dist/cjs/helpers/useShallowSetState.js.map +2 -2
  13. package/dist/cjs/hooks/useMedia.js +35 -38
  14. package/dist/cjs/hooks/useMedia.js.map +2 -2
  15. package/dist/cjs/hooks/useTheme.js +28 -12
  16. package/dist/cjs/hooks/useTheme.js.map +2 -2
  17. package/dist/cjs/views/ThemeInverse.js +1 -2
  18. package/dist/cjs/views/ThemeInverse.js.map +2 -2
  19. package/dist/esm/createComponent.js +45 -96
  20. package/dist/esm/createComponent.js.map +2 -2
  21. package/dist/esm/createTamagui.js +2 -2
  22. package/dist/esm/createTamagui.js.map +1 -1
  23. package/dist/esm/helpers/getSplitStyles.js +48 -38
  24. package/dist/esm/helpers/getSplitStyles.js.map +3 -3
  25. package/dist/esm/helpers/getStylesAtomic.js +2 -4
  26. package/dist/esm/helpers/getStylesAtomic.js.map +2 -2
  27. package/dist/esm/helpers/mergeProps.js.map +2 -2
  28. package/dist/esm/helpers/normalizeValueWithProperty.js +7 -2
  29. package/dist/esm/helpers/normalizeValueWithProperty.js.map +2 -2
  30. package/dist/esm/helpers/useShallowSetState.js.map +2 -2
  31. package/dist/esm/hooks/useMedia.js +36 -39
  32. package/dist/esm/hooks/useMedia.js.map +2 -2
  33. package/dist/esm/hooks/useTheme.js +28 -12
  34. package/dist/esm/hooks/useTheme.js.map +2 -2
  35. package/dist/esm/views/ThemeInverse.js +1 -2
  36. package/dist/esm/views/ThemeInverse.js.map +2 -2
  37. package/package.json +16 -16
  38. package/src/createComponent.tsx +58 -115
  39. package/src/createTamagui.ts +2 -2
  40. package/src/helpers/getSplitStyles.tsx +47 -37
  41. package/src/helpers/getStylesAtomic.ts +2 -4
  42. package/src/helpers/mergeProps.ts +7 -7
  43. package/src/helpers/normalizeValueWithProperty.ts +9 -2
  44. package/src/helpers/useShallowSetState.tsx +3 -1
  45. package/src/hooks/useMedia.tsx +42 -42
  46. package/src/hooks/useTheme.tsx +35 -20
  47. package/src/views/ThemeInverse.tsx +1 -2
  48. package/types/helpers/mergeProps.d.ts +2 -2
  49. package/types/helpers/useShallowSetState.d.ts +2 -1
  50. package/types/hooks/useTheme.d.ts +1 -1
@@ -199,18 +199,6 @@ export const getSplitStyles: StyleSplitter = (
199
199
  let style: ViewStyle = {}
200
200
  let flatTransforms: Record<string, any> | undefined
201
201
 
202
- function mergeStyle(key: string, val: any) {
203
- usedKeys[key] = usedKeys[key] || 1
204
- if (val && val[0] === '_') {
205
- classNames[key] = val
206
- } else if (key in stylePropsTransform) {
207
- flatTransforms ||= {}
208
- flatTransforms[key] = val
209
- } else {
210
- style[key] = normalizeValueWithProperty(val, key)
211
- }
212
- }
213
-
214
202
  const len = propKeys.length
215
203
  const rulesToInsert: RulesToInsert = []
216
204
  const classNames: ClassNamesObject = {}
@@ -225,6 +213,18 @@ export const getSplitStyles: StyleSplitter = (
225
213
  let keyInit = propKeys[i]
226
214
  const valInit = props[keyInit]
227
215
 
216
+ if (validStyleProps[keyInit] || keyInit.includes('-')) {
217
+ if (valInit && valInit[0] === '_') {
218
+ if (shouldDoClasses) {
219
+ classNames[keyInit] = valInit
220
+ } else {
221
+ style[keyInit] = reverseMapClassNameToValue(keyInit, valInit)
222
+ }
223
+ usedKeys[keyInit] = 1
224
+ continue
225
+ }
226
+ }
227
+
228
228
  // normalize shorthands up front
229
229
  const expandedKey = conf.shorthands[keyInit]
230
230
  if (expandedKey) {
@@ -251,6 +251,18 @@ export const getSplitStyles: StyleSplitter = (
251
251
  continue
252
252
  }
253
253
 
254
+ function mergeStyle(key: string, val: any) {
255
+ usedKeys[key] = usedKeys[key] || 1
256
+ if (val && val[0] === '_') {
257
+ classNames[key] = val
258
+ } else if (key in stylePropsTransform) {
259
+ flatTransforms ||= {}
260
+ flatTransforms[key] = val
261
+ } else {
262
+ style[key] = normalizeValueWithProperty(val, key)
263
+ }
264
+ }
265
+
254
266
  if (process.env.TAMAGUI_TARGET === 'web') {
255
267
  /**
256
268
  * Copying in the accessibility/prop handling from react-native-web here
@@ -416,6 +428,7 @@ export const getSplitStyles: StyleSplitter = (
416
428
  !isValidClassName &&
417
429
  keyInit.includes(PROP_SPLIT) &&
418
430
  validStyles[keyInit.split(PROP_SPLIT)[0]]
431
+
419
432
  if (isValidClassName || isMediaOrPseudo) {
420
433
  usedKeys[keyInit] = 1
421
434
  mergeClassName(transforms, classNames, keyInit, valInit, isMediaOrPseudo)
@@ -479,13 +492,9 @@ export const getSplitStyles: StyleSplitter = (
479
492
  }
480
493
 
481
494
  if (process.env.NODE_ENV === 'development' && debug === 'verbose') {
482
- if (!isServer) {
483
- // eslint-disable-next-line no-console
484
- console.log('expanded', expanded)
485
- // eslint-disable-next-line no-console
486
- console.log('usedKeys', usedKeys)
495
+ if (!isServer && isWeb) {
487
496
  // eslint-disable-next-line no-console
488
- console.log('current', { ...style })
497
+ console.log('expanded', expanded, '\nusedKeys', usedKeys, '\ncurrent', { ...style })
489
498
  }
490
499
  // eslint-disable-next-line no-console
491
500
  console.groupEnd()
@@ -528,7 +537,7 @@ export const getSplitStyles: StyleSplitter = (
528
537
  conf,
529
538
  languageContext,
530
539
  true,
531
- true
540
+ state.noClassNames
532
541
  )
533
542
 
534
543
  const descriptor = pseudoDescriptors[key as keyof typeof pseudoDescriptors]
@@ -545,6 +554,7 @@ export const getSplitStyles: StyleSplitter = (
545
554
 
546
555
  if (shouldDoClasses) {
547
556
  const pseudoStyles = getAtomicStyle(pseudoStyleObject, descriptor)
557
+ if (debug) console.log('pseudoStyles', pseudoStyles)
548
558
  for (const psuedoStyle of pseudoStyles) {
549
559
  const fullKey = `${psuedoStyle.property}${PROP_SPLIT}${descriptor.name}`
550
560
  if (!usedKeys[fullKey]) {
@@ -603,7 +613,7 @@ export const getSplitStyles: StyleSplitter = (
603
613
  languageContext,
604
614
  // TODO try true like pseudo
605
615
  false,
606
- true
616
+ state.noClassNames
607
617
  )
608
618
 
609
619
  if (process.env.NODE_ENV === 'development' && debug === 'verbose') {
@@ -647,7 +657,6 @@ export const getSplitStyles: StyleSplitter = (
647
657
  }
648
658
 
649
659
  if (validStyleProps[key]) {
650
- usedKeys[key] = 1
651
660
  mergeStyle(key, val)
652
661
  continue
653
662
  }
@@ -677,31 +686,32 @@ export const getSplitStyles: StyleSplitter = (
677
686
 
678
687
  // add in defaults if not set:
679
688
  if (parentSplitStyles) {
680
- if (process.env.NODE_ENV === 'development') {
681
- if (debug === 'verbose') {
682
- // eslint-disable-next-line no-console
683
- console.log(` add in styled() parent styles`, { parentSplitStyles, shouldDoClasses })
684
- }
685
- }
686
689
  if (process.env.TAMAGUI_TARGET === 'web') {
687
- for (const key in parentSplitStyles.classNames) {
688
- if (key in classNames) continue
689
- if (key in style) continue
690
- const val = parentSplitStyles.classNames[key]
691
- if (!shouldDoClasses) {
692
- style[key] = reverseMapClassNameToValue(key, val)
693
- } else {
690
+ if (shouldDoClasses) {
691
+ for (const key in parentSplitStyles.classNames) {
692
+ const val = parentSplitStyles.classNames[key]
693
+ if (key in style || key in classNames) continue
694
694
  classNames[key] = val
695
695
  }
696
696
  }
697
697
  }
698
698
  if (!shouldDoClasses) {
699
699
  for (const key in parentSplitStyles.style) {
700
- if (key in classNames) continue
701
- if (key in style) continue
700
+ if (key in classNames || key in style) continue
702
701
  style[key] = parentSplitStyles.style[key]
703
702
  }
704
703
  }
704
+ if (process.env.NODE_ENV === 'development') {
705
+ if (debug === 'verbose') {
706
+ // eslint-disable-next-line no-console
707
+ console.log(` add in parent styled()`, {
708
+ parentSplitStyles,
709
+ shouldDoClasses,
710
+ style: { ...style },
711
+ classNames: { ...classNames },
712
+ })
713
+ }
714
+ }
705
715
  }
706
716
 
707
717
  if (process.env.TAMAGUI_TARGET === 'web') {
@@ -716,7 +726,7 @@ export const getSplitStyles: StyleSplitter = (
716
726
  mergeClassName(transforms, classNames, key, atomicStyle.identifier)
717
727
  }
718
728
  if (!IS_STATIC) {
719
- style = emptyObject
729
+ style = {}
720
730
  }
721
731
  }
722
732
  }
@@ -78,10 +78,8 @@ const generateAtomicStyles = (
78
78
  const style = styleIn as Record<string, string | null | undefined>
79
79
 
80
80
  // transform
81
- if (style.transform) {
82
- if (Array.isArray(style.transform)) {
83
- style.transform = style.transform.map(mapTransform).join(' ')
84
- }
81
+ if (style.transform && Array.isArray(style.transform)) {
82
+ style.transform = style.transform.map(mapTransform).join(' ')
85
83
  }
86
84
 
87
85
  styleToCSS(style)
@@ -12,16 +12,16 @@
12
12
  * - shorthands can be expanded before merging
13
13
  */
14
14
 
15
- type StringRecord = Record<string, string>
15
+ type AnyRecord = Record<string, any>
16
16
 
17
17
  export const mergeProps = (
18
18
  a: Object,
19
19
  b: Object,
20
20
  leaveOutClassNames = false,
21
- inverseShorthands?: StringRecord
21
+ inverseShorthands?: AnyRecord
22
22
  ) => {
23
- const out: StringRecord = {}
24
- const outCns: StringRecord = leaveOutClassNames ? {} : (null as any)
23
+ const out: AnyRecord = {}
24
+ const outCns: AnyRecord = leaveOutClassNames ? {} : (null as any)
25
25
  for (const key in a) {
26
26
  mergeProp(out, outCns, a, b, key, leaveOutClassNames, inverseShorthands)
27
27
  }
@@ -32,13 +32,13 @@ export const mergeProps = (
32
32
  }
33
33
 
34
34
  function mergeProp(
35
- out: StringRecord,
36
- outCns: StringRecord,
35
+ out: AnyRecord,
36
+ outCns: AnyRecord,
37
37
  a: Object,
38
38
  b: Object | undefined,
39
39
  key: string,
40
40
  leaveOutClassNames: boolean,
41
- inverseShorthands?: StringRecord
41
+ inverseShorthands?: AnyRecord
42
42
  ) {
43
43
  const val = a[key]
44
44
  const shorthand = inverseShorthands ? inverseShorthands[key] : null
@@ -3,6 +3,8 @@
3
3
  * Copyright (c) Nicolas Gallagher licensed under the MIT license.
4
4
  */
5
5
 
6
+ import { isWeb } from '@tamagui/constants'
7
+
6
8
  import { getAllSelectors } from './insertStyleRule'
7
9
  import { normalizeColor } from './normalizeColor'
8
10
 
@@ -20,9 +22,11 @@ export function normalizeValueWithProperty(value: any, property?: string): any {
20
22
  if (
21
23
  process.env.TAMAGUI_TARGET === 'web' &&
22
24
  typeof value === 'number' &&
23
- (property === undefined || !unitlessNumbers[property])
25
+ (property === undefined || (!unitlessNumbers[property] && !stringNumbers[property]))
24
26
  ) {
25
27
  res = `${value}px`
28
+ } else if (isWeb && property !== undefined && stringNumbers[property]) {
29
+ res = `${res}`
26
30
  } else if (property && colorProps[property]) {
27
31
  res = normalizeColor(value)
28
32
  cache[value] = res
@@ -45,6 +49,10 @@ const colorProps = {
45
49
  textShadowColor: true,
46
50
  }
47
51
 
52
+ const stringNumbers = {
53
+ zIndex: true,
54
+ }
55
+
48
56
  const unitlessNumbers = {
49
57
  WebkitLineClamp: true,
50
58
  animationIterationCount: true,
@@ -74,7 +82,6 @@ const unitlessNumbers = {
74
82
  orphans: true,
75
83
  tabSize: true,
76
84
  widows: true,
77
- zIndex: true,
78
85
  zoom: true,
79
86
  scale: true,
80
87
  scaleX: true,
@@ -1,8 +1,10 @@
1
1
  import React, { useCallback } from 'react'
2
2
 
3
+ import { DebugProp } from '../types'
4
+
3
5
  export function useShallowSetState<State extends Object>(
4
6
  setter: React.Dispatch<React.SetStateAction<State>>,
5
- debug?: boolean | 'verbose',
7
+ debug?: DebugProp,
6
8
  debugName?: string
7
9
  ) {
8
10
  return useCallback(
@@ -1,5 +1,4 @@
1
- import { useIsomorphicLayoutEffect } from '@tamagui/constants'
2
- import { MutableRefObject, useEffect, useMemo, useState } from 'react'
1
+ import { useEffect, useMemo, useSyncExternalStore } from 'react'
3
2
 
4
3
  import { getConfig } from '../config'
5
4
  import { createProxy } from '../helpers/createProxy'
@@ -54,15 +53,12 @@ export function removeMediaQueryListener(key: MediaQueryKey, cb: MediaListener)
54
53
 
55
54
  export const mediaQueryConfig: MediaQueries = {}
56
55
 
57
- export const getMedia = () => {
58
- return mediaState
59
- }
56
+ export const getMedia = () => mediaState
60
57
 
61
58
  // for SSR capture it at time of startup
62
- let curState: MediaQueryState
63
59
  let initState: MediaQueryState
64
60
  export const getInitialMediaState = () => {
65
- return (getConfig().disableSSR ? curState : initState) || {}
61
+ return (getConfig().disableSSR ? mediaState : initState) || {}
66
62
  }
67
63
 
68
64
  let mediaKeysOrdered: string[]
@@ -79,8 +75,8 @@ export const configureMedia = (config: TamaguiInternalConfig) => {
79
75
  mediaState[key] = mediaQueryDefaultActive?.[key] || false
80
76
  }
81
77
  Object.assign(mediaQueryConfig, media)
82
- curState = { ...mediaState }
83
- initState = curState
78
+ initState = { ...mediaState }
79
+ updateCurrentState()
84
80
  mediaKeysOrdered = Object.keys(media)
85
81
  if (config.disableSSR) {
86
82
  setupMediaListeners()
@@ -126,7 +122,7 @@ function setupMediaListeners() {
126
122
  const next = !!getMatch().matches
127
123
  if (next === mediaState[key]) return
128
124
  mediaState[key] = next
129
- curState = { ...mediaState }
125
+ updateCurrentState()
130
126
  const cur = listeners[key]
131
127
  if (cur?.size) {
132
128
  cur.forEach((cb) => cb(next))
@@ -143,45 +139,49 @@ export function useMediaListeners(config: TamaguiInternalConfig) {
143
139
  }, [])
144
140
  }
145
141
 
142
+ const currentStateListeners = new Set<any>()
143
+ function updateCurrentState() {
144
+ currentStateListeners.forEach((cb) => cb(mediaState))
145
+ }
146
+ function subscribe(subscriber: any) {
147
+ currentStateListeners.add(subscriber)
148
+ return () => currentStateListeners.delete(subscriber)
149
+ }
150
+
151
+ type MediaKeysState = {
152
+ [key: string]: any
153
+ }
154
+
146
155
  export function useMedia(): {
147
156
  [key in MediaQueryKey]: boolean
148
157
  } {
149
- const keys = useSafeRef({} as Record<string, boolean>)
150
- const [state, setState] = useState<MediaQueryState>(() => getMediaState(initState || {}, keys))
158
+ const keys = useSafeRef<MediaKeysState>({ prev: initState })
151
159
 
152
- function updateState() {
153
- setState((prev) => {
154
- for (const key in keys.current) {
155
- if (prev[key] !== mediaState[key]) {
156
- return getMediaState({ ...mediaState }, keys)
157
- }
160
+ const state = useSyncExternalStore<MediaQueryState>(
161
+ subscribe,
162
+ () => {
163
+ const { prev, ...curKeys } = keys.current
164
+ const shouldUpdate = !prev || Object.keys(curKeys).some((k) => mediaState[k] !== prev[k])
165
+ if (shouldUpdate) {
166
+ const next = { ...mediaState }
167
+ keys.current.prev = next
168
+ return next
158
169
  }
159
170
  return prev
160
- })
161
- }
162
-
163
- useIsomorphicLayoutEffect(() => {
164
- updateState()
165
- const disposers = Object.keys(keys.current).map((key) =>
166
- addMediaQueryListener(key, updateState)
167
- )
168
- return () => {
169
- disposers.forEach((cb) => cb())
170
- }
171
- }, [keys.current])
172
-
173
- return state
174
- }
175
-
176
- function getMediaState(state: MediaQueryState, keys: MutableRefObject<Record<string, boolean>>) {
177
- return new Proxy(state, {
178
- get(_, key: string) {
179
- if (!keys.current[key]) {
180
- keys.current[key] = true
181
- }
182
- return Reflect.get(state, key)
183
171
  },
184
- })
172
+ () => initState
173
+ )
174
+
175
+ return useMemo(() => {
176
+ return new Proxy(state, {
177
+ get(_, key: string) {
178
+ if (!keys.current[key]) {
179
+ keys.current[key] = true
180
+ }
181
+ return Reflect.get(state, key)
182
+ },
183
+ })
184
+ }, [state])
185
185
  }
186
186
 
187
187
  /**
@@ -37,7 +37,8 @@ export const useTheme = (
37
37
  themeName?: string | null,
38
38
  componentName?: string,
39
39
  props?: ThemeProps,
40
- forceUpdate?: any
40
+ forceUpdate?: any,
41
+ disableTracking?: boolean
41
42
  ): ThemeParsed => {
42
43
  // TODO this can use useChangeThemeEffect almost ready
43
44
  if (isRSC) {
@@ -99,18 +100,40 @@ export const useTheme = (
99
100
  }
100
101
 
101
102
  return useMemo(() => {
102
- return getThemeProxied(theme, name, className, themeManager, state, debugProp)
103
- }, [theme, name, className, themeManager, state, debugProp])
103
+ return getThemeProxied({
104
+ theme,
105
+ name,
106
+ className,
107
+ themeManager,
108
+ onStringKeyAccess(key) {
109
+ if (disableTracking) return
110
+ if (state) {
111
+ if (state.current.isRendering && !state.current.keys.has(key)) {
112
+ state.current.keys.add(key)
113
+ if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
114
+ // eslint-disable-next-line no-console
115
+ console.log(' 🔸 tracking theme', key)
116
+ }
117
+ }
118
+ }
119
+ },
120
+ })
121
+ }, [theme, name, className, themeManager, debugProp, disableTracking])
104
122
  }
105
123
 
106
- function getThemeProxied(
107
- theme: any,
108
- name: string,
109
- className?: string,
110
- themeManager?: ThemeManager | null,
111
- state?: React.MutableRefObject<UseThemeState>,
112
- debugProp?: boolean | 'verbose'
113
- ) {
124
+ function getThemeProxied({
125
+ theme,
126
+ name,
127
+ className,
128
+ themeManager,
129
+ onStringKeyAccess,
130
+ }: {
131
+ theme: any
132
+ name: string
133
+ onStringKeyAccess?: (cb: string) => void
134
+ className?: string
135
+ themeManager?: ThemeManager | null
136
+ }) {
114
137
  return createProxy(theme, {
115
138
  has(_, key) {
116
139
  if (typeof key === 'string' && key[0] === '$') {
@@ -141,15 +164,7 @@ function getThemeProxied(
141
164
  if (!themeManager) {
142
165
  return theme[key]
143
166
  }
144
- if (state) {
145
- if (state.current.isRendering && !state.current.keys.has(key)) {
146
- state.current.keys.add(key)
147
- if (process.env.NODE_ENV === 'development' && debugProp === 'verbose') {
148
- // eslint-disable-next-line no-console
149
- console.log(' 🔸 tracking theme', key)
150
- }
151
- }
152
- }
167
+ onStringKeyAccess?.(key)
153
168
  return themeManager.getValue(key)
154
169
  },
155
170
  })
@@ -19,8 +19,7 @@ export const ThemeInverse = ({
19
19
  useIsomorphicLayoutEffect(() => {
20
20
  if (themeName.startsWith('dark')) {
21
21
  setName('light')
22
- }
23
- if (themeName.startsWith('light')) {
22
+ } else if (themeName.startsWith('light')) {
24
23
  setName('dark')
25
24
  }
26
25
  }, [defaultTheme, themeName])
@@ -11,7 +11,7 @@
11
11
  * - classNames can be extracted out separately
12
12
  * - shorthands can be expanded before merging
13
13
  */
14
- declare type StringRecord = Record<string, string>;
15
- export declare const mergeProps: (a: Object, b: Object, leaveOutClassNames?: boolean, inverseShorthands?: StringRecord) => readonly [StringRecord, StringRecord];
14
+ declare type AnyRecord = Record<string, any>;
15
+ export declare const mergeProps: (a: Object, b: Object, leaveOutClassNames?: boolean, inverseShorthands?: AnyRecord) => readonly [AnyRecord, AnyRecord];
16
16
  export {};
17
17
  //# sourceMappingURL=mergeProps.d.ts.map
@@ -1,3 +1,4 @@
1
1
  import React from 'react';
2
- export declare function useShallowSetState<State extends Object>(setter: React.Dispatch<React.SetStateAction<State>>, debug?: boolean | 'verbose', debugName?: string): (next: Partial<State>) => void;
2
+ import { DebugProp } from '../types';
3
+ export declare function useShallowSetState<State extends Object>(setter: React.Dispatch<React.SetStateAction<State>>, debug?: DebugProp, debugName?: string): (next: Partial<State>) => void;
3
4
  //# sourceMappingURL=useShallowSetState.d.ts.map
@@ -9,7 +9,7 @@ export interface ThemeProps {
9
9
  reset?: boolean;
10
10
  debug?: boolean | 'verbose';
11
11
  }
12
- export declare const useTheme: (themeName?: string | null, componentName?: string, props?: ThemeProps, forceUpdate?: any) => ThemeParsed;
12
+ export declare const useTheme: (themeName?: string | null, componentName?: string, props?: ThemeProps, forceUpdate?: any, disableTracking?: boolean) => ThemeParsed;
13
13
  export declare const getThemeManager: (theme: any) => ThemeManager | undefined;
14
14
  export declare function useThemeName(opts?: {
15
15
  parent?: true;