@swiftbrowser/react 0.17.4 → 0.17.6

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 CHANGED
@@ -403,7 +403,17 @@ An image is inert, as a SwiftUI image is. `<AsyncImage url={…}
403
403
  webInteractions />` gives a remote picture the browser's own image
404
404
  interactions back: a long press's Save / Share / Copy on iOS, dragging it
405
405
  out of the page, the right-click menu. Use it on an image with no tap,
406
- gesture or context menu of its own.
406
+ gesture or context menu of its own. `placeholder` shows until the image
407
+ loaded, `failure` when it could not, and `loadingIndicator` goes over the
408
+ placeholder once a load has run for `loadingIndicatorDelay` ms (300 by
409
+ default): a `<ProgressView />` for a big picture on a slow connection,
410
+ nothing for one that comes quickly or from the cache. The image's
411
+ modifiers apply in every phase, as a SwiftUI `AsyncImage`'s do: a
412
+ thumbnail with `frame={{ width: 72, height: 48 }}` keeps its box while it
413
+ loads (with no placeholder, an empty one), so the text beside it does not
414
+ move when the picture arrives; a `placeholder` at the picture's aspect
415
+ ratio does the same for a picture drawn at its own shape. All three show
416
+ in a SwiftBrowser Native app too.
407
417
 
408
418
  `keyboardType`, `textContentType`, `textInputAutocapitalization` and
409
419
  `autocorrectionDisabled` are SwiftUI's text input hints, for the field they
@@ -1,13 +1,27 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { type ViewModifiers } from '../modifiers';
3
+ /** How long a load runs before `loadingIndicator` shows. */
4
+ export declare const LOADING_INDICATOR_DELAY_MS = 300;
3
5
  export interface AsyncImageProps extends ViewModifiers {
4
6
  url: string;
5
7
  /** `.resizable()` on the loaded image. */
6
8
  resizable?: boolean;
7
- /** Shown until the image loaded (nothing by default). */
9
+ /**
10
+ * Shown until the image loaded (nothing by default). The image's modifiers
11
+ * (its frame, aspect ratio, corner radius) apply to it as they do to the
12
+ * picture, as a SwiftUI `AsyncImage`'s do in every phase: a framed image
13
+ * keeps its box while it loads, so the row beside it does not move.
14
+ */
8
15
  placeholder?: ReactNode;
9
- /** Shown when the image failed to load (the placeholder by default). */
16
+ /** Shown when the image failed to load (the placeholder by default), under the same modifiers. */
10
17
  failure?: ReactNode;
18
+ /**
19
+ * Shown over the placeholder once a load has run for `loadingIndicatorDelay`
20
+ * ms (300 by default): a `<ProgressView />` for a big picture on a slow
21
+ * connection, nothing for one that comes quickly or from the cache.
22
+ */
23
+ loadingIndicator?: ReactNode;
24
+ loadingIndicatorDelay?: number;
11
25
  /**
12
26
  * Keep the browser's own image interactions: a long press's Save / Share /
13
27
  * Copy on iOS, dragging the image out, the right-click menu. Off by
@@ -21,9 +35,11 @@ export interface AsyncImageProps extends ViewModifiers {
21
35
  /**
22
36
  * `AsyncImage(url:)`: the page loads the image to learn its size (the
23
37
  * engine lays a remote image out by its pixel size, as the Swift core does
24
- * after its `image` request), then shows it. In a SwiftBrowser Native app,
25
- * which has no page to load it with, the URL goes to the renderer at once
26
- * and SwiftUI's own `AsyncImage` loads it.
38
+ * after its `image` request), then shows it. A SwiftBrowser Native app's
39
+ * runtime gives it an `Image` too (@swiftbrowser/native's image.ts, over
40
+ * the host's image loader), so the placeholder shows there as well; on a
41
+ * host without one the URL goes to the renderer at once and SwiftUI's own
42
+ * `AsyncImage` loads it.
27
43
  */
28
44
  export declare function AsyncImage(props: AsyncImageProps): ReactNode;
29
45
  //# sourceMappingURL=images.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/components/images.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,EAA8B,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAE9E,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,yDAAyD;IACzD,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,wEAAwE;IACxE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAmBD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,CAgC5D"}
1
+ {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/components/images.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,EAA8B,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAG9E,4DAA4D;AAC5D,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,kGAAkG;IAClG,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAC7B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAmBD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,CA2D5D"}
package/dist/index.js CHANGED
@@ -3404,6 +3404,7 @@ function EditButton(props) {
3404
3404
  const [editing, setEditing] = readBinding(own.editMode, own.onChange);
3405
3405
  return applyModifiers(/* @__PURE__ */ jsx(Button, { title: editing ? "Done" : "Edit", editMode: editing ? "active" : "inactive", action: () => setEditing(!editing) }), modifiers);
3406
3406
  }
3407
+ const LOADING_INDICATOR_DELAY_MS = 300;
3407
3408
  const loaded = /* @__PURE__ */ new Map();
3408
3409
  function inNativeApp() {
3409
3410
  return typeof Image === "undefined" && globalThis.__sbNative !== void 0;
@@ -3434,11 +3435,29 @@ function AsyncImage(props) {
3434
3435
  live = false;
3435
3436
  };
3436
3437
  }, [own.url]);
3438
+ const hasIndicator = own.loadingIndicator !== void 0 && own.loadingIndicator !== null;
3439
+ const delay = own.loadingIndicatorDelay ?? LOADING_INDICATOR_DELAY_MS;
3440
+ const [slow, setSlow] = useState(false);
3441
+ useEffect(() => {
3442
+ if (state !== null || !hasIndicator) {
3443
+ setSlow(false);
3444
+ return;
3445
+ }
3446
+ const timer = setTimeout(() => setSlow(true), delay);
3447
+ return () => clearTimeout(timer);
3448
+ }, [state, hasIndicator, delay]);
3437
3449
  if (inNativeApp()) {
3438
3450
  return applyModifiers(sb("image", { ...imageProps(own), url: own.url, width: 0, height: 0 }), modifiers);
3439
3451
  }
3440
- if (state === null) return own.placeholder ?? null;
3441
- if (state === "failed") return own.failure ?? own.placeholder ?? null;
3452
+ if (state === null) {
3453
+ const placeholder2 = own.placeholder ?? /* @__PURE__ */ jsx(ZStack, {});
3454
+ const shown = slow && hasIndicator ? /* @__PURE__ */ jsxs(ZStack, { children: [
3455
+ placeholder2,
3456
+ own.loadingIndicator
3457
+ ] }) : placeholder2;
3458
+ return applyModifiers(shown, modifiers);
3459
+ }
3460
+ if (state === "failed") return applyModifiers(own.failure ?? own.placeholder ?? /* @__PURE__ */ jsx(ZStack, {}), modifiers);
3442
3461
  return applyModifiers(sb("image", { ...imageProps(own), url: state.url, width: state.width, height: state.height }), modifiers);
3443
3462
  }
3444
3463
  function imageProps(own) {
@@ -4623,6 +4642,7 @@ export {
4623
4642
  Host,
4624
4643
  Image$1 as Image,
4625
4644
  KeyframeAnimator,
4645
+ LOADING_INDICATOR_DELAY_MS,
4626
4646
  Label,
4627
4647
  LabeledContent,
4628
4648
  Layout,