@alann-estrada-ksh/sileo-vue 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sources":["../src/types.ts","../src/toast.ts"],"sourcesContent":["import type { VNodeChild } from \"vue\";\r\n\r\nexport type SileoState =\r\n\t| \"success\"\r\n\t| \"loading\"\r\n\t| \"error\"\r\n\t| \"warning\"\r\n\t| \"info\"\r\n\t| \"action\";\r\n\r\nexport interface SileoStyles {\r\n\ttitle?: string;\r\n\tdescription?: string;\r\n\tbadge?: string;\r\n\tbutton?: string;\r\n\ttoast?: string;\r\n}\r\n\r\nexport interface SileoButton {\r\n\ttitle: string;\r\n\tonClick?: () => void;\r\n}\r\n\r\nexport interface SileoAutopilot {\r\n\texpand?: number;\r\n\tcollapse?: number;\r\n}\r\n\r\nexport interface SileoLifecycleContext {\r\n\tid: string;\r\n\tinstanceId: string;\r\n\tstate: SileoState;\r\n}\r\n\r\nexport interface SileoLifecycleHooks {\r\n\tonShow?: (ctx: SileoLifecycleContext) => void;\r\n\tonExpand?: (ctx: SileoLifecycleContext) => void;\r\n\tonCollapse?: (ctx: SileoLifecycleContext) => void;\r\n\tonDismiss?: (ctx: SileoLifecycleContext) => void;\r\n}\r\n\r\nexport const SILEO_POSITIONS = [\r\n\t\"top-left\",\r\n\t\"top-center\",\r\n\t\"top-right\",\r\n\t\"bottom-left\",\r\n\t\"bottom-center\",\r\n\t\"bottom-right\",\r\n] as const;\r\n\r\nexport type SileoPosition = (typeof SILEO_POSITIONS)[number];\r\n\r\nexport interface SileoOptions {\r\n\tid?: string;\r\n\ttitle?: string;\r\n\tdescription?: VNodeChild | string;\r\n\ttype?: SileoState;\r\n\tposition?: SileoPosition;\r\n\tduration?: number | null;\r\n\ticon?: VNodeChild | null;\r\n\tstyles?: SileoStyles;\r\n\tfill?: string;\r\n\troundness?: number;\r\n\tautopilot?: boolean | SileoAutopilot;\r\n\tswipeToDismiss?: boolean;\r\n\tbutton?: SileoButton;\r\n\tgroupKey?: string;\r\n\thooks?: SileoLifecycleHooks;\r\n}\r\n\r\nexport type SileoOffsetValue = number | string;\r\nexport type SileoOffsetConfig = Partial<\r\n\tRecord<\"top\" | \"right\" | \"bottom\" | \"left\", SileoOffsetValue>\r\n>;\r\n","import {\r\n Teleport,\r\n computed,\r\n defineComponent,\r\n h,\r\n onBeforeUnmount,\r\n onMounted,\r\n ref,\r\n watch,\r\n type ComponentPublicInstance,\r\n type CSSProperties,\r\n type PropType,\r\n} from \"vue\";\r\nimport {\r\n AUTOPILOT_COLLAPSE_DELAY,\r\n AUTOPILOT_EXPAND_DELAY,\r\n BLUR_RATIO,\r\n DEFAULT_ROUNDNESS,\r\n DEFAULT_TOAST_DURATION,\r\n EXIT_DURATION,\r\n GROUP_THRESHOLD,\r\n MIN_EXPAND_RATIO,\r\n PILL_PADDING,\r\n TOAST_HEIGHT,\r\n TOAST_WIDTH,\r\n} from \"./constants\";\r\nimport type {\r\n SileoLifecycleContext,\r\n SileoOffsetConfig,\r\n SileoOffsetValue,\r\n SileoOptions,\r\n SileoPosition,\r\n SileoState,\r\n} from \"./types\";\r\n\r\ninterface SileoItem extends Omit<SileoOptions, \"type\"> {\r\n id: string;\r\n instanceId: string;\r\n state: SileoState;\r\n createdAt: number;\r\n exiting?: boolean;\r\n}\r\n\r\ntype AutopilotConfig = {\r\n expand: number;\r\n collapse: number;\r\n};\r\n\r\ntype Listener = (toasts: SileoItem[]) => void;\r\n\r\nconst store = {\r\n toasts: [] as SileoItem[],\r\n listeners: new Set<Listener>(),\r\n position: \"top-right\" as SileoPosition,\r\n options: undefined as Partial<SileoOptions> | undefined,\r\n\r\n emit() {\r\n for (const listener of this.listeners) listener(this.toasts);\r\n },\r\n\r\n update(updater: (prev: SileoItem[]) => SileoItem[]) {\r\n this.toasts = updater(this.toasts);\r\n this.emit();\r\n },\r\n};\r\n\r\nconst dismissalTimers = new Map<string, ReturnType<typeof setTimeout>>();\r\nlet sequence = 0;\r\n\r\nconst nextId = () => `sileo-${++sequence}-${Date.now().toString(36)}`;\r\nconst nextInstanceId = () => `instance-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;\r\n\r\nconst clearTimer = (instanceId: string) => {\r\n const timer = dismissalTimers.get(instanceId);\r\n if (timer !== undefined) {\r\n clearTimeout(timer);\r\n dismissalTimers.delete(instanceId);\r\n }\r\n};\r\n\r\nconst removeByInstance = (instanceId: string) => {\r\n clearTimer(instanceId);\r\n store.update((prev) => prev.filter((item) => item.instanceId !== instanceId));\r\n};\r\n\r\nconst dismissByInstance = (instanceId: string) => {\r\n const target = store.toasts.find(\r\n (item) => item.instanceId === instanceId && !item.exiting,\r\n );\r\n if (!target) return;\r\n\r\n clearTimer(instanceId);\r\n target.hooks?.onDismiss?.(toLifecycleContext(target));\r\n store.update((prev) =>\r\n prev.map((item) =>\r\n item.instanceId === instanceId ? { ...item, exiting: true } : item,\r\n ),\r\n );\r\n\r\n globalThis.setTimeout(() => {\r\n removeByInstance(instanceId);\r\n }, EXIT_DURATION);\r\n};\r\n\r\nconst scheduleDismiss = (item: SileoItem) => {\r\n clearTimer(item.instanceId);\r\n if (item.duration === null) return;\r\n const duration = item.duration ?? DEFAULT_TOAST_DURATION;\r\n if (duration <= 0) return;\r\n\r\n const timer = globalThis.setTimeout(() => {\r\n dismissByInstance(item.instanceId);\r\n }, duration);\r\n\r\n dismissalTimers.set(item.instanceId, timer);\r\n};\r\n\r\nconst mergeOptions = (options: SileoOptions): SileoOptions => ({\r\n ...store.options,\r\n ...options,\r\n styles: { ...store.options?.styles, ...options.styles },\r\n});\r\n\r\nconst buildItem = (options: SileoOptions, state: SileoState): SileoItem => ({\r\n id: options.id ?? nextId(),\r\n instanceId: nextInstanceId(),\r\n state,\r\n title: options.title,\r\n description: options.description,\r\n position: options.position ?? store.position,\r\n duration: options.duration,\r\n icon: options.icon,\r\n styles: options.styles,\r\n fill: options.fill,\r\n roundness: options.roundness,\r\n autopilot: options.autopilot,\r\n swipeToDismiss: options.swipeToDismiss,\r\n button: options.button,\r\n groupKey: options.groupKey,\r\n hooks: options.hooks,\r\n createdAt: Date.now(),\r\n});\r\n\r\nconst createToast = (raw: SileoOptions, forcedState?: SileoState) => {\r\n const merged = mergeOptions(raw);\r\n const state = forcedState ?? merged.type ?? \"info\";\r\n const hasCustomId = Boolean(merged.id);\r\n\r\n if (hasCustomId) {\r\n const existing = store.toasts.find(\r\n (item) => item.id === merged.id && !item.exiting,\r\n );\r\n if (existing) {\r\n const replacement = buildItem(merged, state);\r\n replacement.id = existing.id;\r\n store.update((prev) =>\r\n prev.map((item) =>\r\n item.instanceId === existing.instanceId ? replacement : item,\r\n ),\r\n );\r\n clearTimer(existing.instanceId);\r\n scheduleDismiss(replacement);\r\n replacement.hooks?.onShow?.(toLifecycleContext(replacement));\r\n return existing.id;\r\n }\r\n }\r\n\r\n const item = buildItem(merged, state);\r\n store.update((prev) => [...prev, item]);\r\n scheduleDismiss(item);\r\n item.hooks?.onShow?.(toLifecycleContext(item));\r\n return item.id;\r\n};\r\n\r\nconst dismissToast = (id?: string) => {\r\n if (!id) {\r\n for (const item of store.toasts) dismissByInstance(item.instanceId);\r\n return;\r\n }\r\n\r\n const active = store.toasts\r\n .filter((item) => item.id === id && !item.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n\r\n if (!active) return;\r\n dismissByInstance(active.instanceId);\r\n};\r\n\r\nconst clearToasts = (position?: SileoPosition) => {\r\n const removed = position\r\n ? store.toasts.filter((item) => item.position === position)\r\n : [...store.toasts];\r\n\r\n for (const item of removed) clearTimer(item.instanceId);\r\n\r\n store.update((prev) =>\r\n position ? prev.filter((item) => item.position !== position) : [],\r\n );\r\n};\r\n\r\nconst toLifecycleContext = (item: SileoItem): SileoLifecycleContext => ({\r\n id: item.id,\r\n instanceId: item.instanceId,\r\n state: item.state,\r\n});\r\n\r\nconst resolveTheme = (theme: \"light\" | \"dark\" | \"system\" | undefined) => {\r\n if (theme === \"light\" || theme === \"dark\") return theme;\r\n if (typeof window === \"undefined\") return \"light\";\r\n if (typeof window.matchMedia !== \"function\") return \"light\";\r\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches\r\n ? \"dark\"\r\n : \"light\";\r\n};\r\n\r\nconst normalizeAutopilot = (\r\n value: SileoOptions[\"autopilot\"],\r\n): AutopilotConfig | null => {\r\n if (value === false) return null;\r\n if (value === true || value === undefined) {\r\n return {\r\n expand: AUTOPILOT_EXPAND_DELAY,\r\n collapse: AUTOPILOT_COLLAPSE_DELAY,\r\n };\r\n }\r\n\r\n const expand = Number.isFinite(value.expand)\r\n ? Math.max(0, Number(value.expand))\r\n : AUTOPILOT_EXPAND_DELAY;\r\n const collapse = Number.isFinite(value.collapse)\r\n ? Math.max(expand + 200, Number(value.collapse))\r\n : AUTOPILOT_COLLAPSE_DELAY;\r\n\r\n return { expand, collapse };\r\n};\r\n\r\nconst renderStateIcon = (state: SileoState) => {\r\n const common = {\r\n viewBox: \"0 0 16 16\",\r\n width: \"14\",\r\n height: \"14\",\r\n \"aria-hidden\": \"true\",\r\n focusable: \"false\",\r\n } as const;\r\n\r\n if (state === \"success\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M3.2 8.4L6.5 11.4L12.8 4.9\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"error\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M5 5L11 11M11 5L5 11\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"warning\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M8 3.2L13 12.5H3L8 3.2Z\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.4\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 6.2V9\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.5\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n h(\"circle\", { cx: \"8\", cy: \"11\", r: \"0.9\", fill: \"currentColor\" }),\r\n ]);\r\n }\r\n if (state === \"action\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M3.5 8H12.5M9.2 4.8L12.4 8L9.2 11.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"loading\") {\r\n return h(\"svg\", { ...common, \"data-sileo-icon\": \"spin\" }, [\r\n h(\"circle\", {\r\n cx: \"8\",\r\n cy: \"8\",\r\n r: \"5\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n opacity: \"0.35\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 3A5 5 0 0 1 13 8\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n ]);\r\n }\r\n\r\n return h(\"svg\", common, [\r\n h(\"circle\", {\r\n cx: \"8\",\r\n cy: \"8\",\r\n r: \"5.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 6.8V10.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n h(\"circle\", { cx: \"8\", cy: \"4.9\", r: \"0.9\", fill: \"currentColor\" }),\r\n ]);\r\n};\r\n\r\nconst getOffsetStyle = (\r\n position: SileoPosition,\r\n offset?: SileoOffsetValue | SileoOffsetConfig,\r\n): CSSProperties | undefined => {\r\n if (offset === undefined) return undefined;\r\n\r\n const value =\r\n typeof offset === \"object\"\r\n ? offset\r\n : { top: offset, right: offset, bottom: offset, left: offset };\r\n\r\n const style: CSSProperties = {};\r\n const px = (v: SileoOffsetValue) => (typeof v === \"number\" ? `${v}px` : v);\r\n\r\n if (position.startsWith(\"top\") && value.top !== undefined) style.top = px(value.top);\r\n if (position.startsWith(\"bottom\") && value.bottom !== undefined) {\r\n style.bottom = px(value.bottom);\r\n }\r\n if (position.endsWith(\"left\") && value.left !== undefined) style.left = px(value.left);\r\n if (position.endsWith(\"right\") && value.right !== undefined) {\r\n style.right = px(value.right);\r\n }\r\n\r\n return style;\r\n};\r\n\r\nexport interface SileoPromiseOptions<T = unknown> {\r\n loading: SileoOptions;\r\n success: SileoOptions | ((data: T) => SileoOptions);\r\n error: SileoOptions | ((error: unknown) => SileoOptions);\r\n action?: SileoOptions | ((data: T) => SileoOptions);\r\n position?: SileoPosition;\r\n}\r\n\r\nexport interface SileoToasterProps {\r\n position?: SileoPosition;\r\n offset?: SileoOffsetValue | SileoOffsetConfig;\r\n options?: Partial<SileoOptions>;\r\n theme?: \"light\" | \"dark\" | \"system\";\r\n container?: string | HTMLElement;\r\n grouping?: boolean;\r\n groupThreshold?: number;\r\n ariaLive?: \"off\" | \"polite\" | \"assertive\";\r\n}\r\n\r\nexport const sileo = {\r\n show: (opts: SileoOptions) => createToast(opts, opts.type ?? \"info\"),\r\n success: (opts: SileoOptions) => createToast(opts, \"success\"),\r\n error: (opts: SileoOptions) => createToast(opts, \"error\"),\r\n warning: (opts: SileoOptions) => createToast(opts, \"warning\"),\r\n info: (opts: SileoOptions) => createToast(opts, \"info\"),\r\n action: (opts: SileoOptions) => createToast(opts, \"action\"),\r\n loading: (opts: SileoOptions) =>\r\n createToast({ ...opts, duration: opts.duration ?? null }, \"loading\"),\r\n\r\n promise: async <T,>(\r\n promise: Promise<T> | (() => Promise<T>),\r\n opts: SileoPromiseOptions<T>,\r\n ): Promise<T> => {\r\n const id = createToast(\r\n { ...opts.loading, duration: null, position: opts.position },\r\n \"loading\",\r\n );\r\n try {\r\n const data = await (typeof promise === \"function\" ? promise() : promise);\r\n const next = opts.action\r\n ? typeof opts.action === \"function\"\r\n ? opts.action(data)\r\n : opts.action\r\n : typeof opts.success === \"function\"\r\n ? opts.success(data)\r\n : opts.success;\r\n createToast({ ...next, id }, opts.action ? \"action\" : \"success\");\r\n return data;\r\n } catch (error) {\r\n const next =\r\n typeof opts.error === \"function\" ? opts.error(error) : opts.error;\r\n createToast({ ...next, id }, \"error\");\r\n throw error;\r\n }\r\n },\r\n\r\n configure: (options: Partial<SileoOptions> & { position?: SileoPosition }) => {\r\n if (options.position) {\r\n store.position = options.position;\r\n }\r\n store.options = {\r\n ...store.options,\r\n ...options,\r\n styles: { ...store.options?.styles, ...options.styles },\r\n };\r\n },\r\n\r\n dismiss: dismissToast,\r\n clear: clearToasts,\r\n};\r\n\r\nexport const Toaster = defineComponent({\r\n name: \"SileoToaster\",\r\n props: {\r\n position: String as PropType<SileoPosition | undefined>,\r\n offset: [Number, String, Object] as PropType<\r\n SileoOffsetValue | SileoOffsetConfig\r\n >,\r\n options: Object as PropType<Partial<SileoOptions>>,\r\n theme: String as PropType<\"light\" | \"dark\" | \"system\">,\r\n container: [String, Object] as PropType<string | HTMLElement>,\r\n grouping: { type: Boolean, default: false },\r\n groupThreshold: { type: Number, default: GROUP_THRESHOLD },\r\n ariaLive: {\r\n type: String as PropType<\"off\" | \"polite\" | \"assertive\">,\r\n default: \"polite\",\r\n },\r\n },\r\n setup(props, { slots }) {\r\n const toasts = ref<SileoItem[]>(store.toasts);\r\n const expandedGroups = ref<Record<string, boolean>>({});\r\n const expandedToasts = ref<Record<string, boolean>>({});\r\n const autopilotTimers = new Map<string, ReturnType<typeof setTimeout>[]>();\r\n const activeToastInstanceId = ref<string | undefined>(undefined);\r\n const contentHeights = ref<Record<string, number>>({});\r\n const contentRefs = new Map<string, HTMLElement>();\r\n const contentObservers = new Map<string, ResizeObserver>();\r\n\r\n // Pill inner measurement (drives SVG pill width)\r\n const readyToasts = ref<Record<string, boolean>>({});\r\n const pillWidths = ref<Record<string, number>>({});\r\n const pillInnerRefs = new Map<string, HTMLElement>();\r\n const pillInnerObservers = new Map<string, ResizeObserver>();\r\n\r\n const measureContentHeight = (instanceId: string) => {\r\n const el = contentRefs.get(instanceId);\r\n if (!el) return;\r\n const measured = Math.max(0, el.scrollHeight);\r\n if (contentHeights.value[instanceId] !== measured) {\r\n contentHeights.value[instanceId] = measured;\r\n }\r\n };\r\n\r\n const setContentRef = (instanceId: string, el: HTMLElement | null) => {\r\n if (el) {\r\n contentRefs.set(instanceId, el);\r\n measureContentHeight(instanceId);\r\n if (typeof ResizeObserver === \"function\") {\r\n if (!contentObservers.has(instanceId)) {\r\n const observer = new ResizeObserver(() => {\r\n measureContentHeight(instanceId);\r\n });\r\n contentObservers.set(instanceId, observer);\r\n }\r\n contentObservers.get(instanceId)?.observe(el);\r\n }\r\n return;\r\n }\r\n\r\n const previous = contentRefs.get(instanceId);\r\n const observer = contentObservers.get(instanceId);\r\n if (previous && observer) observer.unobserve(previous);\r\n contentRefs.delete(instanceId);\r\n };\r\n\r\n const clearContentObserver = (instanceId: string) => {\r\n const observer = contentObservers.get(instanceId);\r\n if (observer) {\r\n observer.disconnect();\r\n contentObservers.delete(instanceId);\r\n }\r\n contentRefs.delete(instanceId);\r\n delete contentHeights.value[instanceId];\r\n };\r\n\r\n const measurePillInner = (instanceId: string) => {\r\n const el = pillInnerRefs.get(instanceId);\r\n if (!el) return;\r\n const w = el.scrollWidth;\r\n if (pillWidths.value[instanceId] !== w) {\r\n pillWidths.value[instanceId] = w;\r\n }\r\n };\r\n\r\n const setPillInnerRef = (instanceId: string, el: HTMLElement | null) => {\r\n if (el) {\r\n pillInnerRefs.set(instanceId, el);\r\n measurePillInner(instanceId);\r\n if (typeof ResizeObserver === 'function' && !pillInnerObservers.has(instanceId)) {\r\n const observer = new ResizeObserver(() => measurePillInner(instanceId));\r\n pillInnerObservers.set(instanceId, observer);\r\n observer.observe(el);\r\n }\r\n return;\r\n }\r\n const prev = pillInnerRefs.get(instanceId);\r\n const obs = pillInnerObservers.get(instanceId);\r\n if (prev && obs) obs.unobserve(prev);\r\n pillInnerRefs.delete(instanceId);\r\n };\r\n\r\n const clearPillInnerObserver = (instanceId: string) => {\r\n const obs = pillInnerObservers.get(instanceId);\r\n if (obs) { obs.disconnect(); pillInnerObservers.delete(instanceId); }\r\n pillInnerRefs.delete(instanceId);\r\n delete pillWidths.value[instanceId];\r\n };\r\n\r\n const scheduleReady = (instanceId: string) => {\r\n globalThis.requestAnimationFrame(() => {\r\n if (pillInnerRefs.has(instanceId) || toasts.value.some(t => t.instanceId === instanceId)) {\r\n readyToasts.value[instanceId] = true;\r\n }\r\n });\r\n };\r\n\r\n const setExpanded = (instanceId: string, expanded: boolean) => {\r\n const previous = expandedToasts.value[instanceId];\r\n if (previous === expanded) return;\r\n\r\n expandedToasts.value[instanceId] = expanded;\r\n if (previous === undefined) return;\r\n\r\n const item = toasts.value.find((toast) => toast.instanceId === instanceId);\r\n if (!item) return;\r\n\r\n if (expanded) {\r\n item.hooks?.onExpand?.(toLifecycleContext(item));\r\n } else {\r\n item.hooks?.onCollapse?.(toLifecycleContext(item));\r\n }\r\n };\r\n\r\n const clearAutopilotTimers = (instanceId: string) => {\r\n const timers = autopilotTimers.get(instanceId);\r\n if (!timers) return;\r\n for (const timer of timers) clearTimeout(timer);\r\n autopilotTimers.delete(instanceId);\r\n };\r\n\r\n const scheduleAutopilot = (item: SileoItem) => {\r\n clearAutopilotTimers(item.instanceId);\r\n const config = normalizeAutopilot(item.autopilot);\r\n if (!item.description || !config) {\r\n setExpanded(item.instanceId, false);\r\n return;\r\n }\r\n\r\n if (\r\n activeToastInstanceId.value !== undefined &&\r\n activeToastInstanceId.value !== item.instanceId\r\n ) {\r\n setExpanded(item.instanceId, false);\r\n return;\r\n }\r\n\r\n setExpanded(item.instanceId, false);\r\n const timers: ReturnType<typeof setTimeout>[] = [];\r\n\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, true);\r\n }, config.expand),\r\n );\r\n\r\n const duration = item.duration ?? DEFAULT_TOAST_DURATION;\r\n if (item.duration !== null) {\r\n const maxCollapse = Math.max(\r\n config.expand + 350,\r\n duration - EXIT_DURATION - 150,\r\n );\r\n const collapseAt = Math.min(config.collapse, maxCollapse);\r\n if (collapseAt > config.expand) {\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, false);\r\n }, collapseAt),\r\n );\r\n }\r\n } else if (config.collapse > config.expand) {\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, false);\r\n }, config.collapse),\r\n );\r\n }\r\n\r\n autopilotTimers.set(item.instanceId, timers);\r\n };\r\n\r\n const listener: Listener = (next) => {\r\n toasts.value = next;\r\n };\r\n\r\n onMounted(() => {\r\n store.listeners.add(listener);\r\n if (props.position) store.position = props.position;\r\n if (props.options) {\r\n store.options = {\r\n ...store.options,\r\n ...props.options,\r\n styles: { ...store.options?.styles, ...props.options.styles },\r\n };\r\n }\r\n });\r\n\r\n onBeforeUnmount(() => {\r\n store.listeners.delete(listener);\r\n for (const instanceId of autopilotTimers.keys()) {\r\n clearAutopilotTimers(instanceId);\r\n }\r\n for (const instanceId of contentObservers.keys()) {\r\n clearContentObserver(instanceId);\r\n }\r\n for (const instanceId of pillInnerObservers.keys()) {\r\n clearPillInnerObserver(instanceId);\r\n }\r\n });\r\n\r\n watch(\r\n () => props.position,\r\n (value) => {\r\n if (value) store.position = value;\r\n },\r\n );\r\n\r\n watch(\r\n () => props.options,\r\n (value) => {\r\n if (!value) return;\r\n store.options = {\r\n ...store.options,\r\n ...value,\r\n styles: { ...store.options?.styles, ...value.styles },\r\n };\r\n },\r\n { deep: true },\r\n );\r\n\r\n watch(\r\n () => toasts.value,\r\n (items) => {\r\n const active = new Set(items.map((item) => item.instanceId));\r\n const latest = [...items]\r\n .filter((item) => !item.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n\r\n activeToastInstanceId.value = latest?.instanceId;\r\n\r\n for (const item of items) {\r\n if (expandedToasts.value[item.instanceId] === undefined) {\r\n setExpanded(item.instanceId, false);\r\n }\r\n }\r\n\r\n for (const instanceId of Object.keys(expandedToasts.value)) {\r\n if (!active.has(instanceId)) {\r\n clearAutopilotTimers(instanceId);\r\n delete expandedToasts.value[instanceId];\r\n clearContentObserver(instanceId);\r\n clearPillInnerObserver(instanceId);\r\n delete readyToasts.value[instanceId];\r\n }\r\n }\r\n },\r\n { immediate: true },\r\n );\r\n\r\n watch(\r\n () => activeToastInstanceId.value,\r\n (activeInstanceId) => {\r\n for (const instanceId of Object.keys(expandedToasts.value)) {\r\n if (instanceId !== activeInstanceId) {\r\n clearAutopilotTimers(instanceId);\r\n setExpanded(instanceId, false);\r\n }\r\n }\r\n\r\n if (!activeInstanceId) return;\r\n const item = toasts.value.find((toast) => toast.instanceId === activeInstanceId);\r\n if (item) {\r\n scheduleAutopilot(item);\r\n }\r\n },\r\n );\r\n\r\n const groupedByPosition = computed(() => {\r\n const map = new Map<SileoPosition, SileoItem[]>();\r\n for (const item of toasts.value) {\r\n const pos = item.position ?? store.position;\r\n const list = map.get(pos);\r\n if (list) {\r\n list.push(item);\r\n } else {\r\n map.set(pos, [item]);\r\n }\r\n }\r\n return map;\r\n });\r\n\r\n const renderToast = (item: SileoItem) => {\r\n const r = Math.max(0, item.roundness ?? DEFAULT_ROUNDNESS);\r\n const isExpanded = Boolean(expandedToasts.value[item.instanceId]);\r\n const isReady = Boolean(readyToasts.value[item.instanceId]);\r\n const swipeEnabled = item.swipeToDismiss ?? true;\r\n const pos = item.position ?? store.position;\r\n const align: 'left' | 'center' | 'right' =\r\n pos.endsWith('left') ? 'left'\r\n : pos.endsWith('right') ? 'right'\r\n : 'center';\r\n const expandDir: 'top' | 'bottom' = pos.startsWith('top') ? 'bottom' : 'top';\r\n\r\n const hasDesc = Boolean(item.description || item.button);\r\n const isLoading = item.state === 'loading';\r\n const isOpen = hasDesc && isExpanded && !isLoading;\r\n\r\n const blur = r * BLUR_RATIO;\r\n const rawPillW = pillWidths.value[item.instanceId] ?? 0;\r\n const resolvedPillWidth = Math.max(rawPillW + PILL_PADDING, TOAST_HEIGHT);\r\n const pillH = TOAST_HEIGHT + blur * 3;\r\n\r\n const pillX = align === 'right' ? TOAST_WIDTH - resolvedPillWidth\r\n : align === 'center' ? (TOAST_WIDTH - resolvedPillWidth) / 2\r\n : 0;\r\n\r\n const contentH = contentHeights.value[item.instanceId] ?? 0;\r\n const minExpanded = TOAST_HEIGHT * MIN_EXPAND_RATIO;\r\n const expanded = hasDesc ? Math.max(minExpanded, TOAST_HEIGHT + contentH) : minExpanded;\r\n const expandedContent = Math.max(0, expanded - TOAST_HEIGHT);\r\n const svgH = Math.max(expanded, TOAST_HEIGHT);\r\n\r\n const resolvedTheme = resolveTheme(props.theme);\r\n const fillColor = item.fill ?? (resolvedTheme === 'dark' ? '#f2f2f2' : '#1a1a1a');\r\n const filterId = `sileo-gooey-${item.instanceId.replace(/[^a-z0-9]/gi, '-')}`;\r\n\r\n const rootStyle: CSSProperties & Record<string, string> = {\r\n '--_h': `${isOpen ? expanded : TOAST_HEIGHT}px`,\r\n '--_pw': `${resolvedPillWidth}px`,\r\n '--_px': `${pillX}px`,\r\n '--_ht': `translateY(${isOpen ? (expandDir === 'bottom' ? 3 : -3) : 0}px) scale(${isOpen ? 0.9 : 1})`,\r\n '--_co': isOpen ? '1' : '0',\r\n };\r\n\r\n const swipeState = {\r\n startY: 0,\r\n pointerId: -1,\r\n dragging: false,\r\n };\r\n\r\n const resetSwipe = (el: HTMLElement) => {\r\n el.style.transform = \"\";\r\n el.style.opacity = \"\";\r\n el.style.transition = \"\";\r\n };\r\n\r\n return h(\r\n \"button\",\r\n {\r\n key: item.instanceId,\r\n type: \"button\",\r\n \"data-sileo-toast\": \"true\",\r\n \"data-state\": item.state,\r\n \"data-exiting\": item.exiting ? \"true\" : \"false\",\r\n \"data-expanded\": isExpanded ? \"true\" : \"false\",\r\n \"data-ready\": isReady ? \"true\" : \"false\",\r\n class: item.styles?.toast,\r\n style: rootStyle,\r\n onMouseenter: () => {\r\n activeToastInstanceId.value = item.instanceId;\r\n setExpanded(item.instanceId, true);\r\n },\r\n onMouseleave: () => {\r\n const autopilot = normalizeAutopilot(item.autopilot);\r\n const latest = [...toasts.value]\r\n .filter((toast) => !toast.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n activeToastInstanceId.value = latest?.instanceId;\r\n if (autopilot !== null) setExpanded(item.instanceId, false);\r\n },\r\n onPointerdown: (e: PointerEvent) => {\r\n if (!swipeEnabled || item.state === \"loading\") return;\r\n const target = e.target as HTMLElement;\r\n if (target.closest(\"[data-sileo-button='true'], .sileo-dismiss\")) {\r\n return;\r\n }\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.startY = e.clientY;\r\n swipeState.pointerId = e.pointerId;\r\n swipeState.dragging = true;\r\n el.setPointerCapture(e.pointerId);\r\n },\r\n onPointermove: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n const deltaY = e.clientY - swipeState.startY;\r\n const clamped = Math.max(-22, Math.min(22, deltaY));\r\n el.style.transform = `translateY(${clamped}px)`;\r\n const alpha = 1 - Math.min(0.35, Math.abs(clamped) / 60);\r\n el.style.opacity = String(alpha);\r\n },\r\n onPointerup: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.dragging = false;\r\n const deltaY = e.clientY - swipeState.startY;\r\n if (el.hasPointerCapture(e.pointerId)) {\r\n el.releasePointerCapture(e.pointerId);\r\n }\r\n if (Math.abs(deltaY) > 34) {\r\n dismissByInstance(item.instanceId);\r\n return;\r\n }\r\n el.style.transition = \"transform 150ms ease, opacity 150ms ease\";\r\n resetSwipe(el);\r\n },\r\n onPointercancel: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.dragging = false;\r\n if (el.hasPointerCapture(e.pointerId)) {\r\n el.releasePointerCapture(e.pointerId);\r\n }\r\n el.style.transition = \"transform 150ms ease, opacity 150ms ease\";\r\n resetSwipe(el);\r\n },\r\n },\r\n [\r\n // SVG gooey canvas\r\n h(\"div\", {\r\n \"data-sileo-canvas\": \"true\",\r\n \"data-edge\": expandDir,\r\n style: { filter: `url(#${filterId})` } as CSSProperties,\r\n }, [\r\n h(\"svg\", {\r\n \"data-sileo-svg\": \"true\",\r\n width: String(TOAST_WIDTH),\r\n height: String(svgH),\r\n viewBox: `0 0 ${TOAST_WIDTH} ${svgH}`,\r\n xmlns: \"http://www.w3.org/2000/svg\",\r\n \"aria-hidden\": \"true\",\r\n focusable: \"false\",\r\n }, [\r\n h(\"defs\", {}, [\r\n h(\"filter\", {\r\n id: filterId,\r\n x: \"-20%\",\r\n y: \"-20%\",\r\n width: \"140%\",\r\n height: \"140%\",\r\n \"color-interpolation-filters\": \"sRGB\",\r\n }, [\r\n h(\"feGaussianBlur\", {\r\n in: \"SourceGraphic\",\r\n stdDeviation: String(blur),\r\n result: \"blur\",\r\n }),\r\n h(\"feColorMatrix\", {\r\n in: \"blur\",\r\n mode: \"matrix\",\r\n values: \"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10\",\r\n result: \"goo\",\r\n }),\r\n h(\"feComposite\", {\r\n in: \"SourceGraphic\",\r\n in2: \"goo\",\r\n operator: \"atop\",\r\n }),\r\n ]),\r\n ]),\r\n h(\"rect\", {\r\n \"data-sileo-pill\": \"true\",\r\n rx: String(r),\r\n ry: String(r),\r\n fill: fillColor,\r\n style: {\r\n x: `${pillX}px`,\r\n width: `${resolvedPillWidth}px`,\r\n height: `${isOpen ? pillH : TOAST_HEIGHT}px`,\r\n } as CSSProperties,\r\n }),\r\n h(\"rect\", {\r\n \"data-sileo-body\": \"true\",\r\n width: String(TOAST_WIDTH),\r\n rx: String(r),\r\n ry: String(r),\r\n fill: fillColor,\r\n style: {\r\n y: `${TOAST_HEIGHT}px`,\r\n height: `${isOpen ? expandedContent : 0}px`,\r\n opacity: isOpen ? \"1\" : \"0\",\r\n } as CSSProperties,\r\n }),\r\n ]),\r\n ]),\r\n\r\n // Header\r\n h(\"div\", {\r\n \"data-sileo-header\": \"true\",\r\n \"data-edge\": expandDir,\r\n }, [\r\n h(\"div\", { \"data-sileo-header-stack\": \"true\" }, [\r\n h(\"div\", {\r\n \"data-sileo-header-inner\": \"true\",\r\n ref: (el: Element | ComponentPublicInstance | null) => {\r\n setPillInnerRef(item.instanceId, el as HTMLElement | null);\r\n if (el) scheduleReady(item.instanceId);\r\n },\r\n }, [\r\n h(\"div\", {\r\n \"data-sileo-badge\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.badge,\r\n }, item.icon ?? renderStateIcon(item.state)),\r\n h(\"span\", {\r\n \"data-sileo-title\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.title,\r\n }, item.title),\r\n h(\"button\", {\r\n type: \"button\",\r\n class: \"sileo-dismiss\",\r\n onClick: (e: MouseEvent) => {\r\n e.stopPropagation();\r\n dismissByInstance(item.instanceId);\r\n },\r\n \"aria-label\": \"Dismiss toast\",\r\n }, \"×\"),\r\n ]),\r\n ]),\r\n ]),\r\n\r\n // Content\r\n hasDesc\r\n ? h(\"div\", {\r\n \"data-sileo-content\": \"true\",\r\n \"data-edge\": expandDir,\r\n \"data-visible\": isOpen ? \"true\" : \"false\",\r\n }, [\r\n h(\"div\", {\r\n \"data-sileo-description\": \"true\",\r\n class: item.styles?.description,\r\n ref: (el: Element | ComponentPublicInstance | null) => {\r\n setContentRef(item.instanceId, el as HTMLElement | null);\r\n },\r\n }, [\r\n item.description\r\n ? h(\"span\", {}, item.description)\r\n : null,\r\n item.button\r\n ? h(\"button\", {\r\n type: \"button\",\r\n \"data-sileo-button\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.button,\r\n onClick: (e: MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n item.button?.onClick?.();\r\n },\r\n }, item.button.title)\r\n : null,\r\n ]),\r\n ])\r\n : null,\r\n ],\r\n );\r\n };\r\n\r\n const renderPosition = (position: SileoPosition, items: SileoItem[]) => {\r\n const buckets = new Map<string, SileoItem[]>();\r\n for (const item of items) {\r\n const key = item.groupKey ?? \"__default__\";\r\n const list = buckets.get(key);\r\n if (list) {\r\n list.push(item);\r\n } else {\r\n buckets.set(key, [item]);\r\n }\r\n }\r\n\r\n const children: ReturnType<typeof h>[] = [];\r\n for (const [bucketKey, bucketItems] of buckets) {\r\n const visible = bucketItems.filter((item) => !item.exiting);\r\n const expandKey = `${position}:${bucketKey}`;\r\n const shouldGroup =\r\n props.grouping &&\r\n visible.length > props.groupThreshold &&\r\n !expandedGroups.value[expandKey];\r\n\r\n if (shouldGroup) {\r\n const label =\r\n bucketKey === \"__default__\"\r\n ? `${visible.length} notifications`\r\n : `${visible.length} ${bucketKey} notifications`;\r\n children.push(\r\n h(\r\n \"button\",\r\n {\r\n key: expandKey,\r\n type: \"button\",\r\n \"data-sileo-group\": \"true\",\r\n class: \"sileo-group\",\r\n onMouseenter: () => {\r\n expandedGroups.value[expandKey] = true;\r\n },\r\n onClick: () => {\r\n expandedGroups.value[expandKey] = true;\r\n },\r\n },\r\n label,\r\n ),\r\n );\r\n continue;\r\n }\r\n\r\n for (const item of bucketItems) {\r\n children.push(renderToast(item));\r\n }\r\n }\r\n\r\n return h(\r\n \"section\",\r\n {\r\n key: position,\r\n \"data-sileo-viewport\": \"true\",\r\n \"data-position\": position,\r\n \"data-theme\": resolveTheme(props.theme),\r\n \"aria-live\": props.ariaLive,\r\n \"aria-atomic\": \"true\",\r\n role: \"status\",\r\n style: getOffsetStyle(position, props.offset),\r\n },\r\n children,\r\n );\r\n };\r\n\r\n return () => {\r\n const viewports = Array.from(groupedByPosition.value.entries()).map(\r\n ([position, items]) => renderPosition(position, items),\r\n );\r\n\r\n const portalTarget = props.container ?? \"body\";\r\n\r\n return h(\"div\", { class: \"sileo-root\" }, [\r\n slots.default?.(),\r\n h(Teleport, { to: portalTarget }, viewports),\r\n ]);\r\n };\r\n },\r\n});\r\n"],"names":[],"mappings":";;;AACO,KAAA,UAAA;AACA,UAAA,WAAA;AACP;AACA;AACA;AACA;AACA;AACA;AACO,UAAA,WAAA;AACP;AACA;AACA;AACO,UAAA,cAAA;AACP;AACA;AACA;AACO,UAAA,qBAAA;AACP;AACA;AACA,WAAA,UAAA;AACA;AACO,UAAA,mBAAA;AACP,mBAAA,qBAAA;AACA,qBAAA,qBAAA;AACA,uBAAA,qBAAA;AACA,sBAAA,qBAAA;AACA;AACO,cAAA,eAAA;AACA,KAAA,aAAA,WAAA,eAAA;AACA,UAAA,YAAA;AACP;AACA;AACA,kBAAA,UAAA;AACA,WAAA,UAAA;AACA,eAAA,aAAA;AACA;AACA,WAAA,UAAA;AACA,aAAA,WAAA;AACA;AACA;AACA,0BAAA,cAAA;AACA;AACA,aAAA,WAAA;AACA;AACA,YAAA,mBAAA;AACA;AACO,KAAA,gBAAA;AACA,KAAA,iBAAA,GAAA,OAAA,CAAA,MAAA,sCAAA,gBAAA;;AC9CA,UAAA,mBAAA;AACP,aAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,WAAA,YAAA,wBAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,eAAA,aAAA;AACA;AAWO,cAAA,KAAA;AACP,iBAAA,YAAA;AACA,oBAAA,YAAA;AACA,kBAAA,YAAA;AACA,oBAAA,YAAA;AACA,iBAAA,YAAA;AACA,mBAAA,YAAA;AACA,oBAAA,YAAA;AACA,0BAAA,OAAA,aAAA,OAAA,YAAA,mBAAA,QAAA,OAAA;AACA,yBAAA,OAAA,CAAA,YAAA;AACA,mBAAA,aAAA;AACA;AACA;AACA,uBAAA,aAAA;AACA;AACO,cAAA,OAAA,EAAuB,GAAa,iBAAiB,GAAa,CAAA,gBAAA;AACzE,cAAA,QAAA,CAAA,aAAA;AACA,YAAA,QAAA,CAAA,gBAAA,GAAA,iBAAA;AACA,aAAA,QAAA,CAAA,OAAA,CAAA,YAAA;AACA,WAAA,QAAA;AACA,eAAA,QAAA,UAAA,WAAA;AACA;AACA,cAAA,kBAAA;AACA;AACA;AACA;AACA,cAAA,iBAAA;AACA;AACA;AACA;AACA,cAAA,QAAA;AACA;AACA;AACA,UAAU,GAAa,CAAA,KAAA,CAAO,GAAa,CAAA,YAAA,EAAe,GAAa,CAAA,eAAA;AACvE;AACA,gBAAgB,GAAa,CAAA,qBAAA,EAAwB,GAAa,CAAA,qBAAA,cAAoC,GAAa,uBAAuB,GAAa,CAAA,gBAAA;AACvJ,cAAA,QAAA,CAAA,aAAA;AACA,YAAA,QAAA,CAAA,gBAAA,GAAA,iBAAA;AACA,aAAA,QAAA,CAAA,OAAA,CAAA,YAAA;AACA,WAAA,QAAA;AACA,eAAA,QAAA,UAAA,WAAA;AACA;AACA,cAAA,kBAAA;AACA;AACA;AACA;AACA,cAAA,iBAAA;AACA;AACA;AACA;AACA,cAAA,QAAA;AACA;AACA;AACA,MAAA,QAAA;AACA;AACA;AACA;AACA,uBAAuB,GAAa,CAAA,uBAAA;;;;"}
1
+ {"version":3,"file":"index.d.mts","sources":["../src/types.ts","../src/toast.ts"],"sourcesContent":["import type { VNodeChild } from \"vue\";\r\n\r\nexport type SileoState =\r\n\t| \"success\"\r\n\t| \"loading\"\r\n\t| \"error\"\r\n\t| \"warning\"\r\n\t| \"info\"\r\n\t| \"action\";\r\n\r\nexport interface SileoStyles {\r\n\ttitle?: string;\r\n\tdescription?: string;\r\n\tbadge?: string;\r\n\tbutton?: string;\r\n\ttoast?: string;\r\n}\r\n\r\nexport interface SileoButton {\r\n\ttitle: string;\r\n\tonClick?: () => void;\r\n}\r\n\r\nexport interface SileoAutopilot {\r\n\texpand?: number;\r\n\tcollapse?: number;\r\n}\r\n\r\nexport interface SileoLifecycleContext {\r\n\tid: string;\r\n\tinstanceId: string;\r\n\tstate: SileoState;\r\n}\r\n\r\nexport interface SileoLifecycleHooks {\r\n\tonShow?: (ctx: SileoLifecycleContext) => void;\r\n\tonExpand?: (ctx: SileoLifecycleContext) => void;\r\n\tonCollapse?: (ctx: SileoLifecycleContext) => void;\r\n\tonDismiss?: (ctx: SileoLifecycleContext) => void;\r\n}\r\n\r\nexport const SILEO_POSITIONS = [\r\n\t\"top-left\",\r\n\t\"top-center\",\r\n\t\"top-right\",\r\n\t\"bottom-left\",\r\n\t\"bottom-center\",\r\n\t\"bottom-right\",\r\n] as const;\r\n\r\nexport type SileoPosition = (typeof SILEO_POSITIONS)[number];\r\n\r\nexport interface SileoOptions {\r\n\tid?: string;\r\n\ttitle?: string;\r\n\tdescription?: VNodeChild | string;\r\n\ttype?: SileoState;\r\n\tposition?: SileoPosition;\r\n\tduration?: number | null;\r\n\ticon?: VNodeChild | null;\r\n\tstyles?: SileoStyles;\r\n\tfill?: string;\r\n\troundness?: number;\r\n\tautopilot?: boolean | SileoAutopilot;\r\n\tswipeToDismiss?: boolean;\r\n\tbutton?: SileoButton;\r\n\tgroupKey?: string;\r\n\thooks?: SileoLifecycleHooks;\r\n}\r\n\r\nexport type SileoOffsetValue = number | string;\r\nexport type SileoOffsetConfig = Partial<\r\n\tRecord<\"top\" | \"right\" | \"bottom\" | \"left\", SileoOffsetValue>\r\n>;\r\n","import {\r\n Teleport,\r\n computed,\r\n defineComponent,\r\n h,\r\n onBeforeUnmount,\r\n onMounted,\r\n ref,\r\n watch,\r\n type ComponentPublicInstance,\r\n type CSSProperties,\r\n type PropType,\r\n} from \"vue\";\r\nimport {\r\n AUTOPILOT_COLLAPSE_DELAY,\r\n AUTOPILOT_EXPAND_DELAY,\r\n BLUR_RATIO,\r\n DEFAULT_ROUNDNESS,\r\n DEFAULT_TOAST_DURATION,\r\n EXIT_DURATION,\r\n GROUP_THRESHOLD,\r\n MIN_EXPAND_RATIO,\r\n PILL_PADDING,\r\n TOAST_HEIGHT,\r\n TOAST_WIDTH,\r\n} from \"./constants\";\r\nimport type {\r\n SileoLifecycleContext,\r\n SileoOffsetConfig,\r\n SileoOffsetValue,\r\n SileoOptions,\r\n SileoPosition,\r\n SileoState,\r\n} from \"./types\";\r\n\r\ninterface SileoItem extends Omit<SileoOptions, \"type\"> {\r\n id: string;\r\n instanceId: string;\r\n state: SileoState;\r\n createdAt: number;\r\n exiting?: boolean;\r\n}\r\n\r\ntype AutopilotConfig = {\r\n expand: number;\r\n collapse: number;\r\n};\r\n\r\ntype Listener = (toasts: SileoItem[]) => void;\r\n\r\nconst store = {\r\n toasts: [] as SileoItem[],\r\n listeners: new Set<Listener>(),\r\n position: \"top-right\" as SileoPosition,\r\n options: undefined as Partial<SileoOptions> | undefined,\r\n\r\n emit() {\r\n for (const listener of this.listeners) listener(this.toasts);\r\n },\r\n\r\n update(updater: (prev: SileoItem[]) => SileoItem[]) {\r\n this.toasts = updater(this.toasts);\r\n this.emit();\r\n },\r\n};\r\n\r\nconst dismissalTimers = new Map<string, ReturnType<typeof setTimeout>>();\r\nlet sequence = 0;\r\n\r\nconst nextId = () => `sileo-${++sequence}-${Date.now().toString(36)}`;\r\nconst nextInstanceId = () => `instance-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;\r\n\r\nconst clearTimer = (instanceId: string) => {\r\n const timer = dismissalTimers.get(instanceId);\r\n if (timer !== undefined) {\r\n clearTimeout(timer);\r\n dismissalTimers.delete(instanceId);\r\n }\r\n};\r\n\r\nconst removeByInstance = (instanceId: string) => {\r\n clearTimer(instanceId);\r\n store.update((prev) => prev.filter((item) => item.instanceId !== instanceId));\r\n};\r\n\r\nconst dismissByInstance = (instanceId: string) => {\r\n const target = store.toasts.find(\r\n (item) => item.instanceId === instanceId && !item.exiting,\r\n );\r\n if (!target) return;\r\n\r\n clearTimer(instanceId);\r\n target.hooks?.onDismiss?.(toLifecycleContext(target));\r\n store.update((prev) =>\r\n prev.map((item) =>\r\n item.instanceId === instanceId ? { ...item, exiting: true } : item,\r\n ),\r\n );\r\n\r\n globalThis.setTimeout(() => {\r\n removeByInstance(instanceId);\r\n }, EXIT_DURATION);\r\n};\r\n\r\nconst scheduleDismiss = (item: SileoItem) => {\r\n clearTimer(item.instanceId);\r\n if (item.duration === null) return;\r\n const duration = item.duration ?? DEFAULT_TOAST_DURATION;\r\n if (duration <= 0) return;\r\n\r\n const timer = globalThis.setTimeout(() => {\r\n dismissByInstance(item.instanceId);\r\n }, duration);\r\n\r\n dismissalTimers.set(item.instanceId, timer);\r\n};\r\n\r\nconst mergeOptions = (options: SileoOptions): SileoOptions => ({\r\n ...store.options,\r\n ...options,\r\n styles: { ...store.options?.styles, ...options.styles },\r\n});\r\n\r\nconst buildItem = (options: SileoOptions, state: SileoState): SileoItem => ({\r\n id: options.id ?? nextId(),\r\n instanceId: nextInstanceId(),\r\n state,\r\n title: options.title,\r\n description: options.description,\r\n position: options.position ?? store.position,\r\n duration: options.duration,\r\n icon: options.icon,\r\n styles: options.styles,\r\n fill: options.fill,\r\n roundness: options.roundness,\r\n autopilot: options.autopilot,\r\n swipeToDismiss: options.swipeToDismiss,\r\n button: options.button,\r\n groupKey: options.groupKey,\r\n hooks: options.hooks,\r\n createdAt: Date.now(),\r\n});\r\n\r\nconst createToast = (raw: SileoOptions, forcedState?: SileoState) => {\r\n const merged = mergeOptions(raw);\r\n const state = forcedState ?? merged.type ?? \"info\";\r\n const hasCustomId = Boolean(merged.id);\r\n\r\n if (hasCustomId) {\r\n const existing = store.toasts.find(\r\n (item) => item.id === merged.id && !item.exiting,\r\n );\r\n if (existing) {\r\n const replacement = buildItem(merged, state);\r\n replacement.id = existing.id;\r\n store.update((prev) =>\r\n prev.map((item) =>\r\n item.instanceId === existing.instanceId ? replacement : item,\r\n ),\r\n );\r\n clearTimer(existing.instanceId);\r\n scheduleDismiss(replacement);\r\n replacement.hooks?.onShow?.(toLifecycleContext(replacement));\r\n return existing.id;\r\n }\r\n }\r\n\r\n const item = buildItem(merged, state);\r\n store.update((prev) => [...prev, item]);\r\n scheduleDismiss(item);\r\n item.hooks?.onShow?.(toLifecycleContext(item));\r\n return item.id;\r\n};\r\n\r\nconst dismissToast = (id?: string) => {\r\n if (!id) {\r\n for (const item of store.toasts) dismissByInstance(item.instanceId);\r\n return;\r\n }\r\n\r\n const active = store.toasts\r\n .filter((item) => item.id === id && !item.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n\r\n if (!active) return;\r\n dismissByInstance(active.instanceId);\r\n};\r\n\r\nconst clearToasts = (position?: SileoPosition) => {\r\n const removed = position\r\n ? store.toasts.filter((item) => item.position === position)\r\n : [...store.toasts];\r\n\r\n for (const item of removed) clearTimer(item.instanceId);\r\n\r\n store.update((prev) =>\r\n position ? prev.filter((item) => item.position !== position) : [],\r\n );\r\n};\r\n\r\nconst toLifecycleContext = (item: SileoItem): SileoLifecycleContext => ({\r\n id: item.id,\r\n instanceId: item.instanceId,\r\n state: item.state,\r\n});\r\n\r\nconst resolveTheme = (theme: \"light\" | \"dark\" | \"system\" | undefined) => {\r\n if (theme === \"light\" || theme === \"dark\") return theme;\r\n if (typeof window === \"undefined\") return \"light\";\r\n if (typeof window.matchMedia !== \"function\") return \"light\";\r\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches\r\n ? \"dark\"\r\n : \"light\";\r\n};\r\n\r\nconst normalizeAutopilot = (\r\n value: SileoOptions[\"autopilot\"],\r\n): AutopilotConfig | null => {\r\n if (value === false) return null;\r\n if (value === true || value === undefined) {\r\n return {\r\n expand: AUTOPILOT_EXPAND_DELAY,\r\n collapse: AUTOPILOT_COLLAPSE_DELAY,\r\n };\r\n }\r\n\r\n const expand = Number.isFinite(value.expand)\r\n ? Math.max(0, Number(value.expand))\r\n : AUTOPILOT_EXPAND_DELAY;\r\n const collapse = Number.isFinite(value.collapse)\r\n ? Math.max(expand + 200, Number(value.collapse))\r\n : AUTOPILOT_COLLAPSE_DELAY;\r\n\r\n return { expand, collapse };\r\n};\r\n\r\nconst renderStateIcon = (state: SileoState) => {\r\n const common = {\r\n viewBox: \"0 0 16 16\",\r\n width: \"14\",\r\n height: \"14\",\r\n \"aria-hidden\": \"true\",\r\n focusable: \"false\",\r\n } as const;\r\n\r\n if (state === \"success\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M3.2 8.4L6.5 11.4L12.8 4.9\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"error\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M5 5L11 11M11 5L5 11\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"warning\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M8 3.2L13 12.5H3L8 3.2Z\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.4\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 6.2V9\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.5\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n h(\"circle\", { cx: \"8\", cy: \"11\", r: \"0.9\", fill: \"currentColor\" }),\r\n ]);\r\n }\r\n if (state === \"action\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M3.5 8H12.5M9.2 4.8L12.4 8L9.2 11.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"loading\") {\r\n return h(\"svg\", { ...common, \"data-sileo-icon\": \"spin\" }, [\r\n h(\"circle\", {\r\n cx: \"8\",\r\n cy: \"8\",\r\n r: \"5\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n opacity: \"0.35\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 3A5 5 0 0 1 13 8\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n ]);\r\n }\r\n\r\n return h(\"svg\", common, [\r\n h(\"circle\", {\r\n cx: \"8\",\r\n cy: \"8\",\r\n r: \"5.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 6.8V10.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n h(\"circle\", { cx: \"8\", cy: \"4.9\", r: \"0.9\", fill: \"currentColor\" }),\r\n ]);\r\n};\r\n\r\nconst getOffsetStyle = (\r\n position: SileoPosition,\r\n offset?: SileoOffsetValue | SileoOffsetConfig,\r\n): CSSProperties | undefined => {\r\n if (offset === undefined) return undefined;\r\n\r\n const value =\r\n typeof offset === \"object\"\r\n ? offset\r\n : { top: offset, right: offset, bottom: offset, left: offset };\r\n\r\n const style: CSSProperties = {};\r\n const px = (v: SileoOffsetValue) => (typeof v === \"number\" ? `${v}px` : v);\r\n\r\n if (position.startsWith(\"top\") && value.top !== undefined) style.top = px(value.top);\r\n if (position.startsWith(\"bottom\") && value.bottom !== undefined) {\r\n style.bottom = px(value.bottom);\r\n }\r\n if (position.endsWith(\"left\") && value.left !== undefined) style.left = px(value.left);\r\n if (position.endsWith(\"right\") && value.right !== undefined) {\r\n style.right = px(value.right);\r\n }\r\n\r\n return style;\r\n};\r\n\r\nexport interface SileoPromiseOptions<T = unknown> {\r\n loading: SileoOptions;\r\n success: SileoOptions | ((data: T) => SileoOptions);\r\n error: SileoOptions | ((error: unknown) => SileoOptions);\r\n action?: SileoOptions | ((data: T) => SileoOptions);\r\n position?: SileoPosition;\r\n}\r\n\r\nexport interface SileoToasterProps {\r\n position?: SileoPosition;\r\n offset?: SileoOffsetValue | SileoOffsetConfig;\r\n options?: Partial<SileoOptions>;\r\n theme?: \"light\" | \"dark\" | \"system\";\r\n container?: string | HTMLElement;\r\n grouping?: boolean;\r\n groupThreshold?: number;\r\n ariaLive?: \"off\" | \"polite\" | \"assertive\";\r\n}\r\n\r\nexport const sileo = {\r\n show: (opts: SileoOptions) => createToast(opts, opts.type ?? \"info\"),\r\n success: (opts: SileoOptions) => createToast(opts, \"success\"),\r\n error: (opts: SileoOptions) => createToast(opts, \"error\"),\r\n warning: (opts: SileoOptions) => createToast(opts, \"warning\"),\r\n info: (opts: SileoOptions) => createToast(opts, \"info\"),\r\n action: (opts: SileoOptions) => createToast(opts, \"action\"),\r\n loading: (opts: SileoOptions) =>\r\n createToast({ ...opts, duration: opts.duration ?? null }, \"loading\"),\r\n\r\n promise: async <T,>(\r\n promise: Promise<T> | (() => Promise<T>),\r\n opts: SileoPromiseOptions<T>,\r\n ): Promise<T> => {\r\n const id = createToast(\r\n { ...opts.loading, duration: null, position: opts.position },\r\n \"loading\",\r\n );\r\n try {\r\n const data = await (typeof promise === \"function\" ? promise() : promise);\r\n const next = opts.action\r\n ? typeof opts.action === \"function\"\r\n ? opts.action(data)\r\n : opts.action\r\n : typeof opts.success === \"function\"\r\n ? opts.success(data)\r\n : opts.success;\r\n createToast({ ...next, id }, opts.action ? \"action\" : \"success\");\r\n return data;\r\n } catch (error) {\r\n const next =\r\n typeof opts.error === \"function\" ? opts.error(error) : opts.error;\r\n createToast({ ...next, id }, \"error\");\r\n throw error;\r\n }\r\n },\r\n\r\n configure: (options: Partial<SileoOptions> & { position?: SileoPosition }) => {\r\n if (options.position) {\r\n store.position = options.position;\r\n }\r\n store.options = {\r\n ...store.options,\r\n ...options,\r\n styles: { ...store.options?.styles, ...options.styles },\r\n };\r\n },\r\n\r\n dismiss: dismissToast,\r\n clear: clearToasts,\r\n};\r\n\r\nexport const Toaster = defineComponent({\r\n name: \"SileoToaster\",\r\n props: {\r\n position: String as PropType<SileoPosition | undefined>,\r\n offset: [Number, String, Object] as PropType<\r\n SileoOffsetValue | SileoOffsetConfig\r\n >,\r\n options: Object as PropType<Partial<SileoOptions>>,\r\n theme: String as PropType<\"light\" | \"dark\" | \"system\">,\r\n container: [String, Object] as PropType<string | HTMLElement>,\r\n grouping: { type: Boolean, default: false },\r\n groupThreshold: { type: Number, default: GROUP_THRESHOLD },\r\n ariaLive: {\r\n type: String as PropType<\"off\" | \"polite\" | \"assertive\">,\r\n default: \"polite\",\r\n },\r\n },\r\n setup(props, { slots }) {\r\n const toasts = ref<SileoItem[]>(store.toasts);\r\n const expandedGroups = ref<Record<string, boolean>>({});\r\n const expandedToasts = ref<Record<string, boolean>>({});\r\n const autopilotTimers = new Map<string, ReturnType<typeof setTimeout>[]>();\r\n const activeToastInstanceId = ref<string | undefined>(undefined);\r\n const contentHeights = ref<Record<string, number>>({});\r\n const contentRefs = new Map<string, HTMLElement>();\r\n const contentObservers = new Map<string, ResizeObserver>();\r\n\r\n // Pill inner measurement (drives SVG pill width)\r\n const readyToasts = ref<Record<string, boolean>>({});\r\n const pillWidths = ref<Record<string, number>>({});\r\n const pillInnerRefs = new Map<string, HTMLElement>();\r\n const pillInnerObservers = new Map<string, ResizeObserver>();\r\n\r\n const measureContentHeight = (instanceId: string) => {\r\n const el = contentRefs.get(instanceId);\r\n if (!el) return;\r\n const measured = Math.max(0, el.scrollHeight);\r\n if (contentHeights.value[instanceId] !== measured) {\r\n contentHeights.value[instanceId] = measured;\r\n }\r\n };\r\n\r\n const setContentRef = (instanceId: string, el: HTMLElement | null) => {\r\n if (el) {\r\n contentRefs.set(instanceId, el);\r\n measureContentHeight(instanceId);\r\n if (typeof ResizeObserver === \"function\") {\r\n if (!contentObservers.has(instanceId)) {\r\n const observer = new ResizeObserver(() => {\r\n measureContentHeight(instanceId);\r\n });\r\n contentObservers.set(instanceId, observer);\r\n }\r\n contentObservers.get(instanceId)?.observe(el);\r\n }\r\n return;\r\n }\r\n\r\n const previous = contentRefs.get(instanceId);\r\n const observer = contentObservers.get(instanceId);\r\n if (previous && observer) observer.unobserve(previous);\r\n contentRefs.delete(instanceId);\r\n };\r\n\r\n const clearContentObserver = (instanceId: string) => {\r\n const observer = contentObservers.get(instanceId);\r\n if (observer) {\r\n observer.disconnect();\r\n contentObservers.delete(instanceId);\r\n }\r\n contentRefs.delete(instanceId);\r\n delete contentHeights.value[instanceId];\r\n };\r\n\r\n const measurePillInner = (instanceId: string) => {\r\n const el = pillInnerRefs.get(instanceId);\r\n if (!el) return;\r\n const header = el.closest('[data-sileo-header]') as HTMLElement | null;\r\n const hPad = header\r\n ? parseFloat(getComputedStyle(header).paddingLeft) + parseFloat(getComputedStyle(header).paddingRight)\r\n : 16;\r\n const w = el.scrollWidth + hPad;\r\n if (pillWidths.value[instanceId] !== w) {\r\n pillWidths.value[instanceId] = w;\r\n }\r\n };\r\n\r\n const setPillInnerRef = (instanceId: string, el: HTMLElement | null) => {\r\n if (el) {\r\n pillInnerRefs.set(instanceId, el);\r\n measurePillInner(instanceId);\r\n if (typeof ResizeObserver === 'function' && !pillInnerObservers.has(instanceId)) {\r\n const observer = new ResizeObserver(() => measurePillInner(instanceId));\r\n pillInnerObservers.set(instanceId, observer);\r\n observer.observe(el);\r\n }\r\n return;\r\n }\r\n const prev = pillInnerRefs.get(instanceId);\r\n const obs = pillInnerObservers.get(instanceId);\r\n if (prev && obs) obs.unobserve(prev);\r\n pillInnerRefs.delete(instanceId);\r\n };\r\n\r\n const clearPillInnerObserver = (instanceId: string) => {\r\n const obs = pillInnerObservers.get(instanceId);\r\n if (obs) { obs.disconnect(); pillInnerObservers.delete(instanceId); }\r\n pillInnerRefs.delete(instanceId);\r\n delete pillWidths.value[instanceId];\r\n };\r\n\r\n const scheduleReady = (instanceId: string) => {\r\n globalThis.requestAnimationFrame(() => {\r\n if (pillInnerRefs.has(instanceId) && toasts.value.some(t => t.instanceId === instanceId)) {\r\n readyToasts.value[instanceId] = true;\r\n }\r\n });\r\n };\r\n\r\n const setExpanded = (instanceId: string, expanded: boolean) => {\r\n const previous = expandedToasts.value[instanceId];\r\n if (previous === expanded) return;\r\n\r\n expandedToasts.value[instanceId] = expanded;\r\n if (previous === undefined) return;\r\n\r\n const item = toasts.value.find((toast) => toast.instanceId === instanceId);\r\n if (!item) return;\r\n\r\n if (expanded) {\r\n item.hooks?.onExpand?.(toLifecycleContext(item));\r\n } else {\r\n item.hooks?.onCollapse?.(toLifecycleContext(item));\r\n }\r\n };\r\n\r\n const clearAutopilotTimers = (instanceId: string) => {\r\n const timers = autopilotTimers.get(instanceId);\r\n if (!timers) return;\r\n for (const timer of timers) clearTimeout(timer);\r\n autopilotTimers.delete(instanceId);\r\n };\r\n\r\n const scheduleAutopilot = (item: SileoItem) => {\r\n clearAutopilotTimers(item.instanceId);\r\n const config = normalizeAutopilot(item.autopilot);\r\n if (!item.description || !config) {\r\n setExpanded(item.instanceId, false);\r\n return;\r\n }\r\n\r\n if (\r\n activeToastInstanceId.value !== undefined &&\r\n activeToastInstanceId.value !== item.instanceId\r\n ) {\r\n setExpanded(item.instanceId, false);\r\n return;\r\n }\r\n\r\n setExpanded(item.instanceId, false);\r\n const timers: ReturnType<typeof setTimeout>[] = [];\r\n\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, true);\r\n }, config.expand),\r\n );\r\n\r\n const duration = item.duration ?? DEFAULT_TOAST_DURATION;\r\n if (item.duration !== null) {\r\n const maxCollapse = Math.max(\r\n config.expand + 350,\r\n duration - EXIT_DURATION - 150,\r\n );\r\n const collapseAt = Math.min(config.collapse, maxCollapse);\r\n if (collapseAt > config.expand) {\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, false);\r\n }, collapseAt),\r\n );\r\n }\r\n } else if (config.collapse > config.expand) {\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, false);\r\n }, config.collapse),\r\n );\r\n }\r\n\r\n autopilotTimers.set(item.instanceId, timers);\r\n };\r\n\r\n const listener: Listener = (next) => {\r\n toasts.value = next;\r\n };\r\n\r\n onMounted(() => {\r\n store.listeners.add(listener);\r\n if (props.position) store.position = props.position;\r\n if (props.options) {\r\n store.options = {\r\n ...store.options,\r\n ...props.options,\r\n styles: { ...store.options?.styles, ...props.options.styles },\r\n };\r\n }\r\n });\r\n\r\n onBeforeUnmount(() => {\r\n store.listeners.delete(listener);\r\n for (const instanceId of autopilotTimers.keys()) {\r\n clearAutopilotTimers(instanceId);\r\n }\r\n for (const instanceId of contentObservers.keys()) {\r\n clearContentObserver(instanceId);\r\n }\r\n for (const instanceId of pillInnerObservers.keys()) {\r\n clearPillInnerObserver(instanceId);\r\n }\r\n });\r\n\r\n watch(\r\n () => props.position,\r\n (value) => {\r\n if (value) store.position = value;\r\n },\r\n );\r\n\r\n watch(\r\n () => props.options,\r\n (value) => {\r\n if (!value) return;\r\n store.options = {\r\n ...store.options,\r\n ...value,\r\n styles: { ...store.options?.styles, ...value.styles },\r\n };\r\n },\r\n { deep: true },\r\n );\r\n\r\n watch(\r\n () => toasts.value,\r\n (items) => {\r\n const active = new Set(items.map((item) => item.instanceId));\r\n const latest = [...items]\r\n .filter((item) => !item.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n\r\n activeToastInstanceId.value = latest?.instanceId;\r\n\r\n for (const item of items) {\r\n if (expandedToasts.value[item.instanceId] === undefined) {\r\n setExpanded(item.instanceId, false);\r\n }\r\n }\r\n\r\n for (const instanceId of Object.keys(expandedToasts.value)) {\r\n if (!active.has(instanceId)) {\r\n clearAutopilotTimers(instanceId);\r\n delete expandedToasts.value[instanceId];\r\n clearContentObserver(instanceId);\r\n clearPillInnerObserver(instanceId);\r\n delete readyToasts.value[instanceId];\r\n }\r\n }\r\n },\r\n { immediate: true },\r\n );\r\n\r\n watch(\r\n () => activeToastInstanceId.value,\r\n (activeInstanceId) => {\r\n for (const instanceId of Object.keys(expandedToasts.value)) {\r\n if (instanceId !== activeInstanceId) {\r\n clearAutopilotTimers(instanceId);\r\n setExpanded(instanceId, false);\r\n }\r\n }\r\n\r\n if (!activeInstanceId) return;\r\n const item = toasts.value.find((toast) => toast.instanceId === activeInstanceId);\r\n if (item) {\r\n scheduleAutopilot(item);\r\n }\r\n },\r\n );\r\n\r\n const groupedByPosition = computed(() => {\r\n const map = new Map<SileoPosition, SileoItem[]>();\r\n for (const item of toasts.value) {\r\n const pos = item.position ?? store.position;\r\n const list = map.get(pos);\r\n if (list) {\r\n list.push(item);\r\n } else {\r\n map.set(pos, [item]);\r\n }\r\n }\r\n return map;\r\n });\r\n\r\n const renderToast = (item: SileoItem) => {\r\n const r = Math.max(0, item.roundness ?? DEFAULT_ROUNDNESS);\r\n const isExpanded = Boolean(expandedToasts.value[item.instanceId]);\r\n const isReady = Boolean(readyToasts.value[item.instanceId]);\r\n const swipeEnabled = item.swipeToDismiss ?? true;\r\n const pos = item.position ?? store.position;\r\n const align: 'left' | 'center' | 'right' =\r\n pos.endsWith('left') ? 'left'\r\n : pos.endsWith('right') ? 'right'\r\n : 'center';\r\n const expandDir: 'top' | 'bottom' = pos.startsWith('top') ? 'bottom' : 'top';\r\n\r\n const hasDesc = Boolean(item.description || item.button);\r\n const isLoading = item.state === 'loading';\r\n const isOpen = hasDesc && isExpanded && !isLoading;\r\n\r\n const blur = r * BLUR_RATIO;\r\n const rawPillW = pillWidths.value[item.instanceId] ?? 0;\r\n const resolvedPillWidth = Math.max(rawPillW + PILL_PADDING, TOAST_HEIGHT);\r\n const pillH = TOAST_HEIGHT + blur * 3;\r\n\r\n const pillX = align === 'right' ? TOAST_WIDTH - resolvedPillWidth\r\n : align === 'center' ? (TOAST_WIDTH - resolvedPillWidth) / 2\r\n : 0;\r\n\r\n const contentH = contentHeights.value[item.instanceId] ?? 0;\r\n const minExpanded = TOAST_HEIGHT * MIN_EXPAND_RATIO;\r\n const expanded = hasDesc ? Math.max(minExpanded, TOAST_HEIGHT + contentH) : minExpanded;\r\n const expandedContent = Math.max(0, expanded - TOAST_HEIGHT);\r\n const svgH = Math.max(expanded, TOAST_HEIGHT);\r\n\r\n const resolvedTheme = resolveTheme(props.theme);\r\n const fillColor = item.fill ?? (resolvedTheme === 'dark' ? '#1a1a1a' : '#f2f2f2');\r\n const filterId = `sileo-gooey-${item.instanceId.replace(/[^a-z0-9]/gi, '-')}`;\r\n\r\n const rootStyle: CSSProperties & Record<string, string> = {\r\n '--_h': `${isOpen ? expanded : TOAST_HEIGHT}px`,\r\n '--_pw': `${resolvedPillWidth}px`,\r\n '--_px': `${pillX}px`,\r\n '--_ht': `translateY(${isOpen ? (expandDir === 'bottom' ? 3 : -3) : 0}px) scale(${isOpen ? 0.9 : 1})`,\r\n '--_co': isOpen ? '1' : '0',\r\n };\r\n\r\n const swipeState = {\r\n startY: 0,\r\n pointerId: -1,\r\n dragging: false,\r\n };\r\n\r\n const resetSwipe = (el: HTMLElement) => {\r\n el.style.transform = \"\";\r\n el.style.opacity = \"\";\r\n el.style.transition = \"\";\r\n };\r\n\r\n return h(\r\n \"button\",\r\n {\r\n key: item.instanceId,\r\n type: \"button\",\r\n \"data-sileo-toast\": \"true\",\r\n \"data-state\": item.state,\r\n \"data-exiting\": item.exiting ? \"true\" : \"false\",\r\n \"data-expanded\": isExpanded ? \"true\" : \"false\",\r\n \"data-ready\": isReady ? \"true\" : \"false\",\r\n class: item.styles?.toast,\r\n style: rootStyle,\r\n onMouseenter: () => {\r\n activeToastInstanceId.value = item.instanceId;\r\n setExpanded(item.instanceId, true);\r\n },\r\n onMouseleave: () => {\r\n const autopilot = normalizeAutopilot(item.autopilot);\r\n const latest = [...toasts.value]\r\n .filter((toast) => !toast.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n activeToastInstanceId.value = latest?.instanceId;\r\n if (autopilot !== null) setExpanded(item.instanceId, false);\r\n },\r\n onPointerdown: (e: PointerEvent) => {\r\n if (!swipeEnabled || item.state === \"loading\") return;\r\n const target = e.target as HTMLElement;\r\n if (target.closest(\"[data-sileo-button='true'], .sileo-dismiss\")) {\r\n return;\r\n }\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.startY = e.clientY;\r\n swipeState.pointerId = e.pointerId;\r\n swipeState.dragging = true;\r\n el.setPointerCapture(e.pointerId);\r\n },\r\n onPointermove: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n const deltaY = e.clientY - swipeState.startY;\r\n const clamped = Math.max(-22, Math.min(22, deltaY));\r\n el.style.transform = `translateY(${clamped}px)`;\r\n const alpha = 1 - Math.min(0.35, Math.abs(clamped) / 60);\r\n el.style.opacity = String(alpha);\r\n },\r\n onPointerup: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.dragging = false;\r\n const deltaY = e.clientY - swipeState.startY;\r\n if (el.hasPointerCapture(e.pointerId)) {\r\n el.releasePointerCapture(e.pointerId);\r\n }\r\n if (Math.abs(deltaY) > 34) {\r\n dismissByInstance(item.instanceId);\r\n return;\r\n }\r\n el.style.transition = \"transform 150ms ease, opacity 150ms ease\";\r\n resetSwipe(el);\r\n },\r\n onPointercancel: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.dragging = false;\r\n if (el.hasPointerCapture(e.pointerId)) {\r\n el.releasePointerCapture(e.pointerId);\r\n }\r\n el.style.transition = \"transform 150ms ease, opacity 150ms ease\";\r\n resetSwipe(el);\r\n },\r\n },\r\n [\r\n // SVG gooey canvas\r\n h(\"div\", {\r\n \"data-sileo-canvas\": \"true\",\r\n \"data-edge\": expandDir,\r\n style: { filter: `url(#${filterId})` } as CSSProperties,\r\n }, [\r\n h(\"svg\", {\r\n \"data-sileo-svg\": \"true\",\r\n width: String(TOAST_WIDTH),\r\n height: String(svgH),\r\n viewBox: `0 0 ${TOAST_WIDTH} ${svgH}`,\r\n xmlns: \"http://www.w3.org/2000/svg\",\r\n \"aria-hidden\": \"true\",\r\n focusable: \"false\",\r\n }, [\r\n h(\"defs\", {}, [\r\n h(\"filter\", {\r\n id: filterId,\r\n x: \"-20%\",\r\n y: \"-20%\",\r\n width: \"140%\",\r\n height: \"140%\",\r\n \"color-interpolation-filters\": \"sRGB\",\r\n }, [\r\n h(\"feGaussianBlur\", {\r\n in: \"SourceGraphic\",\r\n stdDeviation: String(blur),\r\n result: \"blur\",\r\n }),\r\n h(\"feColorMatrix\", {\r\n in: \"blur\",\r\n mode: \"matrix\",\r\n values: \"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10\",\r\n result: \"goo\",\r\n }),\r\n h(\"feComposite\", {\r\n in: \"SourceGraphic\",\r\n in2: \"goo\",\r\n operator: \"atop\",\r\n }),\r\n ]),\r\n ]),\r\n h(\"rect\", {\r\n \"data-sileo-pill\": \"true\",\r\n rx: String(r),\r\n ry: String(r),\r\n fill: fillColor,\r\n style: {\r\n x: `${pillX}px`,\r\n width: `${resolvedPillWidth}px`,\r\n height: `${isOpen ? pillH : TOAST_HEIGHT}px`,\r\n } as CSSProperties,\r\n }),\r\n h(\"rect\", {\r\n \"data-sileo-body\": \"true\",\r\n width: String(TOAST_WIDTH),\r\n rx: String(r),\r\n ry: String(r),\r\n fill: fillColor,\r\n style: {\r\n y: `${TOAST_HEIGHT}px`,\r\n height: `${isOpen ? expandedContent : 0}px`,\r\n opacity: isOpen ? \"1\" : \"0\",\r\n } as CSSProperties,\r\n }),\r\n ]),\r\n ]),\r\n\r\n // Header\r\n h(\"div\", {\r\n \"data-sileo-header\": \"true\",\r\n \"data-edge\": expandDir,\r\n }, [\r\n h(\"div\", { \"data-sileo-header-stack\": \"true\" }, [\r\n h(\"div\", {\r\n \"data-sileo-header-inner\": \"true\",\r\n ref: (el: Element | ComponentPublicInstance | null) => {\r\n setPillInnerRef(item.instanceId, el as HTMLElement | null);\r\n if (el) scheduleReady(item.instanceId);\r\n },\r\n }, [\r\n h(\"div\", {\r\n \"data-sileo-badge\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.badge,\r\n }, item.icon ?? renderStateIcon(item.state)),\r\n h(\"span\", {\r\n \"data-sileo-title\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.title,\r\n }, item.title),\r\n h(\"button\", {\r\n type: \"button\",\r\n class: \"sileo-dismiss\",\r\n onClick: (e: MouseEvent) => {\r\n e.stopPropagation();\r\n dismissByInstance(item.instanceId);\r\n },\r\n \"aria-label\": \"Dismiss toast\",\r\n }, \"×\"),\r\n ]),\r\n ]),\r\n ]),\r\n\r\n // Content\r\n hasDesc\r\n ? h(\"div\", {\r\n \"data-sileo-content\": \"true\",\r\n \"data-edge\": expandDir,\r\n \"data-visible\": isOpen ? \"true\" : \"false\",\r\n }, [\r\n h(\"div\", {\r\n \"data-sileo-description\": \"true\",\r\n class: item.styles?.description,\r\n ref: (el: Element | ComponentPublicInstance | null) => {\r\n setContentRef(item.instanceId, el as HTMLElement | null);\r\n },\r\n }, [\r\n item.description\r\n ? h(\"span\", {}, item.description)\r\n : null,\r\n item.button\r\n ? h(\"button\", {\r\n type: \"button\",\r\n \"data-sileo-button\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.button,\r\n onClick: (e: MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n item.button?.onClick?.();\r\n },\r\n }, item.button.title)\r\n : null,\r\n ]),\r\n ])\r\n : null,\r\n ],\r\n );\r\n };\r\n\r\n const renderPosition = (position: SileoPosition, items: SileoItem[]) => {\r\n const buckets = new Map<string, SileoItem[]>();\r\n for (const item of items) {\r\n const key = item.groupKey ?? \"__default__\";\r\n const list = buckets.get(key);\r\n if (list) {\r\n list.push(item);\r\n } else {\r\n buckets.set(key, [item]);\r\n }\r\n }\r\n\r\n const children: ReturnType<typeof h>[] = [];\r\n for (const [bucketKey, bucketItems] of buckets) {\r\n const visible = bucketItems.filter((item) => !item.exiting);\r\n const expandKey = `${position}:${bucketKey}`;\r\n const shouldGroup =\r\n props.grouping &&\r\n visible.length > props.groupThreshold &&\r\n !expandedGroups.value[expandKey];\r\n\r\n if (shouldGroup) {\r\n const label =\r\n bucketKey === \"__default__\"\r\n ? `${visible.length} notifications`\r\n : `${visible.length} ${bucketKey} notifications`;\r\n children.push(\r\n h(\r\n \"button\",\r\n {\r\n key: expandKey,\r\n type: \"button\",\r\n \"data-sileo-group\": \"true\",\r\n class: \"sileo-group\",\r\n onMouseenter: () => {\r\n expandedGroups.value[expandKey] = true;\r\n },\r\n onClick: () => {\r\n expandedGroups.value[expandKey] = true;\r\n },\r\n },\r\n label,\r\n ),\r\n );\r\n continue;\r\n }\r\n\r\n for (const item of bucketItems) {\r\n children.push(renderToast(item));\r\n }\r\n }\r\n\r\n return h(\r\n \"section\",\r\n {\r\n key: position,\r\n \"data-sileo-viewport\": \"true\",\r\n \"data-position\": position,\r\n \"data-theme\": resolveTheme(props.theme),\r\n \"aria-live\": props.ariaLive,\r\n \"aria-atomic\": \"true\",\r\n role: \"status\",\r\n style: getOffsetStyle(position, props.offset),\r\n },\r\n children,\r\n );\r\n };\r\n\r\n return () => {\r\n const viewports = Array.from(groupedByPosition.value.entries()).map(\r\n ([position, items]) => renderPosition(position, items),\r\n );\r\n\r\n const portalTarget = props.container ?? \"body\";\r\n\r\n return h(\"div\", { class: \"sileo-root\" }, [\r\n slots.default?.(),\r\n h(Teleport, { to: portalTarget }, viewports),\r\n ]);\r\n };\r\n },\r\n});\r\n"],"names":[],"mappings":";;;AACO,KAAA,UAAA;AACA,UAAA,WAAA;AACP;AACA;AACA;AACA;AACA;AACA;AACO,UAAA,WAAA;AACP;AACA;AACA;AACO,UAAA,cAAA;AACP;AACA;AACA;AACO,UAAA,qBAAA;AACP;AACA;AACA,WAAA,UAAA;AACA;AACO,UAAA,mBAAA;AACP,mBAAA,qBAAA;AACA,qBAAA,qBAAA;AACA,uBAAA,qBAAA;AACA,sBAAA,qBAAA;AACA;AACO,cAAA,eAAA;AACA,KAAA,aAAA,WAAA,eAAA;AACA,UAAA,YAAA;AACP;AACA;AACA,kBAAA,UAAA;AACA,WAAA,UAAA;AACA,eAAA,aAAA;AACA;AACA,WAAA,UAAA;AACA,aAAA,WAAA;AACA;AACA;AACA,0BAAA,cAAA;AACA;AACA,aAAA,WAAA;AACA;AACA,YAAA,mBAAA;AACA;AACO,KAAA,gBAAA;AACA,KAAA,iBAAA,GAAA,OAAA,CAAA,MAAA,sCAAA,gBAAA;;AC9CA,UAAA,mBAAA;AACP,aAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,WAAA,YAAA,wBAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,eAAA,aAAA;AACA;AAWO,cAAA,KAAA;AACP,iBAAA,YAAA;AACA,oBAAA,YAAA;AACA,kBAAA,YAAA;AACA,oBAAA,YAAA;AACA,iBAAA,YAAA;AACA,mBAAA,YAAA;AACA,oBAAA,YAAA;AACA,0BAAA,OAAA,aAAA,OAAA,YAAA,mBAAA,QAAA,OAAA;AACA,yBAAA,OAAA,CAAA,YAAA;AACA,mBAAA,aAAA;AACA;AACA;AACA,uBAAA,aAAA;AACA;AACO,cAAA,OAAA,EAAuB,GAAa,iBAAiB,GAAa,CAAA,gBAAA;AACzE,cAAA,QAAA,CAAA,aAAA;AACA,YAAA,QAAA,CAAA,gBAAA,GAAA,iBAAA;AACA,aAAA,QAAA,CAAA,OAAA,CAAA,YAAA;AACA,WAAA,QAAA;AACA,eAAA,QAAA,UAAA,WAAA;AACA;AACA,cAAA,kBAAA;AACA;AACA;AACA;AACA,cAAA,iBAAA;AACA;AACA;AACA;AACA,cAAA,QAAA;AACA;AACA;AACA,UAAU,GAAa,CAAA,KAAA,CAAO,GAAa,CAAA,YAAA,EAAe,GAAa,CAAA,eAAA;AACvE;AACA,gBAAgB,GAAa,CAAA,qBAAA,EAAwB,GAAa,CAAA,qBAAA,cAAoC,GAAa,uBAAuB,GAAa,CAAA,gBAAA;AACvJ,cAAA,QAAA,CAAA,aAAA;AACA,YAAA,QAAA,CAAA,gBAAA,GAAA,iBAAA;AACA,aAAA,QAAA,CAAA,OAAA,CAAA,YAAA;AACA,WAAA,QAAA;AACA,eAAA,QAAA,UAAA,WAAA;AACA;AACA,cAAA,kBAAA;AACA;AACA;AACA;AACA,cAAA,iBAAA;AACA;AACA;AACA;AACA,cAAA,QAAA;AACA;AACA;AACA,MAAA,QAAA;AACA;AACA;AACA;AACA,uBAAuB,GAAa,CAAA,uBAAA;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sources":["../src/types.ts","../src/toast.ts"],"sourcesContent":["import type { VNodeChild } from \"vue\";\r\n\r\nexport type SileoState =\r\n\t| \"success\"\r\n\t| \"loading\"\r\n\t| \"error\"\r\n\t| \"warning\"\r\n\t| \"info\"\r\n\t| \"action\";\r\n\r\nexport interface SileoStyles {\r\n\ttitle?: string;\r\n\tdescription?: string;\r\n\tbadge?: string;\r\n\tbutton?: string;\r\n\ttoast?: string;\r\n}\r\n\r\nexport interface SileoButton {\r\n\ttitle: string;\r\n\tonClick?: () => void;\r\n}\r\n\r\nexport interface SileoAutopilot {\r\n\texpand?: number;\r\n\tcollapse?: number;\r\n}\r\n\r\nexport interface SileoLifecycleContext {\r\n\tid: string;\r\n\tinstanceId: string;\r\n\tstate: SileoState;\r\n}\r\n\r\nexport interface SileoLifecycleHooks {\r\n\tonShow?: (ctx: SileoLifecycleContext) => void;\r\n\tonExpand?: (ctx: SileoLifecycleContext) => void;\r\n\tonCollapse?: (ctx: SileoLifecycleContext) => void;\r\n\tonDismiss?: (ctx: SileoLifecycleContext) => void;\r\n}\r\n\r\nexport const SILEO_POSITIONS = [\r\n\t\"top-left\",\r\n\t\"top-center\",\r\n\t\"top-right\",\r\n\t\"bottom-left\",\r\n\t\"bottom-center\",\r\n\t\"bottom-right\",\r\n] as const;\r\n\r\nexport type SileoPosition = (typeof SILEO_POSITIONS)[number];\r\n\r\nexport interface SileoOptions {\r\n\tid?: string;\r\n\ttitle?: string;\r\n\tdescription?: VNodeChild | string;\r\n\ttype?: SileoState;\r\n\tposition?: SileoPosition;\r\n\tduration?: number | null;\r\n\ticon?: VNodeChild | null;\r\n\tstyles?: SileoStyles;\r\n\tfill?: string;\r\n\troundness?: number;\r\n\tautopilot?: boolean | SileoAutopilot;\r\n\tswipeToDismiss?: boolean;\r\n\tbutton?: SileoButton;\r\n\tgroupKey?: string;\r\n\thooks?: SileoLifecycleHooks;\r\n}\r\n\r\nexport type SileoOffsetValue = number | string;\r\nexport type SileoOffsetConfig = Partial<\r\n\tRecord<\"top\" | \"right\" | \"bottom\" | \"left\", SileoOffsetValue>\r\n>;\r\n","import {\r\n Teleport,\r\n computed,\r\n defineComponent,\r\n h,\r\n onBeforeUnmount,\r\n onMounted,\r\n ref,\r\n watch,\r\n type ComponentPublicInstance,\r\n type CSSProperties,\r\n type PropType,\r\n} from \"vue\";\r\nimport {\r\n AUTOPILOT_COLLAPSE_DELAY,\r\n AUTOPILOT_EXPAND_DELAY,\r\n BLUR_RATIO,\r\n DEFAULT_ROUNDNESS,\r\n DEFAULT_TOAST_DURATION,\r\n EXIT_DURATION,\r\n GROUP_THRESHOLD,\r\n MIN_EXPAND_RATIO,\r\n PILL_PADDING,\r\n TOAST_HEIGHT,\r\n TOAST_WIDTH,\r\n} from \"./constants\";\r\nimport type {\r\n SileoLifecycleContext,\r\n SileoOffsetConfig,\r\n SileoOffsetValue,\r\n SileoOptions,\r\n SileoPosition,\r\n SileoState,\r\n} from \"./types\";\r\n\r\ninterface SileoItem extends Omit<SileoOptions, \"type\"> {\r\n id: string;\r\n instanceId: string;\r\n state: SileoState;\r\n createdAt: number;\r\n exiting?: boolean;\r\n}\r\n\r\ntype AutopilotConfig = {\r\n expand: number;\r\n collapse: number;\r\n};\r\n\r\ntype Listener = (toasts: SileoItem[]) => void;\r\n\r\nconst store = {\r\n toasts: [] as SileoItem[],\r\n listeners: new Set<Listener>(),\r\n position: \"top-right\" as SileoPosition,\r\n options: undefined as Partial<SileoOptions> | undefined,\r\n\r\n emit() {\r\n for (const listener of this.listeners) listener(this.toasts);\r\n },\r\n\r\n update(updater: (prev: SileoItem[]) => SileoItem[]) {\r\n this.toasts = updater(this.toasts);\r\n this.emit();\r\n },\r\n};\r\n\r\nconst dismissalTimers = new Map<string, ReturnType<typeof setTimeout>>();\r\nlet sequence = 0;\r\n\r\nconst nextId = () => `sileo-${++sequence}-${Date.now().toString(36)}`;\r\nconst nextInstanceId = () => `instance-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;\r\n\r\nconst clearTimer = (instanceId: string) => {\r\n const timer = dismissalTimers.get(instanceId);\r\n if (timer !== undefined) {\r\n clearTimeout(timer);\r\n dismissalTimers.delete(instanceId);\r\n }\r\n};\r\n\r\nconst removeByInstance = (instanceId: string) => {\r\n clearTimer(instanceId);\r\n store.update((prev) => prev.filter((item) => item.instanceId !== instanceId));\r\n};\r\n\r\nconst dismissByInstance = (instanceId: string) => {\r\n const target = store.toasts.find(\r\n (item) => item.instanceId === instanceId && !item.exiting,\r\n );\r\n if (!target) return;\r\n\r\n clearTimer(instanceId);\r\n target.hooks?.onDismiss?.(toLifecycleContext(target));\r\n store.update((prev) =>\r\n prev.map((item) =>\r\n item.instanceId === instanceId ? { ...item, exiting: true } : item,\r\n ),\r\n );\r\n\r\n globalThis.setTimeout(() => {\r\n removeByInstance(instanceId);\r\n }, EXIT_DURATION);\r\n};\r\n\r\nconst scheduleDismiss = (item: SileoItem) => {\r\n clearTimer(item.instanceId);\r\n if (item.duration === null) return;\r\n const duration = item.duration ?? DEFAULT_TOAST_DURATION;\r\n if (duration <= 0) return;\r\n\r\n const timer = globalThis.setTimeout(() => {\r\n dismissByInstance(item.instanceId);\r\n }, duration);\r\n\r\n dismissalTimers.set(item.instanceId, timer);\r\n};\r\n\r\nconst mergeOptions = (options: SileoOptions): SileoOptions => ({\r\n ...store.options,\r\n ...options,\r\n styles: { ...store.options?.styles, ...options.styles },\r\n});\r\n\r\nconst buildItem = (options: SileoOptions, state: SileoState): SileoItem => ({\r\n id: options.id ?? nextId(),\r\n instanceId: nextInstanceId(),\r\n state,\r\n title: options.title,\r\n description: options.description,\r\n position: options.position ?? store.position,\r\n duration: options.duration,\r\n icon: options.icon,\r\n styles: options.styles,\r\n fill: options.fill,\r\n roundness: options.roundness,\r\n autopilot: options.autopilot,\r\n swipeToDismiss: options.swipeToDismiss,\r\n button: options.button,\r\n groupKey: options.groupKey,\r\n hooks: options.hooks,\r\n createdAt: Date.now(),\r\n});\r\n\r\nconst createToast = (raw: SileoOptions, forcedState?: SileoState) => {\r\n const merged = mergeOptions(raw);\r\n const state = forcedState ?? merged.type ?? \"info\";\r\n const hasCustomId = Boolean(merged.id);\r\n\r\n if (hasCustomId) {\r\n const existing = store.toasts.find(\r\n (item) => item.id === merged.id && !item.exiting,\r\n );\r\n if (existing) {\r\n const replacement = buildItem(merged, state);\r\n replacement.id = existing.id;\r\n store.update((prev) =>\r\n prev.map((item) =>\r\n item.instanceId === existing.instanceId ? replacement : item,\r\n ),\r\n );\r\n clearTimer(existing.instanceId);\r\n scheduleDismiss(replacement);\r\n replacement.hooks?.onShow?.(toLifecycleContext(replacement));\r\n return existing.id;\r\n }\r\n }\r\n\r\n const item = buildItem(merged, state);\r\n store.update((prev) => [...prev, item]);\r\n scheduleDismiss(item);\r\n item.hooks?.onShow?.(toLifecycleContext(item));\r\n return item.id;\r\n};\r\n\r\nconst dismissToast = (id?: string) => {\r\n if (!id) {\r\n for (const item of store.toasts) dismissByInstance(item.instanceId);\r\n return;\r\n }\r\n\r\n const active = store.toasts\r\n .filter((item) => item.id === id && !item.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n\r\n if (!active) return;\r\n dismissByInstance(active.instanceId);\r\n};\r\n\r\nconst clearToasts = (position?: SileoPosition) => {\r\n const removed = position\r\n ? store.toasts.filter((item) => item.position === position)\r\n : [...store.toasts];\r\n\r\n for (const item of removed) clearTimer(item.instanceId);\r\n\r\n store.update((prev) =>\r\n position ? prev.filter((item) => item.position !== position) : [],\r\n );\r\n};\r\n\r\nconst toLifecycleContext = (item: SileoItem): SileoLifecycleContext => ({\r\n id: item.id,\r\n instanceId: item.instanceId,\r\n state: item.state,\r\n});\r\n\r\nconst resolveTheme = (theme: \"light\" | \"dark\" | \"system\" | undefined) => {\r\n if (theme === \"light\" || theme === \"dark\") return theme;\r\n if (typeof window === \"undefined\") return \"light\";\r\n if (typeof window.matchMedia !== \"function\") return \"light\";\r\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches\r\n ? \"dark\"\r\n : \"light\";\r\n};\r\n\r\nconst normalizeAutopilot = (\r\n value: SileoOptions[\"autopilot\"],\r\n): AutopilotConfig | null => {\r\n if (value === false) return null;\r\n if (value === true || value === undefined) {\r\n return {\r\n expand: AUTOPILOT_EXPAND_DELAY,\r\n collapse: AUTOPILOT_COLLAPSE_DELAY,\r\n };\r\n }\r\n\r\n const expand = Number.isFinite(value.expand)\r\n ? Math.max(0, Number(value.expand))\r\n : AUTOPILOT_EXPAND_DELAY;\r\n const collapse = Number.isFinite(value.collapse)\r\n ? Math.max(expand + 200, Number(value.collapse))\r\n : AUTOPILOT_COLLAPSE_DELAY;\r\n\r\n return { expand, collapse };\r\n};\r\n\r\nconst renderStateIcon = (state: SileoState) => {\r\n const common = {\r\n viewBox: \"0 0 16 16\",\r\n width: \"14\",\r\n height: \"14\",\r\n \"aria-hidden\": \"true\",\r\n focusable: \"false\",\r\n } as const;\r\n\r\n if (state === \"success\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M3.2 8.4L6.5 11.4L12.8 4.9\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"error\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M5 5L11 11M11 5L5 11\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"warning\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M8 3.2L13 12.5H3L8 3.2Z\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.4\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 6.2V9\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.5\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n h(\"circle\", { cx: \"8\", cy: \"11\", r: \"0.9\", fill: \"currentColor\" }),\r\n ]);\r\n }\r\n if (state === \"action\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M3.5 8H12.5M9.2 4.8L12.4 8L9.2 11.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"loading\") {\r\n return h(\"svg\", { ...common, \"data-sileo-icon\": \"spin\" }, [\r\n h(\"circle\", {\r\n cx: \"8\",\r\n cy: \"8\",\r\n r: \"5\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n opacity: \"0.35\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 3A5 5 0 0 1 13 8\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n ]);\r\n }\r\n\r\n return h(\"svg\", common, [\r\n h(\"circle\", {\r\n cx: \"8\",\r\n cy: \"8\",\r\n r: \"5.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 6.8V10.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n h(\"circle\", { cx: \"8\", cy: \"4.9\", r: \"0.9\", fill: \"currentColor\" }),\r\n ]);\r\n};\r\n\r\nconst getOffsetStyle = (\r\n position: SileoPosition,\r\n offset?: SileoOffsetValue | SileoOffsetConfig,\r\n): CSSProperties | undefined => {\r\n if (offset === undefined) return undefined;\r\n\r\n const value =\r\n typeof offset === \"object\"\r\n ? offset\r\n : { top: offset, right: offset, bottom: offset, left: offset };\r\n\r\n const style: CSSProperties = {};\r\n const px = (v: SileoOffsetValue) => (typeof v === \"number\" ? `${v}px` : v);\r\n\r\n if (position.startsWith(\"top\") && value.top !== undefined) style.top = px(value.top);\r\n if (position.startsWith(\"bottom\") && value.bottom !== undefined) {\r\n style.bottom = px(value.bottom);\r\n }\r\n if (position.endsWith(\"left\") && value.left !== undefined) style.left = px(value.left);\r\n if (position.endsWith(\"right\") && value.right !== undefined) {\r\n style.right = px(value.right);\r\n }\r\n\r\n return style;\r\n};\r\n\r\nexport interface SileoPromiseOptions<T = unknown> {\r\n loading: SileoOptions;\r\n success: SileoOptions | ((data: T) => SileoOptions);\r\n error: SileoOptions | ((error: unknown) => SileoOptions);\r\n action?: SileoOptions | ((data: T) => SileoOptions);\r\n position?: SileoPosition;\r\n}\r\n\r\nexport interface SileoToasterProps {\r\n position?: SileoPosition;\r\n offset?: SileoOffsetValue | SileoOffsetConfig;\r\n options?: Partial<SileoOptions>;\r\n theme?: \"light\" | \"dark\" | \"system\";\r\n container?: string | HTMLElement;\r\n grouping?: boolean;\r\n groupThreshold?: number;\r\n ariaLive?: \"off\" | \"polite\" | \"assertive\";\r\n}\r\n\r\nexport const sileo = {\r\n show: (opts: SileoOptions) => createToast(opts, opts.type ?? \"info\"),\r\n success: (opts: SileoOptions) => createToast(opts, \"success\"),\r\n error: (opts: SileoOptions) => createToast(opts, \"error\"),\r\n warning: (opts: SileoOptions) => createToast(opts, \"warning\"),\r\n info: (opts: SileoOptions) => createToast(opts, \"info\"),\r\n action: (opts: SileoOptions) => createToast(opts, \"action\"),\r\n loading: (opts: SileoOptions) =>\r\n createToast({ ...opts, duration: opts.duration ?? null }, \"loading\"),\r\n\r\n promise: async <T,>(\r\n promise: Promise<T> | (() => Promise<T>),\r\n opts: SileoPromiseOptions<T>,\r\n ): Promise<T> => {\r\n const id = createToast(\r\n { ...opts.loading, duration: null, position: opts.position },\r\n \"loading\",\r\n );\r\n try {\r\n const data = await (typeof promise === \"function\" ? promise() : promise);\r\n const next = opts.action\r\n ? typeof opts.action === \"function\"\r\n ? opts.action(data)\r\n : opts.action\r\n : typeof opts.success === \"function\"\r\n ? opts.success(data)\r\n : opts.success;\r\n createToast({ ...next, id }, opts.action ? \"action\" : \"success\");\r\n return data;\r\n } catch (error) {\r\n const next =\r\n typeof opts.error === \"function\" ? opts.error(error) : opts.error;\r\n createToast({ ...next, id }, \"error\");\r\n throw error;\r\n }\r\n },\r\n\r\n configure: (options: Partial<SileoOptions> & { position?: SileoPosition }) => {\r\n if (options.position) {\r\n store.position = options.position;\r\n }\r\n store.options = {\r\n ...store.options,\r\n ...options,\r\n styles: { ...store.options?.styles, ...options.styles },\r\n };\r\n },\r\n\r\n dismiss: dismissToast,\r\n clear: clearToasts,\r\n};\r\n\r\nexport const Toaster = defineComponent({\r\n name: \"SileoToaster\",\r\n props: {\r\n position: String as PropType<SileoPosition | undefined>,\r\n offset: [Number, String, Object] as PropType<\r\n SileoOffsetValue | SileoOffsetConfig\r\n >,\r\n options: Object as PropType<Partial<SileoOptions>>,\r\n theme: String as PropType<\"light\" | \"dark\" | \"system\">,\r\n container: [String, Object] as PropType<string | HTMLElement>,\r\n grouping: { type: Boolean, default: false },\r\n groupThreshold: { type: Number, default: GROUP_THRESHOLD },\r\n ariaLive: {\r\n type: String as PropType<\"off\" | \"polite\" | \"assertive\">,\r\n default: \"polite\",\r\n },\r\n },\r\n setup(props, { slots }) {\r\n const toasts = ref<SileoItem[]>(store.toasts);\r\n const expandedGroups = ref<Record<string, boolean>>({});\r\n const expandedToasts = ref<Record<string, boolean>>({});\r\n const autopilotTimers = new Map<string, ReturnType<typeof setTimeout>[]>();\r\n const activeToastInstanceId = ref<string | undefined>(undefined);\r\n const contentHeights = ref<Record<string, number>>({});\r\n const contentRefs = new Map<string, HTMLElement>();\r\n const contentObservers = new Map<string, ResizeObserver>();\r\n\r\n // Pill inner measurement (drives SVG pill width)\r\n const readyToasts = ref<Record<string, boolean>>({});\r\n const pillWidths = ref<Record<string, number>>({});\r\n const pillInnerRefs = new Map<string, HTMLElement>();\r\n const pillInnerObservers = new Map<string, ResizeObserver>();\r\n\r\n const measureContentHeight = (instanceId: string) => {\r\n const el = contentRefs.get(instanceId);\r\n if (!el) return;\r\n const measured = Math.max(0, el.scrollHeight);\r\n if (contentHeights.value[instanceId] !== measured) {\r\n contentHeights.value[instanceId] = measured;\r\n }\r\n };\r\n\r\n const setContentRef = (instanceId: string, el: HTMLElement | null) => {\r\n if (el) {\r\n contentRefs.set(instanceId, el);\r\n measureContentHeight(instanceId);\r\n if (typeof ResizeObserver === \"function\") {\r\n if (!contentObservers.has(instanceId)) {\r\n const observer = new ResizeObserver(() => {\r\n measureContentHeight(instanceId);\r\n });\r\n contentObservers.set(instanceId, observer);\r\n }\r\n contentObservers.get(instanceId)?.observe(el);\r\n }\r\n return;\r\n }\r\n\r\n const previous = contentRefs.get(instanceId);\r\n const observer = contentObservers.get(instanceId);\r\n if (previous && observer) observer.unobserve(previous);\r\n contentRefs.delete(instanceId);\r\n };\r\n\r\n const clearContentObserver = (instanceId: string) => {\r\n const observer = contentObservers.get(instanceId);\r\n if (observer) {\r\n observer.disconnect();\r\n contentObservers.delete(instanceId);\r\n }\r\n contentRefs.delete(instanceId);\r\n delete contentHeights.value[instanceId];\r\n };\r\n\r\n const measurePillInner = (instanceId: string) => {\r\n const el = pillInnerRefs.get(instanceId);\r\n if (!el) return;\r\n const w = el.scrollWidth;\r\n if (pillWidths.value[instanceId] !== w) {\r\n pillWidths.value[instanceId] = w;\r\n }\r\n };\r\n\r\n const setPillInnerRef = (instanceId: string, el: HTMLElement | null) => {\r\n if (el) {\r\n pillInnerRefs.set(instanceId, el);\r\n measurePillInner(instanceId);\r\n if (typeof ResizeObserver === 'function' && !pillInnerObservers.has(instanceId)) {\r\n const observer = new ResizeObserver(() => measurePillInner(instanceId));\r\n pillInnerObservers.set(instanceId, observer);\r\n observer.observe(el);\r\n }\r\n return;\r\n }\r\n const prev = pillInnerRefs.get(instanceId);\r\n const obs = pillInnerObservers.get(instanceId);\r\n if (prev && obs) obs.unobserve(prev);\r\n pillInnerRefs.delete(instanceId);\r\n };\r\n\r\n const clearPillInnerObserver = (instanceId: string) => {\r\n const obs = pillInnerObservers.get(instanceId);\r\n if (obs) { obs.disconnect(); pillInnerObservers.delete(instanceId); }\r\n pillInnerRefs.delete(instanceId);\r\n delete pillWidths.value[instanceId];\r\n };\r\n\r\n const scheduleReady = (instanceId: string) => {\r\n globalThis.requestAnimationFrame(() => {\r\n if (pillInnerRefs.has(instanceId) || toasts.value.some(t => t.instanceId === instanceId)) {\r\n readyToasts.value[instanceId] = true;\r\n }\r\n });\r\n };\r\n\r\n const setExpanded = (instanceId: string, expanded: boolean) => {\r\n const previous = expandedToasts.value[instanceId];\r\n if (previous === expanded) return;\r\n\r\n expandedToasts.value[instanceId] = expanded;\r\n if (previous === undefined) return;\r\n\r\n const item = toasts.value.find((toast) => toast.instanceId === instanceId);\r\n if (!item) return;\r\n\r\n if (expanded) {\r\n item.hooks?.onExpand?.(toLifecycleContext(item));\r\n } else {\r\n item.hooks?.onCollapse?.(toLifecycleContext(item));\r\n }\r\n };\r\n\r\n const clearAutopilotTimers = (instanceId: string) => {\r\n const timers = autopilotTimers.get(instanceId);\r\n if (!timers) return;\r\n for (const timer of timers) clearTimeout(timer);\r\n autopilotTimers.delete(instanceId);\r\n };\r\n\r\n const scheduleAutopilot = (item: SileoItem) => {\r\n clearAutopilotTimers(item.instanceId);\r\n const config = normalizeAutopilot(item.autopilot);\r\n if (!item.description || !config) {\r\n setExpanded(item.instanceId, false);\r\n return;\r\n }\r\n\r\n if (\r\n activeToastInstanceId.value !== undefined &&\r\n activeToastInstanceId.value !== item.instanceId\r\n ) {\r\n setExpanded(item.instanceId, false);\r\n return;\r\n }\r\n\r\n setExpanded(item.instanceId, false);\r\n const timers: ReturnType<typeof setTimeout>[] = [];\r\n\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, true);\r\n }, config.expand),\r\n );\r\n\r\n const duration = item.duration ?? DEFAULT_TOAST_DURATION;\r\n if (item.duration !== null) {\r\n const maxCollapse = Math.max(\r\n config.expand + 350,\r\n duration - EXIT_DURATION - 150,\r\n );\r\n const collapseAt = Math.min(config.collapse, maxCollapse);\r\n if (collapseAt > config.expand) {\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, false);\r\n }, collapseAt),\r\n );\r\n }\r\n } else if (config.collapse > config.expand) {\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, false);\r\n }, config.collapse),\r\n );\r\n }\r\n\r\n autopilotTimers.set(item.instanceId, timers);\r\n };\r\n\r\n const listener: Listener = (next) => {\r\n toasts.value = next;\r\n };\r\n\r\n onMounted(() => {\r\n store.listeners.add(listener);\r\n if (props.position) store.position = props.position;\r\n if (props.options) {\r\n store.options = {\r\n ...store.options,\r\n ...props.options,\r\n styles: { ...store.options?.styles, ...props.options.styles },\r\n };\r\n }\r\n });\r\n\r\n onBeforeUnmount(() => {\r\n store.listeners.delete(listener);\r\n for (const instanceId of autopilotTimers.keys()) {\r\n clearAutopilotTimers(instanceId);\r\n }\r\n for (const instanceId of contentObservers.keys()) {\r\n clearContentObserver(instanceId);\r\n }\r\n for (const instanceId of pillInnerObservers.keys()) {\r\n clearPillInnerObserver(instanceId);\r\n }\r\n });\r\n\r\n watch(\r\n () => props.position,\r\n (value) => {\r\n if (value) store.position = value;\r\n },\r\n );\r\n\r\n watch(\r\n () => props.options,\r\n (value) => {\r\n if (!value) return;\r\n store.options = {\r\n ...store.options,\r\n ...value,\r\n styles: { ...store.options?.styles, ...value.styles },\r\n };\r\n },\r\n { deep: true },\r\n );\r\n\r\n watch(\r\n () => toasts.value,\r\n (items) => {\r\n const active = new Set(items.map((item) => item.instanceId));\r\n const latest = [...items]\r\n .filter((item) => !item.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n\r\n activeToastInstanceId.value = latest?.instanceId;\r\n\r\n for (const item of items) {\r\n if (expandedToasts.value[item.instanceId] === undefined) {\r\n setExpanded(item.instanceId, false);\r\n }\r\n }\r\n\r\n for (const instanceId of Object.keys(expandedToasts.value)) {\r\n if (!active.has(instanceId)) {\r\n clearAutopilotTimers(instanceId);\r\n delete expandedToasts.value[instanceId];\r\n clearContentObserver(instanceId);\r\n clearPillInnerObserver(instanceId);\r\n delete readyToasts.value[instanceId];\r\n }\r\n }\r\n },\r\n { immediate: true },\r\n );\r\n\r\n watch(\r\n () => activeToastInstanceId.value,\r\n (activeInstanceId) => {\r\n for (const instanceId of Object.keys(expandedToasts.value)) {\r\n if (instanceId !== activeInstanceId) {\r\n clearAutopilotTimers(instanceId);\r\n setExpanded(instanceId, false);\r\n }\r\n }\r\n\r\n if (!activeInstanceId) return;\r\n const item = toasts.value.find((toast) => toast.instanceId === activeInstanceId);\r\n if (item) {\r\n scheduleAutopilot(item);\r\n }\r\n },\r\n );\r\n\r\n const groupedByPosition = computed(() => {\r\n const map = new Map<SileoPosition, SileoItem[]>();\r\n for (const item of toasts.value) {\r\n const pos = item.position ?? store.position;\r\n const list = map.get(pos);\r\n if (list) {\r\n list.push(item);\r\n } else {\r\n map.set(pos, [item]);\r\n }\r\n }\r\n return map;\r\n });\r\n\r\n const renderToast = (item: SileoItem) => {\r\n const r = Math.max(0, item.roundness ?? DEFAULT_ROUNDNESS);\r\n const isExpanded = Boolean(expandedToasts.value[item.instanceId]);\r\n const isReady = Boolean(readyToasts.value[item.instanceId]);\r\n const swipeEnabled = item.swipeToDismiss ?? true;\r\n const pos = item.position ?? store.position;\r\n const align: 'left' | 'center' | 'right' =\r\n pos.endsWith('left') ? 'left'\r\n : pos.endsWith('right') ? 'right'\r\n : 'center';\r\n const expandDir: 'top' | 'bottom' = pos.startsWith('top') ? 'bottom' : 'top';\r\n\r\n const hasDesc = Boolean(item.description || item.button);\r\n const isLoading = item.state === 'loading';\r\n const isOpen = hasDesc && isExpanded && !isLoading;\r\n\r\n const blur = r * BLUR_RATIO;\r\n const rawPillW = pillWidths.value[item.instanceId] ?? 0;\r\n const resolvedPillWidth = Math.max(rawPillW + PILL_PADDING, TOAST_HEIGHT);\r\n const pillH = TOAST_HEIGHT + blur * 3;\r\n\r\n const pillX = align === 'right' ? TOAST_WIDTH - resolvedPillWidth\r\n : align === 'center' ? (TOAST_WIDTH - resolvedPillWidth) / 2\r\n : 0;\r\n\r\n const contentH = contentHeights.value[item.instanceId] ?? 0;\r\n const minExpanded = TOAST_HEIGHT * MIN_EXPAND_RATIO;\r\n const expanded = hasDesc ? Math.max(minExpanded, TOAST_HEIGHT + contentH) : minExpanded;\r\n const expandedContent = Math.max(0, expanded - TOAST_HEIGHT);\r\n const svgH = Math.max(expanded, TOAST_HEIGHT);\r\n\r\n const resolvedTheme = resolveTheme(props.theme);\r\n const fillColor = item.fill ?? (resolvedTheme === 'dark' ? '#f2f2f2' : '#1a1a1a');\r\n const filterId = `sileo-gooey-${item.instanceId.replace(/[^a-z0-9]/gi, '-')}`;\r\n\r\n const rootStyle: CSSProperties & Record<string, string> = {\r\n '--_h': `${isOpen ? expanded : TOAST_HEIGHT}px`,\r\n '--_pw': `${resolvedPillWidth}px`,\r\n '--_px': `${pillX}px`,\r\n '--_ht': `translateY(${isOpen ? (expandDir === 'bottom' ? 3 : -3) : 0}px) scale(${isOpen ? 0.9 : 1})`,\r\n '--_co': isOpen ? '1' : '0',\r\n };\r\n\r\n const swipeState = {\r\n startY: 0,\r\n pointerId: -1,\r\n dragging: false,\r\n };\r\n\r\n const resetSwipe = (el: HTMLElement) => {\r\n el.style.transform = \"\";\r\n el.style.opacity = \"\";\r\n el.style.transition = \"\";\r\n };\r\n\r\n return h(\r\n \"button\",\r\n {\r\n key: item.instanceId,\r\n type: \"button\",\r\n \"data-sileo-toast\": \"true\",\r\n \"data-state\": item.state,\r\n \"data-exiting\": item.exiting ? \"true\" : \"false\",\r\n \"data-expanded\": isExpanded ? \"true\" : \"false\",\r\n \"data-ready\": isReady ? \"true\" : \"false\",\r\n class: item.styles?.toast,\r\n style: rootStyle,\r\n onMouseenter: () => {\r\n activeToastInstanceId.value = item.instanceId;\r\n setExpanded(item.instanceId, true);\r\n },\r\n onMouseleave: () => {\r\n const autopilot = normalizeAutopilot(item.autopilot);\r\n const latest = [...toasts.value]\r\n .filter((toast) => !toast.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n activeToastInstanceId.value = latest?.instanceId;\r\n if (autopilot !== null) setExpanded(item.instanceId, false);\r\n },\r\n onPointerdown: (e: PointerEvent) => {\r\n if (!swipeEnabled || item.state === \"loading\") return;\r\n const target = e.target as HTMLElement;\r\n if (target.closest(\"[data-sileo-button='true'], .sileo-dismiss\")) {\r\n return;\r\n }\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.startY = e.clientY;\r\n swipeState.pointerId = e.pointerId;\r\n swipeState.dragging = true;\r\n el.setPointerCapture(e.pointerId);\r\n },\r\n onPointermove: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n const deltaY = e.clientY - swipeState.startY;\r\n const clamped = Math.max(-22, Math.min(22, deltaY));\r\n el.style.transform = `translateY(${clamped}px)`;\r\n const alpha = 1 - Math.min(0.35, Math.abs(clamped) / 60);\r\n el.style.opacity = String(alpha);\r\n },\r\n onPointerup: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.dragging = false;\r\n const deltaY = e.clientY - swipeState.startY;\r\n if (el.hasPointerCapture(e.pointerId)) {\r\n el.releasePointerCapture(e.pointerId);\r\n }\r\n if (Math.abs(deltaY) > 34) {\r\n dismissByInstance(item.instanceId);\r\n return;\r\n }\r\n el.style.transition = \"transform 150ms ease, opacity 150ms ease\";\r\n resetSwipe(el);\r\n },\r\n onPointercancel: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.dragging = false;\r\n if (el.hasPointerCapture(e.pointerId)) {\r\n el.releasePointerCapture(e.pointerId);\r\n }\r\n el.style.transition = \"transform 150ms ease, opacity 150ms ease\";\r\n resetSwipe(el);\r\n },\r\n },\r\n [\r\n // SVG gooey canvas\r\n h(\"div\", {\r\n \"data-sileo-canvas\": \"true\",\r\n \"data-edge\": expandDir,\r\n style: { filter: `url(#${filterId})` } as CSSProperties,\r\n }, [\r\n h(\"svg\", {\r\n \"data-sileo-svg\": \"true\",\r\n width: String(TOAST_WIDTH),\r\n height: String(svgH),\r\n viewBox: `0 0 ${TOAST_WIDTH} ${svgH}`,\r\n xmlns: \"http://www.w3.org/2000/svg\",\r\n \"aria-hidden\": \"true\",\r\n focusable: \"false\",\r\n }, [\r\n h(\"defs\", {}, [\r\n h(\"filter\", {\r\n id: filterId,\r\n x: \"-20%\",\r\n y: \"-20%\",\r\n width: \"140%\",\r\n height: \"140%\",\r\n \"color-interpolation-filters\": \"sRGB\",\r\n }, [\r\n h(\"feGaussianBlur\", {\r\n in: \"SourceGraphic\",\r\n stdDeviation: String(blur),\r\n result: \"blur\",\r\n }),\r\n h(\"feColorMatrix\", {\r\n in: \"blur\",\r\n mode: \"matrix\",\r\n values: \"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10\",\r\n result: \"goo\",\r\n }),\r\n h(\"feComposite\", {\r\n in: \"SourceGraphic\",\r\n in2: \"goo\",\r\n operator: \"atop\",\r\n }),\r\n ]),\r\n ]),\r\n h(\"rect\", {\r\n \"data-sileo-pill\": \"true\",\r\n rx: String(r),\r\n ry: String(r),\r\n fill: fillColor,\r\n style: {\r\n x: `${pillX}px`,\r\n width: `${resolvedPillWidth}px`,\r\n height: `${isOpen ? pillH : TOAST_HEIGHT}px`,\r\n } as CSSProperties,\r\n }),\r\n h(\"rect\", {\r\n \"data-sileo-body\": \"true\",\r\n width: String(TOAST_WIDTH),\r\n rx: String(r),\r\n ry: String(r),\r\n fill: fillColor,\r\n style: {\r\n y: `${TOAST_HEIGHT}px`,\r\n height: `${isOpen ? expandedContent : 0}px`,\r\n opacity: isOpen ? \"1\" : \"0\",\r\n } as CSSProperties,\r\n }),\r\n ]),\r\n ]),\r\n\r\n // Header\r\n h(\"div\", {\r\n \"data-sileo-header\": \"true\",\r\n \"data-edge\": expandDir,\r\n }, [\r\n h(\"div\", { \"data-sileo-header-stack\": \"true\" }, [\r\n h(\"div\", {\r\n \"data-sileo-header-inner\": \"true\",\r\n ref: (el: Element | ComponentPublicInstance | null) => {\r\n setPillInnerRef(item.instanceId, el as HTMLElement | null);\r\n if (el) scheduleReady(item.instanceId);\r\n },\r\n }, [\r\n h(\"div\", {\r\n \"data-sileo-badge\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.badge,\r\n }, item.icon ?? renderStateIcon(item.state)),\r\n h(\"span\", {\r\n \"data-sileo-title\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.title,\r\n }, item.title),\r\n h(\"button\", {\r\n type: \"button\",\r\n class: \"sileo-dismiss\",\r\n onClick: (e: MouseEvent) => {\r\n e.stopPropagation();\r\n dismissByInstance(item.instanceId);\r\n },\r\n \"aria-label\": \"Dismiss toast\",\r\n }, \"×\"),\r\n ]),\r\n ]),\r\n ]),\r\n\r\n // Content\r\n hasDesc\r\n ? h(\"div\", {\r\n \"data-sileo-content\": \"true\",\r\n \"data-edge\": expandDir,\r\n \"data-visible\": isOpen ? \"true\" : \"false\",\r\n }, [\r\n h(\"div\", {\r\n \"data-sileo-description\": \"true\",\r\n class: item.styles?.description,\r\n ref: (el: Element | ComponentPublicInstance | null) => {\r\n setContentRef(item.instanceId, el as HTMLElement | null);\r\n },\r\n }, [\r\n item.description\r\n ? h(\"span\", {}, item.description)\r\n : null,\r\n item.button\r\n ? h(\"button\", {\r\n type: \"button\",\r\n \"data-sileo-button\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.button,\r\n onClick: (e: MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n item.button?.onClick?.();\r\n },\r\n }, item.button.title)\r\n : null,\r\n ]),\r\n ])\r\n : null,\r\n ],\r\n );\r\n };\r\n\r\n const renderPosition = (position: SileoPosition, items: SileoItem[]) => {\r\n const buckets = new Map<string, SileoItem[]>();\r\n for (const item of items) {\r\n const key = item.groupKey ?? \"__default__\";\r\n const list = buckets.get(key);\r\n if (list) {\r\n list.push(item);\r\n } else {\r\n buckets.set(key, [item]);\r\n }\r\n }\r\n\r\n const children: ReturnType<typeof h>[] = [];\r\n for (const [bucketKey, bucketItems] of buckets) {\r\n const visible = bucketItems.filter((item) => !item.exiting);\r\n const expandKey = `${position}:${bucketKey}`;\r\n const shouldGroup =\r\n props.grouping &&\r\n visible.length > props.groupThreshold &&\r\n !expandedGroups.value[expandKey];\r\n\r\n if (shouldGroup) {\r\n const label =\r\n bucketKey === \"__default__\"\r\n ? `${visible.length} notifications`\r\n : `${visible.length} ${bucketKey} notifications`;\r\n children.push(\r\n h(\r\n \"button\",\r\n {\r\n key: expandKey,\r\n type: \"button\",\r\n \"data-sileo-group\": \"true\",\r\n class: \"sileo-group\",\r\n onMouseenter: () => {\r\n expandedGroups.value[expandKey] = true;\r\n },\r\n onClick: () => {\r\n expandedGroups.value[expandKey] = true;\r\n },\r\n },\r\n label,\r\n ),\r\n );\r\n continue;\r\n }\r\n\r\n for (const item of bucketItems) {\r\n children.push(renderToast(item));\r\n }\r\n }\r\n\r\n return h(\r\n \"section\",\r\n {\r\n key: position,\r\n \"data-sileo-viewport\": \"true\",\r\n \"data-position\": position,\r\n \"data-theme\": resolveTheme(props.theme),\r\n \"aria-live\": props.ariaLive,\r\n \"aria-atomic\": \"true\",\r\n role: \"status\",\r\n style: getOffsetStyle(position, props.offset),\r\n },\r\n children,\r\n );\r\n };\r\n\r\n return () => {\r\n const viewports = Array.from(groupedByPosition.value.entries()).map(\r\n ([position, items]) => renderPosition(position, items),\r\n );\r\n\r\n const portalTarget = props.container ?? \"body\";\r\n\r\n return h(\"div\", { class: \"sileo-root\" }, [\r\n slots.default?.(),\r\n h(Teleport, { to: portalTarget }, viewports),\r\n ]);\r\n };\r\n },\r\n});\r\n"],"names":[],"mappings":";;;AACO,KAAA,UAAA;AACA,UAAA,WAAA;AACP;AACA;AACA;AACA;AACA;AACA;AACO,UAAA,WAAA;AACP;AACA;AACA;AACO,UAAA,cAAA;AACP;AACA;AACA;AACO,UAAA,qBAAA;AACP;AACA;AACA,WAAA,UAAA;AACA;AACO,UAAA,mBAAA;AACP,mBAAA,qBAAA;AACA,qBAAA,qBAAA;AACA,uBAAA,qBAAA;AACA,sBAAA,qBAAA;AACA;AACO,cAAA,eAAA;AACA,KAAA,aAAA,WAAA,eAAA;AACA,UAAA,YAAA;AACP;AACA;AACA,kBAAA,UAAA;AACA,WAAA,UAAA;AACA,eAAA,aAAA;AACA;AACA,WAAA,UAAA;AACA,aAAA,WAAA;AACA;AACA;AACA,0BAAA,cAAA;AACA;AACA,aAAA,WAAA;AACA;AACA,YAAA,mBAAA;AACA;AACO,KAAA,gBAAA;AACA,KAAA,iBAAA,GAAA,OAAA,CAAA,MAAA,sCAAA,gBAAA;;AC9CA,UAAA,mBAAA;AACP,aAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,WAAA,YAAA,wBAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,eAAA,aAAA;AACA;AAWO,cAAA,KAAA;AACP,iBAAA,YAAA;AACA,oBAAA,YAAA;AACA,kBAAA,YAAA;AACA,oBAAA,YAAA;AACA,iBAAA,YAAA;AACA,mBAAA,YAAA;AACA,oBAAA,YAAA;AACA,0BAAA,OAAA,aAAA,OAAA,YAAA,mBAAA,QAAA,OAAA;AACA,yBAAA,OAAA,CAAA,YAAA;AACA,mBAAA,aAAA;AACA;AACA;AACA,uBAAA,aAAA;AACA;AACO,cAAA,OAAA,EAAuB,GAAa,iBAAiB,GAAa,CAAA,gBAAA;AACzE,cAAA,QAAA,CAAA,aAAA;AACA,YAAA,QAAA,CAAA,gBAAA,GAAA,iBAAA;AACA,aAAA,QAAA,CAAA,OAAA,CAAA,YAAA;AACA,WAAA,QAAA;AACA,eAAA,QAAA,UAAA,WAAA;AACA;AACA,cAAA,kBAAA;AACA;AACA;AACA;AACA,cAAA,iBAAA;AACA;AACA;AACA;AACA,cAAA,QAAA;AACA;AACA;AACA,UAAU,GAAa,CAAA,KAAA,CAAO,GAAa,CAAA,YAAA,EAAe,GAAa,CAAA,eAAA;AACvE;AACA,gBAAgB,GAAa,CAAA,qBAAA,EAAwB,GAAa,CAAA,qBAAA,cAAoC,GAAa,uBAAuB,GAAa,CAAA,gBAAA;AACvJ,cAAA,QAAA,CAAA,aAAA;AACA,YAAA,QAAA,CAAA,gBAAA,GAAA,iBAAA;AACA,aAAA,QAAA,CAAA,OAAA,CAAA,YAAA;AACA,WAAA,QAAA;AACA,eAAA,QAAA,UAAA,WAAA;AACA;AACA,cAAA,kBAAA;AACA;AACA;AACA;AACA,cAAA,iBAAA;AACA;AACA;AACA;AACA,cAAA,QAAA;AACA;AACA;AACA,MAAA,QAAA;AACA;AACA;AACA;AACA,uBAAuB,GAAa,CAAA,uBAAA;;;;"}
1
+ {"version":3,"file":"index.d.ts","sources":["../src/types.ts","../src/toast.ts"],"sourcesContent":["import type { VNodeChild } from \"vue\";\r\n\r\nexport type SileoState =\r\n\t| \"success\"\r\n\t| \"loading\"\r\n\t| \"error\"\r\n\t| \"warning\"\r\n\t| \"info\"\r\n\t| \"action\";\r\n\r\nexport interface SileoStyles {\r\n\ttitle?: string;\r\n\tdescription?: string;\r\n\tbadge?: string;\r\n\tbutton?: string;\r\n\ttoast?: string;\r\n}\r\n\r\nexport interface SileoButton {\r\n\ttitle: string;\r\n\tonClick?: () => void;\r\n}\r\n\r\nexport interface SileoAutopilot {\r\n\texpand?: number;\r\n\tcollapse?: number;\r\n}\r\n\r\nexport interface SileoLifecycleContext {\r\n\tid: string;\r\n\tinstanceId: string;\r\n\tstate: SileoState;\r\n}\r\n\r\nexport interface SileoLifecycleHooks {\r\n\tonShow?: (ctx: SileoLifecycleContext) => void;\r\n\tonExpand?: (ctx: SileoLifecycleContext) => void;\r\n\tonCollapse?: (ctx: SileoLifecycleContext) => void;\r\n\tonDismiss?: (ctx: SileoLifecycleContext) => void;\r\n}\r\n\r\nexport const SILEO_POSITIONS = [\r\n\t\"top-left\",\r\n\t\"top-center\",\r\n\t\"top-right\",\r\n\t\"bottom-left\",\r\n\t\"bottom-center\",\r\n\t\"bottom-right\",\r\n] as const;\r\n\r\nexport type SileoPosition = (typeof SILEO_POSITIONS)[number];\r\n\r\nexport interface SileoOptions {\r\n\tid?: string;\r\n\ttitle?: string;\r\n\tdescription?: VNodeChild | string;\r\n\ttype?: SileoState;\r\n\tposition?: SileoPosition;\r\n\tduration?: number | null;\r\n\ticon?: VNodeChild | null;\r\n\tstyles?: SileoStyles;\r\n\tfill?: string;\r\n\troundness?: number;\r\n\tautopilot?: boolean | SileoAutopilot;\r\n\tswipeToDismiss?: boolean;\r\n\tbutton?: SileoButton;\r\n\tgroupKey?: string;\r\n\thooks?: SileoLifecycleHooks;\r\n}\r\n\r\nexport type SileoOffsetValue = number | string;\r\nexport type SileoOffsetConfig = Partial<\r\n\tRecord<\"top\" | \"right\" | \"bottom\" | \"left\", SileoOffsetValue>\r\n>;\r\n","import {\r\n Teleport,\r\n computed,\r\n defineComponent,\r\n h,\r\n onBeforeUnmount,\r\n onMounted,\r\n ref,\r\n watch,\r\n type ComponentPublicInstance,\r\n type CSSProperties,\r\n type PropType,\r\n} from \"vue\";\r\nimport {\r\n AUTOPILOT_COLLAPSE_DELAY,\r\n AUTOPILOT_EXPAND_DELAY,\r\n BLUR_RATIO,\r\n DEFAULT_ROUNDNESS,\r\n DEFAULT_TOAST_DURATION,\r\n EXIT_DURATION,\r\n GROUP_THRESHOLD,\r\n MIN_EXPAND_RATIO,\r\n PILL_PADDING,\r\n TOAST_HEIGHT,\r\n TOAST_WIDTH,\r\n} from \"./constants\";\r\nimport type {\r\n SileoLifecycleContext,\r\n SileoOffsetConfig,\r\n SileoOffsetValue,\r\n SileoOptions,\r\n SileoPosition,\r\n SileoState,\r\n} from \"./types\";\r\n\r\ninterface SileoItem extends Omit<SileoOptions, \"type\"> {\r\n id: string;\r\n instanceId: string;\r\n state: SileoState;\r\n createdAt: number;\r\n exiting?: boolean;\r\n}\r\n\r\ntype AutopilotConfig = {\r\n expand: number;\r\n collapse: number;\r\n};\r\n\r\ntype Listener = (toasts: SileoItem[]) => void;\r\n\r\nconst store = {\r\n toasts: [] as SileoItem[],\r\n listeners: new Set<Listener>(),\r\n position: \"top-right\" as SileoPosition,\r\n options: undefined as Partial<SileoOptions> | undefined,\r\n\r\n emit() {\r\n for (const listener of this.listeners) listener(this.toasts);\r\n },\r\n\r\n update(updater: (prev: SileoItem[]) => SileoItem[]) {\r\n this.toasts = updater(this.toasts);\r\n this.emit();\r\n },\r\n};\r\n\r\nconst dismissalTimers = new Map<string, ReturnType<typeof setTimeout>>();\r\nlet sequence = 0;\r\n\r\nconst nextId = () => `sileo-${++sequence}-${Date.now().toString(36)}`;\r\nconst nextInstanceId = () => `instance-${Math.random().toString(36).slice(2, 10)}-${Date.now().toString(36)}`;\r\n\r\nconst clearTimer = (instanceId: string) => {\r\n const timer = dismissalTimers.get(instanceId);\r\n if (timer !== undefined) {\r\n clearTimeout(timer);\r\n dismissalTimers.delete(instanceId);\r\n }\r\n};\r\n\r\nconst removeByInstance = (instanceId: string) => {\r\n clearTimer(instanceId);\r\n store.update((prev) => prev.filter((item) => item.instanceId !== instanceId));\r\n};\r\n\r\nconst dismissByInstance = (instanceId: string) => {\r\n const target = store.toasts.find(\r\n (item) => item.instanceId === instanceId && !item.exiting,\r\n );\r\n if (!target) return;\r\n\r\n clearTimer(instanceId);\r\n target.hooks?.onDismiss?.(toLifecycleContext(target));\r\n store.update((prev) =>\r\n prev.map((item) =>\r\n item.instanceId === instanceId ? { ...item, exiting: true } : item,\r\n ),\r\n );\r\n\r\n globalThis.setTimeout(() => {\r\n removeByInstance(instanceId);\r\n }, EXIT_DURATION);\r\n};\r\n\r\nconst scheduleDismiss = (item: SileoItem) => {\r\n clearTimer(item.instanceId);\r\n if (item.duration === null) return;\r\n const duration = item.duration ?? DEFAULT_TOAST_DURATION;\r\n if (duration <= 0) return;\r\n\r\n const timer = globalThis.setTimeout(() => {\r\n dismissByInstance(item.instanceId);\r\n }, duration);\r\n\r\n dismissalTimers.set(item.instanceId, timer);\r\n};\r\n\r\nconst mergeOptions = (options: SileoOptions): SileoOptions => ({\r\n ...store.options,\r\n ...options,\r\n styles: { ...store.options?.styles, ...options.styles },\r\n});\r\n\r\nconst buildItem = (options: SileoOptions, state: SileoState): SileoItem => ({\r\n id: options.id ?? nextId(),\r\n instanceId: nextInstanceId(),\r\n state,\r\n title: options.title,\r\n description: options.description,\r\n position: options.position ?? store.position,\r\n duration: options.duration,\r\n icon: options.icon,\r\n styles: options.styles,\r\n fill: options.fill,\r\n roundness: options.roundness,\r\n autopilot: options.autopilot,\r\n swipeToDismiss: options.swipeToDismiss,\r\n button: options.button,\r\n groupKey: options.groupKey,\r\n hooks: options.hooks,\r\n createdAt: Date.now(),\r\n});\r\n\r\nconst createToast = (raw: SileoOptions, forcedState?: SileoState) => {\r\n const merged = mergeOptions(raw);\r\n const state = forcedState ?? merged.type ?? \"info\";\r\n const hasCustomId = Boolean(merged.id);\r\n\r\n if (hasCustomId) {\r\n const existing = store.toasts.find(\r\n (item) => item.id === merged.id && !item.exiting,\r\n );\r\n if (existing) {\r\n const replacement = buildItem(merged, state);\r\n replacement.id = existing.id;\r\n store.update((prev) =>\r\n prev.map((item) =>\r\n item.instanceId === existing.instanceId ? replacement : item,\r\n ),\r\n );\r\n clearTimer(existing.instanceId);\r\n scheduleDismiss(replacement);\r\n replacement.hooks?.onShow?.(toLifecycleContext(replacement));\r\n return existing.id;\r\n }\r\n }\r\n\r\n const item = buildItem(merged, state);\r\n store.update((prev) => [...prev, item]);\r\n scheduleDismiss(item);\r\n item.hooks?.onShow?.(toLifecycleContext(item));\r\n return item.id;\r\n};\r\n\r\nconst dismissToast = (id?: string) => {\r\n if (!id) {\r\n for (const item of store.toasts) dismissByInstance(item.instanceId);\r\n return;\r\n }\r\n\r\n const active = store.toasts\r\n .filter((item) => item.id === id && !item.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n\r\n if (!active) return;\r\n dismissByInstance(active.instanceId);\r\n};\r\n\r\nconst clearToasts = (position?: SileoPosition) => {\r\n const removed = position\r\n ? store.toasts.filter((item) => item.position === position)\r\n : [...store.toasts];\r\n\r\n for (const item of removed) clearTimer(item.instanceId);\r\n\r\n store.update((prev) =>\r\n position ? prev.filter((item) => item.position !== position) : [],\r\n );\r\n};\r\n\r\nconst toLifecycleContext = (item: SileoItem): SileoLifecycleContext => ({\r\n id: item.id,\r\n instanceId: item.instanceId,\r\n state: item.state,\r\n});\r\n\r\nconst resolveTheme = (theme: \"light\" | \"dark\" | \"system\" | undefined) => {\r\n if (theme === \"light\" || theme === \"dark\") return theme;\r\n if (typeof window === \"undefined\") return \"light\";\r\n if (typeof window.matchMedia !== \"function\") return \"light\";\r\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches\r\n ? \"dark\"\r\n : \"light\";\r\n};\r\n\r\nconst normalizeAutopilot = (\r\n value: SileoOptions[\"autopilot\"],\r\n): AutopilotConfig | null => {\r\n if (value === false) return null;\r\n if (value === true || value === undefined) {\r\n return {\r\n expand: AUTOPILOT_EXPAND_DELAY,\r\n collapse: AUTOPILOT_COLLAPSE_DELAY,\r\n };\r\n }\r\n\r\n const expand = Number.isFinite(value.expand)\r\n ? Math.max(0, Number(value.expand))\r\n : AUTOPILOT_EXPAND_DELAY;\r\n const collapse = Number.isFinite(value.collapse)\r\n ? Math.max(expand + 200, Number(value.collapse))\r\n : AUTOPILOT_COLLAPSE_DELAY;\r\n\r\n return { expand, collapse };\r\n};\r\n\r\nconst renderStateIcon = (state: SileoState) => {\r\n const common = {\r\n viewBox: \"0 0 16 16\",\r\n width: \"14\",\r\n height: \"14\",\r\n \"aria-hidden\": \"true\",\r\n focusable: \"false\",\r\n } as const;\r\n\r\n if (state === \"success\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M3.2 8.4L6.5 11.4L12.8 4.9\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"error\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M5 5L11 11M11 5L5 11\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"warning\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M8 3.2L13 12.5H3L8 3.2Z\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.4\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 6.2V9\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.5\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n h(\"circle\", { cx: \"8\", cy: \"11\", r: \"0.9\", fill: \"currentColor\" }),\r\n ]);\r\n }\r\n if (state === \"action\") {\r\n return h(\"svg\", common, [\r\n h(\"path\", {\r\n d: \"M3.5 8H12.5M9.2 4.8L12.4 8L9.2 11.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n \"stroke-linejoin\": \"round\",\r\n }),\r\n ]);\r\n }\r\n if (state === \"loading\") {\r\n return h(\"svg\", { ...common, \"data-sileo-icon\": \"spin\" }, [\r\n h(\"circle\", {\r\n cx: \"8\",\r\n cy: \"8\",\r\n r: \"5\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n opacity: \"0.35\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 3A5 5 0 0 1 13 8\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.8\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n ]);\r\n }\r\n\r\n return h(\"svg\", common, [\r\n h(\"circle\", {\r\n cx: \"8\",\r\n cy: \"8\",\r\n r: \"5.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n }),\r\n h(\"path\", {\r\n d: \"M8 6.8V10.2\",\r\n fill: \"none\",\r\n stroke: \"currentColor\",\r\n \"stroke-width\": \"1.6\",\r\n \"stroke-linecap\": \"round\",\r\n }),\r\n h(\"circle\", { cx: \"8\", cy: \"4.9\", r: \"0.9\", fill: \"currentColor\" }),\r\n ]);\r\n};\r\n\r\nconst getOffsetStyle = (\r\n position: SileoPosition,\r\n offset?: SileoOffsetValue | SileoOffsetConfig,\r\n): CSSProperties | undefined => {\r\n if (offset === undefined) return undefined;\r\n\r\n const value =\r\n typeof offset === \"object\"\r\n ? offset\r\n : { top: offset, right: offset, bottom: offset, left: offset };\r\n\r\n const style: CSSProperties = {};\r\n const px = (v: SileoOffsetValue) => (typeof v === \"number\" ? `${v}px` : v);\r\n\r\n if (position.startsWith(\"top\") && value.top !== undefined) style.top = px(value.top);\r\n if (position.startsWith(\"bottom\") && value.bottom !== undefined) {\r\n style.bottom = px(value.bottom);\r\n }\r\n if (position.endsWith(\"left\") && value.left !== undefined) style.left = px(value.left);\r\n if (position.endsWith(\"right\") && value.right !== undefined) {\r\n style.right = px(value.right);\r\n }\r\n\r\n return style;\r\n};\r\n\r\nexport interface SileoPromiseOptions<T = unknown> {\r\n loading: SileoOptions;\r\n success: SileoOptions | ((data: T) => SileoOptions);\r\n error: SileoOptions | ((error: unknown) => SileoOptions);\r\n action?: SileoOptions | ((data: T) => SileoOptions);\r\n position?: SileoPosition;\r\n}\r\n\r\nexport interface SileoToasterProps {\r\n position?: SileoPosition;\r\n offset?: SileoOffsetValue | SileoOffsetConfig;\r\n options?: Partial<SileoOptions>;\r\n theme?: \"light\" | \"dark\" | \"system\";\r\n container?: string | HTMLElement;\r\n grouping?: boolean;\r\n groupThreshold?: number;\r\n ariaLive?: \"off\" | \"polite\" | \"assertive\";\r\n}\r\n\r\nexport const sileo = {\r\n show: (opts: SileoOptions) => createToast(opts, opts.type ?? \"info\"),\r\n success: (opts: SileoOptions) => createToast(opts, \"success\"),\r\n error: (opts: SileoOptions) => createToast(opts, \"error\"),\r\n warning: (opts: SileoOptions) => createToast(opts, \"warning\"),\r\n info: (opts: SileoOptions) => createToast(opts, \"info\"),\r\n action: (opts: SileoOptions) => createToast(opts, \"action\"),\r\n loading: (opts: SileoOptions) =>\r\n createToast({ ...opts, duration: opts.duration ?? null }, \"loading\"),\r\n\r\n promise: async <T,>(\r\n promise: Promise<T> | (() => Promise<T>),\r\n opts: SileoPromiseOptions<T>,\r\n ): Promise<T> => {\r\n const id = createToast(\r\n { ...opts.loading, duration: null, position: opts.position },\r\n \"loading\",\r\n );\r\n try {\r\n const data = await (typeof promise === \"function\" ? promise() : promise);\r\n const next = opts.action\r\n ? typeof opts.action === \"function\"\r\n ? opts.action(data)\r\n : opts.action\r\n : typeof opts.success === \"function\"\r\n ? opts.success(data)\r\n : opts.success;\r\n createToast({ ...next, id }, opts.action ? \"action\" : \"success\");\r\n return data;\r\n } catch (error) {\r\n const next =\r\n typeof opts.error === \"function\" ? opts.error(error) : opts.error;\r\n createToast({ ...next, id }, \"error\");\r\n throw error;\r\n }\r\n },\r\n\r\n configure: (options: Partial<SileoOptions> & { position?: SileoPosition }) => {\r\n if (options.position) {\r\n store.position = options.position;\r\n }\r\n store.options = {\r\n ...store.options,\r\n ...options,\r\n styles: { ...store.options?.styles, ...options.styles },\r\n };\r\n },\r\n\r\n dismiss: dismissToast,\r\n clear: clearToasts,\r\n};\r\n\r\nexport const Toaster = defineComponent({\r\n name: \"SileoToaster\",\r\n props: {\r\n position: String as PropType<SileoPosition | undefined>,\r\n offset: [Number, String, Object] as PropType<\r\n SileoOffsetValue | SileoOffsetConfig\r\n >,\r\n options: Object as PropType<Partial<SileoOptions>>,\r\n theme: String as PropType<\"light\" | \"dark\" | \"system\">,\r\n container: [String, Object] as PropType<string | HTMLElement>,\r\n grouping: { type: Boolean, default: false },\r\n groupThreshold: { type: Number, default: GROUP_THRESHOLD },\r\n ariaLive: {\r\n type: String as PropType<\"off\" | \"polite\" | \"assertive\">,\r\n default: \"polite\",\r\n },\r\n },\r\n setup(props, { slots }) {\r\n const toasts = ref<SileoItem[]>(store.toasts);\r\n const expandedGroups = ref<Record<string, boolean>>({});\r\n const expandedToasts = ref<Record<string, boolean>>({});\r\n const autopilotTimers = new Map<string, ReturnType<typeof setTimeout>[]>();\r\n const activeToastInstanceId = ref<string | undefined>(undefined);\r\n const contentHeights = ref<Record<string, number>>({});\r\n const contentRefs = new Map<string, HTMLElement>();\r\n const contentObservers = new Map<string, ResizeObserver>();\r\n\r\n // Pill inner measurement (drives SVG pill width)\r\n const readyToasts = ref<Record<string, boolean>>({});\r\n const pillWidths = ref<Record<string, number>>({});\r\n const pillInnerRefs = new Map<string, HTMLElement>();\r\n const pillInnerObservers = new Map<string, ResizeObserver>();\r\n\r\n const measureContentHeight = (instanceId: string) => {\r\n const el = contentRefs.get(instanceId);\r\n if (!el) return;\r\n const measured = Math.max(0, el.scrollHeight);\r\n if (contentHeights.value[instanceId] !== measured) {\r\n contentHeights.value[instanceId] = measured;\r\n }\r\n };\r\n\r\n const setContentRef = (instanceId: string, el: HTMLElement | null) => {\r\n if (el) {\r\n contentRefs.set(instanceId, el);\r\n measureContentHeight(instanceId);\r\n if (typeof ResizeObserver === \"function\") {\r\n if (!contentObservers.has(instanceId)) {\r\n const observer = new ResizeObserver(() => {\r\n measureContentHeight(instanceId);\r\n });\r\n contentObservers.set(instanceId, observer);\r\n }\r\n contentObservers.get(instanceId)?.observe(el);\r\n }\r\n return;\r\n }\r\n\r\n const previous = contentRefs.get(instanceId);\r\n const observer = contentObservers.get(instanceId);\r\n if (previous && observer) observer.unobserve(previous);\r\n contentRefs.delete(instanceId);\r\n };\r\n\r\n const clearContentObserver = (instanceId: string) => {\r\n const observer = contentObservers.get(instanceId);\r\n if (observer) {\r\n observer.disconnect();\r\n contentObservers.delete(instanceId);\r\n }\r\n contentRefs.delete(instanceId);\r\n delete contentHeights.value[instanceId];\r\n };\r\n\r\n const measurePillInner = (instanceId: string) => {\r\n const el = pillInnerRefs.get(instanceId);\r\n if (!el) return;\r\n const header = el.closest('[data-sileo-header]') as HTMLElement | null;\r\n const hPad = header\r\n ? parseFloat(getComputedStyle(header).paddingLeft) + parseFloat(getComputedStyle(header).paddingRight)\r\n : 16;\r\n const w = el.scrollWidth + hPad;\r\n if (pillWidths.value[instanceId] !== w) {\r\n pillWidths.value[instanceId] = w;\r\n }\r\n };\r\n\r\n const setPillInnerRef = (instanceId: string, el: HTMLElement | null) => {\r\n if (el) {\r\n pillInnerRefs.set(instanceId, el);\r\n measurePillInner(instanceId);\r\n if (typeof ResizeObserver === 'function' && !pillInnerObservers.has(instanceId)) {\r\n const observer = new ResizeObserver(() => measurePillInner(instanceId));\r\n pillInnerObservers.set(instanceId, observer);\r\n observer.observe(el);\r\n }\r\n return;\r\n }\r\n const prev = pillInnerRefs.get(instanceId);\r\n const obs = pillInnerObservers.get(instanceId);\r\n if (prev && obs) obs.unobserve(prev);\r\n pillInnerRefs.delete(instanceId);\r\n };\r\n\r\n const clearPillInnerObserver = (instanceId: string) => {\r\n const obs = pillInnerObservers.get(instanceId);\r\n if (obs) { obs.disconnect(); pillInnerObservers.delete(instanceId); }\r\n pillInnerRefs.delete(instanceId);\r\n delete pillWidths.value[instanceId];\r\n };\r\n\r\n const scheduleReady = (instanceId: string) => {\r\n globalThis.requestAnimationFrame(() => {\r\n if (pillInnerRefs.has(instanceId) && toasts.value.some(t => t.instanceId === instanceId)) {\r\n readyToasts.value[instanceId] = true;\r\n }\r\n });\r\n };\r\n\r\n const setExpanded = (instanceId: string, expanded: boolean) => {\r\n const previous = expandedToasts.value[instanceId];\r\n if (previous === expanded) return;\r\n\r\n expandedToasts.value[instanceId] = expanded;\r\n if (previous === undefined) return;\r\n\r\n const item = toasts.value.find((toast) => toast.instanceId === instanceId);\r\n if (!item) return;\r\n\r\n if (expanded) {\r\n item.hooks?.onExpand?.(toLifecycleContext(item));\r\n } else {\r\n item.hooks?.onCollapse?.(toLifecycleContext(item));\r\n }\r\n };\r\n\r\n const clearAutopilotTimers = (instanceId: string) => {\r\n const timers = autopilotTimers.get(instanceId);\r\n if (!timers) return;\r\n for (const timer of timers) clearTimeout(timer);\r\n autopilotTimers.delete(instanceId);\r\n };\r\n\r\n const scheduleAutopilot = (item: SileoItem) => {\r\n clearAutopilotTimers(item.instanceId);\r\n const config = normalizeAutopilot(item.autopilot);\r\n if (!item.description || !config) {\r\n setExpanded(item.instanceId, false);\r\n return;\r\n }\r\n\r\n if (\r\n activeToastInstanceId.value !== undefined &&\r\n activeToastInstanceId.value !== item.instanceId\r\n ) {\r\n setExpanded(item.instanceId, false);\r\n return;\r\n }\r\n\r\n setExpanded(item.instanceId, false);\r\n const timers: ReturnType<typeof setTimeout>[] = [];\r\n\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, true);\r\n }, config.expand),\r\n );\r\n\r\n const duration = item.duration ?? DEFAULT_TOAST_DURATION;\r\n if (item.duration !== null) {\r\n const maxCollapse = Math.max(\r\n config.expand + 350,\r\n duration - EXIT_DURATION - 150,\r\n );\r\n const collapseAt = Math.min(config.collapse, maxCollapse);\r\n if (collapseAt > config.expand) {\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, false);\r\n }, collapseAt),\r\n );\r\n }\r\n } else if (config.collapse > config.expand) {\r\n timers.push(\r\n globalThis.setTimeout(() => {\r\n setExpanded(item.instanceId, false);\r\n }, config.collapse),\r\n );\r\n }\r\n\r\n autopilotTimers.set(item.instanceId, timers);\r\n };\r\n\r\n const listener: Listener = (next) => {\r\n toasts.value = next;\r\n };\r\n\r\n onMounted(() => {\r\n store.listeners.add(listener);\r\n if (props.position) store.position = props.position;\r\n if (props.options) {\r\n store.options = {\r\n ...store.options,\r\n ...props.options,\r\n styles: { ...store.options?.styles, ...props.options.styles },\r\n };\r\n }\r\n });\r\n\r\n onBeforeUnmount(() => {\r\n store.listeners.delete(listener);\r\n for (const instanceId of autopilotTimers.keys()) {\r\n clearAutopilotTimers(instanceId);\r\n }\r\n for (const instanceId of contentObservers.keys()) {\r\n clearContentObserver(instanceId);\r\n }\r\n for (const instanceId of pillInnerObservers.keys()) {\r\n clearPillInnerObserver(instanceId);\r\n }\r\n });\r\n\r\n watch(\r\n () => props.position,\r\n (value) => {\r\n if (value) store.position = value;\r\n },\r\n );\r\n\r\n watch(\r\n () => props.options,\r\n (value) => {\r\n if (!value) return;\r\n store.options = {\r\n ...store.options,\r\n ...value,\r\n styles: { ...store.options?.styles, ...value.styles },\r\n };\r\n },\r\n { deep: true },\r\n );\r\n\r\n watch(\r\n () => toasts.value,\r\n (items) => {\r\n const active = new Set(items.map((item) => item.instanceId));\r\n const latest = [...items]\r\n .filter((item) => !item.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n\r\n activeToastInstanceId.value = latest?.instanceId;\r\n\r\n for (const item of items) {\r\n if (expandedToasts.value[item.instanceId] === undefined) {\r\n setExpanded(item.instanceId, false);\r\n }\r\n }\r\n\r\n for (const instanceId of Object.keys(expandedToasts.value)) {\r\n if (!active.has(instanceId)) {\r\n clearAutopilotTimers(instanceId);\r\n delete expandedToasts.value[instanceId];\r\n clearContentObserver(instanceId);\r\n clearPillInnerObserver(instanceId);\r\n delete readyToasts.value[instanceId];\r\n }\r\n }\r\n },\r\n { immediate: true },\r\n );\r\n\r\n watch(\r\n () => activeToastInstanceId.value,\r\n (activeInstanceId) => {\r\n for (const instanceId of Object.keys(expandedToasts.value)) {\r\n if (instanceId !== activeInstanceId) {\r\n clearAutopilotTimers(instanceId);\r\n setExpanded(instanceId, false);\r\n }\r\n }\r\n\r\n if (!activeInstanceId) return;\r\n const item = toasts.value.find((toast) => toast.instanceId === activeInstanceId);\r\n if (item) {\r\n scheduleAutopilot(item);\r\n }\r\n },\r\n );\r\n\r\n const groupedByPosition = computed(() => {\r\n const map = new Map<SileoPosition, SileoItem[]>();\r\n for (const item of toasts.value) {\r\n const pos = item.position ?? store.position;\r\n const list = map.get(pos);\r\n if (list) {\r\n list.push(item);\r\n } else {\r\n map.set(pos, [item]);\r\n }\r\n }\r\n return map;\r\n });\r\n\r\n const renderToast = (item: SileoItem) => {\r\n const r = Math.max(0, item.roundness ?? DEFAULT_ROUNDNESS);\r\n const isExpanded = Boolean(expandedToasts.value[item.instanceId]);\r\n const isReady = Boolean(readyToasts.value[item.instanceId]);\r\n const swipeEnabled = item.swipeToDismiss ?? true;\r\n const pos = item.position ?? store.position;\r\n const align: 'left' | 'center' | 'right' =\r\n pos.endsWith('left') ? 'left'\r\n : pos.endsWith('right') ? 'right'\r\n : 'center';\r\n const expandDir: 'top' | 'bottom' = pos.startsWith('top') ? 'bottom' : 'top';\r\n\r\n const hasDesc = Boolean(item.description || item.button);\r\n const isLoading = item.state === 'loading';\r\n const isOpen = hasDesc && isExpanded && !isLoading;\r\n\r\n const blur = r * BLUR_RATIO;\r\n const rawPillW = pillWidths.value[item.instanceId] ?? 0;\r\n const resolvedPillWidth = Math.max(rawPillW + PILL_PADDING, TOAST_HEIGHT);\r\n const pillH = TOAST_HEIGHT + blur * 3;\r\n\r\n const pillX = align === 'right' ? TOAST_WIDTH - resolvedPillWidth\r\n : align === 'center' ? (TOAST_WIDTH - resolvedPillWidth) / 2\r\n : 0;\r\n\r\n const contentH = contentHeights.value[item.instanceId] ?? 0;\r\n const minExpanded = TOAST_HEIGHT * MIN_EXPAND_RATIO;\r\n const expanded = hasDesc ? Math.max(minExpanded, TOAST_HEIGHT + contentH) : minExpanded;\r\n const expandedContent = Math.max(0, expanded - TOAST_HEIGHT);\r\n const svgH = Math.max(expanded, TOAST_HEIGHT);\r\n\r\n const resolvedTheme = resolveTheme(props.theme);\r\n const fillColor = item.fill ?? (resolvedTheme === 'dark' ? '#1a1a1a' : '#f2f2f2');\r\n const filterId = `sileo-gooey-${item.instanceId.replace(/[^a-z0-9]/gi, '-')}`;\r\n\r\n const rootStyle: CSSProperties & Record<string, string> = {\r\n '--_h': `${isOpen ? expanded : TOAST_HEIGHT}px`,\r\n '--_pw': `${resolvedPillWidth}px`,\r\n '--_px': `${pillX}px`,\r\n '--_ht': `translateY(${isOpen ? (expandDir === 'bottom' ? 3 : -3) : 0}px) scale(${isOpen ? 0.9 : 1})`,\r\n '--_co': isOpen ? '1' : '0',\r\n };\r\n\r\n const swipeState = {\r\n startY: 0,\r\n pointerId: -1,\r\n dragging: false,\r\n };\r\n\r\n const resetSwipe = (el: HTMLElement) => {\r\n el.style.transform = \"\";\r\n el.style.opacity = \"\";\r\n el.style.transition = \"\";\r\n };\r\n\r\n return h(\r\n \"button\",\r\n {\r\n key: item.instanceId,\r\n type: \"button\",\r\n \"data-sileo-toast\": \"true\",\r\n \"data-state\": item.state,\r\n \"data-exiting\": item.exiting ? \"true\" : \"false\",\r\n \"data-expanded\": isExpanded ? \"true\" : \"false\",\r\n \"data-ready\": isReady ? \"true\" : \"false\",\r\n class: item.styles?.toast,\r\n style: rootStyle,\r\n onMouseenter: () => {\r\n activeToastInstanceId.value = item.instanceId;\r\n setExpanded(item.instanceId, true);\r\n },\r\n onMouseleave: () => {\r\n const autopilot = normalizeAutopilot(item.autopilot);\r\n const latest = [...toasts.value]\r\n .filter((toast) => !toast.exiting)\r\n .sort((a, b) => b.createdAt - a.createdAt)[0];\r\n activeToastInstanceId.value = latest?.instanceId;\r\n if (autopilot !== null) setExpanded(item.instanceId, false);\r\n },\r\n onPointerdown: (e: PointerEvent) => {\r\n if (!swipeEnabled || item.state === \"loading\") return;\r\n const target = e.target as HTMLElement;\r\n if (target.closest(\"[data-sileo-button='true'], .sileo-dismiss\")) {\r\n return;\r\n }\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.startY = e.clientY;\r\n swipeState.pointerId = e.pointerId;\r\n swipeState.dragging = true;\r\n el.setPointerCapture(e.pointerId);\r\n },\r\n onPointermove: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n const deltaY = e.clientY - swipeState.startY;\r\n const clamped = Math.max(-22, Math.min(22, deltaY));\r\n el.style.transform = `translateY(${clamped}px)`;\r\n const alpha = 1 - Math.min(0.35, Math.abs(clamped) / 60);\r\n el.style.opacity = String(alpha);\r\n },\r\n onPointerup: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.dragging = false;\r\n const deltaY = e.clientY - swipeState.startY;\r\n if (el.hasPointerCapture(e.pointerId)) {\r\n el.releasePointerCapture(e.pointerId);\r\n }\r\n if (Math.abs(deltaY) > 34) {\r\n dismissByInstance(item.instanceId);\r\n return;\r\n }\r\n el.style.transition = \"transform 150ms ease, opacity 150ms ease\";\r\n resetSwipe(el);\r\n },\r\n onPointercancel: (e: PointerEvent) => {\r\n if (!swipeEnabled || !swipeState.dragging) return;\r\n if (e.pointerId !== swipeState.pointerId) return;\r\n const el = e.currentTarget as HTMLElement;\r\n swipeState.dragging = false;\r\n if (el.hasPointerCapture(e.pointerId)) {\r\n el.releasePointerCapture(e.pointerId);\r\n }\r\n el.style.transition = \"transform 150ms ease, opacity 150ms ease\";\r\n resetSwipe(el);\r\n },\r\n },\r\n [\r\n // SVG gooey canvas\r\n h(\"div\", {\r\n \"data-sileo-canvas\": \"true\",\r\n \"data-edge\": expandDir,\r\n style: { filter: `url(#${filterId})` } as CSSProperties,\r\n }, [\r\n h(\"svg\", {\r\n \"data-sileo-svg\": \"true\",\r\n width: String(TOAST_WIDTH),\r\n height: String(svgH),\r\n viewBox: `0 0 ${TOAST_WIDTH} ${svgH}`,\r\n xmlns: \"http://www.w3.org/2000/svg\",\r\n \"aria-hidden\": \"true\",\r\n focusable: \"false\",\r\n }, [\r\n h(\"defs\", {}, [\r\n h(\"filter\", {\r\n id: filterId,\r\n x: \"-20%\",\r\n y: \"-20%\",\r\n width: \"140%\",\r\n height: \"140%\",\r\n \"color-interpolation-filters\": \"sRGB\",\r\n }, [\r\n h(\"feGaussianBlur\", {\r\n in: \"SourceGraphic\",\r\n stdDeviation: String(blur),\r\n result: \"blur\",\r\n }),\r\n h(\"feColorMatrix\", {\r\n in: \"blur\",\r\n mode: \"matrix\",\r\n values: \"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10\",\r\n result: \"goo\",\r\n }),\r\n h(\"feComposite\", {\r\n in: \"SourceGraphic\",\r\n in2: \"goo\",\r\n operator: \"atop\",\r\n }),\r\n ]),\r\n ]),\r\n h(\"rect\", {\r\n \"data-sileo-pill\": \"true\",\r\n rx: String(r),\r\n ry: String(r),\r\n fill: fillColor,\r\n style: {\r\n x: `${pillX}px`,\r\n width: `${resolvedPillWidth}px`,\r\n height: `${isOpen ? pillH : TOAST_HEIGHT}px`,\r\n } as CSSProperties,\r\n }),\r\n h(\"rect\", {\r\n \"data-sileo-body\": \"true\",\r\n width: String(TOAST_WIDTH),\r\n rx: String(r),\r\n ry: String(r),\r\n fill: fillColor,\r\n style: {\r\n y: `${TOAST_HEIGHT}px`,\r\n height: `${isOpen ? expandedContent : 0}px`,\r\n opacity: isOpen ? \"1\" : \"0\",\r\n } as CSSProperties,\r\n }),\r\n ]),\r\n ]),\r\n\r\n // Header\r\n h(\"div\", {\r\n \"data-sileo-header\": \"true\",\r\n \"data-edge\": expandDir,\r\n }, [\r\n h(\"div\", { \"data-sileo-header-stack\": \"true\" }, [\r\n h(\"div\", {\r\n \"data-sileo-header-inner\": \"true\",\r\n ref: (el: Element | ComponentPublicInstance | null) => {\r\n setPillInnerRef(item.instanceId, el as HTMLElement | null);\r\n if (el) scheduleReady(item.instanceId);\r\n },\r\n }, [\r\n h(\"div\", {\r\n \"data-sileo-badge\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.badge,\r\n }, item.icon ?? renderStateIcon(item.state)),\r\n h(\"span\", {\r\n \"data-sileo-title\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.title,\r\n }, item.title),\r\n h(\"button\", {\r\n type: \"button\",\r\n class: \"sileo-dismiss\",\r\n onClick: (e: MouseEvent) => {\r\n e.stopPropagation();\r\n dismissByInstance(item.instanceId);\r\n },\r\n \"aria-label\": \"Dismiss toast\",\r\n }, \"×\"),\r\n ]),\r\n ]),\r\n ]),\r\n\r\n // Content\r\n hasDesc\r\n ? h(\"div\", {\r\n \"data-sileo-content\": \"true\",\r\n \"data-edge\": expandDir,\r\n \"data-visible\": isOpen ? \"true\" : \"false\",\r\n }, [\r\n h(\"div\", {\r\n \"data-sileo-description\": \"true\",\r\n class: item.styles?.description,\r\n ref: (el: Element | ComponentPublicInstance | null) => {\r\n setContentRef(item.instanceId, el as HTMLElement | null);\r\n },\r\n }, [\r\n item.description\r\n ? h(\"span\", {}, item.description)\r\n : null,\r\n item.button\r\n ? h(\"button\", {\r\n type: \"button\",\r\n \"data-sileo-button\": \"true\",\r\n \"data-state\": item.state,\r\n class: item.styles?.button,\r\n onClick: (e: MouseEvent) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n item.button?.onClick?.();\r\n },\r\n }, item.button.title)\r\n : null,\r\n ]),\r\n ])\r\n : null,\r\n ],\r\n );\r\n };\r\n\r\n const renderPosition = (position: SileoPosition, items: SileoItem[]) => {\r\n const buckets = new Map<string, SileoItem[]>();\r\n for (const item of items) {\r\n const key = item.groupKey ?? \"__default__\";\r\n const list = buckets.get(key);\r\n if (list) {\r\n list.push(item);\r\n } else {\r\n buckets.set(key, [item]);\r\n }\r\n }\r\n\r\n const children: ReturnType<typeof h>[] = [];\r\n for (const [bucketKey, bucketItems] of buckets) {\r\n const visible = bucketItems.filter((item) => !item.exiting);\r\n const expandKey = `${position}:${bucketKey}`;\r\n const shouldGroup =\r\n props.grouping &&\r\n visible.length > props.groupThreshold &&\r\n !expandedGroups.value[expandKey];\r\n\r\n if (shouldGroup) {\r\n const label =\r\n bucketKey === \"__default__\"\r\n ? `${visible.length} notifications`\r\n : `${visible.length} ${bucketKey} notifications`;\r\n children.push(\r\n h(\r\n \"button\",\r\n {\r\n key: expandKey,\r\n type: \"button\",\r\n \"data-sileo-group\": \"true\",\r\n class: \"sileo-group\",\r\n onMouseenter: () => {\r\n expandedGroups.value[expandKey] = true;\r\n },\r\n onClick: () => {\r\n expandedGroups.value[expandKey] = true;\r\n },\r\n },\r\n label,\r\n ),\r\n );\r\n continue;\r\n }\r\n\r\n for (const item of bucketItems) {\r\n children.push(renderToast(item));\r\n }\r\n }\r\n\r\n return h(\r\n \"section\",\r\n {\r\n key: position,\r\n \"data-sileo-viewport\": \"true\",\r\n \"data-position\": position,\r\n \"data-theme\": resolveTheme(props.theme),\r\n \"aria-live\": props.ariaLive,\r\n \"aria-atomic\": \"true\",\r\n role: \"status\",\r\n style: getOffsetStyle(position, props.offset),\r\n },\r\n children,\r\n );\r\n };\r\n\r\n return () => {\r\n const viewports = Array.from(groupedByPosition.value.entries()).map(\r\n ([position, items]) => renderPosition(position, items),\r\n );\r\n\r\n const portalTarget = props.container ?? \"body\";\r\n\r\n return h(\"div\", { class: \"sileo-root\" }, [\r\n slots.default?.(),\r\n h(Teleport, { to: portalTarget }, viewports),\r\n ]);\r\n };\r\n },\r\n});\r\n"],"names":[],"mappings":";;;AACO,KAAA,UAAA;AACA,UAAA,WAAA;AACP;AACA;AACA;AACA;AACA;AACA;AACO,UAAA,WAAA;AACP;AACA;AACA;AACO,UAAA,cAAA;AACP;AACA;AACA;AACO,UAAA,qBAAA;AACP;AACA;AACA,WAAA,UAAA;AACA;AACO,UAAA,mBAAA;AACP,mBAAA,qBAAA;AACA,qBAAA,qBAAA;AACA,uBAAA,qBAAA;AACA,sBAAA,qBAAA;AACA;AACO,cAAA,eAAA;AACA,KAAA,aAAA,WAAA,eAAA;AACA,UAAA,YAAA;AACP;AACA;AACA,kBAAA,UAAA;AACA,WAAA,UAAA;AACA,eAAA,aAAA;AACA;AACA,WAAA,UAAA;AACA,aAAA,WAAA;AACA;AACA;AACA,0BAAA,cAAA;AACA;AACA,aAAA,WAAA;AACA;AACA,YAAA,mBAAA;AACA;AACO,KAAA,gBAAA;AACA,KAAA,iBAAA,GAAA,OAAA,CAAA,MAAA,sCAAA,gBAAA;;AC9CA,UAAA,mBAAA;AACP,aAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,WAAA,YAAA,wBAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,eAAA,aAAA;AACA;AAWO,cAAA,KAAA;AACP,iBAAA,YAAA;AACA,oBAAA,YAAA;AACA,kBAAA,YAAA;AACA,oBAAA,YAAA;AACA,iBAAA,YAAA;AACA,mBAAA,YAAA;AACA,oBAAA,YAAA;AACA,0BAAA,OAAA,aAAA,OAAA,YAAA,mBAAA,QAAA,OAAA;AACA,yBAAA,OAAA,CAAA,YAAA;AACA,mBAAA,aAAA;AACA;AACA;AACA,uBAAA,aAAA;AACA;AACO,cAAA,OAAA,EAAuB,GAAa,iBAAiB,GAAa,CAAA,gBAAA;AACzE,cAAA,QAAA,CAAA,aAAA;AACA,YAAA,QAAA,CAAA,gBAAA,GAAA,iBAAA;AACA,aAAA,QAAA,CAAA,OAAA,CAAA,YAAA;AACA,WAAA,QAAA;AACA,eAAA,QAAA,UAAA,WAAA;AACA;AACA,cAAA,kBAAA;AACA;AACA;AACA;AACA,cAAA,iBAAA;AACA;AACA;AACA;AACA,cAAA,QAAA;AACA;AACA;AACA,UAAU,GAAa,CAAA,KAAA,CAAO,GAAa,CAAA,YAAA,EAAe,GAAa,CAAA,eAAA;AACvE;AACA,gBAAgB,GAAa,CAAA,qBAAA,EAAwB,GAAa,CAAA,qBAAA,cAAoC,GAAa,uBAAuB,GAAa,CAAA,gBAAA;AACvJ,cAAA,QAAA,CAAA,aAAA;AACA,YAAA,QAAA,CAAA,gBAAA,GAAA,iBAAA;AACA,aAAA,QAAA,CAAA,OAAA,CAAA,YAAA;AACA,WAAA,QAAA;AACA,eAAA,QAAA,UAAA,WAAA;AACA;AACA,cAAA,kBAAA;AACA;AACA;AACA;AACA,cAAA,iBAAA;AACA;AACA;AACA;AACA,cAAA,QAAA;AACA;AACA;AACA,MAAA,QAAA;AACA;AACA;AACA;AACA,uBAAuB,GAAa,CAAA,uBAAA;;;;"}
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
13
13
  var cc = require('./cc-DGff5sSY.js');
