feat: docker compose maybe

This commit is contained in:
2023-11-13 16:10:04 -05:00
parent 180b261e40
commit b625ccd8d6
8031 changed files with 2182966 additions and 0 deletions

View File

@ -0,0 +1,70 @@
<script>import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
import { storeHighlightJs } from "./stores.js";
import { clipboard } from "../../actions/Clipboard/clipboard.js";
export let language = "plaintext";
export let code = "";
export let lineNumbers = false;
export let background = "bg-neutral-900/90";
export let blur = "";
export let text = "text-sm";
export let color = "text-white";
export let rounded = "rounded-container-token";
export let shadow = "shadow";
export let button = "btn btn-sm variant-soft !text-white";
export let buttonLabel = "Copy";
export let buttonCopied = "\u{1F44D}";
const cBase = "overflow-hidden shadow";
const cHeader = "text-xs text-white/50 uppercase flex justify-between items-center p-2 pl-4";
const cPre = "whitespace-pre-wrap break-all p-4 pt-1";
let formatted = false;
let displayCode = code;
let copyState = false;
function languageFormatter(lang) {
if (lang === "js")
return "javascript";
if (lang === "ts")
return "typescript";
if (lang === "shell")
return "terminal";
return lang;
}
function onCopyClick() {
copyState = true;
setTimeout(() => {
copyState = false;
}, 2e3);
dispatch("copy");
}
$:
if ($storeHighlightJs !== void 0) {
displayCode = $storeHighlightJs.highlight(code, { language }).value.trim();
formatted = true;
}
$:
if (lineNumbers) {
displayCode = displayCode.replace(/^/gm, () => {
return '<span class="line"></span> ';
});
formatted = true;
}
$:
classesBase = `${cBase} ${background} ${blur} ${text} ${color} ${rounded} ${shadow} ${$$props.class ?? ""}`;
</script>
<!-- prettier-ignore -->
{#if language && code}
<div class="codeblock {classesBase}" data-testid="codeblock">
<!-- Header -->
<header class="codeblock-header {cHeader}">
<!-- Language -->
<span class="codeblock-language">{languageFormatter(language)}</span>
<!-- Copy Button -->
<button class="codeblock-btn {button}" on:click={onCopyClick} use:clipboard={code}>
{!copyState ? buttonLabel : buttonCopied}
</button>
</header>
<!-- Pre/Code -->
<pre class="codeblock-pre {cPre}"><code class="codeblock-code language-{language} lineNumbers">{#if formatted}{@html displayCode}{:else}{code.trim()}{/if}</code></pre>
</div>
{/if}

View File

@ -0,0 +1,43 @@
import { SvelteComponentTyped } from "svelte";
declare const __propDef: {
props: {
[x: string]: any;
/** Sets a language alias for Highlight.js syntax highlighting.*/
language?: string | undefined;
/** Provide the code snippet to render. Be mindful to escape as needed!*/
code?: string | undefined;
/** Specify if line numbers should be added to the code block*/
lineNumbers?: boolean | undefined;
/** Provide classes to set the background color.*/
background?: string | undefined;
/** Provided classes to set the backdrop blur.*/
blur?: string | undefined;
/** Provide classes to set the text size.*/
text?: string | undefined;
/** Provide classes to set the text color.*/
color?: string | undefined;
/** Provide classes to set the border radius.*/
rounded?: string | undefined;
/** Provide classes to set the box shadow.*/
shadow?: string | undefined;
/** Provide classes to set the button styles.*/
button?: string | undefined;
/** Provide the button label text.*/
buttonLabel?: string | undefined;
/** Provide the button label text when copied.*/
buttonCopied?: string | undefined;
};
events: {
/** {} copy - Fires when the Copy button is pressed.*/
copy: CustomEvent<never>;
} & {
[evt: string]: CustomEvent<any>;
};
slots: {};
};
export type CodeBlockProps = typeof __propDef.props;
export type CodeBlockEvents = typeof __propDef.events;
export type CodeBlockSlots = typeof __propDef.slots;
export default class CodeBlock extends SvelteComponentTyped<CodeBlockProps, CodeBlockEvents, CodeBlockSlots> {
}
export {};

View File

@ -0,0 +1,2 @@
import { type Writable } from 'svelte/store';
export declare const storeHighlightJs: Writable<any>;

View File

@ -0,0 +1,3 @@
import { writable } from 'svelte/store';
export const storeHighlightJs = writable(undefined);
// TODO: add support for other highlighters here in the future

View File

@ -0,0 +1,181 @@
<script>import { createEventDispatcher } from "svelte";
import { BROWSER } from "esm-env";
const dispatch = createEventDispatcher();
import { prefersReducedMotionStore } from "../../index.js";
import { focusTrap } from "../../actions/FocusTrap/focusTrap.js";
import { getDrawerStore } from "./stores.js";
import { fade, fly } from "svelte/transition";
import { dynamicTransition } from "../../internal/transitions.js";
export let position = "left";
export let bgDrawer = "bg-surface-100-800-token";
export let border = "";
export let rounded = "";
export let shadow = "shadow-xl";
export let width = "";
export let height = "";
export let bgBackdrop = "bg-surface-backdrop-token";
export let blur = "";
export let padding = "";
export let zIndex = "z-40";
export let regionBackdrop = "";
export let regionDrawer = "";
export let labelledby = "";
export let describedby = "";
export let duration = 200;
export let transitions = !$prefersReducedMotionStore;
export let opacityTransition = true;
const presets = {
top: { alignment: "items-start", width: "w-full", height: "h-[50%]", rounded: "rounded-bl-container-token rounded-br-container-token" },
bottom: { alignment: "items-end", width: "w-full", height: " h-[50%]", rounded: "rounded-tl-container-token rounded-tr-container-token" },
left: { alignment: "justify-start", width: "w-[90%]", height: "h-full", rounded: "rounded-tr-container-token rounded-br-container-token" },
right: { alignment: "justify-end", width: "w-[90%]", height: "h-full", rounded: "rounded-tl-container-token rounded-bl-container-token" }
};
let elemBackdrop;
let elemDrawer;
let anim = { x: 0, y: 0 };
const drawerStore = getDrawerStore();
const cBackdrop = "fixed top-0 left-0 right-0 bottom-0 flex";
const cDrawer = "overflow-y-auto transition-transform";
const propDefaults = {
position,
bgBackdrop,
blur,
padding,
bgDrawer,
border,
rounded,
shadow,
width,
height,
opacityTransition,
regionBackdrop,
regionDrawer,
labelledby,
describedby,
duration
};
function applyPropSettings(settings) {
position = settings.position || propDefaults.position;
bgBackdrop = settings.bgBackdrop || propDefaults.bgBackdrop;
blur = settings.blur || propDefaults.blur;
padding = settings.padding || propDefaults.padding;
bgDrawer = settings.bgDrawer || propDefaults.bgDrawer;
border = settings.border || propDefaults.border;
rounded = settings.rounded || propDefaults.rounded;
shadow = settings.shadow || propDefaults.shadow;
width = settings.width || propDefaults.width;
height = settings.height || propDefaults.height;
regionBackdrop = settings.regionBackdrop || propDefaults.regionBackdrop;
regionDrawer = settings.regionDrawer || propDefaults.regionDrawer;
labelledby = settings.labelledby || propDefaults.labelledby;
describedby = settings.describedby || propDefaults.describedby;
opacityTransition = settings.opacityTransition || propDefaults.opacityTransition;
duration = settings.duration || propDefaults.duration;
}
function applyAnimationSettings() {
if (!BROWSER)
return;
switch (position) {
case "top":
anim = { x: 0, y: -window.innerWidth };
break;
case "bottom":
anim = { x: 0, y: window.innerWidth };
break;
case "left":
anim = { x: -window.innerHeight, y: 0 };
break;
case "right":
anim = { x: window.innerHeight, y: 0 };
break;
default:
console.error("Error: unknown position property value.");
break;
}
}
function onDrawerInteraction(event) {
if (event.target === elemBackdrop) {
drawerStore.close();
dispatch("backdrop", event);
} else {
dispatch("drawer", event);
}
}
function onKeydownWindow(event) {
if (!$drawerStore)
return;
if (event.code === "Escape")
drawerStore.close();
}
drawerStore.subscribe((settings) => {
if (settings.open !== true)
return;
applyPropSettings(settings);
applyAnimationSettings();
});
$:
classesPosition = presets[position].alignment;
$:
classesWidth = width ? width : presets[position].width;
$:
classesHeight = height ? height : presets[position].height;
$:
classesRounded = rounded ? rounded : presets[position].rounded;
$:
classesBackdrop = `${cBackdrop} ${bgBackdrop} ${padding} ${blur} ${classesPosition} ${regionBackdrop} ${zIndex} ${$$props.class ?? ""}`;
$:
classesDrawer = `${cDrawer} ${bgDrawer} ${border} ${rounded} ${shadow} ${classesWidth} ${classesHeight} ${classesRounded} ${regionDrawer}`;
</script>
<svelte:window on:keydown={onKeydownWindow} />
{#if $drawerStore.open === true}
<!-- Backdrop -->
<!-- FIXME: resolve a11y warnings -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
bind:this={elemBackdrop}
class="drawer-backdrop {classesBackdrop}"
data-testid="drawer-backdrop"
on:mousedown={onDrawerInteraction}
on:touchstart
on:touchend
on:keypress
in:dynamicTransition|local={{
transition: fade,
params: { duration },
enabled: transitions && opacityTransition
}}
out:dynamicTransition|local={{
transition: fade,
params: { duration },
enabled: transitions && opacityTransition
}}
use:focusTrap={true}
>
<!-- Drawer -->
<!-- separate In/Out so anim values update -->
<div
bind:this={elemDrawer}
class="drawer {classesDrawer}"
data-testid="drawer"
role="dialog"
aria-modal="true"
aria-labelledby={labelledby}
aria-describedby={describedby}
in:dynamicTransition|local={{
transition: fly,
params: { x: anim.x, y: anim.y, duration, opacity: opacityTransition ? undefined : 1 },
enabled: transitions
}}
out:dynamicTransition|local={{
transition: fly,
params: { x: anim.x, y: anim.y, duration, opacity: opacityTransition ? undefined : 1 },
enabled: transitions
}}
>
<!-- Slot: Default -->
<slot />
</div>
</div>
{/if}

View File

@ -0,0 +1,62 @@
import { SvelteComponentTyped } from "svelte";
declare const __propDef: {
props: {
[x: string]: any;
/** Set the anchor position.*/
position?: "left" | "top" | "right" | "bottom" | undefined;
/** Drawer - Provide classes to set the drawer background color.*/
bgDrawer?: string | undefined;
/** Drawer - Provide classes to set border color.*/
border?: string | undefined;
/** Drawer - Provide classes to set border radius.*/
rounded?: string | undefined;
/** Drawer - Provide classes to set the box shadow.*/
shadow?: string | undefined;
/** Drawer - Provide classes to override the width.*/
width?: string | undefined;
/** Drawer - Provide classes to override the height.*/
height?: string | undefined;
/** Backdrop - Provide classes to set the backdrop background color*/
bgBackdrop?: string | undefined;
/** Backdrop - Provide classes to set the blur style.*/
blur?: string | undefined;
/** Backdrop - Provide classes to set padding.*/
padding?: string | undefined;
/** Backdrop - Provide a class to override the z-index*/
zIndex?: string | undefined;
/** Provide arbitrary classes to the backdrop region.*/
regionBackdrop?: string | undefined;
/** Provide arbitrary classes to the drawer region.*/
regionDrawer?: string | undefined;
/** Provide an ID of the element labeling the drawer.*/
labelledby?: string | undefined;
/** Provide an ID of the element describing the drawer.*/
describedby?: string | undefined;
/** Set the transition duration in milliseconds.*/
duration?: number | undefined;
/** Enable/Disable transitions*/
transitions?: boolean | undefined;
/** Enable/Disable opacity transition of Drawer*/
opacityTransition?: boolean | undefined;
};
events: {
touchstart: TouchEvent;
touchend: TouchEvent;
keypress: KeyboardEvent;
/** {{ event }} backdrop - Fires on backdrop interaction.*/
backdrop: CustomEvent<MouseEvent>;
/** {{ event }} drawer - Fires on drawer interaction.*/
drawer: CustomEvent<MouseEvent>;
} & {
[evt: string]: CustomEvent<any>;
};
slots: {
default: {};
};
};
export type DrawerProps = typeof __propDef.props;
export type DrawerEvents = typeof __propDef.events;
export type DrawerSlots = typeof __propDef.slots;
export default class Drawer extends SvelteComponentTyped<DrawerProps, DrawerEvents, DrawerSlots> {
}
export {};

View File

@ -0,0 +1,34 @@
/// <reference types="svelte" />
import type { DrawerSettings } from './types.js';
/**
* Retrieves the `drawerStore`.
*
* This can *ONLY* be called from the **top level** of components!
*
* @example
* ```svelte
* <script>
* import { getDrawerStore } from "@skeletonlabs/skeleton";
*
* const drawerStore = getDrawerStore();
*
* drawerStore.open();
* </script>
* ```
*/
export declare function getDrawerStore(): DrawerStore;
/**
* Initializes the `drawerStore`.
*/
export declare function initializeDrawerStore(): DrawerStore;
export type DrawerStore = ReturnType<typeof drawerService>;
declare function drawerService(): {
subscribe: (this: void, run: import("svelte/store").Subscriber<DrawerSettings>, invalidate?: import("svelte/store").Invalidator<DrawerSettings> | undefined) => import("svelte/store").Unsubscriber;
set: (this: void, value: DrawerSettings) => void;
update: (this: void, updater: import("svelte/store").Updater<DrawerSettings>) => void;
/** Open the drawer. */
open: (newSettings?: DrawerSettings) => void;
/** Close the drawer. */
close: () => void;
};
export {};

View File

@ -0,0 +1,50 @@
// Drawer Stores
import { writable } from 'svelte/store';
import { getContext, setContext } from 'svelte';
const DRAWER_STORE_KEY = 'drawerStore';
/**
* Retrieves the `drawerStore`.
*
* This can *ONLY* be called from the **top level** of components!
*
* @example
* ```svelte
* <script>
* import { getDrawerStore } from "@skeletonlabs/skeleton";
*
* const drawerStore = getDrawerStore();
*
* drawerStore.open();
* </script>
* ```
*/
export function getDrawerStore() {
const drawerStore = getContext(DRAWER_STORE_KEY);
if (!drawerStore)
throw new Error('drawerStore is not initialized. Please ensure that `initializeStores()` is invoked in the root layout file of this app!');
return drawerStore;
}
/**
* Initializes the `drawerStore`.
*/
export function initializeDrawerStore() {
const drawerStore = drawerService();
return setContext(DRAWER_STORE_KEY, drawerStore);
}
function drawerService() {
const { subscribe, set, update } = writable({});
return {
subscribe,
set,
update,
/** Open the drawer. */
open: (newSettings) => update(() => {
return { open: true, ...newSettings };
}),
/** Close the drawer. */
close: () => update((d) => {
d.open = false;
return d;
})
};
}

View File

@ -0,0 +1,42 @@
export type { DrawerStore } from './stores.js';
export interface DrawerSettings {
open?: boolean;
/** A unique identifier, useful for setting contents. */
id?: string;
/** Pass arbitrary information for your own persona use. */
meta?: any;
/** Set the anchor position.
* @type {'left' | 'top' | 'right' | 'bottom'}
*/
position?: 'left' | 'top' | 'right' | 'bottom';
/** Backdrop - Provide classes to set the backdrop background color*/
bgBackdrop?: string;
/** Backdrop - Provide classes to set the blur style.*/
blur?: string;
/** Drawer - Provide classes to set padding.*/
padding?: string;
/** Drawer - Provide classes to set the drawer background color.*/
bgDrawer?: string;
/** Drawer - Provide classes to set border color.*/
border?: string;
/** Drawer - Provide classes to set border radius.*/
rounded?: string;
/** Drawer - Provide classes to set box shadow.*/
shadow?: string;
/** Drawer - Provide classes to override the width.*/
width?: string;
/** Drawer - Provide classes to override the height.*/
height?: string;
/** Define the Svelte transition animation duration.*/
duration?: number;
/** Drawer - Enable/Disable opacity transition */
opacityTransition?: boolean;
/** Provide arbitrary classes to the backdrop region. */
regionBackdrop?: string;
/** Provide arbitrary classes to the drawer region. */
regionDrawer?: string;
/** Provide an ID of the element labeling the drawer.*/
labelledby?: string;
/** Provide an ID of the element describing the drawer.*/
describedby?: string;
}

View File

@ -0,0 +1,2 @@
// Drawer Types
export {};

View File

@ -0,0 +1,78 @@
<script>import { onMount } from "svelte";
import { modeCurrent, setModeUserPrefers, setModeCurrent, setInitialClassState, getModeOsPrefers } from "./lightswitch.js";
export let title = "Toggle light or dark mode.";
export let bgLight = "bg-surface-50";
export let bgDark = "bg-surface-900";
export let fillLight = "fill-surface-50";
export let fillDark = "fill-surface-900";
export let width = "w-12";
export let height = "h-6";
export let ring = "ring-[1px] ring-surface-500/30";
export let rounded = "rounded-token";
const cTransition = `transition-all duration-[200ms]`;
const cTrack = "cursor-pointer";
const cThumb = "aspect-square scale-[0.8] flex justify-center items-center";
const cIcon = "w-[70%] aspect-square";
const svgPath = {
sun: "M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM352 256c0 53-43 96-96 96s-96-43-96-96s43-96 96-96s96 43 96 96zm32 0c0-70.7-57.3-128-128-128s-128 57.3-128 128s57.3 128 128 128s128-57.3 128-128z",
moon: "M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
};
function onToggleHandler() {
$modeCurrent = !$modeCurrent;
setModeUserPrefers($modeCurrent);
setModeCurrent($modeCurrent);
}
function onKeyDown(event) {
if (["Enter", "Space"].includes(event.code)) {
event.preventDefault();
event.currentTarget.click();
}
}
onMount(() => {
if (!("modeCurrent" in localStorage)) {
setModeCurrent(getModeOsPrefers());
}
});
$:
trackBg = $modeCurrent === true ? bgLight : bgDark;
$:
thumbBg = $modeCurrent === true ? bgDark : bgLight;
$:
thumbPosition = $modeCurrent === true ? "translate-x-[100%]" : "";
$:
iconFill = $modeCurrent === true ? fillLight : fillDark;
$:
classesTrack = `${cTrack} ${cTransition} ${width} ${height} ${ring} ${rounded} ${trackBg} ${$$props.class ?? ""}`;
$:
classesThumb = `${cThumb} ${cTransition} ${height} ${rounded} ${thumbBg} ${thumbPosition}`;
$:
classesIcon = `${cIcon} ${iconFill}`;
</script>
<svelte:head>
<!-- Workaround for a svelte parsing error: https://github.com/sveltejs/eslint-plugin-svelte/issues/492 -->
{@html `<\u{73}cript nonce="%sveltekit.nonce%">(${setInitialClassState.toString()})();</script>`}
</svelte:head>
<div
class="lightswitch-track {classesTrack}"
on:click={onToggleHandler}
on:click
on:keydown={onKeyDown}
on:keydown
on:keyup
on:keypress
role="switch"
aria-label="Light Switch"
aria-checked={$modeCurrent}
{title}
tabindex="0"
>
<!-- Thumb -->
<div class="lightswitch-thumb {classesThumb}">
<!-- SVG -->
<svg class="lightswitch-icon {classesIcon}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d={$modeCurrent ? svgPath.sun : svgPath.moon} />
</svg>
</div>
</div>

View File

@ -0,0 +1,39 @@
import { SvelteComponentTyped } from "svelte";
declare const __propDef: {
props: {
[x: string]: any;
/** Customize the `title` attribute for the component.*/
title?: string | undefined;
/** Provide classes to set the light background color.*/
bgLight?: string | undefined;
/** Provide classes to set the dark background color.*/
bgDark?: string | undefined;
/** Provide classes to set the light SVG fill color.*/
fillLight?: string | undefined;
/** Provide classes to set the dark SVG fill color.*/
fillDark?: string | undefined;
/** Provide classes to set width styles.*/
width?: string | undefined;
/** Provide classes to set height styles. Should be half of width.*/
height?: string | undefined;
/** Provide classes to set ring styles.*/
ring?: string | undefined;
/** Provide classes to set border radius styles.*/
rounded?: string | undefined;
};
events: {
click: MouseEvent;
keydown: KeyboardEvent;
keyup: KeyboardEvent;
keypress: KeyboardEvent;
} & {
[evt: string]: CustomEvent<any>;
};
slots: {};
};
export type LightSwitchProps = typeof __propDef.props;
export type LightSwitchEvents = typeof __propDef.events;
export type LightSwitchSlots = typeof __propDef.slots;
export default class LightSwitch extends SvelteComponentTyped<LightSwitchProps, LightSwitchEvents, LightSwitchSlots> {
}
export {};

View File

@ -0,0 +1,21 @@
/// <reference types="svelte" />
/** Store: OS Preference Mode */
export declare const modeOsPrefers: import("svelte/store").Writable<boolean>;
/** Store: User Preference Mode */
export declare const modeUserPrefers: import("svelte/store").Writable<boolean | undefined>;
/** Store: Current Mode State */
export declare const modeCurrent: import("svelte/store").Writable<boolean>;
/** Get the OS Preference for light/dark mode */
export declare function getModeOsPrefers(): boolean;
/** Get the User for light/dark mode */
export declare function getModeUserPrefers(): boolean | undefined;
/** Get the Automatic Preference light/dark mode */
export declare function getModeAutoPrefers(): boolean;
/** Set the User Preference for light/dark mode */
export declare function setModeUserPrefers(value: boolean): void;
/** Set the the current light/dark mode */
export declare function setModeCurrent(value: boolean): void;
/** Set the visible light/dark mode on page load. */
export declare function setInitialClassState(): void;
/** Automatically set the visible light/dark, updates on change. */
export declare function autoModeWatcher(): void;

View File

@ -0,0 +1,72 @@
// Lightswitch Service
import { get } from 'svelte/store';
// DO NOT replace this ⬇ import, it has to be imported directly
import { localStorageStore } from '../LocalStorageStore/LocalStorageStore.js';
// Stores ---
// TRUE: light, FALSE: dark
/** Store: OS Preference Mode */
export const modeOsPrefers = localStorageStore('modeOsPrefers', false);
/** Store: User Preference Mode */
export const modeUserPrefers = localStorageStore('modeUserPrefers', undefined);
/** Store: Current Mode State */
export const modeCurrent = localStorageStore('modeCurrent', false);
// Get ---
/** Get the OS Preference for light/dark mode */
export function getModeOsPrefers() {
const prefersLightMode = window.matchMedia('(prefers-color-scheme: light)').matches;
modeOsPrefers.set(prefersLightMode);
return prefersLightMode;
}
/** Get the User for light/dark mode */
export function getModeUserPrefers() {
return get(modeUserPrefers);
}
/** Get the Automatic Preference light/dark mode */
export function getModeAutoPrefers() {
const os = getModeOsPrefers();
const user = getModeUserPrefers();
const modeValue = user !== undefined ? user : os;
return modeValue;
}
// Set ---
/** Set the User Preference for light/dark mode */
export function setModeUserPrefers(value) {
modeUserPrefers.set(value);
}
/** Set the the current light/dark mode */
export function setModeCurrent(value) {
const elemHtmlClasses = document.documentElement.classList;
const classDark = `dark`;
value === true ? elemHtmlClasses.remove(classDark) : elemHtmlClasses.add(classDark);
modeCurrent.set(value);
}
// Lightswitch Utility
/** Set the visible light/dark mode on page load. */
export function setInitialClassState() {
const elemHtmlClasses = document.documentElement.classList;
// Conditions
const condLocalStorageUserPrefs = localStorage.getItem('modeUserPrefers') === 'false';
const condLocalStorageUserPrefsExists = !('modeUserPrefers' in localStorage);
const condMatchMedia = window.matchMedia('(prefers-color-scheme: dark)').matches;
// Add/remove `.dark` class to HTML element
if (condLocalStorageUserPrefs || (condLocalStorageUserPrefsExists && condMatchMedia)) {
elemHtmlClasses.add('dark');
}
else {
elemHtmlClasses.remove('dark');
}
}
// Auto-Switch Utility
/** Automatically set the visible light/dark, updates on change. */
export function autoModeWatcher() {
const mql = window.matchMedia('(prefers-color-scheme: light)');
function setMode(value) {
const elemHtmlClasses = document.documentElement.classList;
const classDark = `dark`;
value === true ? elemHtmlClasses.remove(classDark) : elemHtmlClasses.add(classDark);
}
setMode(mql.matches);
mql.onchange = () => {
setMode(mql.matches);
};
}

View File

@ -0,0 +1,12 @@
import { type Writable } from 'svelte/store';
interface Serializer<T> {
parse(text: string): T;
stringify(object: T): string;
}
type StorageType = 'local' | 'session';
interface Options<T> {
serializer?: Serializer<T>;
storage?: StorageType;
}
export declare function localStorageStore<T>(key: string, initialValue: T, options?: Options<T>): Writable<T>;
export {};

View File

@ -0,0 +1,49 @@
// Source: https://github.com/joshnuss/svelte-local-storage-store
// https://github.com/joshnuss/svelte-local-storage-store/blob/master/index.ts
// Represents version v0.4.0 (2023-01-18)
import { BROWSER } from 'esm-env';
import { writable as internal, get } from 'svelte/store';
/* eslint-disable @typescript-eslint/no-explicit-any */
const stores = {};
function getStorage(type) {
return type === 'local' ? localStorage : sessionStorage;
}
export function localStorageStore(key, initialValue, options) {
const serializer = options?.serializer ?? JSON;
const storageType = options?.storage ?? 'local';
function updateStorage(key, value) {
if (!BROWSER)
return;
getStorage(storageType).setItem(key, serializer.stringify(value));
}
if (!stores[key]) {
const store = internal(initialValue, (set) => {
const json = BROWSER ? getStorage(storageType).getItem(key) : null;
if (json) {
set(serializer.parse(json));
}
if (BROWSER) {
const handleStorage = (event) => {
if (event.key === key)
set(event.newValue ? serializer.parse(event.newValue) : null);
};
window.addEventListener('storage', handleStorage);
return () => window.removeEventListener('storage', handleStorage);
}
});
const { subscribe, set } = store;
stores[key] = {
set(value) {
updateStorage(key, value);
set(value);
},
update(updater) {
const value = updater(get(store));
updateStorage(key, value);
set(value);
},
subscribe
};
}
return stores[key];
}

View File

@ -0,0 +1,217 @@
<script context="module">import { fly, fade } from "svelte/transition";
import { prefersReducedMotionStore } from "../../index.js";
import { dynamicTransition } from "../../internal/transitions.js";
</script>
<script generics="TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition">import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
import { focusTrap } from "../../actions/FocusTrap/focusTrap.js";
import { getModalStore } from "./stores.js";
export let position = "items-center";
export let components = {};
export let background = "bg-surface-100-800-token";
export let width = "w-modal";
export let height = "h-auto";
export let padding = "p-4";
export let spacing = "space-y-4";
export let rounded = "rounded-container-token";
export let shadow = "shadow-xl";
export let zIndex = "z-[999]";
export let buttonNeutral = "variant-ghost-surface";
export let buttonPositive = "variant-filled";
export let buttonTextCancel = "Cancel";
export let buttonTextConfirm = "Confirm";
export let buttonTextSubmit = "Submit";
export let regionBackdrop = "bg-surface-backdrop-token";
export let regionHeader = "text-2xl font-bold";
export let regionBody = "max-h-[200px] overflow-hidden";
export let regionFooter = "flex justify-end space-x-2";
export let transitions = !$prefersReducedMotionStore;
export let transitionIn = fly;
export let transitionInParams = { duration: 150, opacity: 0, x: 0, y: 100 };
export let transitionOut = fly;
export let transitionOutParams = { duration: 150, opacity: 0, x: 0, y: 100 };
const cBackdrop = "fixed top-0 left-0 right-0 bottom-0 overflow-y-auto";
const cTransitionLayer = "w-full h-fit min-h-full p-4 overflow-y-auto flex justify-center";
const cModal = "block overflow-y-auto";
const cModalImage = "w-full h-auto";
let promptValue;
const buttonTextDefaults = {
buttonTextCancel,
buttonTextConfirm,
buttonTextSubmit
};
let currentComponent;
let registeredInteractionWithBackdrop = false;
const modalStore = getModalStore();
modalStore.subscribe((modals) => {
if (!modals.length)
return;
if (modals[0].type === "prompt")
promptValue = modals[0].value;
buttonTextCancel = modals[0].buttonTextCancel || buttonTextDefaults.buttonTextCancel;
buttonTextConfirm = modals[0].buttonTextConfirm || buttonTextDefaults.buttonTextConfirm;
buttonTextSubmit = modals[0].buttonTextSubmit || buttonTextDefaults.buttonTextSubmit;
currentComponent = typeof modals[0].component === "string" ? components[modals[0].component] : modals[0].component;
});
function onBackdropInteractionBegin(event) {
if (!(event.target instanceof Element))
return;
const classList = event.target.classList;
if (classList.contains("modal-backdrop") || classList.contains("modal-transition")) {
registeredInteractionWithBackdrop = true;
}
}
function onBackdropInteractionEnd(event) {
if (!(event.target instanceof Element))
return;
const classList = event.target.classList;
if ((classList.contains("modal-backdrop") || classList.contains("modal-transition")) && registeredInteractionWithBackdrop) {
if ($modalStore[0].response)
$modalStore[0].response(void 0);
modalStore.close();
dispatch("backdrop", event);
}
registeredInteractionWithBackdrop = false;
}
function onClose() {
if ($modalStore[0].response)
$modalStore[0].response(false);
modalStore.close();
}
function onConfirm() {
if ($modalStore[0].response)
$modalStore[0].response(true);
modalStore.close();
}
function onPromptSubmit(event) {
event.preventDefault();
if ($modalStore[0].response)
$modalStore[0].response(promptValue);
modalStore.close();
}
function onKeyDown(event) {
if (!$modalStore.length)
return;
if (event.code === "Escape")
onClose();
}
$:
cPosition = $modalStore[0]?.position ?? position;
$:
classesBackdrop = `${cBackdrop} ${regionBackdrop} ${zIndex} ${$$props.class ?? ""} ${$modalStore[0]?.backdropClasses ?? ""}`;
$:
classesTransitionLayer = `${cTransitionLayer} ${cPosition ?? ""}`;
$:
classesModal = `${cModal} ${background} ${width} ${height} ${padding} ${spacing} ${rounded} ${shadow} ${$modalStore[0]?.modalClasses ?? ""}`;
$:
parent = {
position,
// ---
background,
width,
height,
padding,
spacing,
rounded,
shadow,
// ---
buttonNeutral,
buttonPositive,
buttonTextCancel,
buttonTextConfirm,
buttonTextSubmit,
// ---
regionBackdrop,
regionHeader,
regionBody,
regionFooter,
// ---
onClose
};
</script>
<svelte:window on:keydown={onKeyDown} />
{#if $modalStore.length > 0}
{#key $modalStore}
<!-- Backdrop -->
<!-- FIXME: resolve a11y warnings -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="modal-backdrop {classesBackdrop}"
data-testid="modal-backdrop"
on:mousedown={onBackdropInteractionBegin}
on:mouseup={onBackdropInteractionEnd}
on:touchstart|passive
on:touchend|passive
transition:dynamicTransition|global={{ transition: fade, params: { duration: 150 }, enabled: transitions }}
use:focusTrap={true}
>
<!-- Transition Layer -->
<div
class="modal-transition {classesTransitionLayer}"
in:dynamicTransition|global={{ transition: transitionIn, params: transitionInParams, enabled: transitions }}
out:dynamicTransition|global={{ transition: transitionOut, params: transitionOutParams, enabled: transitions }}
>
{#if $modalStore[0].type !== 'component'}
<!-- Modal: Presets -->
<div class="modal {classesModal}" data-testid="modal" role="dialog" aria-modal="true" aria-label={$modalStore[0].title ?? ''}>
<!-- Header -->
{#if $modalStore[0]?.title}
<header class="modal-header {regionHeader}">{@html $modalStore[0].title}</header>
{/if}
<!-- Body -->
{#if $modalStore[0]?.body}
<article class="modal-body {regionBody}">{@html $modalStore[0].body}</article>
{/if}
<!-- Image -->
{#if $modalStore[0]?.image && typeof $modalStore[0]?.image === 'string'}
<img class="modal-image {cModalImage}" src={$modalStore[0]?.image} alt="Modal" />
{/if}
<!-- Type -->
{#if $modalStore[0].type === 'alert'}
<!-- Template: Alert -->
<footer class="modal-footer {regionFooter}">
<button type="button" class="btn {buttonNeutral}" on:click={onClose}>{buttonTextCancel}</button>
</footer>
{:else if $modalStore[0].type === 'confirm'}
<!-- Template: Confirm -->
<footer class="modal-footer {regionFooter}">
<button type="button" class="btn {buttonNeutral}" on:click={onClose}>{buttonTextCancel}</button>
<button type="button" class="btn {buttonPositive}" on:click={onConfirm}>{buttonTextConfirm}</button>
</footer>
{:else if $modalStore[0].type === 'prompt'}
<!-- Template: Prompt -->
<form class="space-y-4" on:submit={onPromptSubmit}>
<input class="modal-prompt-input input" name="prompt" type="text" bind:value={promptValue} {...$modalStore[0].valueAttr} />
<footer class="modal-footer {regionFooter}">
<button type="button" class="btn {buttonNeutral}" on:click={onClose}>{buttonTextCancel}</button>
<button type="submit" class="btn {buttonPositive}">{buttonTextSubmit}</button>
</footer>
</form>
{/if}
</div>
{:else}
<!-- Modal: Components -->
<!-- Note: keep `contents` class to allow widths from children -->
<div
class="modal contents {$modalStore[0]?.modalClasses ?? ''}"
data-testid="modal-component"
role="dialog"
aria-modal="true"
aria-label={$modalStore[0].title ?? ''}
>
{#if currentComponent?.slot}
<svelte:component this={currentComponent?.ref} {...currentComponent?.props} {parent}>
{@html currentComponent?.slot}
</svelte:component>
{:else}
<svelte:component this={currentComponent?.ref} {...currentComponent?.props} {parent} />
{/if}
</div>
{/if}
</div>
</div>
{/key}
{/if}

View File

@ -0,0 +1,72 @@
import { SvelteComponentTyped } from "svelte";
import { fly } from 'svelte/transition';
import { type Transition, type TransitionParams } from '../../index.js';
type FlyTransition = typeof fly;
import type { ModalComponent } from './types.js';
declare class __sveltets_Render<TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition> {
props(): {
[x: string]: any;
/** Set the modal position within the backdrop container*/
position?: string | undefined;
/** Register a list of reusable component modals.*/
components?: Record<string, ModalComponent> | undefined;
/** Provide classes to style the modal background.*/
background?: string | undefined;
/** Provide classes to style the modal width.*/
width?: string | undefined;
/** Provide classes to style the modal height.*/
height?: string | undefined;
/** Provide classes to style the modal padding.*/
padding?: string | undefined;
/** Provide classes to style the modal spacing.*/
spacing?: string | undefined;
/** Provide classes to style the modal border radius.*/
rounded?: string | undefined;
/** Provide classes to style modal box shadow.*/
shadow?: string | undefined;
/** Provide a class to override the z-index*/
zIndex?: string | undefined;
/** Provide classes for neutral buttons, such as Cancel.*/
buttonNeutral?: string | undefined;
/** Provide classes for positive actions, such as Confirm or Submit.*/
buttonPositive?: string | undefined;
/** Override the text for the Cancel button.*/
buttonTextCancel?: string | undefined;
/** Override the text for the Confirm button.*/
buttonTextConfirm?: string | undefined;
/** Override the text for the Submit button.*/
buttonTextSubmit?: string | undefined;
/** Provide classes to style the backdrop.*/
regionBackdrop?: string | undefined;
/** Provide arbitrary classes to modal header region.*/
regionHeader?: string | undefined;
/** Provide arbitrary classes to modal body region.*/
regionBody?: string | undefined;
/** Provide arbitrary classes to modal footer region.*/
regionFooter?: string | undefined;
/** Enable/Disable transitions*/
transitions?: boolean | undefined;
/** Provide the transition used on entry.*/
transitionIn?: TransitionIn | undefined;
/** Transition params provided to `TransitionIn`.*/
transitionInParams?: TransitionParams<TransitionIn> | undefined;
/** Provide the transition used on exit.*/
transitionOut?: TransitionOut | undefined;
/** Transition params provided to `TransitionOut`.*/
transitionOutParams?: TransitionParams<TransitionOut> | undefined;
};
events(): {
touchstart: TouchEvent;
touchend: TouchEvent;
backdrop: CustomEvent<MouseEvent>;
} & {
[evt: string]: CustomEvent<any>;
};
slots(): {};
}
export type ModalProps<TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition> = ReturnType<__sveltets_Render<TransitionIn, TransitionOut>['props']>;
export type ModalEvents<TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition> = ReturnType<__sveltets_Render<TransitionIn, TransitionOut>['events']>;
export type ModalSlots<TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition> = ReturnType<__sveltets_Render<TransitionIn, TransitionOut>['slots']>;
export default class Modal<TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition> extends SvelteComponentTyped<ModalProps<TransitionIn, TransitionOut>, ModalEvents<TransitionIn, TransitionOut>, ModalSlots<TransitionIn, TransitionOut>> {
}
export {};

View File

@ -0,0 +1,8 @@
<script>import { initializeModalStore, getModalStore } from "./stores.js";
import Modal from "./Modal.svelte";
export let modalSetting;
initializeModalStore();
getModalStore().trigger(modalSetting);
</script>
<Modal />

View File

@ -0,0 +1,17 @@
import { SvelteComponentTyped } from "svelte";
import type { ModalSettings } from './types.js';
declare const __propDef: {
props: {
modalSetting: ModalSettings;
};
events: {
[evt: string]: CustomEvent<any>;
};
slots: {};
};
export type ModalTestProps = typeof __propDef.props;
export type ModalTestEvents = typeof __propDef.events;
export type ModalTestSlots = typeof __propDef.slots;
export default class ModalTest extends SvelteComponentTyped<ModalTestProps, ModalTestEvents, ModalTestSlots> {
}
export {};

View File

@ -0,0 +1,36 @@
/// <reference types="svelte" />
import type { ModalSettings } from './types.js';
/**
* Retrieves the `modalStore`.
*
* This can *ONLY* be called from the **top level** of components!
*
* @example
* ```svelte
* <script>
* import { getmodalStore } from "@skeletonlabs/skeleton";
*
* const modalStore = getModalStore();
*
* modalStore.trigger({ type: "alert", title: "Welcome!" });
* </script>
* ```
*/
export declare function getModalStore(): ModalStore;
/**
* Initializes the `modalStore`.
*/
export declare function initializeModalStore(): ModalStore;
export type ModalStore = ReturnType<typeof modalService>;
declare function modalService(): {
subscribe: (this: void, run: import("svelte/store").Subscriber<ModalSettings[]>, invalidate?: import("svelte/store").Invalidator<ModalSettings[]> | undefined) => import("svelte/store").Unsubscriber;
set: (this: void, value: ModalSettings[]) => void;
update: (this: void, updater: import("svelte/store").Updater<ModalSettings[]>) => void;
/** Append to end of queue. */
trigger: (modal: ModalSettings) => void;
/** Remove first item in queue. */
close: () => void;
/** Remove all items from queue. */
clear: () => void;
};
export {};

View File

@ -0,0 +1,54 @@
// Modal Store Queue
import { writable } from 'svelte/store';
import { getContext, setContext } from 'svelte';
const MODAL_STORE_KEY = 'modalStore';
/**
* Retrieves the `modalStore`.
*
* This can *ONLY* be called from the **top level** of components!
*
* @example
* ```svelte
* <script>
* import { getmodalStore } from "@skeletonlabs/skeleton";
*
* const modalStore = getModalStore();
*
* modalStore.trigger({ type: "alert", title: "Welcome!" });
* </script>
* ```
*/
export function getModalStore() {
const modalStore = getContext(MODAL_STORE_KEY);
if (!modalStore)
throw new Error('modalStore is not initialized. Please ensure that `initializeStores()` is invoked in the root layout file of this app!');
return modalStore;
}
/**
* Initializes the `modalStore`.
*/
export function initializeModalStore() {
const modalStore = modalService();
return setContext(MODAL_STORE_KEY, modalStore);
}
function modalService() {
const { subscribe, set, update } = writable([]);
return {
subscribe,
set,
update,
/** Append to end of queue. */
trigger: (modal) => update((mStore) => {
mStore.push(modal);
return mStore;
}),
/** Remove first item in queue. */
close: () => update((mStore) => {
if (mStore.length > 0)
mStore.shift();
return mStore;
}),
/** Remove all items from queue. */
clear: () => set([])
};
}

View File

@ -0,0 +1,41 @@
export type { ModalStore } from './stores.js';
export interface ModalComponent {
/** Import and provide your component reference. */
ref: any;
/** Provide component props as key/value pairs. */
props?: Record<string, unknown>;
/** Provide an HTML template literal for the default slot. */
slot?: string;
}
export interface ModalSettings {
/** Designate what type of component will display. */
type: 'alert' | 'confirm' | 'prompt' | 'component';
/** Set the modal position within the backdrop container. */
position?: string;
/** Provide the modal header content. Accepts HTML. */
title?: string;
/** Provide the modal body content. Accepts HTML. */
body?: string;
/** Provide a URL to display an image within the modal. */
image?: string;
/** By default, used to provide a prompt value. */
value?: any;
/** Provide input attributes as key/value pairs. */
valueAttr?: object;
/** Provide your component reference key or object. */
component?: ModalComponent | string;
/** Provide a function. Returns the response value. */
response?: (r: any) => void;
/** Provide arbitrary classes to the backdrop. */
backdropClasses?: string;
/** Provide arbitrary classes to the modal window. */
modalClasses?: string;
/** Override the Cancel button label. */
buttonTextCancel?: string;
/** Override the Confirm button label. */
buttonTextConfirm?: string;
/** Override the Submit button label. */
buttonTextSubmit?: string;
/** Pass arbitrary data per modal instance. */
meta?: any;
}

View File

@ -0,0 +1,2 @@
// Modal Types
export {};

View File

@ -0,0 +1,7 @@
import { type Writable } from 'svelte/store';
import type { PopupSettings } from './types.js';
export declare const storePopup: Writable<any>;
export declare function popup(triggerNode: HTMLElement, args: PopupSettings): {
update(newArgs: PopupSettings): void;
destroy(): void;
};

View File

@ -0,0 +1,237 @@
import { get, writable } from 'svelte/store';
// Use a store to pass the Floating UI import references
export const storePopup = writable(undefined);
export function popup(triggerNode, args) {
// Floating UI Modules
const { computePosition, autoUpdate, offset, shift, flip, arrow, size, autoPlacement, hide, inline } = get(storePopup);
// Local State
const popupState = {
open: false,
autoUpdateCleanup: () => { }
};
const focusableAllowedList = ':is(a[href], button, input, textarea, select, details, [tabindex]):not([tabindex="-1"])';
let focusablePopupElements;
const documentationLink = 'https://www.skeleton.dev/utilities/popups';
// Elements
let elemPopup;
let elemArrow;
function setDomElements() {
elemPopup = document.querySelector(`[data-popup="${args.target}"]`) ?? document.createElement('div');
elemArrow = elemPopup.querySelector(`.arrow`) ?? document.createElement('div');
}
setDomElements(); // init
// Render Floating UI Popup
function render() {
// Error handling for required Floating UI modules
if (!elemPopup)
throw new Error(`The data-popup="${args.target}" element was not found. ${documentationLink}`);
if (!computePosition)
throw new Error(`Floating UI 'computePosition' not found for data-popup="${args.target}". ${documentationLink}`);
if (!offset)
throw new Error(`Floating UI 'offset' not found for data-popup="${args.target}". ${documentationLink}`);
if (!shift)
throw new Error(`Floating UI 'shift' not found for data-popup="${args.target}". ${documentationLink}`);
if (!flip)
throw new Error(`Floating UI 'flip' not found for data-popup="${args.target}". ${documentationLink}`);
if (!arrow)
throw new Error(`Floating UI 'arrow' not found for data-popup="${args.target}". ${documentationLink}`);
// Bundle optional middleware
const optionalMiddleware = [];
// https://floating-ui.com/docs/size
if (size)
optionalMiddleware.push(size(args.middleware?.size));
// https://floating-ui.com/docs/autoPlacement
if (autoPlacement)
optionalMiddleware.push(autoPlacement(args.middleware?.autoPlacement));
// https://floating-ui.com/docs/hide
if (hide)
optionalMiddleware.push(hide(args.middleware?.hide));
// https://floating-ui.com/docs/inline
if (inline)
optionalMiddleware.push(inline(args.middleware?.inline));
// Floating UI Compute Position
// https://floating-ui.com/docs/computePosition
computePosition(triggerNode, elemPopup, {
placement: args.placement ?? 'bottom',
// Middleware - NOTE: the order matters:
// https://floating-ui.com/docs/middleware#ordering
middleware: [
// https://floating-ui.com/docs/offset
offset(args.middleware?.offset ?? 8),
// https://floating-ui.com/docs/shift
shift(args.middleware?.shift ?? { padding: 8 }),
// https://floating-ui.com/docs/flip
flip(args.middleware?.flip),
// https://floating-ui.com/docs/arrow
arrow(args.middleware?.arrow ?? { element: elemArrow || null }),
// Implement optional middleware
...optionalMiddleware
]
}).then(({ x, y, placement, middlewareData }) => {
Object.assign(elemPopup.style, {
left: `${x}px`,
top: `${y}px`
});
// Handle Arrow Placement:
// https://floating-ui.com/docs/arrow
if (elemArrow) {
const { x: arrowX, y: arrowY } = middlewareData.arrow;
// @ts-expect-error implicit any
const staticSide = {
top: 'bottom',
right: 'left',
bottom: 'top',
left: 'right'
}[placement.split('-')[0]];
Object.assign(elemArrow.style, {
left: arrowX != null ? `${arrowX}px` : '',
top: arrowY != null ? `${arrowY}px` : '',
right: '',
bottom: '',
[staticSide]: '-4px'
});
}
});
}
// State Handlers
function open() {
if (!elemPopup)
return;
// Set open state to on
popupState.open = true;
// Return the current state
if (args.state)
args.state({ state: popupState.open });
// Update render settings
render();
// Update the DOM
elemPopup.style.display = 'block';
elemPopup.style.opacity = '1';
elemPopup.style.pointerEvents = 'auto';
// enable popup interactions
elemPopup.removeAttribute('inert');
// Trigger Floating UI autoUpdate (open only)
// https://floating-ui.com/docs/autoUpdate
popupState.autoUpdateCleanup = autoUpdate(triggerNode, elemPopup, render);
// Focus the first focusable element within the popup
focusablePopupElements = Array.from(elemPopup?.querySelectorAll(focusableAllowedList));
}
function close(callback) {
if (!elemPopup)
return;
// Set transition duration
const cssTransitionDuration = parseFloat(window.getComputedStyle(elemPopup).transitionDuration.replace('s', '')) * 1000;
setTimeout(() => {
// Set open state to off
popupState.open = false;
// Return the current state
if (args.state)
args.state({ state: popupState.open });
// Update the DOM
elemPopup.style.opacity = '0';
// disable popup interactions
elemPopup.setAttribute('inert', '');
// Cleanup Floating UI autoUpdate (close only)
if (popupState.autoUpdateCleanup)
popupState.autoUpdateCleanup();
// Trigger callback
if (callback)
callback();
}, cssTransitionDuration);
}
// Event Handlers
function toggle() {
popupState.open === false ? open() : close();
}
function onWindowClick(event) {
// Return if the popup is not yet open
if (popupState.open === false)
return;
// Return if click is the trigger element
if (triggerNode.contains(event.target))
return;
// If click it outside the popup
if (elemPopup && elemPopup.contains(event.target) === false) {
close();
return;
}
// Handle Close Query State
const closeQueryString = args.closeQuery === undefined ? 'a[href], button' : args.closeQuery;
const closableMenuElements = elemPopup?.querySelectorAll(closeQueryString);
closableMenuElements?.forEach((elem) => {
if (elem.contains(event.target))
close();
});
}
// Keyboard Interactions for A11y
const onWindowKeyDown = (event) => {
if (popupState.open === false)
return;
// Handle keys
const key = event.key;
// On Esc key
if (key === 'Escape') {
event.preventDefault();
triggerNode.focus();
close();
return;
}
// Update focusable elements (important for Autocomplete)
focusablePopupElements = Array.from(elemPopup?.querySelectorAll(focusableAllowedList));
// On Tab or ArrowDown key
const triggerMenuFocused = popupState.open && document.activeElement === triggerNode;
if (triggerMenuFocused &&
(key === 'ArrowDown' || key === 'Tab') &&
focusableAllowedList.length > 0 &&
focusablePopupElements.length > 0) {
event.preventDefault();
focusablePopupElements[0].focus();
}
};
// Event Listeners
switch (args.event) {
case 'click':
triggerNode.addEventListener('click', toggle, true);
window.addEventListener('click', onWindowClick, true);
break;
case 'hover':
triggerNode.addEventListener('mouseover', open, true);
triggerNode.addEventListener('mouseleave', () => close(), true);
break;
case 'focus-blur':
triggerNode.addEventListener('focus', toggle, true);
triggerNode.addEventListener('blur', () => close(), true);
break;
case 'focus-click':
triggerNode.addEventListener('focus', open, true);
window.addEventListener('click', onWindowClick, true);
break;
default:
throw new Error(`Event value of '${args.event}' is not supported. ${documentationLink}`);
}
window.addEventListener('keydown', onWindowKeyDown, true);
// Render popup on initialization
render();
// Lifecycle
return {
update(newArgs) {
close(() => {
args = newArgs;
render();
setDomElements();
});
},
destroy() {
// Trigger Events
triggerNode.removeEventListener('click', toggle, true);
triggerNode.removeEventListener('mouseover', open, true);
triggerNode.removeEventListener('mouseleave', () => close(), true);
triggerNode.removeEventListener('focus', toggle, true);
triggerNode.removeEventListener('focus', open, true);
triggerNode.removeEventListener('blur', () => close(), true);
// Window Events
window.removeEventListener('click', onWindowClick, true);
window.removeEventListener('keydown', onWindowKeyDown, true);
}
};
}

View File

@ -0,0 +1,40 @@
/** Placement https://floating-ui.com/docs/computePosition#placement */
type Direction = 'top' | 'bottom' | 'left' | 'right';
type Placement = Direction | `${Direction}-start` | `${Direction}-end`;
export interface Middleware {
/** Offset middleware settings: https://floating-ui.com/docs/offset */
offset?: number | Record<string, any>;
/** Shift middleware settings: https://floating-ui.com/docs/shift */
shift?: Record<string, any>;
/** Flip middleware settings: https://floating-ui.com/docs/flip */
flip?: Record<string, any>;
/** Arrow middleware settings: https://floating-ui.com/docs/arrow */
arrow?: {
element: string;
} & Record<string, any>;
/** Size middleware settings: https://floating-ui.com/docs/size */
size?: Record<string, any>;
/** Auto Placement middleware settings: https://floating-ui.com/docs/autoPlacement */
autoPlacement?: Record<string, any>;
/** Hide middleware settings: https://floating-ui.com/docs/hide */
hide?: Record<string, any>;
/** Inline middleware settings: https://floating-ui.com/docs/inline */
inline?: Record<string, any>;
}
export interface PopupSettings {
/** Provide the event type. */
event: 'click' | 'hover' | 'focus-blur' | 'focus-click';
/** Match the popup data value `data-popup="targetNameHere"` */
target: string;
/** Set the placement position. Defaults 'bottom'. */
placement?: Placement;
/** Query elements that close the popup when clicked. Defaults `'a[href], button'`. */
closeQuery?: string;
/** Optional callback function that reports state change. */
state?: (event: {
state: boolean;
}) => void;
/** Provide Floating UI middleware settings. */
middleware?: Middleware;
}
export {};

View File

@ -0,0 +1,2 @@
// Popup Types
export {};

View File

@ -0,0 +1,7 @@
/// <reference types="svelte" />
/**
* Indicates that the user has enabled reduced motion on their device.
*
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion
*/
export declare const prefersReducedMotionStore: import("svelte/store").Readable<boolean>;

View File

@ -0,0 +1,26 @@
import { readable } from 'svelte/store';
import { BROWSER } from 'esm-env';
/** Prefers reduced motion */
const reducedMotionQuery = '(prefers-reduced-motion: reduce)';
function prefersReducedMotion() {
if (!BROWSER)
return false;
return window.matchMedia(reducedMotionQuery).matches;
}
/**
* Indicates that the user has enabled reduced motion on their device.
*
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion
*/
export const prefersReducedMotionStore = readable(prefersReducedMotion(), (set) => {
if (BROWSER) {
const setReducedMotion = (event) => {
set(event.matches);
};
const mediaQueryList = window.matchMedia(reducedMotionQuery);
mediaQueryList.addEventListener('change', setReducedMotion);
return () => {
mediaQueryList.removeEventListener('change', setReducedMotion);
};
}
});

View File

@ -0,0 +1,56 @@
<script>import { fade } from "svelte/transition";
import { tocStore, tocActiveId } from "./stores.js";
export let inactive = "opacity-60 hover:opacity-100";
export let active = "text-primary-500";
export let activeId = "";
export let indentStyles = {
h2: "",
h3: "ml-4",
h4: "ml-8",
h5: "ml-12",
h6: "ml-16"
};
export let regionLead = "font-bold";
export let regionList = "";
export let regionListItem = "";
export let regionAnchor = "";
const cBase = "space-y-4";
const cList = "space-y-2";
const cListItem = "block";
const cAnchor = "";
$:
reactiveActiveId = $tocActiveId ? $tocActiveId : activeId.replace("#", "");
$:
classesBase = `${cBase} ${$$props.class ?? ""}`;
$:
classesList = `${cList} ${regionList}`;
$:
classesListItem = `${cListItem} ${regionListItem}`;
$:
classesAnchor = `${cAnchor} ${regionAnchor}`;
</script>
{#if $tocStore.length}
<nav class="toc {classesBase}" data-testid="toc" transition:fade|local={{ duration: 100 }}>
<!-- Slot: Default (title) -->
<div class={regionLead}>
<slot>Table of Contents</slot>
</div>
<!-- List -->
<ul class="toc-list {classesList}">
{#each $tocStore as tocHeading}
<li class="toc-list-item {classesListItem} {indentStyles[tocHeading.element]}">
<a
href="#{tocHeading.id}"
class="toc-anchor {classesAnchor} {tocHeading.id === reactiveActiveId ? active : inactive}"
on:click={() => {
reactiveActiveId = tocHeading.id;
}}
>
{tocHeading.text}
</a>
</li>
{/each}
</ul>
</nav>
{/if}

View File

@ -0,0 +1,34 @@
import { SvelteComponentTyped } from "svelte";
declare const __propDef: {
props: {
[x: string]: any;
/** Provide classes to set the inactive anchor styles.*/
inactive?: string | undefined;
/** Provide classes to set the active anchor styles.*/
active?: string | undefined;
/** Set the active permalink ID on load.*/
activeId?: string | undefined;
/** Set indentation per each queried element.*/
indentStyles?: Record<string, string> | undefined;
/** Provide arbitrary classes to the lead regions, used for titles.*/
regionLead?: string | undefined;
/** Provide arbitrary classes to style the list element.*/
regionList?: string | undefined;
/** Provide arbitrary classes to style the list item elements.*/
regionListItem?: string | undefined;
/** Provide arbitrary classes to style the anchor elements.*/
regionAnchor?: string | undefined;
};
events: {
[evt: string]: CustomEvent<any>;
};
slots: {
default: {};
};
};
export type TableOfContentsProps = typeof __propDef.props;
export type TableOfContentsEvents = typeof __propDef.events;
export type TableOfContentsSlots = typeof __propDef.slots;
export default class TableOfContents extends SvelteComponentTyped<TableOfContentsProps, TableOfContentsEvents, TableOfContentsSlots> {
}
export {};

View File

@ -0,0 +1,16 @@
interface TOCCrawlerArgs {
/** Set generate mode to automatically set heading IDs. */
mode?: 'generate' | undefined;
/** Provide query list of elements. Defaults h2-h6. */
queryElements?: string;
scrollTarget?: string;
/** Reload the action when this key value changes. */
key?: unknown;
prefix?: string;
suffix?: string;
}
export declare function tocCrawler(node: HTMLElement, args?: TOCCrawlerArgs): {
update(newArgs: TOCCrawlerArgs): void;
destroy(): void;
};
export {};

View File

@ -0,0 +1,78 @@
// Action: Table of Contents Crawler
import { tocStore, tocActiveId } from './stores.js';
export function tocCrawler(node, args) {
let queryElements = 'h2, h3, h4, h5, h6';
let scrollTarget = 'body';
let headings;
let permalinks = [];
function init() {
// Set accepted list of query elements
// (IMPORTANT: must proceed resetting `headings` below)
if (args?.queryElements)
queryElements = args.queryElements;
// Set the desired scroll target to monitor
if (args?.scrollTarget)
scrollTarget = args.scrollTarget;
// Reset local values
headings = node.querySelectorAll(queryElements);
permalinks = [];
// Query and process the headings
queryHeadings();
}
function queryHeadings() {
headings?.forEach((elemHeading) => {
// If heading has ignore attribute, skip it
if (elemHeading.hasAttribute('data-toc-ignore'))
return;
// If generate mode and heading ID not present, assign one
if (args?.mode === 'generate' && !elemHeading.id) {
const newHeadingId = elemHeading.firstChild?.textContent
?.trim()
.replaceAll(/[^a-zA-Z0-9 ]/g, '')
.replaceAll(' ', '-')
.toLowerCase();
const prefix = args.prefix ? `${args.prefix}-` : '';
const suffix = args.suffix ? `-${args.suffix}` : '';
elemHeading.id = prefix + newHeadingId + suffix;
}
// Push heading data to the permalink array
permalinks.push({
element: elemHeading.nodeName.toLowerCase(),
id: elemHeading.id,
text: elemHeading.firstChild?.textContent?.trim() || ''
});
});
// Set the store with the permalink array
tocStore.set(permalinks);
}
// Listens to scroll event, determines top-most heading, provides that to the `tocActiveId` store
function onWindowScroll(e) {
if (!headings?.length)
return;
const targetElem = e.target;
if (!(targetElem instanceof HTMLElement))
throw new Error('scrollTarget is not an HTMLElement');
const scrollableTop = targetElem.getBoundingClientRect().top || 0;
const headingSizeThreshold = 40; // px
for (const elemHeading of headings) {
const headerBoundTop = elemHeading.getBoundingClientRect().top;
const offsetTop = headerBoundTop - scrollableTop + headingSizeThreshold;
if (offsetTop >= 0)
return tocActiveId.set(elemHeading.id);
}
}
// Lifecycle
init();
if (scrollTarget)
document.querySelector(scrollTarget)?.addEventListener('scroll', onWindowScroll);
return {
update(newArgs) {
args = newArgs;
init();
},
destroy() {
if (scrollTarget)
document.querySelector(scrollTarget)?.removeEventListener('scroll', onWindowScroll);
}
};
}

View File

@ -0,0 +1,6 @@
/// <reference types="svelte" />
import type { TOCHeadingLink } from './types.js';
/** Contains the set of table of contents link data. */
export declare const tocStore: import("svelte/store").Writable<TOCHeadingLink[]>;
/** Contains the ID of the top-most visible heading when scrolling. */
export declare const tocActiveId: import("svelte/store").Writable<string>;

View File

@ -0,0 +1,5 @@
import { writable } from 'svelte/store';
/** Contains the set of table of contents link data. */
export const tocStore = writable([]);
/** Contains the ID of the top-most visible heading when scrolling. */
export const tocActiveId = writable(undefined);

View File

@ -0,0 +1,5 @@
export interface TOCHeadingLink {
element: string;
id: string;
text: string;
}

View File

@ -0,0 +1,2 @@
// Table of Content Types
export {};

View File

@ -0,0 +1,141 @@
<script context="module">import { fly } from "svelte/transition";
import { prefersReducedMotionStore } from "../../index.js";
import { dynamicTransition } from "../../internal/transitions.js";
</script>
<script generics="TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition">import { flip } from "svelte/animate";
import { getToastStore } from "./stores.js";
const toastStore = getToastStore();
export let position = "b";
export let max = 3;
export let background = "variant-filled-secondary";
export let width = "max-w-[640px]";
export let color = "";
export let padding = "p-4";
export let spacing = "space-x-4";
export let rounded = "rounded-container-token";
export let shadow = "shadow-lg";
export let zIndex = "z-[888]";
export let buttonAction = "btn variant-filled";
export let buttonDismiss = "btn-icon btn-icon-sm variant-filled";
export let buttonDismissLabel = "\u2715";
export let transitions = !$prefersReducedMotionStore;
export let transitionIn = fly;
export let transitionInParams = { duration: 250 };
export let transitionOut = fly;
export let transitionOutParams = { duration: 250 };
const cWrapper = "flex fixed top-0 left-0 right-0 bottom-0 pointer-events-none";
const cSnackbar = "flex flex-col gap-y-2";
const cToast = "flex justify-between items-center pointer-events-auto";
const cToastActions = "flex items-center space-x-2";
let cPosition;
let cAlign;
let animAxis = { x: 0, y: 0 };
switch (position) {
case "t":
cPosition = "justify-center items-start";
cAlign = "items-center";
animAxis = { x: 0, y: -100 };
break;
case "b":
cPosition = "justify-center items-end";
cAlign = "items-center";
animAxis = { x: 0, y: 100 };
break;
case "l":
cPosition = "justify-start items-center";
cAlign = "items-start";
animAxis = { x: -100, y: 0 };
break;
case "r":
cPosition = "justify-end items-center";
cAlign = "items-end";
animAxis = { x: 100, y: 0 };
break;
case "tl":
cPosition = "justify-start items-start";
cAlign = "items-start";
animAxis = { x: -100, y: 0 };
break;
case "tr":
cPosition = "justify-end items-start";
cAlign = "items-end";
animAxis = { x: 100, y: 0 };
break;
case "bl":
cPosition = "justify-start items-end";
cAlign = "items-start";
animAxis = { x: -100, y: 0 };
break;
case "br":
cPosition = "justify-end items-end";
cAlign = "items-end";
animAxis = { x: 100, y: 0 };
break;
}
function onAction(index) {
$toastStore[index]?.action?.response();
toastStore.close($toastStore[index].id);
}
function onMouseEnter(index) {
if ($toastStore[index]?.hoverable) {
toastStore.freeze(index);
classesSnackbar += " scale-[105%]";
}
}
function onMouseLeave(index) {
if ($toastStore[index]?.hoverable) {
toastStore.unfreeze(index);
classesSnackbar = classesSnackbar.replace(" scale-[105%]", "");
}
}
$:
classesWrapper = `${cWrapper} ${cPosition} ${zIndex} ${$$props.class || ""}`;
$:
classesSnackbar = `${cSnackbar} ${cAlign} ${padding}`;
$:
classesToast = `${cToast} ${width} ${color} ${padding} ${spacing} ${rounded} ${shadow}`;
$:
filteredToasts = Array.from($toastStore).slice(0, max);
</script>
{#if $toastStore.length}
<!-- Wrapper -->
<div class="snackbar-wrapper {classesWrapper}" data-testid="snackbar-wrapper">
<!-- List -->
<div class="snackbar {classesSnackbar}">
{#each filteredToasts as t, i (t)}
<div
animate:flip={{ duration: transitions ? 250 : 0 }}
in:dynamicTransition|global={{
transition: transitionIn,
params: { x: animAxis.x, y: animAxis.y, ...transitionInParams },
enabled: transitions
}}
out:dynamicTransition|global={{
transition: transitionOut,
params: { x: animAxis.x, y: animAxis.y, ...transitionOutParams },
enabled: transitions
}}
on:mouseenter={() => onMouseEnter(i)}
on:mouseleave={() => onMouseLeave(i)}
role={t.hideDismiss ? 'alert' : 'alertdialog'}
aria-live="polite"
>
<!-- Toast -->
<div class="toast {classesToast} {t.background ?? background} {t.classes ?? ''}" data-testid="toast">
<div class="text-base">{@html t.message}</div>
{#if t.action || !t.hideDismiss}
<div class="toast-actions {cToastActions}">
{#if t.action}<button class={buttonAction} on:click={() => onAction(i)}>{@html t.action.label}</button>{/if}
{#if !t.hideDismiss}<button class={buttonDismiss} aria-label="Dismiss toast" on:click={() => toastStore.close(t.id)}
>{buttonDismissLabel}</button
>{/if}
</div>
{/if}
</div>
</div>
{/each}
</div>
</div>
{/if}

View File

@ -0,0 +1,55 @@
import { SvelteComponentTyped } from "svelte";
import { fly } from 'svelte/transition';
import { type Transition, type TransitionParams } from '../../index.js';
type FlyTransition = typeof fly;
declare class __sveltets_Render<TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition> {
props(): {
[x: string]: any;
/** Set the toast position.*/
position?: "b" | "br" | "tr" | "t" | "l" | "r" | "tl" | "bl" | undefined;
/** Maximum toasts that can show at once.*/
max?: number | undefined;
/** Provide classes to set the background color.*/
background?: string | undefined;
/** Provide classes to set width styles.*/
width?: string | undefined;
/** Provide classes to set the text color.*/
color?: string | undefined;
/** Provide classes to set the padding.*/
padding?: string | undefined;
/** Provide classes to set toast horizontal spacing.*/
spacing?: string | undefined;
/** Provide classes to set the border radius styles.*/
rounded?: string | undefined;
/** Provide classes to set the border box shadow.*/
shadow?: string | undefined;
/** Provide a class to override the z-index*/
zIndex?: string | undefined;
/** Provide styles for the action button.*/
buttonAction?: string | undefined;
/** Provide styles for the dismiss button.*/
buttonDismiss?: string | undefined;
/** The button label text.*/
buttonDismissLabel?: string | undefined;
/** Enable/Disable transitions*/
transitions?: boolean | undefined;
/** Provide the transition to used on entry.*/
transitionIn?: TransitionIn | undefined;
/** Transition params provided to `transitionIn`.*/
transitionInParams?: TransitionParams<TransitionIn> | undefined;
/** Provide the transition to used on exit.*/
transitionOut?: TransitionOut | undefined;
/** Transition params provided to `transitionOut`.*/
transitionOutParams?: TransitionParams<TransitionOut> | undefined;
};
events(): {} & {
[evt: string]: CustomEvent<any>;
};
slots(): {};
}
export type ToastProps<TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition> = ReturnType<__sveltets_Render<TransitionIn, TransitionOut>['props']>;
export type ToastEvents<TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition> = ReturnType<__sveltets_Render<TransitionIn, TransitionOut>['events']>;
export type ToastSlots<TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition> = ReturnType<__sveltets_Render<TransitionIn, TransitionOut>['slots']>;
export default class Toast<TransitionIn extends Transition = FlyTransition, TransitionOut extends Transition = FlyTransition> extends SvelteComponentTyped<ToastProps<TransitionIn, TransitionOut>, ToastEvents<TransitionIn, TransitionOut>, ToastSlots<TransitionIn, TransitionOut>> {
}
export {};

View File

@ -0,0 +1,12 @@
<script>import { initializeToastStore, getToastStore } from "./stores.js";
import Toast from "./Toast.svelte";
export let toastSettings = [];
export let max = void 0;
initializeToastStore();
const toastStore = getToastStore();
toastSettings.forEach((element) => {
toastStore.trigger(element);
});
</script>
<Toast {max} />

View File

@ -0,0 +1,18 @@
import { SvelteComponentTyped } from "svelte";
import type { ToastSettings } from './types.js';
declare const __propDef: {
props: {
toastSettings?: ToastSettings[] | undefined;
max?: number | undefined;
};
events: {
[evt: string]: CustomEvent<any>;
};
slots: {};
};
export type ToastTestProps = typeof __propDef.props;
export type ToastTestEvents = typeof __propDef.events;
export type ToastTestSlots = typeof __propDef.slots;
export default class ToastTest extends SvelteComponentTyped<ToastTestProps, ToastTestEvents, ToastTestSlots> {
}
export {};

View File

@ -0,0 +1,37 @@
/// <reference types="svelte" />
import type { ToastSettings, Toast } from './types.js';
/**
* Retrieves the `toastStore`.
*
* This can *ONLY* be called from the **top level** of components!
*
* @example
* ```svelte
* <script>
* import { getToastStore } from "@skeletonlabs/skeleton";
*
* const toastStore = getToastStore();
*
* toastStore.open({ message: "Welcome!" });
* </script>
* ```
*/
export declare function getToastStore(): ToastStore;
/**
* Initializes the `toastStore`.
*/
export declare function initializeToastStore(): ToastStore;
export type ToastStore = ReturnType<typeof toastService>;
declare function toastService(): {
subscribe: (this: void, run: import("svelte/store").Subscriber<Toast[]>, invalidate?: import("svelte/store").Invalidator<Toast[]> | undefined) => import("svelte/store").Unsubscriber;
close: (id: string) => void;
/** Add a new toast to the queue. */
trigger: (toast: ToastSettings) => string;
/** Remain visible on hover */
freeze: (index: number) => void;
/** Cancel remain visible on leave */
unfreeze: (index: number) => void;
/** Remove all toasts from queue */
clear: () => void;
};
export {};

View File

@ -0,0 +1,107 @@
// Toast Store Queue
import { writable } from 'svelte/store';
import { getContext, setContext } from 'svelte';
const toastDefaults = { message: 'Missing Toast Message', autohide: true, timeout: 5000 };
const TOAST_STORE_KEY = 'toastStore';
/**
* Retrieves the `toastStore`.
*
* This can *ONLY* be called from the **top level** of components!
*
* @example
* ```svelte
* <script>
* import { getToastStore } from "@skeletonlabs/skeleton";
*
* const toastStore = getToastStore();
*
* toastStore.open({ message: "Welcome!" });
* </script>
* ```
*/
export function getToastStore() {
const toastStore = getContext(TOAST_STORE_KEY);
if (!toastStore)
throw new Error('toastStore is not initialized. Please ensure that `initializeStores()` is invoked in the root layout file of this app!');
return toastStore;
}
/**
* Initializes the `toastStore`.
*/
export function initializeToastStore() {
const toastStore = toastService();
return setContext(TOAST_STORE_KEY, toastStore);
}
// Note for security; differentiates the queued toasts
function randomUUID() {
const random = Math.random();
return Number(random).toString(32);
}
function toastService() {
const { subscribe, set, update } = writable([]);
/** Remove toast in queue*/
const close = (id) => update((tStore) => {
if (tStore.length > 0) {
const index = tStore.findIndex((t) => t.id === id);
const selectedToast = tStore[index];
if (selectedToast) {
// Trigger Callback
if (selectedToast.callback)
selectedToast.callback({ id, status: 'closed' });
// Clear timeout
if (selectedToast.timeoutId)
clearTimeout(selectedToast.timeoutId);
// Remove
tStore.splice(index, 1);
}
}
return tStore;
});
// If toast should auto-hide, wait X time, then close by ID
function handleAutoHide(toast) {
if (toast.autohide === true) {
return setTimeout(() => {
close(toast.id);
}, toast.timeout);
}
}
return {
subscribe,
close,
/** Add a new toast to the queue. */
trigger: (toast) => {
const id = randomUUID();
update((tStore) => {
// Trigger Callback
if (toast && toast.callback)
toast.callback({ id, status: 'queued' });
// activate autohide when dismiss button is hidden.
if (toast.hideDismiss)
toast.autohide = true;
// Merge with defaults
const tMerged = { ...toastDefaults, ...toast, id };
// Handle auto-hide, if needed
tMerged.timeoutId = handleAutoHide(tMerged);
// Push into store
tStore.push(tMerged);
// Return
return tStore;
});
return id;
},
/** Remain visible on hover */
freeze: (index) => update((tStore) => {
if (tStore.length > 0)
clearTimeout(tStore[index].timeoutId);
return tStore;
}),
/** Cancel remain visible on leave */
unfreeze: (index) => update((tStore) => {
if (tStore.length > 0)
tStore[index].timeoutId = handleAutoHide(tStore[index]);
return tStore;
}),
/** Remove all toasts from queue */
clear: () => set([])
};
}

View File

@ -0,0 +1,35 @@
export type { ToastStore } from './stores.js';
export interface ToastSettings {
/** Provide the toast message. Supports HTML. */
message: string;
/** Provide CSS classes to set the background color. */
background?: string;
/** Enables auto-hide after the timeout duration. */
autohide?: boolean;
/** Set the auto-hide timeout duration. */
timeout?: number;
/** Generate a custom action button UI. */
action?: {
/** The button label. Supports HTML. */
label: string;
/** The function triggered when the button is pressed. */
response: () => void;
};
/** Hide dismiss button */
hideDismiss?: boolean;
/** Remain visible on Hover */
hoverable?: boolean;
/** Provide arbitrary CSS classes to style the toast. */
classes?: string;
/** Callback function that fires on trigger and close. */
callback?: (response: {
id: string;
status: 'queued' | 'closed';
}) => void;
}
export interface Toast extends ToastSettings {
/** A UUID will be auto-assigned on `.trigger()`. */
id: string;
/** The id of the `setTimeout` if `autohide` is enabled */
timeoutId?: ReturnType<typeof setTimeout>;
}

View File

@ -0,0 +1,2 @@
// Toast interface types
export {};

View File

@ -0,0 +1,18 @@
/**
* Used to initialize the stores for the `Modal`, `Toast`, and `Drawer` utilities.
*
* @example
* ```svelte
* <!-- App's root +layout.svelte -->
* <script>
* import { initializeStores, Toast, Modal, Drawer } from "@skeletonlabs/skeleton";
*
* initializeStores();
* </script>
*
* <Toast />
* <Modal />
* <Drawer />
* ```
*/
export declare function initializeStores(): void;

View File

@ -0,0 +1,25 @@
import { initializeModalStore } from './Modal/stores.js';
import { initializeToastStore } from './Toast/stores.js';
import { initializeDrawerStore } from './Drawer/stores.js';
/**
* Used to initialize the stores for the `Modal`, `Toast`, and `Drawer` utilities.
*
* @example
* ```svelte
* <!-- App's root +layout.svelte -->
* <script>
* import { initializeStores, Toast, Modal, Drawer } from "@skeletonlabs/skeleton";
*
* initializeStores();
* </script>
*
* <Toast />
* <Modal />
* <Drawer />
* ```
*/
export function initializeStores() {
initializeModalStore();
initializeToastStore();
initializeDrawerStore();
}