zyzz 0.0.22 → 0.0.23

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 (59) hide show
  1. package/dist/babel/NativeJsx.d.ts.map +1 -1
  2. package/dist/babel/NativeJsx.js +14 -4
  3. package/dist/babel/NativeJsx.js.map +1 -1
  4. package/dist/compiler/Graph.js +78 -35
  5. package/dist/compiler/Graph.js.map +1 -1
  6. package/dist/compiler/internal/Dynamic.d.ts.map +1 -1
  7. package/dist/compiler/internal/Dynamic.js +3 -0
  8. package/dist/compiler/internal/Dynamic.js.map +1 -1
  9. package/dist/compiler/internal/Expression.js +2 -2
  10. package/dist/compiler/internal/Expression.js.map +1 -1
  11. package/dist/compiler/internal/NativeBindings.d.ts.map +1 -1
  12. package/dist/compiler/internal/NativeBindings.js +1 -0
  13. package/dist/compiler/internal/NativeBindings.js.map +1 -1
  14. package/dist/compiler/internal/NativeVars.d.ts.map +1 -1
  15. package/dist/compiler/internal/NativeVars.js +3 -0
  16. package/dist/compiler/internal/NativeVars.js.map +1 -1
  17. package/dist/compiler/internal/Themes.d.ts +2 -0
  18. package/dist/compiler/internal/Themes.d.ts.map +1 -1
  19. package/dist/compiler/internal/Themes.js +24 -22
  20. package/dist/compiler/internal/Themes.js.map +1 -1
  21. package/dist/metro/Graph.d.ts.map +1 -1
  22. package/dist/metro/Graph.js +11 -2
  23. package/dist/metro/Graph.js.map +1 -1
  24. package/dist/react-native/StyleSheet.d.ts +7 -3
  25. package/dist/react-native/StyleSheet.d.ts.map +1 -1
  26. package/dist/react-native/StyleSheet.js +8 -0
  27. package/dist/react-native/StyleSheet.js.map +1 -1
  28. package/dist/react-native/internal/Device.js +28 -1
  29. package/dist/react-native/internal/Device.js.map +2 -2
  30. package/dist/react-native/internal/Scalar.d.ts +16 -0
  31. package/dist/react-native/internal/Scalar.d.ts.map +1 -1
  32. package/dist/react-native/internal/Scalar.js +53 -2
  33. package/dist/react-native/internal/Scalar.js.map +1 -1
  34. package/dist/react-native/internal/Styles.d.ts +19 -0
  35. package/dist/react-native/internal/Styles.d.ts.map +1 -0
  36. package/dist/react-native/internal/Styles.js +35 -0
  37. package/dist/react-native/internal/Styles.js.map +1 -0
  38. package/dist/react-native/react.d.ts +39 -14
  39. package/dist/react-native/react.d.ts.map +1 -1
  40. package/dist/react-native/react.js +8 -39
  41. package/dist/react-native/react.js.map +1 -1
  42. package/dist/runtime/NativeDynamic.d.ts.map +1 -1
  43. package/dist/runtime/NativeDynamic.js +5 -0
  44. package/dist/runtime/NativeDynamic.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/babel/NativeJsx.ts +17 -8
  47. package/src/compiler/Graph.ts +89 -38
  48. package/src/compiler/internal/Dynamic.ts +4 -0
  49. package/src/compiler/internal/Expression.ts +2 -2
  50. package/src/compiler/internal/NativeBindings.ts +1 -0
  51. package/src/compiler/internal/NativeVars.ts +3 -0
  52. package/src/compiler/internal/Themes.ts +29 -23
  53. package/src/metro/Graph.ts +17 -2
  54. package/src/react-native/StyleSheet.ts +32 -9
  55. package/src/react-native/internal/Device.ts +67 -1
  56. package/src/react-native/internal/Scalar.ts +73 -2
  57. package/src/react-native/internal/Styles.ts +77 -0
  58. package/src/react-native/react.ts +61 -61
  59. package/src/runtime/NativeDynamic.ts +7 -0
@@ -120,6 +120,34 @@ export type VarsRead = {
120
120
  readonly vars: Readonly<Record<string, Theme.Definition>>
121
121
  }
122
122
 