14
14
  var vue = require('vue');
15
15
 
16
- __insertCSS(":root{--sileo-spring-easing:linear(0, 0.002 0.6%, 0.007 1.2%, 0.015 1.8%, 0.026 2.4%, 0.041 3.1%, 0.06 3.8%, 0.108 5.3%, 0.157 6.6%, 0.214 8%, 0.467 13.7%, 0.577 16.3%, 0.631 17.7%, 0.682 19.1%, 0.73 20.5%, 0.771 21.8%, 0.808 23.1%, 0.844 24.5%, 0.874 25.8%, 0.903 27.2%, 0.928 28.6%, 0.952 30.1%, 0.972 31.6%, 0.988 33.1%, 1.01 35.7%, 1.025 38.5%, 1.034 41.6%, 1.038 45%, 1.035 50.1%, 1.012 64.2%, 1.003 73%, 0.999 83.7%, 1);--sileo-duration:600ms;--sileo-height:40px;--sileo-width:350px;--sileo-state-success:oklch(0.723 0.219 142.136);--sileo-state-loading:oklch(0.556 0 0);--sileo-state-error:oklch(0.637 0.237 25.331);--sileo-state-warning:oklch(0.795 0.184 86.047);--sileo-state-info:oklch(0.685 0.169 237.323);--sileo-state-action:oklch(0.623 0.214 259.815)}.sileo-root{pointer-events:none}[data-sileo-toast]{position:relative;cursor:pointer;pointer-events:auto;touch-action:none;border:0;background:0 0;padding:0;width:var(--sileo-width);height:var(--_h,var(--sileo-height));opacity:0;transform:translateZ(0) scale(.95);transform-origin:center;contain:layout style;overflow:visible}[data-sileo-toast][data-state=loading]{cursor:default}[data-sileo-toast][data-ready=true]{opacity:1;transform:translateZ(0) scale(1);transition:transform calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-viewport][data-position^=top] [data-sileo-toast]:not([data-ready=true]){transform:translateY(-6px) scale(.95)}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast]:not([data-ready=true]){transform:translateY(6px) scale(.95)}[data-sileo-toast][data-ready=true][data-exiting=true]{opacity:0;pointer-events:none}[data-sileo-viewport][data-position^=top] [data-sileo-toast][data-ready=true][data-exiting=true]{transform:translateY(-6px) scale(.95)}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast][data-ready=true][data-exiting=true]{transform:translateY(6px) scale(.95)}[data-sileo-canvas]{position:absolute;left:0;right:0;pointer-events:none;transform:translateZ(0);contain:layout style;overflow:visible}[data-sileo-canvas][data-edge=top]{bottom:0;transform:scaleY(-1) translateZ(0)}[data-sileo-canvas][data-edge=bottom]{top:0}[data-sileo-svg]{overflow:visible}[data-sileo-pill]{transition:x var(--sileo-duration) var(--sileo-spring-easing),width var(--sileo-duration) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-toast]:not([data-ready=true]) [data-sileo-pill]{transition:none}[data-sileo-body]{transition:height var(--sileo-duration) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .08) ease calc(var(--sileo-duration) * .04)}[data-sileo-toast][data-expanded=true] [data-sileo-body]{transition:height var(--sileo-duration) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .6) ease calc(var(--sileo-duration) * .3)}[data-sileo-toast]:not([data-ready=true]) [data-sileo-body]{transition:none}[data-sileo-header]{position:absolute;z-index:20;display:flex;align-items:center;padding:.5rem;height:var(--sileo-height);overflow:hidden;left:var(--_px,0);transform:var(--_ht);max-width:var(--_pw)}[data-sileo-toast][data-ready=true] [data-sileo-header]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),left var(--sileo-duration) var(--sileo-spring-easing),max-width var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-header][data-edge=top]{bottom:0}[data-sileo-header][data-edge=bottom]{top:0}[data-sileo-header-stack]{position:relative;display:inline-flex;align-items:center;height:100%}[data-sileo-header-inner]{display:flex;align-items:center;gap:.5rem;white-space:nowrap}[data-sileo-badge]{display:flex;height:24px;width:24px;flex-shrink:0;align-items:center;justify-content:center;padding:2px;box-sizing:border-box;border-radius:9999px;color:var(--sileo-tone,currentColor);background-color:var(--sileo-tone-bg,transparent)}[data-sileo-title]{font-size:.825rem;line-height:1rem;font-weight:500;text-transform:capitalize;color:var(--sileo-tone,currentColor)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state]{--_c:var(--sileo-state-success)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=loading]{--_c:var(--sileo-state-loading)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=error]{--_c:var(--sileo-state-error)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=warning]{--_c:var(--sileo-state-warning)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=info]{--_c:var(--sileo-state-info)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=action]{--_c:var(--sileo-state-action)}:is([data-sileo-badge],[data-sileo-title])[data-state]{--sileo-tone:var(--_c);--sileo-tone-bg:color-mix(in oklch, var(--_c) 20%, transparent)}.sileo-dismiss{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:9999px;border:0;background:rgba(128,128,128,.25);color:currentColor;font-size:10px;cursor:pointer;flex-shrink:0;opacity:.6;transition:opacity 150ms ease,background 150ms ease}.sileo-dismiss:hover{opacity:1;background:rgba(128,128,128,.4)}[data-sileo-content]{position:absolute;left:0;z-index:10;width:100%;pointer-events:none;opacity:var(--_co, 0)}[data-sileo-content]:not([data-visible=true]){content-visibility:hidden}[data-sileo-toast][data-ready=true] [data-sileo-content]{transition:opacity calc(var(--sileo-duration) * .08) ease calc(var(--sileo-duration) * .04)}[data-sileo-content][data-edge=top]{top:0}[data-sileo-content][data-edge=bottom]{top:var(--sileo-height)}[data-sileo-content][data-visible=true]{pointer-events:auto}[data-sileo-toast][data-ready=true] [data-sileo-content][data-visible=true]{transition:opacity calc(var(--sileo-duration) * .6) ease calc(var(--sileo-duration) * .3)}[data-sileo-description]{width:100%;text-align:left;padding:1rem;font-size:.875rem;line-height:1.25rem}[data-sileo-button]{display:flex;align-items:center;justify-content:center;height:1.75rem;padding:0 .625rem;margin-top:.75rem;border-radius:9999px;border:0;font-size:.75rem;font-weight:500;cursor:pointer;color:var(--sileo-btn-color,currentColor);background-color:var(--sileo-btn-bg,transparent);transition:background-color 150ms ease;text-decoration:none}[data-sileo-button]:hover{background-color:var(--sileo-btn-bg-hover,transparent)}[data-sileo-button][data-state]{--sileo-btn-color:var(--_c);--sileo-btn-bg:color-mix(in oklch, var(--_c) 15%, transparent);--sileo-btn-bg-hover:color-mix(in oklch, var(--_c) 25%, transparent)}[data-sileo-icon=spin]{animation:sileo-spin 1s linear infinite}@keyframes sileo-spin{to{transform:rotate(360deg)}}[data-sileo-viewport]{position:fixed;z-index:9999;display:flex;gap:.75rem;padding:.75rem;pointer-events:none;max-width:calc(100vw - 1.5rem);contain:layout style}[data-sileo-viewport][data-position^=top] [data-sileo-toast]:not([data-ready=true]){margin-bottom:calc(-1 * (var(--sileo-height) + .75rem))}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast]:not([data-ready=true]){margin-top:calc(-1 * (var(--sileo-height) + .75rem))}[data-sileo-viewport][data-position^=top]{top:0;flex-direction:column-reverse}[data-sileo-viewport][data-position^=bottom]{bottom:0;flex-direction:column}[data-sileo-viewport][data-position$=left]{left:0;align-items:flex-start}[data-sileo-viewport][data-position$=right]{right:0;align-items:flex-end}[data-sileo-viewport][data-position$=center]{left:50%;transform:translateX(-50%);align-items:center}[data-sileo-viewport][data-theme=dark] [data-sileo-description]{color:rgba(0,0,0,.5)}[data-sileo-viewport][data-theme=light] [data-sileo-description]{color:rgba(255,255,255,.5)}.sileo-group{display:inline-flex;align-items:center;justify-content:center;height:var(--sileo-height);padding:0 1rem;border-radius:9999px;border:0;font-size:.75rem;font-weight:600;cursor:pointer;pointer-events:auto;background:rgba(128,128,128,.15);backdrop-filter:blur(8px);width:var(--sileo-width)}@media (prefers-reduced-motion:reduce){[data-sileo-viewport],[data-sileo-viewport] *,[data-sileo-viewport] ::after,[data-sileo-viewport] ::before{animation-duration:0s!important;transition-duration:0s!important}}");
16
+ __insertCSS(":root{--sileo-spring-easing:linear(0, 0.002 0.6%, 0.007 1.2%, 0.015 1.8%, 0.026 2.4%, 0.041 3.1%, 0.06 3.8%, 0.108 5.3%, 0.157 6.6%, 0.214 8%, 0.467 13.7%, 0.577 16.3%, 0.631 17.7%, 0.682 19.1%, 0.73 20.5%, 0.771 21.8%, 0.808 23.1%, 0.844 24.5%, 0.874 25.8%, 0.903 27.2%, 0.928 28.6%, 0.952 30.1%, 0.972 31.6%, 0.988 33.1%, 1.01 35.7%, 1.025 38.5%, 1.034 41.6%, 1.038 45%, 1.035 50.1%, 1.012 64.2%, 1.003 73%, 0.999 83.7%, 1);--sileo-duration:600ms;--sileo-height:40px;--sileo-width:350px;--sileo-state-success:oklch(0.723 0.219 142.136);--sileo-state-loading:oklch(0.556 0 0);--sileo-state-error:oklch(0.637 0.237 25.331);--sileo-state-warning:oklch(0.795 0.184 86.047);--sileo-state-info:oklch(0.685 0.169 237.323);--sileo-state-action:oklch(0.623 0.214 259.815)}.sileo-root{pointer-events:none}[data-sileo-toast]{position:relative;cursor:pointer;pointer-events:auto;touch-action:none;border:0;background:0 0;padding:0;width:var(--sileo-width);height:var(--_h,var(--sileo-height));opacity:0;transform:translateZ(0) scale(.95);transform-origin:center;contain:layout style;overflow:visible}[data-sileo-toast][data-state=loading]{cursor:default}[data-sileo-toast][data-ready=true]{opacity:1;transform:translateZ(0) scale(1);transition:transform calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-viewport][data-position^=top] [data-sileo-toast]:not([data-ready=true]){transform:translateY(-6px) scale(.95)}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast]:not([data-ready=true]){transform:translateY(6px) scale(.95)}[data-sileo-toast][data-ready=true][data-exiting=true]{opacity:0;pointer-events:none}[data-sileo-viewport][data-position^=top] [data-sileo-toast][data-ready=true][data-exiting=true]{transform:translateY(-6px) scale(.95)}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast][data-ready=true][data-exiting=true]{transform:translateY(6px) scale(.95)}[data-sileo-canvas]{position:absolute;left:0;right:0;pointer-events:none;transform:translateZ(0);contain:layout style;overflow:visible}[data-sileo-canvas][data-edge=top]{bottom:0;transform:scaleY(-1) translateZ(0)}[data-sileo-canvas][data-edge=bottom]{top:0}[data-sileo-svg]{overflow:visible}[data-sileo-pill]{transition:x var(--sileo-duration) var(--sileo-spring-easing),width var(--sileo-duration) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-toast]:not([data-ready=true]) [data-sileo-pill]{transition:none}[data-sileo-body]{transition:height var(--sileo-duration) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .08) ease calc(var(--sileo-duration) * .04)}[data-sileo-toast][data-expanded=true] [data-sileo-body]{transition:height var(--sileo-duration) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .6) ease calc(var(--sileo-duration) * .3)}[data-sileo-toast]:not([data-ready=true]) [data-sileo-body]{transition:none}[data-sileo-header]{position:absolute;z-index:20;display:flex;align-items:center;padding:.5rem;height:var(--sileo-height);overflow:hidden;left:var(--_px,0);transform:var(--_ht);max-width:var(--_pw)}[data-sileo-toast][data-ready=true] [data-sileo-header]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),left var(--sileo-duration) var(--sileo-spring-easing),max-width var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-header][data-edge=top]{bottom:0}[data-sileo-header][data-edge=bottom]{top:0}[data-sileo-header-stack]{position:relative;display:inline-flex;align-items:center;height:100%}[data-sileo-header-inner]{display:flex;align-items:center;gap:.5rem;white-space:nowrap}[data-sileo-badge]{display:flex;height:24px;width:24px;flex-shrink:0;align-items:center;justify-content:center;padding:2px;box-sizing:border-box;border-radius:9999px;color:var(--sileo-tone,currentColor);background-color:var(--sileo-tone-bg,transparent)}[data-sileo-title]{font-size:.825rem;line-height:1rem;font-weight:500;text-transform:capitalize;color:var(--sileo-tone,currentColor)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state]{--_c:var(--sileo-state-success)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=loading]{--_c:var(--sileo-state-loading)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=error]{--_c:var(--sileo-state-error)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=warning]{--_c:var(--sileo-state-warning)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=info]{--_c:var(--sileo-state-info)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=action]{--_c:var(--sileo-state-action)}:is([data-sileo-badge],[data-sileo-title])[data-state]{--sileo-tone:var(--_c);--sileo-tone-bg:color-mix(in oklch, var(--_c) 20%, transparent)}.sileo-dismiss{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:9999px;border:0;background:rgba(128,128,128,.25);color:currentColor;font-size:10px;cursor:pointer;flex-shrink:0;opacity:.6;transition:opacity 150ms ease,background 150ms ease}.sileo-dismiss:hover{opacity:1;background:rgba(128,128,128,.4)}[data-sileo-content]{position:absolute;left:0;z-index:10;width:100%;pointer-events:none;opacity:var(--_co, 0)}[data-sileo-content]:not([data-visible=true]){content-visibility:hidden}[data-sileo-toast][data-ready=true] [data-sileo-content]{transition:opacity calc(var(--sileo-duration) * .08) ease calc(var(--sileo-duration) * .04)}[data-sileo-content][data-edge=top]{top:0}[data-sileo-content][data-edge=bottom]{top:var(--sileo-height)}[data-sileo-content][data-visible=true]{pointer-events:auto}[data-sileo-toast][data-ready=true] [data-sileo-content][data-visible=true]{transition:opacity calc(var(--sileo-duration) * .6) ease calc(var(--sileo-duration) * .3)}[data-sileo-description]{width:100%;text-align:left;padding:1rem;font-size:.875rem;line-height:1.25rem}[data-sileo-button]{display:flex;align-items:center;justify-content:center;height:1.75rem;padding:0 .625rem;margin-top:.75rem;border-radius:9999px;border:0;font-size:.75rem;font-weight:500;cursor:pointer;color:var(--sileo-btn-color,currentColor);background-color:var(--sileo-btn-bg,transparent);transition:background-color 150ms ease;text-decoration:none}[data-sileo-button]:hover{background-color:var(--sileo-btn-bg-hover,transparent)}[data-sileo-button][data-state]{--sileo-btn-color:var(--_c);--sileo-btn-bg:color-mix(in oklch, var(--_c) 15%, transparent);--sileo-btn-bg-hover:color-mix(in oklch, var(--_c) 25%, transparent)}[data-sileo-icon=spin]{animation:sileo-spin 1s linear infinite}@keyframes sileo-spin{to{transform:rotate(360deg)}}[data-sileo-viewport]{position:fixed;z-index:9999;display:flex;gap:.75rem;padding:.75rem;pointer-events:none;max-width:calc(100vw - 1.5rem);contain:layout style}[data-sileo-viewport][data-position^=top] [data-sileo-toast]:not([data-ready=true]){margin-bottom:calc(-1 * (var(--sileo-height) + .75rem))}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast]:not([data-ready=true]){margin-top:calc(-1 * (var(--sileo-height) + .75rem))}[data-sileo-viewport][data-position^=top]{top:0;flex-direction:column-reverse}[data-sileo-viewport][data-position^=bottom]{bottom:0;flex-direction:column}[data-sileo-viewport][data-position$=left]{left:0;align-items:flex-start}[data-sileo-viewport][data-position$=right]{right:0;align-items:flex-end}[data-sileo-viewport][data-position$=center]{left:50%;transform:translateX(-50%);align-items:center}[data-sileo-viewport][data-theme=dark] [data-sileo-header]{color:#f2f2f2}[data-sileo-viewport][data-theme=dark] .sileo-dismiss{background:rgba(255,255,255,.12)}[data-sileo-viewport][data-theme=light] [data-sileo-header]{color:#1a1a1a}[data-sileo-viewport][data-theme=light] .sileo-dismiss{background:rgba(0,0,0,.12)}[data-sileo-viewport][data-theme=dark] [data-sileo-description]{color:rgba(255,255,255,.5)}[data-sileo-viewport][data-theme=light] [data-sileo-description]{color:rgba(0,0,0,.5)}.sileo-group{display:inline-flex;align-items:center;justify-content:center;height:var(--sileo-height);padding:0 1.25rem;border-radius:9999px;border:0;font-size:.75rem;font-weight:600;cursor:pointer;pointer-events:auto;width:var(--sileo-width);background:#1a1a1a;color:#f2f2f2}[data-sileo-viewport][data-theme=light] .sileo-group{background:#f2f2f2;color:#1a1a1a}@media (prefers-reduced-motion:reduce){[data-sileo-viewport],[data-sileo-viewport] *,[data-sileo-viewport] ::after,[data-sileo-viewport] ::before{animation-duration:0s!important;transition-duration:0s!important}}");
17
17
 
