@swiftbrowser/react 0.0.0-stage → 0.4.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/README.md +435 -2
- package/dist/binding.d.ts +9 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/bridge.d.ts +17 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/components/animators.d.ts +40 -0
- package/dist/components/animators.d.ts.map +1 -0
- package/dist/components/canvas.d.ts +138 -0
- package/dist/components/canvas.d.ts.map +1 -0
- package/dist/components/charts.d.ts +100 -0
- package/dist/components/charts.d.ts.map +1 -0
- package/dist/components/containers.d.ts +78 -0
- package/dist/components/containers.d.ts.map +1 -0
- package/dist/components/controls.d.ts +106 -0
- package/dist/components/controls.d.ts.map +1 -0
- package/dist/components/geometry.d.ts +131 -0
- package/dist/components/geometry.d.ts.map +1 -0
- package/dist/components/host.d.ts +24 -0
- package/dist/components/host.d.ts.map +1 -0
- package/dist/components/images.d.ts +18 -0
- package/dist/components/images.d.ts.map +1 -0
- package/dist/components/lists.d.ts +26 -0
- package/dist/components/lists.d.ts.map +1 -0
- package/dist/components/media.d.ts +105 -0
- package/dist/components/media.d.ts.map +1 -0
- package/dist/components/menus.d.ts +20 -0
- package/dist/components/menus.d.ts.map +1 -0
- package/dist/components/navigation.d.ts +22 -0
- package/dist/components/navigation.d.ts.map +1 -0
- package/dist/components/row.d.ts +34 -0
- package/dist/components/row.d.ts.map +1 -0
- package/dist/components/shapes.d.ts +107 -0
- package/dist/components/shapes.d.ts.map +1 -0
- package/dist/components/split.d.ts +25 -0
- package/dist/components/split.d.ts.map +1 -0
- package/dist/components/stacks.d.ts +138 -0
- package/dist/components/stacks.d.ts.map +1 -0
- package/dist/components/system.d.ts +25 -0
- package/dist/components/system.d.ts.map +1 -0
- package/dist/components/table.d.ts +36 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/tabs.d.ts +20 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/text.d.ts +63 -0
- package/dist/components/text.d.ts.map +1 -0
- package/dist/coordinateSpace.d.ts +20 -0
- package/dist/coordinateSpace.d.ts.map +1 -0
- package/dist/dragDrop.d.ts +16 -0
- package/dist/dragDrop.d.ts.map +1 -0
- package/dist/environment.d.ts +54 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/host/instance.d.ts +67 -0
- package/dist/host/instance.d.ts.map +1 -0
- package/dist/host/reconciler.d.ts +9 -0
- package/dist/host/reconciler.d.ts.map +1 -0
- package/dist/host/root.d.ts +29 -0
- package/dist/host/root.d.ts.map +1 -0
- package/dist/host/sb.d.ts +5 -0
- package/dist/host/sb.d.ts.map +1 -0
- package/dist/host/tree.d.ts +35 -0
- package/dist/host/tree.d.ts.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4038 -0
- package/dist/index.js.map +1 -0
- package/dist/inspector.d.ts +11 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/island.d.ts +25 -0
- package/dist/island.d.ts.map +1 -0
- package/dist/lazy.d.ts +19 -0
- package/dist/lazy.d.ts.map +1 -0
- package/dist/lifecycle.d.ts +11 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/modifiers.d.ts +330 -0
- package/dist/modifiers.d.ts.map +1 -0
- package/dist/mount.d.ts +41 -0
- package/dist/mount.d.ts.map +1 -0
- package/dist/navigation-context.d.ts +15 -0
- package/dist/navigation-context.d.ts.map +1 -0
- package/dist/path.d.ts +48 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/pointer.d.ts +65 -0
- package/dist/pointer.d.ts.map +1 -0
- package/dist/presentation.d.ts +38 -0
- package/dist/presentation.d.ts.map +1 -0
- package/dist/scrollReader.d.ts +27 -0
- package/dist/scrollReader.d.ts.map +1 -0
- package/dist/values.d.ts +118 -0
- package/dist/values.d.ts.map +1 -0
- package/package.json +47 -4
package/README.md
CHANGED
|
@@ -1,3 +1,436 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @swiftbrowser/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
SwiftUI's components for React. A pure React/TypeScript web app writes its
|
|
4
|
+
screens with `List`, `NavigationStack`, `Toggle`, `Picker` and the rest, and
|
|
5
|
+
gets the same iOS UI a SwiftUI app gets in SwiftBrowser: the components emit
|
|
6
|
+
the [render ops](../../docs/ops-protocol.md) the Swift shim emits, and the
|
|
7
|
+
[web renderer](../../Web/README.md) draws them with its iOS layout engine,
|
|
8
|
+
typography, animations, navigation bars, tab bars, sheets and alerts. No
|
|
9
|
+
Swift, no WebAssembly: the package is React components and a renderer host.
|
|
10
|
+
|
|
11
|
+
It works in both directions with ordinary React, and the two nest: a
|
|
12
|
+
SwiftUI tree can live inside a `react-dom` page, and ordinary React DOM can
|
|
13
|
+
live inside a SwiftUI element (see [Mixing with ordinary React](#mixing-with-ordinary-react)).
|
|
14
|
+
|
|
15
|
+
The package is experimental.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install @swiftbrowser/react @swiftbrowser/web react react-dom
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
It is built for a bundler (Vite, or any that compiles TypeScript in
|
|
22
|
+
dependencies): the renderer it drives comes from `@swiftbrowser/web`'s
|
|
23
|
+
TypeScript sources, as does the stylesheet the page imports.
|
|
24
|
+
|
|
25
|
+
## Getting started
|
|
26
|
+
|
|
27
|
+
### A whole page
|
|
28
|
+
|
|
29
|
+
The simplest app is a page whose root is a SwiftUI tree. The page provides
|
|
30
|
+
the renderer's stylesheet and a root element, and `mount` draws the tree in
|
|
31
|
+
it. In the device frame (the markup of `index.html` here, with the iPhone
|
|
32
|
+
bezel, status bar and the theme controls) the root is the `#screen` element;
|
|
33
|
+
on a phone the renderer can also draw full screen.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import '@swiftbrowser/web/src/styles.css';
|
|
37
|
+
import { useState } from 'react';
|
|
38
|
+
import { mount, Label, List, NavigationLink, NavigationStack, Section, Text, Toggle } from '@swiftbrowser/react';
|
|
39
|
+
|
|
40
|
+
function Library() {
|
|
41
|
+
const [downloads, setDownloads] = useState(true);
|
|
42
|
+
return (
|
|
43
|
+
<List navigationTitle="Library">
|
|
44
|
+
<Section header="Reading">
|
|
45
|
+
<NavigationLink destination={<Text>Detail</Text>}>
|
|
46
|
+
<Text>The Swift Programming Language</Text>
|
|
47
|
+
</NavigationLink>
|
|
48
|
+
</Section>
|
|
49
|
+
<Section header="Settings">
|
|
50
|
+
<Toggle isOn={[downloads, setDownloads]}>
|
|
51
|
+
<Label title="Automatic Downloads" systemImage="arrow.down.circle" />
|
|
52
|
+
</Toggle>
|
|
53
|
+
</Section>
|
|
54
|
+
</List>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
mount(document.getElementById('screen')!, <NavigationStack><Library /></NavigationStack>);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`mount` returns the root and the renderer; `setEnvironment({ colorScheme,
|
|
62
|
+
dynamicTypeSize })` on it switches dark mode and the Dynamic Type size, as
|
|
63
|
+
the demo page's controls do.
|
|
64
|
+
|
|
65
|
+
### Inside an existing React app
|
|
66
|
+
|
|
67
|
+
In a `react-dom` app, `SwiftUIView` is a normal component that draws its
|
|
68
|
+
children with the renderer in a box of the page's own layout, with the
|
|
69
|
+
device's design tokens and none of its frame:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import '@swiftbrowser/web/src/styles.css';
|
|
73
|
+
import { SwiftUIView, List, Section, Toggle } from '@swiftbrowser/react';
|
|
74
|
+
|
|
75
|
+
function Settings({ dark, setDark }) {
|
|
76
|
+
return (
|
|
77
|
+
<SwiftUIView fit="content" colorScheme={dark ? 'dark' : 'light'} className="settings">
|
|
78
|
+
<List>
|
|
79
|
+
<Section header="Appearance">
|
|
80
|
+
<Toggle title="Dark Mode" isOn={[dark, setDark]} />
|
|
81
|
+
</Section>
|
|
82
|
+
</List>
|
|
83
|
+
</SwiftUIView>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`fit="content"` makes the island as tall as its content, like a block in
|
|
89
|
+
the page's flow; `fit="fill"` (the default) draws in the box the page sizes
|
|
90
|
+
with CSS, which is what navigation stacks and tab views want, since they fill
|
|
91
|
+
their screen. `colorScheme` and `dynamicTypeSize` are props. The island is
|
|
92
|
+
a React root of its own: the page's React state reaches it through props as
|
|
93
|
+
usual, and its contexts through `contexts={[…]}` (see
|
|
94
|
+
[Contexts across roots](#contexts-across-roots)).
|
|
95
|
+
|
|
96
|
+
### Try the demos
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
npm install
|
|
100
|
+
cd packages/react
|
|
101
|
+
npm run dev # http://localhost:5174/ — Examples/Fidelity, in React
|
|
102
|
+
# http://localhost:5174/island.html — islands in a react-dom page
|
|
103
|
+
# http://localhost:5174/wide.html — a regular-width app: split view, table, inspector
|
|
104
|
+
npm run test:unit # the React Fidelity tabs emit the same tree as the Swift app
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`?screen=<name>` on the first page opens one of the Fidelity screens alone
|
|
108
|
+
(`list`, `detail`, `form`, `controls`, `typography`, `shapes`, `layout`,
|
|
109
|
+
`tabs`, `sheet`, `alert`, `search`, `post`, `settings`, `reorder`), like the
|
|
110
|
+
Swift app's `-screen` argument; `extras`, `more` and `web` show the
|
|
111
|
+
components beyond it.
|
|
112
|
+
|
|
113
|
+
## Writing views
|
|
114
|
+
|
|
115
|
+
### Components
|
|
116
|
+
|
|
117
|
+
The components are named as in SwiftUI and take the same arguments as
|
|
118
|
+
props: `<VStack alignment="leading" spacing={8}>`, `<Text font="headline">`,
|
|
119
|
+
`<Image systemName="heart.fill" />`, `<Button title="Save" action={save} />`,
|
|
120
|
+
`<Picker label="Plan" selection={[plan, setPlan]} options={plans} />`,
|
|
121
|
+
`<Section header="Reading" footer="…">`. Children are the content closure.
|
|
122
|
+
`ForEach` takes `data` and a render function, with `id`, `onDelete` and
|
|
123
|
+
`onMove` as in SwiftUI; `Group` applies its modifiers to each child.
|
|
124
|
+
|
|
125
|
+
Text is plain strings and numbers inside `<Text>`; nested `<Text>`s and
|
|
126
|
+
inline `<Image>`s make a rich text (`Text + Text`), and `italic`,
|
|
127
|
+
`underline`, `strikethrough` and `tracking` are its own props.
|
|
128
|
+
|
|
129
|
+
### Modifiers as props
|
|
130
|
+
|
|
131
|
+
Every component takes SwiftUI's modifiers as props, and applies them **in
|
|
132
|
+
the order they are written, innermost first**, so a chain reads the same:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<Image
|
|
136
|
+
systemName="swift"
|
|
137
|
+
font={system(17, 'semibold')}
|
|
138
|
+
foregroundStyle="white"
|
|
139
|
+
frame={{ width: 32, height: 32 }}
|
|
140
|
+
background={{ fill: 'orange', in: <RoundedRectangle cornerRadius={8} /> }}
|
|
141
|
+
/>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
is `Image(systemName: "swift").font(.system(size: 17, weight: .semibold))
|
|
145
|
+
.foregroundStyle(.white).frame(width: 32, height: 32).background(.orange, in:
|
|
146
|
+
RoundedRectangle(cornerRadius: 8))`. Each modifier wraps the view in the same
|
|
147
|
+
`styled` element the Swift shim emits, so the renderer lays it out
|
|
148
|
+
identically. A modifier that must apply twice goes in `modifiers={[…]}`, an
|
|
149
|
+
ordered list. The environment modifiers (`buttonStyle`, `controlSize`,
|
|
150
|
+
`pickerStyle`, `labelsHidden`) are React contexts the views below read, and
|
|
151
|
+
`navigationTitle`, `toolbar`, `searchable`, `sheet`, `alert`,
|
|
152
|
+
`presentationDetents` and `preferredColorScheme` attach to the enclosing
|
|
153
|
+
screen, sheet or page, as the SwiftUI preferences do. Effects (`blur`,
|
|
154
|
+
`grayscale`, `saturation`, `brightness`, `contrast`, `hueRotation`,
|
|
155
|
+
`colorInvert`, `colorMultiply`, `blendMode`, `mask`, `rotation3DEffect`),
|
|
156
|
+
layout hints (`aspectRatio`, `scaledToFit`, `position`, `zIndex`,
|
|
157
|
+
`alignmentGuide`, `ignoresSafeArea`, `allowsHitTesting`, `lineLimit` ranges)
|
|
158
|
+
and the lifecycle (`onAppear`, `task` with an `AbortSignal` for
|
|
159
|
+
cancellation, `onChangeOf`) are modifiers too, as are `draggable` and
|
|
160
|
+
`dropDestination`.
|
|
161
|
+
|
|
162
|
+
Values are plain data: colors by name (`"red"`, `"secondary"`,
|
|
163
|
+
`"secondarySystemBackground"`) or through `Color` (`Color.opacity('black',
|
|
164
|
+
0.2)`, `Color.gradient('teal')`, `Color.hex('#336699')`); fonts by text
|
|
165
|
+
style (`"title2"`) or `{ textStyle: 'title2', weight: 'bold' }` and
|
|
166
|
+
`system(size, weight, design)`; frames as `{ width, height, maxWidth:
|
|
167
|
+
Infinity, alignment }`; padding as `true`, a number, `['vertical', 6]` or
|
|
168
|
+
insets; gradients through `LinearGradient`, `RadialGradient` and
|
|
169
|
+
`AngularGradient`.
|
|
170
|
+
|
|
171
|
+
### Bindings
|
|
172
|
+
|
|
173
|
+
A binding is a `useState` pair, so `isOn={[downloads, setDownloads]}` reads
|
|
174
|
+
like `isOn: $downloads`. Everything that takes one (`Toggle`, `Picker`,
|
|
175
|
+
`Slider`, `Stepper`, `TextField`, `TextEditor`, `DatePicker`, `TabView`'s
|
|
176
|
+
`selection`, `List`'s `selection` and `editing`, `NavigationStack`'s `path`,
|
|
177
|
+
the `sheet` and `alert` modifiers' `isPresented`) also takes a plain value
|
|
178
|
+
with an `onChange` handler. The setter can be anything with that signature.
|
|
179
|
+
|
|
180
|
+
When the renderer sends an event (a tap, a switch, a selection), the
|
|
181
|
+
handler's state updates render synchronously and the renderer gets the next
|
|
182
|
+
pass inside the event, the way the Swift core answers one.
|
|
183
|
+
|
|
184
|
+
### Navigation and presentation
|
|
185
|
+
|
|
186
|
+
`NavigationStack` holds the pushed values (or takes a `path` binding).
|
|
187
|
+
`NavigationLink` pushes a `value`, which the enclosing screen's
|
|
188
|
+
`navigationDestination` builds a screen for, or a `destination` view
|
|
189
|
+
directly. `TabView` holds `Tab`s. `.sheet` and `.alert` are modifiers whose
|
|
190
|
+
content and actions render as root-level elements while presented;
|
|
191
|
+
`presentationDetents` inside a sheet sets its detents. `useDismiss()` inside
|
|
192
|
+
a pushed screen or a sheet pops or dismisses it.
|
|
193
|
+
|
|
194
|
+
### Lists
|
|
195
|
+
|
|
196
|
+
`List` and `Form` hold `Section`s and rows. `selection={[…]}` makes rows with
|
|
197
|
+
a `tag` (or a `NavigationLink` value) selectable, one value or a `Set`;
|
|
198
|
+
`editing` is the edit mode, which `EditButton` flips. `ForEach`'s `onDelete`
|
|
199
|
+
makes its rows deletable (a swipe, or edit mode's minus) and `onMove` makes
|
|
200
|
+
them reorder with a grip, with `moveItems` to apply the move to an array.
|
|
201
|
+
`swipeActions` and `contextMenu` are modifiers on a row.
|
|
202
|
+
|
|
203
|
+
### Size classes
|
|
204
|
+
|
|
205
|
+
`useHorizontalSizeClass()` is `compact` below 700 points of width (a phone)
|
|
206
|
+
and `regular` from it (a tablet, a desktop window); `mount` and
|
|
207
|
+
`SwiftUIView` follow the root's width as it changes. The views that adapt do
|
|
208
|
+
it as on iOS: `NavigationSplitView` is two columns when regular (the
|
|
209
|
+
sidebar's toggle button hides and shows the sidebar) and one stack when
|
|
210
|
+
compact, which pushes the detail while the sidebar's `List(selection:)` has
|
|
211
|
+
a selection and clears it on back; `.inspector` is a trailing panel when
|
|
212
|
+
regular and a sheet when compact; `Table` shows every column under sortable
|
|
213
|
+
titles when regular and the first column when compact.
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
<NavigationSplitView
|
|
217
|
+
sidebar={<List selection={[team, setTeam]}>{/* rows with a tag */}</List>}
|
|
218
|
+
detail={<Table data={people} columns={columns} sortOrder={[order, setOrder]} />}
|
|
219
|
+
/>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Scrolling
|
|
223
|
+
|
|
224
|
+
`List`, `LazyVStack` and `LazyHStack` build a long `ForEach` a batch of
|
|
225
|
+
rows at a time as they scroll into view. `ScrollViewReader` hands a proxy
|
|
226
|
+
whose `scrollTo(id, anchor)` scrolls to the view marked `scrollID={id}`,
|
|
227
|
+
building lazy rows up to it first. `refreshable` (on a `List` or
|
|
228
|
+
`ScrollView`, or above one) runs an async action on a pull, with the
|
|
229
|
+
spinner while it runs. `scrollTargetBehavior="paging"` or `"viewAligned"` on
|
|
230
|
+
a `ScrollView` snaps, with `scrollTargetLayout` on the stack inside.
|
|
231
|
+
Name a coordinate space with `coordinateSpace="page"` on the `ScrollView`
|
|
232
|
+
(or any view), and a `GeometryReader` inside reads its frame there with
|
|
233
|
+
`proxy.frame({ named: 'page' })`, updated as the page scrolls.
|
|
234
|
+
|
|
235
|
+
### Drawing and media
|
|
236
|
+
|
|
237
|
+
`Shape` takes a `path` function of the rectangle it is laid out in and
|
|
238
|
+
draws a `Path` (`move`, `addLine`, `addCurve`, `addArc`, `addRoundedRect`…);
|
|
239
|
+
`UnevenRoundedRectangle` and `ContainerRelativeShape` are built on it.
|
|
240
|
+
`Canvas` hands its renderer a `GraphicsContext` (fill, stroke, text, images,
|
|
241
|
+
transforms, opacity, clips, filters, gradients in its own coordinates), and
|
|
242
|
+
`TimelineView` re-renders on a schedule, so a clock is a `TimelineView`
|
|
243
|
+
around a `Canvas`. Views passed in the `symbols` prop (`{ star: <Image
|
|
244
|
+
systemName="star.fill" /> }`) come back from `resolveSymbol` and draw with
|
|
245
|
+
`drawSymbol` at a point, under the context's transform and opacity; they are
|
|
246
|
+
real views laid over the canvas, so they draw above its other commands. `useAVPlayer(url)` is an `AVPlayer` (play, pause, seek,
|
|
247
|
+
mute, volume, rate) that `VideoPlayer` shows in the page's `<video>`; the
|
|
248
|
+
position is read or observed without re-rendering.
|
|
249
|
+
|
|
250
|
+
### Mouse, keyboard and effects
|
|
251
|
+
|
|
252
|
+
A web app may have a mouse and a keyboard, so the modifiers SwiftUI keeps
|
|
253
|
+
for iPad and Mac work here: `onHover` (a mouse entering and leaving the
|
|
254
|
+
view's frame), `help` (a tooltip), `pointerStyle` (`link`, `text`,
|
|
255
|
+
`grabIdle`… or any CSS cursor), `keyboardShortcut` on a `Button` (`⌘` is
|
|
256
|
+
Ctrl off Apple platforms) and `onKeyPress` (keys pressed while no text field
|
|
257
|
+
has the focus). `symbolEffect` draws SF Symbols' effects (`bounce`, `pulse`,
|
|
258
|
+
`variableColor`, `rotate`, `wiggle`, `breathe`, `scale`, `appear`), running
|
|
259
|
+
while active or once per change of a value. `matchedGeometryEffect` with a
|
|
260
|
+
namespace from `useNamespace()` moves a view into the frame of the one it
|
|
261
|
+
replaces in an animated change. `contentTransition="numericText"` rolls a
|
|
262
|
+
text's old value out and the new one in when it changes in an animated pass,
|
|
263
|
+
upward or downward as the number grows or shrinks (`{ numericText: {
|
|
264
|
+
countsDown } }` fixes the direction); the whole text rolls, not each digit.
|
|
265
|
+
`opacity` and `interpolate` crossfade. `sensoryFeedback` vibrates where the
|
|
266
|
+
device can.
|
|
267
|
+
|
|
268
|
+
### Animation
|
|
269
|
+
|
|
270
|
+
`withAnimation(animation, () => …)` animates the pass a state change makes,
|
|
271
|
+
and the `animation` modifier a view's changes when a value changes.
|
|
272
|
+
`PhaseAnimator` moves its content through phases, each change animated;
|
|
273
|
+
`KeyframeAnimator` plays tracks of keyframes (`linear`, `cubic`, `spring`,
|
|
274
|
+
`move`) and renders its content with the values each frame.
|
|
275
|
+
|
|
276
|
+
### Charts
|
|
277
|
+
|
|
278
|
+
`Chart` takes `BarMark`, `LineMark`, `AreaMark`, `PointMark` and `RuleMark`
|
|
279
|
+
with their values in `x` and `y`: categories, numbers or dates. The
|
|
280
|
+
component builds the scales and ticks the Swift Charts module builds
|
|
281
|
+
(bands, nice numeric domains that include zero, calendar strides), groups
|
|
282
|
+
lines and areas by series, colors `foregroundStyleBy` series from the chart
|
|
283
|
+
palette with a legend, and places `annotation`s.
|
|
284
|
+
|
|
285
|
+
## Mixing with ordinary React
|
|
286
|
+
|
|
287
|
+
Both directions work, and they nest: a `react-dom` page can hold a SwiftUI
|
|
288
|
+
island, and the island can hold ordinary React DOM inside one of its rows.
|
|
289
|
+
The island page of the demo (`island.html`) does exactly that.
|
|
290
|
+
|
|
291
|
+
### A SwiftUI tree inside a react-dom app
|
|
292
|
+
|
|
293
|
+
`SwiftUIView`, shown in [Getting started](#inside-an-existing-react-app):
|
|
294
|
+
a component whose children are a SwiftUI tree, drawn in a box of the page.
|
|
295
|
+
|
|
296
|
+
### React DOM inside a SwiftUI element
|
|
297
|
+
|
|
298
|
+
`Host` puts ordinary React DOM, with its own components, stylesheets and
|
|
299
|
+
event handlers, inside a SwiftUI tree. Its children render with `react-dom`
|
|
300
|
+
in a `host` element the layout engine sizes, so the DOM sits inside list
|
|
301
|
+
rows, stacks and screens like any other view:
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
function Composer() {
|
|
305
|
+
const [notes, setNotes] = useState('');
|
|
306
|
+
return (
|
|
307
|
+
<List>
|
|
308
|
+
<Section header="Appearance">
|
|
309
|
+
<LabeledContent label="Notes" value={`${notes.length} characters`} />
|
|
310
|
+
</Section>
|
|
311
|
+
<Section header="Composer" footer="A textarea and a counter, rendered by react-dom inside the list row.">
|
|
312
|
+
<Host className="composer">
|
|
313
|
+
<textarea value={notes} placeholder="Write something…" onChange={(e) => setNotes(e.target.value)} />
|
|
314
|
+
<div className="count">{280 - notes.length}</div>
|
|
315
|
+
</Host>
|
|
316
|
+
</Section>
|
|
317
|
+
<Section header="Map">
|
|
318
|
+
<Host sizing="fill" frame={{ height: 160 }}>
|
|
319
|
+
<MapView />
|
|
320
|
+
</Host>
|
|
321
|
+
</Section>
|
|
322
|
+
</List>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Typing in the textarea updates `notes` in the React component above, the
|
|
328
|
+
SwiftUI row shows the count, and the row grows with the text.
|
|
329
|
+
|
|
330
|
+
- **Sizing.** `sizing="content"` (the default) measures the DOM like text:
|
|
331
|
+
as wide as the content wants, up to the width the parent proposes, and as
|
|
332
|
+
tall as it is at that width. Whenever the content changes size (more
|
|
333
|
+
lines, an image that loaded, a state change), the tree is laid out again
|
|
334
|
+
around it. `sizing="fill"` takes the proposal, so a `frame` (or the
|
|
335
|
+
parent's size) decides the box and the content fills it; `height: 100%`
|
|
336
|
+
in the content reaches the box.
|
|
337
|
+
- **Styling.** The content inherits the device's font and text color, and
|
|
338
|
+
the iOS tokens are CSS custom properties it can use:
|
|
339
|
+
`var(--sb-color-secondary)`, `var(--sb-color-accent)`,
|
|
340
|
+
`var(--sb-color-separator)`, the Dynamic Type sizes, and so on; they
|
|
341
|
+
follow dark mode. `className` and `style` land on the wrapper the
|
|
342
|
+
children render in.
|
|
343
|
+
- **Events.** The DOM handles its own events. Pointer downs inside a host
|
|
344
|
+
never start the renderer's gestures (no swipe, no drag), so text
|
|
345
|
+
selection, scrolling inside the host and native controls work; the row
|
|
346
|
+
around it still swipes when the pan starts outside the host.
|
|
347
|
+
- **Hooks.** The content can use this package's hooks: `useDismiss()` from a
|
|
348
|
+
sheet or a pushed screen, `useNavigation()` to push, `useColorScheme()`.
|
|
349
|
+
|
|
350
|
+
A host is a React root of its own, so it needs the `react-dom` peer
|
|
351
|
+
dependency, and React context does not cross into it by itself; see below.
|
|
352
|
+
|
|
353
|
+
### Contexts across roots
|
|
354
|
+
|
|
355
|
+
`SwiftUIView` and `Host` each start a React root, and React context stops at
|
|
356
|
+
a root boundary. This package's own contexts (the environment, navigation,
|
|
357
|
+
dismiss, the view environment) are bridged automatically. For the page's
|
|
358
|
+
own contexts, name them on the component and they are read on the outer
|
|
359
|
+
side and provided again inside:
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
<SwiftUIView contexts={[ThemeContext, RouterContext]}>…</SwiftUIView>
|
|
363
|
+
<Host contexts={[ThemeContext]}>…</Host>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
The list must be the same on every render of that component (each entry is
|
|
367
|
+
read with a hook). Props, callbacks and external stores need no bridging.
|
|
368
|
+
|
|
369
|
+
## How it works
|
|
370
|
+
|
|
371
|
+
- `src/host/`: a [react-reconciler](https://www.npmjs.com/package/react-reconciler)
|
|
372
|
+
host whose instances are protocol elements. After each React commit,
|
|
373
|
+
`HostTree.flush` resolves two virtual kinds (a *preference* merges props
|
|
374
|
+
into the enclosing `navscreen` or `sheet`; a *hoist* moves children to the
|
|
375
|
+
enclosing screen or to the root, as `.toolbar`, `.sheet` and `.alert` do),
|
|
376
|
+
diffs the result against the last pass and emits one render pass: creates,
|
|
377
|
+
updates, inserts (parents before children), removes, `commit`. Events from
|
|
378
|
+
the renderer reach the element's handlers and flush synchronously.
|
|
379
|
+
- `createSwiftUIRoot(sink)` renders a tree into any ops sink; `mount(screen,
|
|
380
|
+
element)` pairs a root with a `Renderer` from `@swiftbrowser/web/src/renderer`.
|
|
381
|
+
`withAnimation(() => setCount(1))` stamps the pass's `commit` with the
|
|
382
|
+
animation, as `withAnimation { }` does.
|
|
383
|
+
- `src/modifiers.tsx` applies the modifier props; `src/components/` holds the
|
|
384
|
+
components; `src/components/host.tsx` and `src/island.tsx` the two mixing
|
|
385
|
+
components, over the renderer's `host` element kind and its island layout
|
|
386
|
+
mode ([docs/ops-protocol.md](../../docs/ops-protocol.md#host-elements-the-pages-own-dom)).
|
|
387
|
+
- `test/fidelity.test.tsx` renders `Examples/Fidelity`'s tab view through the
|
|
388
|
+
host and compares the element tree with `Examples/Fidelity/__snapshots__/fidelity.jsonl`,
|
|
389
|
+
the ops the Swift app emits for the same screens; `test/extras.test.tsx`
|
|
390
|
+
covers the components beyond the Swift app.
|
|
391
|
+
|
|
392
|
+
## What is covered
|
|
393
|
+
|
|
394
|
+
- **Layout**: stacks, `LazyVStack`, `LazyHStack`, `Spacer`, `Divider`,
|
|
395
|
+
`ScrollView`, `ScrollViewReader`, `Group`, `ForEach` (with `onDelete` and
|
|
396
|
+
`onMove`), `Grid`, `LazyVGrid`, `ViewThatFits`, `GeometryReader` (its size,
|
|
397
|
+
its frame on screen or in a `coordinateSpace` named on an ancestor, and its
|
|
398
|
+
safe-area insets), custom `Layout` (with `layoutValue`s, priorities, view
|
|
399
|
+
spacing, explicit alignment guides and a cache), `safeAreaInset`.
|
|
400
|
+
- **Content**: `Text` (with `Text + Text` runs, inline images, italic,
|
|
401
|
+
underline, strikethrough, tracking), `Image` (symbols, catalog images
|
|
402
|
+
with the `assets` option, remote images and `AsyncImage`), `Label` and its
|
|
403
|
+
styles, `LabeledContent`, `Link`, `Color` as a view, shapes (built-in,
|
|
404
|
+
custom paths, `UnevenRoundedRectangle`, `ContainerRelativeShape`) with
|
|
405
|
+
fills, strokes and gradients, `Canvas`, `TimelineView`, `VideoPlayer`,
|
|
406
|
+
`Gauge`, `ProgressView`, Swift Charts.
|
|
407
|
+
- **Controls**: `Button` (styles, roles, sizes), `Toggle`, `Picker`,
|
|
408
|
+
`Slider`, `Stepper`, `TextField` and `SecureField` (focus bindings, submit
|
|
409
|
+
labels), `TextEditor`, `DatePicker`, `ColorPicker`, `ShareLink`, `Menu`,
|
|
410
|
+
`EditButton`.
|
|
411
|
+
- **Containers**: `List` (selection, edit mode, swipe actions, deletion,
|
|
412
|
+
reordering, pull to refresh), `Form`, `Section`, `Table`, `GroupBox`,
|
|
413
|
+
`DisclosureGroup`, `ControlGroup`, `ContentUnavailableView`.
|
|
414
|
+
- **Navigation and presentation**: `NavigationStack` (value and destination
|
|
415
|
+
links, a `path` binding, nested stacks flattened), `NavigationSplitView`,
|
|
416
|
+
`TabView`/`Tab`, toolbars, search fields, sheets with detents, popovers
|
|
417
|
+
(as sheets), alerts and dialogs, inspectors.
|
|
418
|
+
- **Interaction**: `onTapGesture`, `dragGesture`, `onLongPressGesture`,
|
|
419
|
+
`contextMenu`, `draggable` and `dropDestination` (in the app, and text or
|
|
420
|
+
files from outside the page), `onHover`, `help`, `pointerStyle`,
|
|
421
|
+
`keyboardShortcut`, `onKeyPress`, `sensoryFeedback`.
|
|
422
|
+
- **Animation and effects**: `withAnimation`, `animation`, transitions,
|
|
423
|
+
`PhaseAnimator`, `KeyframeAnimator`, `matchedGeometryEffect`,
|
|
424
|
+
`symbolEffect`, `contentTransition`, `redacted` placeholders, `textCase`.
|
|
425
|
+
- **Mixing**: `Host` and `SwiftUIView`.
|
|
426
|
+
|
|
427
|
+
## Not yet
|
|
428
|
+
|
|
429
|
+
- `contentTransition="numericText"` rolls the whole text rather than only
|
|
430
|
+
the digits that changed, and `Canvas` symbols always draw above the
|
|
431
|
+
canvas's other commands.
|
|
432
|
+
- The Swift-only bridges (SwiftData, URLSession, notifications, Core ML,
|
|
433
|
+
StoreKit) have no React counterpart, by design: a web app uses the
|
|
434
|
+
platform's own.
|
|
435
|
+
- The ops protocol is the contract with the renderer and is versioned with it:
|
|
436
|
+
this package imports the renderer's sources from `@swiftbrowser/web/src/*`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `Binding<T>`: a `useState` pair, so `isOn={[downloads, setDownloads]}`
|
|
3
|
+
* reads like `isOn: $downloads`. A plain value with an `onChange` handler is
|
|
4
|
+
* accepted everywhere a binding is.
|
|
5
|
+
*/
|
|
6
|
+
export type Binding<T> = readonly [T, (value: T) => void];
|
|
7
|
+
export type BindingValue<T> = Binding<T> | T;
|
|
8
|
+
export declare function readBinding<T>(value: BindingValue<T>, onChange?: (value: T) => void): [T, (next: T) => void];
|
|
9
|
+
//# sourceMappingURL=binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC;AAE1D,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAE7C,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,CAM5G"}
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React context does not cross a root boundary, and a `Host` (a react-dom
|
|
3
|
+
* root inside the SwiftUI tree) or a `SwiftUIView` (a SwiftUI root inside a
|
|
4
|
+
* react-dom tree) is one. The bridge reads the contexts on the outer side and
|
|
5
|
+
* provides them again on the inner side. This package's own contexts always
|
|
6
|
+
* travel; a component names any others it needs.
|
|
7
|
+
*/
|
|
8
|
+
import { type Context, type ReactNode } from 'react';
|
|
9
|
+
/**
|
|
10
|
+
* Returns a function that wraps `children` in providers of the current
|
|
11
|
+
* values of this package's contexts and of `contexts`. The list of contexts
|
|
12
|
+
* must not change between renders (each is read with a hook).
|
|
13
|
+
*/
|
|
14
|
+
export declare function useContextBridge(contexts?: readonly Context<unknown>[], options?: {
|
|
15
|
+
environment?: boolean;
|
|
16
|
+
}): (children: ReactNode) => ReactNode;
|
|
17
|
+
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAA6B,KAAK,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAWhF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,GAAE,SAAS,OAAO,CAAC,OAAO,CAAC,EAAO,EAC1C,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,CAAC,QAAQ,EAAE,SAAS,KAAK,SAAS,CASpC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PhaseAnimator` and `KeyframeAnimator`. A phase animator moves its content
|
|
3
|
+
* through phases, each change in one animated pass that the renderer tweens
|
|
4
|
+
* (as the shim does); a keyframe animator computes its values frame by frame
|
|
5
|
+
* along tracks of keyframes and renders them, as SwiftUI evaluates its
|
|
6
|
+
* content per frame.
|
|
7
|
+
*/
|
|
8
|
+
import type { Animation } from '@swiftbrowser/web/src/protocol';
|
|
9
|
+
import { type ReactNode } from 'react';
|
|
10
|
+
export interface PhaseAnimatorProps<Phase> {
|
|
11
|
+
phases: readonly Phase[];
|
|
12
|
+
/** Each change runs through the phases once, back to the first; without one the phases cycle while shown. */
|
|
13
|
+
trigger?: unknown;
|
|
14
|
+
/** The animation into a phase (`default` by default). */
|
|
15
|
+
animation?: (phase: Phase) => Animation;
|
|
16
|
+
children: (phase: Phase) => ReactNode;
|
|
17
|
+
}
|
|
18
|
+
export declare function PhaseAnimator<Phase>({ phases, trigger, animation, children }: PhaseAnimatorProps<Phase>): ReactNode;
|
|
19
|
+
export type KeyframeCurve = 'linear' | 'cubic' | 'spring' | 'move';
|
|
20
|
+
/** One keyframe of a track: the value it reaches, in how long (seconds), along which curve. */
|
|
21
|
+
export interface Keyframe {
|
|
22
|
+
to: number;
|
|
23
|
+
duration: number;
|
|
24
|
+
curve?: KeyframeCurve;
|
|
25
|
+
}
|
|
26
|
+
export type KeyframeTracks<Value extends Record<string, number>> = {
|
|
27
|
+
[K in keyof Value]?: readonly Keyframe[];
|
|
28
|
+
};
|
|
29
|
+
export interface KeyframeAnimatorProps<Value extends Record<string, number>> {
|
|
30
|
+
initialValue: Value;
|
|
31
|
+
keyframes: KeyframeTracks<Value>;
|
|
32
|
+
/** Each change plays the keyframes once; without one they repeat while shown (`repeating`). */
|
|
33
|
+
trigger?: unknown;
|
|
34
|
+
repeating?: boolean;
|
|
35
|
+
children: (value: Value) => ReactNode;
|
|
36
|
+
}
|
|
37
|
+
/** The tracks' values `elapsed` seconds in (each track from the initial value through its keyframes). */
|
|
38
|
+
export declare function keyframeValues<Value extends Record<string, number>>(initial: Value, tracks: KeyframeTracks<Value>, elapsed: number): Value;
|
|
39
|
+
export declare function KeyframeAnimator<Value extends Record<string, number>>({ initialValue, keyframes, trigger, repeating, children, }: KeyframeAnimatorProps<Value>): ReactNode;
|
|
40
|
+
//# sourceMappingURL=animators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animators.d.ts","sourceRoot":"","sources":["../../src/components/animators.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAOpE,MAAM,WAAW,kBAAkB,CAAC,KAAK;IACvC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IACzB,6GAA6G;IAC7G,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC;IACxC,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC;CACvC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,kBAAkB,CAAC,KAAK,CAAC,GAAG,SAAS,CAuCnH;AAMD,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEnE,+FAA+F;AAC/F,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,SAAS,QAAQ,EAAE;CAAE,CAAC;AAEhH,MAAM,WAAW,qBAAqB,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IACzE,YAAY,EAAE,KAAK,CAAC;IACpB,SAAS,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IACjC,+FAA+F;IAC/F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC;CACvC;AAkBD,yGAAyG;AACzG,wBAAgB,cAAc,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAsB1I;AAMD,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EACrE,YAAY,EACZ,SAAS,EACT,OAAO,EACP,SAAS,EACT,QAAQ,GACT,EAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG,SAAS,CA+B1C"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Canvas`: a `GeometryReader` whose content, at the reported size, is a
|
|
3
|
+
* `canvas` element. The renderer function draws into a `GraphicsContext`
|
|
4
|
+
* that records commands, as Sources/SwiftUI/API/Canvas.swift does; the page
|
|
5
|
+
* draws them as SVG.
|
|
6
|
+
*/
|
|
7
|
+
import type { CanvasCommand, CanvasFilter } from '@swiftbrowser/web/src/protocol';
|
|
8
|
+
import { type ReactNode } from 'react';
|
|
9
|
+
import { type ViewModifiers } from '../modifiers';
|
|
10
|
+
import type { Path, Point } from '../path';
|
|
11
|
+
import { type ColorValue, type FontValue, type StyleValue, type UnitPointValue } from '../values';
|
|
12
|
+
type Matrix = [number, number, number, number, number, number];
|
|
13
|
+
/** A shading in the drawing's coordinates: a style, or a gradient between two points. */
|
|
14
|
+
export type Shading = StyleValue | {
|
|
15
|
+
linearGradient: {
|
|
16
|
+
colors?: ColorValue[];
|
|
17
|
+
stops?: {
|
|
18
|
+
color: ColorValue;
|
|
19
|
+
location: number;
|
|
20
|
+
}[];
|
|
21
|
+
start: Point;
|
|
22
|
+
end: Point;
|
|
23
|
+
};
|
|
24
|
+
} | {
|
|
25
|
+
radialGradient: {
|
|
26
|
+
colors?: ColorValue[];
|
|
27
|
+
stops?: {
|
|
28
|
+
color: ColorValue;
|
|
29
|
+
location: number;
|
|
30
|
+
}[];
|
|
31
|
+
center: Point;
|
|
32
|
+
startRadius: number;
|
|
33
|
+
endRadius: number;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export interface StrokeOptions {
|
|
37
|
+
lineWidth?: number;
|
|
38
|
+
lineCap?: 'butt' | 'round' | 'square';
|
|
39
|
+
lineJoin?: 'miter' | 'round' | 'bevel';
|
|
40
|
+
miterLimit?: number;
|
|
41
|
+
dash?: number[];
|
|
42
|
+
dashPhase?: number;
|
|
43
|
+
}
|
|
44
|
+
/** What a canvas image is: a symbol, a catalog image, or a remote image of a known size. */
|
|
45
|
+
export type CanvasImage = {
|
|
46
|
+
systemName: string;
|
|
47
|
+
} | {
|
|
48
|
+
name: string;
|
|
49
|
+
} | {
|
|
50
|
+
url: string;
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
};
|
|
54
|
+
/** A view passed in `Canvas`'s `symbols`, resolved by its id. */
|
|
55
|
+
export interface ResolvedSymbol {
|
|
56
|
+
symbol: string;
|
|
57
|
+
}
|
|
58
|
+
/** Where a symbol was drawn: the point (the context's transform applied) and the look at that moment. */
|
|
59
|
+
export interface SymbolDraw {
|
|
60
|
+
id: string;
|
|
61
|
+
x: number;
|
|
62
|
+
y: number;
|
|
63
|
+
rotation: number;
|
|
64
|
+
scale: {
|
|
65
|
+
x: number;
|
|
66
|
+
y: number;
|
|
67
|
+
};
|
|
68
|
+
opacity: number;
|
|
69
|
+
}
|
|
70
|
+
/** `GraphicsContext`: what `Canvas`'s renderer draws with. Methods return the context for chaining. */
|
|
71
|
+
export declare class GraphicsContext {
|
|
72
|
+
readonly size: {
|
|
73
|
+
width: number;
|
|
74
|
+
height: number;
|
|
75
|
+
};
|
|
76
|
+
readonly commands: CanvasCommand[];
|
|
77
|
+
readonly symbolDraws: SymbolDraw[];
|
|
78
|
+
private symbolIds;
|
|
79
|
+
/** @internal The ids `symbols` gave the canvas. */
|
|
80
|
+
setSymbols(ids: Iterable<string>): void;
|
|
81
|
+
/** `resolveSymbol(id:)`: the view the canvas was given under `id`, or null. */
|
|
82
|
+
resolveSymbol(id: string): ResolvedSymbol | null;
|
|
83
|
+
/**
|
|
84
|
+
* `draw(symbol, at:)`: the symbol's view centered on the point. Symbols are
|
|
85
|
+
* views laid over the drawing, so they draw above its other commands.
|
|
86
|
+
*/
|
|
87
|
+
drawSymbol(symbol: ResolvedSymbol, at: Point): this;
|
|
88
|
+
private state;
|
|
89
|
+
constructor(size: {
|
|
90
|
+
width: number;
|
|
91
|
+
height: number;
|
|
92
|
+
});
|
|
93
|
+
get opacity(): number;
|
|
94
|
+
set opacity(value: number);
|
|
95
|
+
get blendMode(): string | undefined;
|
|
96
|
+
set blendMode(value: string | undefined);
|
|
97
|
+
translateBy(x: number, y: number): this;
|
|
98
|
+
scaleBy(x: number, y?: number): this;
|
|
99
|
+
/** Degrees, clockwise on screen. */
|
|
100
|
+
rotate(degrees: number): this;
|
|
101
|
+
concatenate(matrix: Matrix): this;
|
|
102
|
+
clip(to: Path): this;
|
|
103
|
+
addFilter(filter: CanvasFilter): this;
|
|
104
|
+
/** `drawLayer { }`: draws with a copy of the state, restored after. */
|
|
105
|
+
drawLayer(body: (context: GraphicsContext) => void): this;
|
|
106
|
+
fill(path: Path, with_: Shading, options?: {
|
|
107
|
+
eoFill?: boolean;
|
|
108
|
+
}): this;
|
|
109
|
+
stroke(path: Path, with_: Shading, options?: StrokeOptions | number): this;
|
|
110
|
+
/** `draw(Text(...), at:anchor:)` */
|
|
111
|
+
drawText(text: string, at: Point, options?: {
|
|
112
|
+
anchor?: UnitPointValue;
|
|
113
|
+
font?: FontValue;
|
|
114
|
+
color?: StyleValue;
|
|
115
|
+
}): this;
|
|
116
|
+
/** `draw(Image(...), at:anchor:)` or `draw(_:in:)` with `size`. */
|
|
117
|
+
drawImage(image: CanvasImage, at: Point, options?: {
|
|
118
|
+
anchor?: UnitPointValue;
|
|
119
|
+
size?: {
|
|
120
|
+
width: number;
|
|
121
|
+
height: number;
|
|
122
|
+
};
|
|
123
|
+
color?: StyleValue;
|
|
124
|
+
}): this;
|
|
125
|
+
private push;
|
|
126
|
+
}
|
|
127
|
+
export interface CanvasProps extends ViewModifiers {
|
|
128
|
+
/** `Canvas { context, size in }` */
|
|
129
|
+
renderer: (context: GraphicsContext, size: {
|
|
130
|
+
width: number;
|
|
131
|
+
height: number;
|
|
132
|
+
}) => void;
|
|
133
|
+
/** `Canvas(renderer:symbols:)`: views the renderer draws by id (`resolveSymbol`, `drawSymbol`). */
|
|
134
|
+
symbols?: Record<string, ReactNode>;
|
|
135
|
+
}
|
|
136
|
+
export declare function Canvas(props: CanvasProps): ReactNode;
|
|
137
|
+
export {};
|
|
138
|
+
//# sourceMappingURL=canvas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../src/components/canvas.tsx"],"names":[],"mappings":"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"}
|