@omg-dev/pwa 0.4.33 → 0.4.35

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/dist/auto.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as countVisit, c as isInIframe, d as setAutoShown, l as isStandalone, n as VibesInstallPrompt, o as detectPlatform, p as wasAutoShown, s as isDismissed } from "./react-BYbYeTts.mjs";
1
+ import { a as countVisit, c as isInIframe, f as setAutoShown, l as isIosInstallPlatform, m as wasAutoShown, n as VibesInstallPrompt, o as detectPlatform, s as isDismissed, u as isStandalone } from "./react-BHavFwjG.mjs";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { createRoot } from "react-dom/client";
4
4
  //#region src/auto.tsx
@@ -8,7 +8,7 @@ function shouldAutoShow() {
8
8
  if (isStandalone()) return false;
9
9
  if (isDismissed() || wasAutoShown()) return false;
10
10
  const platform = detectPlatform();
11
- if (platform !== "ios-safari" && platform !== "ios-chrome" && platform !== "ios-other" && platform !== "android") return false;
11
+ if (!isIosInstallPlatform(platform) && platform !== "android") return false;
12
12
  return countVisit() >= 2;
13
13
  }
14
14
  function mount() {
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as countVisit, c as isInIframe, f as setDismissed, i as useInstallPrompt, l as isStandalone, n as VibesInstallPrompt, o as detectPlatform, r as defaultAppName, s as isDismissed, t as InstallGuide, u as promptNativeInstall } from "./react-BYbYeTts.mjs";
2
- export { InstallGuide, VibesInstallPrompt, countVisit, defaultAppName, detectPlatform, isDismissed, isInIframe, isStandalone, promptNativeInstall, setDismissed, useInstallPrompt };
1
+ import { a as countVisit, c as isInIframe, d as promptNativeInstall, i as useInstallPrompt, l as isIosInstallPlatform, n as VibesInstallPrompt, o as detectPlatform, p as setDismissed, r as defaultAppName, s as isDismissed, t as InstallGuide, u as isStandalone } from "./react-BHavFwjG.mjs";
2
+ export { InstallGuide, VibesInstallPrompt, countVisit, defaultAppName, detectPlatform, isDismissed, isInIframe, isIosInstallPlatform, isStandalone, promptNativeInstall, setDismissed, useInstallPrompt };
@@ -2,6 +2,9 @@ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from
2
2
  import { createPortal } from "react-dom";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  //#region src/core.ts
