feat: lint codebase and run precommit hook

This commit is contained in:
Rotimi Best
2025-12-25 09:01:51 +00:00
parent 334c67f22a
commit b31ccf3037
214 changed files with 6191 additions and 2880 deletions
+10 -10
View File
@@ -1,13 +1,13 @@
import Root from "./button-group.svelte";
import Text from "./button-group-text.svelte";
import Separator from "./button-group-separator.svelte";
import Root from './button-group.svelte';
import Text from './button-group-text.svelte';
import Separator from './button-group-separator.svelte';
export {
Root,
Text,
Separator,
//
Root as ButtonGroup,
Text as ButtonGroupText,
Separator as ButtonGroupSeparator,
Root,
Text,
Separator,
//
Root as ButtonGroup,
Text as ButtonGroupText,
Separator as ButtonGroupSeparator
};
+22 -22
View File
@@ -1,25 +1,25 @@
import Root from "./card.svelte";
import Content from "./card-content.svelte";
import Description from "./card-description.svelte";
import Footer from "./card-footer.svelte";
import Header from "./card-header.svelte";
import Title from "./card-title.svelte";
import Action from "./card-action.svelte";
import Root from './card.svelte';
import Content from './card-content.svelte';
import Description from './card-description.svelte';
import Footer from './card-footer.svelte';
import Header from './card-header.svelte';
import Title from './card-title.svelte';
import Action from './card-action.svelte';
export {
Root,
Content,
Description,
Footer,
Header,
Title,
Action,
//
Root as Card,
Content as CardContent,
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle,
Action as CardAction,
Root,
Content,
Description,
Footer,
Header,
Title,
Action,
//
Root as Card,
Content as CardContent,
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle,
Action as CardAction
};
@@ -1,7 +1,7 @@
<script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte";
import { Drawer as DrawerPrimitive } from 'vaul-svelte';
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.CloseProps = $props();
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.CloseProps = $props();
</script>
<DrawerPrimitive.Close bind:ref data-slot="drawer-close" {...restProps} />
@@ -1,7 +1,7 @@
<script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte";
import { Drawer as DrawerPrimitive } from 'vaul-svelte';
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.TriggerProps = $props();
let { ref = $bindable(null), ...restProps }: DrawerPrimitive.TriggerProps = $props();
</script>
<DrawerPrimitive.Trigger bind:ref data-slot="drawer-trigger" {...restProps} />
+7 -7
View File
@@ -1,12 +1,12 @@
<script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte";
import { Drawer as DrawerPrimitive } from 'vaul-svelte';
let {
shouldScaleBackground = true,
open = $bindable(false),
activeSnapPoint = $bindable(null),
...restProps
}: DrawerPrimitive.RootProps = $props();
let {
shouldScaleBackground = true,
open = $bindable(false),
activeSnapPoint = $bindable(null),
...restProps
}: DrawerPrimitive.RootProps = $props();
</script>
<DrawerPrimitive.Root {shouldScaleBackground} bind:open bind:activeSnapPoint {...restProps} />
+34 -34
View File
@@ -1,41 +1,41 @@
import { Drawer as DrawerPrimitive } from "vaul-svelte";
import { Drawer as DrawerPrimitive } from 'vaul-svelte';
import Root from "./drawer.svelte";
import Content from "./drawer-content.svelte";
import Description from "./drawer-description.svelte";
import Overlay from "./drawer-overlay.svelte";
import Footer from "./drawer-footer.svelte";
import Header from "./drawer-header.svelte";
import Title from "./drawer-title.svelte";
import NestedRoot from "./drawer-nested.svelte";
import Close from "./drawer-close.svelte";
import Trigger from "./drawer-trigger.svelte";
import Root from './drawer.svelte';
import Content from './drawer-content.svelte';
import Description from './drawer-description.svelte';
import Overlay from './drawer-overlay.svelte';
import Footer from './drawer-footer.svelte';
import Header from './drawer-header.svelte';
import Title from './drawer-title.svelte';
import NestedRoot from './drawer-nested.svelte';
import Close from './drawer-close.svelte';
import Trigger from './drawer-trigger.svelte';
const Portal: typeof DrawerPrimitive.Portal = DrawerPrimitive.Portal;
export {
Root,
NestedRoot,
Content,
Description,
Overlay,
Footer,
Header,
Title,
Trigger,
Portal,
Close,
Root,
NestedRoot,
Content,
Description,
Overlay,
Footer,
Header,
Title,
Trigger,
Portal,
Close,
//
Root as Drawer,
NestedRoot as DrawerNestedRoot,
Content as DrawerContent,
Description as DrawerDescription,
Overlay as DrawerOverlay,
Footer as DrawerFooter,
Header as DrawerHeader,
Title as DrawerTitle,
Trigger as DrawerTrigger,
Portal as DrawerPortal,
Close as DrawerClose,
//
Root as Drawer,
NestedRoot as DrawerNestedRoot,
Content as DrawerContent,
Description as DrawerDescription,
Overlay as DrawerOverlay,
Footer as DrawerFooter,
Header as DrawerHeader,
Title as DrawerTitle,
Trigger as DrawerTrigger,
Portal as DrawerPortal,
Close as DrawerClose
};
+19 -19
View File
@@ -1,22 +1,22 @@
import Root from "./empty.svelte";
import Header from "./empty-header.svelte";
import Media from "./empty-media.svelte";
import Title from "./empty-title.svelte";
import Description from "./empty-description.svelte";
import Content from "./empty-content.svelte";
import Root from './empty.svelte';
import Header from './empty-header.svelte';
import Media from './empty-media.svelte';
import Title from './empty-title.svelte';
import Description from './empty-description.svelte';
import Content from './empty-content.svelte';
export {
Root,
Header,
Media,
Title,
Description,
Content,
//
Root as Empty,
Header as EmptyHeader,
Media as EmptyMedia,
Title as EmptyTitle,
Description as EmptyDescription,
Content as EmptyContent,
Root,
Header,
Media,
Title,
Description,
Content,
//
Root as Empty,
Header as EmptyHeader,
Media as EmptyMedia,
Title as EmptyTitle,
Description as EmptyDescription,
Content as EmptyContent
};
+4 -11
View File
@@ -1,14 +1,7 @@
import { LinkPreview as HoverCardPrimitive } from "bits-ui";
import Content from "./hover-card-content.svelte";
import Trigger from "./hover-card-trigger.svelte";
import { LinkPreview as HoverCardPrimitive } from 'bits-ui';
import Content from './hover-card-content.svelte';
import Trigger from './hover-card-trigger.svelte';
const Root = HoverCardPrimitive.Root;
export {
Root,
Content,
Trigger,
Root as HoverCard,
Content as HoverCardContent,
Trigger as HoverCardTrigger,
};
export { Root, Content, Trigger, Root as HoverCard, Content as HoverCardContent, Trigger as HoverCardTrigger };
+19 -19
View File
@@ -1,22 +1,22 @@
import Root from "./input-group.svelte";
import Addon from "./input-group-addon.svelte";
import Button from "./input-group-button.svelte";
import Input from "./input-group-input.svelte";
import Text from "./input-group-text.svelte";
import Textarea from "./input-group-textarea.svelte";
import Root from './input-group.svelte';
import Addon from './input-group-addon.svelte';
import Button from './input-group-button.svelte';
import Input from './input-group-input.svelte';
import Text from './input-group-text.svelte';
import Textarea from './input-group-textarea.svelte';
export {
Root,
Addon,
Button,
Input,
Text,
Textarea,
//
Root as InputGroup,
Addon as InputGroupAddon,
Button as InputGroupButton,
Input as InputGroupInput,
Text as InputGroupText,
Textarea as InputGroupTextarea,
Root,
Addon,
Button,
Input,
Text,
Textarea,
//
Root as InputGroup,
Addon as InputGroupAddon,
Button as InputGroupButton,
Input as InputGroupInput,
Text as InputGroupText,
Textarea as InputGroupTextarea
};
+4 -4
View File
@@ -1,7 +1,7 @@
import Root from "./input.svelte";
import Root from './input.svelte';
export {
Root,
//
Root as Input,
Root,
//
Root as Input
};
+31 -31
View File
@@ -1,34 +1,34 @@
import Root from "./item.svelte";
import Group from "./item-group.svelte";
import Separator from "./item-separator.svelte";
import Header from "./item-header.svelte";
import Footer from "./item-footer.svelte";
import Content from "./item-content.svelte";
import Title from "./item-title.svelte";
import Description from "./item-description.svelte";
import Actions from "./item-actions.svelte";
import Media from "./item-media.svelte";
import Root from './item.svelte';
import Group from './item-group.svelte';
import Separator from './item-separator.svelte';
import Header from './item-header.svelte';
import Footer from './item-footer.svelte';
import Content from './item-content.svelte';
import Title from './item-title.svelte';
import Description from './item-description.svelte';
import Actions from './item-actions.svelte';
import Media from './item-media.svelte';
export {
Root,
Group,
Separator,
Header,
Footer,
Content,
Title,
Description,
Actions,
Media,
//
Root as Item,
Group as ItemGroup,
Separator as ItemSeparator,
Header as ItemHeader,
Footer as ItemFooter,
Content as ItemContent,
Title as ItemTitle,
Description as ItemDescription,
Actions as ItemActions,
Media as ItemMedia,
Root,
Group,
Separator,
Header,
Footer,
Content,
Title,
Description,
Actions,
Media,
//
Root as Item,
Group as ItemGroup,
Separator as ItemSeparator,
Header as ItemHeader,
Footer as ItemFooter,
Content as ItemContent,
Title as ItemTitle,
Description as ItemDescription,
Actions as ItemActions,
Media as ItemMedia
};
+7 -7
View File
@@ -1,10 +1,10 @@
import Root from "./kbd.svelte";
import Group from "./kbd-group.svelte";
import Root from './kbd.svelte';
import Group from './kbd-group.svelte';
export {
Root,
Group,
//
Root as Kbd,
Group as KbdGroup,
Root,
Group,
//
Root as Kbd,
Group as KbdGroup
};
+25 -25
View File
@@ -1,28 +1,28 @@
import Root from "./navigation-menu.svelte";
import Content from "./navigation-menu-content.svelte";
import Indicator from "./navigation-menu-indicator.svelte";
import Item from "./navigation-menu-item.svelte";
import Link from "./navigation-menu-link.svelte";
import List from "./navigation-menu-list.svelte";
import Trigger from "./navigation-menu-trigger.svelte";
import Viewport from "./navigation-menu-viewport.svelte";
import Root from './navigation-menu.svelte';
import Content from './navigation-menu-content.svelte';
import Indicator from './navigation-menu-indicator.svelte';
import Item from './navigation-menu-item.svelte';
import Link from './navigation-menu-link.svelte';
import List from './navigation-menu-list.svelte';
import Trigger from './navigation-menu-trigger.svelte';
import Viewport from './navigation-menu-viewport.svelte';
export {
Root,
Content,
Indicator,
Item,
Link,
List,
Trigger,
Viewport,
//
Root as NavigationMenuRoot,
Content as NavigationMenuContent,
Indicator as NavigationMenuIndicator,
Item as NavigationMenuItem,
Link as NavigationMenuLink,
List as NavigationMenuList,
Trigger as NavigationMenuTrigger,
Viewport as NavigationMenuViewport,
Root,
Content,
Indicator,
Item,
Link,
List,
Trigger,
Viewport,
//
Root as NavigationMenuRoot,
Content as NavigationMenuContent,
Indicator as NavigationMenuIndicator,
Item as NavigationMenuItem,
Link as NavigationMenuLink,
List as NavigationMenuList,
Trigger as NavigationMenuTrigger,
Viewport as NavigationMenuViewport
};
+1 -7
View File
@@ -10,12 +10,6 @@
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="page-action"
class={cn('ui:flex ui:items-center ui:gap-2', className)}
{...restProps}
>
<div bind:this={ref} data-slot="page-action" class={cn('ui:flex ui:items-center ui:gap-2', className)} {...restProps}>
{@render children?.()}
</div>
@@ -18,4 +18,3 @@
>
{@render children?.()}
</p>
@@ -5,153 +5,150 @@ import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common';
import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en';
const passwordOptions = {
translations: zxcvbnEnPackage.translations,
graphs: zxcvbnCommonPackage.adjacencyGraphs,
dictionary: {
...zxcvbnCommonPackage.dictionary,
...zxcvbnEnPackage.dictionary
}
translations: zxcvbnEnPackage.translations,
graphs: zxcvbnCommonPackage.adjacencyGraphs,
dictionary: {
...zxcvbnCommonPackage.dictionary,
...zxcvbnEnPackage.dictionary
}
};
zxcvbnOptions.setOptions(passwordOptions);
type PasswordRootStateProps = WritableBoxedValues<{
hidden: boolean;
hidden: boolean;
}> &
ReadableBoxedValues<{
minScore: number;
}>;
ReadableBoxedValues<{
minScore: number;
}>;
type PasswordState = {
value: string;
copyMounted: boolean;
toggleMounted: boolean;
strengthMounted: boolean;
tainted: boolean;
value: string;
copyMounted: boolean;
toggleMounted: boolean;
strengthMounted: boolean;
tainted: boolean;
};
const defaultPasswordState: PasswordState = {
value: '',
copyMounted: false,
toggleMounted: false,
strengthMounted: false,
tainted: false
value: '',
copyMounted: false,
toggleMounted: false,
strengthMounted: false,
tainted: false
};
class PasswordRootState {
passwordState = $state(defaultPasswordState);
passwordState = $state(defaultPasswordState);
constructor(readonly opts: PasswordRootStateProps) {}
constructor(readonly opts: PasswordRootStateProps) {}
// only re-run when the password changes
strength = $derived.by(() => zxcvbn(this.passwordState.value));
// only re-run when the password changes
strength = $derived.by(() => zxcvbn(this.passwordState.value));
}
type PasswordInputStateProps = WritableBoxedValues<{
value: string;
value: string;
}> &
ReadableBoxedValues<{
ref: HTMLInputElement | null;
}>;
ReadableBoxedValues<{
ref: HTMLInputElement | null;
}>;
class PasswordInputState {
constructor(
readonly root: PasswordRootState,
readonly opts: PasswordInputStateProps
) {
watch(
() => this.opts.value.current,
() => {
if (this.root.passwordState.value !== this.opts.value.current) {
this.root.passwordState.tainted = true;
this.root.passwordState.value = this.opts.value.current;
}
}
);
constructor(
readonly root: PasswordRootState,
readonly opts: PasswordInputStateProps
) {
watch(
() => this.opts.value.current,
() => {
if (this.root.passwordState.value !== this.opts.value.current) {
this.root.passwordState.tainted = true;
this.root.passwordState.value = this.opts.value.current;
}
}
);
$effect(() => {
if (!this.root.passwordState.strengthMounted) return;
$effect(() => {
if (!this.root.passwordState.strengthMounted) return;
// if the password is empty, we let the `required` attribute handle the validation
if (
this.root.passwordState.value !== '' &&
this.root.strength.score < this.root.opts.minScore.current
) {
this.opts.ref.current?.setCustomValidity('Password is too weak');
} else {
this.opts.ref.current?.setCustomValidity('');
}
});
}
// if the password is empty, we let the `required` attribute handle the validation
if (this.root.passwordState.value !== '' && this.root.strength.score < this.root.opts.minScore.current) {
this.opts.ref.current?.setCustomValidity('Password is too weak');
} else {
this.opts.ref.current?.setCustomValidity('');
}
});
}
props = $derived.by(() => ({
'aria-invalid':
this.root.strength.score < this.root.opts.minScore.current &&
this.root.passwordState.tainted &&
this.root.passwordState.strengthMounted
}));
props = $derived.by(() => ({
'aria-invalid':
this.root.strength.score < this.root.opts.minScore.current &&
this.root.passwordState.tainted &&
this.root.passwordState.strengthMounted
}));
}
class PasswordToggleVisibilityState {
constructor(readonly root: PasswordRootState) {
this.root.passwordState.toggleMounted = true;
constructor(readonly root: PasswordRootState) {
this.root.passwordState.toggleMounted = true;
// this way we go back to the correct padding when toggle is unmounted
$effect(() => {
return () => {
this.root.passwordState.toggleMounted = false;
};
});
}
// this way we go back to the correct padding when toggle is unmounted
$effect(() => {
return () => {
this.root.passwordState.toggleMounted = false;
};
});
}
}
class PasswordCopyState {
constructor(readonly root: PasswordRootState) {
this.root.passwordState.copyMounted = true;
constructor(readonly root: PasswordRootState) {
this.root.passwordState.copyMounted = true;
// this way we go back to the correct padding when copy is unmounted
$effect(() => {
return () => {
this.root.passwordState.copyMounted = false;
};
});
}
// this way we go back to the correct padding when copy is unmounted
$effect(() => {
return () => {
this.root.passwordState.copyMounted = false;
};
});
}
}
class PasswordStrengthState {
constructor(readonly root: PasswordRootState) {
this.root.passwordState.strengthMounted = true;
constructor(readonly root: PasswordRootState) {
this.root.passwordState.strengthMounted = true;
$effect(() => {
return () => {
this.root.passwordState.strengthMounted = false;
};
});
}
$effect(() => {
return () => {
this.root.passwordState.strengthMounted = false;
};
});
}
get strength() {
return this.root.strength;
}
get strength() {
return this.root.strength;
}
}
const ctx = new Context<PasswordRootState>('password-root-state');
export function usePassword(props: PasswordRootStateProps) {
return ctx.set(new PasswordRootState(props));
return ctx.set(new PasswordRootState(props));
}
export function usePasswordInput(props: PasswordInputStateProps) {
return new PasswordInputState(ctx.get(), props);
return new PasswordInputState(ctx.get(), props);
}
export function usePasswordToggleVisibility() {
return new PasswordToggleVisibilityState(ctx.get());
return new PasswordToggleVisibilityState(ctx.get());
}
export function usePasswordCopy() {
return new PasswordCopyState(ctx.get());
return new PasswordCopyState(ctx.get());
}
export function usePasswordStrength() {
return new PasswordStrengthState(ctx.get());
return new PasswordStrengthState(ctx.get());
}
+18 -25
View File
@@ -1,47 +1,40 @@
import type {
WithChildren,
WithoutChildren,
Meter as MeterPrimitive,
Toggle as TogglePrimitive
} from 'bits-ui';
import type { WithChildren, WithoutChildren, Meter as MeterPrimitive, Toggle as TogglePrimitive } from 'bits-ui';
import type { HTMLAttributes, HTMLInputAttributes } from 'svelte/elements';
import type { CopyButtonProps } from '../copy-button/types';
import type { ZxcvbnResult } from '@zxcvbn-ts/core';
export type PasswordRootPropsWithoutHTML = WithChildren<{
ref?: HTMLDivElement | null;
hidden?: boolean;
/** The minimum acceptable score for a password. (0-4)
*
* @default 3
*/
minScore?: 0 | 1 | 2 | 3 | 4;
ref?: HTMLDivElement | null;
hidden?: boolean;
/** The minimum acceptable score for a password. (0-4)
*
* @default 3
*/
minScore?: 0 | 1 | 2 | 3 | 4;
}>;
export type PasswordRootProps = WithoutChildren<HTMLAttributes<HTMLDivElement>> &
PasswordRootPropsWithoutHTML;
export type PasswordRootProps = WithoutChildren<HTMLAttributes<HTMLDivElement>> & PasswordRootPropsWithoutHTML;
export type PasswordInputPropsWithoutHTML = WithChildren<{
ref?: HTMLInputElement | null;
value?: string;
ref?: HTMLInputElement | null;
value?: string;
}>;
export type PasswordInputProps = Omit<
WithoutChildren<HTMLInputAttributes>,
'type' | 'files' | 'aria-invalid' | 'value'
WithoutChildren<HTMLInputAttributes>,
'type' | 'files' | 'aria-invalid' | 'value'
> &
PasswordInputPropsWithoutHTML;
PasswordInputPropsWithoutHTML;
export type PasswordToggleVisibilityProps = Omit<
TogglePrimitive.RootProps,
'children' | 'pressed' | 'aria-label' | 'tabindex'
TogglePrimitive.RootProps,
'children' | 'pressed' | 'aria-label' | 'tabindex'
>;
export type PasswordCopyButtonProps = Omit<CopyButtonProps, 'children' | 'text'>;
export type PasswordStrengthPropsWithoutHTML = {
strength?: ZxcvbnResult;
strength?: ZxcvbnResult;
};
export type PasswordStrengthProps = PasswordStrengthPropsWithoutHTML &
WithoutChildren<MeterPrimitive.RootProps>;
export type PasswordStrengthProps = PasswordStrengthPropsWithoutHTML & WithoutChildren<MeterPrimitive.RootProps>;
+4 -4
View File
@@ -1,7 +1,7 @@
import Root from "./separator.svelte";
import Root from './separator.svelte';
export {
Root,
//
Root as Separator,
Root,
//
Root as Separator
};
+4 -4
View File
@@ -1,7 +1,7 @@
import Root from "./textarea.svelte";
import Root from './textarea.svelte';
export {
Root,
//
Root as Textarea,
Root,
//
Root as Textarea
};
+4 -9
View File
@@ -1,13 +1,8 @@
import Root from './toggle.svelte';
export {
toggleVariants,
type ToggleSize,
type ToggleVariant,
type ToggleVariants
} from './toggle.svelte';
export { toggleVariants, type ToggleSize, type ToggleVariant, type ToggleVariants } from './toggle.svelte';
export {
Root,
//
Root as Toggle
Root,
//
Root as Toggle
};
@@ -2,4 +2,3 @@ import DomainInput from './domain-input.svelte';
export { DomainInput };
export { DomainInput as default };
@@ -8,12 +8,7 @@
tooltipSide?: 'top' | 'bottom' | 'left' | 'right';
}
let {
tooltip,
tooltipSide = 'top',
children,
...buttonProps
}: Props = $props();
let { tooltip, tooltipSide = 'top', children, ...buttonProps }: Props = $props();
</script>
{#if tooltip}
@@ -34,4 +29,3 @@
{@render children?.()}
</Button>
{/if}
@@ -2,4 +2,3 @@ import IconButton from './icon-button.svelte';
export { IconButton };
export { IconButton as default };
+51 -55
View File
@@ -1,32 +1,32 @@
import type { CropArea } from 'svelte-easy-crop';
export const getFileFromUrl = async (url: string, fileName = 'cropped.png'): Promise<File> => {
// Fetch the file data from the URL
const response = await fetch(url);
// Fetch the file data from the URL
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch resource: ${response.status} ${response.statusText}`);
}
if (!response.ok) {
throw new Error(`Failed to fetch resource: ${response.status} ${response.statusText}`);
}
// Convert the response into a Blob
const blob = await response.blob();
// Convert the response into a Blob
const blob = await response.blob();
// Create and return a File. You can set a custom type if needed.
return new File([blob], fileName, { type: blob.type });
// Create and return a File. You can set a custom type if needed.
return new File([blob], fileName, { type: blob.type });
};
const createImage = (url: string): Promise<HTMLImageElement> => {
return new Promise<HTMLImageElement>((resolve, reject) => {
const image = new Image();
image.addEventListener('load', () => resolve(image));
image.addEventListener('error', (error) => reject(error));
image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox
image.src = url;
});
return new Promise<HTMLImageElement>((resolve, reject) => {
const image = new Image();
image.addEventListener('load', () => resolve(image));
image.addEventListener('error', (error) => reject(error));
image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox
image.src = url;
});
};
const getRadianAngle = (degreeValue: number) => {
return (degreeValue * Math.PI) / 180;
return (degreeValue * Math.PI) / 180;
};
/** Gets the cropped image from the src using the cropped area
@@ -36,50 +36,46 @@ const getRadianAngle = (degreeValue: number) => {
* @param rotation
* @returns
*/
export const getCroppedImg = async (
imageSrc: string,
pixelCrop: CropArea,
rotation = 0
): Promise<string> => {
const image = await createImage(imageSrc);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
export const getCroppedImg = async (imageSrc: string, pixelCrop: CropArea, rotation = 0): Promise<string> => {
const image = await createImage(imageSrc);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Error getting 2d rendering context');
}
if (!ctx) {
throw new Error('Error getting 2d rendering context');
}
const maxSize = Math.max(image.width, image.height);
const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2));
const maxSize = Math.max(image.width, image.height);
const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2));
// set each dimensions to double largest dimension to allow for a safe area for the
// image to rotate in without being clipped by canvas context
canvas.width = safeArea;
canvas.height = safeArea;
// set each dimensions to double largest dimension to allow for a safe area for the
// image to rotate in without being clipped by canvas context
canvas.width = safeArea;
canvas.height = safeArea;
// translate canvas context to a central location on image to allow rotating around the center.
ctx.translate(safeArea / 2, safeArea / 2);
ctx.rotate(getRadianAngle(rotation));
ctx.translate(-safeArea / 2, -safeArea / 2);
// translate canvas context to a central location on image to allow rotating around the center.
ctx.translate(safeArea / 2, safeArea / 2);
ctx.rotate(getRadianAngle(rotation));
ctx.translate(-safeArea / 2, -safeArea / 2);
// draw rotated image and store data.
ctx.drawImage(image, safeArea / 2 - image.width * 0.5, safeArea / 2 - image.height * 0.5);
const data = ctx.getImageData(0, 0, safeArea, safeArea);
// draw rotated image and store data.
ctx.drawImage(image, safeArea / 2 - image.width * 0.5, safeArea / 2 - image.height * 0.5);
const data = ctx.getImageData(0, 0, safeArea, safeArea);
// set canvas width to final desired crop size - this will clear existing context
canvas.width = pixelCrop.width;
canvas.height = pixelCrop.height;
// set canvas width to final desired crop size - this will clear existing context
canvas.width = pixelCrop.width;
canvas.height = pixelCrop.height;
// paste generated rotate image with correct offsets for x,y crop values.
ctx.putImageData(
data,
Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x),
Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y)
);
// paste generated rotate image with correct offsets for x,y crop values.
ctx.putImageData(
data,
Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x),
Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y)
);
return new Promise((resolve) => {
canvas.toBlob((file) => {
resolve(URL.createObjectURL(file!));
}, 'image/png');
});
return new Promise((resolve) => {
canvas.toBlob((file) => {
resolve(URL.createObjectURL(file!));
}, 'image/png');
});
};
@@ -2,4 +2,3 @@ import InputField from './input-field.svelte';
export { InputField };
export { InputField as default };
-1
View File
@@ -2,4 +2,3 @@ import Password from './password.svelte';
export { Password };
export { Password as default };
@@ -50,7 +50,7 @@
bind:value
{disabled}
aria-invalid={ariaInvalid}
autocomplete={autocomplete}
{autocomplete}
{...restProps}
/>
<InputGroup.Addon align="inline-end">
@@ -77,4 +77,3 @@
</Tooltip.Provider>
</InputGroup.Addon>
</InputGroup.Root>
-1
View File
@@ -2,4 +2,3 @@ import Search from './search.svelte';
export { Search };
export { Search as default };
@@ -4,13 +4,13 @@ import List from './underline-tabs-list.svelte';
import Trigger from './underline-tabs-trigger.svelte';
export {
Root,
Content,
List,
Trigger,
//
Root as UnderlineTabs,
Content as UnderlineTabsContent,
List as UnderlineTabsList,
Trigger as UnderlineTabsTrigger
Root,
Content,
List,
Trigger,
//
Root as UnderlineTabs,
Content as UnderlineTabsContent,
List as UnderlineTabsList,
Trigger as UnderlineTabsTrigger
};
@@ -4,85 +4,85 @@ import type { Tabs as TabsPrimitive } from 'bits-ui';
import { crossfade } from 'svelte/transition';
export const [send, receive] = crossfade({
duration: (d) => Math.sqrt(d * 200)
duration: (d) => Math.sqrt(d * 200)
});
type UnderlineTabsRootProps = WritableBoxedValues<{
value: string;
value: string;
}> &
ReadableBoxedValues<{
id: string;
}>;
ReadableBoxedValues<{
id: string;
}>;
class UnderlineTabsRootState {
hoveredTab = $state<string | null>(null);
isHovered = $state(false);
constructor(readonly opts: UnderlineTabsRootProps) {
this.hoveredTab = this.opts.value.current;
}
hoveredTab = $state<string | null>(null);
isHovered = $state(false);
constructor(readonly opts: UnderlineTabsRootProps) {
this.hoveredTab = this.opts.value.current;
}
}
type UnderlineTabsTriggerProps = ReadableBoxedValues<{
value: string;
onmouseenter: TabsPrimitive.TriggerProps['onmouseenter'];
onmouseleave: TabsPrimitive.TriggerProps['onmouseleave'];
onfocus: TabsPrimitive.TriggerProps['onfocus'];
onblur: TabsPrimitive.TriggerProps['onblur'];
value: string;
onmouseenter: TabsPrimitive.TriggerProps['onmouseenter'];
onmouseleave: TabsPrimitive.TriggerProps['onmouseleave'];
onfocus: TabsPrimitive.TriggerProps['onfocus'];
onblur: TabsPrimitive.TriggerProps['onblur'];
}>;
class UnderlineTabsTriggerState {
constructor(
readonly opts: UnderlineTabsTriggerProps,
readonly rootState: UnderlineTabsRootState
) {}
constructor(
readonly opts: UnderlineTabsTriggerProps,
readonly rootState: UnderlineTabsRootState
) {}
handleFocus() {
this.rootState.isHovered = true;
this.rootState.hoveredTab = this.opts.value.current;
}
handleFocus() {
this.rootState.isHovered = true;
this.rootState.hoveredTab = this.opts.value.current;
}
handleBlur() {
if (this.rootState.hoveredTab === this.opts.value.current) {
this.rootState.isHovered = false;
}
}
handleBlur() {
if (this.rootState.hoveredTab === this.opts.value.current) {
this.rootState.isHovered = false;
}
}
onmouseenter(e: Parameters<NonNullable<TabsPrimitive.TriggerProps['onmouseenter']>>[0]) {
this.handleFocus();
this.opts.onmouseenter.current?.(e);
}
onmouseenter(e: Parameters<NonNullable<TabsPrimitive.TriggerProps['onmouseenter']>>[0]) {
this.handleFocus();
this.opts.onmouseenter.current?.(e);
}
onmouseleave(e: Parameters<NonNullable<TabsPrimitive.TriggerProps['onmouseleave']>>[0]) {
this.handleBlur();
this.opts.onmouseleave.current?.(e);
}
onmouseleave(e: Parameters<NonNullable<TabsPrimitive.TriggerProps['onmouseleave']>>[0]) {
this.handleBlur();
this.opts.onmouseleave.current?.(e);
}
onfocus(e: Parameters<NonNullable<TabsPrimitive.TriggerProps['onfocus']>>[0]) {
this.handleFocus();
this.opts.onfocus.current?.(e);
}
onfocus(e: Parameters<NonNullable<TabsPrimitive.TriggerProps['onfocus']>>[0]) {
this.handleFocus();
this.opts.onfocus.current?.(e);
}
onblur(e: Parameters<NonNullable<TabsPrimitive.TriggerProps['onblur']>>[0]) {
this.handleBlur();
onblur(e: Parameters<NonNullable<TabsPrimitive.TriggerProps['onblur']>>[0]) {
this.handleBlur();
this.opts.onblur.current?.(e);
}
this.opts.onblur.current?.(e);
}
props = $derived.by(() => ({
value: this.opts.value.current,
onmouseenter: this.onmouseenter.bind(this),
onmouseleave: this.onmouseleave.bind(this),
onfocus: this.onfocus.bind(this),
onblur: this.onblur.bind(this)
}));
props = $derived.by(() => ({
value: this.opts.value.current,
onmouseenter: this.onmouseenter.bind(this),
onmouseleave: this.onmouseleave.bind(this),
onfocus: this.onfocus.bind(this),
onblur: this.onblur.bind(this)
}));
}
const ctx = new Context<UnderlineTabsRootState>('underline-tabs-root-ctx');
export function useUnderlineTabs(opts: UnderlineTabsRootProps) {
return ctx.set(new UnderlineTabsRootState(opts));
return ctx.set(new UnderlineTabsRootState(opts));
}
export function useUnderlineTabsTrigger(opts: UnderlineTabsTriggerProps) {
return new UnderlineTabsTriggerState(opts, ctx.get());
return new UnderlineTabsTriggerState(opts, ctx.get());
}
+47 -47
View File
@@ -1,6 +1,6 @@
type Options = {
/** The time before the copied status is reset. */
delay: number;
/** The time before the copied status is reset. */
delay: number;
};
/** Use this hook to copy text to the clipboard and show a copied state.
@@ -26,58 +26,58 @@ type Options = {
*
*/
export class UseClipboard {
#copiedStatus = $state<'success' | 'failure'>();
private delay: number;
private timeout: ReturnType<typeof setTimeout> | undefined = undefined;
#copiedStatus = $state<'success' | 'failure'>();
private delay: number;
private timeout: ReturnType<typeof setTimeout> | undefined = undefined;
constructor({ delay = 500 }: Partial<Options> = {}) {
this.delay = delay;
}
constructor({ delay = 500 }: Partial<Options> = {}) {
this.delay = delay;
}
/** Copies the given text to the users clipboard.
*
* ## Usage
* ```ts
* clipboard.copy('Hello, World!');
* ```
*
* @param text
* @returns
*/
async copy(text: string) {
if (this.timeout) {
this.#copiedStatus = undefined;
clearTimeout(this.timeout);
}
/** Copies the given text to the users clipboard.
*
* ## Usage
* ```ts
* clipboard.copy('Hello, World!');
* ```
*
* @param text
* @returns
*/
async copy(text: string) {
if (this.timeout) {
this.#copiedStatus = undefined;
clearTimeout(this.timeout);
}
try {
await navigator.clipboard.writeText(text);
try {
await navigator.clipboard.writeText(text);
this.#copiedStatus = 'success';
this.#copiedStatus = 'success';
this.timeout = setTimeout(() => {
this.#copiedStatus = undefined;
}, this.delay);
} catch {
// an error can occur when not in the browser or if the user hasn't given clipboard access
this.#copiedStatus = 'failure';
this.timeout = setTimeout(() => {
this.#copiedStatus = undefined;
}, this.delay);
} catch {
// an error can occur when not in the browser or if the user hasn't given clipboard access
this.#copiedStatus = 'failure';
this.timeout = setTimeout(() => {
this.#copiedStatus = undefined;
}, this.delay);
}
this.timeout = setTimeout(() => {
this.#copiedStatus = undefined;
}, this.delay);
}
return this.#copiedStatus;
}
return this.#copiedStatus;
}
/** true when the user has just copied to the clipboard. */
get copied() {
return this.#copiedStatus === 'success';
}
/** true when the user has just copied to the clipboard. */
get copied() {
return this.#copiedStatus === 'success';
}
/** Indicates whether a copy has occurred
* and gives a status of either `success` or `failure`. */
get status() {
return this.#copiedStatus;
}
/** Indicates whether a copy has occurred
* and gives a status of either `success` or `failure`. */
get status() {
return this.#copiedStatus;
}
}
File diff suppressed because one or more lines are too long