@swiftbrowser/web 0.1.0 → 0.3.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.
Files changed (99) hide show
  1. package/README.md +141 -63
  2. package/app/index.html +21 -10
  3. package/node/dist/node/apps.d.ts +16 -0
  4. package/node/dist/node/apps.d.ts.map +1 -1
  5. package/node/dist/node/apps.js +9 -0
  6. package/node/dist/node/apps.js.map +1 -1
  7. package/node/dist/node/config.d.ts +14 -1
  8. package/node/dist/node/config.d.ts.map +1 -1
  9. package/node/dist/node/config.js +20 -3
  10. package/node/dist/node/config.js.map +1 -1
  11. package/node/dist/node/examples.d.ts +2 -1
  12. package/node/dist/node/examples.d.ts.map +1 -1
  13. package/node/dist/node/examples.js +12 -2
  14. package/node/dist/node/examples.js.map +1 -1
  15. package/node/dist/plugins/app-manifests.d.ts +4 -1
  16. package/node/dist/plugins/app-manifests.d.ts.map +1 -1
  17. package/node/dist/plugins/app-manifests.js +3 -2
  18. package/node/dist/plugins/app-manifests.js.map +1 -1
  19. package/node/dist/plugins/asset-catalogs.d.ts +15 -3
  20. package/node/dist/plugins/asset-catalogs.d.ts.map +1 -1
  21. package/node/dist/plugins/asset-catalogs.js +111 -13
  22. package/node/dist/plugins/asset-catalogs.js.map +1 -1
  23. package/node/dist/plugins/included-sites.d.ts +26 -0
  24. package/node/dist/plugins/included-sites.d.ts.map +1 -0
  25. package/node/dist/plugins/included-sites.js +70 -0
  26. package/node/dist/plugins/included-sites.js.map +1 -0
  27. package/node/dist/plugins/pdfImages.d.ts +27 -0
  28. package/node/dist/plugins/pdfImages.d.ts.map +1 -0
  29. package/node/dist/plugins/pdfImages.js +145 -0
  30. package/node/dist/plugins/pdfImages.js.map +1 -0
  31. package/node/dist/plugins/swift-wasm.d.ts +19 -1
  32. package/node/dist/plugins/swift-wasm.d.ts.map +1 -1
  33. package/node/dist/plugins/swift-wasm.js +45 -7
  34. package/node/dist/plugins/swift-wasm.js.map +1 -1
  35. package/node/dist/plugins/symbolTemplates.d.ts +22 -0
  36. package/node/dist/plugins/symbolTemplates.d.ts.map +1 -0
  37. package/node/dist/plugins/symbolTemplates.js +83 -0
  38. package/node/dist/plugins/symbolTemplates.js.map +1 -0
  39. package/package.json +4 -2
  40. package/plugins/app-manifests.ts +3 -2
  41. package/plugins/asset-catalogs.ts +119 -14
  42. package/plugins/included-sites.ts +74 -0
  43. package/plugins/pdfImages.ts +163 -0
  44. package/plugins/swift-wasm.ts +52 -6
  45. package/plugins/symbolTemplates.ts +88 -0
  46. package/public/_headers +7 -0
  47. package/public/app/oauth-callback.html +62 -0
  48. package/src/animation.ts +235 -8
  49. package/src/appFiles.ts +201 -0
  50. package/src/base64.ts +43 -0
  51. package/src/bitmapPlan.ts +143 -0
  52. package/src/bitmapStore.ts +206 -0
  53. package/src/bitmapWorker.ts +41 -0
  54. package/src/bitmapWorkerProtocol.ts +15 -0
  55. package/src/bitmaps.ts +283 -0
  56. package/src/blockedHosts.ts +29 -0
  57. package/src/canvasDrawing.ts +361 -0
  58. package/src/chartDom.ts +2 -2
  59. package/src/chromeColor.ts +179 -0
  60. package/src/clipboard.ts +78 -0
  61. package/src/colorMultiply.ts +93 -0
  62. package/src/colorPicker.ts +44 -0
  63. package/src/corsProxy.ts +45 -0
  64. package/src/gaugeRing.ts +74 -0
  65. package/src/gif.ts +297 -0
  66. package/src/hostRequests.ts +426 -0
  67. package/src/imageOps.ts +592 -0
  68. package/src/interaction.ts +68 -3
  69. package/src/landing.ts +2 -2
  70. package/src/largeTitle.ts +79 -0
  71. package/src/layout/chart.ts +9 -3
  72. package/src/layout/engine.ts +681 -174
  73. package/src/layout/index.ts +3 -1
  74. package/src/layout/text.ts +217 -1
  75. package/src/layout/types.ts +31 -0
  76. package/src/layout/typography.ts +24 -7
  77. package/src/layout/video.ts +24 -0
  78. package/src/layoutDom.ts +66 -6
  79. package/src/main.ts +127 -4
  80. package/src/media.ts +132 -0
  81. package/src/mock.ts +4 -3
  82. package/src/notifications.ts +109 -0
  83. package/src/pathShape.ts +156 -0
  84. package/src/protocol.ts +782 -13
  85. package/src/renderer.ts +603 -44
  86. package/src/richText.ts +65 -0
  87. package/src/scanner.ts +234 -0
  88. package/src/scrollTo.ts +123 -0
  89. package/src/styles.css +521 -100
  90. package/src/styles.ts +61 -0
  91. package/src/swiftData.ts +128 -0
  92. package/src/symbols.ts +658 -28
  93. package/src/translation.ts +126 -0
  94. package/src/video.ts +299 -0
  95. package/src/vite-env.d.ts +6 -0
  96. package/src/wasm.ts +227 -32
  97. package/src/webAuth.ts +267 -0
  98. package/src/webSocket.ts +148 -0
  99. package/src/zoneinfo.ts +269 -0
package/src/protocol.ts CHANGED
@@ -26,7 +26,44 @@ export type SemanticColorName =
26
26
  | 'accent'
27
27
  | 'systemBackground'
28
28
  | 'secondarySystemBackground'