18
18
  const DEFAULT_TOAST_DURATION = 6000;
19
19
  const EXIT_DURATION = DEFAULT_TOAST_DURATION * 0.1;
@@ -441,7 +441,9 @@ const Toaster = vue.defineComponent({
441
441
  const measurePillInner = (instanceId)=>{
442
442
  const el = pillInnerRefs.get(instanceId);
443
443
  if (!el) return;
444
- const w = el.scrollWidth;
444
+ const header = el.closest('[data-sileo-header]');
445
+ const hPad = header ? parseFloat(getComputedStyle(header).paddingLeft) + parseFloat(getComputedStyle(header).paddingRight) : 16;
446
+ const w = el.scrollWidth + hPad;
445
447
  if (pillWidths.value[instanceId] !== w) {
446
448
  pillWidths.value[instanceId] = w;
447
449
  }
@@ -473,7 +475,7 @@ const Toaster = vue.defineComponent({
473
475
  };
474
476
  const scheduleReady = (instanceId)=>{
475
477
  globalThis.requestAnimationFrame(()=>{
476
- if (pillInnerRefs.has(instanceId) || toasts.value.some((t)=>t.instanceId === instanceId)) {
478
+ if (pillInnerRefs.has(instanceId) && toasts.value.some((t)=>t.instanceId === instanceId)) {
477
479
  readyToasts.value[instanceId] = true;
478
480
  }
479
481
  });
@@ -645,7 +647,7 @@ const Toaster = vue.defineComponent({
645
647
  const expandedContent = Math.max(0, expanded - TOAST_HEIGHT);
646
648
  const svgH = Math.max(expanded, TOAST_HEIGHT);
647
649
  const resolvedTheme = resolveTheme(props.theme);
648
- const fillColor = (_item_fill = item.fill) != null ? _item_fill : resolvedTheme === 'dark' ? '#f2f2f2' : '#1a1a1a';
650
+ const fillColor = (_item_fill = item.fill) != null ? _item_fill : resolvedTheme === 'dark' ? '#1a1a1a' : '#f2f2f2';
649
651
  const filterId = `sileo-gooey-${item.instanceId.replace(/[^a-z0-9]/gi, '-')}`;
650
652
  const rootStyle = {
651
653
  '--_h': `${isOpen ? expanded : TOAST_HEIGHT}px`,
package/dist/index.mjs CHANGED
@@ -11,7 +11,7 @@ function __insertCSS(code) {
11
11
  import { _ as _extends } from './cc-2Yt7NqMX.mjs';
12
12
  import { defineComponent, ref, onMounted, onBeforeUnmount, watch, computed, h, Teleport } from 'vue';
13
13
 
14
- __insertCSS(":root{--sileo-spring-easing:linear(0, 0.002 0.6%, 0.007 1.2%, 0.015 1.8%, 0.026 2.4%, 0.041 3.1%, 0.06 3.8%, 0.108 5.3%, 0.157 6.6%, 0.214 8%, 0.467 13.7%, 0.577 16.3%, 0.631 17.7%, 0.682 19.1%, 0.73 20.5%, 0.771 21.8%, 0.808 23.1%, 0.844 24.5%, 0.874 25.8%, 0.903 27.2%, 0.928 28.6%, 0.952 30.1%, 0.972 31.6%, 0.988 33.1%, 1.01 35.7%, 1.025 38.5%, 1.034 41.6%, 1.038 45%, 1.035 50.1%, 1.012 64.2%, 1.003 73%, 0.999 83.7%, 1);--sileo-duration:600ms;--sileo-height:40px;--sileo-width:350px;--sileo-state-success:oklch(0.723 0.219 142.136);--sileo-state-loading:oklch(0.556 0 0);--sileo-state-error:oklch(0.637 0.237 25.331);--sileo-state-warning:oklch(0.795 0.184 86.047);--sileo-state-info:oklch(0.685 0.169 237.323);--sileo-state-action:oklch(0.623 0.214 259.815)}.sileo-root{pointer-events:none}[data-sileo-toast]{position:relative;cursor:pointer;pointer-events:auto;touch-action:none;border:0;background:0 0;padding:0;width:var(--sileo-width);height:var(--_h,var(--sileo-height));opacity:0;transform:translateZ(0) scale(.95);transform-origin:center;contain:layout style;overflow:visible}[data-sileo-toast][data-state=loading]{cursor:default}[data-sileo-toast][data-ready=true]{opacity:1;transform:translateZ(0) scale(1);transition:transform calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-viewport][data-position^=top] [data-sileo-toast]:not([data-ready=true]){transform:translateY(-6px) scale(.95)}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast]:not([data-ready=true]){transform:translateY(6px) scale(.95)}[data-sileo-toast][data-ready=true][data-exiting=true]{opacity:0;pointer-events:none}[data-sileo-viewport][data-position^=top] [data-sileo-toast][data-ready=true][data-exiting=true]{transform:translateY(-6px) scale(.95)}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast][data-ready=true][data-exiting=true]{transform:translateY(6px) scale(.95)}[data-sileo-canvas]{position:absolute;left:0;right:0;pointer-events:none;transform:translateZ(0);contain:layout style;overflow:visible}[data-sileo-canvas][data-edge=top]{bottom:0;transform:scaleY(-1) translateZ(0)}[data-sileo-canvas][data-edge=bottom]{top:0}[data-sileo-svg]{overflow:visible}[data-sileo-pill]{transition:x var(--sileo-duration) var(--sileo-spring-easing),width var(--sileo-duration) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-toast]:not([data-ready=true]) [data-sileo-pill]{transition:none}[data-sileo-body]{transition:height var(--sileo-duration) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .08) ease calc(var(--sileo-duration) * .04)}[data-sileo-toast][data-expanded=true] [data-sileo-body]{transition:height var(--sileo-duration) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .6) ease calc(var(--sileo-duration) * .3)}[data-sileo-toast]:not([data-ready=true]) [data-sileo-body]{transition:none}[data-sileo-header]{position:absolute;z-index:20;display:flex;align-items:center;padding:.5rem;height:var(--sileo-height);overflow:hidden;left:var(--_px,0);transform:var(--_ht);max-width:var(--_pw)}[data-sileo-toast][data-ready=true] [data-sileo-header]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),left var(--sileo-duration) var(--sileo-spring-easing),max-width var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-header][data-edge=top]{bottom:0}[data-sileo-header][data-edge=bottom]{top:0}[data-sileo-header-stack]{position:relative;display:inline-flex;align-items:center;height:100%}[data-sileo-header-inner]{display:flex;align-items:center;gap:.5rem;white-space:nowrap}[data-sileo-badge]{display:flex;height:24px;width:24px;flex-shrink:0;align-items:center;justify-content:center;padding:2px;box-sizing:border-box;border-radius:9999px;color:var(--sileo-tone,currentColor);background-color:var(--sileo-tone-bg,transparent)}[data-sileo-title]{font-size:.825rem;line-height:1rem;font-weight:500;text-transform:capitalize;color:var(--sileo-tone,currentColor)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state]{--_c:var(--sileo-state-success)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=loading]{--_c:var(--sileo-state-loading)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=error]{--_c:var(--sileo-state-error)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=warning]{--_c:var(--sileo-state-warning)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=info]{--_c:var(--sileo-state-info)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=action]{--_c:var(--sileo-state-action)}:is([data-sileo-badge],[data-sileo-title])[data-state]{--sileo-tone:var(--_c);--sileo-tone-bg:color-mix(in oklch, var(--_c) 20%, transparent)}.sileo-dismiss{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:9999px;border:0;background:rgba(128,128,128,.25);color:currentColor;font-size:10px;cursor:pointer;flex-shrink:0;opacity:.6;transition:opacity 150ms ease,background 150ms ease}.sileo-dismiss:hover{opacity:1;background:rgba(128,128,128,.4)}[data-sileo-content]{position:absolute;left:0;z-index:10;width:100%;pointer-events:none;opacity:var(--_co, 0)}[data-sileo-content]:not([data-visible=true]){content-visibility:hidden}[data-sileo-toast][data-ready=true] [data-sileo-content]{transition:opacity calc(var(--sileo-duration) * .08) ease calc(var(--sileo-duration) * .04)}[data-sileo-content][data-edge=top]{top:0}[data-sileo-content][data-edge=bottom]{top:var(--sileo-height)}[data-sileo-content][data-visible=true]{pointer-events:auto}[data-sileo-toast][data-ready=true] [data-sileo-content][data-visible=true]{transition:opacity calc(var(--sileo-duration) * .6) ease calc(var(--sileo-duration) * .3)}[data-sileo-description]{width:100%;text-align:left;padding:1rem;font-size:.875rem;line-height:1.25rem}[data-sileo-button]{display:flex;align-items:center;justify-content:center;height:1.75rem;padding:0 .625rem;margin-top:.75rem;border-radius:9999px;border:0;font-size:.75rem;font-weight:500;cursor:pointer;color:var(--sileo-btn-color,currentColor);background-color:var(--sileo-btn-bg,transparent);transition:background-color 150ms ease;text-decoration:none}[data-sileo-button]:hover{background-color:var(--sileo-btn-bg-hover,transparent)}[data-sileo-button][data-state]{--sileo-btn-color:var(--_c);--sileo-btn-bg:color-mix(in oklch, var(--_c) 15%, transparent);--sileo-btn-bg-hover:color-mix(in oklch, var(--_c) 25%, transparent)}[data-sileo-icon=spin]{animation:sileo-spin 1s linear infinite}@keyframes sileo-spin{to{transform:rotate(360deg)}}[data-sileo-viewport]{position:fixed;z-index:9999;display:flex;gap:.75rem;padding:.75rem;pointer-events:none;max-width:calc(100vw - 1.5rem);contain:layout style}[data-sileo-viewport][data-position^=top] [data-sileo-toast]:not([data-ready=true]){margin-bottom:calc(-1 * (var(--sileo-height) + .75rem))}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast]:not([data-ready=true]){margin-top:calc(-1 * (var(--sileo-height) + .75rem))}[data-sileo-viewport][data-position^=top]{top:0;flex-direction:column-reverse}[data-sileo-viewport][data-position^=bottom]{bottom:0;flex-direction:column}[data-sileo-viewport][data-position$=left]{left:0;align-items:flex-start}[data-sileo-viewport][data-position$=right]{right:0;align-items:flex-end}[data-sileo-viewport][data-position$=center]{left:50%;transform:translateX(-50%);align-items:center}[data-sileo-viewport][data-theme=dark] [data-sileo-description]{color:rgba(0,0,0,.5)}[data-sileo-viewport][data-theme=light] [data-sileo-description]{color:rgba(255,255,255,.5)}.sileo-group{display:inline-flex;align-items:center;justify-content:center;height:var(--sileo-height);padding:0 1rem;border-radius:9999px;border:0;font-size:.75rem;font-weight:600;cursor:pointer;pointer-events:auto;background:rgba(128,128,128,.15);backdrop-filter:blur(8px);width:var(--sileo-width)}@media (prefers-reduced-motion:reduce){[data-sileo-viewport],[data-sileo-viewport] *,[data-sileo-viewport] ::after,[data-sileo-viewport] ::before{animation-duration:0s!important;transition-duration:0s!important}}");
14
+ __insertCSS(":root{--sileo-spring-easing:linear(0, 0.002 0.6%, 0.007 1.2%, 0.015 1.8%, 0.026 2.4%, 0.041 3.1%, 0.06 3.8%, 0.108 5.3%, 0.157 6.6%, 0.214 8%, 0.467 13.7%, 0.577 16.3%, 0.631 17.7%, 0.682 19.1%, 0.73 20.5%, 0.771 21.8%, 0.808 23.1%, 0.844 24.5%, 0.874 25.8%, 0.903 27.2%, 0.928 28.6%, 0.952 30.1%, 0.972 31.6%, 0.988 33.1%, 1.01 35.7%, 1.025 38.5%, 1.034 41.6%, 1.038 45%, 1.035 50.1%, 1.012 64.2%, 1.003 73%, 0.999 83.7%, 1);--sileo-duration:600ms;--sileo-height:40px;--sileo-width:350px;--sileo-state-success:oklch(0.723 0.219 142.136);--sileo-state-loading:oklch(0.556 0 0);--sileo-state-error:oklch(0.637 0.237 25.331);--sileo-state-warning:oklch(0.795 0.184 86.047);--sileo-state-info:oklch(0.685 0.169 237.323);--sileo-state-action:oklch(0.623 0.214 259.815)}.sileo-root{pointer-events:none}[data-sileo-toast]{position:relative;cursor:pointer;pointer-events:auto;touch-action:none;border:0;background:0 0;padding:0;width:var(--sileo-width);height:var(--_h,var(--sileo-height));opacity:0;transform:translateZ(0) scale(.95);transform-origin:center;contain:layout style;overflow:visible}[data-sileo-toast][data-state=loading]{cursor:default}[data-sileo-toast][data-ready=true]{opacity:1;transform:translateZ(0) scale(1);transition:transform calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-viewport][data-position^=top] [data-sileo-toast]:not([data-ready=true]){transform:translateY(-6px) scale(.95)}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast]:not([data-ready=true]){transform:translateY(6px) scale(.95)}[data-sileo-toast][data-ready=true][data-exiting=true]{opacity:0;pointer-events:none}[data-sileo-viewport][data-position^=top] [data-sileo-toast][data-ready=true][data-exiting=true]{transform:translateY(-6px) scale(.95)}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast][data-ready=true][data-exiting=true]{transform:translateY(6px) scale(.95)}[data-sileo-canvas]{position:absolute;left:0;right:0;pointer-events:none;transform:translateZ(0);contain:layout style;overflow:visible}[data-sileo-canvas][data-edge=top]{bottom:0;transform:scaleY(-1) translateZ(0)}[data-sileo-canvas][data-edge=bottom]{top:0}[data-sileo-svg]{overflow:visible}[data-sileo-pill]{transition:x var(--sileo-duration) var(--sileo-spring-easing),width var(--sileo-duration) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-toast]:not([data-ready=true]) [data-sileo-pill]{transition:none}[data-sileo-body]{transition:height var(--sileo-duration) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .08) ease calc(var(--sileo-duration) * .04)}[data-sileo-toast][data-expanded=true] [data-sileo-body]{transition:height var(--sileo-duration) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .6) ease calc(var(--sileo-duration) * .3)}[data-sileo-toast]:not([data-ready=true]) [data-sileo-body]{transition:none}[data-sileo-header]{position:absolute;z-index:20;display:flex;align-items:center;padding:.5rem;height:var(--sileo-height);overflow:hidden;left:var(--_px,0);transform:var(--_ht);max-width:var(--_pw)}[data-sileo-toast][data-ready=true] [data-sileo-header]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),left var(--sileo-duration) var(--sileo-spring-easing),max-width var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-header][data-edge=top]{bottom:0}[data-sileo-header][data-edge=bottom]{top:0}[data-sileo-header-stack]{position:relative;display:inline-flex;align-items:center;height:100%}[data-sileo-header-inner]{display:flex;align-items:center;gap:.5rem;white-space:nowrap}[data-sileo-badge]{display:flex;height:24px;width:24px;flex-shrink:0;align-items:center;justify-content:center;padding:2px;box-sizing:border-box;border-radius:9999px;color:var(--sileo-tone,currentColor);background-color:var(--sileo-tone-bg,transparent)}[data-sileo-title]{font-size:.825rem;line-height:1rem;font-weight:500;text-transform:capitalize;color:var(--sileo-tone,currentColor)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state]{--_c:var(--sileo-state-success)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=loading]{--_c:var(--sileo-state-loading)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=error]{--_c:var(--sileo-state-error)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=warning]{--_c:var(--sileo-state-warning)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=info]{--_c:var(--sileo-state-info)}:is([data-sileo-badge],[data-sileo-title],[data-sileo-button])[data-state=action]{--_c:var(--sileo-state-action)}:is([data-sileo-badge],[data-sileo-title])[data-state]{--sileo-tone:var(--_c);--sileo-tone-bg:color-mix(in oklch, var(--_c) 20%, transparent)}.sileo-dismiss{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:9999px;border:0;background:rgba(128,128,128,.25);color:currentColor;font-size:10px;cursor:pointer;flex-shrink:0;opacity:.6;transition:opacity 150ms ease,background 150ms ease}.sileo-dismiss:hover{opacity:1;background:rgba(128,128,128,.4)}[data-sileo-content]{position:absolute;left:0;z-index:10;width:100%;pointer-events:none;opacity:var(--_co, 0)}[data-sileo-content]:not([data-visible=true]){content-visibility:hidden}[data-sileo-toast][data-ready=true] [data-sileo-content]{transition:opacity calc(var(--sileo-duration) * .08) ease calc(var(--sileo-duration) * .04)}[data-sileo-content][data-edge=top]{top:0}[data-sileo-content][data-edge=bottom]{top:var(--sileo-height)}[data-sileo-content][data-visible=true]{pointer-events:auto}[data-sileo-toast][data-ready=true] [data-sileo-content][data-visible=true]{transition:opacity calc(var(--sileo-duration) * .6) ease calc(var(--sileo-duration) * .3)}[data-sileo-description]{width:100%;text-align:left;padding:1rem;font-size:.875rem;line-height:1.25rem}[data-sileo-button]{display:flex;align-items:center;justify-content:center;height:1.75rem;padding:0 .625rem;margin-top:.75rem;border-radius:9999px;border:0;font-size:.75rem;font-weight:500;cursor:pointer;color:var(--sileo-btn-color,currentColor);background-color:var(--sileo-btn-bg,transparent);transition:background-color 150ms ease;text-decoration:none}[data-sileo-button]:hover{background-color:var(--sileo-btn-bg-hover,transparent)}[data-sileo-button][data-state]{--sileo-btn-color:var(--_c);--sileo-btn-bg:color-mix(in oklch, var(--_c) 15%, transparent);--sileo-btn-bg-hover:color-mix(in oklch, var(--_c) 25%, transparent)}[data-sileo-icon=spin]{animation:sileo-spin 1s linear infinite}@keyframes sileo-spin{to{transform:rotate(360deg)}}[data-sileo-viewport]{position:fixed;z-index:9999;display:flex;gap:.75rem;padding:.75rem;pointer-events:none;max-width:calc(100vw - 1.5rem);contain:layout style}[data-sileo-viewport][data-position^=top] [data-sileo-toast]:not([data-ready=true]){margin-bottom:calc(-1 * (var(--sileo-height) + .75rem))}[data-sileo-viewport][data-position^=bottom] [data-sileo-toast]:not([data-ready=true]){margin-top:calc(-1 * (var(--sileo-height) + .75rem))}[data-sileo-viewport][data-position^=top]{top:0;flex-direction:column-reverse}[data-sileo-viewport][data-position^=bottom]{bottom:0;flex-direction:column}[data-sileo-viewport][data-position$=left]{left:0;align-items:flex-start}[data-sileo-viewport][data-position$=right]{right:0;align-items:flex-end}[data-sileo-viewport][data-position$=center]{left:50%;transform:translateX(-50%);align-items:center}[data-sileo-viewport][data-theme=dark] [data-sileo-header]{color:#f2f2f2}[data-sileo-viewport][data-theme=dark] .sileo-dismiss{background:rgba(255,255,255,.12)}[data-sileo-viewport][data-theme=light] [data-sileo-header]{color:#1a1a1a}[data-sileo-viewport][data-theme=light] .sileo-dismiss{background:rgba(0,0,0,.12)}[data-sileo-viewport][data-theme=dark] [data-sileo-description]{color:rgba(255,255,255,.5)}[data-sileo-viewport][data-theme=light] [data-sileo-description]{color:rgba(0,0,0,.5)}.sileo-group{display:inline-flex;align-items:center;justify-content:center;height:var(--sileo-height);padding:0 1.25rem;border-radius:9999px;border:0;font-size:.75rem;font-weight:600;cursor:pointer;pointer-events:auto;width:var(--sileo-width);background:#1a1a1a;color:#f2f2f2}[data-sileo-viewport][data-theme=light] .sileo-group{background:#f2f2f2;color:#1a1a1a}@media (prefers-reduced-motion:reduce){[data-sileo-viewport],[data-sileo-viewport] *,[data-sileo-viewport] ::after,[data-sileo-viewport] ::before{animation-duration:0s!important;transition-duration:0s!important}}");
15
15
 
16
16
  const DEFAULT_TOAST_DURATION = 6000;
17
17
  const EXIT_DURATION = DEFAULT_TOAST_DURATION * 0.1;
@@ -439,7 +439,9 @@ const Toaster = defineComponent({
439
439
  const measurePillInner = (instanceId)=>{
440
440
  const el = pillInnerRefs.get(instanceId);
441
441
  if (!el) return;
442
- const w = el.scrollWidth;
442
+ const header = el.closest('[data-sileo-header]');
443
+ const hPad = header ? parseFloat(getComputedStyle(header).paddingLeft) + parseFloat(getComputedStyle(header).paddingRight) : 16;
444
+ const w = el.scrollWidth + hPad;
443
445
  if (pillWidths.value[instanceId] !== w) {
444
446
  pillWidths.value[instanceId] = w;
445
447
  }
@@ -471,7 +473,7 @@ const Toaster = defineComponent({
471
473
  };
472
474
  const scheduleReady = (instanceId)=>{
473
475
  globalThis.requestAnimationFrame(()=>{
474
- if (pillInnerRefs.has(instanceId) || toasts.value.some((t)=>t.instanceId === instanceId)) {
476
+ if (pillInnerRefs.has(instanceId) && toasts.value.some((t)=>t.instanceId === instanceId)) {
475
477
  readyToasts.value[instanceId] = true;
476
478
  }
477
479
  });
@@ -643,7 +645,7 @@ const Toaster = defineComponent({
643
645
  const expandedContent = Math.max(0, expanded - TOAST_HEIGHT);
644
646
  const svgH = Math.max(expanded, TOAST_HEIGHT);
645
647
  const resolvedTheme = resolveTheme(props.theme);
646
- const fillColor = (_item_fill = item.fill) != null ? _item_fill : resolvedTheme === 'dark' ? '#f2f2f2' : '#1a1a1a';
648
+ const fillColor = (_item_fill = item.fill) != null ? _item_fill : resolvedTheme === 'dark' ? '#1a1a1a' : '#f2f2f2';
647
649
  const filterId = `sileo-gooey-${item.instanceId.replace(/[^a-z0-9]/gi, '-')}`;
648
650
  const rootStyle = {
649
651
  '--_h': `${isOpen ? expanded : TOAST_HEIGHT}px`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alann-estrada-ksh/sileo-vue",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "An opinionated toast notification library for Vue.",
5
5
  "license": "MIT",
6
6
  "repository": {