Merge fore release 10/10 (#2130)

Co-authored-by: Mahmoud Zino <74062808+Mahmoud-zino@users.noreply.github.com>
Co-authored-by: CokaKoala <31664583+AdrianGonz97@users.noreply.github.com>
Co-authored-by: Edrich Hans Chua <edrich@xendit.co>
Co-authored-by: A̴m̴r̴ ع̲مِےـرۅ <akkk33@protonmail.com>
Co-authored-by: Jordan Watts <91865823+jwatts777@users.noreply.github.com>
Co-authored-by: Hugo Korte <63101006+Hugos68@users.noreply.github.com>
Co-authored-by: Aaron Crockett <aaron.crockett.00@gmail.com>
This commit is contained in:
Chris Simmons
2023-10-10 10:51:55 -05:00
committed by GitHub
parent 5b9aab9c55
commit 3a30de270b
53 changed files with 1370 additions and 523 deletions

View File

@@ -0,0 +1,5 @@
---
"@skeletonlabs/tw-plugin": patch
---
bugfix: Separated light/dark classes for input validation variants

View File

@@ -0,0 +1,5 @@
---
"@skeletonlabs/skeleton": patch
---
bugfix: Resolved an issue that allowed focusTrap to escape hidden inputs

View File

@@ -0,0 +1,6 @@
---
"@skeletonlabs/skeleton": minor
---
- feat: File Dropzone and File Button now include a `fileInput` prop to reference to the input element.
- feat: File Dropzone now forwards the `on:focus`, `on:focusin`, and `on:focusout` events.

View File

@@ -0,0 +1,5 @@
---
"@skeletonlabs/tw-plugin": patch
---
bugfix: Resolved an issue where the Ring design token would generate invalid CSS when using the important modifier `!`

View File

@@ -0,0 +1,5 @@
---
"@skeletonlabs/skeleton": minor
---
feat: Added `input-chip-interface` and `input-chip-wrapper` region classes to Input Chips.

View File

@@ -0,0 +1,5 @@
---
"@skeletonlabs/skeleton": patch
---
feat: Added a `strokeLinecap` property to to Progress Radials, enabling rounded styling

View File

@@ -0,0 +1,5 @@
---
"@skeletonlabs/skeleton": patch
---
chore: Accordion now has RTL compatibility via the use of the `text-start` class

View File

@@ -0,0 +1,15 @@
---
"@skeletonlabs/skeleton": minor
---
feat: Multiple revisions to the Tree View component:
- Enhanced and properly named non-recursive Tree View events.
- Separated the recursive Tree View under the new component RecursiveTreeView.
- RecursiveTreeView now utilizes the `relational` prop to enable relational checking.
- RecursiveTreeView is now using ID arrays with 2-way binding to control the Tree View state, including:
- `expandedNodes`
- `disabledNodes`
- `checkedNodes`
- `indeterminateNodes` (affects only multiple relational mode)
- TreeViewNode now requires a unique ID to support the new checking system.

View File

@@ -0,0 +1,5 @@
---
"@skeletonlabs/skeleton": patch
---
bugfix: Added `title` prop to Lightswitch component for better i18n support

View File

@@ -222,9 +222,21 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
plugins: [sveltekit(), purgeCss()] plugins: [sveltekit(), purgeCss(`;
});
`; if (opts.codeblocks) {
contents += `{
safelist: {
// any selectors that begin with "hljs-" will not be purged
greedy: [/^hljs-/],
},
}),
],
});`;
} else {
contents += `)]
});`;
}
writeFileSync(filename, contents); writeFileSync(filename, contents);
} }
@@ -475,9 +487,18 @@ function copyTemplate(opts) {
scriptEndReg, scriptEndReg,
` `
// Highlight JS // Highlight JS
import hljs from 'highlight.js'; import hljs from 'highlight.js/lib/core';
import 'highlight.js/styles/github-dark.css'; import 'highlight.js/styles/github-dark.css';
import { storeHighlightJs } from '@skeletonlabs/skeleton'; import { storeHighlightJs } from '@skeletonlabs/skeleton';
import xml from 'highlight.js/lib/languages/xml'; // for HTML
import css from 'highlight.js/lib/languages/css';
import javascript from 'highlight.js/lib/languages/javascript';
import typescript from 'highlight.js/lib/languages/typescript';
hljs.registerLanguage('xml', xml); // for HTML
hljs.registerLanguage('css', css);
hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('typescript', typescript);
storeHighlightJs.set(hljs); storeHighlightJs.set(hljs);
</script>`, </script>`,
); );

View File