29
- | 'clear';
29
+ /** UIKit's semantic colors, by their iOS names (`Color(uiColor:)`). */
30
+ | 'tertiarySystemBackground'
31
+ | 'systemGroupedBackground'
32
+ | 'secondarySystemGroupedBackground'
33
+ | 'tertiarySystemGroupedBackground'
34
+ | 'label'
35
+ | 'secondaryLabel'
36
+ | 'tertiaryLabel'
37
+ | 'quaternaryLabel'
38
+ | 'placeholderText'
39
+ | 'separator'
40
+ | 'opaqueSeparator'
41
+ | 'systemFill'
42
+ | 'secondarySystemFill'
43
+ | 'tertiarySystemFill'
44
+ | 'quaternarySystemFill'
45
+ | 'systemGray2'
46
+ | 'systemGray3'
47
+ | 'systemGray4'
48
+ | 'systemGray5'
49
+ | 'systemGray6'
50
+ /** The system palette (`Color.red`, `UIColor.systemRed`…): `--sb-color-<name>`. */
51
+ | 'red'
52
+ | 'orange'
53
+ | 'yellow'
54
+ | 'green'
55
+ | 'mint'
56
+ | 'teal'
57
+ | 'cyan'
58
+ | 'blue'
59
+ | 'indigo'
60
+ | 'purple'
61
+ | 'pink'
62
+ | 'brown'
63
+ | 'gray'
64
+ | 'clear'
65
+ /** `TintShapeStyle` (`.tint` as a style): the nearest `.tint(_:)`, else the accent color. */
66
+ | 'tint';
30
67
 
31
68
  /**
32
69
  * Semantic color the renderer resolves for the current color scheme. `a`
@@ -103,6 +140,17 @@ export interface RadialGradientStyle extends StyleOpacity {
103
140
  radialGradient: RadialGradient;
104
141
  }
105
142
 
143
+ export interface AngularGradient {
144
+ stops: GradientStop[];
145
+ center: UnitPoint;
146
+ /** Degrees, clockwise from the positive x axis (3 o'clock); the stops span start...end. */
147
+ startAngle: number;
148
+ endAngle: number;
149
+ }
150
+ export interface AngularGradientStyle extends StyleOpacity {
151
+ angularGradient: AngularGradient;
152
+ }
153
+
106
154
  /** `Color.gradient`: a subtle vertical gradient derived from one color. */
