@swiftbrowser/react 0.17.7 → 0.18.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 +243 -14
- package/dist/appStorage.d.ts +43 -0
- package/dist/appStorage.d.ts.map +1 -0
- package/dist/attributedText.d.ts +76 -0
- package/dist/attributedText.d.ts.map +1 -0
- package/dist/audioRecorder.d.ts +52 -0
- package/dist/audioRecorder.d.ts.map +1 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/components/charts.d.ts +5 -0
- package/dist/components/charts.d.ts.map +1 -1
- package/dist/components/containers.d.ts +5 -0
- package/dist/components/containers.d.ts.map +1 -1
- package/dist/components/controls.d.ts +84 -8
- package/dist/components/controls.d.ts.map +1 -1
- package/dist/components/geometry.d.ts +18 -0
- package/dist/components/geometry.d.ts.map +1 -1
- package/dist/components/images.d.ts.map +1 -1
- package/dist/components/lists.d.ts.map +1 -1
- package/dist/components/media.d.ts +18 -10
- package/dist/components/media.d.ts.map +1 -1
- package/dist/components/stacks.d.ts +70 -4
- package/dist/components/stacks.d.ts.map +1 -1
- package/dist/environment.d.ts +2 -1
- package/dist/environment.d.ts.map +1 -1
- package/dist/fileManager.d.ts +58 -0
- package/dist/fileManager.d.ts.map +1 -0
- package/dist/host/root.d.ts +16 -2
- package/dist/host/root.d.ts.map +1 -1
- package/dist/host/tree.d.ts +9 -0
- package/dist/host/tree.d.ts.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1200 -369
- package/dist/index.js.map +1 -1
- package/dist/measureText.d.ts +32 -0
- package/dist/measureText.d.ts.map +1 -0
- package/dist/modifiers.d.ts +81 -5
- package/dist/modifiers.d.ts.map +1 -1
- package/dist/mount.d.ts +5 -0
- package/dist/mount.d.ts.map +1 -1
- package/dist/presentation.d.ts +5 -1
- package/dist/presentation.d.ts.map +1 -1
- package/dist/scrollReader.d.ts +34 -8
- package/dist/scrollReader.d.ts.map +1 -1
- package/dist/undo.d.ts +108 -0
- package/dist/undo.d.ts.map +1 -0
- package/dist/visualEffect.d.ts +10 -14
- package/dist/visualEffect.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -34,6 +34,25 @@ worker resolves from its sources.
|
|
|
34
34
|
|
|
35
35
|
## Getting started
|
|
36
36
|
|
|
37
|
+
### A new app
|
|
38
|
+
|
|
39
|
+
One command lays out an app that runs the same code on iOS and the web:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
npx @swiftbrowser/cli native init MyApp
|
|
43
|
+
cd MyApp && npm install
|
|
44
|
+
npm run dev:web # the app as a web page, at http://localhost:5175/
|
|
45
|
+
npm run build # ios/app.js, the bundle MyApp.xcodeproj runs natively
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The app is `src/App.tsx`, beside `ios/` (the Xcode target's folder: the
|
|
49
|
+
App file, the asset catalog and the bundle), `web/` and `MyApp.xcodeproj`,
|
|
50
|
+
as React Native lays its shells out. `src/index.tsx` is the iOS entry (`run(<App />)`
|
|
51
|
+
in a [SwiftBrowser Native](https://github.com/noya-app/SwiftBrowser/blob/main/Native/README.md)
|
|
52
|
+
app, where the views are real SwiftUI) and `web/main.tsx` the web entry
|
|
53
|
+
(`mount(...)` in a Vite page, where this package's renderer draws them);
|
|
54
|
+
neither holds app code. The rest of this section is what those entries do.
|
|
55
|
+
|
|
37
56
|
### A whole page
|
|
38
57
|
|
|
39
58
|
The simplest app is a page whose root is a SwiftUI tree. The page provides
|
|
@@ -168,7 +187,7 @@ identically. A modifier that must apply twice goes in `modifiers={[…]}`, an
|
|
|
168
187
|
ordered list. A layered style is an array: `foregroundStyle={['white',
|
|
169
188
|
'indigo']}` is `.foregroundStyle(.white, .indigo)`, whose second style a
|
|
170
189
|
`symbolRenderingMode="palette"` symbol draws its disc in. The environment
|
|
171
|
-
modifiers (`buttonStyle`, `controlSize`, `pickerStyle`, `labelsHidden`,
|
|
190
|
+
modifiers (`buttonStyle`, `controlSize`, `pickerStyle`, `toggleStyle`, `labelsHidden`,
|
|
172
191
|
`symbolRenderingMode`) are React contexts the views below read, and
|
|
173
192
|
`navigationTitle`, `toolbar`, `toolbarRole`, `searchable`, `sheet`, `alert`,
|
|
174
193
|
`presentationDetents` and `preferredColorScheme` attach to the enclosing
|
|
@@ -203,6 +222,52 @@ When the renderer sends an event (a tap, a switch, a selection), the
|
|
|
203
222
|
handler's state updates render synchronously and the renderer gets the next
|
|
204
223
|
pass inside the event, the way the Swift core answers one.
|
|
205
224
|
|
|
225
|
+
`TextEditor` also edits `AttributedText`, SwiftUI's `AttributedString` in
|
|
226
|
+
`TextEditor(text:selection:)`: runs of `{ text, bold, italic, underline,
|
|
227
|
+
strikethrough, foregroundColor, backgroundColor, font }` (`backgroundColor`
|
|
228
|
+
a highlight behind the run, `font` its own `{ size, weight, design }` over
|
|
229
|
+
the editor's). What the viewer types takes the
|
|
230
|
+
attributes of what it replaced, else of the character before it, and the
|
|
231
|
+
formatting the native editor's own edit menu applies (iOS 26's Bold,
|
|
232
|
+
Italic, Underline) comes back into the text. A `selection` binding, on a
|
|
233
|
+
plain or an attributed editor, gets the viewer's selection in UTF-16
|
|
234
|
+
offsets, so a Bold button can set `AttributedText.settingAttributes(text,
|
|
235
|
+
start, end, { bold: true })` on it; a selection the app sets moves the
|
|
236
|
+
caret (when the app focuses the editor, if it is not focused).
|
|
237
|
+
`.scrollDisabled` makes an editor as tall as its lines, for one in a
|
|
238
|
+
scrolling page:
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
const [text, setText] = useState<AttributedText>([{ text: 'Hello ' }, { text: 'world', bold: true }]);
|
|
242
|
+
const [selection, setSelection] = useState<TextSelection>({ start: 0, end: 0 });
|
|
243
|
+
<TextEditor text={[text, setText]} selection={[selection, setSelection]} scrollDisabled />
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`TextField` and `TextEditor` take `onDeleteBackward`, which SwiftUI has no
|
|
247
|
+
modifier for: Backspace with nothing selected and the caret at the start of
|
|
248
|
+
the text (an empty field included) deletes nothing and runs it, so a
|
|
249
|
+
paragraph editor can merge the paragraph into the one before it.
|
|
250
|
+
|
|
251
|
+
`measureText(text, font, width)` is the size a `Text` of `text` in `font`
|
|
252
|
+
(a text style name or `system(size, weight, design)`) takes wrapped to
|
|
253
|
+
`width` points: `{ width, height, lines }`, answered synchronously by the
|
|
254
|
+
renderer's typography on the web and by the host's text system natively
|
|
255
|
+
(`canMeasureText()` says whether something answers yet), for an app that
|
|
256
|
+
paginates or sizes from its text.
|
|
257
|
+
|
|
258
|
+
A field's `focused` is `.focused($isFocused)`: a boolean binding the field
|
|
259
|
+
follows and sets. With `focusedValue` it is `.focused($field, equals:)`:
|
|
260
|
+
one binding of the focused field's value (null for none) shared by the
|
|
261
|
+
fields, each naming its own. What a handler for `onSubmit` sets wins over
|
|
262
|
+
the keyboard's dismissal (the same field again keeps it, another takes it),
|
|
263
|
+
and a field that takes the focus is scrolled into view:
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
const [field, setField] = useState<'username' | 'password' | null>('username');
|
|
267
|
+
<TextField placeholder="Username" text={[name, setName]} focused={[field, setField]} focusedValue="username" onSubmit={() => setField('password')} />
|
|
268
|
+
<SecureField placeholder="Password" text={[password, setPassword]} focused={[field, setField]} focusedValue="password" onSubmit={signIn} />
|
|
269
|
+
```
|
|
270
|
+
|
|
206
271
|
### Navigation and presentation
|
|
207
272
|
|
|
208
273
|
`NavigationStack` holds the pushed values (or takes a `path` binding).
|
|
@@ -212,7 +277,11 @@ directly. A pushed screen's title takes the display mode of the screen below
|
|
|
212
277
|
it unless it sets its own (large below a large title), as on iOS. `TabView`
|
|
213
278
|
holds `Tab`s, each with a `systemImage` or an asset catalog `image` (a custom
|
|
214
279
|
symbol) for its bar item. `.sheet` and `.alert` are modifiers whose
|
|
215
|
-
content and actions render as root-level elements while presented
|
|
280
|
+
content and actions render as root-level elements while presented (an
|
|
281
|
+
alert's `actions` take a `TextField` beside the `Button`s, drawn in the
|
|
282
|
+
card above them: `alert={{ title: 'Rename', isPresented, actions: <><TextField
|
|
283
|
+
text={[name, setName]} /><Button title="Cancel" role="cancel" /><Button
|
|
284
|
+
title="Rename" action={rename} /></> }}`);
|
|
216
285
|
`presentationDetents` inside a sheet sets its detents. `useDismiss()` inside
|
|
217
286
|
a pushed screen or a sheet pops or dismisses it.
|
|
218
287
|
|
|
@@ -269,6 +338,10 @@ swapped item (`{playing ? <ToolbarItem>…stop…</ToolbarItem> :
|
|
|
269
338
|
`placement="largeTitle"` (iOS 26) puts a view in the large title row in
|
|
270
339
|
place of the title's text, a logo beside the screen's name say; it scrolls
|
|
271
340
|
away with the content and the bar shows `navigationTitle` once it has.
|
|
341
|
+
`placement="keyboard"` is the bar above the keyboard while a text input of
|
|
342
|
+
the screen is being edited (a formatting bar, a Done button), its items in
|
|
343
|
+
a row from the leading edge, a `Spacer` pushing the rest to the trailing
|
|
344
|
+
edge; it is hidden while nothing in the screen is being edited.
|
|
272
345
|
|
|
273
346
|
```tsx
|
|
274
347
|
<List
|
|
@@ -352,13 +425,37 @@ view scrolls under, or leaves it.
|
|
|
352
425
|
Name a coordinate space with `coordinateSpace="page"` on the `ScrollView`
|
|
353
426
|
(or any view), and a `GeometryReader` inside reads its frame there with
|
|
354
427
|
`proxy.frame({ named: 'page' })`, updated as the page scrolls.
|
|
428
|
+
`onGeometryChange={{ of: (proxy) => proxy.size, action: setSize }}` is
|
|
429
|
+
`.onGeometryChange(for:of:action:)`: the action hears the transform's
|
|
430
|
+
answer when it changes, after the render, without a reader or a child
|
|
431
|
+
that reports in an effect.
|
|
432
|
+
|
|
433
|
+
`onScrollGeometryChange` on a `ScrollView` is `.onScrollGeometryChange`:
|
|
434
|
+
a function hears the scroll view's `ScrollGeometry` (`contentOffset`,
|
|
435
|
+
`contentSize`, `containerSize`, `contentInsets`, `visibleRect`) on every
|
|
436
|
+
change, once per frame as it scrolls; `{ of, action }` calls `action(value,
|
|
437
|
+
oldValue)` only when `of(geometry)` answers differently, so an app that
|
|
438
|
+
watches the content size alone is not rendered per frame. A
|
|
439
|
+
`scrollPosition` binding set to a point (`{ x, y }`, an axis left out
|
|
440
|
+
keeping its offset) scrolls there, as `ScrollPosition.scrollTo(x:y:)`
|
|
441
|
+
does, smoothly inside `withAnimation`; so does `proxy.scrollTo({ x, y })`
|
|
442
|
+
for the scroll views inside a reader. The binding reads back as ids, never
|
|
443
|
+
as a point. `pinnedViews={['sectionHeaders']}` on a `LazyVStack`,
|
|
444
|
+
`LazyHStack`, `LazyVGrid` or `LazyHGrid` keeps each `Section`'s header at
|
|
445
|
+
the scroll view's edge while its section passes (`'sectionFooters'` too);
|
|
446
|
+
a string header pins as a text. The Showcase's Numbers lays a
|
|
447
|
+
spreadsheet's frozen letters, numbers and header cells over a
|
|
448
|
+
`ScrollView axes="both"` this way, moved by the offset it reports.
|
|
355
449
|
|
|
356
450
|
`scrollTransition` draws effects as a view leaves the visible area of its
|
|
357
451
|
scroll view and comes back. The closure gets the effect to build on and the
|
|
358
452
|
phase (`topLeading`, `identity` or `bottomTrailing`, with `value` -1, 0 or
|
|
359
|
-
1)
|
|
360
|
-
view
|
|
361
|
-
|
|
453
|
+
1); it runs once per phase, and the renderer draws the three effects as the
|
|
454
|
+
view scrolls (natively, SwiftUI's own `.scrollTransition`), so they follow
|
|
455
|
+
the finger without a round trip to the app. By default the effect follows
|
|
456
|
+
the scrolling, drawn partway while the view is partly out;
|
|
457
|
+
`configuration: 'animated'` switches phases with an animation instead.
|
|
458
|
+
`scrollIndicators="hidden"` hides a scroll view's indicators (a pager's). `containerRelativeFrame` sizes a view against the scroll
|
|
362
459
|
view's visible area, so a carousel of cards is:
|
|
363
460
|
|
|
364
461
|
```tsx
|
|
@@ -397,7 +494,8 @@ systemName="star.fill" /> }`) come back from `resolveSymbol` and draw with
|
|
|
397
494
|
`drawSymbol` at a point, under the context's transform and opacity; they are
|
|
398
495
|
real views laid over the canvas, so they draw above its other commands. `useAVPlayer(url)` is an `AVPlayer` (play, pause, seek,
|
|
399
496
|
mute, volume, rate) that `VideoPlayer` shows in the page's `<video>`; the
|
|
400
|
-
position is read or observed without re-rendering
|
|
497
|
+
position is read or observed without re-rendering, four times a second or
|
|
498
|
+
as often as `{ report: 1 / 60 }` asks (a scrubber moving with the sound). A player shown by no
|
|
401
499
|
view plays nothing, so an audio app (a playlist, a podcast) puts an
|
|
402
500
|
`<AudioPlayer player={…} />` somewhere that stays mounted: the player at
|
|
403
501
|
no size, out of the way of taps, playing on wherever the screens go.
|
|
@@ -409,6 +507,23 @@ has too (`@swiftbrowser/native`, "Now playing"). `Slider`'s `onEditingChanged` s
|
|
|
409
507
|
ends, so a scrubber bound to the position can stop following it while the
|
|
410
508
|
thumb is held.
|
|
411
509
|
|
|
510
|
+
`useAudioRecorder()` records from the microphone as `AVAudioRecorder` does:
|
|
511
|
+
`record()` asks for the microphone and starts, `pause()`, `resume()`,
|
|
512
|
+
`stop()` (resolving with the recording's `url` and `duration`) and
|
|
513
|
+
`cancel()`; while recording, `time` counts the seconds and `levels` holds
|
|
514
|
+
the input's level at each tick (0…1, twenty a second), for a waveform. A
|
|
515
|
+
refused microphone sets `error` (`permission: …`). A recording is a file in
|
|
516
|
+
the app's `Documents/Recordings` (`fileManager`, below), on both sides: the
|
|
517
|
+
page records with `MediaRecorder` into its IndexedDB home, the native app
|
|
518
|
+
AAC with `AVAudioRecorder` into its sandbox, and `discard(url)` deletes the
|
|
519
|
+
file. The URL plays with `useAVPlayer`. `trim(url, keep)` makes a new
|
|
520
|
+
recording out of the parts of one in `keep` (`[{ start, end }]` in seconds,
|
|
521
|
+
in order: one range trims to it, two around a range delete it), resolving
|
|
522
|
+
with the new recording and leaving the original for the app to discard; the
|
|
523
|
+
page cuts with the Web Audio API and writes WAV, the native app exports AAC
|
|
524
|
+
with `AVAssetExportSession`. (A page that keeps no files answers `data:`
|
|
525
|
+
URLs instead.)
|
|
526
|
+
|
|
412
527
|
An image is inert, as a SwiftUI image is. `<AsyncImage url={…}
|
|
413
528
|
webInteractions />` gives a remote picture the browser's own image
|
|
414
529
|
interactions back: a long press's Save / Share / Copy on iOS, dragging it
|
|
@@ -443,6 +558,27 @@ chooser calls `onCancellation`:
|
|
|
443
558
|
onCompletion: ([file]) => upload(file) }} />
|
|
444
559
|
```
|
|
445
560
|
|
|
561
|
+
### Gestures
|
|
562
|
+
|
|
563
|
+
`onTapGesture`, `dragGesture`, `magnifyGesture`, `rotateGesture` and
|
|
564
|
+
`onLongPressGesture` are the gestures as modifiers, and they compose as
|
|
565
|
+
SwiftUI's do: the innermost gesture whose motion matches wins, so a draggable
|
|
566
|
+
item inside a tappable canvas keeps its drag. `simultaneousGesture={{
|
|
567
|
+
dragGesture: { … } }}` runs a gesture beside the others instead (it begins
|
|
568
|
+
when its own `minimumDistance` is met and ends when the pointer lifts,
|
|
569
|
+
whoever owns the pointer), and `highPriorityGesture={{ … }}` asks one before
|
|
570
|
+
the gestures of the views inside it; both take the gesture as the modifier
|
|
571
|
+
it would otherwise be. A drag's `location` and `startLocation` are in the
|
|
572
|
+
view's own space unless the gesture names another: `coordinateSpace:
|
|
573
|
+
'global'`, or `{ named: 'canvas' }` for a space an ancestor set with
|
|
574
|
+
`coordinateSpace="canvas"`, which is what moves an item under the finger
|
|
575
|
+
without the item's own space moving along. `onTapGesture={{ count: 2, action
|
|
576
|
+
}}` is a double tap; with a single tap on the same view (in `modifiers`, so
|
|
577
|
+
the double tap is inside), the single tap waits for the double-tap interval
|
|
578
|
+
to pass, as on iOS. `contentShape="rectangle"` makes the whole frame take
|
|
579
|
+
taps and gestures, which, as in SwiftUI, only what a view draws does
|
|
580
|
+
otherwise: a 44 pt box around a 14 pt handle, a clear slide.
|
|
581
|
+
|
|
446
582
|
### Mouse, keyboard and effects
|
|
447
583
|
|
|
448
584
|
A web app may have a mouse and a keyboard, so the modifiers SwiftUI keeps
|
|
@@ -465,10 +601,97 @@ countsDown } }` fixes the direction); the whole text rolls, not each digit.
|
|
|
465
601
|
`opacity` and `interpolate` crossfade. `sensoryFeedback` vibrates where the
|
|
466
602
|
device can.
|
|
467
603
|
|
|
604
|
+
### Undo
|
|
605
|
+
|
|
606
|
+
`useUndoManager()` is `@Environment(\.undoManager)`: the window's manager,
|
|
607
|
+
with `NSUndoManager`'s rules. After a change, `registerUndo(name, undo)`
|
|
608
|
+
registers what takes it back; run by `undo()`, the closure registers its own
|
|
609
|
+
inverse the same way, which lands on the redo stack because the manager is
|
|
610
|
+
undoing. A new registration drops what could be redone. `canUndo`,
|
|
611
|
+
`canRedo`, `undoActionName` and `redoActionName` re-render the view as they
|
|
612
|
+
change, so a toolbar's buttons follow the stack. Registrations in one event
|
|
613
|
+
undo as one (`groupsByEvent`); `beginGrouping(name)`/`endGrouping()`, or
|
|
614
|
+
`group(name, () => …)`, make several events one step; a registration with a
|
|
615
|
+
`key` joins the group before it when it has the same key, so a run of
|
|
616
|
+
keystrokes into one field (`key: \`text:${id}\``) undoes at once, until an
|
|
617
|
+
undo or a change with another key. `removeAllActions(target)` forgets what a
|
|
618
|
+
`target` registered (a document that closed); `levelsOfUndo` caps the stack.
|
|
619
|
+
|
|
620
|
+
```tsx
|
|
621
|
+
const undo = useUndoManager();
|
|
622
|
+
const rename = (title: string) => {
|
|
623
|
+
const before = doc.title;
|
|
624
|
+
undo.registerUndo('Rename', () => rename(before)); // run as an undo, this registers the redo
|
|
625
|
+
setDoc({ ...doc, title });
|
|
626
|
+
};
|
|
627
|
+
<Button title="Undo" action={() => undo.undo()} disabled={!undo.canUndo} />
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
The stack lives in JavaScript, since its closures do and `canUndo` must
|
|
631
|
+
change in the pass that registered them, and the system is told what it
|
|
632
|
+
can do and sends its undo and redo to it: on a page, ⌘Z and ⇧⌘Z (Ctrl-Z,
|
|
633
|
+
Ctrl-Shift-Z and Ctrl-Y off Apple platforms) while no text input has the
|
|
634
|
+
focus (a focused field keeps the browser's own undo of its text); in a
|
|
635
|
+
SwiftBrowser Native app, the window's `UndoManager` mirrors the stack, so
|
|
636
|
+
the shake, the three-finger swipe, a keyboard's ⌘Z and the Edit menu reach
|
|
637
|
+
it. `UndoManagerContext` gives an island a manager of its own.
|
|
638
|
+
|
|
639
|
+
### App storage
|
|
640
|
+
|
|
641
|
+
`useAppStorage(key, default)` is `@AppStorage`: a value kept under a key
|
|
642
|
+
in the app's storage as JSON (`localStorage`: the browser's on a page, the
|
|
643
|
+
device's in a SwiftBrowser Native app), read back at the next launch, and
|
|
644
|
+
shared by every view that reads the key, so a write through any of them
|
|
645
|
+
re-renders them all. It returns the value and a setter with `useState`'s
|
|
646
|
+
shape; the default (a value, or a function making one) stands in while
|
|
647
|
+
nothing is stored, and setting `undefined` removes the key. `decode`
|
|
648
|
+
migrates what an older version saved; `onError` hears of a write the
|
|
649
|
+
storage refused (a page's storage is full), which the app still keeps for
|
|
650
|
+
the session; `store` swaps the storage for anything with `localStorage`'s
|
|
651
|
+
shape.
|
|
652
|
+
|
|
653
|
+
```tsx
|
|
654
|
+
const [settings, setSettings] = useAppStorage('settings', { theme: 'system', size: 1 });
|
|
655
|
+
<Picker title="Theme" selection={settings.theme} onChange={(theme) => setSettings({ ...settings, theme })}>…</Picker>
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### Files
|
|
659
|
+
|
|
660
|
+
`fileManager` is `FileManager.default`, with `URL.documentsDirectory` and
|
|
661
|
+
its siblings: the app's own files, kept across launches. On a page they
|
|
662
|
+
live in the app's IndexedDB database (the same iOS-like home a Swift app
|
|
663
|
+
has there: `/data/Documents`, `/data/Library/Application Support`,
|
|
664
|
+
`/data/Library/Caches`, and `/tmp` for the session); in a SwiftBrowser
|
|
665
|
+
Native app they are the app's sandbox. Paths are `file:` URLs, made with
|
|
666
|
+
`url(directory, path)`. The calls are Swift's, asynchronous on both sides
|
|
667
|
+
since the page's storage is: `contents`, `text`, `write` (bytes, a Blob or
|
|
668
|
+
text; the directory must exist), `contentsOfDirectory`, `createDirectory`
|
|
669
|
+
(with its parents), `fileExists`, `attributesOfItem`, `moveItem`,
|
|
670
|
+
`copyItem`, `removeItem`. Failures say what went wrong first: `notFound:`,
|
|
671
|
+
`exists:`, `isDirectory:`, `notDirectory:`, `unsupported:` (a URL outside
|
|
672
|
+
the app's directories, or a page that keeps no files: `available`).
|
|
673
|
+
|
|
674
|
+
```tsx
|
|
675
|
+
const recordings = fileManager.url(fileManager.documentsDirectory, 'Recordings');
|
|
676
|
+
await fileManager.createDirectory(recordings);
|
|
677
|
+
const memo = fileManager.url(recordings, `${id}.m4a`);
|
|
678
|
+
await fileManager.write(memo, blob);
|
|
679
|
+
<AudioPlayer url={memo} /> // a VideoPlayer, AudioPlayer or Image loads a file: URL itself
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
A `VideoPlayer`, `AudioPlayer` or `Image` given a `file:` URL loads the file
|
|
683
|
+
(on a page through an object URL of its bytes, which `loadURL` also
|
|
684
|
+
answers; natively the URL as it is). `mount`'s `app` option names the
|
|
685
|
+
database on a page (`sb-files:<app>`).
|
|
686
|
+
|
|
468
687
|
### Animation
|
|
469
688
|
|
|
470
689
|
`withAnimation(animation, () => …)` animates the pass a state change makes,
|
|
471
690
|
and the `animation` modifier a view's changes when a value changes.
|
|
691
|
+
`withAnimation(animation, body, completion)` runs the completion once the
|
|
692
|
+
pass has finished animating (`{ completionCriteria: 'removed' }` before
|
|
693
|
+
the body waits for every effect to be gone); a body that renders nothing
|
|
694
|
+
runs it at once.
|
|
472
695
|
`PhaseAnimator` moves its content through phases, each change animated;
|
|
473
696
|
`KeyframeAnimator` plays tracks of keyframes (`linear`, `cubic`, `spring`,
|
|
474
697
|
`move`) and renders its content with the values each frame.
|
|
@@ -480,7 +703,8 @@ with their values in `x` and `y`: categories, numbers or dates. The
|
|
|
480
703
|
component builds the scales and ticks the Swift Charts module builds
|
|
481
704
|
(bands, nice numeric domains that include zero, calendar strides), groups
|
|
482
705
|
lines and areas by series, colors `foregroundStyleBy` series from the chart
|
|
483
|
-
palette with a legend,
|
|
706
|
+
palette with a legend, puts bars of one `x` side by side by their
|
|
707
|
+
`positionBy` (`.position(by:)`), and places `annotation`s. `yAxisFormat` writes
|
|
484
708
|
the numeric axis's labels (`.chartYAxis { AxisMarks(format:) }`), as in
|
|
485
709
|
`yAxisFormat={(v) => `$${v}`}`.
|
|
486
710
|
|
|
@@ -596,11 +820,14 @@ read with a hook). Props, callbacks and external stores need no bridging.
|
|
|
596
820
|
## What is covered
|
|
597
821
|
|
|
598
822
|
- **Layout**: stacks, `LazyVStack`, `LazyHStack`, `Spacer`, `Divider`,
|
|
599
|
-
`ScrollView` (with `contentMargins
|
|
823
|
+
`ScrollView` (with `contentMargins`, scroll edge effects,
|
|
824
|
+
`onScrollGeometryChange` and a `scrollPosition` by id or point),
|
|
600
825
|
`ScrollViewReader`, `Group`, `ForEach` (with `onDelete` and `onMove`),
|
|
601
|
-
`Grid`, `LazyVGrid`, `LazyHGrid
|
|
826
|
+
`Grid`, `LazyVGrid`, `LazyHGrid` (with `pinnedViews`), `ViewThatFits`,
|
|
827
|
+
`GeometryReader` (its
|
|
602
828
|
size, its frame on screen or in a `coordinateSpace` named on an ancestor,
|
|
603
|
-
the visible bounds of its scroll view, and its safe-area insets),
|
|
829
|
+
the visible bounds of its scroll view, and its safe-area insets),
|
|
830
|
+
`onGeometryChange`, custom
|
|
604
831
|
`Layout` (with `layoutValue`s, priorities, view spacing, explicit
|
|
605
832
|
alignment guides and a cache), `containerRelativeFrame`, `safeAreaInset`.
|
|
606
833
|
- **Content**: `Text` (with `Text + Text` runs, inline images, italic,
|
|
@@ -621,11 +848,13 @@ read with a hook). Props, callbacks and external stores need no bridging.
|
|
|
621
848
|
links, a `path` binding, nested stacks flattened), `NavigationSplitView`,
|
|
622
849
|
`TabView`/`Tab`, toolbars, search fields, sheets with detents, popovers
|
|
623
850
|
(as sheets), alerts and dialogs, inspectors.
|
|
624
|
-
- **Interaction**: `onTapGesture
|
|
625
|
-
`
|
|
626
|
-
`
|
|
851
|
+
- **Interaction**: `onTapGesture` (with `count`), `dragGesture` (with
|
|
852
|
+
`coordinateSpace`), `magnifyGesture`, `rotateGesture`,
|
|
853
|
+
`onLongPressGesture`, `simultaneousGesture`, `highPriorityGesture`,
|
|
854
|
+
`contentShape`, `contextMenu`, `draggable` and `dropDestination` (in the app, and text or
|
|
627
855
|
files from outside the page), `onHover`, `help`, `pointerStyle`,
|
|
628
|
-
`keyboardShortcut`, `onKeyPress`, `sensoryFeedback
|
|
856
|
+
`keyboardShortcut`, `onKeyPress`, `sensoryFeedback`, `useUndoManager`,
|
|
857
|
+
`useAppStorage`, `fileManager`.
|
|
629
858
|
- **Animation and effects**: `withAnimation`, `animation`, transitions,
|
|
630
859
|
`PhaseAnimator`, `KeyframeAnimator`, `matchedGeometryEffect`,
|
|
631
860
|
`symbolEffect`, `contentTransition`, `visualEffect`, `scrollTransition`,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useAppStorage(key, default)`: SwiftUI's `@AppStorage`. A value kept under a
|
|
3
|
+
* key in the app's storage (`localStorage`: the browser's on a page, the
|
|
4
|
+
* device's in a SwiftBrowser Native app) as JSON, read back at the next
|
|
5
|
+
* launch, and shared by every view that reads the key: a write through any of
|
|
6
|
+
* them re-renders them all. The default stands in while nothing is stored,
|
|
7
|
+
* and setting `undefined` removes the key, as assigning nil does.
|
|
8
|
+
*
|
|
9
|
+
* The hook is the key's writer: it keeps what it read and re-reads storage
|
|
10
|
+
* only when another tab writes the key (the window's `storage` event). A
|
|
11
|
+
* write that storage refuses (a page's storage is full) still takes effect
|
|
12
|
+
* in the app, for the session, and is reported to `onError`.
|
|
13
|
+
*/
|
|
14
|
+
/** Where the values go: `localStorage`'s shape. */
|
|
15
|
+
export interface AppStorageStore {
|
|
16
|
+
getItem(key: string): string | null;
|
|
17
|
+
setItem(key: string, value: string): void;
|
|
18
|
+
removeItem(key: string): void;
|
|
19
|
+
}
|
|
20
|
+
export interface AppStorageOptions<T> {
|
|
21
|
+
/** The storage; `localStorage` by default (memory where a page has none, or refuses it). */
|
|
22
|
+
store?: AppStorageStore;
|
|
23
|
+
/**
|
|
24
|
+
* What a stored value becomes on load: a migration from what an older
|
|
25
|
+
* version of the app saved. A throw falls back to the default. The readers
|
|
26
|
+
* of a key share one; the last to mount decides.
|
|
27
|
+
*/
|
|
28
|
+
decode?: (stored: unknown) => T;
|
|
29
|
+
/** A write storage refused (out of room). The value is kept in memory regardless. */
|
|
30
|
+
onError?: (error: unknown) => void;
|
|
31
|
+
}
|
|
32
|
+
/** The new value, or a function of the current one, as `useState`'s setter takes. */
|
|
33
|
+
export type AppStorageSetter<T> = (value: T | ((current: T) => T)) => void;
|
|
34
|
+
/** The store `useAppStorage` uses when none is given. */
|
|
35
|
+
export declare const appStorage: AppStorageStore;
|
|
36
|
+
/**
|
|
37
|
+
* The value stored under `key`, or `defaultValue` (a value, or a function
|
|
38
|
+
* making one, as `useState` takes) while there is none, and its setter. The
|
|
39
|
+
* value must survive JSON: plain objects, arrays, strings, numbers,
|
|
40
|
+
* booleans, null.
|
|
41
|
+
*/
|
|
42
|
+
export declare function useAppStorage<T>(key: string, defaultValue: T | (() => T), options?: AppStorageOptions<T>): [T, AppStorageSetter<T>];
|
|
43
|
+
//# sourceMappingURL=appStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appStorage.d.ts","sourceRoot":"","sources":["../src/appStorage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,4FAA4F;IAC5F,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,CAAC,CAAC;IAChC,qFAAqF;IACrF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED,qFAAqF;AACrF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC;AA0F3E,yDAAyD;AACzD,eAAO,MAAM,UAAU,EAAE,eAA8C,CAAC;AA2BxE;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,GAAE,iBAAiB,CAAC,CAAC,CAAM,GAAG,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAoCvI"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attributed text, SwiftUI's `AttributedString` as the rich `TextEditor`
|
|
3
|
+
* edits it (`TextEditor(text: $attributed, selection: $selection)`, iOS 26):
|
|
4
|
+
* runs of text, each with its own weight, slant, lines and color. Plain
|
|
5
|
+
* objects, so the text saves as JSON; the functions here are what an edit
|
|
6
|
+
* in the editor does to it.
|
|
7
|
+
*/
|
|
8
|
+
import type { FontDesign, FontWeight, TextRun } from '@swiftbrowser/web/src/protocol';
|
|
9
|
+
import { type ColorValue } from './values';
|
|
10
|
+
/** A run's own font over the editor's (`AttributedString`'s `font`): `Font.system(size:weight:design:)`'s parts, each optional. */
|
|
11
|
+
export interface RunFont {
|
|
12
|
+
size?: number;
|
|
13
|
+
weight?: FontWeight;
|
|
14
|
+
design?: FontDesign;
|
|
15
|
+
}
|
|
16
|
+
export interface AttributedRun {
|
|
17
|
+
text: string;
|
|
18
|
+
bold?: boolean;
|
|
19
|
+
italic?: boolean;
|
|
20
|
+
underline?: boolean;
|
|
21
|
+
strikethrough?: boolean;
|
|
22
|
+
/** The run's color, over the editor's foreground. */
|
|
23
|
+
foregroundColor?: ColorValue;
|
|
24
|
+
/** A color behind the run's glyphs (`backgroundColor`): a find highlight, say. */
|
|
25
|
+
backgroundColor?: ColorValue;
|
|
26
|
+
/** The run's size, weight or design over the editor's font (`bold` is the weight `bold` when the font names none). */
|
|
27
|
+
font?: RunFont;
|
|
28
|
+
}
|
|
29
|
+
export type AttributedText = readonly AttributedRun[];
|
|
30
|
+
/** A range of an editor's text in UTF-16 offsets (JavaScript's string indices); `start === end` is the caret. */
|
|
31
|
+
export interface TextSelection {
|
|
32
|
+
start: number;
|
|
33
|
+
end: number;
|
|
34
|
+
}
|
|
35
|
+
/** A run's attributes without its text. */
|
|
36
|
+
export type TextAttributes = Omit<AttributedRun, 'text'>;
|
|
37
|
+
/** The same text with no empty runs and no two alike side by side. */
|
|
38
|
+
declare function normalized(text: AttributedText): AttributedText;
|
|
39
|
+
/** The runs between two offsets, cut where they fall. */
|
|
40
|
+
declare function slice(text: AttributedText, start: number, end?: number): AttributedText;
|
|
41
|
+
/** The attributes of the character at `offset` (the last one's past the end; none in an empty text). */
|
|
42
|
+
declare function attributesAt(text: AttributedText, offset: number): TextAttributes;
|
|
43
|
+
/**
|
|
44
|
+
* `text` changed to read `next`, as a keystroke, a paste or a deletion in
|
|
45
|
+
* the editor changes it: the start and end that did not change keep their
|
|
46
|
+
* runs, and what was typed takes the attributes of what it replaced, else
|
|
47
|
+
* of the character before it (the first character's, typed at the start).
|
|
48
|
+
*/
|
|
49
|
+
declare function replacingCharacters(text: AttributedText, next: string): AttributedText;
|
|
50
|
+
/** `text` with `change` over the characters between `start` and `end`. */
|
|
51
|
+
declare function settingAttributes(text: AttributedText, start: number, end: number, change: TextAttributes): AttributedText;
|
|
52
|
+
/** Whether every character between `start` and `end` has `key` on (false for an empty range). */
|
|
53
|
+
declare function every(text: AttributedText, start: number, end: number, key: 'bold' | 'italic' | 'underline' | 'strikethrough'): boolean;
|
|
54
|
+
/** The functions over attributed text, as `AttributedString`'s methods. */
|
|
55
|
+
export declare const AttributedText: {
|
|
56
|
+
plain: (text: AttributedText) => string;
|
|
57
|
+
normalized: typeof normalized;
|
|
58
|
+
slice: typeof slice;
|
|
59
|
+
attributesAt: typeof attributesAt;
|
|
60
|
+
replacingCharacters: typeof replacingCharacters;
|
|
61
|
+
settingAttributes: typeof settingAttributes;
|
|
62
|
+
every: typeof every;
|
|
63
|
+
/** A text of one run. */
|
|
64
|
+
of: (text: string, attributes?: TextAttributes) => AttributedText;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* The text the protocol's runs stand for (a `runs` event: the native
|
|
68
|
+
* editor's own Bold, Italic or Underline over the text), `protocolRuns`
|
|
69
|
+
* read back: a bold weight is `bold`, a line is on, a color is the run's,
|
|
70
|
+
* and a run's size, design and other weight are its `font`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function fromProtocolRuns(runs: readonly TextRun[]): AttributedText;
|
|
73
|
+
/** The protocol's runs for a text: a run's font over the editor's (bold is the weight `bold`), its lines and colors. */
|
|
74
|
+
export declare function protocolRuns(text: AttributedText): TextRun[];
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=attributedText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attributedText.d.ts","sourceRoot":"","sources":["../src/attributedText.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AACtF,OAAO,EAAS,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAElD,mIAAmI;AACnI,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qDAAqD;IACrD,eAAe,CAAC,EAAE,UAAU,CAAC;IAC7B,kFAAkF;IAClF,eAAe,CAAC,EAAE,UAAU,CAAC;IAC7B,sHAAsH;IACtH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,MAAM,cAAc,GAAG,SAAS,aAAa,EAAE,CAAC;AAEtD,iHAAiH;AACjH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,2CAA2C;AAC3C,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AAiCzD,sEAAsE;AACtE,iBAAS,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc,CASxD;AAED,yDAAyD;AACzD,iBAAS,KAAK,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAW,GAAG,cAAc,CAUlF;AAED,wGAAwG;AACxG,iBAAS,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAO1E;AAED;;;;;GAKG;AACH,iBAAS,mBAAmB,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,CAY/E;AAED,0EAA0E;AAC1E,iBAAS,iBAAiB,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,cAAc,CAGnH;AAED,iGAAiG;AACjG,iBAAS,KAAK,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,eAAe,GAAG,OAAO,CAGhI;AAED,2EAA2E;AAC3E,eAAO,MAAM,cAAc;kBAnGN,cAAc,KAAG,MAAM;;;;;;;IA2G1C,yBAAyB;eACd,MAAM,eAAc,cAAc,KAAQ,cAAc;CACpE,CAAC;AAKF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,cAAc,CAqBzE;AAED,wHAAwH;AACxH,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,EAAE,CAa5D"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useAudioRecorder()`: the microphone, as an iOS app records with
|
|
3
|
+
* `AVAudioRecorder`. `record()` asks for the microphone and starts; while it
|
|
4
|
+
* records the hook meters the input (`levels`, one 0…1 level per tick, for a
|
|
5
|
+
* waveform) and counts `time`; `stop()` resolves with the recording's URL
|
|
6
|
+
* (a `data:` URL on a page, a file natively) to play with `useAVPlayer`;
|
|
7
|
+
* `trim()` makes a new recording out of parts of one. The page records with
|
|
8
|
+
* `MediaRecorder` and cuts with the Web Audio API (Web/src/audioRecorder.ts);
|
|
9
|
+
* the native app answers with `AVAudioRecorder` and `AVAssetExportSession`
|
|
10
|
+
* (AudioRecorderBridge.swift).
|
|
11
|
+
*/
|
|
12
|
+
import type { AudioRange, AudioRecording } from '@swiftbrowser/web/src/audioRecorder';
|
|
13
|
+
export type { AudioRange, AudioRecording };
|
|
14
|
+
export type AudioRecorderStatus = 'idle' | 'starting' | 'recording' | 'paused' | 'stopping';
|
|
15
|
+
export interface AudioRecorder {
|
|
16
|
+
/** Whether this page or app can record at all. */
|
|
17
|
+
available: boolean;
|
|
18
|
+
status: AudioRecorderStatus;
|
|
19
|
+
/** Seconds recorded so far (it holds while paused). */
|
|
20
|
+
time: number;
|
|
21
|
+
/** The input's level at each tick of this recording (0 silent … 1 loud), oldest first. */
|
|
22
|
+
levels: readonly number[];
|
|
23
|
+
/** Why the last call failed (`permission: …` when the microphone was refused), or null. */
|
|
24
|
+
error: string | null;
|
|
25
|
+
record(): Promise<void>;
|
|
26
|
+
pause(): Promise<void>;
|
|
27
|
+
resume(): Promise<void>;
|
|
28
|
+
/** Ends the recording and resolves with it, or null when there was none. */
|
|
29
|
+
stop(): Promise<AudioRecording | null>;
|
|
30
|
+
/** Ends the recording and keeps nothing. */
|
|
31
|
+
cancel(): Promise<void>;
|
|
32
|
+
/** Deletes a recording `stop` made before (its file, natively), when the app deletes it. */
|
|
33
|
+
discard(url: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* A new recording made of the parts of `url` in `keep` (seconds, in
|
|
36
|
+
* order, concatenated): `[{ start, end }]` trims to that range, two parts
|
|
37
|
+
* around a range delete it. Resolves with the new recording (a `data:` URL
|
|
38
|
+
* on a page, a new file natively); the original is left as it was (the app
|
|
39
|
+
* discards it when it no longer wants it). Rejects with `unsupported: …`
|
|
40
|
+
* where the host cannot cut audio.
|
|
41
|
+
*/
|
|
42
|
+
trim(url: string, keep: readonly {
|
|
43
|
+
start: number;
|
|
44
|
+
end: number;
|
|
45
|
+
}[]): Promise<AudioRecording>;
|
|
46
|
+
}
|
|
47
|
+
export interface AudioRecorderOptions {
|
|
48
|
+
/** Milliseconds between meter readings (50: twenty a second). */
|
|
49
|
+
meterInterval?: number;
|
|
50
|
+
}
|
|
51
|
+
export declare function useAudioRecorder(options?: AudioRecorderOptions): AudioRecorder;
|
|
52
|
+
//# sourceMappingURL=audioRecorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioRecorder.d.ts","sourceRoot":"","sources":["../src/audioRecorder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AAItF,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC;AAE3C,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE5F,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,2FAA2F;IAC3F,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,4EAA4E;IAC5E,IAAI,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACvC,4CAA4C;IAC5C,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,4FAA4F;IAC5F,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC;;;;;;;OAOG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC7F;AAED,MAAM,WAAW,oBAAoB;IACnC,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CAuHlF"}
|
package/dist/bridge.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;AAahF;;;;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"}
|
|
@@ -32,6 +32,11 @@ export interface BarMarkProps extends MarkCommon {
|
|
|
32
32
|
cornerRadius?: number;
|
|
33
33
|
/** `width: .fixed(points)` */
|
|
34
34
|
width?: number;
|
|
35
|
+
/**
|
|
36
|
+
* `.position(by:)`: bars at the same `x` side by side, one slot of the
|
|
37
|
+
* band per value (in first-seen order across the chart's bars).
|
|
38
|
+
*/
|
|
39
|
+
positionBy?: string;
|
|
35
40
|
}
|
|
36
41
|
export interface LineMarkProps extends MarkCommon {
|
|
37
42
|
x: ChartValue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"charts.d.ts","sourceRoot":"","sources":["../../src/components/charts.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAEV,kBAAkB,EAGlB,WAAW,EACX,cAAc,EAEf,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEpE,OAAO,EAA8B,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAGxD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IACjE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,SAAS,CAAC;CACpB;AAED,UAAU,UAAU;IAClB,eAAe,CAAC,EAAE,UAAU,CAAC;IAC7B,6FAA6F;IAC7F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,CAAC,EAAE,UAAU,CAAC;IACd,CAAC,EAAE,UAAU,CAAC;IACd,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"charts.d.ts","sourceRoot":"","sources":["../../src/components/charts.tsx"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ import type { GaugeStyle, HStackAlignment, VStackAlignment } from '@swiftbrowser
|
|
|
7
7
|
import { type ReactNode } from 'react';
|
|
8
8
|
import { type BindingValue } from '../binding';
|
|
9
9
|
import { type ViewModifiers } from '../modifiers';
|
|
10
|
+
import { type PinnedViews } from './stacks';
|
|
10
11
|
export interface GaugeProps extends ViewModifiers {
|
|
11
12
|
value: number;
|
|
12
13
|
/** `in: 0...1` */
|
|
@@ -30,6 +31,8 @@ export interface LazyVStackProps extends ViewModifiers {
|
|
|
30
31
|
spacing?: number | null;
|
|
31
32
|
/** `.scrollTargetLayout()`: the children are the scroll view's snap points. */
|
|
32
33
|
scrollTargetLayout?: boolean;
|
|
34
|
+
/** `pinnedViews: [.sectionHeaders]`: each `Section`'s header stays at the top while its rows scroll by. */
|
|
35
|
+
pinnedViews?: PinnedViews;
|
|
33
36
|
children?: ReactNode;
|
|
34
37
|
}
|
|
35
38
|
/** `LazyVStack`: a `VStack` whose long `ForEach`es build their rows as they scroll into view. */
|
|
@@ -38,6 +41,8 @@ export interface LazyHStackProps extends ViewModifiers {
|
|
|
38
41
|
alignment?: HStackAlignment;
|
|
39
42
|
spacing?: number | null;
|
|
40
43
|
scrollTargetLayout?: boolean;
|
|
44
|
+
/** `pinnedViews: [.sectionHeaders]`: each `Section`'s header stays at the leading edge while its cells scroll by. */
|
|
45
|
+
pinnedViews?: PinnedViews;
|
|
41
46
|
children?: ReactNode;
|
|
42
47
|
}
|
|
43
48
|
export declare function LazyHStack(props: LazyHStackProps): ReactNode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"containers.d.ts","sourceRoot":"","sources":["../../src/components/containers.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACnG,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE3D,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,EAAsC,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"containers.d.ts","sourceRoot":"","sources":["../../src/components/containers.tsx"],"names":[],"mappings":"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"}
|