@swiftbrowser/react 0.17.8 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +243 -14
  2. package/dist/appStorage.d.ts +43 -0
  3. package/dist/appStorage.d.ts.map +1 -0
  4. package/dist/attributedText.d.ts +76 -0
  5. package/dist/attributedText.d.ts.map +1 -0
  6. package/dist/audioRecorder.d.ts +52 -0
  7. package/dist/audioRecorder.d.ts.map +1 -0
  8. package/dist/bridge.d.ts.map +1 -1
  9. package/dist/components/charts.d.ts +5 -0
  10. package/dist/components/charts.d.ts.map +1 -1
  11. package/dist/components/containers.d.ts +5 -0
  12. package/dist/components/containers.d.ts.map +1 -1
  13. package/dist/components/controls.d.ts +84 -8
  14. package/dist/components/controls.d.ts.map +1 -1
  15. package/dist/components/geometry.d.ts +18 -0
  16. package/dist/components/geometry.d.ts.map +1 -1
  17. package/dist/components/images.d.ts.map +1 -1
  18. package/dist/components/lists.d.ts.map +1 -1
  19. package/dist/components/media.d.ts +18 -10
  20. package/dist/components/media.d.ts.map +1 -1
  21. package/dist/components/stacks.d.ts +70 -4
  22. package/dist/components/stacks.d.ts.map +1 -1
  23. package/dist/environment.d.ts +2 -1
  24. package/dist/environment.d.ts.map +1 -1
  25. package/dist/fileManager.d.ts +58 -0
  26. package/dist/fileManager.d.ts.map +1 -0
  27. package/dist/host/root.d.ts +16 -2
  28. package/dist/host/root.d.ts.map +1 -1
  29. package/dist/host/tree.d.ts +9 -0
  30. package/dist/host/tree.d.ts.map +1 -1
  31. package/dist/index.d.ts +9 -3
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1200 -369
  34. package/dist/index.js.map +1 -1
  35. package/dist/measureText.d.ts +32 -0
  36. package/dist/measureText.d.ts.map +1 -0
  37. package/dist/modifiers.d.ts +81 -5
  38. package/dist/modifiers.d.ts.map +1 -1
  39. package/dist/mount.d.ts +5 -0
  40. package/dist/mount.d.ts.map +1 -1
  41. package/dist/presentation.d.ts +5 -1
  42. package/dist/presentation.d.ts.map +1 -1
  43. package/dist/scrollReader.d.ts +34 -8
  44. package/dist/scrollReader.d.ts.map +1 -1
  45. package/dist/undo.d.ts +108 -0
  46. package/dist/undo.d.ts.map +1 -0
  47. package/dist/visualEffect.d.ts +10 -14
  48. package/dist/visualEffect.d.ts.map +1 -1
  49. 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). By default the effect follows the scrolling, drawn partway while the
360
- view is partly out; `configuration: 'animated'` switches phases with an
361
- animation instead. `containerRelativeFrame` sizes a view against the scroll
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. A player shown by no
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, and places `annotation`s. `yAxisFormat` writes
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` and scroll edge effects),
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`, `ViewThatFits`, `GeometryReader` (its
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), custom
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`, `dragGesture`, `magnifyGesture`,
625
- `rotateGesture`, `onLongPressGesture`,
626
- `contextMenu`, `draggable` and `dropDestination` (in the app, and text or
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"}
@@ -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;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"}
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":"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"}
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":"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"}
1
+ {"version":3,"file":"containers.d.ts","sourceRoot":"","sources":["../../src/components/containers.tsx"],"names":[],"mappings":"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"}