Migrates to svelte 5

This commit is contained in:
Christian Beutel
2025-01-24 17:46:13 +01:00
parent 7571f6e9b9
commit 3721b7f537
106 changed files with 5524 additions and 4232 deletions

View File

@@ -1,15 +1,38 @@
<script lang="ts">
export let type: "button" | "submit" | "reset" | null | undefined =
undefined;
export let icon: string = "";
export let extraClasses: string = "";
export let primary: boolean = false;
export let secondary: boolean = false;
export let large: boolean = false;
export let loading: boolean = false;
export let disabled: boolean = false;
export let tooltip: string | undefined = undefined;
export let id: string | undefined = undefined
import type { Snippet } from "svelte";
import type { MouseEventHandler } from "svelte/elements";
import { createBubbler } from "svelte/legacy";
const bubble = createBubbler();
interface Props {
type?: "button" | "submit" | "reset" | null | undefined;
icon?: string;
extraClasses?: string;
primary?: boolean;
secondary?: boolean;
large?: boolean;
loading?: boolean;
disabled?: boolean;
tooltip?: string | undefined;
id?: string | undefined;
children?: Snippet;
onclick?: MouseEventHandler<HTMLButtonElement>;
}
let {
type = undefined,
icon = "",
extraClasses = "",
primary = false,
secondary = false,
large = false,
loading = false,
disabled = false,
tooltip = undefined,
id = undefined,
children,
onclick = undefined,
}: Props = $props();
</script>
<button
@@ -21,7 +44,7 @@
disabled={disabled || loading}
class:tooltip={(tooltip?.length ?? 0) > 0}
data-title={tooltip}
on:click
{onclick}
{type}
{id}
>
@@ -29,7 +52,7 @@
{#if icon}
<i class="fa fa-{icon} mr-2"></i>
{/if}
<slot />
{@render children?.()}
</div>
{#if loading}
<div class="absolute aspect-square spinner"></div>

View File

@@ -1,22 +1,22 @@
<script lang="ts">
import type { SummitLog } from "$lib/models/summit_log";
import { createEventDispatcher } from "svelte";
import { isSameDay, isToday } from "../../util/date_util";
import { _ } from "svelte-i18n";
export let logs: SummitLog[] = [];
export let colorMap: Record<string, string> = {};
interface Props {
logs?: SummitLog[];
colorMap?: Record<string, string>;
onforward?: (data: { start: Date; end: Date }) => void;
onbackward?: (data: { start: Date; end: Date }) => void;
onclick?: (date: Date) => void;
}
const dispatch = createEventDispatcher<{
forward: {
start: Date;
end: Date;
};
backward: {
start: Date;
end: Date;
};
click: Date;
}>();
let {
logs = [],
colorMap = {},
onforward,
onbackward,
onclick,
}: Props = $props();
const weekdays = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
const months = [
@@ -34,14 +34,13 @@
"Dezember",
];
const today = new Date();
let currentMonth = today.getMonth();
let currentYear = today.getFullYear();
let currentMonth = $state(today.getMonth());
let currentYear = $state(today.getFullYear());
let currentMonthArray: ({
date: Date | undefined;
today: boolean;
log?: SummitLog;
} | null)[];
$: currentMonthArray = generateMonthArray(currentYear, currentMonth, logs);
} | null)[] = $derived(generateMonthArray(currentYear, currentMonth, logs));
function calculateFirstDayOfMonthDayOfWeek(year: number, month: number) {
const date = new Date(year, month, 1);
@@ -94,7 +93,7 @@
} else {
currentMonth++;
}
dispatch("forward", {
onforward?.({
start: new Date(currentYear, currentMonth, 1),
end: new Date(currentYear, currentMonth + 1, 0),
});
@@ -107,7 +106,7 @@
} else {
currentMonth--;
}
dispatch("backward", {
onbackward?.({
start: new Date(currentYear, currentMonth, 1),
end: new Date(currentYear, currentMonth + 1, 0),
});
@@ -124,7 +123,7 @@
if (!date) {
return;
}
dispatch("click", date);
onclick?.(date);
}
</script>
@@ -133,10 +132,12 @@
<span class="text-lg">{months[currentMonth]}</span>
<span>{currentYear}</span>
</div>
<button class="btn-icon mr-2" on:click={monthMinus}
><i class="fa fa-caret-left"></i></button
<button
aria-label="Previous month"
class="btn-icon mr-2"
onclick={monthMinus}><i class="fa fa-caret-left"></i></button
>
<button class="btn-icon" on:click={monthPlus}
<button aria-label="Next month" class="btn-icon" onclick={monthPlus}
><i class="fa fa-caret-right"></i></button
>
</div>
@@ -157,7 +158,7 @@
{#each { length: 42 } as _, i}
<button
class="calendar-day flex items-center justify-center rounded-xl"
on:click={() => handleDateClick(currentMonthArray[i]?.date)}
onclick={() => handleDateClick(currentMonthArray[i]?.date)}
class:today={currentMonthArray[i]?.today}
style="background-color: {colorMap[
colorKey(currentMonthArray, i)

View File

@@ -1,4 +1,4 @@
<script context="module" lang="ts">
<script module lang="ts">
export type ComboboxItem = {
text: string;
value: any;
@@ -7,22 +7,41 @@
</script>
<script lang="ts">
import { createEventDispatcher, tick } from "svelte";
import type { ChangeEventHandler } from "svelte/elements";
import TextField from "./text_field.svelte";
import { tick } from "svelte";
export let name: string = "";
export let icon: string = "";
export let label: string = "";
export let value: string = "";
export let items: ComboboxItem[] = [];
export let placeholder: string = "";
export let extraClasses: string = "";
interface Props {
name?: string;
icon?: string;
label?: string;
value?: string;
items?: ComboboxItem[];
placeholder?: string;
extraClasses?: string;
onchange?: ChangeEventHandler<HTMLInputElement>;
onupdate?: (q: string) => void;
onclick?: (item: ComboboxItem) => void;
}
const dispatch = createEventDispatcher();
let {
name = "",
icon = "",
label = "",
value = $bindable(""),
items = [],
placeholder = "",
extraClasses = "",
onchange,
onupdate,
onclick,
}: Props = $props();
let searching: boolean = false;
let searching: boolean = $state(false);
$: dropDownOpen = value.length > 0 && items.length > 0 && searching;
let dropDownOpen = $derived(
value.length > 0 && items.length > 0 && searching,
);
async function onSearchType() {
await tick();
@@ -45,12 +64,13 @@
}
function update(q: string) {
dispatch("update", q);
onupdate?.(q);
}
function handleItemClick(item: ComboboxItem) {
function handleItemClick(e: Event, item: ComboboxItem) {
e.stopPropagation();
value = item.value;
dispatch("click", item);
onclick?.(item);
}
</script>
@@ -63,10 +83,10 @@
{label}
{placeholder}
bind:value
on:change
on:input={onSearchType}
on:focusin={() => (searching = true)}
on:focusout={() => (searching = false)}
{onchange}
oninput={onSearchType}
onfocusin={() => (searching = true)}
onfocusout={() => (searching = false)}
></TextField>
{#if dropDownOpen}
@@ -76,13 +96,13 @@
style="z-index: 1001"
>
{#each items as item}
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<li
class="menu-item flex items-center px-4 py-3 cursor-pointer hover:bg-menu-item-background-hover focus:bg-menu-item-background-focus transition-colors"
tabindex="0"
on:mousedown|stopPropagation={() => handleItemClick(item)}
on:keydown|stopPropagation={() => handleItemClick(item)}
onmousedown={(e) => handleItemClick(e, item)}
onkeydown={(e) => handleItemClick(e, item)}
>
<i class="fa fa-{item.icon} mr-6"></i>

View File

@@ -1,10 +1,22 @@
<script lang="ts">
import { _ } from "svelte-i18n";
import type { ChangeEventHandler } from "svelte/elements";
export let name: string = "";
export let value: string | Date = "";
export let label: string = "";
export let error: string | string[] | null = "";
interface Props {
name?: string;
value?: string | Date;
label?: string;
error?: string | string[] | null;
onchange?: ChangeEventHandler<HTMLInputElement>;
}
let {
name = "",
value = $bindable(),
label = "",
error = "",
onchange
}: Props = $props();
</script>
<div>
@@ -21,7 +33,7 @@
class:bg-input-background-error={(error?.length ?? 0) > 0}
type="date"
bind:value
on:change
{onchange}
/>
</div>

View File

@@ -1,17 +1,25 @@
<script lang="ts">
import * as noUiSlider from "nouislider";
import "nouislider/dist/nouislider.css";
import { createEventDispatcher, onMount } from "svelte";
import { onMount } from "svelte";
export let minValue = 0;
export let maxValue = 100;
interface Props {
minValue?: number;
maxValue?: number;
currentMin?: any;
currentMax?: any;
onset?: (data: [number, number]) => void;
}
export let currentMin = minValue;
export let currentMax = maxValue;
let {
minValue = 0,
maxValue = 100,
currentMin = $bindable(minValue),
currentMax = $bindable(maxValue),
onset,
}: Props = $props();
let sliderContainer: any;
const dispatch = createEventDispatcher();
let sliderContainer: any = $state();
onMount(() => {
const updateValues = (values: string[]) => {
@@ -31,7 +39,7 @@
sliderContainer.noUiSlider.on("update", updateValues);
sliderContainer.noUiSlider.on("set", () => {
dispatch("set", [currentMin, currentMax]);
onset?.([currentMin, currentMax]);
});
});
</script>

View File

@@ -1,21 +1,30 @@
<script lang="ts">
import { browser } from "$app/environment";
import { beforeNavigate } from "$app/navigation";
import type { Snippet } from 'svelte';
import { backIn, backOut } from "svelte/easing";
import { slide } from "svelte/transition";
export let open: boolean = false;
$: if (open && browser) {
document.body.style.position = "fixed";
document.body.style.top = `-${window.scrollY}px`;
} else if (browser) {
const scrollY = document.body.style.top;
document.body.style.position = "";
document.body.style.top = "";
window.scrollTo(0, parseInt(scrollY || "0") * -1);
interface Props {
open?: boolean;
children?: Snippet;
}
let { open = $bindable(false), children }: Props = $props();
$effect(() => {
if (open && browser) {
document.body.style.position = "fixed";
document.body.style.top = `-${window.scrollY}px`;
} else if (browser) {
const scrollY = document.body.style.top;
document.body.style.position = "";
document.body.style.top = "";
window.scrollTo(0, parseInt(scrollY || "0") * -1);
}
});
beforeNavigate(() => {
open = false;
});
@@ -24,8 +33,8 @@
{#if open}
<div
class="fixed drawer-overlay h-screen w-screen backdrop-blur-md z-50 bg-gray-500/50 cursor-pointer"
on:click={() => (open = false)}
on:keydown={(e) => {
onclick={() => (open = false)}
onkeydown={(e) => {
if (e.key == "Escape") open = false;
}}
role="button"
@@ -36,6 +45,6 @@
in:slide={{ axis: "x", easing: backOut }}
out:slide={{ axis: "x", easing: backIn }}
>
<slot />
{@render children?.()}
</div>
{/if}

View File

@@ -1,4 +1,4 @@
<script context="module" lang="ts">
<script module lang="ts">
export type DropdownItem = {
text: string;
value: any;
@@ -7,15 +7,19 @@
</script>
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { type Snippet } from "svelte";
import { fly } from "svelte/transition";
export let items: DropdownItem[] = [];
export let size: string = "regular";
interface Props {
items?: DropdownItem[];
size?: string;
children?: Snippet<[any]>;
onchange?: (item: DropdownItem) => void;
}
const dispatch = createEventDispatcher();
let { items = [], size = "regular", children, onchange }: Props = $props();
let isOpen = false;
let isOpen = $state(false);
export function toggleMenu(e: MouseEvent) {
e.stopPropagation();
@@ -27,13 +31,18 @@
isOpen = false;
}
function handleItemClick(item: { text: string; value: any }) {
dispatch("change", item);
function handleItemClick(e: Event, item: { text: string; value: any }) {
e.stopPropagation();
onchange?.(item);
closeMenu();
}
function handleWindowClick(e: MouseEvent) {
if ((e.target as HTMLElement).parentElement?.classList.contains("dropdown-toggle")) {
if (
(e.target as HTMLElement).parentElement?.classList.contains(
"dropdown-toggle",
)
) {
return;
}
@@ -41,19 +50,20 @@
}
</script>
<svelte:window on:mouseup={handleWindowClick} />
<svelte:window onmouseup={handleWindowClick} />
<div class="dropdown relative">
<div class="dropdown-toggle">
<slot {toggleMenu}>
{#if children}{@render children({ toggleMenu })}{:else}
<button
aria-label="Toggle menu"
class="btn-icon flex items-center justify-center"
on:click={toggleMenu}
onclick={toggleMenu}
type="button"
>
<i class="fa fa-ellipsis-vertical text-{size}"></i>
</button>
</slot>
{/if}
</div>
{#if isOpen}
@@ -68,7 +78,7 @@
<li
class="menu-item flex items-center px-4 py-3 cursor-pointer hover:bg-menu-item-background-hover focus:bg-menu-item-background-focus transition-colors"
role="presentation"
on:mousedown|stopPropagation={() => handleItemClick(item)}
onmousedown={(e) => handleItemClick(e, item)}
>
{#if item.icon}
<i class="fa fa-{item.icon} mr-3"></i>

View File

@@ -1,9 +1,21 @@
<script lang="ts">
export let label: string = "";
export let name: string = "";
export let accept: string = "*";
import { createBubbler } from 'svelte/legacy';
export let files: FileList | null = null;
const bubble = createBubbler();
interface Props {
label?: string;
name?: string;
accept?: string;
files?: FileList | null;
}
let {
label = "",
name = "",
accept = "*",
files = $bindable(null)
}: Props = $props();
</script>
<div>
@@ -18,6 +30,6 @@
bind:files
class="cursor-pointer bg-input-background border border-input-border rounded-md p-3 transition-colors focus:border-input-border-focus focus:outline-none focus:ring-0 w-full file:rounded-lg file:px-4 file:py-2 file:border file:border-input-border-focus file:font-medium file:mr-4"
type="file"
on:change
onchange={bubble('change')}
/>
</div>

View File

@@ -1,8 +1,24 @@
<script lang="ts">
export let id: string;
export let title: string;
export let size: string = "max-w-2xl";
import type { Snippet } from "svelte";
interface Props {
id: string;
title: string;
size?: string;
children?: Snippet<[any]>;
content?: Snippet;
footer?: Snippet<[any]>;
}
let {
id,
title,
size = "max-w-2xl",
children,
content,
footer,
}: Props = $props();
export function openModal() {
document.body.style.top = `-${window.scrollY}px`;
document.body.style.position = "fixed";
@@ -21,7 +37,7 @@
}
</script>
<slot {openModal} />
{@render children?.({ openModal })}
<dialog
{id}
tabindex="-1"
@@ -40,7 +56,7 @@
<button
type="button"
class="rounded-full btn-icon"
on:click={closeModal}
onclick={closeModal}
>
<i class="fa fa-close"></i>
<span class="sr-only">Close modal</span>
@@ -48,11 +64,11 @@
</div>
<!-- Modal body -->
<div class="p-4 md:p-5 space-y-4">
<slot name="content" />
{@render content?.()}
</div>
<!-- Modal footer -->
<div class="p-4 md:p-5 border-t border-separator rounded-b">
<slot name="footer" {closeModal} />
{@render footer?.({ closeModal })}
</div>
</div>
</dialog>

View File

@@ -1,45 +1,58 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { _ } from "svelte-i18n";
import type { SelectItem } from "./select.svelte";
export let items: SelectItem[] = [];
export let value: SelectItem[] = [];
export let label: string = "";
export let name: string = "";
export let placeholder: string = "";
let showDropdown = false;
let dropdownRef;
const dispatch = createEventDispatcher();
function toggleItem(item: SelectItem) {
if (value.includes(item)) {
value = value.filter((i) => i !== item);
} else {
value = [...value, item];
}
dispatch("change", value);
interface Props {
items?: SelectItem[];
value?: SelectItem[];
label?: string;
name?: string;
placeholder?: string;
onchange?: (value: SelectItem[]) => void
}
function removeItem(item: SelectItem) {
let {
items = [],
value = $bindable([]),
label = "",
name = "",
placeholder = "",
onchange
}: Props = $props();
let showDropdown = $state(false);
let dropdownRef = $state();
function toggleItem(item: SelectItem) {
const itemIndex = value.findIndex((i) => i.value == item.value);
if (itemIndex >= 0) {
value.splice(itemIndex, 1);
} else {
value.push(item);
}
onchange?.(value);
}
function removeItem(e: Event, item: SelectItem) {
e.stopPropagation();
value = value.filter((i) => i !== item);
dispatch("change", value);
onchange?.(value);
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="relative max-w-full">
{#if label.length}
<label for={name} class="text-sm font-medium pb-1 block">
{label}
</label>
{/if}
<button
<div
role="presentation"
class="min-w-44 w-full flex flex-wrap items-center gap-2 border border-input-border bg-input-background min-h-[50px] p-3 rounded-md transition-colors focus:border-input-border-focus focus:outline-none focus:ring-0"
on:click={() => (showDropdown = !showDropdown)}
onclick={() => (showDropdown = !showDropdown)}
>
{#if value.length === 0}
<span class="text-gray-400">{placeholder}</span>
@@ -50,14 +63,15 @@
>
<span class="text-sm">{$_(item.text)}</span>
<button
on:click|stopPropagation={() => removeItem(item)}
aria-label="Close"
onclick={(e) => removeItem(e, item)}
class="text-white hover:bg-primary-hover rounded-full w-4 h-4 flex items-center justify-center"
>
<i class="fa fa-close"></i>
</button>
</div>
{/each}
</button>
</div>
<!-- Dropdown menu -->
{#if showDropdown}
@@ -67,11 +81,11 @@
>
{#each items as item}
<button
on:click={() => toggleItem(item)}
onclick={() => toggleItem(item)}
class="px-3 py-2 hover:bg-menu-item-background-hover cursor-pointer flex justify-between items-center w-full"
>
<span>{$_(item.text)}</span>
{#if value.includes(item)}
{#if value.findIndex((i) => i.value == item.value) >= 0}
<div class="ml-auto">
<i class="fa fa-check"></i>
</div>

View File

@@ -1,20 +1,23 @@
<script lang="ts">
import { range } from "$lib/util/array_util";
import { createEventDispatcher } from "svelte";
export let page: number;
export let totalPages: number;
interface Props {
page: number;
totalPages: number;
onpagination?: (page: number) => void;
}
let { page, totalPages, onpagination }: Props = $props();
const maxPagesToRender = 6;
const dispatch = createEventDispatcher();
function update(clickedPage: number) {
if (page !== clickedPage) {
dispatch("pagination", clickedPage);
onpagination?.(clickedPage);
}
}
$: numberGenerator = () => {
let numberGenerator = $derived(() => {
if (totalPages < 4) {
return [];
}
@@ -31,21 +34,22 @@
return range(page + Math.max(1, maxPagesToRender - 5), page - 2);
}
return [];
};
});
</script>
<div class="flex gap-x-4 justify-center">
{#if totalPages > 1}
<button
aria-label="Next page"
class:text-gray-500={page == 1}
disabled={page == 1}
on:click={() => update(page - 1)}
onclick={() => update(page - 1)}
><i class="fa fa-caret-left"></i></button
>
<button
class="w-8 aspect-square rounded-lg transition-colors border-input-border hover:bg-menu-item-background-hover"
class:border={page == 1}
on:click={() => update(1)}>1</button
onclick={() => update(1)}>1</button
>
{#if totalPages > maxPagesToRender && page >= maxPagesToRender - 2 && page > 2}
<span>...</span>
@@ -53,14 +57,14 @@
<button
class="w-8 aspect-square rounded-lg transition-colors border-input-border hover:bg-menu-item-background-hover"
class:border={page == 2}
on:click={() => update(2)}>2</button
onclick={() => update(2)}>2</button
>
{/if}
{#each numberGenerator() as i}
<button
class="w-8 aspect-square rounded-lg transition-colors border-input-border hover:bg-menu-item-background-hover"
class:border={page == i + 1}
on:click={() => update(i + 1)}>{i + 1}</button
onclick={() => update(i + 1)}>{i + 1}</button
>
{/each}
{#if totalPages > maxPagesToRender && page < totalPages - 3}
@@ -69,18 +73,19 @@
<button
class="w-8 aspect-square rounded-lg transition-colors border-input-border hover:bg-menu-item-background-hover"
class:border={page == totalPages - 1}
on:click={() => update(totalPages - 1)}>{totalPages - 1}</button
onclick={() => update(totalPages - 1)}>{totalPages - 1}</button
>
{/if}
<button
class="w-8 aspect-square rounded-lg transition-colors border-input-border hover:bg-menu-item-background-hover"
class:border={page == totalPages}
on:click={() => update(totalPages)}>{totalPages}</button
onclick={() => update(totalPages)}>{totalPages}</button
>
<button
aria-label="Previous page"
class:text-gray-500={page == totalPages}
disabled={page == totalPages}
on:click={() => update(page + 1)}
onclick={() => update(page + 1)}
><i class="fa fa-caret-right"></i></button
>
{/if}

View File

@@ -1,4 +1,4 @@
<script context="module" lang="ts">
<script module lang="ts">
export type RadioItem = {
text: string;
value: string;
@@ -8,17 +8,19 @@
</script>
<script lang="ts">
import { createEventDispatcher } from "svelte";
interface Props {
items: RadioItem[];
name: string;
selected?: number;
onchange?: (item: RadioItem) => void
}
export let items: RadioItem[];
export let name: string;
export let selected: number = 0;
let { items, name, selected = $bindable(0), onchange }: Props = $props();
const dispatch = createEventDispatcher();
function handleRadioChange(radioIndex: number) {
selected = radioIndex;
dispatch("change", items[selected]);
onchange?.(items[selected]);
}
</script>
@@ -31,7 +33,7 @@
checked={i == selected}
value={item.value}
class="w-4 h-4 accent-primary border-input-border focus:ring-input-ring focus:ring-2"
on:change={() => handleRadioChange(i)}
onchange={() => handleRadioChange(i)}
/>
<div class="ms-2 text-sm">
<label for="{name}-radio-{i}"

View File

@@ -1,4 +1,5 @@
<script context="module" lang="ts">
<!-- @migration-task Error while migrating Svelte code: This migration would change the name of a slot making the component unusable -->
<script module lang="ts">
export type SearchItem = {
text: string;
description?: string;
@@ -8,26 +9,43 @@
</script>
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { fade } from "svelte/transition";
import TextField from "./text_field.svelte";
import type { Snippet } from "svelte";
export let maxSearchLength: number = 5;
export let value: string = "";
export let items: SearchItem[] = [];
export let placeholder: string = "Search...";
export let large: boolean = false;
export let extraClasses: string = "";
export let label: string = "";
export let clearAfterSelect: boolean = true;
interface Props {
maxSearchLength?: number;
value?: string;
items?: SearchItem[];
placeholder?: string;
large?: boolean;
extraClasses?: string;
label?: string;
clearAfterSelect?: boolean;
prepend?: Snippet<[any]>;
onupdate?: (q: string) => void;
onclick?: (item: SearchItem) => void;
}
const dispatch = createEventDispatcher();
let {
maxSearchLength = 5,
value = $bindable(""),
items = $bindable([]),
placeholder = "Search...",
large = false,
extraClasses = "",
label = "",
clearAfterSelect = true,
prepend,
onupdate,
onclick
}: Props = $props();
let lastSearch: string = "";
let searching: boolean = false;
let searching: boolean = $state(false);
let typingTimer!: any;
$: dropDownOpen = value.length > 0 && items.length > 0 && searching;
let dropDownOpen = $derived(value.length > 0 && items.length > 0 && searching);
function onSearchType() {
clearTimeout(typingTimer);
@@ -42,12 +60,13 @@
function update(q: string) {
lastSearch = q;
dispatch("update", q);
onupdate?.(q);
}
function handleItemClick(item: SearchItem) {
function handleItemClick(e: Event, item: SearchItem) {
e.stopPropagation();
searching = false;
dispatch("click", item);
onclick?.(item);
if (clearAfterSelect) {
clear();
}
@@ -70,11 +89,12 @@
</span>
{#if value.length > 0}
<button
aria-label="Clear"
type="button"
class="btn-icon absolute {large
? 'bottom-[31px]'
: 'bottom-[25px]'} translate-y-1/2 right-0 mr-2"
on:click={clear}
onclick={clear}
in:fade={{ duration: 150 }}
out:fade={{ duration: 150 }}
>
@@ -91,9 +111,9 @@
: "px-10"}
{placeholder}
bind:value
on:input={onSearchType}
on:focusin={() => (searching = true)}
on:focusout={() => (searching = false)}
oninput={onSearchType}
onfocusin={() => (searching = true)}
onfocusout={() => (searching = false)}
></TextField>
{#if dropDownOpen}
@@ -103,17 +123,17 @@
style="z-index: 1001"
>
{#each items as item}
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<li
class="menu-item flex items-center px-4 py-3 cursor-pointer hover:bg-menu-item-background-hover focus:bg-menu-item-background-focus transition-colors"
tabindex="0"
on:mousedown|stopPropagation={() => handleItemClick(item)}
on:keydown|stopPropagation={() => handleItemClick(item)}
onmousedown={(e) => handleItemClick(e, item)}
onkeydown={(e) => handleItemClick(e, item)}
>
<slot name="item-header" {item}>
{#if prepend}{@render prepend({ item, })}{:else}
<i class="fa fa-{item.icon} mr-6"></i>
</slot>
{/if}
<div>
<p>{item.text}</p>

View File

@@ -1,4 +1,4 @@
<script context="module" lang="ts">
<script module lang="ts">
export type SelectItem = {
text: string;
value: any;
@@ -6,18 +6,26 @@
</script>
<script lang="ts">
import { createEventDispatcher } from "svelte";
interface Props {
name?: string;
items?: SelectItem[];
value?: any;
label?: string;
disabled?: boolean;
onchange?: (value: any) => void
}
export let name: string = "";
export let items: SelectItem[] = [];
export let value: any = items.at(0)?.value ?? "";
export let label: string = "";
export let disabled: boolean = false;
const dispatch = createEventDispatcher();
let {
name = "",
items = [],
value = $bindable(items.at(0)?.value ?? ""),
label = "",
disabled = false,
onchange
}: Props = $props();
function onChange(target: any) {
dispatch("change", target?.value);
onchange?.(target?.value);
}
</script>
@@ -33,7 +41,7 @@
class:text-gray-500={disabled}
{disabled}
bind:value
on:change={(e) => onChange(e.target)}
onchange={(e) => onChange(e.target)}
>
{#each items as item}
<option value={item.value}>{item.text}</option>

View File

@@ -1,16 +1,23 @@
<script lang="ts">
import noUiSlider from "nouislider";
import "nouislider/dist/nouislider.css";
import { createEventDispatcher, onMount } from "svelte";
import { onMount } from "svelte";
export let minValue = 0;
export let maxValue = 100;
interface Props {
minValue?: number;
maxValue?: number;
currentValue?: any;
onset?: (value: number) => void;
}
export let currentValue = maxValue / 2;
let {
minValue = 0,
maxValue = 100,
currentValue = $bindable(maxValue / 2),
onset,
}: Props = $props();
let sliderContainer: any;
const dispatch = createEventDispatcher();
let sliderContainer: any = $state();
onMount(() => {
const updateValues = (values: string[]) => {
@@ -29,7 +36,7 @@
sliderContainer.noUiSlider.on("update", updateValues);
sliderContainer.noUiSlider.on("set", () => {
dispatch("set", currentValue);
onset?.(currentValue);
});
});
</script>

View File

@@ -1,18 +1,22 @@
<script lang="ts">
import { onMount } from "svelte";
import { cubicOut } from "svelte/easing";
import { tweened } from "svelte/motion";
import { Tween } from "svelte/motion";
export let tabs: string[];
export let activeTab: number;
export let extraClasses: string = "";
interface Props {
tabs: string[];
activeTab: number;
extraClasses?: string;
}
const indicatorPosition = tweened(0, {
let { tabs, activeTab = $bindable(), extraClasses = "" }: Props = $props();
const indicatorPosition = new Tween(0, {
duration: 300,
easing: cubicOut,
});
const indicatorWidth = tweened(0, {
const indicatorWidth = new Tween(0, {
duration: 300,
easing: cubicOut,
});
@@ -37,13 +41,13 @@
<div id="tabs" class="flex gap-2 overflow-x-auto relative {extraClasses}">
<div
class="absolute h-full bg-menu-item-background-hover rounded-t-lg top-0 z-0"
style="width: {$indicatorWidth}px; left: {$indicatorPosition}px;"
style="width: {indicatorWidth.current}px; left: {indicatorPosition.current}px;"
></div>
{#each tabs as tab, i}
<button
class="tab z-10"
class:tab-active={activeTab == i}
on:click={() => switchTabs(i)}>{tab}</button
onclick={() => switchTabs(i)}>{tab}</button
>
{/each}
</div>

View File

@@ -1,16 +1,40 @@
<script lang="ts">
import { _ } from "svelte-i18n";
import type { ChangeEventHandler, FocusEventHandler, FormEventHandler } from "svelte/elements";
export let name: string = "";
export let value: string | number = "";
export let placeholder: string = "";
export let disabled: boolean = false;
export let label: string = "";
export let error: string | string[] | null = "";
export let icon: string = "";
export let extraClasses: string = "";
export let type: "text" | "password" | "search" = "text";
export let autocomplete: "on" | "off" = "on";
interface Props {
name?: string;
value?: string | number;
placeholder?: string;
disabled?: boolean;
label?: string;
error?: string | string[] | null;
icon?: string;
extraClasses?: string;
type?: "text" | "password" | "search";
autocomplete?: "on" | "off";
onchange?: ChangeEventHandler<HTMLInputElement>;
oninput?: FormEventHandler<HTMLInputElement>;
onfocusin?: FocusEventHandler<HTMLInputElement>;
onfocusout?: FocusEventHandler<HTMLInputElement>;
}
let {
name = "",
value = $bindable(""),
placeholder = "",
disabled = false,
label = "",
error = "",
icon = "",
extraClasses = "",
type = "text",
autocomplete = "on",
onchange,
oninput,
onfocusin,
onfocusout
}: Props = $props();
function typeAction(node: HTMLInputElement) {
node.type = type;
}
@@ -38,10 +62,10 @@
{autocomplete}
use:typeAction
bind:value
on:change
on:input
on:focusin
on:focusout
{onchange}
{oninput}
{onfocusin}
{onfocusout}
{placeholder}
/>
</div>

View File

@@ -1,13 +1,28 @@
<script lang="ts">
import { createBubbler } from 'svelte/legacy';
const bubble = createBubbler();
import { _ } from "svelte-i18n";
export let name: string = "";
export let value: string | number = "";
export let placeholder: string = "";
export let rows: number = 3;
export let label: string = "";
export let error: string | string[] | null = "";
export let extraClasses: string = "";
interface Props {
name?: string;
value?: string | number;
placeholder?: string;
rows?: number;
label?: string;
error?: string | string[] | null;
extraClasses?: string;
}
let {
name = "",
value = $bindable(""),
placeholder = "",
rows = 3,
label = "",
error = "",
extraClasses = ""
}: Props = $props();
</script>
<div>
@@ -24,8 +39,8 @@
class:border-red-400={(error?.length ?? 0) > 0}
class:bg-input-background-error={(error?.length ?? 0) > 0}
bind:value
on:change
/>
onchange={bubble('change')}
></textarea>
{#if error}
<span class="textfield-error text-xs text-red-400">
{error instanceof Array ? $_(error[0]) : error}

View File

@@ -25,7 +25,7 @@
<button
type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white ml-6"
on:click={closeToast}
onclick={closeToast}
>
<i class="fa fa-close"></i>
<span class="sr-only">Close modal</span>

View File

@@ -1,15 +1,24 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
export let name: string = "";
export let value: boolean = false;
export let label: string = "";
export let error: string = "";
interface Props {
name?: string;
value?: boolean;
label?: string;
error?: string;
onchange?: (value: boolean) => void
}
let {
name = "",
value = $bindable(false),
label = "",
error = "",
onchange
}: Props = $props();
const dispatch = createEventDispatcher();
function handleToggleChange() {
dispatch("change", value);
onchange?.(value);
}
</script>
@@ -20,7 +29,7 @@
type="checkbox"
class="sr-only peer"
value="1"
on:change={handleToggleChange}
onchange={handleToggleChange}
/>
<div
class="w-11 h-6 bg-input-background border border-input-border peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-input-ring rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary"