zyzz 0.0.22 → 0.0.24
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/README.md +6 -6
- package/dist/babel/NativeJsx.d.ts.map +1 -1
- package/dist/babel/NativeJsx.js +14 -4
- package/dist/babel/NativeJsx.js.map +1 -1
- package/dist/compiler/Graph.js +78 -35
- package/dist/compiler/Graph.js.map +1 -1
- package/dist/compiler/internal/Dynamic.d.ts.map +1 -1
- package/dist/compiler/internal/Dynamic.js +3 -0
- package/dist/compiler/internal/Dynamic.js.map +1 -1
- package/dist/compiler/internal/Expression.js +2 -2
- package/dist/compiler/internal/Expression.js.map +1 -1
- package/dist/compiler/internal/NativeBindings.d.ts.map +1 -1
- package/dist/compiler/internal/NativeBindings.js +1 -0
- package/dist/compiler/internal/NativeBindings.js.map +1 -1
- package/dist/compiler/internal/NativeVars.d.ts.map +1 -1
- package/dist/compiler/internal/NativeVars.js +3 -0
- package/dist/compiler/internal/NativeVars.js.map +1 -1
- package/dist/compiler/internal/Themes.d.ts +2 -0
- package/dist/compiler/internal/Themes.d.ts.map +1 -1
- package/dist/compiler/internal/Themes.js +24 -22
- package/dist/compiler/internal/Themes.js.map +1 -1
- package/dist/internal/Configuration.d.ts.map +1 -1
- package/dist/internal/Configuration.js +2 -1
- package/dist/internal/Configuration.js.map +1 -1
- package/dist/metro/Graph.d.ts.map +1 -1
- package/dist/metro/Graph.js +11 -2
- package/dist/metro/Graph.js.map +1 -1
- package/dist/node/Host.d.ts.map +1 -1
- package/dist/node/Host.js +40 -12
- package/dist/node/Host.js.map +1 -1
- package/dist/react-native/StyleSheet.d.ts +7 -3
- package/dist/react-native/StyleSheet.d.ts.map +1 -1
- package/dist/react-native/StyleSheet.js +8 -0
- package/dist/react-native/StyleSheet.js.map +1 -1
- package/dist/react-native/internal/Device.js +118 -38
- package/dist/react-native/internal/Device.js.map +3 -3
- package/dist/react-native/internal/NativeZyzz.js.map +2 -2
- package/dist/react-native/internal/Scalar.d.ts +16 -0
- package/dist/react-native/internal/Scalar.d.ts.map +1 -1
- package/dist/react-native/internal/Scalar.js +53 -2
- package/dist/react-native/internal/Scalar.js.map +1 -1
- package/dist/react-native/internal/Store.d.ts +4 -0
- package/dist/react-native/internal/Store.d.ts.map +1 -1
- package/dist/react-native/internal/Store.js.map +1 -1
- package/dist/react-native/internal/Styles.d.ts +19 -0
- package/dist/react-native/internal/Styles.d.ts.map +1 -0
- package/dist/react-native/internal/Styles.js +35 -0
- package/dist/react-native/internal/Styles.js.map +1 -0
- package/dist/react-native/internal/Viewport.d.ts +6 -0
- package/dist/react-native/internal/Viewport.d.ts.map +1 -1
- package/dist/react-native/internal/Viewport.js +2 -0
- package/dist/react-native/internal/Viewport.js.map +1 -1
- package/dist/react-native/react.d.ts +44 -16
- package/dist/react-native/react.d.ts.map +1 -1
- package/dist/react-native/react.js +68 -47
- package/dist/react-native/react.js.map +1 -1
- package/dist/runtime/NativeContext.d.ts +6 -0
- package/dist/runtime/NativeContext.d.ts.map +1 -1
- package/dist/runtime/NativeContext.js +62 -9
- package/dist/runtime/NativeContext.js.map +1 -1
- package/dist/runtime/NativeDynamic.d.ts.map +1 -1
- package/dist/runtime/NativeDynamic.js +5 -0
- package/dist/runtime/NativeDynamic.js.map +1 -1
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +4 -1
- package/dist/unplugin/index.js.map +1 -1
- package/dist/variants.d.ts +3 -2
- package/dist/variants.d.ts.map +1 -1
- package/dist/variants.js.map +1 -1
- package/native/NativeZyzz.cpp +52 -65
- package/native/NativeZyzz.h +0 -4
- package/native/ZyzzSpecJSI.h +0 -10
- package/native/android/ZyzzSpec-generated.cpp +0 -6
- package/package.json +1 -1
- package/src/babel/NativeJsx.ts +17 -8
- package/src/compiler/Graph.ts +89 -38
- package/src/compiler/internal/Dynamic.ts +4 -0
- package/src/compiler/internal/Expression.ts +2 -2
- package/src/compiler/internal/NativeBindings.ts +1 -0
- package/src/compiler/internal/NativeVars.ts +3 -0
- package/src/compiler/internal/Themes.ts +29 -23
- package/src/internal/Configuration.ts +2 -1
- package/src/metro/Graph.ts +17 -2
- package/src/node/Host.ts +64 -17
- package/src/react-native/StyleSheet.ts +32 -9
- package/src/react-native/internal/Device.ts +196 -49
- package/src/react-native/internal/NativeZyzz.ts +4 -6
- package/src/react-native/internal/Scalar.ts +73 -2
- package/src/react-native/internal/Store.ts +4 -0
- package/src/react-native/internal/Styles.ts +77 -0
- package/src/react-native/internal/Viewport.ts +7 -0
- package/src/react-native/react.ts +142 -72
- package/src/runtime/NativeContext.ts +83 -9
- package/src/runtime/NativeDynamic.ts +7 -0
- package/src/unplugin/index.ts +4 -1
- package/src/variants.ts +3 -2
|
@@ -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
|
|
|
@@ -13,34 +14,128 @@ export function defineConfig<const options extends Config.create.Options = {}>(
|
|
|
13
14
|
options: options & Parameters<typeof Config.create<options>>[0] = {} as never,
|
|
14
15
|
): Subscription.defineConfig.ReturnType<options> {
|
|
15
16
|
const config = Subscription.defineConfig<options>(options)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
// Copy descriptors rather than spreading, so deferred helpers stay deferred.
|
|
18
|
+
return Object.freeze(
|
|
19
|
+
Object.defineProperties(
|
|
20
|
+
{ Provider: dimensions(config.Provider) } as Record<string, unknown>,
|
|
21
|
+
Object.fromEntries(
|
|
22
|
+
Object.entries(Object.getOwnPropertyDescriptors(config)).filter(
|
|
23
|
+
([name]) => name !== 'Provider',
|
|
24
|
+
),
|
|
25
|
+
),
|
|
26
|
+
),
|
|
27
|
+
) as unknown as Subscription.defineConfig.ReturnType<options>
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
/** Provides selected variables, appearance, and automatic native dimensions. */
|
|
23
31
|
export const Provider = dimensions(Subscription.Provider)
|
|
24
32
|
|
|
25
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Compiler-inserted subscription for resolving native style applications.
|
|
35
|
+
* Outside a Provider, selections use default variables, the light scheme, and window dimensions.
|
|
36
|
+
*/
|
|
37
|
+
export function useStyles(): Subscription.useStyles.ReturnType {
|
|
38
|
+
const owner = React.useContext(Store.context)
|
|
39
|
+
const [fallback] = React.useState(() =>
|
|
40
|
+
Store.create({
|
|
41
|
+
colorScheme: 'light',
|
|
42
|
+
viewport: ReactNative.Dimensions.get('window'),
|
|
43
|
+
}),
|
|
44
|
+
)
|
|
45
|
+
React.useLayoutEffect(() => {
|
|
46
|
+
if (owner) return
|
|
47
|
+
const update = () =>
|
|
48
|
+
fallback.update({
|
|
49
|
+
colorScheme: 'light',
|
|
50
|
+
viewport: ReactNative.Dimensions.get('window'),
|
|
51
|
+
})
|
|
52
|
+
// Dimensions can change between render and commit.
|
|
53
|
+
update()
|
|
54
|
+
const subscription = ReactNative.Dimensions.addEventListener(
|
|
55
|
+
'change',
|
|
56
|
+
update,
|
|
57
|
+
)
|
|
58
|
+
return () => subscription.remove()
|
|
59
|
+
}, [fallback, owner])
|
|
60
|
+
|
|
61
|
+
return Styles.use(owner ?? fallback)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { useVars } from '../react.js'
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolves compiled style props for function components using React updates.
|
|
68
|
+
* @param Component - Function, memo, or ref-forwarding component receiving native styles.
|
|
69
|
+
* @param options - Additional style-bearing prop names.
|
|
70
|
+
* @returns A component with the original call signature and ref. Type parameters remain when `styleProps` is omitted.
|
|
71
|
+
* @throws For invalid style prop names.
|
|
72
|
+
*/
|
|
73
|
+
export function withStyles<
|
|
74
|
+
props extends object,
|
|
75
|
+
result extends React.ReactNode,
|
|
76
|
+
>(
|
|
77
|
+
Component: (props: props) => result,
|
|
78
|
+
options?: NoInfer<Subscription.withStyles.Options<(props: props) => result>>,
|
|
79
|
+
): (props: props) => result
|
|
80
|
+
/**
|
|
81
|
+
* Resolves compiled style props for class and host components using React updates.
|
|
82
|
+
* @param Component - Class component or host element type receiving native styles.
|
|
83
|
+
* @param options - Additional style-bearing prop names.
|
|
84
|
+
* @returns A component preserving the original props and instance ref.
|
|
85
|
+
* @throws For invalid style prop names.
|
|
86
|
+
*/
|
|
87
|
+
export function withStyles<const component extends React.ElementType>(
|
|
88
|
+
Component: component,
|
|
89
|
+
options?: Subscription.withStyles.Options<component>,
|
|
90
|
+
): React.ForwardRefExoticComponent<React.ComponentPropsWithRef<component>>
|
|
91
|
+
export function withStyles(
|
|
92
|
+
Component: React.ElementType,
|
|
93
|
+
options: Subscription.withStyles.Options<
|
|
94
|
+
(props: Readonly<Record<string, unknown>>) => React.ReactNode
|
|
95
|
+
> = {},
|
|
96
|
+
): React.ElementType {
|
|
97
|
+
return Styles.wrap(Component, options, useStyles)
|
|
98
|
+
}
|
|
26
99
|
|
|
27
100
|
function dimensions<props extends Subscription.Provider.Props>(
|
|
28
101
|
Component: React.FunctionComponent<props>,
|
|
29
102
|
): React.FunctionComponent<props> {
|
|
30
103
|
return function WindowProvider(props: props) {
|
|
31
104
|
const window = ReactNative.useWindowDimensions()
|
|
105
|
+
const scheme = ReactNative.useColorScheme()
|
|
32
106
|
const viewport = React.useMemo(
|
|
33
107
|
() => ({ height: window.height, width: window.width }),
|
|
34
108
|
[window.height, window.width],
|
|
35
109
|
)
|
|
110
|
+
const appearance = React.useMemo(
|
|
111
|
+
() => ({
|
|
112
|
+
adaptive,
|
|
113
|
+
colorScheme: scheme === 'dark' ? ('dark' as const) : ('light' as const),
|
|
114
|
+
}),
|
|
115
|
+
[scheme],
|
|
116
|
+
)
|
|
36
117
|
return React.createElement(
|
|
37
118
|
Viewport.context.Provider,
|
|
38
119
|
{ value: viewport },
|
|
39
|
-
React.createElement(
|
|
120
|
+
React.createElement(
|
|
121
|
+
Viewport.appearance.Provider,
|
|
122
|
+
{ value: appearance },
|
|
123
|
+
React.createElement(Component, props),
|
|
124
|
+
),
|
|
40
125
|
)
|
|
41
126
|
}
|
|
42
127
|
}
|
|
43
128
|
|
|
129
|
+
// iOS resolves these colors from the window's appearance, so scheme changes need no Fabric update.
|
|
130
|
+
const adaptive =
|
|
131
|
+
ReactNative.Platform.OS === 'ios'
|
|
132
|
+
? (light: string | number, dark: string | number) =>
|
|
133
|
+
ReactNative.DynamicColorIOS({
|
|
134
|
+
dark: dark as ReactNative.ColorValue,
|
|
135
|
+
light: light as ReactNative.ColorValue,
|
|
136
|
+
})
|
|
137
|
+
: undefined
|
|
138
|
+
|
|
44
139
|
/** Compiler-only binding path, with React subscriptions for Expo Go. */
|
|
45
140
|
export const useNativeStyles = useBindings
|
|
46
141
|
|
|
@@ -55,55 +150,68 @@ type Instance = {
|
|
|
55
150
|
|
|
56
151
|
function useBindings() {
|
|
57
152
|
const owner = React.useContext(Store.context)
|
|
58
|
-
|
|
59
|
-
|
|
153
|
+
// Views inside a Provider never read the fallback, so it is created only outside one.
|
|
154
|
+
const fallbackRef = React.useRef<ReturnType<typeof Store.create> | undefined>(
|
|
155
|
+
undefined,
|
|
156
|
+
)
|
|
157
|
+
if (!owner)
|
|
158
|
+
fallbackRef.current ??= Store.create({
|
|
60
159
|
colorScheme: 'light',
|
|
61
160
|
viewport: ReactNative.Dimensions.get('window'),
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const store = owner ?? fallback
|
|
161
|
+
})
|
|
162
|
+
const fallback = fallbackRef.current
|
|
163
|
+
const store = (owner ?? fallback)!
|
|
65
164
|
const [, render] = React.useReducer((count: number) => count + 1, 0)
|
|
66
|
-
const [refs] = React.useState(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
165
|
+
const [refs] = React.useState(() => {
|
|
166
|
+
const entries = new Map<
|
|
167
|
+
Instance,
|
|
168
|
+
{
|
|
169
|
+
active: boolean
|
|
170
|
+
ref: React.Ref<Instance> | undefined
|
|
171
|
+
release: () => void
|
|
172
|
+
}
|
|
173
|
+
>()
|
|
174
|
+
let scheduled = false
|
|
175
|
+
return {
|
|
176
|
+
entries,
|
|
177
|
+
// Runs after the commit, once every cleanup and re-attachment has settled, without a layout effect per render.
|
|
178
|
+
sweep() {
|
|
179
|
+
if (scheduled) return
|
|
180
|
+
scheduled = true
|
|
181
|
+
queueMicrotask(() => {
|
|
182
|
+
scheduled = false
|
|
183
|
+
for (const [instance, entry] of entries) {
|
|
184
|
+
if (entry.active) continue
|
|
185
|
+
entry.release()
|
|
186
|
+
entries.delete(instance)
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
},
|
|
82
190
|
}
|
|
83
191
|
})
|
|
84
|
-
React.useLayoutEffect(
|
|
85
|
-
() => () => {
|
|
86
|
-
for (const entry of refs.values()) entry.release()
|
|
87
|
-
refs.clear()
|
|
88
|
-
},
|
|
89
|
-
[refs],
|
|
90
|
-
)
|
|
91
192
|
return {
|
|
92
193
|
style: NativeContext.application,
|
|
93
194
|
view: (props: Record<string, unknown>) => {
|
|
94
195
|
const snapshot = store.getSnapshot()
|
|
95
196
|
const selected = NativeContext.resolve(props.style, snapshot)
|
|
96
197
|
const initial = NativeContext.key(props.style, snapshot)
|
|
198
|
+
// A context-free style never needs a binding, so React keeps a stable element without a ref wrapper.
|
|
199
|
+
if (
|
|
200
|
+
!initial.length &&
|
|
201
|
+
typeof selected !== 'function' &&
|
|
202
|
+
props.ref == null
|
|
203
|
+
)
|
|
204
|
+
return { ...props, style: selected }
|
|
97
205
|
|
|
98
206
|
return {
|
|
99
207
|
...props,
|
|
100
208
|
ref: (value: Instance | null) => {
|
|
101
209
|
const ref = props.ref as React.Ref<Instance> | undefined
|
|
102
210
|
if (!value) return
|
|
103
|
-
let entry = refs.get(value)
|
|
211
|
+
let entry = refs.entries.get(value)
|
|
104
212
|
if (entry && entry.ref !== ref) {
|
|
105
213
|
entry.release()
|
|
106
|
-
refs.delete(value)
|
|
214
|
+
refs.entries.delete(value)
|
|
107
215
|
entry = undefined
|
|
108
216
|
}
|
|
109
217
|
if (!entry) {
|
|
@@ -118,7 +226,7 @@ function useBindings() {
|
|
|
118
226
|
else if (ref) ref.current = null
|
|
119
227
|
},
|
|
120
228
|
}
|
|
121
|
-
refs.set(value, entry)
|
|
229
|
+
refs.entries.set(value, entry)
|
|
122
230
|
}
|
|
123
231
|
entry.active = true
|
|
124
232
|
const attached = entry
|
|
@@ -133,7 +241,9 @@ function useBindings() {
|
|
|
133
241
|
if (store.getSnapshot() !== snapshot) render()
|
|
134
242
|
return unsubscribe
|
|
135
243
|
}
|
|
136
|
-
|
|
244
|
+
// Most views never change selection, so they bind on their first patch.
|
|
245
|
+
let id: number | undefined
|
|
246
|
+
let bound = false
|
|
137
247
|
let previous = initial
|
|
138
248
|
const read = (context: NativeContext.Context) => {
|
|
139
249
|
const keys = NativeContext.key(props.style, context)
|
|
@@ -142,30 +252,38 @@ function useBindings() {
|
|
|
142
252
|
keys.every((key, index) => Object.is(key, previous[index]))
|
|
143
253
|
)
|
|
144
254
|
return { commit: () => {} }
|
|
145
|
-
const
|
|
146
|
-
if (!selective(
|
|
255
|
+
const next = NativeContext.resolve(props.style, context)
|
|
256
|
+
if (!selective(next)) return { commit: render }
|
|
257
|
+
id ??= ++bindings
|
|
147
258
|
return {
|
|
148
259
|
commit: () => {
|
|
149
260
|
previous = keys
|
|
261
|
+
bound = true
|
|
150
262
|
},
|
|
151
|
-
patch:
|
|
152
|
-
id,
|
|
153
|
-
|
|
154
|
-
|
|
263
|
+
patch: bound
|
|
264
|
+
? { id, props: nativeProps(next) }
|
|
265
|
+
: {
|
|
266
|
+
id,
|
|
267
|
+
node,
|
|
268
|
+
props: nativeProps(next),
|
|
269
|
+
rendered: nativeProps(selected),
|
|
270
|
+
},
|
|
155
271
|
}
|
|
156
272
|
}
|
|
157
273
|
// Context can change between render and committing the host ref.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
274
|
+
if (store.getSnapshot() !== snapshot) {
|
|
275
|
+
const current = read(store.getSnapshot())
|
|
276
|
+
if (current.patch) write([current.patch])
|
|
277
|
+
current.commit()
|
|
278
|
+
}
|
|
161
279
|
const unbind = store.bind(read, write)
|
|
162
280
|
return () => {
|
|
163
281
|
unbind()
|
|
164
|
-
NativeZyzz!.detach(id)
|
|
282
|
+
if (bound) NativeZyzz!.detach(id!)
|
|
165
283
|
}
|
|
166
284
|
})()
|
|
167
285
|
const update = () =>
|
|
168
|
-
fallback
|
|
286
|
+
fallback!.update({
|
|
169
287
|
colorScheme: 'light',
|
|
170
288
|
viewport: ReactNative.Dimensions.get('window'),
|
|
171
289
|
})
|
|
@@ -180,6 +298,7 @@ function useBindings() {
|
|
|
180
298
|
detach()
|
|
181
299
|
subscription?.remove()
|
|
182
300
|
attached.active = false
|
|
301
|
+
refs.sweep()
|
|
183
302
|
}
|
|
184
303
|
},
|
|
185
304
|
style: selected,
|
|
@@ -190,6 +309,21 @@ function useBindings() {
|
|
|
190
309
|
|
|
191
310
|
function selective(style: unknown): boolean {
|
|
192
311
|
if (typeof style === 'function') return false
|
|
312
|
+
const shared =
|
|
313
|
+
typeof style === 'object' &&
|
|
314
|
+
style !== null &&
|
|
315
|
+
!Array.isArray(style) &&
|
|
316
|
+
Object.isFrozen(style)
|
|
317
|
+
const cached = shared ? patchable.get(style) : undefined
|
|
318
|
+
if (cached !== undefined) return cached
|
|
319
|
+
const result = writable(style)
|
|
320
|
+
if (shared) patchable.set(style, result)
|
|
321
|
+
return result
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const patchable = new WeakMap<object, boolean>()
|
|
325
|
+
|
|
326
|
+
function writable(style: unknown): boolean {
|
|
193
327
|
const value = ReactNative.StyleSheet.flatten(
|
|
194
328
|
style as ReactNative.StyleProp<ReactNative.ViewStyle>,
|
|
195
329
|
)
|
|
@@ -213,7 +347,16 @@ function selective(style: unknown): boolean {
|
|
|
213
347
|
)
|
|
214
348
|
}
|
|
215
349
|
|
|
350
|
+
// Compiled styles are frozen and shared by every view that selects them.
|
|
351
|
+
const converted = new WeakMap<object, Record<string, unknown>>()
|
|
216
352
|
function nativeProps(style: unknown): Record<string, unknown> {
|
|
353
|
+
const shared =
|
|
354
|
+
typeof style === 'object' &&
|
|
355
|
+
style !== null &&
|
|
356
|
+
!Array.isArray(style) &&
|
|
357
|
+
Object.isFrozen(style)
|
|
358
|
+
const cached = shared ? converted.get(style) : undefined
|
|
359
|
+
if (cached) return cached
|
|
217
360
|
const flattened =
|
|
218
361
|
ReactNative.StyleSheet.flatten(
|
|
219
362
|
style as ReactNative.StyleProp<ReactNative.ViewStyle>,
|
|
@@ -223,9 +366,13 @@ function nativeProps(style: unknown): Record<string, unknown> {
|
|
|
223
366
|
if (name === 'color' || name.endsWith('Color'))
|
|
224
367
|
result[name] = ReactNative.processColor(value as ReactNative.ColorValue)
|
|
225
368
|
}
|
|
369
|
+
if (shared) converted.set(style, result)
|
|
226
370
|
return result
|
|
227
371
|
}
|
|
228
372
|
|
|
373
|
+
// Binding identifiers are unique per JavaScript runtime, like the native module.
|
|
374
|
+
let bindings = 0
|
|
375
|
+
|
|
229
376
|
function write(patches: readonly Store.Patch[]) {
|
|
230
377
|
NativeZyzz!.update(patches)
|
|
231
378
|
}
|
|
@@ -4,16 +4,14 @@ import { TurboModuleRegistry } from 'react-native'
|
|
|
4
4
|
|
|
5
5
|
/** Codegen contract, consumed only by the native adapter. */
|
|
6
6
|
export interface Spec extends TurboModule {
|
|
7
|
-
/** Links a mounted Fabric node and its rendered style. */
|
|
8
|
-
readonly attach: (
|
|
9
|
-
node: CodegenTypes.UnsafeObject,
|
|
10
|
-
props: CodegenTypes.UnsafeObject,
|
|
11
|
-
) => number
|
|
12
7
|
/** Releases a view binding. */
|
|
13
8
|
readonly detach: (id: number) => void
|
|
14
9
|
/** Reports live bindings and applied update batches. */
|
|
15
10
|
readonly inspect: () => CodegenTypes.UnsafeObject
|
|
16
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Applies one batch of changed native styles. An update with `node` and
|
|
13
|
+
* `rendered` first binds that mounted Fabric node to its rendered style.
|
|
14
|
+
*/
|
|
17
15
|
readonly update: (updates: ReadonlyArray<CodegenTypes.UnsafeObject>) => void
|
|
18
16
|
}
|
|
19
17
|
|
|
@@ -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 === '
|
|
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
|
-
|
|
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,
|
|
@@ -6,8 +6,12 @@ import * as React from 'react'
|
|
|
6
6
|
export type Patch = {
|
|
7
7
|
/** Runtime-local mounted view identifier. */
|
|
8
8
|
readonly id: number
|
|
9
|
+
/** Mounted Fabric node, sent until the binding's first patch commits. */
|
|
10
|
+
readonly node?: object | undefined
|
|
9
11
|
/** Processed native style properties. */
|
|
10
12
|
readonly props: Record<string, unknown>
|
|
13
|
+
/** Processed style React rendered, sent with `node`. */
|
|
14
|
+
readonly rendered?: Record<string, unknown> | undefined
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
type Selection = {
|
|
@@ -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,4 +1,5 @@
|
|
|
1
1
|
/** Connects the native device adapter to framework subscriptions. @module */
|
|
2
|
+
import type * as NativeContext from '../../runtime/NativeContext.js'
|
|
2
3
|
import type * as Media from '../../runtime/internal/NativeMedia.js'
|
|
3
4
|
import * as React from 'react'
|
|
4
5
|
|
|
@@ -6,3 +7,9 @@ import * as React from 'react'
|
|
|
6
7
|
export const context = React.createContext<Media.Viewport | undefined>(
|
|
7
8
|
undefined,
|
|
8
9
|
)
|
|
10
|
+
|
|
11
|
+
/** Device appearance for `colorScheme: 'system'`, with the platform's appearance-following color when it has one. */
|
|
12
|
+
export const appearance = React.createContext<{
|
|
13
|
+
readonly adaptive?: NativeContext.Context['adaptive']
|
|
14
|
+
readonly colorScheme: 'dark' | 'light'
|
|
15
|
+
}>({ colorScheme: 'light' })
|