5
+ function isIosInstallPlatform(platform) {
6
+ return platform === "ios-safari" || platform === "ios-chrome" || platform === "ios-other";
7
+ }
5
8
  function detectPlatform() {
6
9
  if (typeof navigator === "undefined") return "unsupported";
7
10
  const ua = navigator.userAgent;
@@ -198,6 +201,7 @@ const CSS = `
198
201
  .vpwa-open .vpwa-backdrop { opacity: 1; }
199
202
  .vpwa-sheet {
200
203
  position: relative; width: 100%; max-width: 430px;
204
+ max-height: calc(100dvh - 12px); overflow-y: auto; overscroll-behavior: contain;
201
205
  background: var(--card, var(--vpwa-bg));
202
206
  color: var(--card-foreground, var(--vpwa-fg));
203
207
  border: 1px solid var(--border, var(--vpwa-border)); border-bottom: none;
@@ -253,6 +257,46 @@ const CSS = `
253
257
  .vpwa-steptext { font-size: 14px; font-weight: 500; }
254
258
  .vpwa-stepcap { font-weight: 400; color: var(--muted-foreground, var(--vpwa-muted)); }
255
259
 
260
+ /* ── iOS walkthrough video ──────────────────────────────────────────── */
261
+ .vpwa-video-guide {
262
+ display: flex; flex-direction: column; align-items: center; gap: 10px;
263
+ margin: 0; padding: 0;
264
+ }
265
+ .vpwa-video-frame {
266
+ position: relative; overflow: hidden;
267
+ width: min(100%, 31.5dvh, 292.5px);
268
+ aspect-ratio: 9 / 16;
269
+ border: 1px solid var(--border, var(--vpwa-border)); border-radius: 18px;
270
+ background: #000;
271
+ box-shadow: 0 14px 38px rgba(0, 0, 0, 0.24);
272
+ }
273
+ .vpwa-video {
274
+ display: block; width: 100%; height: 100%;
275
+ object-fit: cover;
276
+ }
277
+ .vpwa-video-loading {
278
+ position: absolute; inset: 0;
279
+ display: flex; align-items: flex-end; justify-content: center;
280
+ padding: 20px;
281
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.48), transparent 42%);
282
+ pointer-events: none;
283
+ }
284
+ .vpwa-video-loading span {
285
+ color: transparent; font-size: 12px; font-weight: 600; line-height: 1;
286
+ letter-spacing: 0.01em;
287
+ background: linear-gradient(100deg, rgba(255,255,255,0.46) 20%, #fff 45%, rgba(255,255,255,0.46) 70%);
288
+ background-size: 220% 100%; background-clip: text; -webkit-background-clip: text;
289
+ animation: vpwa-text-shimmer 1.6s ease-in-out infinite;
290
+ }
291
+ @keyframes vpwa-text-shimmer {
292
+ from { background-position: 100% 0; }
293
+ to { background-position: -100% 0; }
294
+ }
295
+ .vpwa-video-guide figcaption {
296
+ max-width: 32rem; color: var(--muted-foreground, var(--vpwa-muted));
297
+ font-size: 12px; line-height: 1.45; text-align: center;
298
+ }
299
+
256
300
  /* ── mocks ───────────────────────────────────────────────────────────── */
257
301
  .vpwa-toolbar {
258
302
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
@@ -293,6 +337,7 @@ const CSS = `
293
337
  }
294
338
  @media (prefers-reduced-motion: reduce) {
295
339
  .vpwa-target { animation: none; }
340
+ .vpwa-video-loading span { animation: none; color: rgba(255,255,255,0.82); }
296
341
  .vpwa-sheet, .vpwa-backdrop, .vpwa-banner { transition: none; }
297
342
  }
298
343
 
@@ -690,15 +735,19 @@ function stepsFor(platform) {
690
735
  case "unsupported": return null;
691
736
  }
692
737
  }
693
- function InstallGuide({ open, onClose, appName }) {
738
+ function InstallGuide({ open, onClose, appName, iosVideoSrc, iosVideoPoster }) {
694
739
  const { platform, canPrompt, installed, promptInstall } = useInstallPrompt();
695
740
  const name = appName ?? defaultAppName();
696
741
  const [mounted, setMounted] = useState(open);
697
742
  const [shown, setShown] = useState(false);
743
+ const [videoFailed, setVideoFailed] = useState(false);
744
+ const [videoLoading, setVideoLoading] = useState(true);
698
745
  useEffect(() => {
699
746
  ensureStyles();
700
747
  if (open) {
701
748
  setMounted(true);
749
+ setVideoFailed(false);
750
+ setVideoLoading(true);
702
751
  const raf = requestAnimationFrame(() => requestAnimationFrame(() => setShown(true)));
703
752
  return () => cancelAnimationFrame(raf);
704
753
  }
@@ -746,6 +795,33 @@ function InstallGuide({ open, onClose, appName }) {
746
795
  children: "Not now"
747
796
  })]
748
797
  });
798
+ else if (isIosInstallPlatform(platform) && iosVideoSrc && !videoFailed) body = /* @__PURE__ */ jsxs("figure", {
799
+ className: "vpwa-video-guide",
800
+ children: [/* @__PURE__ */ jsxs("div", {
801
+ className: "vpwa-video-frame",
802
+ children: [/* @__PURE__ */ jsx("video", {
803
+ className: "vpwa-video",
804
+ src: iosVideoSrc,
805
+ poster: iosVideoPoster,
806
+ autoPlay: true,
807
+ disablePictureInPicture: true,
808
+ loop: true,
809
+ muted: true,
810
+ playsInline: true,
811
+ preload: "auto",
812
+ "aria-label": "iOS Add to Home Screen walkthrough",
813
+ onCanPlay: () => setVideoLoading(false),
814
+ onPlaying: () => setVideoLoading(false),
815
+ onWaiting: () => setVideoLoading(true),
816
+ onError: () => setVideoFailed(true)
817
+ }), videoLoading ? /* @__PURE__ */ jsx("div", {
818
+ className: "vpwa-video-loading",
819
+ role: "status",
820
+ "aria-label": "Loading walkthrough video",
821
+ children: /* @__PURE__ */ jsx("span", { children: "Loading walkthrough…" })
822
+ }) : null]
823
+ }), /* @__PURE__ */ jsx("figcaption", { children: "Open your browser’s Share menu, choose Add to Home Screen, then tap Add." })]
824
+ });
749
825
  else if (steps) body = /* @__PURE__ */ jsx("ol", {
750
826
  className: "vpwa-steps",
751
827
  children: steps.map((step, i) => /* @__PURE__ */ jsxs("li", {
@@ -872,4 +948,4 @@ function VibesInstallPrompt({ appName, variant = "inline", className }) {
872
948
  })] });
873
949
  }
874
950
  //#endregion
875
- export { countVisit as a, isInIframe as c, setAutoShown as d, setDismissed as f, useInstallPrompt as i, isStandalone as l, VibesInstallPrompt as n, detectPlatform as o, wasAutoShown as p, defaultAppName as r, isDismissed as s, InstallGuide as t, promptNativeInstall as u };
951
+ export { countVisit as a, isInIframe as c, promptNativeInstall as d, setAutoShown as f, useInstallPrompt as i, isIosInstallPlatform as l, wasAutoShown as m, VibesInstallPrompt as n, detectPlatform as o, setDismissed as p, defaultAppName as r, isDismissed as s, InstallGuide as t, isStandalone as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omg-dev/pwa",
3
- "version": "0.4.33",
3
+ "version": "0.4.35",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/src/auto.tsx CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  detectPlatform,
19
19
  isDismissed,
20
20
  isInIframe,
21
+ isIosInstallPlatform,
21
22
  isStandalone,
22
23
  setAutoShown,
23
24
  wasAutoShown,
@@ -30,7 +31,7 @@ function shouldAutoShow(): boolean {
30
31
  if (isStandalone()) return false
31
32
  if (isDismissed() || wasAutoShown()) return false
32
33
  const platform = detectPlatform()
33
- if (platform !== "ios-safari" && platform !== "ios-chrome" && platform !== "ios-other" && platform !== "android") {
34
+ if (!isIosInstallPlatform(platform) && platform !== "android") {
34
35
  return false
35
36
  }
36
37
  return countVisit() >= 2
package/src/core.ts CHANGED
@@ -20,6 +20,12 @@ export type InstallPlatform =
20
20
  | "desktop-safari" // macOS Safari — File → Add to Dock
21
21
  | "unsupported" // desktop Firefox, webviews, …
22
22
 
23
+ export function isIosInstallPlatform(
24
+ platform: InstallPlatform,
25
+ ): platform is "ios-safari" | "ios-chrome" | "ios-other" {
26
+ return platform === "ios-safari" || platform === "ios-chrome" || platform === "ios-other"
27
+ }
28
+
23
29
  export function detectPlatform(): InstallPlatform {
24
30
  if (typeof navigator === "undefined") return "unsupported"
25
31
  const ua = navigator.userAgent
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ export {
9
9
  detectPlatform,
10
10
  isStandalone,
11
11
  isInIframe,
12
+ isIosInstallPlatform,
12
13
  promptNativeInstall,
13
14
  isDismissed,
14
15
  setDismissed,
package/src/react.tsx CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  getServerInstallState,
14
14
  isDismissed,
15
15
  isInIframe,
16
+ isIosInstallPlatform,
16
17
  promptNativeInstall,
17
18
  setDismissed,
18
19
  subscribeInstallState,
@@ -169,10 +170,16 @@ export function InstallGuide({
169
170
  open,
170
171
  onClose,
171
172
  appName,
173
+ iosVideoSrc,
174
+ iosVideoPoster,
172
175
  }: {
173
176
  open: boolean
174
177
  onClose: () => void
175
178
  appName?: string
179
+ /** Optional iOS walkthrough video. Static steps remain the load-failure fallback. */
180
+ iosVideoSrc?: string
181
+ /** Optional poster shown while the iOS walkthrough video loads. */
182
+ iosVideoPoster?: string
176
183
  }) {
177
184
  const { platform, canPrompt, installed, promptInstall } = useInstallPrompt()
178
185
  const name = appName ?? defaultAppName()
@@ -180,11 +187,15 @@ export function InstallGuide({
180
187
  // `shown` drives the CSS open state one frame after mount.
181
188
  const [mounted, setMounted] = useState(open)
182
189
  const [shown, setShown] = useState(false)
190
+ const [videoFailed, setVideoFailed] = useState(false)
191
+ const [videoLoading, setVideoLoading] = useState(true)
183
192
 
184
193
  useEffect(() => {
185
194
  ensureStyles()
186
195
  if (open) {
187
196
  setMounted(true)
197
+ setVideoFailed(false)
198
+ setVideoLoading(true)
188
199
  const raf = requestAnimationFrame(() => requestAnimationFrame(() => setShown(true)))
189
200
  return () => cancelAnimationFrame(raf)
190
201
  }
@@ -235,6 +246,37 @@ export function InstallGuide({
235
246
  </button>
236
247
  </div>
237
248
  )
249
+ } else if (isIosInstallPlatform(platform) && iosVideoSrc && !videoFailed) {
250
+ body = (
251
+ <figure className="vpwa-video-guide">
252
+ <div className="vpwa-video-frame">
253
+ <video
254
+ className="vpwa-video"
255
+ src={iosVideoSrc}
256
+ poster={iosVideoPoster}
257
+ autoPlay
258
+ disablePictureInPicture
259
+ loop
260
+ muted
261
+ playsInline
262
+ preload="auto"
263
+ aria-label="iOS Add to Home Screen walkthrough"
264
+ onCanPlay={() => setVideoLoading(false)}
265
+ onPlaying={() => setVideoLoading(false)}
266
+ onWaiting={() => setVideoLoading(true)}
267
+ onError={() => setVideoFailed(true)}
268
+ />
269
+ {videoLoading ? (
270
+ <div className="vpwa-video-loading" role="status" aria-label="Loading walkthrough video">
271
+ <span>Loading walkthrough…</span>
272
+ </div>
273
+ ) : null}
274
+ </div>
275
+ <figcaption>
276
+ Open your browser’s Share menu, choose Add to Home Screen, then tap Add.
277
+ </figcaption>
278
+ </figure>
279
+ )
238
280
  } else if (steps) {
239
281
  body = (
240
282
  <ol className="vpwa-steps">
package/src/styles.ts CHANGED
@@ -54,6 +54,7 @@ const CSS = `
54
54
  .vpwa-open .vpwa-backdrop { opacity: 1; }
55
55
  .vpwa-sheet {
56
56
  position: relative; width: 100%; max-width: 430px;
57
+ max-height: calc(100dvh - 12px); overflow-y: auto; overscroll-behavior: contain;
57
58
  background: var(--card, var(--vpwa-bg));
58
59
  color: var(--card-foreground, var(--vpwa-fg));
59
60
  border: 1px solid var(--border, var(--vpwa-border)); border-bottom: none;
@@ -109,6 +110,46 @@ const CSS = `
109
110
  .vpwa-steptext { font-size: 14px; font-weight: 500; }
110
111
  .vpwa-stepcap { font-weight: 400; color: var(--muted-foreground, var(--vpwa-muted)); }
111
112
 
113
+ /* ── iOS walkthrough video ──────────────────────────────────────────── */
114
+ .vpwa-video-guide {
115
+ display: flex; flex-direction: column; align-items: center; gap: 10px;
116
+ margin: 0; padding: 0;
117
+ }
118
+ .vpwa-video-frame {
119
+ position: relative; overflow: hidden;
120
+ width: min(100%, 31.5dvh, 292.5px);
121
+ aspect-ratio: 9 / 16;
122
+ border: 1px solid var(--border, var(--vpwa-border)); border-radius: 18px;
123
+ background: #000;
124
+ box-shadow: 0 14px 38px rgba(0, 0, 0, 0.24);
125
+ }
126
+ .vpwa-video {
127
+ display: block; width: 100%; height: 100%;
128
+ object-fit: cover;
129
+ }
130
+ .vpwa-video-loading {
131
+ position: absolute; inset: 0;
132
+ display: flex; align-items: flex-end; justify-content: center;
133
+ padding: 20px;
134
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.48), transparent 42%);
135
+ pointer-events: none;
136
+ }
137
+ .vpwa-video-loading span {
138
+ color: transparent; font-size: 12px; font-weight: 600; line-height: 1;
139
+ letter-spacing: 0.01em;
140
+ background: linear-gradient(100deg, rgba(255,255,255,0.46) 20%, #fff 45%, rgba(255,255,255,0.46) 70%);
141
+ background-size: 220% 100%; background-clip: text; -webkit-background-clip: text;
142
+ animation: vpwa-text-shimmer 1.6s ease-in-out infinite;
143
+ }
144
+ @keyframes vpwa-text-shimmer {
145
+ from { background-position: 100% 0; }
146
+ to { background-position: -100% 0; }
147
+ }
148
+ .vpwa-video-guide figcaption {
149
+ max-width: 32rem; color: var(--muted-foreground, var(--vpwa-muted));
150
+ font-size: 12px; line-height: 1.45; text-align: center;
151
+ }
152
+
112
153
  /* ── mocks ───────────────────────────────────────────────────────────── */
113
154
  .vpwa-toolbar {
114
155
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
@@ -149,6 +190,7 @@ const CSS = `
149
190
  }
150
191
  @media (prefers-reduced-motion: reduce) {
151
192
  .vpwa-target { animation: none; }
193
+ .vpwa-video-loading span { animation: none; color: rgba(255,255,255,0.82); }
152
194
  .vpwa-sheet, .vpwa-backdrop, .vpwa-banner { transition: none; }
153
195
  }
154
196