@melt-ui/svelte
Advanced tools
| import { type Readable } from 'svelte/store'; | ||
| type CreateSliderArgs = { | ||
| value: number[]; | ||
| min?: number; | ||
| max?: number; | ||
| step?: number; | ||
| orientation?: 'horizontal' | 'vertical'; | ||
| disabled?: boolean; | ||
| }; | ||
| export declare const createSlider: (args?: CreateSliderArgs) => { | ||
| root: Readable<{ | ||
| disabled: boolean; | ||
| 'data-orientation': "horizontal" | "vertical"; | ||
| } & { | ||
| 'data-melt-id': string; | ||
| }>; | ||
| slider: Readable<{ | ||
| disabled: boolean; | ||
| 'data-orientation': "horizontal" | "vertical"; | ||
| } & { | ||
| 'data-melt-id': string; | ||
| }>; | ||
| range: Readable<{ | ||
| style: string; | ||
| }>; | ||
| thumb: Readable<() => { | ||
| role: string; | ||
| 'aria-label': string; | ||
| 'aria-valuemin': number; | ||
| 'aria-valuemax': number; | ||
| 'aria-valuenow': number; | ||
| 'data-melt-part': string; | ||
| style: string; | ||
| tabindex: number; | ||
| } & { | ||
| 'data-melt-id': string; | ||
| }>; | ||
| value: import("svelte/store").Writable<number[]>; | ||
| options: import("svelte/store").Writable<Omit<{ | ||
| value: number[]; | ||
| min: number; | ||
| max: number; | ||
| step: number; | ||
| orientation: 'horizontal' | 'vertical'; | ||
| disabled: boolean; | ||
| }, "value">>; | ||
| }; | ||
| export {}; |
| import { effect, elementDerived, elementMultiDerived, getElementByMeltId, isBrowser, kbd, omit, styleToString, } from '../../internal/helpers'; | ||
| import { derived, get, writable } from 'svelte/store'; | ||
| const defaults = { | ||
| value: [], | ||
| min: 0, | ||
| max: 100, | ||
| step: 1, | ||
| orientation: 'horizontal', | ||
| disabled: false, | ||
| }; | ||
| export const createSlider = (args = defaults) => { | ||
| const withDefaults = { ...defaults, ...args }; | ||
| const options = writable(omit(withDefaults, 'value')); | ||
| const value = writable(withDefaults.value); | ||
| const isActive = writable(false); | ||
| const currentThumbIndex = writable(0); | ||
| const activeThumb = writable(null); | ||
| const root = elementDerived(options, ($options) => { | ||
| return { disabled: $options.disabled, 'data-orientation': $options.orientation }; | ||
| }); | ||
| const range = derived([value, options], ([$value, $options]) => { | ||
| const orientationStyles = $options.orientation === 'horizontal' | ||
| ? { | ||
| left: `${$value.length > 1 ? Math.min(...$value) ?? 0 : 0}%`, | ||
| right: `calc(${100 - (Math.max(...$value) ?? 0)}%)`, | ||
| } | ||
| : { | ||
| top: `${$value.length > 1 ? Math.min(...$value) ?? 0 : 0}%`, | ||
| bottom: `calc(${100 - (Math.max(...$value) ?? 0)}%)`, | ||
| }; | ||
| return { | ||
| style: styleToString({ | ||
| position: 'absolute', | ||
| ...orientationStyles, | ||
| }), | ||
| }; | ||
| }); | ||
| const getAllThumbs = derived(root, ($root) => { | ||
| return () => { | ||
| const rootEl = getElementByMeltId($root['data-melt-id']); | ||
| if (!rootEl) | ||
| return; | ||
| return Array.from(rootEl.querySelectorAll('[data-melt-part="thumb"]')); | ||
| }; | ||
| }); | ||
| const updatePosition = (val, index) => { | ||
| value.update((prev) => { | ||
| if (!prev) | ||
| return [val]; | ||
| const isFirst = index === 0; | ||
| const isLast = index === prev.length - 1; | ||
| if (!isLast && val > prev[index + 1]) { | ||
| prev[index] = prev[index + 1]; | ||
| } | ||
| else if (!isFirst && val < prev[index - 1]) { | ||
| prev[index] = prev[index - 1]; | ||
| } | ||
| else { | ||
| prev[index] = val; | ||
| } | ||
| return prev.map(Math.abs); | ||
| }); | ||
| }; | ||
| const thumb = elementMultiDerived([value, getAllThumbs, options], ([$value, $getAllThumbs, $options], { attach, index }) => { | ||
| return () => { | ||
| const { min: $min, max: $max, disabled: $disabled } = $options; | ||
| const currentThumb = get(currentThumbIndex); | ||
| if (currentThumb < $value.length) { | ||
| currentThumbIndex.update((prev) => prev + 1); | ||
| } | ||
| attach('keydown', (event) => { | ||
| if ($disabled) | ||
| return; | ||
| const target = event.currentTarget; | ||
| const thumbs = $getAllThumbs(); | ||
| if (!thumbs) | ||
| return; | ||
| const index = thumbs.indexOf(target); | ||
| currentThumbIndex.set(index); | ||
| if (![ | ||
| kbd.ARROW_LEFT, | ||
| kbd.ARROW_RIGHT, | ||
| kbd.ARROW_UP, | ||
| kbd.ARROW_DOWN, | ||
| kbd.HOME, | ||
| kbd.END, | ||
| ].includes(event.key)) { | ||
| return; | ||
| } | ||
| event.preventDefault(); | ||
| const step = $options.step; | ||
| const $value = get(value); | ||
| switch (event.key) { | ||
| case kbd.HOME: { | ||
| updatePosition($min, index); | ||
| break; | ||
| } | ||
| case kbd.END: { | ||
| updatePosition($max, index); | ||
| break; | ||
| } | ||
| case kbd.ARROW_LEFT: { | ||
| if ($value[index] > $min && $options.orientation === 'horizontal') { | ||
| const newValue = $value[index] - step; | ||
| updatePosition(newValue, index); | ||
| } | ||
| break; | ||
| } | ||
| case kbd.ARROW_RIGHT: { | ||
| if ($value[index] < $max && $options.orientation === 'horizontal') { | ||
| const newValue = $value[index] + step; | ||
| updatePosition(newValue, index); | ||
| } | ||
| break; | ||
| } | ||
| case kbd.ARROW_UP: { | ||
| if ($value[index] > $min && $options.orientation === 'vertical') { | ||
| const newValue = $value[index] - step; | ||
| updatePosition(newValue, index); | ||
| } | ||
| else if ($value[index] < $max) { | ||
| const newValue = $value[index] + step; | ||
| updatePosition(newValue, index); | ||
| } | ||
| break; | ||
| } | ||
| case kbd.ARROW_DOWN: { | ||
| if ($value[index] < $max && $options.orientation === 'vertical') { | ||
| const newValue = $value[index] + step; | ||
| updatePosition(newValue, index); | ||
| } | ||
| else if ($value[index] > $min) { | ||
| const newValue = $value[index] - step; | ||
| updatePosition(newValue, index); | ||
| } | ||
| break; | ||
| } | ||
| } | ||
| }); | ||
| return { | ||
| role: 'slider', | ||
| 'aria-label': 'Volume', | ||
| 'aria-valuemin': $min, | ||
| 'aria-valuemax': $max, | ||
| 'aria-valuenow': $value[index], | ||
| 'data-melt-part': 'thumb', | ||
| style: styleToString({ | ||
| position: 'absolute', | ||
| ...($options.orientation === 'horizontal' | ||
| ? { left: `${$value[index]}%;`, translate: '-50% 0' } | ||
| : { top: `${$value[index]}%;`, translate: '0 -50%`' }), | ||
| }), | ||
| tabindex: $disabled ? -1 : 0, | ||
| }; | ||
| }; | ||
| }); | ||
| effect([root, getAllThumbs, options], ([$root, $getAllThumbs, $options]) => { | ||
| const { min: $min, max: $max, disabled: $disabled } = $options; | ||
| if (!isBrowser || $disabled) | ||
| return; | ||
| const applyPosition = (clientXY, activeThumbIdx, leftOrTop, rightOrBottom) => { | ||
| const percent = (clientXY - leftOrTop) / (rightOrBottom - leftOrTop); | ||
| const val = Math.round(percent * ($max - $min) + $min); | ||
| if (val < $min || val > $max) | ||
| return; | ||
| const step = $options.step; | ||
| const newValue = Math.round(val / step) * step; | ||
| updatePosition(newValue, activeThumbIdx); | ||
| }; | ||
| const getClosestThumb = (e) => { | ||
| const thumbs = $getAllThumbs(); | ||
| if (!thumbs) | ||
| return; | ||
| thumbs.forEach((thumb) => thumb.blur()); | ||
| const distances = thumbs.map((thumb) => { | ||
| if ($options.orientation === 'horizontal') { | ||
| const { left, right } = thumb.getBoundingClientRect(); | ||
| return Math.abs(e.clientX - (left + right) / 2); | ||
| } | ||
| else { | ||
| const { top, bottom } = thumb.getBoundingClientRect(); | ||
| return Math.abs(e.clientY - (top + bottom) / 2); | ||
| } | ||
| }); | ||
| const thumb = thumbs[distances.indexOf(Math.min(...distances))]; | ||
| const index = thumbs.indexOf(thumb); | ||
| return { thumb, index }; | ||
| }; | ||
| const pointerDown = (e) => { | ||
| e.preventDefault(); | ||
| const sliderEl = getElementByMeltId($root['data-melt-id']); | ||
| const closestThumb = getClosestThumb(e); | ||
| if (!closestThumb || !sliderEl) | ||
| return; | ||
| if (!sliderEl.contains(e.target)) | ||
| return; | ||
| activeThumb.set(closestThumb); | ||
| closestThumb.thumb.focus(); | ||
| isActive.set(true); | ||
| if ($options.orientation === 'horizontal') { | ||
| const { left, right } = sliderEl.getBoundingClientRect(); | ||
| applyPosition(e.clientX, closestThumb.index, left, right); | ||
| } | ||
| else { | ||
| const { top, bottom } = sliderEl.getBoundingClientRect(); | ||
| applyPosition(e.clientY, closestThumb.index, top, bottom); | ||
| } | ||
| }; | ||
| const pointerUp = () => { | ||
| isActive.set(false); | ||
| }; | ||
| const pointerMove = (e) => { | ||
| if (!get(isActive)) | ||
| return; | ||
| const sliderEl = getElementByMeltId($root['data-melt-id']); | ||
| const closestThumb = get(activeThumb); | ||
| if (!sliderEl || !closestThumb) | ||
| return; | ||
| closestThumb.thumb.focus(); | ||
| if ($options.orientation === 'horizontal') { | ||
| const { left, right } = sliderEl.getBoundingClientRect(); | ||
| applyPosition(e.clientX, closestThumb.index, left, right); | ||
| } | ||
| else { | ||
| const { top, bottom } = sliderEl.getBoundingClientRect(); | ||
| applyPosition(e.clientY, closestThumb.index, top, bottom); | ||
| } | ||
| }; | ||
| document.addEventListener('pointerdown', pointerDown); | ||
| document.addEventListener('pointerup', pointerUp); | ||
| document.addEventListener('pointerleave', pointerUp); | ||
| document.addEventListener('pointermove', pointerMove); | ||
| return () => { | ||
| document.removeEventListener('pointerdown', pointerDown); | ||
| document.removeEventListener('pointerup', pointerUp); | ||
| document.removeEventListener('pointerleave', pointerUp); | ||
| document.removeEventListener('pointermove', pointerMove); | ||
| }; | ||
| }); | ||
| return { | ||
| root, | ||
| slider: root, | ||
| range, | ||
| thumb, | ||
| value, | ||
| options, | ||
| }; | ||
| }; |
@@ -12,4 +12,5 @@ export * from './accordion'; | ||
| export * from './radio-group'; | ||
| export * from './slider'; | ||
| export * from './dialog'; | ||
| export * from './tooltip'; | ||
| export * from './pagination'; |
@@ -12,4 +12,5 @@ export * from './accordion'; | ||
| export * from './radio-group'; | ||
| export * from './slider'; | ||
| export * from './dialog'; | ||
| export * from './tooltip'; | ||
| export * from './pagination'; |
| import { elementDerived, elementMultiDerived, kbd, omit } from '../../internal/helpers'; | ||
| import { derived, writable } from 'svelte/store'; | ||
| const defaults = { | ||
| perPage: 10, | ||
| perPage: 1, | ||
| siblingCount: 1, | ||
@@ -6,0 +6,0 @@ page: 1, |
@@ -18,2 +18,3 @@ import type { FloatingConfig } from '../../internal/actions'; | ||
| selected?: string | number; | ||
| name?: string; | ||
| }; | ||
@@ -47,2 +48,4 @@ export declare function createSelect(args?: CreateSelectArgs): { | ||
| 'data-selected': string | undefined; | ||
| 'data-value': string | number; | ||
| 'data-type': "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"; | ||
| tabindex: number; | ||
@@ -53,3 +56,3 @@ } & { | ||
| selected: import("svelte/store").Writable<string | number | null>; | ||
| selectedText: import("svelte/store").Writable<string | null>; | ||
| selectedText: import("svelte/store").Writable<string | number | null>; | ||
| arrow: import("svelte/store").Readable<{ | ||
@@ -60,9 +63,14 @@ 'data-arrow': boolean; | ||
| isSelected: import("svelte/store").Readable<(value: string | number) => boolean>; | ||
| options: import("svelte/store").Writable<{ | ||
| positioning?: FloatingConfig | undefined; | ||
| arrowSize?: number | undefined; | ||
| required?: boolean | undefined; | ||
| disabled?: boolean | undefined; | ||
| selected?: string | number | undefined; | ||
| options: import("svelte/store").Writable<Omit<CreateSelectArgs, "selected">>; | ||
| input: import("svelte/store").Readable<{ | ||
| type: string; | ||
| name: string | undefined; | ||
| value: string | number | null; | ||
| 'aria-hidden': boolean; | ||
| hidden: boolean; | ||
| tabIndex: number; | ||
| required: boolean | undefined; | ||
| disabled: boolean | undefined; | ||
| style: string; | ||
| }>; | ||
| }; |
| import { usePopper } from '../../internal/actions/popper'; | ||
| import { debounce, effect, elementDerived, elementMultiDerived, getElementByMeltId, isBrowser, kbd, styleToString, uuid, } from '../../internal/helpers'; | ||
| import { debounce, effect, elementDerived, elementMultiDerived, getElementByMeltId, isBrowser, kbd, omit, styleToString, uuid, } from '../../internal/helpers'; | ||
| import { sleep } from '../../internal/helpers/sleep'; | ||
@@ -17,3 +17,3 @@ import { tick } from 'svelte'; | ||
| const withDefaults = { ...defaults, ...args }; | ||
| const options = writable({ ...withDefaults }); | ||
| const options = writable(omit(withDefaults, 'selected')); | ||
| const open = writable(false); | ||
@@ -105,2 +105,4 @@ const selected = writable(withDefaults.selected ?? null); | ||
| 'data-selected': $selected === value ? '' : undefined, | ||
| 'data-value': value, | ||
| 'data-type': typeof value, | ||
| tabindex: 0, | ||
@@ -192,3 +194,11 @@ }; | ||
| if (selectedOption) { | ||
| selectedText.set(selectedOption.innerText); | ||
| const data = selectedOption.getAttribute('data-value'); | ||
| if (data) { | ||
| if (selectedOption.getAttribute('data-type') === 'number') { | ||
| selectedText.set(+data); | ||
| } | ||
| else { | ||
| selectedText.set(data); | ||
| } | ||
| } | ||
| } | ||
@@ -202,3 +212,22 @@ }); | ||
| }); | ||
| return { trigger, menu, open, option, selected, selectedText, arrow, isSelected, options }; | ||
| const input = derived([selected, options], ([$selected, $options]) => { | ||
| return { | ||
| type: 'hidden', | ||
| name: $options.name, | ||
| value: $selected, | ||
| 'aria-hidden': true, | ||
| hidden: true, | ||
| tabIndex: -1, | ||
| required: $options.required, | ||
| disabled: $options.disabled, | ||
| style: styleToString({ | ||
| position: 'absolute', | ||
| opacity: 0, | ||
| 'pointer-events': 'none', | ||
| margin: 0, | ||
| transform: 'translateX(-100%)', | ||
| }), | ||
| }; | ||
| }); | ||
| return { trigger, menu, open, option, selected, selectedText, arrow, isSelected, options, input }; | ||
| } |
@@ -51,2 +51,5 @@ import { type Readable } from 'svelte/store'; | ||
| }; | ||
| type MultiHelpers = Helpers & { | ||
| index: number; | ||
| }; | ||
| type ReturnWithObj<T extends () => void, Obj> = ReturnType<T> extends void ? Obj : ReturnType<T> & Obj; | ||
@@ -89,3 +92,3 @@ /** | ||
| */ | ||
| export declare function elementMultiDerived<S extends Stores, T extends (...args: any[]) => Record<string, unknown> | void>(stores: S, fn: (values: StoresValues<S>, helpers: Helpers) => T): Readable<(...args: Parameters<T>) => ReturnWithObj<T, { | ||
| export declare function elementMultiDerived<S extends Stores, T extends (...args: any[]) => Record<string, unknown> | void>(stores: S, fn: (values: StoresValues<S>, helpers: MultiHelpers) => T): Readable<(...args: Parameters<T>) => ReturnWithObj<T, { | ||
| 'data-melt-id': string; | ||
@@ -92,0 +95,0 @@ }>>; |
@@ -168,2 +168,3 @@ import { onDestroy, tick } from 'svelte'; | ||
| const { addUnsubscriber, createElInterface, unsubscribe } = initElementHelpers((newId) => (id = newId)); | ||
| let index = 0; | ||
| return derived(stores, ($storeValues) => { | ||
@@ -175,3 +176,10 @@ // Unsubscribe from all events | ||
| const { attach, getElement, addAction } = createElInterface(); | ||
| const returned = fn($storeValues, { attach, getElement, addUnsubscriber, addAction }); | ||
| const returned = fn($storeValues, { | ||
| attach, | ||
| getElement, | ||
| addUnsubscriber, | ||
| addAction, | ||
| index: index++, | ||
| }); | ||
| addUnsubscriber(() => index--); | ||
| return { ...returned(...args), 'data-melt-id': id }; | ||
@@ -178,0 +186,0 @@ }; |
+1
-1
| { | ||
| "name": "@melt-ui/svelte", | ||
| "version": "0.8.3", | ||
| "version": "0.9.0", | ||
| "license": "MIT", | ||
@@ -5,0 +5,0 @@ "exports": { |
140075
10.34%101
2.02%3667
10.42%