@melt-ui/svelte
Advanced tools
Comparing version 0.50.1 to 0.51.0
@@ -54,6 +54,6 @@ import type { BuilderReturn, WhenTrue } from '../../internal/types.js'; | ||
} | number; | ||
export type Accordion = BuilderReturn<typeof createAccordion>; | ||
export type AccordionElements = Accordion['elements']; | ||
export type AccordionOptions = Accordion['options']; | ||
export type AccordionStates = Accordion['states']; | ||
export type AccordionHelpers = Accordion['helpers']; | ||
export type Accordion<Multiple extends boolean = false> = BuilderReturn<typeof createAccordion<Multiple>>; | ||
export type AccordionElements<Multiple extends boolean = false> = BuilderReturn<typeof createAccordion<Multiple>>['elements']; | ||
export type AccordionOptions<Multiple extends boolean = false> = BuilderReturn<typeof createAccordion<Multiple>>['options']; | ||
export type AccordionStates<Multiple extends boolean = false> = BuilderReturn<typeof createAccordion<Multiple>>['states']; | ||
export type AccordionHelpers<Multiple extends boolean = false> = BuilderReturn<typeof createAccordion<Multiple>>['helpers']; |
@@ -102,6 +102,2 @@ import { useEscapeKeydown, usePopper } from '../../internal/actions/index.js'; | ||
} | ||
const activeTrigger = getElementByMeltId(ids.input); | ||
if (activeTrigger) { | ||
activeTrigger.focus(); | ||
} | ||
} | ||
@@ -108,0 +104,0 @@ /** |
@@ -121,6 +121,6 @@ import type { FloatingConfig } from '../../internal/actions/index.js'; | ||
}; | ||
export type Select = BuilderReturn<typeof createSelect>; | ||
export type SelectElements = Select['elements']; | ||
export type SelectOptions = Select['options']; | ||
export type SelectStates = Select['states']; | ||
export type SelectHelpers = Select['helpers']; | ||
export type Select<Value = unknown, Multiple extends boolean = false, S extends SelectSelected<Multiple, Value> = SelectSelected<Multiple, Value>> = BuilderReturn<typeof createSelect<Value, Multiple, S>>; | ||
export type SelectElements<Value = unknown, Multiple extends boolean = false, S extends SelectSelected<Multiple, Value> = SelectSelected<Multiple, Value>> = BuilderReturn<typeof createSelect<Value, Multiple, S>>['elements']; | ||
export type SelectOptions<Value = unknown, Multiple extends boolean = false, S extends SelectSelected<Multiple, Value> = SelectSelected<Multiple, Value>> = BuilderReturn<typeof createSelect<Value, Multiple, S>>['options']; | ||
export type SelectStates<Value = unknown, Multiple extends boolean = false, S extends SelectSelected<Multiple, Value> = SelectSelected<Multiple, Value>> = BuilderReturn<typeof createSelect<Value, Multiple, S>>['states']; | ||
export type SelectHelpers<Value = unknown, Multiple extends boolean = false, S extends SelectSelected<Multiple, Value> = SelectSelected<Multiple, Value>> = BuilderReturn<typeof createSelect<Value, Multiple, S>>['helpers']; |
@@ -28,6 +28,6 @@ import type { BuilderReturn } from '../../internal/types.js'; | ||
}; | ||
export type Toasts = BuilderReturn<typeof createToaster>; | ||
export type ToastsElements = Toasts['elements']; | ||
export type ToastsOptions = Toasts['options']; | ||
export type ToastsStates = Toasts['states']; | ||
export type ToastsHelpers = Toasts['helpers']; | ||
export type Toasts<T = object> = BuilderReturn<typeof createToaster<T>>; | ||
export type ToastsElements<T = object> = BuilderReturn<typeof createToaster<T>>['elements']; | ||
export type ToastsOptions<T = object> = BuilderReturn<typeof createToaster<T>>['options']; | ||
export type ToastsStates<T = object> = BuilderReturn<typeof createToaster<T>>['states']; | ||
export type ToastsHelpers<T = object> = BuilderReturn<typeof createToaster<T>>['helpers']; |
@@ -21,6 +21,6 @@ import type { BuilderReturn, Orientation } from '../../internal/types.js'; | ||
} | string; | ||
export type ToggleGroup = BuilderReturn<typeof createToggleGroup>; | ||
export type ToggleGroupElements = ToggleGroup['elements']; | ||
export type ToggleGroupOptions = ToggleGroup['options']; | ||
export type ToggleGroupStates = ToggleGroup['states']; | ||
export type ToggleGroupHelpers = ToggleGroup['helpers']; | ||
export type ToggleGroup<T extends ToggleGroupType = 'single'> = BuilderReturn<typeof createToggleGroup<T>>; | ||
export type ToggleGroupElements<T extends ToggleGroupType = 'single'> = BuilderReturn<typeof createToggleGroup<T>>['elements']; | ||
export type ToggleGroupOptions<T extends ToggleGroupType = 'single'> = BuilderReturn<typeof createToggleGroup<T>>['options']; | ||
export type ToggleGroupStates<T extends ToggleGroupType = 'single'> = BuilderReturn<typeof createToggleGroup<T>>['states']; | ||
export type ToggleGroupHelpers<T extends ToggleGroupType = 'single'> = BuilderReturn<typeof createToggleGroup<T>>['helpers']; |
/// <reference types="svelte" /> | ||
import type { MeltActionReturn } from '../../internal/types.js'; | ||
import { type Writable } from 'svelte/store'; | ||
import type { TooltipEvents } from './events.js'; | ||
@@ -9,4 +10,5 @@ import type { CreateTooltipProps } from './types.js'; | ||
'aria-describedby': string; | ||
id: string; | ||
}, string>; | ||
content: import("../../internal/helpers/index.js").ExplicitBuilderReturn<[import("svelte/store").Readable<boolean>, import("svelte/store").Writable<string | HTMLElement | null>], (node: HTMLElement) => MeltActionReturn<TooltipEvents['content']>, ([$isVisible, $portal]: [boolean, string | HTMLElement | null]) => { | ||
content: import("../../internal/helpers/index.js").ExplicitBuilderReturn<[import("svelte/store").Readable<boolean>, Writable<string | HTMLElement | null>], (node: HTMLElement) => MeltActionReturn<TooltipEvents['content']>, ([$isVisible, $portal]: [boolean, string | HTMLElement | null]) => { | ||
role: string; | ||
@@ -19,3 +21,3 @@ hidden: boolean | undefined; | ||
}, string>; | ||
arrow: import("../../internal/helpers/index.js").ExplicitBuilderReturn<import("svelte/store").Writable<number>, import("svelte/action").Action<any, any, Record<never, any>>, ($arrowSize: number) => { | ||
arrow: import("../../internal/helpers/index.js").ExplicitBuilderReturn<Writable<number>, import("svelte/action").Action<any, any, Record<never, any>>, ($arrowSize: number) => { | ||
'data-arrow': boolean; | ||
@@ -33,16 +35,17 @@ style: string; | ||
options: { | ||
forceVisible: import("svelte/store").Writable<boolean>; | ||
defaultOpen: import("svelte/store").Writable<boolean>; | ||
onOpenChange?: import("svelte/store").Writable<import("../../internal/helpers/index.js").ChangeFn<boolean> | undefined> | undefined; | ||
portal: import("svelte/store").Writable<string | HTMLElement | null>; | ||
positioning: import("svelte/store").Writable<import("../../internal/actions/index.js").FloatingConfig | { | ||
forceVisible: Writable<boolean>; | ||
defaultOpen: Writable<boolean>; | ||
onOpenChange?: Writable<import("../../internal/helpers/index.js").ChangeFn<boolean> | undefined> | undefined; | ||
portal: Writable<string | HTMLElement | null>; | ||
positioning: Writable<import("../../internal/actions/index.js").FloatingConfig | { | ||
placement: "bottom"; | ||
}>; | ||
closeOnEscape: import("svelte/store").Writable<boolean>; | ||
arrowSize: import("svelte/store").Writable<number>; | ||
openDelay: import("svelte/store").Writable<number>; | ||
closeDelay: import("svelte/store").Writable<number>; | ||
closeOnPointerDown: import("svelte/store").Writable<boolean>; | ||
disableHoverableContent: import("svelte/store").Writable<boolean>; | ||
closeOnEscape: Writable<boolean>; | ||
arrowSize: Writable<number>; | ||
group: Writable<string | boolean | undefined>; | ||
openDelay: Writable<number>; | ||
closeDelay: Writable<number>; | ||
closeOnPointerDown: Writable<boolean>; | ||
disableHoverableContent: Writable<boolean>; | ||
}; | ||
}; |
@@ -1,5 +0,4 @@ | ||
import { addEventListener, addMeltEventListener, builder, createElHelpers, derivedVisible, effect, executeCallbacks, generateId, getPortalDestination, isBrowser, isTouch, kbd, makeHullFromElements, noop, omit, overridable, pointInPolygon, styleToString, toWritableStores, } from '../../internal/helpers/index.js'; | ||
import { addEventListener, addMeltEventListener, builder, createElHelpers, effect, executeCallbacks, generateId, getPortalDestination, isBrowser, isTouch, kbd, makeHullFromElements, noop, omit, overridable, pointInPolygon, styleToString, toWritableStores, } from '../../internal/helpers/index.js'; | ||
import { useFloating, usePortal } from '../../internal/actions/index.js'; | ||
import { onMount, tick } from 'svelte'; | ||
import { get, writable } from 'svelte/store'; | ||
import { derived, get, writable } from 'svelte/store'; | ||
const defaults = { | ||
@@ -18,11 +17,14 @@ positioning: { | ||
disableHoverableContent: false, | ||
group: undefined, | ||
}; | ||
const { name } = createElHelpers('tooltip'); | ||
// Store a global map to get the currently open tooltip. | ||
const groupMap = new Map(); | ||
export function createTooltip(props) { | ||
const withDefaults = { ...defaults, ...props }; | ||
const options = toWritableStores(omit(withDefaults, 'open')); | ||
const { positioning, arrowSize, closeOnPointerDown, openDelay, closeDelay, forceVisible, portal, closeOnEscape, disableHoverableContent, } = options; | ||
const { positioning, arrowSize, closeOnPointerDown, openDelay, closeDelay, forceVisible, portal, closeOnEscape, disableHoverableContent, group, } = options; | ||
const openWritable = withDefaults.open ?? writable(withDefaults.defaultOpen); | ||
const open = overridable(openWritable, withDefaults?.onOpenChange); | ||
const activeTrigger = writable(null); | ||
const openReason = writable(null); | ||
const ids = { | ||
@@ -33,10 +35,10 @@ content: generateId(), | ||
let clickedTrigger = false; | ||
onMount(() => { | ||
const getEl = (part) => { | ||
if (!isBrowser) | ||
return; | ||
activeTrigger.set(document.querySelector(`[aria-describedby="${ids.content}"]`)); | ||
}); | ||
return null; | ||
return document.getElementById(ids[part]); | ||
}; | ||
let openTimeout = null; | ||
let closeTimeout = null; | ||
function openTooltip() { | ||
function openTooltip(reason) { | ||
if (closeTimeout) { | ||
@@ -49,2 +51,4 @@ window.clearTimeout(closeTimeout); | ||
open.set(true); | ||
// Don't override the reason if it's already set. | ||
openReason.update((prev) => prev ?? reason); | ||
openTimeout = null; | ||
@@ -59,7 +63,14 @@ }, get(openDelay)); | ||
} | ||
if (isBlur && isMouseInTooltipArea) | ||
if (isBlur && isMouseInTooltipArea) { | ||
// Normally when blurring the trigger, we want to close the tooltip. | ||
// The exception is when the mouse is still in the tooltip area. | ||
// In that case, we have to set the openReason to pointer, so that | ||
// it can close when the mouse leaves the tooltip area. | ||
openReason.set('pointer'); | ||
return; | ||
} | ||
if (!closeTimeout) { | ||
closeTimeout = window.setTimeout(() => { | ||
open.set(false); | ||
openReason.set(null); | ||
if (isBlur) | ||
@@ -75,2 +86,3 @@ clickedTrigger = false; | ||
'aria-describedby': ids.content, | ||
id: ids.trigger, | ||
}; | ||
@@ -92,3 +104,3 @@ }, | ||
return; | ||
openTooltip(); | ||
openTooltip('pointer'); | ||
}), addMeltEventListener(node, 'pointerleave', (e) => { | ||
@@ -104,3 +116,3 @@ if (isTouch(e)) | ||
return; | ||
openTooltip(); | ||
openTooltip('focus'); | ||
}), addMeltEventListener(node, 'blur', () => closeTooltip(true)), addMeltEventListener(node, 'keydown', (e) => { | ||
@@ -120,3 +132,5 @@ if (get(closeOnEscape) && e.key === kbd.ESCAPE) { | ||
}); | ||
const isVisible = derivedVisible({ open, activeTrigger, forceVisible }); | ||
const isVisible = derived([open, forceVisible], ([$open, $forceVisible]) => { | ||
return $open || $forceVisible; | ||
}); | ||
const content = builder(name('content'), { | ||
@@ -139,4 +153,5 @@ stores: [isVisible, portal], | ||
let unsubPortal = noop; | ||
const unsubDerived = effect([isVisible, activeTrigger, positioning, portal], ([$isVisible, $activeTrigger, $positioning, $portal]) => { | ||
if (!$isVisible || !$activeTrigger) { | ||
const unsubDerived = effect([isVisible, positioning, portal], ([$isVisible, $positioning, $portal]) => { | ||
const triggerEl = getEl('trigger'); | ||
if (!$isVisible || !triggerEl) { | ||
unsubPortal(); | ||
@@ -146,19 +161,17 @@ unsubFloating(); | ||
} | ||
tick().then(() => { | ||
const floatingReturn = useFloating($activeTrigger, node, $positioning); | ||
unsubFloating = floatingReturn.destroy; | ||
if (!$portal) { | ||
unsubPortal(); | ||
return; | ||
const floatingReturn = useFloating(triggerEl, node, $positioning); | ||
unsubFloating = floatingReturn.destroy; | ||
if (!$portal) { | ||
unsubPortal(); | ||
return; | ||
} | ||
const portalDest = getPortalDestination(node, $portal); | ||
if (portalDest) { | ||
const portalReturn = usePortal(node, portalDest); | ||
if (portalReturn && portalReturn.destroy) { | ||
unsubPortal = portalReturn.destroy; | ||
} | ||
const portalDest = getPortalDestination(node, $portal); | ||
if (portalDest) { | ||
const portalReturn = usePortal(node, portalDest); | ||
if (portalReturn && portalReturn.destroy) { | ||
unsubPortal = portalReturn.destroy; | ||
} | ||
} | ||
}); | ||
} | ||
}); | ||
const unsubEvents = executeCallbacks(addMeltEventListener(node, 'pointerenter', openTooltip), addMeltEventListener(node, 'pointerdown', openTooltip)); | ||
const unsubEvents = executeCallbacks(addMeltEventListener(node, 'pointerenter', () => openTooltip('pointer')), addMeltEventListener(node, 'pointerdown', () => openTooltip('pointer'))); | ||
return { | ||
@@ -186,12 +199,24 @@ destroy() { | ||
let isMouseInTooltipArea = false; | ||
effect([isVisible, activeTrigger], ([$isVisible, $activeTrigger]) => { | ||
if (!$isVisible || !$activeTrigger) | ||
effect(open, ($open) => { | ||
if (!$open) | ||
return; | ||
const groupValue = get(group); | ||
if (groupValue === undefined || groupValue === false) { | ||
return; | ||
} | ||
// Close the currently open tooltip in the same group | ||
// and set this tooltip as the open one. | ||
const currentOpen = groupMap.get(groupValue); | ||
currentOpen?.set(false); | ||
groupMap.set(groupValue, open); | ||
}); | ||
effect([open, openReason], ([$open, $openReason]) => { | ||
if (!$open || !isBrowser) | ||
return; | ||
return executeCallbacks(addEventListener(document, 'mousemove', (e) => { | ||
const contentEl = document.getElementById(ids.content); | ||
if (!contentEl) | ||
const contentEl = getEl('content'); | ||
const triggerEl = getEl('trigger'); | ||
if (!contentEl || !triggerEl) | ||
return; | ||
const polygonElements = get(disableHoverableContent) | ||
? [$activeTrigger] | ||
: [$activeTrigger, contentEl]; | ||
const polygonElements = get(disableHoverableContent) ? [triggerEl] : [triggerEl, contentEl]; | ||
const polygon = makeHullFromElements(polygonElements); | ||
@@ -202,5 +227,6 @@ isMouseInTooltipArea = pointInPolygon({ | ||
}, polygon); | ||
if (isMouseInTooltipArea || | ||
(document.activeElement === $activeTrigger && !clickedTrigger)) { | ||
openTooltip(); | ||
if ($openReason !== 'pointer') | ||
return; | ||
if (isMouseInTooltipArea) { | ||
openTooltip('pointer'); | ||
} | ||
@@ -207,0 +233,0 @@ else { |
@@ -20,2 +20,8 @@ import type { FloatingConfig } from '../../internal/actions/index.js'; | ||
/** | ||
* If set to `true`, whenever you open this tooltip, all other tooltips | ||
* with `group` also set to `true` will close. If you pass in a string | ||
* instead, only tooltips with the same `group` value will be closed. | ||
*/ | ||
group?: boolean | string; | ||
/** | ||
* If not undefined, the tooltip will be rendered within the provided element or selector. | ||
@@ -22,0 +28,0 @@ * |
@@ -16,6 +16,7 @@ import type { Action, ActionReturn } from 'svelte/action'; | ||
* <script> | ||
* const { builder, melt } = createBuilder(); | ||
* import { createLabel, melt } from '@melt-ui/svelte'; | ||
* const { elements: { root } } = createLabel(); | ||
* </script> | ||
* | ||
* <div use:melt={$builder} /> | ||
* <label use:melt={$root} /> | ||
* ``` | ||
@@ -22,0 +23,0 @@ */ |
@@ -9,6 +9,7 @@ /** | ||
* <script> | ||
* const { builder, melt } = createBuilder(); | ||
* import { createLabel, melt } from '@melt-ui/svelte'; | ||
* const { elements: { root } } = createLabel(); | ||
* </script> | ||
* | ||
* <div use:melt={$builder} /> | ||
* <label use:melt={$root} /> | ||
* ``` | ||
@@ -15,0 +16,0 @@ */ |
{ | ||
"name": "@melt-ui/svelte", | ||
"version": "0.50.1", | ||
"version": "0.51.0", | ||
"license": "MIT", | ||
@@ -5,0 +5,0 @@ "repository": "github:melt-ui/melt-ui", |
@@ -24,9 +24,6 @@ <h1 align="center"> | ||
Install the `@melt-ui/svelte` package with your package manager of choice: | ||
Run our installer script to get started: | ||
```sh | ||
npm install @melt-ui/svelte | ||
yarn add @melt-ui/svelte | ||
pnpm add @melt-ui/svelte | ||
bun add @melt-ui/svelte | ||
npx @melt-ui/cli@latest init | ||
``` | ||
@@ -38,9 +35,13 @@ | ||
<script> | ||
import { createCollapsible } from '@melt-ui/svelte' | ||
const { open, root, content, trigger } = createCollapsible() | ||
import { createCollapsible, melt } from '@melt-ui/svelte' | ||
const { | ||
elements: { root, content, trigger }, | ||
states: { open } | ||
} = createCollapsible() | ||
</script> | ||
<div {...$root}> | ||
<button {...$trigger}>{$open ? 'Close' : 'Open'}</button> | ||
<div {...$content}>Obi-Wan says: Hello there!</div> | ||
<div use:melt="{$root}"> | ||
<button use:melt="{$trigger}">{$open ? 'Close' : 'Open'}</button> | ||
<div use:melt="{$content}">Obi-Wan says: Hello there!</div> | ||
</div> | ||
@@ -54,7 +55,6 @@ ``` | ||
Melt UI is under active development. Currently planned features can be found in the | ||
[roadmap](#roadmap), or in the [issues tab](https://github.com/melt-ui/melt-ui/issues), alongside | ||
bug reports. | ||
[issues tab](https://github.com/melt-ui/melt-ui/issues), alongside bug reports. | ||
We work on this project on a volunteer basis in our free time. If you notice something that hasn't | ||
been implemented yet or could be improved, do consider contributing to the project. The goal is to | ||
been implemented yet or could be improved, do consider contributing to the project! The goal is to | ||
enhance the experience of building with Svelte and improve the ecosystem for everyone. | ||
@@ -78,43 +78,2 @@ | ||
## Roadmap | ||
| Component name | Status | | ||
| ----------------- | ------ | | ||
| Accordion | ✅ | | ||
| Avatar | ✅ | | ||
| Calendar | | | ||
| Checkbox | ✅ | | ||
| Collapsible | ✅ | | ||
| ComboBox | ✅ | | ||
| Command Center | | | ||
| Context Menu | ✅ | | ||
| Dialog | ✅ | | ||
| Dropdown Menu | ✅ | | ||
| Dropzone | | | ||
| Label | ✅ | | ||
| Link Preview | ✅ | | ||
| Menubar | ✅ | | ||
| Navigation Menu | | | ||
| Pagination | ✅ | | ||
| Pin Input | ✅ | | ||
| Popover | ✅ | | ||
| Progress | ✅ | | ||
| Radio Group | ✅ | | ||
| Scroll Area | | | ||
| Select | ✅ | | ||
| Separator | ✅ | | ||
| Slider | ✅ | | ||
| Spin Button | | | ||
| Switch | ✅ | | ||
| Table of Contents | ✅ | | ||
| Tabs | ✅ | | ||
| Tags Input | ✅ | | ||
| Toast | ✅ | | ||
| Toggle | ✅ | | ||
| Toggle Group | ✅ | | ||
| Toolbar | ✅ | | ||
| Tooltip | ✅ | | ||
| Tree View | | | ||
| ??? | | | ||
## Similar projects | ||
@@ -121,0 +80,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
617097
14277
79