123
+ /** Resolves the variable alternatives a linked configuration or variable set exposes to native hooks. */
124
+ export function varsRead(
125
+ link: Pick<Link, 'call' | 'definition' | 'members'>,
126
+ ): Omit<VarsRead, 'end' | 'start'> {
127
+ const named = link.call.options?.themes !== undefined
128
+ const vars = (() => {
129
+ if (!named) return { default: link.definition }
130
+ return Object.fromEntries(
131
+ Object.entries(link.members ?? {}).flatMap((entry) => {
132
+ const [key, member] = entry
133
+ const path = JSON.parse(key) as string[]
134
+ if (path.length === 1 && link.call.selection)
135
+ return [[path[0]!, member.definition]]
136
+ if (path.length === 2 && path[0] === 'themes')
137
+ return [[path[1]!, member.definition]]
138
+ return []
139
+ }),
140
+ )
141
+ })()
142
+
143
+ return {
144
+ defaultVars: named ? String(link.call.options!.defaultTheme) : 'default',
145
+ owner: link.call.name,
146
+ unnamed: !named,
147
+ vars,
148
+ }
149
+ }
150
+
123
151
  /** Shared graph data; no filesystem or runtime evaluation is involved. */
124
152
  export type Context = {
125
153
  /** Canonical identifier names collected during graph validation. */
@@ -1796,29 +1824,7 @@ export function collect(program: Ast.Program, options: collect.Options) {
1796
1824
  )
1797
1825
  return false
1798
1826
 
1799
- const named = link.call.options?.themes !== undefined
1800
- const vars = (() => {
1801
- if (!named) return { default: link.definition }
1802
- return Object.fromEntries(
1803
- Object.entries(link.members ?? {}).flatMap((entry) => {
1804
- const [key, member] = entry
1805
- const path = JSON.parse(key) as string[]
1806
- if (path.length === 1 && link.call.selection)
1807
- return [[path[0]!, member.definition]]
1808
- if (path.length === 2 && path[0] === 'themes')
1809
- return [[path[1]!, member.definition]]
1810
- return []
1811
- }),
1812
- )
1813
- })()
1814
- varsReads.push({
1815
- defaultVars: named ? String(link.call.options!.defaultTheme) : 'default',
1816
- end: node.end,
1817
- owner: link.call.name,
1818
- start: node.start,
1819
- unnamed: !named,
1820
- vars,
1821
- })
1827
+ varsReads.push({ ...varsRead(link), end: node.end, start: node.start })
1822
1828
  return true
1823
1829
  }
1824
1830
 
