Skip to content

Commit

Permalink
fix: Avatar.Image crossorigin/referrerpolicy issues (#721)
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte authored Oct 3, 2024
1 parent c4fcb03 commit 1083bc2
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/odd-games-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"bits-ui": patch
---

fix bug preventing `crossorigin` and `referrerpolicy` attributes to work with `Avatar.Image` component
56 changes: 40 additions & 16 deletions packages/bits-ui/src/lib/bits/avatar/avatar.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { untrack } from "svelte";
import type { ReadableBox, WritableBox } from "svelte-toolbelt";
import type { HTMLImgAttributes } from "svelte/elements";
import type { AvatarImageLoadingStatus } from "./types.js";
import { createContext } from "$lib/internal/createContext.js";
import type { ReadableBoxedValues } from "$lib/internal/box.svelte.js";
Expand All @@ -10,6 +11,9 @@ const AVATAR_ROOT_ATTR = "data-avatar-root";
const AVATAR_IMAGE_ATTR = "data-avatar-image";
const AVATAR_FALLBACK_ATTR = "data-avatar-fallback";

type CrossOrigin = HTMLImgAttributes["crossorigin"];
type ReferrerPolicy = HTMLImgAttributes["referrerpolicy"];

/**
* ROOT
*/
Expand Down Expand Up @@ -38,13 +42,17 @@ class AvatarRootState {
});
}

loadImage(src: string) {
let imageTimerId: NodeJS.Timeout;
loadImage(src: string, crossorigin?: CrossOrigin, referrerPolicy?: ReferrerPolicy) {
let imageTimerId: number;
const image = new Image();

image.src = src;
if (crossorigin) image.crossOrigin = crossorigin;
if (referrerPolicy) image.referrerPolicy = referrerPolicy;

this.loadingStatus.current = "loading";
image.onload = () => {
imageTimerId = setTimeout(() => {
imageTimerId = window.setTimeout(() => {
this.loadingStatus.current = "loaded";
}, this.delayMs.current);
};
Expand Down Expand Up @@ -81,29 +89,43 @@ class AvatarRootState {
type AvatarImageStateProps = WithRefProps<
ReadableBoxedValues<{
src: AvatarImageSrc;
crossOrigin: CrossOrigin;
referrerPolicy: ReferrerPolicy;
}>
>;

class AvatarImageState {
#id: AvatarImageStateProps["id"];
#ref: AvatarImageStateProps["ref"];
src: AvatarImageStateProps["src"];
root: AvatarRootState;
#crossOrigin: AvatarImageStateProps["crossOrigin"];
#referrerPolicy: AvatarImageStateProps["referrerPolicy"];
#src: AvatarImageStateProps["src"];
#root: AvatarRootState;

constructor(props: AvatarImageStateProps, root: AvatarRootState) {
this.root = root;
this.src = props.src;
this.#root = root;
this.#src = props.src;
this.#id = props.id;
this.#ref = props.ref;
this.#crossOrigin = props.crossOrigin;
this.#referrerPolicy = props.referrerPolicy;

useRefById({
id: this.#id,
ref: this.#ref,
});

$effect.pre(() => {
if (!this.src.current) return;
untrack(() => this.root.loadImage(this.src.current ?? ""));
if (!this.#src.current) return;
// dependency on crossorigin
this.#crossOrigin.current;
untrack(() =>
this.#root.loadImage(
this.#src.current ?? "",
this.#crossOrigin.current,
this.#referrerPolicy.current
)
);
});
}

Expand All @@ -112,11 +134,13 @@ class AvatarImageState {
({
id: this.#id.current,
style: {
display: this.root.loadingStatus.current === "loaded" ? "block" : "none",
display: this.#root.loadingStatus.current === "loaded" ? "block" : "none",
},
"data-status": this.root.loadingStatus.current,
"data-status": this.#root.loadingStatus.current,
[AVATAR_IMAGE_ATTR]: "",
src: this.src.current,
src: this.#src.current,
crossorigin: this.#crossOrigin.current,
referrerpolicy: this.#referrerPolicy.current,
}) as const
);
}
Expand All @@ -130,10 +154,10 @@ type AvatarFallbackStateProps = WithRefProps;
class AvatarFallbackState {
#id: AvatarFallbackStateProps["id"];
#ref: AvatarFallbackStateProps["ref"];
root: AvatarRootState;
#root: AvatarRootState;

constructor(props: AvatarFallbackStateProps, root: AvatarRootState) {
this.root = root;
this.#root = root;
this.#id = props.id;
this.#ref = props.ref;

Expand All @@ -147,9 +171,9 @@ class AvatarFallbackState {
() =>
({
style: {
display: this.root.loadingStatus.current === "loaded" ? "none" : undefined,
display: this.#root.loadingStatus.current === "loaded" ? "none" : undefined,
},
"data-status": this.root.loadingStatus.current,
"data-status": this.#root.loadingStatus.current,
[AVATAR_FALLBACK_ATTR]: "",
}) as const
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
import { mergeProps } from "$lib/internal/mergeProps.js";
import { useId } from "$lib/internal/useId.js";
let { src, child, id = useId(), ref = $bindable(null), ...restProps }: ImageProps = $props();
let {
src,
child,
id = useId(),
ref = $bindable(null),
crossorigin = "",
referrerpolicy = undefined,
...restProps
}: ImageProps = $props();
const imageState = useAvatarImage({
src: box.with(() => src),
Expand All @@ -14,6 +22,8 @@
() => ref,
(v) => (ref = v)
),
crossOrigin: box.with(() => crossorigin),
referrerPolicy: box.with(() => referrerpolicy),
});
const mergedProps = $derived(mergeProps(restProps, imageState.props));
Expand Down
9 changes: 7 additions & 2 deletions sites/docs/src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { buildDocsIndex } from "$lib/scripts/build-search.js";

export function handle({ event, resolve }) {
return resolve(event);
export async function handle({ event, resolve }) {
const response = await resolve(event);

response.headers.set("Cross-Origin-Embedder-Policy", "require-corp");
response.headers.set("Cross-Origin-Opener-Policy", "same-origin");

return response;
}
18 changes: 15 additions & 3 deletions sites/docs/src/routes/(main)/sink/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
<script lang="ts">
import DropdownMenuDemo from "$lib/components/demos/dropdown-menu-demo.svelte";
import { Avatar } from "bits-ui";
</script>

<div class="flex items-center justify-center">
<DropdownMenuDemo />
<div class="mt-12 grid w-full justify-center gap-12">
<Avatar.Root class="h-32 w-32">
<Avatar.Image src="https://picsum.photos/200" alt="random-pic" crossorigin="anonymous" />
<Avatar.Fallback>Not loaded</Avatar.Fallback>
</Avatar.Root>

<Avatar.Root class="h-32 w-32">
<Avatar.Image
src="https://github.com/huntabyte.png"
alt="@huntabyte"
crossorigin="anonymous"
/>
<Avatar.Fallback>Not loaded</Avatar.Fallback>
</Avatar.Root>
</div>

0 comments on commit 1083bc2

Please sign in to comment.