@stoker-platform/web-app 0.5.212 → 0.5.214

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @stoker-platform/web-app
2
2
 
3
+ ## 0.5.214
4
+
5
+ ### Patch Changes
6
+
7
+ - fix: fix Safari form image flicker
8
+
9
+ ## 0.5.213
10
+
11
+ ### Patch Changes
12
+
13
+ - fix: fix collection change UI flicker
14
+
3
15
  ## 0.5.212
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoker-platform/web-app",
3
- "version": "0.5.212",
3
+ "version": "0.5.214",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "scripts": {
@@ -2051,6 +2051,9 @@ function Collection({
2051
2051
  relationList ? "xl:flex-row" : "lg:flex-row",
2052
2052
  )}
2053
2053
  >
2054
+ {!isInitialized && !relationList && (
2055
+ <div className="hidden lg:block h-9 shrink-0" aria-hidden="true" />
2056
+ )}
2054
2057
  {isInitialized && (
2055
2058
  <>
2056
2059
  {formList && (
@@ -14,10 +14,37 @@ import { Check, X } from "lucide-react"
14
14
  import { LoadingSpinner } from "@/components/ui/loading-spinner"
15
15
  import { Button } from "@/components/ui/button"
16
16
  import { preloadCacheEnabled } from "./preloadCacheEnabled"
17
- import { useState } from "react"
17
+ import { useEffect, useRef, useState } from "react"
18
18
  import { cn } from "@/lib/utils"
19
19
  import { getSafeUrl } from "./isSafeUrl"
20
20
 
21
+ const FormattedFieldImage = ({ src, alt, form }: { src: string; alt?: string; form?: boolean }) => {
22
+ const imgRef = useRef<HTMLImageElement | null>(null)
23
+ const [loadedSrc, setLoadedSrc] = useState<string | null>(null)
24
+ const isLoaded = loadedSrc === src
25
+
26
+ useEffect(() => {
27
+ if (imgRef.current?.complete && imgRef.current.naturalWidth > 0) {
28
+ setLoadedSrc(src)
29
+ }
30
+ }, [src])
31
+
32
+ return (
33
+ <img
34
+ ref={imgRef}
35
+ src={getSafeUrl(src)}
36
+ alt={alt || ""}
37
+ decoding="async"
38
+ onLoad={() => setLoadedSrc(src)}
39
+ className={cn(
40
+ isLoaded ? "opacity-100" : "opacity-0",
41
+ form ? "max-h-[300px]" : "max-h-[60px]",
42
+ "transition-opacity duration-200 ease-in-out max-w-full object-contain rounded",
43
+ )}
44
+ />
45
+ )
46
+ }
47
+
21
48
  export const getFormattedFieldValue = (
22
49
  customization: CollectionCustomization,
23
50
  field: CollectionField,
@@ -120,22 +147,6 @@ export const getFormattedFieldValue = (
120
147
  }
121
148
  }
122
149
 
123
- const Image = ({ src, alt }: { src: string; alt?: string }) => {
124
- const [loaded, setLoaded] = useState(false)
125
- return (
126
- <img
127
- src={getSafeUrl(src)}
128
- alt={alt || ""}
129
- onLoad={() => setLoaded(true)}
130
- className={cn(
131
- loaded ? "opacity-100" : "opacity-0",
132
- form ? "max-h-[300px]" : "max-h-[60px]",
133
- "transition-opacity duration-300 ease-in-out max-w-full object-contain rounded",
134
- )}
135
- />
136
- )
137
- }
138
-
139
150
  if (modified) {
140
151
  if (value === "tick") {
141
152
  return (
@@ -234,7 +245,7 @@ export const getFormattedFieldValue = (
234
245
  if (image) {
235
246
  return (
236
247
  <div className="w-full min-w-[100px]">
237
- <Image src={value} alt={field.name} />
248
+ <FormattedFieldImage src={value} alt={field.name} form={form} />
238
249
  </div>
239
250
  )
240
251
  }
@@ -1,8 +1,13 @@
1
1
  import { flushSync } from "react-dom"
2
2
 
3
+ const isWebKit =
4
+ typeof navigator !== "undefined" &&
5
+ /AppleWebKit/.test(navigator.userAgent) &&
6
+ !/Chrom(e|ium)|Edg|OPR|Android/.test(navigator.userAgent)
7
+
3
8
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
9
  export const runViewTransition = (callback: () => any) => {
5
- if (document.startViewTransition && document.visibilityState === "visible") {
10
+ if (!isWebKit && document.startViewTransition && document.visibilityState === "visible") {
6
11
  try {
7
12
  const transition = document.startViewTransition(() => {
8
13
  flushSync(() => {