@@ -148,7 +148,10 @@ export function read(
148
148
  seen.add(filename)
149
149
  const source = snapshot.readSync(filename)
150
150
  const file = manifest(filename)
151
- if (file && !/['"]zyzz(?:\/default)?['"]/.test(source)) {
151
+ if (
152
+ file &&
153
+ !/['"]zyzz(?:\/default|\/react-native(?:\/react)?)?['"]/.test(source)
154
+ ) {
152
155
  const data = JSON.parse(snapshot.readSync(file)) as Record<
153
156
  string,
154
157
  unknown
@@ -174,7 +177,19 @@ export function read(
174
177
  for (const node of snapshot.parse({ moduleId: identity(filename), source })
175
178
  .program.body) {
176
179
  const name = specifier(node)
177
- if (name === 'zyzz' || name === 'zyzz/default') {
180
+ // Consumers import vars profiles from defineConfig modules, so those modules must compile.
181
+ const native =
182
+ (name === 'zyzz/react-native' || name === 'zyzz/react-native/react') &&
183
+ node.type === 'ImportDeclaration' &&
184
+ node.specifiers.some(
185
+ (entry) =>
186
+ entry.type === 'ImportSpecifier' &&
187
+ entry.importKind !== 'type' &&
188
+ (entry.imported.type === 'Identifier'
189
+ ? entry.imported.name
190
+ : entry.imported.value) === 'defineConfig',
191
+ )
192
+ if (name === 'zyzz' || name === 'zyzz/default' || native) {
178
193
  authored.add(filename)
179
194
  return true
180
195
  }
@@ -278,6 +278,14 @@ export function compile<
278
278
  output[`${property}Right`] = right!
279
279
  output[`${property}Bottom`] = bottom!
280
280
  output[`${property}Left`] = left!
281
+ } else if (property === 'flex') {
282
+ Object.assign(output, Scalar.flex(value, options, path))
283
+ } else if (property === 'inset') {
284
+ const values =
285
+ typeof value === 'string' ? Calculation.parts(value) : [value]
286
+ const [top, right = top, bottom = top, left = right] =
287
+ values.map((value) => convert('offset', value, options, path))
288
+ Object.assign(output, { top, right, bottom, left })
281
289
  } else if (
282
290
  property === 'borderColor' ||
283
291
  property === 'borderWidth'
@@ -613,15 +621,26 @@ type Length =
613
621
  | `${number}px`
614
622
  | `${number}rem`
615
623
  | `${'c' | 'C'}${'a' | 'A'}${'l' | 'L'}${'c' | 'C'}(${string})`
616
- type Box =
617
- | Length
618
- | `${Length} ${Length}`
619
- | `${Length} ${Length} ${Length}`
620
- | Box4
624
+ type Box<item extends string | number = Length> =
625
+ | item
626
+ | `${item} ${item}`
627
+ | `${item} ${item} ${item}`
628
+ | Box4<item>
621
629
  // Distribute the first item to keep case-insensitive calculations below the template expansion limit.
622
- type Box4<first extends string | number = Length> = first extends unknown
623
- ? `${first} ${Length} ${Length} ${Length}`
624
- : never
630
+ type Box4<
631
+ item extends string | number,
632
+ first extends string | number = item,
633
+ > = first extends unknown ? `${first} ${item} ${item} ${item}` : never
634
+ type FlexBasis = Length | `${number}%` | 'auto'
635
+ type Flex =
636
+ | number
637
+ | 'initial'
638
+ | 'none'
639
+ | FlexBasis
640
+ | `${number} ${number | FlexBasis}`
641
+ | `${FlexBasis} ${number}`
642
+ | `${number} ${number} ${FlexBasis}`
643
+ | `${FlexBasis} ${number} ${number}`
625
644
  type Weight =
626
645
  | 'bold'
627
646
  | 'normal'
@@ -666,7 +685,11 @@ type Atom<kind> = kind extends readonly string[]
666
685
  ? Length | number
667
686
  : kind extends 'box' | 'boxSigned'
668
687
  ? Box
669
- : Length
688
+ : kind extends 'boxOffset'
689
+ ? Box<Length | `${number}%`>
690
+ : kind extends 'flex'
691
+ ? Flex
692
+ : Length
670
693
 
671
694
  type Reference<property extends keyof typeof properties> = {
672
695
  [group in Token.Group]: property extends Token.Properties<group>
@@ -5,6 +5,7 @@ import NativeZyzz from './NativeZyzz.js'
5
5
  import * as React from 'react'
6
6
  import * as ReactNative from 'react-native'
7
7
  import * as Store from './Store.js'
8
+ import * as Styles from './Styles.js'
8
9
  import * as Subscription from '../react.js'
9
10
  import * as Viewport from './Viewport.js'
10
11
 
@@ -22,7 +23,72 @@ export function defineConfig<const options extends Config.create.Options = {}>(
22
23
  /** Provides selected variables, appearance, and automatic native dimensions. */
23
24
  export const Provider = dimensions(Subscription.Provider)
24
25
 
25
- export { useStyles, useVars, withStyles } from '../react.js'
26
+ /**
27
+ * Compiler-inserted subscription for resolving native style applications.
28
+ * Outside a Provider, selections use default variables, the light scheme, and window dimensions.
29
+ */
30
+ export function useStyles(): Subscription.useStyles.ReturnType {
31
+ const owner = React.useContext(Store.context)
32
+ const [fallback] = React.useState(() =>
33
+ Store.create({
34
+ colorScheme: 'light',
35
+ viewport: ReactNative.Dimensions.get('window'),
36
+ }),
37
+ )
38
+ React.useLayoutEffect(() => {
39
+ if (owner) return
40
+ const update = () =>
41
+ fallback.update({
42
+ colorScheme: 'light',
43
+ viewport: ReactNative.Dimensions.get('window'),
44
+ })
45
+ // Dimensions can change between render and commit.
46
+ update()
47
+ const subscription = ReactNative.Dimensions.addEventListener(
48
+ 'change',
49
+ update,
50
+ )
51
+ return () => subscription.remove()
52
+ }, [fallback, owner])
53
+
54
+ return Styles.use(owner ?? fallback)
55
+ }
56
+
57
+ export { useVars } from '../react.js'
58
+
59
+ /**
60
+ * Resolves compiled style props for function components using React updates.
61
+ * @param Component - Function, memo, or ref-forwarding component receiving native styles.
62
+ * @param options - Additional style-bearing prop names.
63
+ * @returns A component with the original call signature and ref. Type parameters remain when `styleProps` is omitted.
64
+ * @throws For invalid style prop names.
65
+ */
66
+ export function withStyles<
67
+ props extends object,
68
+ result extends React.ReactNode,
69
+ >(
70
+ Component: (props: props) => result,
71
+ options?: NoInfer<Subscription.withStyles.Options<(props: props) => result>>,
72
+ ): (props: props) => result
73
+ /**
74
+ * Resolves compiled style props for class and host components using React updates.
75
+ * @param Component - Class component or host element type receiving native styles.
76
+ * @param options - Additional style-bearing prop names.
77
+ * @returns A component preserving the original props and instance ref.
78
+ * @throws For invalid style prop names.
79
+ */
80
+ export function withStyles<const component extends React.ElementType>(
81
+ Component: component,
82
+ options?: Subscription.withStyles.Options<component>,
83
+ ): React.ForwardRefExoticComponent<React.ComponentPropsWithRef<component>>
84
+ export function withStyles(
85
+ Component: React.ElementType,
86
+ options: Subscription.withStyles.Options<
87
+ (props: Readonly<Record<string, unknown>>) => React.ReactNode
88
+ > = {},
89
+ ): React.ElementType {
90
+ return Styles.wrap(Component, options, useStyles)
91
+ }
26
92
 
27
93
  function dimensions<props extends Subscription.Provider.Props>(
28
94
  Component: React.FunctionComponent<props>,
@@ -48,6 +48,7 @@ export const properties = {
48
48
  columnGap: 'length',
49
49
  direction: ['ltr', 'rtl'],
50
50
  display: ['contents', 'flex', 'none'],
51
+ flex: 'flex',
51
52
  flexBasis: 'size',
52
53
  flexDirection: ['column', 'column-reverse', 'row', 'row-reverse'],
53
54
  flexGrow: 'number',
@@ -60,6 +61,7 @@ export const properties = {
60
61
  fontWeight: 'weight',
61
62
  gap: 'length',
62
63
  height: 'size',
64
+ inset: 'boxOffset',
63
65
  justifyContent: [
64
66
  'center',
65
67
  'flex-end',
@@ -206,18 +208,87 @@ export function convert(
206
208
  return value
207
209
  fail('unsupported_value', 'Invalid native percentage.', path)
208
210
  }
209
- if (kind === 'box' || kind === 'boxSigned') {
211
+ if (kind === 'flex') {
212
+ flex(value, options, path)
213
+ return 0
214
+ }
215
+ if (kind === 'box' || kind === 'boxOffset' || kind === 'boxSigned') {
210
216
  const values =
211
217
  typeof value === 'string' ? Calculation.parts(value) : [value]
212
218
  if (!values.length || values.length > 4)
213
219
  fail('unsupported_value', 'Use one to four scalar lengths.', path)
214
220
  for (const value of values)
215
- length(value, options, kind === 'boxSigned', path)
221
+ if (kind === 'boxOffset') convert('offset', value, options, path)
222
+ else length(value, options, kind === 'boxSigned', path)
216
223
  return 0
217
224
  }
218
225
  return length(value, options, kind === 'signed' || kind === 'offset', path)
219
226
  }
220
227
 
228
+ /**
229
+ * Expands the CSS `flex` shorthand to native longhands with CSS semantics.
230
+ * Omitted factors default to 1 and an omitted basis to 0, unlike the React Native `flex` prop.
231
+ */
232
+ export function flex(
233
+ value: number | string,
234
+ options: Options,
235
+ path: readonly string[],
236
+ ): flex.ReturnType {
237
+ const keyword = typeof value === 'string' ? value.trim() : undefined
238
+ if (keyword === 'none')
239
+ return { flexBasis: 'auto', flexGrow: 0, flexShrink: 0 }
240
+ if (keyword === 'auto')
241
+ return { flexBasis: 'auto', flexGrow: 1, flexShrink: 1 }
242
+ if (keyword === 'initial')
243
+ return { flexBasis: 'auto', flexGrow: 0, flexShrink: 1 }
244
+
245
+ const parts = typeof value === 'string' ? Calculation.parts(value) : [value]
246
+ const kinds: ('basis' | 'factor')[] = []
247
+ for (const part of parts) {
248
+ const numeric =
249
+ typeof part === 'number' ||
250
+ /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(part)
251
+ // A unitless zero after two factors is the basis, as in CSS.
252
+ const basis =
253
+ !numeric || (kinds.join(' ') === 'factor factor' && Number(part) === 0)
254
+ kinds.push(basis ? 'basis' : 'factor')
255
+ }
256
+ // Factors stay adjacent, and the basis may precede or follow them.
257
+ if (
258
+ ![
259
+ 'basis',
260
+ 'basis factor',
261
+ 'basis factor factor',
262
+ 'factor',
263
+ 'factor basis',
264
+ 'factor factor',
265
+ 'factor factor basis',
266
+ ].includes(kinds.join(' '))
267
+ )
268
+ fail('unsupported_value', 'Invalid flex shorthand.', path)
269
+
270
+ const factors = parts
271
+ .filter((_, index) => kinds[index] === 'factor')
272
+ .map((part) => convert('number', Number(part), options, path) as number)
273
+ const basis = parts.find((_, index) => kinds[index] === 'basis')
274
+
275
+ return {
276
+ flexBasis: basis === undefined ? 0 : convert('size', basis, options, path),
277
+ flexGrow: factors[0] ?? 1,
278
+ flexShrink: factors[1] ?? 1,
279
+ }
280
+ }
281
+
282
+ /** Native longhands produced by the `flex` shorthand. */
283
+ export declare namespace flex {
284
+ /** Grow and shrink factors with a converted basis. */
285
+ type ReturnType = {
286
+ readonly flexBasis: number | string
287
+ readonly flexGrow: number
288
+ readonly flexShrink: number
289
+ }
290
+ }
291
+
221
292
  /** Converts a portable length to logical units. */
222
293
  export function length(
223
294
  value: number | string,
@@ -0,0 +1,77 @@
1
+ /** Resolves compiled style applications for consumers outside native view bindings. @module */
2
+ import * as NativeContext from '../../runtime/NativeContext.js'
3
+ import * as React from 'react'
4
+ import type { useStyles } from '../react.js'
5
+ import type * as Store from './Store.js'
6
+
7
+ /** Subscribes to a selection and returns its style resolvers. */
8
+ export function use(
9
+ store: ReturnType<typeof Store.create>,
10
+ ): useStyles.ReturnType {
11
+ const value = React.useSyncExternalStore(
12
+ store.subscribe,
13
+ store.getSnapshot,
14
+ store.getSnapshot,
15
+ )
16
+ return React.useMemo(
17
+ () => ({
18
+ props: (props) =>
19
+ props && Object.hasOwn(props, 'style')
20
+ ? { ...props, style: NativeContext.resolve(props.style, value) }
21
+ : props,
22
+ style: <style>(style: style, input?: unknown) =>
23
+ NativeContext.resolve(style, value, input) as useStyles.Style<style>,
24
+ }),
25
+ [value],
26
+ )
27
+ }
28
+
29
+ /**
30
+ * Wraps a component so its style props resolve through an entrypoint's selection hook.
31
+ * @throws For invalid style prop names.
32
+ */
33
+ export function wrap<const component extends React.ElementType>(
34
+ Component: component,
35
+ options: wrap.Options,
36
+ useStyles: () => useStyles.ReturnType,
37
+ ) {
38
+ const additional = options.styleProps ?? []
39
+ if (
40
+ !Array.isArray(additional) ||
41
+ additional.some(
42
+ (name) =>
43
+ typeof name !== 'string' || !name || name === 'key' || name === 'ref',
44
+ )
45
+ )
46
+ throw new Error(
47
+ 'withStyles requires style prop names excluding key and ref.',
48
+ )
49
+
50
+ const names = [...new Set(['style', 'contentContainerStyle', ...additional])]
51
+ const Wrapped = React.forwardRef<
52
+ React.ComponentRef<component>,
53
+ React.ComponentPropsWithoutRef<component>
54
+ >((props, ref) => {
55
+ const selected = useStyles()
56
+ const resolved: Record<string, unknown> = { ...props, ref }
57
+ for (const name of names)
58
+ if (Object.hasOwn(props, name))
59
+ resolved[name] = selected.style(
60
+ (props as Record<string, unknown>)[name],
61
+ )
62
+
63
+ return React.createElement(Component, resolved)
64
+ })
65
+ Wrapped.displayName = `withStyles(${typeof Component === 'string' ? Component : Component.displayName || Component.name || 'Component'})`
66
+
67
+ return Wrapped
68
+ }
69
+
70
+ /** Style-bearing prop names accepted by a wrapper. */
71
+ export declare namespace wrap {
72
+ /** Entrypoints narrow these names to the wrapped component's props. */
73
+ type Options = {
74
+ /** Additional style props declared by the wrapped component. */
75
+ readonly styleProps?: readonly string[] | undefined
76
+ }
77
+ }
@@ -1,17 +1,13 @@
1
1
  /** React subscriptions for compiled native styles and variable values. @module */
2
2
  import * as Config from '../Config.js'
3
- import * as NativeContext from '../runtime/NativeContext.js'
3
+ import type * as NativeContext from '../runtime/NativeContext.js'
4
4
  import * as NativeVars from '../runtime/NativeVars.js'
5
5
  import * as React from 'react'
6
6
  import type * as Vars from '../Vars.js'
7
7
  import * as Store from './internal/Store.js'
8
+ import * as Styles from './internal/Styles.js'
8
9
  import * as Viewport from './internal/Viewport.js'
9
10
 
10
- const unbound = {
11
- getSnapshot: () => undefined,
12
- subscribe: () => () => {},
13
- }
14
-
15
11
  /** Creates native authoring helpers and a Provider typed to the configured variables. */
16
12
  export function defineConfig<const options extends Config.create.Options = {}>(
17
13
  options: options & Parameters<typeof Config.create<options>>[0] = {} as never,
@@ -113,15 +109,39 @@ export declare namespace Provider {
113
109
  }
114
110
  }
115
111
 
116
- /** Compiler-inserted subscription for resolving native style applications. */
117
- export function useStyles() {
118
- const store = React.useContext(Store.context) ?? unbound
119
- const value = React.useSyncExternalStore(
120
- store.subscribe,
121
- store.getSnapshot,
122
- store.getSnapshot,
112
+ /**
113
+ * Compiler-inserted subscription for resolving native style applications.
114
+ * Outside a Provider, selections use default variables and the light scheme.
115
+ */
116
+ export function useStyles(): useStyles.ReturnType {
117
+ const [fallback] = React.useState(() =>
118
+ Store.create({ colorScheme: 'light' }),
123
119
  )
124
- return React.useMemo(() => styles(value), [value])
120
+ return Styles.use(React.useContext(Store.context) ?? fallback)
121
+ }
122
+
123
+ /** Resolver types for Provider-selected native styles. */
124
+ export declare namespace useStyles {
125
+ /** Resolvers bound to the nearest Provider selection. */
126
+ type ReturnType = {
127
+ /** Resolves the `style` prop of applied props and preserves other props. */
128
+ readonly props: <
129
+ props extends Readonly<Record<string, unknown>> | null | undefined,
130
+ >(
131
+ props: props,
132
+ ) => props
133
+ /** Resolves an applied style, native style, style array, or style callback. */
134
+ readonly style: <style>(style: style, input?: unknown) => Style<style>
135
+ }
136
+ /**
137
+ * Resolved style type. Caller-owned values keep their types. Native applications
138
+ * always carry `style`, so an applied style drops the `undefined` allowed by the shared props type.
139
+ */
140
+ type Style<style> = [
141
+ style extends object ? (string extends keyof style ? style : never) : never,
142
+ ] extends [never]
143
+ ? style
144
+ : Exclude<style, undefined>
125
145
  }
126
146
 
127
147
  /** Compiler fallback for native view bindings when no native adapter is installed. */
@@ -171,46 +191,37 @@ export function useVars(
171
191
  }
172
192
 
173
193
  /**
174
- * Resolves compiled style props for components using React updates.
175
- * @param Component - Function, class, or ref-forwarding component receiving native styles.
194
+ * Resolves compiled style props for function components using React updates.
195
+ * @param Component - Function, memo, or ref-forwarding component receiving native styles.
196
+ * @param options - Additional style-bearing prop names.
197
+ * @returns A component with the original call signature and ref. Type parameters remain when `styleProps` is omitted.
198
+ * @throws For invalid style prop names.
199
+ */
200
+ export function withStyles<
201
+ props extends object,
202
+ result extends React.ReactNode,
203
+ >(
204
+ Component: (props: props) => result,
205
+ options?: NoInfer<withStyles.Options<(props: props) => result>>,
206
+ ): (props: props) => result
207
+ /**
208
+ * Resolves compiled style props for class and host components using React updates.
209
+ * @param Component - Class component or host element type receiving native styles.
176
210
  * @param options - Additional style-bearing prop names.
177
- * @returns A component preserving the original props and ref.
178
- * @throws For invalid style prop names or unresolved native selections.
211
+ * @returns A component preserving the original props and instance ref.
212
+ * @throws For invalid style prop names.
179
213
  */
180
214
  export function withStyles<const component extends React.ElementType>(
181
215
  Component: component,
182
- options: withStyles.Options<component> = {},
183
- ) {
184
- const additional = options.styleProps ?? []
185
- if (
186
- !Array.isArray(additional) ||
187
- additional.some(
188
- (name) =>
189
- typeof name !== 'string' || !name || name === 'key' || name === 'ref',
190
- )
191
- )
192
- throw new Error(
193
- 'withStyles requires style prop names excluding key and ref.',
194
- )
195
-
196
- const names = [...new Set(['style', 'contentContainerStyle', ...additional])]
197
- const Wrapped = React.forwardRef<
198
- React.ComponentRef<component>,
199
- React.ComponentPropsWithoutRef<component>
200
- >((props, ref) => {
201
- const selected = useStyles()
202
- const resolved: Record<string, unknown> = { ...props, ref }
203
- for (const name of names)
204
- if (Object.hasOwn(props, name))
205
- resolved[name] = selected.style(
206
- (props as Record<string, unknown>)[name],
207
- )
208
-
209
- return React.createElement(Component, resolved)
210
- })
211
- Wrapped.displayName = `withStyles(${typeof Component === 'string' ? Component : Component.displayName || Component.name || 'Component'})`
212
-
213
- return Wrapped
216
+ options?: withStyles.Options<component>,
217
+ ): React.ForwardRefExoticComponent<React.ComponentPropsWithRef<component>>
218
+ export function withStyles(
219
+ Component: React.ElementType,
220
+ options: withStyles.Options<
221
+ (props: Readonly<Record<string, unknown>>) => React.ReactNode
222
+ > = {},
223
+ ): React.ElementType {
224
+ return Styles.wrap(Component, options, useStyles)
214
225
  }
215
226
 
216
227
  /** Style-bearing component properties resolved by the wrapper. */
@@ -226,14 +237,3 @@ export declare namespace withStyles {
226
237
  | undefined
227
238
  }
228
239
  }
229
-
230
- function styles(value: NativeContext.Context | undefined) {
231
- return {
232
- props: (props: Record<string, unknown> | null | undefined) =>
233
- props && Object.hasOwn(props, 'style')
234
- ? { ...props, style: NativeContext.resolve(props.style, value) }
235
- : props,
236
- style: (style: unknown, input?: unknown) =>
237
- NativeContext.resolve(style, value, input),
238
- }
239
- }
@@ -205,6 +205,13 @@ export function create(
205
205
  [`${property}Bottom`]: bottom,
206
206
  [`${property}Left`]: left,
207
207
  })
208
+ } else if (property === 'inset') {
209
+ const parts =
210
+ typeof value === 'string' ? Calculation.parts(value) : [value]
211
+ const [top, right = top, bottom = top, left = right] = parts.map(
212
+ (part) => Scalar.convert('offset', part, options, [property]),
213
+ )
214
+ Object.assign(output, { top, right, bottom, left })
208
215
  } else if (property === 'borderColor' || property === 'borderWidth') {
209
216
  for (const side of ['Top', 'Right', 'Bottom', 'Left'])
210
217
  output[