107
155
  export interface ColorGradientStyle extends StyleOpacity {
108
156
  colorGradient: Color;
@@ -119,6 +167,7 @@ export type ShapeStyle =
119
167
  | HierarchicalStyle
120
168
  | LinearGradientStyle
121
169
  | RadialGradientStyle
170
+ | AngularGradientStyle
122
171
  | ColorGradientStyle
123
172
  | MaterialStyle;
124
173
 
@@ -275,6 +324,11 @@ export interface Style {
275
324
  background?: ShapeStyle;
276
325
  frame?: Frame;
277
326
  cornerRadius?: number;
327
+ /**
328
+ * `.background(_:)` in a container shape (`.containerShape`): the
329
+ * background's corner radius. Unlike `cornerRadius` it does not clip.
330
+ */
331
+ backgroundCornerRadius?: number;
278
332
  opacity?: number;
279
333
  /** Dims the subtree (`opacity: 0.4`) and blocks input (`pointer-events: none`). */
280
334
  disabled?: boolean;
@@ -293,6 +347,8 @@ export interface Style {
293
347
  */
294
348
  animation?: Animation;
295
349
  animationToken?: number;
350
+ /** This box is a `Label`'s icon: a list row tints it (its title keeps the row's color). */
351
+ labelIcon?: boolean;
296
352
  /** How this subtree appears and disappears inside an animated commit. */
297
353
  transition?: Transition;
298
354
  // Phase 5
@@ -364,6 +420,34 @@ export type HStackAlignment = 'top' | 'center' | 'bottom';
364
420
 
365
421
  export interface TextProps {
366
422
  text: string;
423
+ /**
424
+ * Rich text (`Text + Text`, `Text(AttributedString)`, an interpolated
425
+ * image, a Text-only modifier such as `.italic()`): the text as runs, each
426
+ * styled on its own. `text` is then the plain content.
427
+ */
428
+ runs?: TextRun[];
429
+ }
430
+ /**
431
+ * One run of a rich text: its `text`, or an inline `image` (an `image`
432
+ * element's props). Unset fields take the text's own style.
433
+ */
434
+ export interface TextRun {
435
+ text?: string;
436
+ image?: ImageProps;
437
+ /** A Font JSON object applied over the text's font (as `styled` `font`). */
438
+ font?: Record<string, unknown>;
439
+ italic?: boolean;
440
+ /** A ShapeStyle JSON object (as `styled` `foreground`). */
441
+ foreground?: unknown;
442
+ /** A line through or under the run; `color` defaults to the run's own. */
443
+ strikethrough?: { color?: unknown };
444
+ underline?: { color?: unknown };
445
+ /** Extra space after each character, in points. */
446
+ kerning?: number;
447
+ /** Points to raise the run by (negative lowers it). */
448
+ baselineOffset?: number;
449
+ /** A tap on the run sends `link` with this URL. */
450
+ link?: string;
367
451
  }
368
452
  export interface VStackProps {
369
453
  spacing: number | null;
@@ -399,6 +483,12 @@ export interface StyledProps {
399
483
  /** Same alignment names as `Frame.alignment`; later children draw on top. */
400
484
  export interface ZStackProps {
401
485
  alignment: FrameAlignment;
486
+ /**
487
+ * Report the element's laid-out size with `geometry` events, as a
488
+ * `geometry` element does (an anchor preference's wrapper; see
489
+ * docs/ops-protocol.md, "Preferences").
490
+ */
491
+ reportsSize?: boolean;
402
492
  }
403
493
  /** `systemName` is an SF Symbol name; the renderer picks the closest glyph. */
404
494
  export interface SymbolImageProps {
@@ -415,7 +505,69 @@ export interface AssetImageProps {
415
505
  name: string;
416
506
  resizable: boolean;
417
507
  }
418
- export type ImageProps = SymbolImageProps | AssetImageProps;
508
+ /**
509
+ * Phase 13: a remote image (`AsyncImage`). The page loaded it already (an
510
+ * `image` request op) and told the app its pixel size; `width` / `height`
511
+ * are that size in points, the intrinsic size the layout engine uses unless
512
+ * `resizable`.
513
+ */
514
+ export interface UrlImageProps {
515
+ url: string;
516
+ resizable: boolean;
517
+ width: number;
518
+ height: number;
519
+ }
520
+ /**
521
+ * A `UIImage` / `CGImage` bitmap (`Image(uiImage:)`): a recipe the page draws
522
+ * (src/bitmaps.ts). `width` / `height` are the image's size in points, the
523
+ * intrinsic size unless `resizable`. See docs/ops-protocol.md, "Bitmaps and
524
+ * Core Image".
525
+ */
526
+ export interface BitmapImageProps {
527
+ bitmap: BitmapRecipe;
528
+ resizable: boolean;
529
+ width: number;
530
+ height: number;
531
+ /** `.interpolation(.none)`: scaled without smoothing. */
532
+ interpolation?: 'none';
533
+ }
534
+ export type ImageProps = SymbolImageProps | AssetImageProps | UrlImageProps | BitmapImageProps;
535
+
536
+ /**
537
+ * How to draw a bitmap: the `imagedata` source named by `source` (null: an
538
+ * empty image) with `steps` applied in order, in Core Image's coordinate
539
+ * space (units of source pixels, origin at the bottom left). The result is
540
+ * `width` × `height` pixels.
541
+ */
542
+ export interface BitmapRecipe {
543
+ source: string | null;
544
+ /** A frame of an animated source (a GIF), composed as the frames before it leave the canvas; absent: the file as it is. */
545
+ frame?: number;
546
+ steps: BitmapStep[];
547
+ width: number;
548
+ height: number;
549
+ }
550
+
551
+ /** A point in Core Image's space. */
552
+ export type BitmapPoint = [number, number];
553
+
554
+ /** One step of a recipe; the formulas are in docs/ops-protocol.md. */
555
+ export type BitmapStep =
556
+ | { op: 'crop' | 'render'; x: number; y: number; width: number; height: number }
557
+ | { op: 'clamp' }
558
+ | { op: 'sepiaTone'; intensity: number }
559
+ | { op: 'edges'; intensity: number }
560
+ | { op: 'crystallize'; radius: number; center: BitmapPoint }
561
+ | { op: 'pixellate'; scale: number; center: BitmapPoint }
562
+ | { op: 'gaussianBlur'; radius: number }
563
+ | { op: 'unsharpMask'; radius: number; intensity: number }
564
+ | { op: 'vignette'; intensity: number; radius: number; center: BitmapPoint; size: number }
565
+ /** Core Graphics drawing an image in a rectangle: every point p goes to (p.x × sx + tx, p.y × sy + ty). */
566
+ | { op: 'place'; sx: number; sy: number; tx: number; ty: number }
567
+ /** A clip to a rounded rectangle (an ellipse when the radii are half its sides). */
568
+ | { op: 'roundedClip'; x: number; y: number; width: number; height: number; rx: number; ry: number }
569
+ /** `UIImage.withTintColor`: every pixel the color (straight RGBA, 0…1), at the pixel's alpha times the color's. */
570
+ | { op: 'tint'; r: number; g: number; b: number; a: number };
419
571
  export type DividerProps = Record<string, never>;
420
572
  export type ScrollAxes = 'vertical' | 'horizontal' | 'both';
421
573
  export interface ScrollViewProps {
@@ -469,7 +621,9 @@ export interface ColorProps {
469
621
  color: Color;
470
622
  }
471
623
  /** Phase 6 adds `containerRelative` (`ContainerRelativeShape`): a rounded rectangle with `cornerRadius`. */
472
- export type ShapeKind = 'rectangle' | 'roundedRectangle' | 'circle' | 'capsule' | 'ellipse' | 'containerRelative';
624
+ export type ShapeKind = 'rectangle' | 'roundedRectangle' | 'circle' | 'capsule' | 'ellipse' | 'containerRelative'
625
+ /** A custom shape drawn from its path (`ShapeProps.path`). */
626
+ | 'path';
473
627
  export interface Stroke {
474
628
  /** Phase 6: any ShapeStyle; a gradient stroke falls back to its first stop. */
475
629
  color: ShapeStyle;
@@ -480,6 +634,13 @@ export interface Stroke {
480
634
  * `data-sb-stroke`.
481
635
  */
482
636
  inset?: boolean;
637
+ /** A custom shape's `StrokeStyle` (a built-in shape's border has none of these but `dash`). */
638
+ lineCap?: 'butt' | 'round' | 'square';
639
+ lineJoin?: 'miter' | 'round' | 'bevel';
640
+ miterLimit?: number;
641
+ /** A built-in shape draws any dash pattern as CSS dashes. */
642
+ dash?: number[];
643
+ dashPhase?: number;
483
644
  }
484
645
  /** `fill: null` means the current foreground color; fills its proposal. */
485
646
  export interface ShapeProps {
@@ -488,9 +649,33 @@ export interface ShapeProps {
488
649
  /** Phase 6: any ShapeStyle. */
489
650
  fill: ShapeStyle | null;
490
651
  stroke: Stroke | null;
652
+ /**
653
+ * `.trim(from:to:)`: the part of the outline between the fractions, drawn
654
+ * as a conic mask around the center from 3 o'clock, clockwise (exact for a
655
+ * circle). Absent when the shape is not trimmed.
656
+ */
657
+ trim?: { from: number; to: number };
658
+ /**
659
+ * A custom shape (`shape: "path"`, src/pathShape.ts): SVG path data for a
660
+ * rectangle of `size` at the origin, the size the element reported
661
+ * (`reportsSize`). Empty until the first report.
662
+ */
663
+ path?: string;
664
+ size?: { width: number; height: number };
665
+ /** `FillStyle(eoFill: true)`: overlapping parts of the path cancel out. */
666
+ fillRule?: 'evenodd';
667
+ /** The element reports its laid-out size (a custom shape draws its path for it). */
668
+ reportsSize?: boolean;
669
+ /**
670
+ * An animated change of the shape's `animatableData`: the path at each
671
+ * fraction of the change (`paths[index[i]]` at `progress[i]`), played in
672
+ * the commit's animation.
673
+ */
674
+ frames?: { progress: number[]; index: number[]; paths: string[] };
491
675
  }
492
676
  /** Phase 8: `full` is `.fullScreenCover` (the card fills the screen). */
493
- export type Detent = 'medium' | 'large' | 'full';
677
+ /** `height:<points>` / `fraction:<f>`: `.presentationDetents([.height(300)])`, `.fraction(0.4)`. */
678
+ export type Detent = 'medium' | 'large' | 'full' | `height:${number}` | `fraction:${number}`;
494
679
  /**
495
680
  * Always a root-level element (child of id 0) placed after the app's tree. A
496
681
  * `tap` with the sheet's own id is a dismiss request.
@@ -510,6 +695,10 @@ export interface TabViewProps {
510
695
  export interface TabProps {
511
696
  title: string;
512
697
  systemImage: string | null;
698
+ /** iOS 18 `Tab(…).badge(…)`: a red badge on the bar item (absent when none). */
699
+ badge?: string;
700
+ /** iOS 18 `Tab(role: .search)` (absent otherwise); Swift already put it last. */
701
+ role?: 'search';
513
702
  }
514
703
  export type PickerStyle = 'segmented' | 'menu' | 'inline';
515
704
  /**
@@ -611,11 +800,13 @@ export type GaugeStyle =
611
800
  | 'linearCapacity'
612
801
  | 'accessoryLinear'
613
802
  | 'accessoryCircular'
614
- | 'accessoryCircularCapacity';
803
+ | 'accessoryCircularCapacity'
804
+ /** A determinate `ProgressView`: the label above the bar, the current value label under it. */
805
+ | 'progress';
615
806
  /**
616
807
  * `Gauge`: `value` is normalized to 0...1. Children in order: the label (when
617
808
  * `hasLabel`) and the current value label (when `hasCurrentValueLabel`).
618
- * Circular styles render as linear for now.
809
+ * Circular styles draw a ring with one of the labels in the middle.
619
810
  */
620
811
  export interface GaugeProps {
621
812
  value: number;
@@ -641,7 +832,16 @@ export interface DecoratedProps {
641
832
  * a popover built from the current items (`button`/`navlink` rows that send
642
833
  * `tap`, `picker` sections that send `select`, `divider`, `section`, `text`).
643
834
  */
644
- export type MenuProps = Record<string, never>;
835
+ /**
836
+ * Lazy menus: `deferred: true` while the items are not built (only the label
837
+ * is a child). The page sends a `menu` event with `open: true` when the
838
+ * viewer opens it and shows the popover once the items arrive (`deferred:
839
+ * false`), and `open: false` when it closes. Absent when the app builds the
840
+ * items eagerly (headless runs, the mocks).
841
+ */
842
+ export interface MenuProps {
843
+ deferred?: boolean;
844
+ }
645
845
  export type ToolbarPlacement = 'trailing' | 'leading' | 'principal' | 'bottom';
646
846
  /**
647
847
  * `.toolbar { ToolbarItem(placement:) { ... } }`, an extra child of a
@@ -662,8 +862,13 @@ export interface SearchFieldProps {
662
862
  /** Defaults to "Search". */
663
863
  placeholder: string;
664
864
  }
665
- /** `.onTapGesture`: one child, the child's size; a click anywhere on it sends `tap`. */
666
- export type TapGestureProps = Record<string, never>;
865
+ /**
866
+ * `.onTapGesture`: one child, the child's size; a click anywhere on it sends
867
+ * `tap`. `location` (`onTapGesture { point in }`) adds the click's point.
868
+ */
869
+ export interface TapGestureProps {
870
+ location?: boolean;
871
+ }
667
872
 
668
873
  export interface ElementPropsByKind {
669
874
  text: TextProps;
@@ -758,7 +963,238 @@ export interface DefaultsOp {
758
963
  values: Record<string, unknown>;
759
964
  }
760
965
 
761
- export type Op = CreateOp | UpdateOp | InsertOp | RemoveOp | CommitOp | DefaultsOp;
966
+ /**
967
+ * Phase 13: something only the page can do for the app. The loader performs
968
+ * it (src/hostRequests.ts) and answers with a `ResponseEvent` of the same
969
+ * `id`; the renderer never sees it.
970
+ */
971
+ export interface RequestOp {
972
+ op: 'request';
973
+ id: number;
974
+ kind: 'fetch' | 'image' | 'share' | 'pickFiles' | 'notification' | 'scanner' | 'websocket' | 'openURL' | 'webAuth' | 'media' | 'download' | 'clipboard' | 'translate' | 'pickColor';
975
+ params:
976
+ | FetchRequestParams
977
+ | ImageRequestParams
978
+ | ShareRequestParams
979
+ | PickFilesRequestParams
980
+ | NotificationRequestParams
981
+ | ScannerRequestParams
982
+ | WebSocketRequestParams
983
+ | OpenURLRequestParams
984
+ | WebAuthRequestParams
985
+ | MediaRequestParams
986
+ | DownloadRequestParams
987
+ | ClipboardRequestParams
988
+ | TranslateRequestParams
989
+ | PickColorRequestParams;
990
+ }
991
+ /**
992
+ * `translate` (Translation; src/translation.ts): `availability` of a pair
993
+ * (`source` null: detected from `text`), or `translate` the `texts` from
994
+ * `source` (null: detected) to `target`, BCP 47 tags. `allowDownload`: the
995
+ * session may download the pair's model.
996
+ */
997
+ export interface TranslateRequestParams {
998
+ action: 'availability' | 'translate';
999
+ source?: string | null;
1000
+ target: string;
1001
+ text?: string;
1002
+ texts?: string[];
1003
+ allowDownload?: boolean;
1004
+ }
1005
+ /** `pickColor` (`ColorPicker`; src/colorPicker.ts): the chooser opens at `color` (`#rrggbb`). */
1006
+ export interface PickColorRequestParams {
1007
+ color?: string;
1008
+ }
1009
+ /**
1010
+ * `URLSessionWebSocketTask`: one call on a socket (src/webSocket.ts). `open`
1011
+ * takes `url` and `protocols`; the others the `socket` id `open` answered.
1012
+ */
1013
+ /** `openURL` (`OpenURLAction`, a link in a `Text`, `UIApplication.open`): the page opens `url` in a new tab; the value is null. */
1014
+ /**
1015
+ * `webAuth` (AuthenticationServices; docs/ops-protocol.md, "Signing in"):
1016
+ * `start` opens `url`, the provider's authorize page, in a popup window and
1017
+ * answers `{ url }`, the callback page URL the window ended at; `cancel` closes
1018
+ * the window, and the pending `start` fails with `cancelled: …`.
1019
+ * `callbackScheme` is the app's scheme the redirect stands for, `callbackPage`
1020
+ * the callback page the app put in its place; `ephemeral` is the app's wish
1021
+ * for a private session, which a popup cannot grant.
1022
+ */
1023
+ export interface WebAuthRequestParams {
1024
+ action?: 'start' | 'cancel';
1025
+ url?: string;
1026
+ callbackScheme?: string;
1027
+ callbackPage?: string;
1028
+ ephemeral?: boolean;
1029
+ }
1030
+ export interface OpenURLRequestParams {
1031
+ url: string;
1032
+ }
1033
+ export interface WebSocketRequestParams {
1034
+ action: 'open' | 'send' | 'receive' | 'close';
1035
+ url?: string;
1036
+ protocols?: string[];
1037
+ socket?: number;
1038
+ /** `send`: a text message. */
1039
+ string?: string;
1040
+ /** `send`: a binary message, base64. */
1041
+ data?: string;
1042
+ /** `close`: the close code and reason. */
1043
+ code?: number;
1044
+ reason?: string;
1045
+ }
1046
+ /** `URLSession`: one HTTP request. `body` is base64; `timeout` in ms (0: none). */
1047
+ export interface FetchRequestParams {
1048
+ url: string;
1049
+ method: string;
1050
+ headers: Record<string, string>;
1051
+ body: string | null;
1052
+ timeout?: number;
1053
+ }
1054
+ /** `AsyncImage`: decode the image at `url` and report its pixel size. */
1055
+ export interface ImageRequestParams {
1056
+ url: string;
1057
+ }
1058
+
1059
+ /**
1060
+ * `ShareLink`: share through the Web Share API, or download where the browser
1061
+ * cannot. `images` are asset-catalog names, shared (or downloaded) as their
1062
+ * files. The response value is a `ShareResult`.
1063
+ */
1064
+ export interface ShareRequestParams {
1065
+ title?: string;
1066
+ text?: string;
1067
+ url?: string;
1068
+ images?: string[];
1069
+ /** `Image(uiImage:)` items: recipes the page draws into PNG files. */
1070
+ bitmaps?: BitmapRecipe[];
1071
+ /** Files the app made: a `Transferable` item's exported bytes, a `UIImage`'s own file. */
1072
+ files?: SharedFile[];
1073
+ }
1074
+
1075
+ /** A file the app hands to the page whole: its name, MIME type and bytes (base64). */
1076
+ export interface SharedFile {
1077
+ name: string;
1078
+ type: string;
1079
+ data: string;
1080
+ }
1081
+
1082
+ /**
1083
+ * Saving (Photos' change requests, `UIImageWriteToSavedPhotosAlbum`): the
1084
+ * page downloads each file, catalog image and bitmap (a PNG named after
1085
+ * `title`). The response value is a `ShareResult` (`downloaded`).
1086
+ */
1087
+ export interface DownloadRequestParams {
1088
+ title?: string;
1089
+ files?: SharedFile[];
1090
+ images?: string[];
1091
+ bitmaps?: BitmapRecipe[];
1092
+ }
1093
+
1094
+ /**
1095
+ * `UIPasteboard.general`: `write` copies `text`, or the first picture (a
1096
+ * file, catalog image or bitmap, as PNG) to the system clipboard and answers
1097
+ * `{}`; `readText` answers `{ text }`.
1098
+ */
1099
+ export interface ClipboardRequestParams {
1100
+ action: 'write' | 'readText';
1101
+ text?: string;
1102
+ files?: SharedFile[];
1103
+ images?: string[];
1104
+ bitmaps?: BitmapRecipe[];
1105
+ }
1106
+
1107
+ /**
1108
+ * `PhotosPicker`: let the viewer choose files with the browser's file
1109
+ * chooser. `accept` is the input's `accept` (`image/*,video/*`; empty: any
1110
+ * file). The response value is a `PickFilesResult`; a dismissed chooser
1111
+ * answers with no items.
1112
+ */
1113
+ export interface PickFilesRequestParams {
1114
+ accept: string;
1115
+ multiple: boolean;
1116
+ }
1117
+
1118
+ /**
1119
+ * UserNotifications (docs/ops-protocol.md, "Notifications"): `status` reads
1120
+ * `Notification.permission`, `request` asks for it, `show` presents a
1121
+ * notification that came due (`title` absent: the badge only), `close`
1122
+ * closes delivered ones by identifier, `badge` sets the app badge. The
1123
+ * response value is a `NotificationResult`.
1124
+ */
1125
+ export interface NotificationRequestParams {
1126
+ action: 'status' | 'request' | 'show' | 'close' | 'badge';
1127
+ /** `show`: the request's identifier, the notification's `tag`. */
1128
+ id?: string;
1129
+ title?: string;
1130
+ subtitle?: string;
1131
+ body?: string;
1132
+ silent?: boolean;
1133
+ /** `show` and `badge`: the app badge (0 clears it). */
1134
+ badge?: number;
1135
+ /** `close`: the identifiers to close. */
1136
+ ids?: string[];
1137
+ }
1138
+
1139
+ /**
1140
+ * AVFoundation's capture sessions (docs/ops-protocol.md, "Code scanning"):
1141
+ * `probe` asks whether the page can scan `formats` (`BarcodeDetector` names)
1142
+ * with a camera; `scan` opens the camera over the page until a code is found
1143
+ * or the viewer closes it, or, where the browser cannot read codes, asks the
1144
+ * viewer to type one. The response value is a `ScannerResult`.
1145
+ */
1146
+ export interface ScannerRequestParams {
1147
+ action: 'probe' | 'scan';
1148
+ formats: string[];
1149
+ torch?: boolean;
1150
+ vibrate?: boolean;
1151
+ /** Which camera: the back one (`environment`, the default) or the front one (`user`). */
1152
+ facing?: 'environment' | 'user';
1153
+ }
1154
+
1155
+ /**
1156
+ * Phase 18: one save of a SwiftData store (`ModelContext.save()`, or the main
1157
+ * context's autosave at the end of an event). The loader hands it to the page,
1158
+ * which writes it to the app's IndexedDB database (src/swiftData.ts) and
1159
+ * passes every store back at the next launch as `SB_SWIFTDATA`. `clear`
1160
+ * (`ModelContainer.deleteAllData()`) empties the store before `put`.
1161
+ */
1162
+ export interface SwiftDataOp {
1163
+ op: 'swiftdata';
1164
+ store: string;
1165
+ put: SwiftDataRecord[];
1166
+ delete: number[];
1167
+ clear?: boolean;
1168
+ }
1169
+
1170
+ /** A saved model: its key in the store, its entity (class) name and its properties' JSON. */
1171
+ export interface SwiftDataRecord {
1172
+ id: number;
1173
+ entity: string;
1174
+ values: Record<string, unknown>;
1175
+ }
1176
+
1177
+ /**
1178
+ * A bitmap's source, sent once (by content `key`) before the first element
1179
+ * that draws it: an encoded image file (`mime`, `data`) or a pixel buffer
1180
+ * (`format`, `pixels`: one gray or four RGBA bytes per pixel, rows from the
1181
+ * top). The bytes are base64 in printed ops; from a module with the
1182
+ * `sb_bytes_*` exports, src/wasm.ts reads them out of linear memory
1183
+ * (docs/ops-protocol.md, "Bytes"). `width` × `height` is its size in pixels, as
1184
+ * drawn (a JPEG's EXIF rotation applied). The renderer keeps it for the run.
1185
+ */
1186
+ export interface ImageDataOp {
1187
+ op: 'imagedata';
1188
+ key: string;
1189
+ width: number;
1190
+ height: number;
1191
+ mime?: string;
1192
+ data?: string | Uint8Array;
1193
+ format?: 'gray' | 'rgba';
1194
+ pixels?: string | Uint8Array;
1195
+ }
1196
+
1197
+ export type Op = CreateOp | UpdateOp | InsertOp | RemoveOp | CommitOp | DefaultsOp | RequestOp | SwiftDataOp | ImageDataOp;
762
1198
 
763
1199
  // ---------------------------------------------------------------------------
764
1200
  // Events (renderer → Swift)
@@ -806,6 +1242,15 @@ export function isDynamicTypeSize(value: unknown): value is DynamicTypeSize {
806
1242
  export interface TapEvent {
807
1243
  type: 'tap';
808
1244
  id: ElementId;
1245
+ /** A `tapgesture` with `location`: the tap's point in the element's own coordinates. */
1246
+ x?: number;
1247
+ y?: number;
1248
+ }
1249
+ /** A link run (`TextRun.link`) of a `text` was tapped; `value` is its URL. */
1250
+ export interface LinkEvent {
1251
+ type: 'link';
1252
+ id: ElementId;
1253
+ value: string;
809
1254
  }
810
1255
  /** A `textfield` or `searchfield` received input. */
811
1256
  export interface TextEvent {
@@ -875,13 +1320,32 @@ export type RendererEvent =
875
1320
  | ToggleEvent
876
1321
  | EnvironmentEvent
877
1322
  | SelectEvent
1323
+ | LinkEvent
878
1324
  | GeometryEvent
879
1325
  | LayoutEvent
880
1326
  | BatchEvent
881
1327
  // Phase 8: gestures, rows, navigation (see the block at the end of this file)
882
1328
  | InteractionEvent
883
1329
  // Phase 8: alerts, controls (see the block at the end of this file)
884
- | Phase8ControlEvent;
1330
+ | Phase8ControlEvent
1331
+ // Phase 13: the answer to a `request` op
1332
+ | ResponseEvent
1333
+ // A `video` element's playback (src/video.ts)
1334
+ | VideoEvent
1335
+ | LazyEvent
1336
+ | MenuEvent;
1337
+
1338
+ /**
1339
+ * Phase 13: the page's answer to the `request` op with this `id`: `value`
1340
+ * (a `FetchResult` or `ImageResult` from src/hostRequests.ts) on success,
1341
+ * `error` (a short message the app maps to a `URLError`) on failure.
1342
+ */
1343
+ export interface ResponseEvent {
1344
+ type: 'response';
1345
+ id: number;
1346
+ value?: unknown;
1347
+ error?: string;
1348
+ }
885
1349
 
886
1350
  /**
887
1351
  * Several events delivered in one `sb_event_json` call: Swift applies each
@@ -934,6 +1398,11 @@ export function isRadialGradientStyle(style: ShapeStyle): style is RadialGradien
934
1398
  return typeof g === 'object' && g !== null;
935
1399
  }
936
1400
 
1401
+ export function isAngularGradientStyle(style: ShapeStyle): style is AngularGradientStyle {
1402
+ const g = (style as AngularGradientStyle).angularGradient;
1403
+ return typeof g === 'object' && g !== null;
1404
+ }
1405
+
937
1406
  export function isColorGradientStyle(style: ShapeStyle): style is ColorGradientStyle {
938
1407
  const c = (style as ColorGradientStyle).colorGradient;
939
1408
  return typeof c === 'object' && c !== null;
@@ -948,12 +1417,31 @@ export function isAssetImage(props: ImageProps): props is AssetImageProps {
948
1417
  return typeof (props as AssetImageProps).name === 'string';
949
1418
  }
950
1419
 
1420
+ /** True for the bitmap form of `image` props (`{ bitmap, resizable, width, height }`). */
1421
+ export function isBitmapImage(props: ImageProps): props is BitmapImageProps {
1422
+ const bitmap = (props as BitmapImageProps).bitmap;
1423
+ return typeof bitmap === 'object' && bitmap !== null;
1424
+ }
1425
+
1426
+ /** True for the remote form of `image` props (`{ url, resizable, width, height }`, Phase 13). */
1427
+ export function isUrlImage(props: ImageProps): props is UrlImageProps {
1428
+ return typeof (props as UrlImageProps).url === 'string';
1429
+ }
1430
+
951
1431
  /** Cheap structural check used when parsing an untrusted JSON buffer. */
952
1432
  export function isOp(value: unknown): value is Op {
953
1433
  if (typeof value !== 'object' || value === null) return false;
954
1434
  const op = (value as { op?: unknown }).op;
955
1435
  return (
956
- op === 'create' || op === 'update' || op === 'insert' || op === 'remove' || op === 'commit' || op === 'defaults'
1436
+ op === 'create' ||
1437
+ op === 'update' ||
1438
+ op === 'insert' ||
1439
+ op === 'remove' ||
1440
+ op === 'commit' ||
1441
+ op === 'defaults' ||
1442
+ op === 'request' ||
1443
+ op === 'swiftdata' ||
1444
+ op === 'imagedata'
957
1445
  );
958
1446
  }
959
1447
 
@@ -1095,6 +1583,8 @@ export interface ChartAxisLabel {
1095
1583
  /** Unit position along the axis (y: 0 is the top). */
1096
1584
  at: number;
1097
1585
  text: string;
1586
+ /** `leading`: an x label that starts at its tick (a date's) instead of centering on it. */
1587
+ anchor?: 'leading';
1098
1588
  }
1099
1589
  /** `null` for the whole axis means hidden. Positions are unit values along the axis. */
1100
1590
  export interface ChartAxis {
@@ -1192,7 +1682,10 @@ export interface SwipeActionsProps {
1192
1682
  }
1193
1683
 
1194
1684
  /** `.contextMenu { }`: `[content, ...items]`; a long press or right click lists the items (as in a `menu`) in a popover. */
1195
- export type ContextMenuProps = Record<string, never>;
1685
+ /** As `MenuProps`: `deferred` while the items (every child but the content) are not built. */
1686
+ export interface ContextMenuProps {
1687
+ deferred?: boolean;
1688
+ }
1196
1689
 
1197
1690
  /**
1198
1691
  * A `List` row that can be selected (`selectable`, `selected`) or deleted
@@ -1235,6 +1728,19 @@ export interface Style {
1235
1728
  hitTesting?: boolean;
1236
1729
  }
1237
1730
 
1731
+ export interface Style {
1732
+ /** `.blendMode`: the CSS `mix-blend-mode` the box is composited with. */
1733
+ blendMode?: string;
1734
+ /** `.zIndex(_:)`: the box's CSS `z-index` among its siblings. */
1735
+ zIndex?: number;
1736
+ /**
1737
+ * `.alignmentGuide(_:computeValue:)`: the box's guide `name` on `axis` is
1738
+ * `a + b·width + c·height` (`value: [a, b, c]`), which the containers that
1739
+ * align it by that guide use instead of the default (layout/engine.ts).
1740
+ */
1741
+ alignmentGuide?: { axis: 'horizontal' | 'vertical'; name: string; value: [number, number, number] };
1742
+ }
1743
+
1238
1744
  export type DragPhase = 'began' | 'changed' | 'ended' | 'cancelled';
1239
1745
 
1240
1746
  /**
@@ -1325,6 +1831,8 @@ export interface DatePickerProps {
1325
1831
  /** A `<textarea>` filling the proposal; input sends `text` like a `textfield`. */
1326
1832
  export interface TextEditorProps {
1327
1833
  text: string;
1834
+ /** Bound focus (UIKit's `becomeFirstResponder`): the editor takes or gives up focus; `focus` events report changes. */
1835
+ focused?: boolean;
1328
1836
  }
1329
1837
 
1330
1838
  /** A `stepper` half was tapped: +1 or −1. */
@@ -1394,6 +1902,29 @@ export interface Style {
1394
1902
  rotation3D?: Rotation3D;
1395
1903
  /** `.blur(radius:)`: `filter: blur(<radius>px)`, combined with `shadow` in one `filter`. */
1396
1904
  blur?: number;
1905
+ /** `.brightness(_:)`: SwiftUI's amount (-1…1), drawn as CSS `brightness(1 + amount)`. */
1906
+ brightness?: number;
1907
+ /** `.saturation(_:)`: CSS `saturate()`. */
1908
+ saturation?: number;
1909
+ /** `.contrast(_:)`: CSS `contrast()`. */
1910
+ contrast?: number;
1911
+ /** `.grayscale(_:)`: CSS `grayscale()` (0…1). */
1912
+ grayscale?: number;
1913
+ /** `.hueRotation(_:)`: degrees, CSS `hue-rotate()`. */
1914
+ hueRotation?: number;
1915
+ /** `.colorInvert()`: CSS `invert(1)`. */
1916
+ colorInvert?: boolean;
1917
+ /**
1918
+ * `.colorMultiply(_:)`: each component (and the alpha) multiplied by the
1919
+ * color's, drawn with an SVG `feColorMatrix` filter (colorMultiply.ts).
1920
+ */
1921
+ colorMultiply?: Color;
1922
+ /** View `.underline(_:)`: underlines the texts below (false turns an ancestor's off). */
1923
+ underline?: boolean;
1924
+ /** View `.strikethrough(_:)`: strikes the texts below through (false turns it off). */
1925
+ strikethrough?: boolean;
1926
+ /** View `.italic(_:)`: slants the texts below (false turns it off). */
1927
+ italic?: boolean;
1397
1928
  /**
1398
1929
  * `.ignoresSafeArea(edges:)`: the engine extends the box to the screen
1399
1930
  * edge on each listed side when the box touches that safe-area edge. The
@@ -1419,3 +1950,241 @@ export type ScenePhase = 'active' | 'inactive' | 'background';
1419
1950
  export interface EnvironmentEvent {
1420
1951
  scenePhase?: ScenePhase;
1421
1952
  }
1953
+
1954
+ // ---------------------------------------------------------------------------
1955
+ // Multi-line text fields, view section headers, scrolling to a view
1956
+ // ---------------------------------------------------------------------------
1957
+
1958
+ export interface TextFieldProps {
1959
+ /**
1960
+ * `TextField(_:text:axis: .vertical)`: a `<textarea>` that grows with its
1961
+ * text between the enclosing `lineLimitMin` (1) and `lineLimit` (none)
1962
+ * lines and scrolls past that; Return breaks the line unless the field has
1963
+ * `submit` (then Shift-Return does). Absent for a one-line field.
1964
+ */
1965
+ axis?: 'vertical';
1966
+ }
1967
+
1968
+ export interface ListProps {
1969
+ /** `.scrollContentBackground(.hidden)`: the list draws no background of its own (rows keep theirs). */
1970
+ background?: 'hidden';
1971
+ /** `.scrollDismissesKeyboard(.immediately | .interactively)`: scrolling blurs the text input being edited inside. */
1972
+ dismissesKeyboard?: boolean;
1973
+ }
1974
+
1975
+ export interface ScrollViewProps {
1976
+ /** As for `list`. */
1977
+ dismissesKeyboard?: boolean;
1978
+ }
1979
+
1980
+ /**
1981
+ * A `Section`'s header or footer that is a view rather than a `Text`: the
1982
+ * first (`sectionheader`) or last (`sectionfooter`) child of the `section`,
1983
+ * holding the view. In a list it sits above / below the section's card in
1984
+ * the content width (footnote font, secondary color); elsewhere it stacks
1985
+ * with the rows.
1986
+ */
1987
+ export type SectionSupplementProps = Record<string, never>;
1988
+
1989
+ export interface ElementPropsByKind {
1990
+ sectionheader: SectionSupplementProps;
1991
+ sectionfooter: SectionSupplementProps;
1992
+ }
1993
+
1994
+ /** `ScrollViewProxy.scrollTo(_:anchor:)`: where the target should end up (null: as little scrolling as shows it). */
1995
+ export interface ScrollToCommand {
1996
+ anchor: UnitPoint | null;
1997
+ /** Increases with every request (across readers); the page carries out each serial once. */
1998
+ serial: number;
1999
+ }
2000
+
2001
+ export interface Style {
2002
+ /** `.id(_:)` inside a `ScrollViewReader`: this box is a scroll target. */
2003
+ scrollID?: boolean;
2004
+ /**
2005
+ * Set in the pass in which a `ScrollViewProxy` asks to scroll to this box
2006
+ * (and kept until the reader re-runs): after laying out a pass that brings
2007
+ * a serial above every one it handled, the page scrolls the scroll views
2008
+ * and lists around it (smoothly when the pass's commit is animated).
2009
+ */
2010
+ scrollTo?: ScrollToCommand;
2011
+ /** The lower bound of a `.lineLimit(2...4)` range (absent for 1): a vertical text field's minimum lines. */
2012
+ lineLimitMin?: number;
2013
+ }
2014
+
2015
+ // ---------------------------------------------------------------------------
2016
+ // Video and audio (docs/ops-protocol.md, "Video and audio")
2017
+ // ---------------------------------------------------------------------------
2018
+
2019
+ /**
2020
+ * A `video` element: an `AVPlayer` shown by a view (AVKit's `VideoPlayer`, an
2021
+ * `AVPlayerLayer`), as an HTML `<video>` (src/video.ts). No children.
2022
+ */
2023
+ /**
2024
+ * Lazy rows: the rows of a `List` or lazy stack that are not built yet
2025
+ * (`remaining` of them, after the built ones). It takes no space; when it
2026
+ * comes within a screen of its scroll view's visible area the page sends a
2027
+ * `lazy` event, and Swift builds the next rows (a new spacer follows them).
2028
+ */
2029
+ export interface LazySpacerProps {
2030
+ axis: 'vertical' | 'horizontal';
2031
+ remaining: number;
2032
+ }
2033
+
2034
+ /** Lazy menus: the viewer opened (`open: true`) or closed a deferred `menu` / `contextmenu`. */
2035
+ export interface MenuEvent {
2036
+ type: 'menu';
2037
+ id: ElementId;
2038
+ open: boolean;
2039
+ }
2040
+
2041
+ /** A `lazyspacer` came near the visible area: build the next rows. */
2042
+ export interface LazyEvent {
2043
+ type: 'lazy';
2044
+ id: ElementId;
2045
+ }
2046
+
2047
+ export interface VideoProps {
2048
+ /** The media URL (remote, data or blob); null while there is none. */
2049
+ url: string | null;
2050
+ /** Whether the player plays here (only the newest view of a player plays it). */
2051
+ playing: boolean;
2052
+ muted: boolean;
2053
+ /** 0…1. */
2054
+ volume: number;
2055
+ /** The playback rate (positive; the player's default rate while paused). */
2056
+ rate: number;
2057
+ /** An `AVPlayerLooper` loops the item. */
2058
+ loop: boolean;
2059
+ /** CSS `object-fit`: `contain`, `cover`, `fill`. */
2060
+ gravity: 'resizeAspect' | 'resizeAspectFill' | 'resize';
2061
+ /** The browser's native playback controls (`VideoPlayer`, `AVPlayerViewController`). */
2062
+ controls: boolean;
2063
+ /** Seconds; the page seeks once per serial (and reports `seeked` with it). */
2064
+ seek: { time: number; serial: number };
2065
+ /** Report the position this often (seconds) while playing, when shorter than `timeupdate`'s pace. */
2066
+ report?: number;
2067
+ /** The video's size in pixels, once known: the aspect ratio of an axis left open. */
2068
+ width?: number;
2069
+ height?: number;
2070
+ }
2071
+
2072
+ export interface ElementPropsByKind {
2073
+ video: VideoProps;
2074
+ lazyspacer: LazySpacerProps;
2075
+ }
2076
+
2077
+ /**
2078
+ * A gradient of a `Canvas` (`GraphicsContext.Shading`), in the drawing's own
2079
+ * coordinates (points, after the command's `transform`) rather than the
2080
+ * unit square of a view's box.
2081
+ */
2082
+ export interface CanvasLinearGradientShading {
2083
+ canvasLinearGradient: { stops: GradientStop[]; start: { x: number; y: number }; end: { x: number; y: number } };
2084
+ }
2085
+ export interface CanvasRadialGradientShading {
2086
+ canvasRadialGradient: { stops: GradientStop[]; center: { x: number; y: number }; startRadius: number; endRadius: number };
2087
+ }
2088
+ export type CanvasShading = ShapeStyle | CanvasLinearGradientShading | CanvasRadialGradientShading;
2089
+
2090
+ /** A `GraphicsContext.Filter`: one key, its amount (degrees for `hueRotation`). */
2091
+ export type CanvasFilter =
2092
+ | { blur: number }
2093
+ | { shadow: { color: Color; radius: number; x: number; y: number } }
2094
+ | { grayscale: number }
2095
+ | { saturation: number }
2096
+ | { brightness: number }
2097
+ | { contrast: number }
2098
+ | { hueRotation: number }
2099
+ | { invert: number };
2100
+
2101
+ /** The context's state when a command was drawn. */
2102
+ export interface CanvasState {
2103
+ /** `[a, b, c, d, tx, ty]`, as `CGAffineTransform` (and SVG's `matrix()`); absent for the identity. */
2104
+ transform?: [number, number, number, number, number, number];
2105
+ opacity?: number;
2106
+ /** CSS `mix-blend-mode`. */
2107
+ blendMode?: string;
2108
+ /** SVG path data, in the canvas's coordinates; the command draws inside all of them. */
2109
+ clips?: string[];
2110
+ filters?: CanvasFilter[];
2111
+ }
2112
+
2113
+ export type CanvasCommand = CanvasState &
2114
+ (
2115
+ | { op: 'fill'; path: string; shading: CanvasShading; evenOdd?: boolean }
2116
+ | {
2117
+ op: 'stroke';
2118
+ path: string;
2119
+ shading: CanvasShading;
2120
+ lineWidth: number;
2121
+ lineCap?: 'butt' | 'round' | 'square';
2122
+ lineJoin?: 'miter' | 'round' | 'bevel';
2123
+ miterLimit?: number;
2124
+ dash?: number[];
2125
+ dashPhase?: number;
2126
+ }
2127
+ | {
2128
+ op: 'image';
2129
+ image: ImageProps;
2130
+ /** The point `anchor` (a unit point of the image) is placed at. */
2131
+ x: number;
2132
+ y: number;
2133
+ anchor: UnitPoint;
2134
+ /** `draw(_:in:)`: the rectangle's size; otherwise the image's own size. */
2135
+ width?: number;
2136
+ height?: number;
2137
+ /** A resolved image's `shading`: a template image is painted with it. */
2138
+ shading?: CanvasShading;
2139
+ }
2140
+ | { op: 'text'; text: string; x: number; y: number; anchor: UnitPoint; shading: CanvasShading; font?: Font }
2141
+ );
2142
+
2143
+ /**
2144
+ * A `Canvas` at one size (src/canvasDrawing.ts): the commands its renderer
2145
+ * drew for `width` × `height`, the size the canvas reported. It fills its
2146
+ * proposal and clips the drawing to its box.
2147
+ */
2148
+ export interface CanvasProps {
2149
+ width: number;
2150
+ height: number;
2151
+ commands: CanvasCommand[];
2152
+ }
2153
+
2154
+ export interface ElementPropsByKind {
2155
+ canvas: CanvasProps;
2156
+ }
2157
+
2158
+ /**
2159
+ * What a `video` element reports: `ready` (metadata), `time`, `status`
2160
+ * (playing, waiting, paused), `seeked` (a `seek` serial done), `play` /
2161
+ * `pause` (the viewer used the controls; `reason: "blocked"` when the browser
2162
+ * refused to start), `volume`, `rate`, `ended`, `error` (a `MediaError` code).
2163
+ */
2164
+ export interface VideoEvent {
2165
+ type: 'video';
2166
+ id: ElementId;
2167
+ event: 'ready' | 'time' | 'status' | 'seeked' | 'play' | 'pause' | 'volume' | 'rate' | 'ended' | 'error';
2168
+ url?: string;
2169
+ time?: number;
2170
+ duration?: number | null;
2171
+ width?: number;
2172
+ height?: number;
2173
+ status?: 'playing' | 'waiting' | 'paused';
2174
+ serial?: number;
2175
+ reason?: 'blocked';
2176
+ muted?: boolean;
2177
+ volume?: number;
2178
+ rate?: number;
2179
+ code?: number;
2180
+ message?: string;
2181
+ }
2182
+
2183
+ /** `media` requests (src/media.ts): AVFoundation's files and probes, QuickLook's previews. */
2184
+ export interface MediaRequestParams {
2185
+ action: 'file' | 'probe' | 'release' | 'open';
2186
+ url?: string;
2187
+ name?: string;
2188
+ type?: string;
2189
+ data?: string;
2190
+ }