@@ -256,7 +256,7 @@ Problems? Open an issue on ${cyan('https://github.com/skeletonlabs/skeleton/issu
let packages = [ let packages = [
{ value: 'forms', label: 'Add Tailwind forms?', package: '@tailwindcss/forms', force: false }, { value: 'forms', label: 'Add Tailwind forms?', package: '@tailwindcss/forms', force: false },
{ value: 'typography', label: 'Add Tailwind typography?', package: '@tailwindcss/typography', force: false }, { value: 'typography', label: 'Add Tailwind typography?', package: '@tailwindcss/typography', force: false },
// { value: 'codeblocks', label: 'Add CodeBlock (installs highlight.js)?', package: 'highlight.js', force: false }, { value: 'codeblocks', label: 'Add CodeBlock (installs highlight.js)?', package: 'highlight.js', force: false },
{ value: 'popups', label: 'Add Popups (installs floating-ui)?', package: '@floating-ui/dom', force: false }, { value: 'popups', label: 'Add Popups (installs floating-ui)?', package: '@floating-ui/dom', force: false },
// { value: 'mdsvex', label: 'Add Markdown support (installs mdsvex)?', package: 'mdsvex', force: false }, // { value: 'mdsvex', label: 'Add Markdown support (installs mdsvex)?', package: 'mdsvex', force: false },
]; ];

View File

@@ -1,6 +1,6 @@
<!-- YOU CAN DELETE EVERYTHING IN THIS PAGE --> <!-- YOU CAN DELETE EVERYTHING IN THIS PAGE -->
<div class="container h-full mx-auto flex justify-center items-center"> <div class="container h-screen mx-auto flex justify-center items-center">
<div class="space-y-5"> <div class="space-y-5">
<h1 class="h1">Let's get cracking bones!</h1> <h1 class="h1">Let's get cracking bones!</h1>
<p>Start by exploring:</p> <p>Start by exploring:</p>

View File

@@ -162,7 +162,7 @@
/* success */ /* success */
.input-success { .input-success {
@apply !bg-success-200 !border-success-500 !text-success-700; @apply bg-success-200 dark:bg-success-200 border-success-500 dark:border-success-500 text-success-700 dark:text-success-700;
} }
.input-success::-moz-placeholder { .input-success::-moz-placeholder {
@apply text-success-700; @apply text-success-700;
@@ -176,7 +176,7 @@
/* warning */ /* warning */
.input-warning { .input-warning {
@apply !bg-warning-200 !border-warning-500 !text-warning-700; @apply bg-warning-200 dark:bg-warning-200 border-warning-500 dark:border-warning-500 text-warning-700 dark:text-warning-700;
} }
.input-warning::-moz-placeholder { .input-warning::-moz-placeholder {
@apply text-warning-700; @apply text-warning-700;
@@ -190,7 +190,7 @@
/* error */ /* error */
.input-error { .input-error {
@apply !bg-error-200 !border-error-500 !text-error-500; @apply bg-error-200 dark:bg-error-200 border-error-500 dark:border-error-500 text-error-500 dark:text-error-500;
} }
.input-error::-moz-placeholder { .input-error::-moz-placeholder {
@apply text-error-500; @apply text-error-500;

View File

@@ -27,7 +27,7 @@ export const rings = (): CssClasses => {
// Example: .ring-outline-token // Example: .ring-outline-token
'.ring-outline-token': { '.ring-outline-token': {
...ringOutlineShared, ...ringOutlineShared,
'--tw-ring-color': 'rgb(23 23 23 / 0.05);' // neutral-900, 5% opacity '--tw-ring-color': 'rgb(23 23 23 / 0.05)' // neutral-900, 5% opacity
}, },
'.dark .ring-outline-token': { '.dark .ring-outline-token': {
...ringOutlineShared, ...ringOutlineShared,

View File

@@ -1,6 +1,7 @@
// Action: Focus Trap // Action: Focus Trap
export function focusTrap(node: HTMLElement, enabled: boolean) { export function focusTrap(node: HTMLElement, enabled: boolean) {
const elemWhitelist = 'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'; const elemWhitelist =
'a[href]:not([tabindex="-1"]), button:not([tabindex="-1"]), input:not([tabindex="-1"]), textarea:not([tabindex="-1"]), select:not([tabindex="-1"]), details:not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])';
let elemFirst: HTMLElement; let elemFirst: HTMLElement;
let elemLast: HTMLElement; let elemLast: HTMLElement;

View File

@@ -37,7 +37,7 @@
// Classes // Classes
const cBase = ''; const cBase = '';
const cControl = 'text-left w-full flex items-center space-x-4'; const cControl = 'text-start w-full flex items-center space-x-4';
const cControlCaret = 'fill-current w-3 transition-transform duration-[200ms]'; const cControlCaret = 'fill-current w-3 transition-transform duration-[200ms]';
const cPanel = ''; const cPanel = '';

View File

@@ -8,6 +8,10 @@
* @type {FileList} * @type {FileList}
*/ */
export let files: FileList | undefined = undefined; export let files: FileList | undefined = undefined;
/**
* File input reference.
*/
export let fileInput: HTMLInputElement | undefined = undefined;
/** /**
* Required. Set a unique name for the file input. * Required. Set a unique name for the file input.
* @type {string} * @type {string}
@@ -18,11 +22,8 @@
/** Provide a button variant or other class styles. */ /** Provide a button variant or other class styles. */
export let button: CssClasses = 'btn variant-filled'; export let button: CssClasses = 'btn variant-filled';
// Local
let elemFileInput: HTMLElement;
function onButtonClick(): void { function onButtonClick(): void {
elemFileInput.click(); if (fileInput) fileInput.click();
} }
function prunedRestProps() { function prunedRestProps() {
@@ -38,7 +39,7 @@
<div class="file-button {classesBase}" data-testid="file-button"> <div class="file-button {classesBase}" data-testid="file-button">
<!-- NOTE: Don't use `hidden` as it prevents `required` from operating --> <!-- NOTE: Don't use `hidden` as it prevents `required` from operating -->
<div class="w-0 h-0 overflow-hidden"> <div class="w-0 h-0 overflow-hidden">
<input type="file" bind:this={elemFileInput} bind:files {name} {...prunedRestProps()} on:change /> <input type="file" bind:this={fileInput} bind:files {name} {...prunedRestProps()} on:change />
</div> </div>
<!-- Button --> <!-- Button -->
<button <button

View File

@@ -8,6 +8,10 @@
* @type {FileList} * @type {FileList}
*/ */
export let files: FileList | undefined = undefined; export let files: FileList | undefined = undefined;
/**
* File input reference.
*/
export let fileInput: HTMLInputElement | undefined = undefined;
/** /**
* Required. Set a unique name for the file input. * Required. Set a unique name for the file input.
* @type {string} * @type {string}
@@ -55,6 +59,7 @@
<!-- NOTE: keep `bind:files` here, unlike FileButton --> <!-- NOTE: keep `bind:files` here, unlike FileButton -->
<input <input
bind:files bind:files
bind:this={fileInput}
type="file" type="file"
{name} {name}
class="dropzone-input {classesInput}" class="dropzone-input {classesInput}"
@@ -68,6 +73,9 @@
on:keydown on:keydown
on:keyup on:keyup
on:keypress on:keypress
on:focus
on:focusin
on:focusout
/> />
<!-- Interface --> <!-- Interface -->
<div class="dropzone-interface {classesInterface} {regionInterface}"> <div class="dropzone-interface {classesInterface} {regionInterface}">

View File

@@ -78,6 +78,14 @@
/** Provide classes to set border radius styles. */ /** Provide classes to set border radius styles. */
export let rounded: CssClasses = 'rounded-container-token'; export let rounded: CssClasses = 'rounded-container-token';
// Props (regions)
/** Provide arbitrary classes to style the chip wrapper region. */
export let regionChipWrapper: CssClasses = '';
/** Provide arbitrary classes to style the chip list region. */
export let regionChipList: CssClasses = '';
/** Provide arbitrary classes to style the input field region. */
export let regionInput: CssClasses = '';
// Props (transition) // Props (transition)
/** /**
* Enable/Disable transitions * Enable/Disable transitions
@@ -127,7 +135,7 @@
// Classes // Classes
const cBase = 'textarea cursor-pointer'; const cBase = 'textarea cursor-pointer';
const cInterface = 'space-y-4'; const cChipWrapper = 'space-y-4';
const cChipList = 'flex flex-wrap gap-2'; const cChipList = 'flex flex-wrap gap-2';
const cInputField = 'unstyled bg-transparent border-0 !ring-0 p-0 w-full'; const cInputField = 'unstyled bg-transparent border-0 !ring-0 p-0 w-full';
@@ -220,9 +228,9 @@
$: classesInvalid = inputValid === false ? invalid : ''; $: classesInvalid = inputValid === false ? invalid : '';
// Reactive // Reactive
$: classesBase = `${cBase} ${padding} ${rounded} ${$$props.class ?? ''} ${classesInvalid}`; $: classesBase = `${cBase} ${padding} ${rounded} ${$$props.class ?? ''} ${classesInvalid}`;
$: classesInterface = `${cInterface}`; $: classesChipWrapper = `${cChipWrapper} ${regionChipWrapper}`;
$: classesChipList = `${cChipList}`; $: classesChipList = `${cChipList} ${regionChipList}`;
$: classesInputField = `${cInputField}`; $: classesInput = `${cInputField} ${regionInput}`;
$: chipValues = $: chipValues =
value?.map((val, i) => { value?.map((val, i) => {
if (chipValues[i]?.val === val) return chipValues[i]; if (chipValues[i]?.val === val) return chipValues[i];
@@ -238,15 +246,15 @@
{#each value as option}<option value={option}>{option}</option>{/each} {#each value as option}<option value={option}>{option}</option>{/each}
</select> </select>
</div> </div>
<!-- Interface --> <!-- Chip Wrapper -->
<div class="input-chip-interface {classesInterface}"> <div class="input-chip-wrapper {classesChipWrapper}">
<!-- Input Field --> <!-- Input Field -->
<form on:submit={addChip}> <form on:submit={addChip}>
<input <input
type="text" type="text"
bind:value={input} bind:value={input}
placeholder={$$restProps.placeholder ?? 'Enter values...'} placeholder={$$restProps.placeholder ?? 'Enter values...'}
class="input-chip-field {classesInputField}" class="input-chip-field {classesInput}"
on:input={onInputHandler} on:input={onInputHandler}
on:input on:input
on:focus on:focus

View File

@@ -15,6 +15,8 @@
export let stroke = 40; // px export let stroke = 40; // px
/** Sets the base font size. Scales responsively. */ /** Sets the base font size. Scales responsively. */
export let font = 56; // px export let font = 56; // px
/** Sets the stoke-linecap value */
export let strokeLinecap: 'butt' | 'round' | 'square' = 'butt';
// Props (styles) // Props (styles)
/** Provide classes to set the width. */ /** Provide classes to set the width. */
@@ -37,7 +39,7 @@
// Calculated Values // Calculated Values
const baseSize = 512; // px const baseSize = 512; // px
const radius: number = baseSize / 2; const radius: number = baseSize / 2 - stroke / 2;
let circumference: number = radius; let circumference: number = radius;
let dashoffset: number; let dashoffset: number;
@@ -73,18 +75,19 @@
<!-- Draw SVG --> <!-- Draw SVG -->
<svg viewBox="0 0 {baseSize} {baseSize}" class="rounded-full" class:animate-spin={value === undefined}> <svg viewBox="0 0 {baseSize} {baseSize}" class="rounded-full" class:animate-spin={value === undefined}>
<!-- Track --> <!-- Track -->
<circle class="progress-radial-track {cBaseTrack} {track}" stroke-width={stroke} r={baseSize / 2} cx="50%" cy="50%" /> <circle class="progress-radial-track {cBaseTrack} {track}" stroke-width={stroke} r={radius} cx="50%" cy="50%" />
<!-- Meter --> <!-- Meter -->
<circle <circle
class="progress-radial-meter {cBaseMeter} {meter}" class="progress-radial-meter {cBaseMeter} {meter}"
stroke-width={stroke} stroke-width={stroke}
r={baseSize / 2} r={radius}
cx="50%" cx="50%"
cy="50%" cy="50%"
style:stroke-dasharray="{circumference} style:stroke-dasharray="{circumference}
{circumference}" {circumference}"
style:stroke-dashoffset={dashoffset} style:stroke-dashoffset={dashoffset}
stroke-linecap={strokeLinecap}
/> />
<!-- Center Text --> <!-- Center Text -->

View File

@@ -18,7 +18,8 @@ describe('ProgressRadial.svelte', () => {
meter: 'stroke-black dark:stroke-white', meter: 'stroke-black dark:stroke-white',
color: 'fill-black dark:fill-white', color: 'fill-black dark:fill-white',
font: 56, font: 56,
label: 'testProgressRadial1' label: 'testProgressRadial1',
roundedLineCap: true
} }
}); });
expect(getByTestId('progress-radial')).toBeTruthy(); expect(getByTestId('progress-radial')).toBeTruthy();

View File

@@ -0,0 +1,111 @@
<script lang="ts">
import { setContext } from 'svelte';
// Types
import type { CssClasses, TreeViewNode } from '../../index.js';
import RecursiveTreeViewItem from './RecursiveTreeViewItem.svelte';
// Props (parent)
/** Enable tree-view selection. */
export let selection = false;
/** Enable selection of multiple items. */
export let multiple = false;
/** Enable relational checking. */
export let relational = false;
/**
* Provide data-driven nodes.
* @type {TreeViewNode[]}
*/
export let nodes: TreeViewNode[] = [];
/**
* provides id's of expanded nodes
* @type {string[]}
*/
export let expandedNodes: string[] = [];
/**
* provides id's of disabled nodes
* @type {string[]}
*/
export let disabledNodes: string[] = [];
/**
* provides id's of checked nodes
* @type {string[]}
*/
export let checkedNodes: string[] = [];
/**
* provides id's of indeterminate nodes
* @type {string[]}
*/
export let indeterminateNodes: string[] = [];
/** Provide classes to set the tree width. */
export let width: CssClasses = 'w-full';
/** Provide classes to set the vertical spacing between items. */
export let spacing: CssClasses = 'space-y-1';
// Props (children)
/** Set open by default on load. */
export let open = false;
/** Set the tree disabled state */
export let disabled = false;
/** Provide classes to set the tree item padding styles. */
export let padding: CssClasses = 'py-4 px-4';
/** Provide classes to set the tree children indentation */
export let indent: CssClasses = 'ml-4';
/** Provide classes to set the tree item hover styles. */
export let hover: CssClasses = 'hover:variant-soft';
/** Provide classes to set the tree item rounded styles. */
export let rounded: CssClasses = 'rounded-container-token';
// Props (symbols)
/** Set the rotation of the item caret in the open state. */
export let caretOpen: CssClasses = 'rotate-180';
/** Set the rotation of the item caret in the closed state. */
export let caretClosed: CssClasses = '';
/* Set the hyphen symbol opacity for non-expandable rows. */
export let hyphenOpacity: CssClasses = 'opacity-10';
// Props (regions)
/** Provide arbitrary classes to the tree item summary region. */
export let regionSummary: CssClasses = '';
/** Provide arbitrary classes to the symbol icon region. */
export let regionSymbol: CssClasses = '';
/** Provide arbitrary classes to the children region. */
export let regionChildren: CssClasses = '';
// Props A11y
/** Provide the ARIA labelledby value. */
export let labelledby = '';
// Context API
setContext('open', open);
setContext('selection', selection);
setContext('multiple', multiple);
setContext('relational', relational);
setContext('disabled', disabled);
setContext('padding', padding);
setContext('indent', indent);
setContext('hover', hover);
setContext('rounded', rounded);
setContext('caretOpen', caretOpen);
setContext('caretClosed', caretClosed);
setContext('hyphenOpacity', hyphenOpacity);
setContext('regionSummary', regionSummary);
setContext('regionSymbol', regionSymbol);
setContext('regionChildren', regionChildren);
// Reactive
$: classesBase = `${width} ${spacing} ${$$props.class ?? ''}`;
</script>
<div
class="tree {classesBase}"
data-testid="tree"
role="tree"
aria-multiselectable={multiple}
aria-label={labelledby}
aria-disabled={disabled}
>
{#if nodes && nodes.length > 0}
<RecursiveTreeViewItem {nodes} bind:expandedNodes bind:disabledNodes bind:checkedNodes bind:indeterminateNodes />
{/if}
</div>

View File

@@ -0,0 +1,161 @@
<script lang="ts">
import TreeViewItem from './TreeViewItem.svelte';
import RecursiveTreeViewItem from './RecursiveTreeViewItem.svelte';
import type { TreeViewNode } from './types.js';
import { getContext, onMount, tick } from 'svelte';
// this can't be passed using context, since we have to pass it to recursive children.
/** Provide data-driven nodes. */
export let nodes: TreeViewNode[] = [];
/**
* provides id's of expanded nodes
* @type {string[]}
*/
export let expandedNodes: string[] = [];
/**
* provides id's of disabled nodes
* @type {string[]}
*/
export let disabledNodes: string[] = [];
/**
* provides id's of checked nodes
* @type {string[]}
*/
export let checkedNodes: string[] = [];
/**
* provides id's of indeterminate nodes
* @type {string[]}
*/
export let indeterminateNodes: string[] = [];
// Context API
let selection: boolean = getContext('selection');
let multiple: boolean = getContext('multiple');
let relational: boolean = getContext('relational');
let tempCheckedNodes: string[] = [];
// Locals
let group: unknown;
let name = '';
function toggleNode(node: TreeViewNode, open: boolean) {
// toggle only nodes with children
if (!node.children?.length) return;
if (open) {
// node is not registered as opened
if (!expandedNodes.includes(node.id)) {
expandedNodes.push(node.id);
expandedNodes = expandedNodes;
}
} else {
// node is registered as open
if (expandedNodes.includes(node.id)) {
expandedNodes.splice(expandedNodes.indexOf(node.id), 1);
expandedNodes = expandedNodes;
}
}
}
function checkNode(node: TreeViewNode, checked: boolean, indeterminate: boolean) {
if (checked) {
// node is not registered as checked
if (!checkedNodes.includes(node.id)) {
checkedNodes.push(node.id);
checkedNodes = checkedNodes;
}
// node is not indeterminate but registered as indeterminate
if (!indeterminate && indeterminateNodes.includes(node.id)) {
indeterminateNodes.splice(indeterminateNodes.indexOf(node.id), 1);
indeterminateNodes = indeterminateNodes;
}
} else {
// node is registered as checked
if (checkedNodes.includes(node.id)) {
checkedNodes.splice(checkedNodes.indexOf(node.id), 1);
checkedNodes = checkedNodes;
}
// node is indeterminate but not registered as indeterminate
if (indeterminate && !indeterminateNodes.includes(node.id)) {
indeterminateNodes.push(node.id);
indeterminateNodes = indeterminateNodes;
// node is not indeterminate but registered as indeterminate
} else if (!indeterminate && indeterminateNodes.includes(node.id)) {
indeterminateNodes.splice(indeterminateNodes.indexOf(node.id), 1);
indeterminateNodes = indeterminateNodes;
}
}
}
// init check flow will messup the checked nodes, so we save it to reassign it onMount.
tempCheckedNodes = [...checkedNodes];
onMount(async () => {
if (selection) {
// random number as name
name = String(Math.random());
// init groups if not initialized yet
if (group === undefined) {
if (multiple) {
group = [];
nodes.forEach((node) => {
if (checkedNodes.includes(node.id) && Array.isArray(group)) group.push(node.id);
});
group = group;
} else if (!nodes.some((node) => checkedNodes.includes(node.id))) {
group = '';
}
}
// remove relational links
if (!relational) treeItems = [];
// reassign checkNodes to ensure component starting with the correct check values.
checkedNodes = [];
await tick();
checkedNodes = [...tempCheckedNodes];
}
});
// important to pass children up to items (recursively)
export let treeItems: TreeViewItem[] = [];
let children: TreeViewItem[][] = [];
</script>
{#if nodes && nodes.length > 0}
{#each nodes as node, i}
<TreeViewItem
bind:this={treeItems[i]}
bind:children={children[i]}
bind:group
bind:name
bind:value={node.id}
hideLead={!node.lead}
hideChildren={!node.children || node.children.length === 0}
open={expandedNodes.includes(node.id)}
disabled={disabledNodes.includes(node.id)}
checked={checkedNodes.includes(node.id)}
indeterminate={indeterminateNodes.includes(node.id)}
on:toggle={(e) => toggleNode(node, e.detail.open)}
on:groupChange={(e) => checkNode(node, e.detail.checked, e.detail.indeterminate)}
>
{@html node.content}
<svelte:fragment slot="lead">
{@html node.lead}
</svelte:fragment>
<svelte:fragment slot="children">
<RecursiveTreeViewItem
nodes={node.children}
bind:expandedNodes
bind:disabledNodes
bind:checkedNodes
bind:indeterminateNodes
bind:treeItems={children[i]}
/>
</svelte:fragment>
</TreeViewItem>
{/each}
{/if}

View File

@@ -2,19 +2,13 @@
import { setContext } from 'svelte'; import { setContext } from 'svelte';
// Types // Types
import type { CssClasses, TreeViewNode } from '../../index.js'; import type { CssClasses } from '../../index.js';
import TreeViewDataDrivenItem from './TreeViewDataDrivenItem.svelte';
// Props (parent) // Props (parent)
/** Enable tree-view selection. */ /** Enable tree-view selection. */
export let selection = false; export let selection = false;
/** Enable selection of multiple items. */ /** Enable selection of multiple items. */
export let multiple = false; export let multiple = false;
/**
* Provide data-driven nodes.
* @type {TreeViewNode[]}
*/
export let nodes: TreeViewNode[] = [];
/** Provide classes to set the tree width. */ /** Provide classes to set the tree width. */
export let width: CssClasses = 'w-full'; export let width: CssClasses = 'w-full';
/** Provide classes to set the vertical spacing between items. */ /** Provide classes to set the vertical spacing between items. */
@@ -114,9 +108,5 @@
aria-label={labelledby} aria-label={labelledby}
aria-disabled={disabled} aria-disabled={disabled}
> >
{#if nodes && nodes.length > 0} <slot />
<TreeViewDataDrivenItem bind:nodes on:change on:click on:toggle on:keydown on:keyup />
{:else}
<slot />
{/if}
</div> </div>

View File

@@ -1,119 +0,0 @@
<script lang="ts">
/**
* This component is only in Data-driven tree-view to add children recursively.
*/
import { getContext, onMount } from 'svelte';
import TreeViewDataDrivenItem from './TreeViewDataDrivenItem.svelte';
import TreeViewItem from './TreeViewItem.svelte';
import type { TreeViewNode } from './types.js';
// this can't be passed using context, since we have to pass it to recursive children.
/** Provide data-driven nodes. */
export let nodes: TreeViewNode[] = [];
// Context API
/** Enable tree-view selection */
export let selection: boolean = getContext('selection');
/** Enable selection of multiple items. */
export let multiple: boolean = getContext('multiple');
// Locals
let group: unknown;
let name = '';
// Lifecycle
onMount(() => {
// random number as name
name = String(Math.random());
if (selection) {
group = multiple ? [] : '';
// manage group (checking) on initialization.
if (multiple) {
nodes.forEach((node) => {
if (!Array.isArray(group)) return;
// handle relations
if (node.children && node.children.length > 0) {
// at least one child is indeterminate => indeterminate item
if (node.children.some((c) => c.indeterminate)) {
node.indeterminate = true;
}
// all children are checked => check item
else if (node.children.every((c) => c.checked)) {
node.indeterminate = false;
group.push(node.value);
group = group;
}
// not all children are checked => indeterminate item
else if (node.children.some((c) => c.checked)) {
node.indeterminate = true;
}
// all children are unchecked => uncheck item
else {
node.indeterminate = false;
node.checked = false;
}
} else if (node.checked) {
group.push(node.value);
group = group;
}
});
// single selection mode
} else {
nodes.forEach((node) => {
if (!node.checked) return;
group = node.value;
});
}
}
});
// Functionality
function onGroupChange() {
if (multiple) {
nodes.forEach((node) => {
if (!Array.isArray(group)) return;
node.checked = group.includes(node.value);
});
} else {
nodes.forEach((node) => {
node.checked = node.value === group;
});
}
}
// important to pass children up to items (recursively)
export let treeItems: TreeViewItem[] = [];
let children: TreeViewItem[][] = [];
</script>
{#if nodes && nodes.length > 0}
{#each nodes as node, i}
<TreeViewItem
bind:this={treeItems[i]}
bind:open={node.open}
hideLead={!node.lead}
hideChildren={!node.children || node.children.length === 0}
bind:disabled={node.disabled}
bind:group
bind:name
bind:indeterminate={node.indeterminate}
bind:value={node.value}
bind:children={children[i]}
on:change={onGroupChange}
on:change
on:click
on:toggle
on:keydown
on:keyup
>
{@html node.content}
<svelte:fragment slot="lead">
{@html node.lead}
</svelte:fragment>
<svelte:fragment slot="children">
<TreeViewDataDrivenItem bind:nodes={node.children} bind:treeItems={children[i]} />
</svelte:fragment>
</TreeViewItem>
{/each}
{/if}

View File

@@ -7,7 +7,7 @@
* @slot {{}} lead - Allows for an optional leading element, such as an icon. * @slot {{}} lead - Allows for an optional leading element, such as an icon.
* @slot {{}} children - Provide TreeView item children. * @slot {{}} children - Provide TreeView item children.
*/ */
import { getContext, createEventDispatcher } from 'svelte'; import { getContext, createEventDispatcher, onMount } from 'svelte';
// Types // Types
import type { CssClasses, SvelteEvent, TreeViewItem } from '../../index.js'; import type { CssClasses, SvelteEvent, TreeViewItem } from '../../index.js';
@@ -28,6 +28,8 @@
* @type {unknown} * @type {unknown}
*/ */
export let value: unknown = undefined; export let value: unknown = undefined;
/** Set the input's check state */
export let checked = false;
/** /**
* Provide children references to support relational checking. * Provide children references to support relational checking.
* @type {TreeViewItem[]} * @type {TreeViewItem[]}
@@ -78,7 +80,6 @@
export let hideChildren = false; export let hideChildren = false;
// Locals // Locals
let checked = false;
let treeItem: HTMLDetailsElement; let treeItem: HTMLDetailsElement;
let childrenDiv: HTMLDivElement; let childrenDiv: HTMLDivElement;
@@ -90,28 +91,54 @@
// Svelte Checkbox Bugfix // Svelte Checkbox Bugfix
// GitHub: https://github.com/sveltejs/svelte/issues/2308 // GitHub: https://github.com/sveltejs/svelte/issues/2308
// REPL: https://svelte.dev/repl/de117399559f4e7e9e14e2fc9ab243cc?version=3.12.1 // REPL: https://svelte.dev/repl/de117399559f4e7e9e14e2fc9ab243cc?version=3.12.1
$: if (multiple) updateCheckbox(group); $: if (multiple) updateCheckbox(group, indeterminate);
$: if (multiple) updateGroup(checked); $: if (multiple) updateGroup(checked, indeterminate);
function updateCheckbox(group: unknown) { $: if (!multiple) updateRadio(group);
$: if (!multiple) updateRadioGroup(checked);
let initUpdate = true;
function updateCheckbox(group: unknown, indeterminate: boolean) {
if (!Array.isArray(group)) return; if (!Array.isArray(group)) return;
checked = group.indexOf(value) >= 0; checked = group.indexOf(value) >= 0;
/** @event {{checked: boolean, indeterminate: boolean}} groupChange - Fires when the group changes */
dispatch('groupChange', { checked: checked, indeterminate: indeterminate });
dispatch('childChange');
// called only once when initializing to apply default checks
if (initUpdate) {
onParentChange();
initUpdate = false;
}
} }
function updateGroup(checked: boolean) { function updateGroup(checked: boolean, indeterminate: boolean) {
if (!Array.isArray(group)) return; if (!Array.isArray(group)) return;
const index = group.indexOf(value); const index = group.indexOf(value);
if (checked) { if (checked) {
if (index < 0) { if (index < 0) {
group.push(value); group.push(value);
group = group; group = group;
// called only when the group changes
onParentChange();
} }
} else { } else {
if (index >= 0) { if (index >= 0) {
group.splice(index, 1); group.splice(index, 1);
group = group; group = group;
// called only when the group changes
onParentChange();
} }
} }
} }
function updateRadio(group: unknown) {
checked = group === value;
/** @event {{checked: boolean, indeterminate: boolean}} groupChange - Fires when the group changes */
dispatch('groupChange', { checked: checked, indeterminate: false });
if (group) dispatch('childChange');
}
function updateRadioGroup(checked: boolean) {
if (checked && group !== value) group = value;
else if (!checked && group === value) group = '';
}
// called when a child's value is changed // called when a child's value is changed
function onChildValueChange() { function onChildValueChange() {
if (multiple) { if (multiple) {
@@ -123,6 +150,10 @@
// at least one child is indeterminate => indeterminate item // at least one child is indeterminate => indeterminate item
if (children.some((c) => c.indeterminate)) { if (children.some((c) => c.indeterminate)) {
indeterminate = true; indeterminate = true;
if (index >= 0) {
group.splice(index, 1);
group = group;
}
} }
// all children are checked => check item // all children are checked => check item
else if (childrenValues.every((c) => Array.isArray(childrenGroup) && childrenGroup.includes(c))) { else if (childrenValues.every((c) => Array.isArray(childrenGroup) && childrenGroup.includes(c))) {
@@ -135,6 +166,10 @@
// not all children are checked => indeterminate item // not all children are checked => indeterminate item
else if (childrenValues.some((c) => Array.isArray(childrenGroup) && childrenGroup.includes(c))) { else if (childrenValues.some((c) => Array.isArray(childrenGroup) && childrenGroup.includes(c))) {
indeterminate = true; indeterminate = true;
if (index >= 0) {
group.splice(index, 1);
group = group;
}
} }
// all children are unchecked => uncheck item // all children are unchecked => uncheck item
else { else {
@@ -147,13 +182,17 @@
} }
// single selection mode // single selection mode
else { else {
if (group !== value) { // one of the children is checked => check item
// check item if (group !== value && children.some((c) => c.checked)) {
group = value; group = value;
// none of the children are checked => uncheck item
} else if (group === value && !children.some((c) => c.checked)) {
group = '';
} }
} }
// important to notify parent of item // important to notify parent of item
dispatch('change'); /** @event childChange - Fires when the group of the child changes */
dispatch('childChange');
} }
// used to update children of item when checked / unchecked in multiple mode // used to update children of item when checked / unchecked in multiple mode
@@ -168,7 +207,6 @@
if (!child || !Array.isArray(child.group)) return; if (!child || !Array.isArray(child.group)) return;
child.indeterminate = false; child.indeterminate = false;
if (child.group.indexOf(child.value) < 0) { if (child.group.indexOf(child.value) < 0) {
// child.group = [...child.group, child.value] won't work here.
child.group.push(child.value); child.group.push(child.value);
child.group = child.group; child.group = child.group;
} }
@@ -184,6 +222,7 @@
}; };
children.forEach((child) => { children.forEach((child) => {
if (!child) return;
// if parent is checked, check all children, else uncheck all children // if parent is checked, check all children, else uncheck all children
index >= 0 ? checkChild(child) : uncheckChild(child); index >= 0 ? checkChild(child) : uncheckChild(child);
// notify children to update values // notify children to update values
@@ -192,11 +231,11 @@
} }
// used to update children of item when checked / unchecked in single mode // used to update children of item when checked / unchecked in single mode
$: if (!multiple && group) { $: if (!multiple && group !== undefined) {
if (group !== value) { if (group !== value) {
// uncheck all children // uncheck all children
children.forEach((child) => { children.forEach((child) => {
if (child) child.group = []; if (child) child.group = '';
}); });
} }
} }
@@ -208,7 +247,7 @@
// whenever children are changed, reassign on:change events. // whenever children are changed, reassign on:change events.
$: children.forEach((child) => { $: children.forEach((child) => {
if (child) child.$on('change', () => onChildValueChange()); if (child) child.$on('childChange', onChildValueChange);
}); });
// A11y Key Down Handler // A11y Key Down Handler
@@ -337,12 +376,10 @@
{value} {value}
bind:checked bind:checked
bind:indeterminate bind:indeterminate
on:click
on:change
on:change={onParentChange} on:change={onParentChange}
/> />
{:else} {:else}
<input class="radio tree-item-radio" type="radio" bind:group {name} {value} on:click on:change /> <input class="radio tree-item-radio" type="radio" bind:group {name} {value} />
{/if} {/if}
{/if} {/if}

View File

@@ -1,18 +1,12 @@
export interface TreeViewNode { export interface TreeViewNode {
/** Nodes Unique ID */
id: string;
/** Main content. accepts HTML. */ /** Main content. accepts HTML. */
content: string; content: string;
/** Lead content. accepts HTML. */ /** Lead content. accepts HTML. */
lead?: string; lead?: string;
/** Set open by default on load. */
open?: boolean;
/** Set the tree disabled state. */
disabled?: boolean;
/** children nodes. */ /** children nodes. */
children?: TreeViewNode[]; children?: TreeViewNode[];
/** Set the input's value. */ /** Set the input's value. */
value?: unknown; value?: unknown;
/** input checked */
checked?: boolean;
/** input is set to indeterminate, only availabe in multiple selection mode. */
indeterminate?: boolean;
} }

View File

@@ -87,7 +87,8 @@ export { default as Tab } from './components/Tab/Tab.svelte';
export { default as TabAnchor } from './components/Tab/TabAnchor.svelte'; export { default as TabAnchor } from './components/Tab/TabAnchor.svelte';
export { default as TreeView } from './components/TreeView/TreeView.svelte'; export { default as TreeView } from './components/TreeView/TreeView.svelte';
export { default as TreeViewItem } from './components/TreeView/TreeViewItem.svelte'; export { default as TreeViewItem } from './components/TreeView/TreeViewItem.svelte';
export { default as TreeViewDataDrivenItem } from './components/TreeView/TreeViewDataDrivenItem.svelte'; export { default as RecursiveTreeView } from './components/TreeView/RecursiveTreeView.svelte';
export { default as RecursiveTreeViewItem } from './components/TreeView/RecursiveTreeViewItem.svelte';
// Utility Components // Utility Components
export { default as CodeBlock } from './utilities/CodeBlock/CodeBlock.svelte'; export { default as CodeBlock } from './utilities/CodeBlock/CodeBlock.svelte';
export { default as Modal } from './utilities/Modal/Modal.svelte'; export { default as Modal } from './utilities/Modal/Modal.svelte';

View File

@@ -6,6 +6,9 @@
import type { CssClasses, SvelteEvent } from '../../index.js'; import type { CssClasses, SvelteEvent } from '../../index.js';
// Props // Props
/** Customize the `title` attribute for the component. */
export let title = 'Toggle light or dark mode.';
// Props (styles)
/** Provide classes to set the light background color. */ /** Provide classes to set the light background color. */
export let bgLight: CssClasses = 'bg-surface-50'; export let bgLight: CssClasses = 'bg-surface-50';
/** Provide classes to set the dark background color. */ /** Provide classes to set the dark background color. */
@@ -85,7 +88,7 @@
role="switch" role="switch"
aria-label="Light Switch" aria-label="Light Switch"
aria-checked={$modeCurrent} aria-checked={$modeCurrent}
title="Toggle {$modeCurrent === true ? 'Dark' : 'Light'} Mode" {title}
tabindex="0" tabindex="0"
> >
<!-- Thumb --> <!-- Thumb -->

View File

@@ -19,6 +19,9 @@
}, },
"license": "MIT", "license": "MIT",
"homepage": "https://skeleton.dev/", "homepage": "https://skeleton.dev/",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.4.2"
},
"devDependencies": { "devDependencies": {
"@faker-js/faker": "^8.0.2", "@faker-js/faker": "^8.0.2",
"@floating-ui/dom": "^1.2.9", "@floating-ui/dom": "^1.2.9",
@@ -49,6 +52,7 @@
"tslib": "^2.5.3", "tslib": "^2.5.3",
"typescript": "^5.0.3", "typescript": "^5.0.3",
"vite": "^4.3.9", "vite": "^4.3.9",
"vite-plugin-tailwind-purgecss": "^0.1.3",
"vitest": "^0.32.0" "vitest": "^0.32.0"
}, },
"type": "module" "type": "module"

View File

@@ -4,6 +4,11 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
dependencies:
'@fortawesome/fontawesome-free':
specifier: ^6.4.2
version: 6.4.2
devDependencies: devDependencies:
'@faker-js/faker': '@faker-js/faker':
specifier: ^8.0.2 specifier: ^8.0.2
@@ -92,6 +97,9 @@ devDependencies:
vite: vite:
specifier: ^4.3.9 specifier: ^4.3.9
version: 4.3.9(@types/node@20.1.4) version: 4.3.9(@types/node@20.1.4)
vite-plugin-tailwind-purgecss:
specifier: ^0.1.3
version: 0.1.3(vite@4.3.9)
vitest: vitest:
specifier: ^0.32.0 specifier: ^0.32.0
version: 0.32.0 version: 0.32.0
@@ -353,6 +361,12 @@ packages:
'@floating-ui/core': 1.2.6 '@floating-ui/core': 1.2.6
dev: true dev: true
/@fortawesome/fontawesome-free@6.4.2:
resolution: {integrity: sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==}
engines: {node: '>=6'}
requiresBuild: true
dev: false
/@humanwhocodes/config-array@0.11.10: /@humanwhocodes/config-array@0.11.10:
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
@@ -601,6 +615,10 @@ packages:
resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==}
dev: true dev: true
/@types/estree@1.0.2:
resolution: {integrity: sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==}
dev: true
/@types/json-schema@7.0.11: /@types/json-schema@7.0.11:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true dev: true
@@ -1089,6 +1107,11 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/commander@10.0.1:
resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
engines: {node: '>=14'}
dev: true
/commander@4.1.1: /commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -1422,6 +1445,12 @@ packages:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true dev: true
/estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
dependencies:
'@types/estree': 1.0.2
dev: true
/esutils@2.0.3: /esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -1585,6 +1614,17 @@ packages:
path-is-absolute: 1.0.1 path-is-absolute: 1.0.1
dev: true dev: true
/glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 5.1.6
once: 1.4.0
dev: true
/globals@13.20.0: /globals@13.20.0:
resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1908,6 +1948,13 @@ packages:
brace-expansion: 1.1.11 brace-expansion: 1.1.11
dev: true dev: true
/minimatch@5.1.6:
resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
dev: true
/minimatch@9.0.3: /minimatch@9.0.3:
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
@@ -2299,6 +2346,16 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/purgecss@6.0.0-alpha.0:
resolution: {integrity: sha512-UC7d7uIyZsky+srEsSXny9BkbTcVn3ZtBCNX3rW3DsqJKhvUXFRpufA4ktcHzWF0+JLZgmsqjUm/8R82x9bHpw==}
hasBin: true
dependencies:
commander: 10.0.1
glob: 8.1.0
postcss: 8.4.24
postcss-selector-parser: 6.0.12
dev: true
/query-string@8.1.0: /query-string@8.1.0:
resolution: {integrity: sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==} resolution: {integrity: sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
@@ -3059,6 +3116,16 @@ packages:
- terser - terser
dev: true dev: true
/vite-plugin-tailwind-purgecss@0.1.3(vite@4.3.9):
resolution: {integrity: sha512-VVz9fwKBEEFSbj/rKxtwtczvoSrIqbzbo6S+MT7gH0CsmKNwlx947VMoV8B085ocxGCuFlddOPRDszNXLi2nTQ==}
peerDependencies:
vite: ^4.1.1
dependencies:
estree-walker: 3.0.3
purgecss: 6.0.0-alpha.0
vite: 4.3.9(@types/node@20.1.4)
dev: true
/vite@4.3.9(@types/node@20.1.4): /vite@4.3.9(@types/node@20.1.4):
resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==} resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}

View File

@@ -3,17 +3,11 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<!-- NOTE: SEO handled in root page layout: /src/+layout.svelte --> <!-- NOTE: SEO handled in root layout: /src/+layout.svelte -->
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head% %sveltekit.head%
<!-- Font Awesome -->
<!-- https://fontawesome.com/docs/web/setup/host-yourself/webfonts -->
<link href="/font-awesome/css/fontawesome.min.css" rel="stylesheet" />
<link href="/font-awesome/css/brands.min.css" rel="stylesheet" />
<link href="/font-awesome/css/solid.min.css" rel="stylesheet" />
</head> </head>
<body data-theme=""> <body data-theme="">

View File

@@ -10,6 +10,14 @@
const cBase = 'bg-surface-50 dark:bg-surface-900 border-t border-surface-500/10 text-xs md:text-base'; const cBase = 'bg-surface-50 dark:bg-surface-900 border-t border-surface-500/10 text-xs md:text-base';
const cRowOne = 'flex flex-col md:flex-row justify-between items-center md:items-start space-y-5 md:space-y-0'; const cRowOne = 'flex flex-col md:flex-row justify-between items-center md:items-start space-y-5 md:space-y-0';
const cRowTwo = 'flex flex-col md:flex-row justify-between items-center md:items-start space-y-4 md:space-y-0'; const cRowTwo = 'flex flex-col md:flex-row justify-between items-center md:items-start space-y-4 md:space-y-0';
// Social Icons
const socialLinks = [
{ title: 'GitHub', href: 'https://github.com/skeletonlabs/skeleton', icon: 'fa-github' },
{ title: 'Discord', href: 'https://discord.gg/EXqV7W8MtY', icon: 'fa-discord' },
{ title: 'X (Twitter)', href: 'https://x.com/SkeletonUI', icon: 'fa-x-twitter' },
{ title: 'YouTube', href: 'https://www.youtube.com/@skeletonlabs', icon: 'fa-youtube' }
];
</script> </script>
<div class="page-footer {cBase}"> <div class="page-footer {cBase}">
@@ -67,23 +75,12 @@
<span class="opacity-10 mx-2">|</span> <span class="opacity-10 mx-2">|</span>
<a class="anchor" href="https://skeletonlabs.co/" target="_blank" rel="noreferrer">Skeleton Labs </a> <a class="anchor" href="https://skeletonlabs.co/" target="_blank" rel="noreferrer">Skeleton Labs </a>
</p> </p>
<div class="flex space-x-4"> <div class="flex gap-6">
<a class="btn variant-soft" href="https://github.com/skeletonlabs/skeleton" target="_blank" rel="noreferrer"> {#each socialLinks as sl}
<i class="fa-brands fa-github" /> <a class="opacity-75 hover:opacity-100" href={sl.href} target="_blank" rel="noreferrer" title={sl.title}>
<span class="hidden md:inline-block ml-2">Github</span> <i class="fa-brands text-xl {sl.icon}" />
</a> </a>
<a class="btn variant-soft" href="https://discord.gg/EXqV7W8MtY" target="_blank" rel="noreferrer"> {/each}
<i class="fa-brands fa-discord" />
<span class="hidden md:inline-block ml-2">Discord</span>
</a>
<a class="btn variant-soft" href="https://twitter.com/SkeletonUI" target="_blank" rel="noreferrer">
<i class="fa-brands fa-twitter" />
<span class="hidden md:inline-block ml-2">Twitter</span>
</a>
<a class="btn variant-soft" href="https://www.youtube.com/@skeletonlabs" target="_blank" rel="noreferrer">
<i class="fa-brands fa-youtube" />
<span class="hidden md:inline-block ml-2">YouTube</span>
</a>
</div> </div>
</section> </section>
</div> </div>

View File

@@ -226,6 +226,8 @@
<CodeBlock <CodeBlock
language="ts" language="ts"
code={` code={`
import { afterNavigate } from '$app/navigation';
afterNavigate((params: any) => { afterNavigate((params: any) => {
const isNewPage: boolean = params.from && params.to && params.from.route.id !== params.to.route.id; const isNewPage: boolean = params.from && params.to && params.from.route.id !== params.to.route.id;
const elemPage = document.querySelector('#page'); const elemPage = document.querySelector('#page');

View File

@@ -47,12 +47,12 @@
<!-- Slot: Usage --> <!-- Slot: Usage -->
<svelte:fragment slot="usage"> <svelte:fragment slot="usage">
<p> <section class="space-y-4">
Uses <code class="code">input[type='file']</code> and allows for all native input features and accessibility. Including Uses <code class="code">input[type='file']</code> and allows for all native input features and accessibility. Including
<code class="code">multiple</code>, <code class="code">multiple</code>,
<code class="code">accept</code>, and <code class="code">required</code>. <code class="code">accept</code>, and <code class="code">required</code>.
</p> </section>
<div class="space-y-4"> <section class="space-y-4">
<h2 class="h2">Customization</h2> <h2 class="h2">Customization</h2>
<p> <p>
Customize the component with the available <code class="code">icon</code>, <code class="code">message</code>, and Customize the component with the available <code class="code">icon</code>, <code class="code">message</code>, and
@@ -80,18 +80,18 @@
/> />
</svelte:fragment> </svelte:fragment>
</DocsPreview> </DocsPreview>
</div> </section>
<div class="space-y-4"> <section class="space-y-4">
<h2 class="h2">Binding Method</h2> <h2 class="h2">Binding Method</h2>
<p>Use a <code class="code">FileList</code> to bind the file data.</p> <p>Use a <code class="code">FileList</code> to bind the file data.</p>
<CodeBlock language="ts" code={`let files: FileList;`} /> <CodeBlock language="ts" code={`let files: FileList;`} />
<CodeBlock language="html" code={`<FileDropzone ... bind:files={files} />`} /> <CodeBlock language="html" code={`<FileDropzone ... bind:files={files} />`} />
</div> </section>
<div class="space-y-4"> <section class="space-y-4">
<h2 class="h2">On Change Event</h2> <h2 class="h2">On Change Event</h2>
<p>Use the <code class="code">on:change</code> event to monitor file selection or changes.</p> <p>Use the <code class="code">on:change</code> event to monitor file selection or changes.</p>
<CodeBlock language="ts" code={`function onChangeHandler(e: Event): void {\n\tconsole.log('file data:', e);\n}`} /> <CodeBlock language="ts" code={`function onChangeHandler(e: Event): void {\n\tconsole.log('file data:', e);\n}`} />
<CodeBlock language="html" code={`<FileDropzone ... on:change={onChangeHandler}>Upload</FileDropzone>`} /> <CodeBlock language="html" code={`<FileDropzone ... on:change={onChangeHandler}>Upload</FileDropzone>`} />
</div> </section>
</svelte:fragment> </svelte:fragment>
</DocsShell> </DocsShell>

View File

@@ -21,6 +21,7 @@
// Reactive // Reactive
$: props = { value: 50, max: 100, step: 10 }; $: props = { value: 50, max: 100, step: 10 };
$: strokeProps = { value: 100, max: 400, step: 20 }; $: strokeProps = { value: 100, max: 400, step: 20 };
let strokeLinecap: 'butt' | 'round' | 'square' = 'butt';
</script> </script>
<DocsShell {settings}> <DocsShell {settings}>
@@ -45,47 +46,96 @@
<section class="space-y-4"> <section class="space-y-4">
<h2 class="h2">Styling</h2> <h2 class="h2">Styling</h2>
<p> <p>
Use the <code class="code">stroke</code> <code class="code">meter</code> or <code class="code">track</code>properties to style the Use the <code class="code">meter</code>, or <code class="code">track</code>, <code class="code">stroke</code>,
radial. <code class="code">strokeLinecap</code> properties to style the radial.
</p> </p>
<DocsPreview background="neutral"> <DocsPreview background="neutral">
<svelte:fragment slot="preview"> <svelte:fragment slot="preview">
<div class="w-full grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-2 text-center"> <div class="w-full grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-2 text-center">
<div class="p-4 space-y-2"> <div class="p-4 space-y-2">
<ProgressRadial stroke={strokeProps.value} meter="stroke-primary-500" track="stroke-primary-500/30" width="w-full" /> <ProgressRadial
stroke={strokeProps.value}
meter="stroke-primary-500"
track="stroke-primary-500/30"
width="w-full"
{strokeLinecap}
value={50}
/>
<p>Primary</p> <p>Primary</p>
</div> </div>
<div class="p-4 space-y-2"> <div class="p-4 space-y-2">
<ProgressRadial stroke={strokeProps.value} meter="stroke-secondary-500" track="stroke-secondary-500/30" width="w-full" /> <ProgressRadial
stroke={strokeProps.value}
meter="stroke-secondary-500"
track="stroke-secondary-500/30"
width="w-full"
{strokeLinecap}
value={50}
/>
<p>Secondary</p> <p>Secondary</p>
</div> </div>
<div class="p-4 space-y-2"> <div class="p-4 space-y-2">
<ProgressRadial stroke={strokeProps.value} meter="stroke-tertiary-500" track="stroke-tertiary-500/30" width="w-full" /> <ProgressRadial
stroke={strokeProps.value}
meter="stroke-tertiary-500"
track="stroke-tertiary-500/30"
width="w-full"
{strokeLinecap}
value={50}
/>
<p>Tertiary</p> <p>Tertiary</p>
</div> </div>
<div class="p-4 space-y-2"> <div class="p-4 space-y-2">
<ProgressRadial stroke={strokeProps.value} meter="stroke-success-500" track="stroke-success-500/30" width="w-full" /> <ProgressRadial
stroke={strokeProps.value}
meter="stroke-success-500"
track="stroke-success-500/30"
width="w-full"
{strokeLinecap}
value={50}
/>
<p>Success</p> <p>Success</p>
</div> </div>
<div class="p-4 space-y-2"> <div class="p-4 space-y-2">
<ProgressRadial stroke={strokeProps.value} meter="stroke-warning-500" track="stroke-warning-500/30" width="w-full" /> <ProgressRadial
stroke={strokeProps.value}
meter="stroke-warning-500"
track="stroke-warning-500/30"
width="w-full"
{strokeLinecap}
value={50}
/>
<p>Warning</p> <p>Warning</p>
</div> </div>
<div class="p-4 space-y-2"> <div class="p-4 space-y-2">
<ProgressRadial stroke={strokeProps.value} meter="stroke-error-500" track="stroke-error-500/30" width="w-full" /> <ProgressRadial
stroke={strokeProps.value}
meter="stroke-error-500"
track="stroke-error-500/30"
width="w-full"
{strokeLinecap}
value={50}
/>
<p>Error</p> <p>Error</p>
</div> </div>
</div> </div>
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
<div class="w-48 mx-auto"> <div class="flex justify-between items-center gap-4">
<input type="range" min="20" max={strokeProps.max} step={strokeProps.step} bind:value={strokeProps.value} /> <div class="w-60">
<input type="range" min="20" max={strokeProps.max} step={strokeProps.step} bind:value={strokeProps.value} />
</div>
<select bind:value={strokeLinecap} class="select w-auto">
{#each ['butt', 'round', 'square'] as v}
<option value={v}>{v}</option>
{/each}
</select>
</div> </div>
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="source"> <svelte:fragment slot="source">
<CodeBlock <CodeBlock
language="html" language="html"
code={`<ProgressRadial ... stroke={${strokeProps.value}} meter="stroke-primary-500" track="stroke-primary-500/30" />`} code={`<ProgressRadial ... stroke={${strokeProps.value}} meter="stroke-primary-500" track="stroke-primary-500/30" strokeLinecap={${strokeLinecap}} />`}
/> />
</svelte:fragment> </svelte:fragment>
</DocsPreview> </DocsPreview>

View File

@@ -3,19 +3,21 @@
import { DocsFeature, type DocsShellSettings } from '$lib/layouts/DocsShell/types'; import { DocsFeature, type DocsShellSettings } from '$lib/layouts/DocsShell/types';
import DocsPreview from '$lib/components/DocsPreview/DocsPreview.svelte'; import DocsPreview from '$lib/components/DocsPreview/DocsPreview.svelte';
// Components // Components
import { TreeView, TreeViewItem, type TreeViewNode } from '@skeletonlabs/skeleton'; import { TreeView, TreeViewItem, RecursiveTreeView } from '@skeletonlabs/skeleton';
// Utilities // Utilities
import { CodeBlock } from '@skeletonlabs/skeleton'; import { CodeBlock } from '@skeletonlabs/skeleton';
// Sveld // Sveld
import sveldTreeView from '@skeletonlabs/skeleton/components/TreeView/TreeView.svelte?raw&sveld'; import sveldTreeView from '@skeletonlabs/skeleton/components/TreeView/TreeView.svelte?raw&sveld';
import sveldTreeViewItem from '@skeletonlabs/skeleton/components/TreeView/TreeViewItem.svelte?raw&sveld'; import sveldTreeViewItem from '@skeletonlabs/skeleton/components/TreeView/TreeViewItem.svelte?raw&sveld';
import sveldRecursiveTreeView from '@skeletonlabs/skeleton/components/TreeView/RecursiveTreeView.svelte?raw&sveld';
import { nodes } from './exampleData';
// Docs Shell // Docs Shell
const settings: DocsShellSettings = { const settings: DocsShellSettings = {
feature: DocsFeature.Component, feature: DocsFeature.Component,
name: 'Tree Views', name: 'Tree Views',
description: 'Display information in a hierarchical structure using collapsible nodes.', description: 'Display information in a hierarchical structure using collapsible nodes.',
imports: ['TreeView', 'TreeViewItem', 'type TreeViewNode'], imports: ['TreeView', 'TreeViewItem', 'RecursiveTreeView', 'type TreeViewNode'],
source: 'packages/skeleton/src/lib/components/TreeView', source: 'packages/skeleton/src/lib/components/TreeView',
aria: 'https://www.w3.org/WAI/ARIA/apg/patterns/treeview/', aria: 'https://www.w3.org/WAI/ARIA/apg/patterns/treeview/',
components: [ components: [
@@ -38,7 +40,8 @@
'regionSymbol', 'regionSymbol',
'regionChildren' 'regionChildren'
] ]
} },
{ label: 'RecursiveTreeView', sveld: sveldRecursiveTreeView }
], ],
keyboard: [ keyboard: [
['<kbd class="kbd">Tab</kbd>', "Focus the next tree-view item or it's input."], ['<kbd class="kbd">Tab</kbd>', "Focus the next tree-view item or it's input."],
@@ -67,94 +70,11 @@
let expandTree: TreeView; let expandTree: TreeView;
let simpleDD: TreeViewNode[] = [ let expandedNodes: string[] = [];
{ let disabledNodes: string[] = ['programming'];
content: 'Books', let singleCheckedNodes: string[] = [];
lead: '<i class="fa-solid fa-book-skull"></i>', let multiCheckedNodes: string[] = ['javascript'];
open: true, let indeterminateNodes: string[] = [];
children: [
{ content: 'Clean Code', value: 'Clean Code' },
{ content: 'The Clean Coder', value: 'The Clean Coder' },
{ content: 'The Art of Unix Programming', value: 'The Art of Unix Programming' }
],
value: 'books'
},
{
content: 'Movies',
lead: '<i class="fa-solid fa-film"></i>',
children: [
{ content: 'The Flash', value: 'The Flash' },
{ content: 'Guardians of the Galaxy', value: 'Guardians of the Galaxy' },
{ content: 'Black Panther', value: 'Black Panther' }
],
value: 'movies'
},
{
content: 'TV',
lead: '<i class="fa-solid fa-tv"></i>',
value: 'tv'
}
];
let singleDD: TreeViewNode[] = [
{
content: 'Books',
lead: '<i class="fa-solid fa-book-skull"></i>',
open: true,
checked: true,
children: [
{ content: 'Clean Code', value: 'Clean Code' },
{ content: 'The Clean Coder', value: 'The Clean Coder' },
{ content: 'The Art of Unix Programming', value: 'The Art of Unix Programming', checked: true }
],
value: 'books'
},
{
content: 'Movies',
lead: '<i class="fa-solid fa-film"></i>',
children: [
{ content: 'The Flash', value: 'The Flash' },
{ content: 'Guardians of the Galaxy', value: 'Guardians of the Galaxy' },
{ content: 'Black Panther', value: 'Black Panther' }
],
value: 'movies'
},
{
content: 'TV',
lead: '<i class="fa-solid fa-tv"></i>',
value: 'tv'
}
];
let multipleDD: TreeViewNode[] = [
{
content: 'Books',
lead: '<i class="fa-solid fa-book-skull"></i>',
open: true,
indeterminate: true,
children: [
{ content: 'Clean Code', value: 'Clean Code' },
{ content: 'The Clean Coder', value: 'The Clean Coder', checked: true },
{ content: 'The Art of Unix Programming', value: 'The Art of Unix Programming', checked: true }
],
value: 'books'
},
{
content: 'Movies',
lead: '<i class="fa-solid fa-film"></i>',
children: [
{ content: 'The Flash', value: 'The Flash' },
{ content: 'Guardians of the Galaxy', value: 'Guardians of the Galaxy' },
{ content: 'Black Panther', value: 'Black Panther' }
],
value: 'movies'
},
{
content: 'TV',
lead: '<i class="fa-solid fa-tv"></i>',
value: 'tv'
}
];
</script> </script>
<DocsShell {settings}> <DocsShell {settings}>
@@ -734,62 +654,25 @@ let booksChildren: TreeViewItem[] = [];
<!-- Recursive Mode --> <!-- Recursive Mode -->
<section class="space-y-4"> <section class="space-y-4">
<h2 class="h2">Recursive Mode</h2> <h2 class="h2">Recursive Mode</h2>
<p>Tree views can be generated using a recursive data-driven method.</p>
<DocsPreview background="neutral" regionFooter="flex justify-center gap-4">
<svelte:fragment slot="preview">
<TreeView bind:nodes={simpleDD} />
</svelte:fragment>
<svelte:fragment slot="source">
<CodeBlock
language="ts"
code={`
let myTreeViewNodes: TreeViewNode[] = [
{
content: 'Books',
lead: '(icon)',
open: true,
children: [
{ content: 'Clean Code' },
{ content: 'The Clean Coder' },
{ content: 'The Art of Unix Programming' },
]
},
// ...
]
`}
/>
<CodeBlock
language="html"
code={`
<TreeView nodes={myTreeViewNodes}/>
`}
/>
</svelte:fragment>
</DocsPreview>
<!-- Single Selection -->
<h3 class="h3">Single Selection</h3>
<!-- prettier-ignore -->
<p> <p>
Relational checking is automatically applied when generating your list in a recursive manner. Setting a child as <code class="code">checked</code> will not automatically affect the parent. Tree views can be generated with a recursive data-driven method using the <code class="code">RecursiveTreeView</code> components.
</p> </p>
<DocsPreview background="neutral" regionFooter="flex justify-center gap-4"> <DocsPreview background="neutral" regionFooter="flex justify-center gap-4">
<svelte:fragment slot="preview"> <svelte:fragment slot="preview">
<TreeView bind:nodes={singleDD} selection /> <RecursiveTreeView {nodes} />
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="source"> <svelte:fragment slot="source">
<p>To get expected results make sure to include a <em>unique Id</em> for each node.</p>
<CodeBlock <CodeBlock
language="ts" language="ts"
code={` code={`
let myTreeViewNodes: TreeViewNode[] = [ let myTreeViewNodes: TreeViewNode[] = [
{ {
content: 'Books', id: 'unique-id'
content: 'content',
lead: '(icon)', lead: '(icon)',
open: true,
checked: true,
children: [ children: [
{ content: 'Clean Code' }, //...
{ content: 'The Clean Coder' },
{ content: 'The Art of Unix Programming', checked: true },
] ]
}, },
// ... // ...
@@ -799,45 +682,140 @@ let myTreeViewNodes: TreeViewNode[] = [
<CodeBlock <CodeBlock
language="html" language="html"
code={` code={`
<TreeView bind:nodes={myTreeViewNodes} selection/> <RecursiveTreeView nodes={nodes} />
`} `}
/> />
</svelte:fragment> </svelte:fragment>
</DocsPreview> </DocsPreview>
<!-- Multiple Selection --> <!-- Expanded -->
<h3 class="h3">Multiple Selection</h3> <h3 class="h3">Expanded</h3>
<p>Relational checking is automatically applied when generating your list in a recursive manner.</p> <!-- prettier-ignore -->
<p>
To access and modify the expanded nodes use <code class="code">expandedNodes</code> array prop.
</p>
<DocsPreview background="neutral" regionFooter="flex justify-center gap-4"> <DocsPreview background="neutral" regionFooter="flex justify-center gap-4">
<svelte:fragment slot="preview"> <svelte:fragment slot="preview">
<TreeView bind:nodes={multipleDD} selection multiple /> <RecursiveTreeView {nodes} bind:expandedNodes />
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="source"> <svelte:fragment slot="source">
<CodeBlock <CodeBlock
language="ts" language="ts"
code={` code={`
let myTreeViewNodes: TreeViewNode[] = [ let myTreeViewNodes: TreeViewNode[] = //...
{ let expandedNodes : string[] = [];
content: 'Books',
lead: '(icon)',
open: true,
indeterminate: true,
children: [
{ content: 'Clean Code' },
{ content: 'The Clean Coder', checked: true },
{ content: 'The Art of Unix Programming', checked: true },
]
},
// ...
]
`} `}
/> />
<CodeBlock <CodeBlock
language="html" language="html"
code={` code={`
<TreeView bind:nodes={myTreeViewNodes} selection multiple/> <RecursiveTreeView nodes={nodes} bind:expandedNodes={expandedNodes} />
`} `}
/> />
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="footer">
<span>Expanded nodes: <code class="code">{expandedNodes}</code></span>
</svelte:fragment>
</DocsPreview>
<!-- Disabled -->
<h3 class="h3">Disabled</h3>
<!-- prettier-ignore -->
<p>
To access and modify the disabled nodes use <code class="code">disabledNodes</code> array prop.
</p>
<DocsPreview background="neutral" regionFooter="flex justify-center gap-4">
<svelte:fragment slot="preview">
<RecursiveTreeView {nodes} bind:disabledNodes />
</svelte:fragment>
<svelte:fragment slot="source">
<CodeBlock
language="ts"
code={`
let myTreeViewNodes: TreeViewNode[] = //...
let disabledNodes : string[] = [];
`}
/>
<CodeBlock
language="html"
code={`
<RecursiveTreeView nodes={nodes} bind:disabledNodes={disabledNodes} />
`}
/>
</svelte:fragment>
<svelte:fragment slot="footer">
<span>Disabled nodes: <code class="code">{disabledNodes}</code></span>
</svelte:fragment>
</DocsPreview>
<!-- Selection -->
<h3 class="h3">Selection</h3>
<!-- prettier-ignore -->
<p>
Just like normal Tree-view, Recursive Tree-view supports selection with both <em>single</em> and <em>multiple</em> modes.
</p>
<p>To access and modify the checked nodes use <code class="code">checkedNodes</code> array prop.</p>
<DocsPreview background="neutral" regionFooter="flex justify-center gap-4">
<svelte:fragment slot="preview">
<RecursiveTreeView selection {nodes} bind:checkedNodes={singleCheckedNodes} />
</svelte:fragment>
<svelte:fragment slot="source">
<CodeBlock
language="ts"
code={`
let myTreeViewNodes: TreeViewNode[] = //...
let checkedNodes : string[] = [];
`}
/>
<CodeBlock
language="html"
code={`
<RecursiveTreeView selection nodes={nodes} bind:checkedNodes={checkedNodes} />
`}
/>
</svelte:fragment>
<svelte:fragment slot="footer">
<span>checked nodes: <code class="code">{singleCheckedNodes}</code></span>
</svelte:fragment>
</DocsPreview>
<!-- Relational Selection -->
<h3 class="h3">Relational Selection</h3>
<!-- prettier-ignore -->
<p>
Just like normal Tree-view, Recursive Tree-view supports relational selection using the prop <code class="code">relational</code>.
</p>
<p>To access and modify the checked nodes use <code class="code">checkedNodes</code> array prop.</p>
<p>
In multiple relational selection mode, an extra array prop <code class="code">indeterminateNodes</code> is available to indicate indeterminate
nodes.
</p>
<DocsPreview background="neutral" regionFooter="flex justify-center gap-4">
<svelte:fragment slot="preview">
<RecursiveTreeView selection multiple relational {nodes} bind:checkedNodes={multiCheckedNodes} bind:indeterminateNodes />
</svelte:fragment>
<svelte:fragment slot="source">
<CodeBlock
language="ts"
code={`
let myTreeViewNodes: TreeViewNode[] = //...
let checkedNodes : string[] = [];
let indeterminateNodes : string[] = [];
`}
/>
<CodeBlock
language="html"
code={`
<RecursiveTreeView
selection
multiple
relational
nodes={nodes}
bind:checkedNodes={checkedNodes}
bind:indeterminateNodes={indeterminateNodes}/>
`}
/>
</svelte:fragment>
<svelte:fragment slot="footer">
<span>indeterminate nodes: <code class="code whitespace-normal">{indeterminateNodes}</code></span>
</svelte:fragment>
</DocsPreview> </DocsPreview>
</section> </section>
</svelte:fragment> </svelte:fragment>

View File

@@ -0,0 +1,223 @@
import type { TreeViewNode } from '@skeletonlabs/skeleton';
export const nodes: TreeViewNode[] = [
{
id: 'programming',
content: 'programming',
value: 'programming',
children: [
{
id: 'language',
content: 'language',
value: 'language',
children: [
{
id: 'javascript',
content: 'javascript',
value: 'javascript'
},
{
id: 'c#',
content: 'c#',
value: 'c#'
},
{
id: 'rust',
content: 'rust',
value: 'rust'
}
]
},
{
content: 'database',
value: 'database',
id: 'database',
children: [
{
id: 'mongodb',
content: 'mongodb',
value: 'mongodb'
},
{
id: 'mssql',
content: 'mssql',
value: 'mssql'
},
{
id: 'casandra',
content: 'casandra',
value: 'casandra'
}
]
},
{
content: 'framework',
value: 'framework',
id: 'framework',
children: [
{
id: 'svelte',
content: 'svelte',
value: 'svelte'
},
{
id: 'angular',
content: 'angular',
value: 'angular'
},
{
id: 'react',
content: 'react',
value: 'react'
}
]
}
]
},
{
content: 'books',
value: 'books',
id: 'books',
children: [
{
id: 'clean code',
content: 'clean code',
value: 'clean code',
children: [
{
id: 'clean code - section 1',
content: 'clean code - section 1',
value: 'clean code - section 1'
},
{
id: 'clean code - section 2',
content: 'clean code - section 2',
value: 'clean code - section 2'
},
{
id: 'clean code - section 3',
content: 'clean code - section 3',
value: 'clean code - section 3'
}
]
},
{
id: 'structure',
content: 'structure',
value: 'structure',
children: [
{
id: 'structure - section 1',
content: 'structure - section 1',
value: 'structure - section 1'
},
{
id: 'structure - section 2',
content: 'structure - section 2',
value: 'structure - section 2'
},
{
id: 'structure - section 3',
content: 'structure - section 3',
value: 'structure - section 3'
}
]
},
{
id: 'clean coder',
content: 'clean coder',
value: 'clean coder',
children: [
{
id: 'clean coder - section 1',
content: 'clean coder - section 1',
value: 'clean coder - section 1'
},
{
id: 'clean coder - section 2',
content: 'clean coder - section 2',
value: 'clean coder - section 2'
},
{
id: 'clean coder - section 3',
content: 'clean coder - section 3',
value: 'clean coder - section 3'
}
]
}
]
},
{
id: 'series',
content: 'series',
value: 'series',
children: [
{
id: 'Mr. Robot',
content: 'Mr. Robot',
value: 'Mr. Robot',
children: [
{
id: 'Mr. Robot - season 1',
content: 'Mr. Robot - season 1',
value: 'Mr. Robot - season 1'
},
{
id: 'Mr. Robot - season 2',
content: 'Mr. Robot - season 2',
value: 'Mr. Robot - season 2'
},
{
id: 'Mr. Robot - season 3',
content: 'Mr. Robot - season 3',
value: 'Mr. Robot - season 3'
}
]
},
{
id: 'silicon valley',
content: 'silicon valley',
value: 'silicon valley',
children: [
{
id: 'silicon valley - season 1',
content: 'silicon valley - season 1',
value: 'silicon valley - season 1'
},
{
id: 'silicon valley - season 2',
content: 'silicon valley - season 2',
value: 'silicon valley - season 2'
},
{
id: 'silicon valley - season 3',
content: 'silicon valley - season 3',
value: 'silicon valley - season 3'
}
]
},
{
id: 'code monkeys',
content: 'code monkeys',
value: 'code monkeys',
children: [
{
id: 'code monkeys - season 1',
content: 'code monkeys - season 1',
value: 'code monkeys - season 1'
},
{
id: 'code monkeys - season 2',
content: 'code monkeys - season 2',
value: 'code monkeys - season 2'
},
{
id: 'code monkeys - season 3',
content: 'code monkeys - season 3',
value: 'code monkeys - season 3'
}
]
}
]
}
];

View File

@@ -20,6 +20,9 @@
[`<div class="${cSwatch} bg-surface-500" />`, '[style]-surface-[50-900]', 'The base level colors, used for backgrounds.'] [`<div class="${cSwatch} bg-surface-500" />`, '[style]-surface-[50-900]', 'The base level colors, used for backgrounds.']
] ]
}; };
const shadesArr = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
const colorsArr = ['primary', 'secondary', 'tertiary', 'success', 'warning', 'error', 'surface'];
</script> </script>
<LayoutPage> <LayoutPage>
@@ -32,11 +35,117 @@
<Table source={tableProps} /> <Table source={tableProps} />
<!-- Color Palette -->
<section class="space-y-4">
<h2 class="h2">Color Palette</h2>
<p>Shade 500 is used as the default value.</p>
<div class="grid grid-cols-2 lg:grid-cols-4 gap-2">
<!-- primary -->
<div class="rounded-container-token overflow-hidden">
<div class="bg-primary-500 p-2 py-4"><span class="text-on-primary-token">Primary</span></div>
<div class="bg-primary-50 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">50</span></div>
<div class="bg-primary-100 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">100</span></div>
<div class="bg-primary-200 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">200</span></div>
<div class="bg-primary-300 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">300</span></div>
<div class="bg-primary-400 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">400</span></div>
<div class="bg-primary-500 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">500 ★</span></div>
<div class="bg-primary-600 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">600</span></div>
<div class="bg-primary-700 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">700</span></div>
<div class="bg-primary-800 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">800</span></div>
<div class="bg-primary-900 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">900</span></div>
</div>
<!-- secondary -->
<div class="rounded-container-token overflow-hidden">
<div class="bg-secondary-500 p-2 py-4"><span class="text-on-secondary-token">Secondary</span></div>
<div class="bg-secondary-50 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">50</span></div>
<div class="bg-secondary-100 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">100</span></div>
<div class="bg-secondary-200 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">200</span></div>
<div class="bg-secondary-300 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">300</span></div>
<div class="bg-secondary-400 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">400</span></div>
<div class="bg-secondary-500 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">500 ★</span></div>
<div class="bg-secondary-600 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">600</span></div>
<div class="bg-secondary-700 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">700</span></div>
<div class="bg-secondary-800 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">800</span></div>
<div class="bg-secondary-900 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">900</span></div>
</div>
<!-- tertiary -->
<div class="rounded-container-token overflow-hidden">
<div class="bg-tertiary-500 p-2 py-4"><span class="text-on-tertiary-token">Tertiary</span></div>
<div class="bg-tertiary-50 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">50</span></div>
<div class="bg-tertiary-100 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">100</span></div>
<div class="bg-tertiary-200 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">200</span></div>
<div class="bg-tertiary-300 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">300</span></div>
<div class="bg-tertiary-400 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">400</span></div>
<div class="bg-tertiary-500 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">500 ★</span></div>
<div class="bg-tertiary-600 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">600</span></div>
<div class="bg-tertiary-700 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">700</span></div>
<div class="bg-tertiary-800 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">800</span></div>
<div class="bg-tertiary-900 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">900</span></div>
</div>
<!-- success -->
<div class="rounded-container-token overflow-hidden">
<div class="bg-success-500 p-2 py-4"><span class="text-on-success-token">Success</span></div>
<div class="bg-success-50 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">50</span></div>
<div class="bg-success-100 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">100</span></div>
<div class="bg-success-200 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">200</span></div>
<div class="bg-success-300 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">300</span></div>
<div class="bg-success-400 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">400</span></div>
<div class="bg-success-500 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">500 ★</span></div>
<div class="bg-success-600 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">600</span></div>
<div class="bg-success-700 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">700</span></div>
<div class="bg-success-800 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">800</span></div>
<div class="bg-success-900 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">900</span></div>
</div>
<!-- warning -->
<div class="rounded-container-token overflow-hidden">
<div class="bg-warning-500 p-2 py-4"><span class="text-on-warning-token">Warning</span></div>
<div class="bg-warning-50 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">50</span></div>
<div class="bg-warning-100 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">100</span></div>
<div class="bg-warning-200 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">200</span></div>
<div class="bg-warning-300 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">300</span></div>
<div class="bg-warning-400 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">400</span></div>
<div class="bg-warning-500 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">500 ★</span></div>
<div class="bg-warning-600 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">600</span></div>
<div class="bg-warning-700 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">700</span></div>
<div class="bg-warning-800 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">800</span></div>
<div class="bg-warning-900 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">900</span></div>
</div>
<!-- error -->
<div class="rounded-container-token overflow-hidden">
<div class="bg-error-500 p-2 py-4"><span class="text-on-error-token">Error</span></div>
<div class="bg-error-50 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">50</span></div>
<div class="bg-error-100 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">100</span></div>
<div class="bg-error-200 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">200</span></div>
<div class="bg-error-300 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">300</span></div>
<div class="bg-error-400 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">400</span></div>
<div class="bg-error-500 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">500 ★</span></div>
<div class="bg-error-600 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">600</span></div>
<div class="bg-error-700 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">700</span></div>
<div class="bg-error-800 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">800</span></div>
<div class="bg-error-900 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">900</span></div>
</div>
<!-- surface -->
<div class="rounded-container-token overflow-hidden col-span-2">
<div class="bg-surface-500 p-2 py-4"><span class="text-on-surface-token">Surface</span></div>
<div class="bg-surface-50 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">50</span></div>
<div class="bg-surface-100 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">100</span></div>
<div class="bg-surface-200 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">200</span></div>
<div class="bg-surface-300 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">300</span></div>
<div class="bg-surface-400 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">400</span></div>
<div class="bg-surface-500 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">500 ★</span></div>
<div class="bg-surface-600 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">600</span></div>
<div class="bg-surface-700 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">700</span></div>
<div class="bg-surface-800 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">800</span></div>
<div class="bg-surface-900 p-2"><span class="bg-black/30 text-white p-1 text-xs rounded">900</span></div>
</div>
</div>
</section>
<!-- Reference --> <!-- Reference -->
<section class="space-y-4"> <section class="space-y-4">
<h2 class="h2">Usage</h2> <h2 class="h2">Usage</h2>
<CodeBlock language="html" code={`<!-- Inlined classes -->\n<div class="bg-primary-500 text-secondary-500">Skeleton</div>`} /> <CodeBlock language="html" code={`<!-- Inlined classes -->\n<div class="bg-primary-500 text-secondary-500">Skeleton</div>`} />
<CodeBlock language="html" code={`<!-- Tailwind opacity scale -->\n<div class="border border-primary-500/50">Skeleton</div>`} /> <CodeBlock language="html" code={`<!-- Tailwind opacity scale -->\n<div class="border border-primary-500/30">Skeleton</div>`} />
<CodeBlock language="css" code={`/* Using Tailwind @apply */\n.example { @apply text-primary-500; }`} /> <CodeBlock language="css" code={`/* Using Tailwind @apply */\n.example { @apply text-primary-500; }`} />
<CodeBlock <CodeBlock
language="css" language="css"

View File

@@ -118,7 +118,7 @@ export let parameters: Record<string, string> = { foo: 'bar' };
<h3 class="h3">Tailwind Class Props</h3> <h3 class="h3">Tailwind Class Props</h3>
<p> <p>
For props that pass one or more CSS utility classes, make sure to import and append the <code class="code">CSSClasses</code> type. For props that pass one or more CSS utility classes, make sure to import and append the <code class="code">CSSClasses</code> type.
This resolve to a type of <code class="code">string</code> and allows our build process to identify props that support Tailwind Intellisense. This resolves to a type of <code class="code">string</code> and allows our build process to identify props that support Tailwind Intellisense.
</p> </p>
<CodeBlock language="typescript" code={`import type { CssClasses } from '../..';`} /> <CodeBlock language="typescript" code={`import type { CssClasses } from '../..';`} />
<CodeBlock <CodeBlock
@@ -216,7 +216,7 @@ $: classesLabel = \`\${cBaseLabel}\`; // child element
<section class="space-y-4"> <section class="space-y-4">
<h2 class="h2">Dynamic Transitions</h2> <h2 class="h2">Dynamic Transitions</h2>
<p> <p>
Skeleton has a convention for implement dynamic transitions within components. Please follow the guidelines below to ensure you are Skeleton has a convention for implementing dynamic transitions within components. Please follow the guidelines below to ensure you are
following our standard for this process. following our standard for this process.
</p> </p>
<blockquote class="blockquote"> <blockquote class="blockquote">

View File

@@ -213,7 +213,7 @@ body { background: red; }
</p> </p>
</div> </div>
<div> <div>
<a class="btn variant-filled-secondary font-bold" href="https://csshero.org/mesher/" target="_blank" rel="noreferrer"> <a class="btn variant-filled font-bold" href="https://csshero.org/mesher/" target="_blank" rel="noreferrer">
<span>Create a Mesh</span> <span>Create a Mesh</span>
<i class="fa-solid fa-arrow-up-right-from-square" /> <i class="fa-solid fa-arrow-up-right-from-square" />
</a> </a>

View File

@@ -73,7 +73,7 @@
</p> </p>
<h3 class="h3">Force Enable</h3> <h3 class="h3">Force Enable</h3>
<p> <p>
For components with subtle transitions, you may choose to override this behavior by applying a property of <code class="code">{`transition={true}`}</code> to the component. We encourage you to use this setting with caution though. For components with subtle transitions, you may choose to override this behavior by applying a property of <code class="code">{`transitions={true}`}</code> to the component. We encourage you to use this setting with caution though.
</p> </p>
<h3 class="h3">Store</h3> <h3 class="h3">Store</h3>
<p> <p>

View File

@@ -280,8 +280,26 @@ modalStore.trigger(modal);
</section> </section>
<!-- Modal Settings --> <!-- Modal Settings -->
<section class="space-y-4"> <section class="space-y-4">
<h2 class="h2">Modal Settings</h2> <div class="grid grid-cols-1 md:grid-cols-[1fr_auto] gap-4 items-center">
<p>These additional settings are available to all modals.</p> <div class="space-y-4">
<h2 class="h2">Modal Settings</h2>
<p>
Define settings <u>per modal instance</u> via the <code class="code">trigger()</code> method. These are similar to modal properties,
but do not provide the same breath of options.
</p>
</div>
<div>
<a
class="btn variant-filled font-bold"
href="https://github.com/skeletonlabs/skeleton/blob/master/packages/skeleton/src/lib/utilities/Modal/types.ts#L14"
target="_blank"
rel="noreferrer"
>
<span>Available Settings</span>
<i class="fa-solid fa-arrow-up-right-from-square" />
</a>
</div>
</div>
<!-- prettier-ignore --> <!-- prettier-ignore -->
<CodeBlock <CodeBlock
language="ts" language="ts"
@@ -295,6 +313,13 @@ const modal: ModalSettings = {\n
};`} };`}
/> />
</section> </section>
<!-- Modal Properties -->
<section class="space-y-4">
<h2 class="h2">Modal Properties</h2>
<p>
Define <u>global settings</u> for all modal instances. Tap the "Props" tab at the top of the page for a full list of options.
</p>
</section>
<!-- Async Response --> <!-- Async Response -->
<section class="space-y-4"> <section class="space-y-4">
<h2 class="h2">Async Response</h2> <h2 class="h2">Async Response</h2>
@@ -325,7 +350,8 @@ new Promise<boolean>((resolve) => {
<h2 class="h2">Component Modals</h2> <h2 class="h2">Component Modals</h2>
<span class="badge variant-filled-warning">Advanced</span> <span class="badge variant-filled-warning">Advanced</span>
</div> </div>
<p>To create custom modals, generate a new component, then pass this to the Modal system.</p> <p>Skeleton allows you to generate custom modals using Svelte components.</p>
<h3 class="sr-only">Example Modals</h3>
<DocsPreview background="neutral"> <DocsPreview background="neutral">
<svelte:fragment slot="preview"> <svelte:fragment slot="preview">
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 md:max-w-[480px] mx-auto"> <div class="grid grid-cols-1 md:grid-cols-4 gap-4 md:max-w-[480px] mx-auto">
@@ -335,159 +361,241 @@ new Promise<boolean>((resolve) => {
<button class="btn variant-filled" on:click={modalComponentImage}>Image</button> <button class="btn variant-filled" on:click={modalComponentImage}>Image</button>
</div> </div>
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="footer"> <svelte:fragment slot="source">
<div class="text-center"> <div class="text-center">
<!-- prettier-ignore --> <a
<a class="btn variant-ghost" href="https://github.com/skeletonlabs/skeleton/tree/master/sites/skeleton.dev/src/lib/modals/examples" target="_blank" rel="noreferrer">View Source Code</a> class="btn variant-ghost"
href="https://github.com/skeletonlabs/skeleton/tree/master/sites/skeleton.dev/src/lib/modals/examples"
target="_blank"
rel="noreferrer"
>
<i class="fa-brands fa-github" />
<span>View Source on GitHub</span>
<i class="fa-solid fa-arrow-up-right-from-square" />
</a>
</div> </div>
</svelte:fragment> </svelte:fragment>
<svelte:fragment slot="source"> </DocsPreview>
<TabGroup regionPanel="space-y-4"> <h3 class="h3">Choose a Method</h3>
<Tab bind:group={tabCustom} name="component-modals" value="register">Reusable Registry</Tab> <TabGroup regionList="!grid grid-cols-2" regionPanel="space-y-4">
<Tab bind:group={tabCustom} name="component-modals" value="direct">Direct Method</Tab> <Tab bind:group={tabCustom} name="component-modals" value="register">Registry (recommended)</Tab>
<!-- Panel --> <Tab bind:group={tabCustom} name="component-modals" value="direct">Direct</Tab>
<svelte:fragment slot="panel"> <!-- Panel -->
{#if tabCustom === 'register'} <svelte:fragment slot="panel">
<!-- prettier-ignore --> {#if tabCustom === 'register'}
<p> <p>
Import custom components in your root layout, create a modal registry object, then pass this object to the Modal <code class="code">components</code> property. This will create a set of reusable custom modals that are globally avialable to your application. Add the following to your
</p> your root layout in <code class="code">/src/routes/+layout.svelte</code>.
<CodeBlock </p>
language="ts" <CodeBlock
code={` language="ts"
// import ModalComponentOne from '...'; code={`
// import ModalComponentTwo from '...';\n import ModalComponentOne from '/example/path/here';
const modalComponentRegistry: Record<string, ModalComponent> = {\n import ModalComponentTwo from '/example/path/here';\n
// Custom Modal 1 const modalRegistry: Record<string, ModalComponent> = {
modalComponentOne: { // Set a unique modal ID, then pass the component reference
// Pass a reference to your custom component modalComponentOne: { ref: ModalComponentOne },
ref: ModalComponentOne, modalComponentTwo: { ref: ModalComponentTwo },
// Add the component properties as key/value pairs
props: { background: 'bg-red-500' },
// Provide a template literal for the default component slot
slot: '<p>Skeleton</p>'
},\n
// Custom Modal 2
modalComponentTwo: { ref: ModalComponentTwo },\n
// ... // ...
}; };
`} `}
/> />
<CodeBlock language="html" code={`<Modal components={modalComponentRegistry} />`} /> <p>Provide the <code class="code">modalRegistry</code> to the modal component, which also resides in your root layout.</p>
<p> <CodeBlock language="html" code={`<Modal components={modalRegistry} />`} />
When triggering a component, pass <code class="code">component: string</code>, where the value represents the registry <p>
object key. Then, when triggering a new component, set the value of <code class="code">component</code> to the unique modal ID as registered
</p> above.
<CodeBlock </p>
language="ts" <CodeBlock
code={` language="ts"
code={`
const modal: ModalSettings = { const modal: ModalSettings = {
type: 'component', type: 'component',
// Pass the component registry key as a string:
component: 'modalComponentOne', component: 'modalComponentOne',
}; };
modalStore.trigger(modal); modalStore.trigger(modal);
`} `}
/> />
{:else if tabCustom === 'direct'} {:else if tabCustom === 'direct'}
<p> <p>This will implement a single component for a one-off modal instance.</p>
For one-off components you can create a <code class="code">ModalComponent</code> object containing your component, props, and <CodeBlock
slot values. language="ts"
</p> code={`
<CodeBlock import MyCustomComponent from '/example/path/here';\n
language="ts" const modalComponent: ModalComponent = { ref: MyCustomComponent };\n
code={`
// import MyCustomComponent from '...';\n
const modalComponent: ModalComponent = {
// Pass a reference to your custom component
ref: MyCustomComponent,
// Add the component properties as key/value pairs
props: { background: 'bg-red-500' },
// Provide a template literal for the default component slot
slot: '<p>Skeleton</p>'
};
`}
/>
<p>
When triggering a component, pass the <code class="code">component: ModalComponent</code> directly to
<code class="code">ModalSettings</code>.
</p>
<CodeBlock
language="ts"
code={`
const modal: ModalSettings = { const modal: ModalSettings = {
type: 'component', type: 'component',
// Pass the component directly:
component: modalComponent, component: modalComponent,
}; };
modalStore.trigger(modal); modalStore.trigger(modal);
`} `}
/> />
{/if} {/if}
</svelte:fragment>
</TabGroup>
</svelte:fragment> </svelte:fragment>
</DocsPreview> </TabGroup>
<p>See the additional information below to learn how to use custom component modals.</p> <h3 class="h3">Creating a Component</h3>
<p>Learn more about how to construct a custom modal component via the tips below.</p>
<Accordion autocollapse class="card variant-glass p-4"> <Accordion autocollapse class="card variant-glass p-4">
<AccordionItem open> <AccordionItem open>
<svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Accessing Store Data</h3></svelte:fragment> <svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Accessing Modal Store Data</h3></svelte:fragment>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
<p> <p>When creating a custom component, make sure to import the modal store. This should proceed all following tips.</p>
Import and use the <code class="code">modalStore</code>. All provided data is available within your component via <CodeBlock
<code class="code">$modalStore[0]</code>. language="ts"
</p> code={`
import { getModalStore } from '@skeletonlabs/skeleton';\n
const modalStore = getModalStore();
`}
/>
</svelte:fragment> </svelte:fragment>
</AccordionItem> </AccordionItem>
<AccordionItem> <AccordionItem>
<svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>The Visible Modal</h3></svelte:fragment> <svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Determining the Active Modal</h3></svelte:fragment>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
<p>The foremost and visible modal in your queue uses index zero <code class="code">$modalStore[0]</code>.</p> <p>
The active and visible modal in your queue is always available at the zero index: <code class="code">$modalStore[0]</code>.
</p>
<CodeBlock language="ts" code={`if ($modalStore[0]) console.log($modalStore[0].title);`} />
</svelte:fragment> </svelte:fragment>
</AccordionItem> </AccordionItem>
<AccordionItem> <AccordionItem>
<svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Modal Parent Properties</h3></svelte:fragment> <svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Template Conditional</h3></svelte:fragment>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
<p> <p>
The Modal component in your root layout is considered the "parent" component. Your custom modal component will be generated Wrap your component template markup in an <code class="code">#if</code> statement before accessing modal store values.
within this. All properties for the parent component are passed down via the <code class="code">parent</code> prop. For
example
<code class="code">parent.background</code> would provide the <em>background</em> property value.
</p> </p>
<p>Tap the <em>Props</em> tab on this page to view a full list of available <code class="code">parent</code> props.</p> <CodeBlock
language="html"
code={`
{#if $modalStore[0]}
<header>{$modalStore[0].title}</header>
<article>{$modalStore[0].body}</article>
{/if}
`}
/>
</svelte:fragment> </svelte:fragment>
</AccordionItem> </AccordionItem>
<AccordionItem> <AccordionItem>
<svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Triggering a Response</h3></svelte:fragment> <svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Inherit Modal Component Props</h3></svelte:fragment>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
<p> <p>
Use the <code class="code">$modalStore[0].response('myResponseDataHere');</code> trigger the response function and return a value. The modal component in your root layout is considered the "parent" component, which includes a set of global configuration
properties. For example:
</p> </p>
<CodeBlock language="html" code={`<Modal background="bg-green-500" />`} />
<blockquote class="blockquote">
TIP: tap the <u>Props</u> tab at the top of this page for a full list of available properties.
</blockquote>
<p>
Custom modal components are then generated within this using <code class="code">svelte:component</code>. By default, Skeleton
provides all parent props to your custom component using a
<code class="code">parent</code>
prop. This can be enabled by adding the following to your custom component.
</p>
<CodeBlock language="ts" code={`export let parent: any;`} />
<p>You may then access and use any parent value.</p>
<CodeBlock language="html" code={`<pre class="pre">{JSON.stringify(parent)}</pre>`} />
<CodeBlock language="html" code={`<pre class="pre">background: {parent.background}</pre>`} />
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Returning a Response Value</h3></svelte:fragment>
<svelte:fragment slot="content">
<p>
Use the <code class="code">$modalStore[0].response()</code> callback method to return a modal response value.
</p>
<CodeBlock language="ts" code={`$modalStore[0].response({ foo: 'bar' });`} />
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Passing Arbitrary Data</h3></svelte:fragment>
<svelte:fragment slot="content">
<p>When triggering a component, use the Modal Settings <code class="code">meta</code> key to pass arbitrary data.</p>
<CodeBlock
language="ts"
code={`
const modal: ModalSettings = {
// ...
meta: { foo: 'bar', fizz: 'buzz', fn: myCustomFunction }
};
`}
/>
<p>You can then use <code class="code">$modalStore[0].meta</code> within your custom component to retrieve this data.</p>
<CodeBlock language="html" code={`<pre class="pre">{JSON.stringify($modalStore[0].meta)}</pre>`} />
<CodeBlock language="html" code={`<pre class="pre">foo: {$modalStore[0].meta.foo}</pre>`} />
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Component Props and Slots</h3></svelte:fragment>
<svelte:fragment slot="content">
<p>
When creating a <code class="code">ModalComponent</code> instance, you can <u>optionally</u> pass
<code class="code">props</code>
and default
<code class="code">slot</code> values as shown.
</p>
<CodeBlock
language="ts"
code={`
import MyCustomComponent from '/example/path/here';\n
const modalComponent: ModalComponent = {
ref: MyCustomComponent
props: { foo: 'bar' },
slot: '<p>Skeleton</p>'
};
`}
/>
<p>These values be utilized per standard Svelte component conventions.</p>
<CodeBlock language="ts" code={`export let foo = 'fizzbuzz';`} />
<CodeBlock language="html" code={`<slot />`} />
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Setting the Modal Width</h3></svelte:fragment>
<svelte:fragment slot="content">
<p>
Skeleton's modal component uses a <code class="code">width</code> property, which can use either an arbitrary width class, or any
of the canned options provided below:
</p>
<ul class="list-disc list-outside ml-4 space-y-1">
<li><code class="code">.w-modal</code> - the standard modal size.</li>
<li><code class="code">.w-modal-slim</code> - the smaller modal width.</li>
<li><code class="code">.w-modal-wide</code> - the larger modal width.</li>
</ul>
<p>Your custom component can either inherit this from the <code class="code">parent.width</code> property.</p>
<CodeBlock
language="html"
code={`
{#if $modalStore[0]}
<div class="{parent.width}">
<h2 class="h2">Wide Modal</h2>
<p>This will be a wide modal.</p>
</div>
{/if}
`}
/>
<p>Likewise these classes can be defined directly into the template.</p>
<CodeBlock
language="html"
code={`
{#if $modalStore[0]}
<div class="w-modal-wide">
<h2 class="h2">Wide Modal</h2>
<p>This will be a wide modal.</p>
</div>
{/if}
`}
/>
</svelte:fragment> </svelte:fragment>
</AccordionItem> </AccordionItem>
<AccordionItem> <AccordionItem>
<svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Closing a Modal</h3></svelte:fragment> <svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Closing a Modal</h3></svelte:fragment>
<svelte:fragment slot="content"> <svelte:fragment slot="content">
<p> <p>You can use either method below to close self close the modal. Use this for "cancel" actions.</p>
Use the <code class="code">parent.onClose()</code> or <code class="code">modalStore.close()</code> methods to close the modal. <CodeBlock language="ts" code={`parent.onClose();`} />
</p> <CodeBlock language="ts" code={`modalStore.close();`} />
</svelte:fragment> </svelte:fragment>
</AccordionItem> </AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Accessing Metadata</h3></svelte:fragment>
<svelte:fragment slot="content">
<p>Arbitrary metadata is available using <code class="code">$modalStore[0].meta?.someKey</code>.</p>
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary"><h3 class="h3" data-toc-ignore>Standardize Modal Widths</h3></svelte:fragment>
<svelte:fragment slot="content">
<!-- prettier-ignore -->
<p>
Standard <em>alert</em>, <em>confirm</em>, and <em>prompt</em> modals include a <code class="code">width</code> property to adjust the width size. If you wish to resize your custom component modals, apply the Tailwind width utility classes directly within your component markup. To help standardize modal widths we provided three canned options: <code class="code">.w-modal</code>, <code class="code">.w-modal-slim</code>, and <code class="code">.w-modal-wide</code>.
</svelte:fragment
>
</AccordionItem>
</Accordion> </Accordion>
</section> </section>
<section class="space-y-4"> <section class="space-y-4">

View File

@@ -530,6 +530,50 @@ const popupState: PopupSettings = {
<hr /> <hr />
<!-- Handling Loops -->
<section class="space-y-4">
<h2 class="h2">Handling Loops</h2>
<p>
Popups maintain a 1-1 relationship between the trigger and the popup element. This means when using <code class="code">#each</code> block
to iterate and create a set of popups, you must provide a unique popup element and popup settings.
</p>
<DocsPreview background="neutral" regionPreview="text-token">
<svelte:fragment slot="preview">
<div class="grid grid-cols-1 gap-2">
{#each ['A', 'B', 'C'] as label, i}
<!-- Trigger -->
<button class="btn variant-filled" use:popup={{ event: 'click', target: 'loopExample-' + i, placement: 'top' }}>
Show {label}
</button>
<!-- Popup -->
<div class="card p-4 shadow-xl" data-popup="loopExample-{i}">Popup {label}</div>
{/each}
</div>
</svelte:fragment>
<svelte:fragment slot="source">
<p>
Inline popup settings for each <code class="code">use:popup</code> directive, and take note of how the index
<code class="code">i</code>
is appended to both <code class="code">target</code> and
<code class="code">data-popup</code>.
</p>
<CodeBlock
language="html"
code={`
{#each ['A', 'B', 'C'] as label, i}
<!-- Trigger -->
<button use:popup={{ event: 'click', target: 'loopExample-' + i, placement: 'top' }}>
Show {label}
</button>
<!-- Popup -->
<div data-popup="loopExample-{i}">Popup {label}</div>
{/each}
`}
/>
</svelte:fragment>
</DocsPreview>
</section>
<!-- Combobox --> <!-- Combobox -->
<section class="space-y-4"> <section class="space-y-4">
<h2 class="h2">Combobox</h2> <h2 class="h2">Combobox</h2>

View File

@@ -54,6 +54,10 @@
import '$lib/styles/blog.css'; import '$lib/styles/blog.css';
// Global Stylesheets // Global Stylesheets
import '../app.postcss'; import '../app.postcss';
// Font Awesome
import '@fortawesome/fontawesome-free/css/fontawesome.css';
import '@fortawesome/fontawesome-free/css/brands.css';
import '@fortawesome/fontawesome-free/css/solid.css';
// Handle Vercel Production Mode // Handle Vercel Production Mode
import type { LayoutServerData } from './$types'; import type { LayoutServerData } from './$types';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +0,0 @@
/*!
* Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Copyright 2023 Fonticons, Inc.
*/
:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}

View File

@@ -1,4 +1,5 @@
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import { purgeCss } from 'vite-plugin-tailwind-purgecss';
import type { UserConfig } from 'vite'; import type { UserConfig } from 'vite';
import skeletonPluginWatcher from './vite-plugin-skeleton-plugin-watcher'; import skeletonPluginWatcher from './vite-plugin-skeleton-plugin-watcher';
import sveld from './vite-plugin-sveld'; import sveld from './vite-plugin-sveld';
@@ -9,7 +10,17 @@ const json = readFileSync('../../packages/skeleton/package.json', 'utf8');
const pkg = JSON.parse(json); const pkg = JSON.parse(json);
const config: UserConfig = { const config: UserConfig = {
plugins: [sveltekit(), sveld(), skeletonPluginWatcher()], plugins: [
sveltekit(),
sveld(),
skeletonPluginWatcher(),
purgeCss({
safelist: {
// any selectors that begin with "hljs-" will not be purged
greedy: [/^hljs-/]
}
})
],
define: { define: {
__PACKAGE__: pkg __PACKAGE__: pkg
} }