sileo 0.0.5 → 0.0.7

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.
package/dist/index.d.mts CHANGED
@@ -42,6 +42,7 @@ interface SileoPromiseOptions<T = unknown> {
42
42
  success: SileoOptions | ((data: T) => SileoOptions);
43
43
  error: SileoOptions | ((err: unknown) => SileoOptions);
44
44
  action?: SileoOptions | ((data: T) => SileoOptions);
45
+ position?: SileoPosition;
45
46
  }
46
47
  declare const sileo: {
47
48
  show: (opts: SileoOptions) => string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sources":["../src/types.ts","../src/toast.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nexport type SileoState =\n\t| \"success\"\n\t| \"loading\"\n\t| \"error\"\n\t| \"warning\"\n\t| \"info\"\n\t| \"action\";\n\nexport interface SileoStyles {\n\ttitle?: string;\n\tdescription?: string;\n\tbadge?: string;\n\tbutton?: string;\n}\n\nexport interface SileoButton {\n\ttitle: string;\n\tonClick: () => void;\n}\n\nexport const SILEO_POSITIONS = [\n\t\"top-left\",\n\t\"top-center\",\n\t\"top-right\",\n\t\"bottom-left\",\n\t\"bottom-center\",\n\t\"bottom-right\",\n] as const;\n\nexport type SileoPosition = (typeof SILEO_POSITIONS)[number];\n\nexport interface SileoOptions {\n\ttitle?: string;\n\tdescription?: ReactNode | string;\n\tposition?: SileoPosition;\n\tduration?: number | null;\n\ticon?: ReactNode | null;\n\tstyles?: SileoStyles;\n\tfill?: string;\n\troundness?: number;\n\tautopilot?: boolean | { expand?: number; collapse?: number };\n\tbutton?: SileoButton;\n}\n","import {\n\ttype CSSProperties,\n\ttype MouseEventHandler,\n\ttype ReactNode,\n\tuseCallback,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport { Sileo } from \"./sileo\";\nimport {\n\tSILEO_POSITIONS,\n\ttype SileoOptions,\n\ttype SileoPosition,\n\ttype SileoState,\n} from \"./types\";\n\n/* -------------------------------- Constants ------------------------------- */\n\nconst DEFAULT_DURATION = 6000;\nconst EXIT_DURATION = DEFAULT_DURATION * 0.1;\nconst AUTO_EXPAND_DELAY = DEFAULT_DURATION * 0.025;\nconst AUTO_COLLAPSE_DELAY = DEFAULT_DURATION - 2000;\n\nconst pillAlign = (pos: SileoPosition) =>\n\tpos.includes(\"right\") ? \"right\" : pos.includes(\"center\") ? \"center\" : \"left\";\nconst expandDir = (pos: SileoPosition) =>\n\tpos.startsWith(\"top\") ? (\"bottom\" as const) : (\"top\" as const);\n\n/* ---------------------------------- Types --------------------------------- */\n\ninterface InternalSileoOptions extends SileoOptions {\n\tid?: string;\n\tstate?: SileoState;\n}\n\ninterface SileoItem extends InternalSileoOptions {\n\tid: string;\n\tinstanceId: string;\n\texiting?: boolean;\n\tautoExpandDelayMs?: number;\n\tautoCollapseDelayMs?: number;\n}\n\ntype SileoOffsetValue = number | string;\ntype SileoOffsetConfig = Partial<\n\tRecord<\"top\" | \"right\" | \"bottom\" | \"left\", SileoOffsetValue>\n>;\n\nexport interface SileoToasterProps {\n\tchildren?: ReactNode;\n\tposition?: SileoPosition;\n\toffset?: SileoOffsetValue | SileoOffsetConfig;\n\toptions?: Partial<SileoOptions>;\n}\n\n/* ------------------------------ Global State ------------------------------ */\n\ntype SileoListener = (toasts: SileoItem[]) => void;\n\nconst store = {\n\ttoasts: [] as SileoItem[],\n\tlisteners: new Set<SileoListener>(),\n\tposition: \"top-right\" as SileoPosition,\n\toptions: undefined as Partial<SileoOptions> | undefined,\n\n\temit() {\n\t\tfor (const fn of this.listeners) fn(this.toasts);\n\t},\n\n\tupdate(fn: (prev: SileoItem[]) => SileoItem[]) {\n\t\tthis.toasts = fn(this.toasts);\n\t\tthis.emit();\n\t},\n};\n\nlet idCounter = 0;\nconst generateId = () =>\n\t`${++idCounter}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n\nconst timeoutKey = (t: SileoItem) => `${t.id}:${t.instanceId}`;\n\n/* ------------------------------- Toast API -------------------------------- */\n\nconst dismissToast = (id: string) => {\n\tconst item = store.toasts.find((t) => t.id === id);\n\tif (!item || item.exiting) return;\n\n\tstore.update((prev) =>\n\t\tprev.map((t) => (t.id === id ? { ...t, exiting: true } : t)),\n\t);\n\n\tsetTimeout(\n\t\t() => store.update((prev) => prev.filter((t) => t.id !== id)),\n\t\tEXIT_DURATION,\n\t);\n};\n\nconst resolveAutopilot = (\n\topts: InternalSileoOptions,\n\tduration: number | null,\n): { expandDelayMs?: number; collapseDelayMs?: number } => {\n\tif (opts.autopilot === false || !duration || duration <= 0) return {};\n\tconst cfg = typeof opts.autopilot === \"object\" ? opts.autopilot : undefined;\n\tconst clamp = (v: number) => Math.min(duration, Math.max(0, v));\n\treturn {\n\t\texpandDelayMs: clamp(cfg?.expand ?? AUTO_EXPAND_DELAY),\n\t\tcollapseDelayMs: clamp(cfg?.collapse ?? AUTO_COLLAPSE_DELAY),\n\t};\n};\n\nconst mergeOptions = (options: InternalSileoOptions) => ({\n\t...store.options,\n\t...options,\n\tstyles: { ...store.options?.styles, ...options.styles },\n});\n\nconst buildSileoItem = (\n\tmerged: InternalSileoOptions,\n\tid: string,\n\tfallbackPosition?: SileoPosition,\n): SileoItem => {\n\tconst duration = merged.duration ?? DEFAULT_DURATION;\n\tconst auto = resolveAutopilot(merged, duration);\n\treturn {\n\t\t...merged,\n\t\tid,\n\t\tinstanceId: generateId(),\n\t\tposition: merged.position ?? fallbackPosition ?? store.position,\n\t\tautoExpandDelayMs: auto.expandDelayMs,\n\t\tautoCollapseDelayMs: auto.collapseDelayMs,\n\t};\n};\n\nconst createToast = (options: InternalSileoOptions) => {\n\tconst live = store.toasts.filter((t) => !t.exiting);\n\tconst merged = mergeOptions(options);\n\n\tconst id = merged.id ?? \"sileo-default\";\n\tconst prev = live.find((t) => t.id === id);\n\tconst item = buildSileoItem(merged, id, prev?.position);\n\n\tif (prev) {\n\t\tstore.update((p) => p.map((t) => (t.id === id ? item : t)));\n\t} else {\n\t\tstore.update((p) => [...p, item]);\n\t}\n\treturn { id, duration: merged.duration ?? DEFAULT_DURATION };\n};\n\nconst updateToast = (id: string, options: InternalSileoOptions) => {\n\tconst existing = store.toasts.find((t) => t.id === id);\n\tif (!existing) return;\n\n\tconst item = buildSileoItem(mergeOptions(options), id, existing.position);\n\tstore.update((prev) => prev.map((t) => (t.id === id ? item : t)));\n};\n\nexport interface SileoPromiseOptions<T = unknown> {\n\tloading: Pick<SileoOptions, \"title\" | \"icon\">;\n\tsuccess: SileoOptions | ((data: T) => SileoOptions);\n\terror: SileoOptions | ((err: unknown) => SileoOptions);\n\taction?: SileoOptions | ((data: T) => SileoOptions);\n}\n\nexport const sileo = {\n\tshow: (opts: SileoOptions) => createToast(opts).id,\n\tsuccess: (opts: SileoOptions) =>\n\t\tcreateToast({ ...opts, state: \"success\" }).id,\n\terror: (opts: SileoOptions) => createToast({ ...opts, state: \"error\" }).id,\n\twarning: (opts: SileoOptions) =>\n\t\tcreateToast({ ...opts, state: \"warning\" }).id,\n\tinfo: (opts: SileoOptions) => createToast({ ...opts, state: \"info\" }).id,\n\taction: (opts: SileoOptions) => createToast({ ...opts, state: \"action\" }).id,\n\n\tpromise: <T,>(\n\t\tpromise: Promise<T> | (() => Promise<T>),\n\t\topts: SileoPromiseOptions<T>,\n\t): Promise<T> => {\n\t\tconst { id } = createToast({\n\t\t\t...opts.loading,\n\t\t\tstate: \"loading\",\n\t\t\tduration: null,\n\t\t});\n\n\t\tconst p = typeof promise === \"function\" ? promise() : promise;\n\n\t\tp.then((data) => {\n\t\t\tif (opts.action) {\n\t\t\t\tconst actionOpts =\n\t\t\t\t\ttypeof opts.action === \"function\" ? opts.action(data) : opts.action;\n\t\t\t\tupdateToast(id, { ...actionOpts, state: \"action\", id });\n\t\t\t} else {\n\t\t\t\tconst successOpts =\n\t\t\t\t\ttypeof opts.success === \"function\"\n\t\t\t\t\t\t? opts.success(data)\n\t\t\t\t\t\t: opts.success;\n\t\t\t\tupdateToast(id, { ...successOpts, state: \"success\", id });\n\t\t\t}\n\t\t}).catch((err) => {\n\t\t\tconst errorOpts =\n\t\t\t\ttypeof opts.error === \"function\" ? opts.error(err) : opts.error;\n\t\t\tupdateToast(id, { ...errorOpts, state: \"error\", id });\n\t\t});\n\n\t\treturn p;\n\t},\n\n\tdismiss: dismissToast,\n\n\tclear: (position?: SileoPosition) =>\n\t\tstore.update((prev) =>\n\t\t\tposition ? prev.filter((t) => t.position !== position) : [],\n\t\t),\n};\n\n/* ------------------------------ Toaster Component ------------------------- */\n\nexport function Toaster({\n\tchildren,\n\tposition = \"top-right\",\n\toffset,\n\toptions,\n}: SileoToasterProps) {\n\tconst [toasts, setToasts] = useState<SileoItem[]>(store.toasts);\n\tconst [activeId, setActiveId] = useState<string>();\n\n\tconst hoverRef = useRef(false);\n\tconst timersRef = useRef(new Map<string, number>());\n\tconst listRef = useRef(toasts);\n\tconst latestRef = useRef<string | undefined>(undefined);\n\tconst handlersCache = useRef(\n\t\tnew Map<\n\t\t\tstring,\n\t\t\t{\n\t\t\t\tenter: MouseEventHandler<HTMLButtonElement>;\n\t\t\t\tleave: MouseEventHandler<HTMLButtonElement>;\n\t\t\t}\n\t\t>(),\n\t);\n\n\tuseEffect(() => {\n\t\tstore.position = position;\n\t\tstore.options = options;\n\t}, [position, options]);\n\n\tconst clearAllTimers = useCallback(() => {\n\t\tfor (const t of timersRef.current.values()) clearTimeout(t);\n\t\ttimersRef.current.clear();\n\t}, []);\n\n\tconst schedule = useCallback((items: SileoItem[]) => {\n\t\tif (hoverRef.current) return;\n\n\t\tfor (const item of items) {\n\t\t\tif (item.exiting) continue;\n\t\t\tconst key = timeoutKey(item);\n\t\t\tif (timersRef.current.has(key)) continue;\n\n\t\t\tconst dur = item.duration ?? DEFAULT_DURATION;\n\t\t\tif (dur === null || dur <= 0) continue;\n\n\t\t\ttimersRef.current.set(\n\t\t\t\tkey,\n\t\t\t\twindow.setTimeout(() => dismissToast(item.id), dur),\n\t\t\t);\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\tconst listener: SileoListener = (next) => setToasts(next);\n\t\tstore.listeners.add(listener);\n\t\treturn () => {\n\t\t\tstore.listeners.delete(listener);\n\t\t\tclearAllTimers();\n\t\t};\n\t}, [clearAllTimers]);\n\n\tuseEffect(() => {\n\t\tlistRef.current = toasts;\n\n\t\tconst toastKeys = new Set(toasts.map(timeoutKey));\n\t\tconst toastIds = new Set(toasts.map((t) => t.id));\n\t\tfor (const [key, timer] of timersRef.current) {\n\t\t\tif (!toastKeys.has(key)) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\ttimersRef.current.delete(key);\n\t\t\t}\n\t\t}\n\t\tfor (const id of handlersCache.current.keys()) {\n\t\t\tif (!toastIds.has(id)) handlersCache.current.delete(id);\n\t\t}\n\n\t\tschedule(toasts);\n\t}, [toasts, schedule]);\n\n\tconst handleMouseEnterRef =\n\t\tuseRef<MouseEventHandler<HTMLButtonElement>>(null);\n\tconst handleMouseLeaveRef =\n\t\tuseRef<MouseEventHandler<HTMLButtonElement>>(null);\n\n\thandleMouseEnterRef.current = useCallback<\n\t\tMouseEventHandler<HTMLButtonElement>\n\t>(() => {\n\t\tif (hoverRef.current) return;\n\t\thoverRef.current = true;\n\t\tclearAllTimers();\n\t}, [clearAllTimers]);\n\n\thandleMouseLeaveRef.current = useCallback<\n\t\tMouseEventHandler<HTMLButtonElement>\n\t>(() => {\n\t\tif (!hoverRef.current) return;\n\t\thoverRef.current = false;\n\t\tschedule(listRef.current);\n\t}, [schedule]);\n\n\tconst latest = useMemo(() => {\n\t\tfor (let i = toasts.length - 1; i >= 0; i--) {\n\t\t\tif (!toasts[i].exiting) return toasts[i].id;\n\t\t}\n\t\treturn undefined;\n\t}, [toasts]);\n\n\tuseEffect(() => {\n\t\tlatestRef.current = latest;\n\t\tsetActiveId(latest);\n\t}, [latest]);\n\n\tconst getHandlers = useCallback((toastId: string) => {\n\t\tlet cached = handlersCache.current.get(toastId);\n\t\tif (cached) return cached;\n\n\t\tcached = {\n\t\t\tenter: ((e) => {\n\t\t\t\tsetActiveId((prev) => (prev === toastId ? prev : toastId));\n\t\t\t\thandleMouseEnterRef.current?.(e);\n\t\t\t}) as MouseEventHandler<HTMLButtonElement>,\n\t\t\tleave: ((e) => {\n\t\t\t\tsetActiveId((prev) =>\n\t\t\t\t\tprev === latestRef.current ? prev : latestRef.current,\n\t\t\t\t);\n\t\t\t\thandleMouseLeaveRef.current?.(e);\n\t\t\t}) as MouseEventHandler<HTMLButtonElement>,\n\t\t};\n\n\t\thandlersCache.current.set(toastId, cached);\n\t\treturn cached;\n\t}, []);\n\n\tconst getViewportStyle = useCallback(\n\t\t(pos: SileoPosition): CSSProperties | undefined => {\n\t\t\tif (offset === undefined) return undefined;\n\n\t\t\tconst o =\n\t\t\t\ttypeof offset === \"object\"\n\t\t\t\t\t? offset\n\t\t\t\t\t: { top: offset, right: offset, bottom: offset, left: offset };\n\n\t\t\tconst s: CSSProperties = {};\n\t\t\tconst px = (v: SileoOffsetValue) =>\n\t\t\t\ttypeof v === \"number\" ? `${v}px` : v;\n\n\t\t\tif (pos.startsWith(\"top\") && o.top) s.top = px(o.top);\n\t\t\tif (pos.startsWith(\"bottom\") && o.bottom) s.bottom = px(o.bottom);\n\t\t\tif (pos.endsWith(\"left\") && o.left) s.left = px(o.left);\n\t\t\tif (pos.endsWith(\"right\") && o.right) s.right = px(o.right);\n\n\t\t\treturn s;\n\t\t},\n\t\t[offset],\n\t);\n\n\tconst byPosition = useMemo(() => {\n\t\tconst map = {} as Partial<Record<SileoPosition, SileoItem[]>>;\n\t\tfor (const t of toasts) {\n\t\t\tconst pos = t.position ?? position;\n\t\t\tconst arr = map[pos];\n\t\t\tif (arr) {\n\t\t\t\tarr.push(t);\n\t\t\t} else {\n\t\t\t\tmap[pos] = [t];\n\t\t\t}\n\t\t}\n\t\treturn map;\n\t}, [toasts, position]);\n\n\treturn (\n\t\t<>\n\t\t\t{children}\n\t\t\t{SILEO_POSITIONS.map((pos) => {\n\t\t\t\tconst items = byPosition[pos];\n\t\t\t\tif (!items?.length) return null;\n\n\t\t\t\tconst pill = pillAlign(pos);\n\t\t\t\tconst expand = expandDir(pos);\n\n\t\t\t\treturn (\n\t\t\t\t\t<section\n\t\t\t\t\t\tkey={pos}\n\t\t\t\t\t\tdata-sileo-viewport\n\t\t\t\t\t\tdata-position={pos}\n\t\t\t\t\t\taria-live=\"polite\"\n\t\t\t\t\t\tstyle={getViewportStyle(pos)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{items.map((item) => {\n\t\t\t\t\t\t\tconst h = getHandlers(item.id);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<Sileo\n\t\t\t\t\t\t\t\t\tkey={item.id}\n\t\t\t\t\t\t\t\t\tid={item.id}\n\t\t\t\t\t\t\t\t\tstate={item.state}\n\t\t\t\t\t\t\t\t\ttitle={item.title}\n\t\t\t\t\t\t\t\t\tdescription={item.description}\n\t\t\t\t\t\t\t\t\tposition={pill}\n\t\t\t\t\t\t\t\t\texpand={expand}\n\t\t\t\t\t\t\t\t\ticon={item.icon}\n\t\t\t\t\t\t\t\t\tfill={item.fill}\n\t\t\t\t\t\t\t\t\tstyles={item.styles}\n\t\t\t\t\t\t\t\t\tbutton={item.button}\n\t\t\t\t\t\t\t\t\troundness={item.roundness}\n\t\t\t\t\t\t\t\t\texiting={item.exiting}\n\t\t\t\t\t\t\t\t\tautoExpandDelayMs={item.autoExpandDelayMs}\n\t\t\t\t\t\t\t\t\tautoCollapseDelayMs={item.autoCollapseDelayMs}\n\t\t\t\t\t\t\t\t\trefreshKey={item.instanceId}\n\t\t\t\t\t\t\t\t\tcanExpand={activeId === undefined || activeId === item.id}\n\t\t\t\t\t\t\t\t\tonMouseEnter={h.enter}\n\t\t\t\t\t\t\t\t\tonMouseLeave={h.leave}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</section>\n\t\t\t\t);\n\t\t\t})}\n\t\t</>\n\t);\n}\n"],"names":[],"mappings":";;AACO,KAAA,UAAA;AACA,UAAA,WAAA;AACP;AACA;AACA;AACA;AACA;AACO,UAAA,WAAA;AACP;AACA;AACA;AACO,cAAA,eAAA;AACA,KAAA,aAAA,WAAA,eAAA;AACA,UAAA,YAAA;AACP;AACA,kBAAA,SAAA;AACA,eAAA,aAAA;AACA;AACA,WAAA,SAAA;AACA,aAAA,WAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAA,WAAA;AACA;;AC1BA,KAAA,gBAAA;AACA,KAAA,iBAAA,GAAA,OAAA,CAAA,MAAA,sCAAA,gBAAA;AACO,UAAA,iBAAA;AACP,eAAA,SAAA;AACA,eAAA,aAAA;AACA,aAAA,gBAAA,GAAA,iBAAA;AACA,cAAA,OAAA,CAAA,YAAA;AACA;AACO,UAAA,mBAAA;AACP,aAAA,IAAA,CAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,WAAA,YAAA,sBAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA;AACO,cAAA,KAAA;AACP,iBAAA,YAAA;AACA,oBAAA,YAAA;AACA,kBAAA,YAAA;AACA,oBAAA,YAAA;AACA,iBAAA,YAAA;AACA,mBAAA,YAAA;AACA,0BAAA,OAAA,aAAA,OAAA,YAAA,mBAAA,QAAA,OAAA;AACA;AACA,uBAAA,aAAA;AACA;AACO,iBAAA,OAAA,2CAAA,iBAAA;;;;"}
1
+ {"version":3,"file":"index.d.mts","sources":["../src/types.ts","../src/toast.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nexport type SileoState =\n\t| \"success\"\n\t| \"loading\"\n\t| \"error\"\n\t| \"warning\"\n\t| \"info\"\n\t| \"action\";\n\nexport interface SileoStyles {\n\ttitle?: string;\n\tdescription?: string;\n\tbadge?: string;\n\tbutton?: string;\n}\n\nexport interface SileoButton {\n\ttitle: string;\n\tonClick: () => void;\n}\n\nexport const SILEO_POSITIONS = [\n\t\"top-left\",\n\t\"top-center\",\n\t\"top-right\",\n\t\"bottom-left\",\n\t\"bottom-center\",\n\t\"bottom-right\",\n] as const;\n\nexport type SileoPosition = (typeof SILEO_POSITIONS)[number];\n\nexport interface SileoOptions {\n\ttitle?: string;\n\tdescription?: ReactNode | string;\n\tposition?: SileoPosition;\n\tduration?: number | null;\n\ticon?: ReactNode | null;\n\tstyles?: SileoStyles;\n\tfill?: string;\n\troundness?: number;\n\tautopilot?: boolean | { expand?: number; collapse?: number };\n\tbutton?: SileoButton;\n}\n","import {\n\ttype CSSProperties,\n\ttype MouseEventHandler,\n\ttype ReactNode,\n\tuseCallback,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport { Sileo } from \"./sileo\";\nimport {\n\tSILEO_POSITIONS,\n\ttype SileoOptions,\n\ttype SileoPosition,\n\ttype SileoState,\n} from \"./types\";\n\n/* -------------------------------- Constants ------------------------------- */\n\nconst DEFAULT_DURATION = 6000;\nconst EXIT_DURATION = DEFAULT_DURATION * 0.1;\nconst AUTO_EXPAND_DELAY = DEFAULT_DURATION * 0.025;\nconst AUTO_COLLAPSE_DELAY = DEFAULT_DURATION - 2000;\n\nconst pillAlign = (pos: SileoPosition) =>\n\tpos.includes(\"right\") ? \"right\" : pos.includes(\"center\") ? \"center\" : \"left\";\nconst expandDir = (pos: SileoPosition) =>\n\tpos.startsWith(\"top\") ? (\"bottom\" as const) : (\"top\" as const);\n\n/* ---------------------------------- Types --------------------------------- */\n\ninterface InternalSileoOptions extends SileoOptions {\n\tid?: string;\n\tstate?: SileoState;\n}\n\ninterface SileoItem extends InternalSileoOptions {\n\tid: string;\n\tinstanceId: string;\n\texiting?: boolean;\n\tautoExpandDelayMs?: number;\n\tautoCollapseDelayMs?: number;\n}\n\ntype SileoOffsetValue = number | string;\ntype SileoOffsetConfig = Partial<\n\tRecord<\"top\" | \"right\" | \"bottom\" | \"left\", SileoOffsetValue>\n>;\n\nexport interface SileoToasterProps {\n\tchildren?: ReactNode;\n\tposition?: SileoPosition;\n\toffset?: SileoOffsetValue | SileoOffsetConfig;\n\toptions?: Partial<SileoOptions>;\n}\n\n/* ------------------------------ Global State ------------------------------ */\n\ntype SileoListener = (toasts: SileoItem[]) => void;\n\nconst store = {\n\ttoasts: [] as SileoItem[],\n\tlisteners: new Set<SileoListener>(),\n\tposition: \"top-right\" as SileoPosition,\n\toptions: undefined as Partial<SileoOptions> | undefined,\n\n\temit() {\n\t\tfor (const fn of this.listeners) fn(this.toasts);\n\t},\n\n\tupdate(fn: (prev: SileoItem[]) => SileoItem[]) {\n\t\tthis.toasts = fn(this.toasts);\n\t\tthis.emit();\n\t},\n};\n\nlet idCounter = 0;\nconst generateId = () =>\n\t`${++idCounter}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n\nconst timeoutKey = (t: SileoItem) => `${t.id}:${t.instanceId}`;\n\n/* ------------------------------- Toast API -------------------------------- */\n\nconst dismissToast = (id: string) => {\n\tconst item = store.toasts.find((t) => t.id === id);\n\tif (!item || item.exiting) return;\n\n\tstore.update((prev) =>\n\t\tprev.map((t) => (t.id === id ? { ...t, exiting: true } : t)),\n\t);\n\n\tsetTimeout(\n\t\t() => store.update((prev) => prev.filter((t) => t.id !== id)),\n\t\tEXIT_DURATION,\n\t);\n};\n\nconst resolveAutopilot = (\n\topts: InternalSileoOptions,\n\tduration: number | null,\n): { expandDelayMs?: number; collapseDelayMs?: number } => {\n\tif (opts.autopilot === false || !duration || duration <= 0) return {};\n\tconst cfg = typeof opts.autopilot === \"object\" ? opts.autopilot : undefined;\n\tconst clamp = (v: number) => Math.min(duration, Math.max(0, v));\n\treturn {\n\t\texpandDelayMs: clamp(cfg?.expand ?? AUTO_EXPAND_DELAY),\n\t\tcollapseDelayMs: clamp(cfg?.collapse ?? AUTO_COLLAPSE_DELAY),\n\t};\n};\n\nconst mergeOptions = (options: InternalSileoOptions) => ({\n\t...store.options,\n\t...options,\n\tstyles: { ...store.options?.styles, ...options.styles },\n});\n\nconst buildSileoItem = (\n\tmerged: InternalSileoOptions,\n\tid: string,\n\tfallbackPosition?: SileoPosition,\n): SileoItem => {\n\tconst duration = merged.duration ?? DEFAULT_DURATION;\n\tconst auto = resolveAutopilot(merged, duration);\n\treturn {\n\t\t...merged,\n\t\tid,\n\t\tinstanceId: generateId(),\n\t\tposition: merged.position ?? fallbackPosition ?? store.position,\n\t\tautoExpandDelayMs: auto.expandDelayMs,\n\t\tautoCollapseDelayMs: auto.collapseDelayMs,\n\t};\n};\n\nconst createToast = (options: InternalSileoOptions) => {\n\tconst live = store.toasts.filter((t) => !t.exiting);\n\tconst merged = mergeOptions(options);\n\n\tconst id = merged.id ?? \"sileo-default\";\n\tconst prev = live.find((t) => t.id === id);\n\tconst item = buildSileoItem(merged, id, prev?.position);\n\n\tif (prev) {\n\t\tstore.update((p) => p.map((t) => (t.id === id ? item : t)));\n\t} else {\n\t\tstore.update((p) => [...p.filter((t) => t.id !== id), item]);\n\t}\n\treturn { id, duration: merged.duration ?? DEFAULT_DURATION };\n};\n\nconst updateToast = (id: string, options: InternalSileoOptions) => {\n\tconst existing = store.toasts.find((t) => t.id === id);\n\tif (!existing) return;\n\n\tconst item = buildSileoItem(mergeOptions(options), id, existing.position);\n\tstore.update((prev) => prev.map((t) => (t.id === id ? item : t)));\n};\n\nexport interface SileoPromiseOptions<T = unknown> {\n\tloading: Pick<SileoOptions, \"title\" | \"icon\">;\n\tsuccess: SileoOptions | ((data: T) => SileoOptions);\n\terror: SileoOptions | ((err: unknown) => SileoOptions);\n\taction?: SileoOptions | ((data: T) => SileoOptions);\n\tposition?: SileoPosition;\n}\n\nexport const sileo = {\n\tshow: (opts: SileoOptions) => createToast(opts).id,\n\tsuccess: (opts: SileoOptions) =>\n\t\tcreateToast({ ...opts, state: \"success\" }).id,\n\terror: (opts: SileoOptions) => createToast({ ...opts, state: \"error\" }).id,\n\twarning: (opts: SileoOptions) =>\n\t\tcreateToast({ ...opts, state: \"warning\" }).id,\n\tinfo: (opts: SileoOptions) => createToast({ ...opts, state: \"info\" }).id,\n\taction: (opts: SileoOptions) => createToast({ ...opts, state: \"action\" }).id,\n\n\tpromise: <T,>(\n\t\tpromise: Promise<T> | (() => Promise<T>),\n\t\topts: SileoPromiseOptions<T>,\n\t): Promise<T> => {\n\t\tconst { id } = createToast({\n\t\t\t...opts.loading,\n\t\t\tstate: \"loading\",\n\t\t\tduration: null,\n\t\t\tposition: opts.position,\n\t\t});\n\n\t\tconst p = typeof promise === \"function\" ? promise() : promise;\n\n\t\tp.then((data) => {\n\t\t\tif (opts.action) {\n\t\t\t\tconst actionOpts =\n\t\t\t\t\ttypeof opts.action === \"function\" ? opts.action(data) : opts.action;\n\t\t\t\tupdateToast(id, { ...actionOpts, state: \"action\", id });\n\t\t\t} else {\n\t\t\t\tconst successOpts =\n\t\t\t\t\ttypeof opts.success === \"function\"\n\t\t\t\t\t\t? opts.success(data)\n\t\t\t\t\t\t: opts.success;\n\t\t\t\tupdateToast(id, { ...successOpts, state: \"success\", id });\n\t\t\t}\n\t\t}).catch((err) => {\n\t\t\tconst errorOpts =\n\t\t\t\ttypeof opts.error === \"function\" ? opts.error(err) : opts.error;\n\t\t\tupdateToast(id, { ...errorOpts, state: \"error\", id });\n\t\t});\n\n\t\treturn p;\n\t},\n\n\tdismiss: dismissToast,\n\n\tclear: (position?: SileoPosition) =>\n\t\tstore.update((prev) =>\n\t\t\tposition ? prev.filter((t) => t.position !== position) : [],\n\t\t),\n};\n\n/* ------------------------------ Toaster Component ------------------------- */\n\nexport function Toaster({\n\tchildren,\n\tposition = \"top-right\",\n\toffset,\n\toptions,\n}: SileoToasterProps) {\n\tconst [toasts, setToasts] = useState<SileoItem[]>(store.toasts);\n\tconst [activeId, setActiveId] = useState<string>();\n\n\tconst hoverRef = useRef(false);\n\tconst timersRef = useRef(new Map<string, number>());\n\tconst listRef = useRef(toasts);\n\tconst latestRef = useRef<string | undefined>(undefined);\n\tconst handlersCache = useRef(\n\t\tnew Map<\n\t\t\tstring,\n\t\t\t{\n\t\t\t\tenter: MouseEventHandler<HTMLButtonElement>;\n\t\t\t\tleave: MouseEventHandler<HTMLButtonElement>;\n\t\t\t\tdismiss: () => void;\n\t\t\t}\n\t\t>(),\n\t);\n\n\tuseEffect(() => {\n\t\tstore.position = position;\n\t\tstore.options = options;\n\t}, [position, options]);\n\n\tconst clearAllTimers = useCallback(() => {\n\t\tfor (const t of timersRef.current.values()) clearTimeout(t);\n\t\ttimersRef.current.clear();\n\t}, []);\n\n\tconst schedule = useCallback((items: SileoItem[]) => {\n\t\tif (hoverRef.current) return;\n\n\t\tfor (const item of items) {\n\t\t\tif (item.exiting) continue;\n\t\t\tconst key = timeoutKey(item);\n\t\t\tif (timersRef.current.has(key)) continue;\n\n\t\t\tconst dur = item.duration ?? DEFAULT_DURATION;\n\t\t\tif (dur === null || dur <= 0) continue;\n\n\t\t\ttimersRef.current.set(\n\t\t\t\tkey,\n\t\t\t\twindow.setTimeout(() => dismissToast(item.id), dur),\n\t\t\t);\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\tconst listener: SileoListener = (next) => setToasts(next);\n\t\tstore.listeners.add(listener);\n\t\treturn () => {\n\t\t\tstore.listeners.delete(listener);\n\t\t\tclearAllTimers();\n\t\t};\n\t}, [clearAllTimers]);\n\n\tuseEffect(() => {\n\t\tlistRef.current = toasts;\n\n\t\tconst toastKeys = new Set(toasts.map(timeoutKey));\n\t\tconst toastIds = new Set(toasts.map((t) => t.id));\n\t\tfor (const [key, timer] of timersRef.current) {\n\t\t\tif (!toastKeys.has(key)) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\ttimersRef.current.delete(key);\n\t\t\t}\n\t\t}\n\t\tfor (const id of handlersCache.current.keys()) {\n\t\t\tif (!toastIds.has(id)) handlersCache.current.delete(id);\n\t\t}\n\n\t\tschedule(toasts);\n\t}, [toasts, schedule]);\n\n\tconst handleMouseEnterRef =\n\t\tuseRef<MouseEventHandler<HTMLButtonElement>>(null);\n\tconst handleMouseLeaveRef =\n\t\tuseRef<MouseEventHandler<HTMLButtonElement>>(null);\n\n\thandleMouseEnterRef.current = useCallback<\n\t\tMouseEventHandler<HTMLButtonElement>\n\t>(() => {\n\t\tif (hoverRef.current) return;\n\t\thoverRef.current = true;\n\t\tclearAllTimers();\n\t}, [clearAllTimers]);\n\n\thandleMouseLeaveRef.current = useCallback<\n\t\tMouseEventHandler<HTMLButtonElement>\n\t>(() => {\n\t\tif (!hoverRef.current) return;\n\t\thoverRef.current = false;\n\t\tschedule(listRef.current);\n\t}, [schedule]);\n\n\tconst latest = useMemo(() => {\n\t\tfor (let i = toasts.length - 1; i >= 0; i--) {\n\t\t\tif (!toasts[i].exiting) return toasts[i].id;\n\t\t}\n\t\treturn undefined;\n\t}, [toasts]);\n\n\tuseEffect(() => {\n\t\tlatestRef.current = latest;\n\t\tsetActiveId(latest);\n\t}, [latest]);\n\n\tconst getHandlers = useCallback((toastId: string) => {\n\t\tlet cached = handlersCache.current.get(toastId);\n\t\tif (cached) return cached;\n\n\t\tcached = {\n\t\t\tenter: ((e) => {\n\t\t\t\tsetActiveId((prev) => (prev === toastId ? prev : toastId));\n\t\t\t\thandleMouseEnterRef.current?.(e);\n\t\t\t}) as MouseEventHandler<HTMLButtonElement>,\n\t\t\tleave: ((e) => {\n\t\t\t\tsetActiveId((prev) =>\n\t\t\t\t\tprev === latestRef.current ? prev : latestRef.current,\n\t\t\t\t);\n\t\t\t\thandleMouseLeaveRef.current?.(e);\n\t\t\t}) as MouseEventHandler<HTMLButtonElement>,\n\t\t\tdismiss: () => dismissToast(toastId),\n\t\t};\n\n\t\thandlersCache.current.set(toastId, cached);\n\t\treturn cached;\n\t}, []);\n\n\tconst getViewportStyle = useCallback(\n\t\t(pos: SileoPosition): CSSProperties | undefined => {\n\t\t\tif (offset === undefined) return undefined;\n\n\t\t\tconst o =\n\t\t\t\ttypeof offset === \"object\"\n\t\t\t\t\t? offset\n\t\t\t\t\t: { top: offset, right: offset, bottom: offset, left: offset };\n\n\t\t\tconst s: CSSProperties = {};\n\t\t\tconst px = (v: SileoOffsetValue) =>\n\t\t\t\ttypeof v === \"number\" ? `${v}px` : v;\n\n\t\t\tif (pos.startsWith(\"top\") && o.top) s.top = px(o.top);\n\t\t\tif (pos.startsWith(\"bottom\") && o.bottom) s.bottom = px(o.bottom);\n\t\t\tif (pos.endsWith(\"left\") && o.left) s.left = px(o.left);\n\t\t\tif (pos.endsWith(\"right\") && o.right) s.right = px(o.right);\n\n\t\t\treturn s;\n\t\t},\n\t\t[offset],\n\t);\n\n\tconst byPosition = useMemo(() => {\n\t\tconst map = {} as Partial<Record<SileoPosition, SileoItem[]>>;\n\t\tfor (const t of toasts) {\n\t\t\tconst pos = t.position ?? position;\n\t\t\tconst arr = map[pos];\n\t\t\tif (arr) {\n\t\t\t\tarr.push(t);\n\t\t\t} else {\n\t\t\t\tmap[pos] = [t];\n\t\t\t}\n\t\t}\n\t\treturn map;\n\t}, [toasts, position]);\n\n\treturn (\n\t\t<>\n\t\t\t{children}\n\t\t\t{SILEO_POSITIONS.map((pos) => {\n\t\t\t\tconst items = byPosition[pos];\n\t\t\t\tif (!items?.length) return null;\n\n\t\t\t\tconst pill = pillAlign(pos);\n\t\t\t\tconst expand = expandDir(pos);\n\n\t\t\t\treturn (\n\t\t\t\t\t<section\n\t\t\t\t\t\tkey={pos}\n\t\t\t\t\t\tdata-sileo-viewport\n\t\t\t\t\t\tdata-position={pos}\n\t\t\t\t\t\taria-live=\"polite\"\n\t\t\t\t\t\tstyle={getViewportStyle(pos)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{items.map((item) => {\n\t\t\t\t\t\t\tconst h = getHandlers(item.id);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<Sileo\n\t\t\t\t\t\t\t\t\tkey={item.id}\n\t\t\t\t\t\t\t\t\tid={item.id}\n\t\t\t\t\t\t\t\t\tstate={item.state}\n\t\t\t\t\t\t\t\t\ttitle={item.title}\n\t\t\t\t\t\t\t\t\tdescription={item.description}\n\t\t\t\t\t\t\t\t\tposition={pill}\n\t\t\t\t\t\t\t\t\texpand={expand}\n\t\t\t\t\t\t\t\t\ticon={item.icon}\n\t\t\t\t\t\t\t\t\tfill={item.fill}\n\t\t\t\t\t\t\t\t\tstyles={item.styles}\n\t\t\t\t\t\t\t\t\tbutton={item.button}\n\t\t\t\t\t\t\t\t\troundness={item.roundness}\n\t\t\t\t\t\t\t\t\texiting={item.exiting}\n\t\t\t\t\t\t\t\t\tautoExpandDelayMs={item.autoExpandDelayMs}\n\t\t\t\t\t\t\t\t\tautoCollapseDelayMs={item.autoCollapseDelayMs}\n\t\t\t\t\t\t\t\t\trefreshKey={item.instanceId}\n\t\t\t\t\t\t\t\t\tcanExpand={activeId === undefined || activeId === item.id}\n\t\t\t\t\t\t\t\t\tonMouseEnter={h.enter}\n\t\t\t\t\t\t\t\t\tonMouseLeave={h.leave}\n\t\t\t\t\t\t\t\t\tonDismiss={h.dismiss}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</section>\n\t\t\t\t);\n\t\t\t})}\n\t\t</>\n\t);\n}\n"],"names":[],"mappings":";;AACO,KAAA,UAAA;AACA,UAAA,WAAA;AACP;AACA;AACA;AACA;AACA;AACO,UAAA,WAAA;AACP;AACA;AACA;AACO,cAAA,eAAA;AACA,KAAA,aAAA,WAAA,eAAA;AACA,UAAA,YAAA;AACP;AACA,kBAAA,SAAA;AACA,eAAA,aAAA;AACA;AACA,WAAA,SAAA;AACA,aAAA,WAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAA,WAAA;AACA;;AC1BA,KAAA,gBAAA;AACA,KAAA,iBAAA,GAAA,OAAA,CAAA,MAAA,sCAAA,gBAAA;AACO,UAAA,iBAAA;AACP,eAAA,SAAA;AACA,eAAA,aAAA;AACA,aAAA,gBAAA,GAAA,iBAAA;AACA,cAAA,OAAA,CAAA,YAAA;AACA;AACO,UAAA,mBAAA;AACP,aAAA,IAAA,CAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,WAAA,YAAA,sBAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,eAAA,aAAA;AACA;AACO,cAAA,KAAA;AACP,iBAAA,YAAA;AACA,oBAAA,YAAA;AACA,kBAAA,YAAA;AACA,oBAAA,YAAA;AACA,iBAAA,YAAA;AACA,mBAAA,YAAA;AACA,0BAAA,OAAA,aAAA,OAAA,YAAA,mBAAA,QAAA,OAAA;AACA;AACA,uBAAA,aAAA;AACA;AACO,iBAAA,OAAA,2CAAA,iBAAA;;;;"}
package/dist/index.d.ts CHANGED
@@ -42,6 +42,7 @@ interface SileoPromiseOptions<T = unknown> {
42
42
  success: SileoOptions | ((data: T) => SileoOptions);
43
43
  error: SileoOptions | ((err: unknown) => SileoOptions);
44
44
  action?: SileoOptions | ((data: T) => SileoOptions);
45
+ position?: SileoPosition;
45
46
  }
46
47
  declare const sileo: {
47
48
  show: (opts: SileoOptions) => string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sources":["../src/types.ts","../src/toast.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nexport type SileoState =\n\t| \"success\"\n\t| \"loading\"\n\t| \"error\"\n\t| \"warning\"\n\t| \"info\"\n\t| \"action\";\n\nexport interface SileoStyles {\n\ttitle?: string;\n\tdescription?: string;\n\tbadge?: string;\n\tbutton?: string;\n}\n\nexport interface SileoButton {\n\ttitle: string;\n\tonClick: () => void;\n}\n\nexport const SILEO_POSITIONS = [\n\t\"top-left\",\n\t\"top-center\",\n\t\"top-right\",\n\t\"bottom-left\",\n\t\"bottom-center\",\n\t\"bottom-right\",\n] as const;\n\nexport type SileoPosition = (typeof SILEO_POSITIONS)[number];\n\nexport interface SileoOptions {\n\ttitle?: string;\n\tdescription?: ReactNode | string;\n\tposition?: SileoPosition;\n\tduration?: number | null;\n\ticon?: ReactNode | null;\n\tstyles?: SileoStyles;\n\tfill?: string;\n\troundness?: number;\n\tautopilot?: boolean | { expand?: number; collapse?: number };\n\tbutton?: SileoButton;\n}\n","import {\n\ttype CSSProperties,\n\ttype MouseEventHandler,\n\ttype ReactNode,\n\tuseCallback,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport { Sileo } from \"./sileo\";\nimport {\n\tSILEO_POSITIONS,\n\ttype SileoOptions,\n\ttype SileoPosition,\n\ttype SileoState,\n} from \"./types\";\n\n/* -------------------------------- Constants ------------------------------- */\n\nconst DEFAULT_DURATION = 6000;\nconst EXIT_DURATION = DEFAULT_DURATION * 0.1;\nconst AUTO_EXPAND_DELAY = DEFAULT_DURATION * 0.025;\nconst AUTO_COLLAPSE_DELAY = DEFAULT_DURATION - 2000;\n\nconst pillAlign = (pos: SileoPosition) =>\n\tpos.includes(\"right\") ? \"right\" : pos.includes(\"center\") ? \"center\" : \"left\";\nconst expandDir = (pos: SileoPosition) =>\n\tpos.startsWith(\"top\") ? (\"bottom\" as const) : (\"top\" as const);\n\n/* ---------------------------------- Types --------------------------------- */\n\ninterface InternalSileoOptions extends SileoOptions {\n\tid?: string;\n\tstate?: SileoState;\n}\n\ninterface SileoItem extends InternalSileoOptions {\n\tid: string;\n\tinstanceId: string;\n\texiting?: boolean;\n\tautoExpandDelayMs?: number;\n\tautoCollapseDelayMs?: number;\n}\n\ntype SileoOffsetValue = number | string;\ntype SileoOffsetConfig = Partial<\n\tRecord<\"top\" | \"right\" | \"bottom\" | \"left\", SileoOffsetValue>\n>;\n\nexport interface SileoToasterProps {\n\tchildren?: ReactNode;\n\tposition?: SileoPosition;\n\toffset?: SileoOffsetValue | SileoOffsetConfig;\n\toptions?: Partial<SileoOptions>;\n}\n\n/* ------------------------------ Global State ------------------------------ */\n\ntype SileoListener = (toasts: SileoItem[]) => void;\n\nconst store = {\n\ttoasts: [] as SileoItem[],\n\tlisteners: new Set<SileoListener>(),\n\tposition: \"top-right\" as SileoPosition,\n\toptions: undefined as Partial<SileoOptions> | undefined,\n\n\temit() {\n\t\tfor (const fn of this.listeners) fn(this.toasts);\n\t},\n\n\tupdate(fn: (prev: SileoItem[]) => SileoItem[]) {\n\t\tthis.toasts = fn(this.toasts);\n\t\tthis.emit();\n\t},\n};\n\nlet idCounter = 0;\nconst generateId = () =>\n\t`${++idCounter}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n\nconst timeoutKey = (t: SileoItem) => `${t.id}:${t.instanceId}`;\n\n/* ------------------------------- Toast API -------------------------------- */\n\nconst dismissToast = (id: string) => {\n\tconst item = store.toasts.find((t) => t.id === id);\n\tif (!item || item.exiting) return;\n\n\tstore.update((prev) =>\n\t\tprev.map((t) => (t.id === id ? { ...t, exiting: true } : t)),\n\t);\n\n\tsetTimeout(\n\t\t() => store.update((prev) => prev.filter((t) => t.id !== id)),\n\t\tEXIT_DURATION,\n\t);\n};\n\nconst resolveAutopilot = (\n\topts: InternalSileoOptions,\n\tduration: number | null,\n): { expandDelayMs?: number; collapseDelayMs?: number } => {\n\tif (opts.autopilot === false || !duration || duration <= 0) return {};\n\tconst cfg = typeof opts.autopilot === \"object\" ? opts.autopilot : undefined;\n\tconst clamp = (v: number) => Math.min(duration, Math.max(0, v));\n\treturn {\n\t\texpandDelayMs: clamp(cfg?.expand ?? AUTO_EXPAND_DELAY),\n\t\tcollapseDelayMs: clamp(cfg?.collapse ?? AUTO_COLLAPSE_DELAY),\n\t};\n};\n\nconst mergeOptions = (options: InternalSileoOptions) => ({\n\t...store.options,\n\t...options,\n\tstyles: { ...store.options?.styles, ...options.styles },\n});\n\nconst buildSileoItem = (\n\tmerged: InternalSileoOptions,\n\tid: string,\n\tfallbackPosition?: SileoPosition,\n): SileoItem => {\n\tconst duration = merged.duration ?? DEFAULT_DURATION;\n\tconst auto = resolveAutopilot(merged, duration);\n\treturn {\n\t\t...merged,\n\t\tid,\n\t\tinstanceId: generateId(),\n\t\tposition: merged.position ?? fallbackPosition ?? store.position,\n\t\tautoExpandDelayMs: auto.expandDelayMs,\n\t\tautoCollapseDelayMs: auto.collapseDelayMs,\n\t};\n};\n\nconst createToast = (options: InternalSileoOptions) => {\n\tconst live = store.toasts.filter((t) => !t.exiting);\n\tconst merged = mergeOptions(options);\n\n\tconst id = merged.id ?? \"sileo-default\";\n\tconst prev = live.find((t) => t.id === id);\n\tconst item = buildSileoItem(merged, id, prev?.position);\n\n\tif (prev) {\n\t\tstore.update((p) => p.map((t) => (t.id === id ? item : t)));\n\t} else {\n\t\tstore.update((p) => [...p, item]);\n\t}\n\treturn { id, duration: merged.duration ?? DEFAULT_DURATION };\n};\n\nconst updateToast = (id: string, options: InternalSileoOptions) => {\n\tconst existing = store.toasts.find((t) => t.id === id);\n\tif (!existing) return;\n\n\tconst item = buildSileoItem(mergeOptions(options), id, existing.position);\n\tstore.update((prev) => prev.map((t) => (t.id === id ? item : t)));\n};\n\nexport interface SileoPromiseOptions<T = unknown> {\n\tloading: Pick<SileoOptions, \"title\" | \"icon\">;\n\tsuccess: SileoOptions | ((data: T) => SileoOptions);\n\terror: SileoOptions | ((err: unknown) => SileoOptions);\n\taction?: SileoOptions | ((data: T) => SileoOptions);\n}\n\nexport const sileo = {\n\tshow: (opts: SileoOptions) => createToast(opts).id,\n\tsuccess: (opts: SileoOptions) =>\n\t\tcreateToast({ ...opts, state: \"success\" }).id,\n\terror: (opts: SileoOptions) => createToast({ ...opts, state: \"error\" }).id,\n\twarning: (opts: SileoOptions) =>\n\t\tcreateToast({ ...opts, state: \"warning\" }).id,\n\tinfo: (opts: SileoOptions) => createToast({ ...opts, state: \"info\" }).id,\n\taction: (opts: SileoOptions) => createToast({ ...opts, state: \"action\" }).id,\n\n\tpromise: <T,>(\n\t\tpromise: Promise<T> | (() => Promise<T>),\n\t\topts: SileoPromiseOptions<T>,\n\t): Promise<T> => {\n\t\tconst { id } = createToast({\n\t\t\t...opts.loading,\n\t\t\tstate: \"loading\",\n\t\t\tduration: null,\n\t\t});\n\n\t\tconst p = typeof promise === \"function\" ? promise() : promise;\n\n\t\tp.then((data) => {\n\t\t\tif (opts.action) {\n\t\t\t\tconst actionOpts =\n\t\t\t\t\ttypeof opts.action === \"function\" ? opts.action(data) : opts.action;\n\t\t\t\tupdateToast(id, { ...actionOpts, state: \"action\", id });\n\t\t\t} else {\n\t\t\t\tconst successOpts =\n\t\t\t\t\ttypeof opts.success === \"function\"\n\t\t\t\t\t\t? opts.success(data)\n\t\t\t\t\t\t: opts.success;\n\t\t\t\tupdateToast(id, { ...successOpts, state: \"success\", id });\n\t\t\t}\n\t\t}).catch((err) => {\n\t\t\tconst errorOpts =\n\t\t\t\ttypeof opts.error === \"function\" ? opts.error(err) : opts.error;\n\t\t\tupdateToast(id, { ...errorOpts, state: \"error\", id });\n\t\t});\n\n\t\treturn p;\n\t},\n\n\tdismiss: dismissToast,\n\n\tclear: (position?: SileoPosition) =>\n\t\tstore.update((prev) =>\n\t\t\tposition ? prev.filter((t) => t.position !== position) : [],\n\t\t),\n};\n\n/* ------------------------------ Toaster Component ------------------------- */\n\nexport function Toaster({\n\tchildren,\n\tposition = \"top-right\",\n\toffset,\n\toptions,\n}: SileoToasterProps) {\n\tconst [toasts, setToasts] = useState<SileoItem[]>(store.toasts);\n\tconst [activeId, setActiveId] = useState<string>();\n\n\tconst hoverRef = useRef(false);\n\tconst timersRef = useRef(new Map<string, number>());\n\tconst listRef = useRef(toasts);\n\tconst latestRef = useRef<string | undefined>(undefined);\n\tconst handlersCache = useRef(\n\t\tnew Map<\n\t\t\tstring,\n\t\t\t{\n\t\t\t\tenter: MouseEventHandler<HTMLButtonElement>;\n\t\t\t\tleave: MouseEventHandler<HTMLButtonElement>;\n\t\t\t}\n\t\t>(),\n\t);\n\n\tuseEffect(() => {\n\t\tstore.position = position;\n\t\tstore.options = options;\n\t}, [position, options]);\n\n\tconst clearAllTimers = useCallback(() => {\n\t\tfor (const t of timersRef.current.values()) clearTimeout(t);\n\t\ttimersRef.current.clear();\n\t}, []);\n\n\tconst schedule = useCallback((items: SileoItem[]) => {\n\t\tif (hoverRef.current) return;\n\n\t\tfor (const item of items) {\n\t\t\tif (item.exiting) continue;\n\t\t\tconst key = timeoutKey(item);\n\t\t\tif (timersRef.current.has(key)) continue;\n\n\t\t\tconst dur = item.duration ?? DEFAULT_DURATION;\n\t\t\tif (dur === null || dur <= 0) continue;\n\n\t\t\ttimersRef.current.set(\n\t\t\t\tkey,\n\t\t\t\twindow.setTimeout(() => dismissToast(item.id), dur),\n\t\t\t);\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\tconst listener: SileoListener = (next) => setToasts(next);\n\t\tstore.listeners.add(listener);\n\t\treturn () => {\n\t\t\tstore.listeners.delete(listener);\n\t\t\tclearAllTimers();\n\t\t};\n\t}, [clearAllTimers]);\n\n\tuseEffect(() => {\n\t\tlistRef.current = toasts;\n\n\t\tconst toastKeys = new Set(toasts.map(timeoutKey));\n\t\tconst toastIds = new Set(toasts.map((t) => t.id));\n\t\tfor (const [key, timer] of timersRef.current) {\n\t\t\tif (!toastKeys.has(key)) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\ttimersRef.current.delete(key);\n\t\t\t}\n\t\t}\n\t\tfor (const id of handlersCache.current.keys()) {\n\t\t\tif (!toastIds.has(id)) handlersCache.current.delete(id);\n\t\t}\n\n\t\tschedule(toasts);\n\t}, [toasts, schedule]);\n\n\tconst handleMouseEnterRef =\n\t\tuseRef<MouseEventHandler<HTMLButtonElement>>(null);\n\tconst handleMouseLeaveRef =\n\t\tuseRef<MouseEventHandler<HTMLButtonElement>>(null);\n\n\thandleMouseEnterRef.current = useCallback<\n\t\tMouseEventHandler<HTMLButtonElement>\n\t>(() => {\n\t\tif (hoverRef.current) return;\n\t\thoverRef.current = true;\n\t\tclearAllTimers();\n\t}, [clearAllTimers]);\n\n\thandleMouseLeaveRef.current = useCallback<\n\t\tMouseEventHandler<HTMLButtonElement>\n\t>(() => {\n\t\tif (!hoverRef.current) return;\n\t\thoverRef.current = false;\n\t\tschedule(listRef.current);\n\t}, [schedule]);\n\n\tconst latest = useMemo(() => {\n\t\tfor (let i = toasts.length - 1; i >= 0; i--) {\n\t\t\tif (!toasts[i].exiting) return toasts[i].id;\n\t\t}\n\t\treturn undefined;\n\t}, [toasts]);\n\n\tuseEffect(() => {\n\t\tlatestRef.current = latest;\n\t\tsetActiveId(latest);\n\t}, [latest]);\n\n\tconst getHandlers = useCallback((toastId: string) => {\n\t\tlet cached = handlersCache.current.get(toastId);\n\t\tif (cached) return cached;\n\n\t\tcached = {\n\t\t\tenter: ((e) => {\n\t\t\t\tsetActiveId((prev) => (prev === toastId ? prev : toastId));\n\t\t\t\thandleMouseEnterRef.current?.(e);\n\t\t\t}) as MouseEventHandler<HTMLButtonElement>,\n\t\t\tleave: ((e) => {\n\t\t\t\tsetActiveId((prev) =>\n\t\t\t\t\tprev === latestRef.current ? prev : latestRef.current,\n\t\t\t\t);\n\t\t\t\thandleMouseLeaveRef.current?.(e);\n\t\t\t}) as MouseEventHandler<HTMLButtonElement>,\n\t\t};\n\n\t\thandlersCache.current.set(toastId, cached);\n\t\treturn cached;\n\t}, []);\n\n\tconst getViewportStyle = useCallback(\n\t\t(pos: SileoPosition): CSSProperties | undefined => {\n\t\t\tif (offset === undefined) return undefined;\n\n\t\t\tconst o =\n\t\t\t\ttypeof offset === \"object\"\n\t\t\t\t\t? offset\n\t\t\t\t\t: { top: offset, right: offset, bottom: offset, left: offset };\n\n\t\t\tconst s: CSSProperties = {};\n\t\t\tconst px = (v: SileoOffsetValue) =>\n\t\t\t\ttypeof v === \"number\" ? `${v}px` : v;\n\n\t\t\tif (pos.startsWith(\"top\") && o.top) s.top = px(o.top);\n\t\t\tif (pos.startsWith(\"bottom\") && o.bottom) s.bottom = px(o.bottom);\n\t\t\tif (pos.endsWith(\"left\") && o.left) s.left = px(o.left);\n\t\t\tif (pos.endsWith(\"right\") && o.right) s.right = px(o.right);\n\n\t\t\treturn s;\n\t\t},\n\t\t[offset],\n\t);\n\n\tconst byPosition = useMemo(() => {\n\t\tconst map = {} as Partial<Record<SileoPosition, SileoItem[]>>;\n\t\tfor (const t of toasts) {\n\t\t\tconst pos = t.position ?? position;\n\t\t\tconst arr = map[pos];\n\t\t\tif (arr) {\n\t\t\t\tarr.push(t);\n\t\t\t} else {\n\t\t\t\tmap[pos] = [t];\n\t\t\t}\n\t\t}\n\t\treturn map;\n\t}, [toasts, position]);\n\n\treturn (\n\t\t<>\n\t\t\t{children}\n\t\t\t{SILEO_POSITIONS.map((pos) => {\n\t\t\t\tconst items = byPosition[pos];\n\t\t\t\tif (!items?.length) return null;\n\n\t\t\t\tconst pill = pillAlign(pos);\n\t\t\t\tconst expand = expandDir(pos);\n\n\t\t\t\treturn (\n\t\t\t\t\t<section\n\t\t\t\t\t\tkey={pos}\n\t\t\t\t\t\tdata-sileo-viewport\n\t\t\t\t\t\tdata-position={pos}\n\t\t\t\t\t\taria-live=\"polite\"\n\t\t\t\t\t\tstyle={getViewportStyle(pos)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{items.map((item) => {\n\t\t\t\t\t\t\tconst h = getHandlers(item.id);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<Sileo\n\t\t\t\t\t\t\t\t\tkey={item.id}\n\t\t\t\t\t\t\t\t\tid={item.id}\n\t\t\t\t\t\t\t\t\tstate={item.state}\n\t\t\t\t\t\t\t\t\ttitle={item.title}\n\t\t\t\t\t\t\t\t\tdescription={item.description}\n\t\t\t\t\t\t\t\t\tposition={pill}\n\t\t\t\t\t\t\t\t\texpand={expand}\n\t\t\t\t\t\t\t\t\ticon={item.icon}\n\t\t\t\t\t\t\t\t\tfill={item.fill}\n\t\t\t\t\t\t\t\t\tstyles={item.styles}\n\t\t\t\t\t\t\t\t\tbutton={item.button}\n\t\t\t\t\t\t\t\t\troundness={item.roundness}\n\t\t\t\t\t\t\t\t\texiting={item.exiting}\n\t\t\t\t\t\t\t\t\tautoExpandDelayMs={item.autoExpandDelayMs}\n\t\t\t\t\t\t\t\t\tautoCollapseDelayMs={item.autoCollapseDelayMs}\n\t\t\t\t\t\t\t\t\trefreshKey={item.instanceId}\n\t\t\t\t\t\t\t\t\tcanExpand={activeId === undefined || activeId === item.id}\n\t\t\t\t\t\t\t\t\tonMouseEnter={h.enter}\n\t\t\t\t\t\t\t\t\tonMouseLeave={h.leave}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</section>\n\t\t\t\t);\n\t\t\t})}\n\t\t</>\n\t);\n}\n"],"names":[],"mappings":";;AACO,KAAA,UAAA;AACA,UAAA,WAAA;AACP;AACA;AACA;AACA;AACA;AACO,UAAA,WAAA;AACP;AACA;AACA;AACO,cAAA,eAAA;AACA,KAAA,aAAA,WAAA,eAAA;AACA,UAAA,YAAA;AACP;AACA,kBAAA,SAAA;AACA,eAAA,aAAA;AACA;AACA,WAAA,SAAA;AACA,aAAA,WAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAA,WAAA;AACA;;AC1BA,KAAA,gBAAA;AACA,KAAA,iBAAA,GAAA,OAAA,CAAA,MAAA,sCAAA,gBAAA;AACO,UAAA,iBAAA;AACP,eAAA,SAAA;AACA,eAAA,aAAA;AACA,aAAA,gBAAA,GAAA,iBAAA;AACA,cAAA,OAAA,CAAA,YAAA;AACA;AACO,UAAA,mBAAA;AACP,aAAA,IAAA,CAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,WAAA,YAAA,sBAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA;AACO,cAAA,KAAA;AACP,iBAAA,YAAA;AACA,oBAAA,YAAA;AACA,kBAAA,YAAA;AACA,oBAAA,YAAA;AACA,iBAAA,YAAA;AACA,mBAAA,YAAA;AACA,0BAAA,OAAA,aAAA,OAAA,YAAA,mBAAA,QAAA,OAAA;AACA;AACA,uBAAA,aAAA;AACA;AACO,iBAAA,OAAA,2CAAA,iBAAA;;;;"}
1
+ {"version":3,"file":"index.d.ts","sources":["../src/types.ts","../src/toast.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nexport type SileoState =\n\t| \"success\"\n\t| \"loading\"\n\t| \"error\"\n\t| \"warning\"\n\t| \"info\"\n\t| \"action\";\n\nexport interface SileoStyles {\n\ttitle?: string;\n\tdescription?: string;\n\tbadge?: string;\n\tbutton?: string;\n}\n\nexport interface SileoButton {\n\ttitle: string;\n\tonClick: () => void;\n}\n\nexport const SILEO_POSITIONS = [\n\t\"top-left\",\n\t\"top-center\",\n\t\"top-right\",\n\t\"bottom-left\",\n\t\"bottom-center\",\n\t\"bottom-right\",\n] as const;\n\nexport type SileoPosition = (typeof SILEO_POSITIONS)[number];\n\nexport interface SileoOptions {\n\ttitle?: string;\n\tdescription?: ReactNode | string;\n\tposition?: SileoPosition;\n\tduration?: number | null;\n\ticon?: ReactNode | null;\n\tstyles?: SileoStyles;\n\tfill?: string;\n\troundness?: number;\n\tautopilot?: boolean | { expand?: number; collapse?: number };\n\tbutton?: SileoButton;\n}\n","import {\n\ttype CSSProperties,\n\ttype MouseEventHandler,\n\ttype ReactNode,\n\tuseCallback,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport { Sileo } from \"./sileo\";\nimport {\n\tSILEO_POSITIONS,\n\ttype SileoOptions,\n\ttype SileoPosition,\n\ttype SileoState,\n} from \"./types\";\n\n/* -------------------------------- Constants ------------------------------- */\n\nconst DEFAULT_DURATION = 6000;\nconst EXIT_DURATION = DEFAULT_DURATION * 0.1;\nconst AUTO_EXPAND_DELAY = DEFAULT_DURATION * 0.025;\nconst AUTO_COLLAPSE_DELAY = DEFAULT_DURATION - 2000;\n\nconst pillAlign = (pos: SileoPosition) =>\n\tpos.includes(\"right\") ? \"right\" : pos.includes(\"center\") ? \"center\" : \"left\";\nconst expandDir = (pos: SileoPosition) =>\n\tpos.startsWith(\"top\") ? (\"bottom\" as const) : (\"top\" as const);\n\n/* ---------------------------------- Types --------------------------------- */\n\ninterface InternalSileoOptions extends SileoOptions {\n\tid?: string;\n\tstate?: SileoState;\n}\n\ninterface SileoItem extends InternalSileoOptions {\n\tid: string;\n\tinstanceId: string;\n\texiting?: boolean;\n\tautoExpandDelayMs?: number;\n\tautoCollapseDelayMs?: number;\n}\n\ntype SileoOffsetValue = number | string;\ntype SileoOffsetConfig = Partial<\n\tRecord<\"top\" | \"right\" | \"bottom\" | \"left\", SileoOffsetValue>\n>;\n\nexport interface SileoToasterProps {\n\tchildren?: ReactNode;\n\tposition?: SileoPosition;\n\toffset?: SileoOffsetValue | SileoOffsetConfig;\n\toptions?: Partial<SileoOptions>;\n}\n\n/* ------------------------------ Global State ------------------------------ */\n\ntype SileoListener = (toasts: SileoItem[]) => void;\n\nconst store = {\n\ttoasts: [] as SileoItem[],\n\tlisteners: new Set<SileoListener>(),\n\tposition: \"top-right\" as SileoPosition,\n\toptions: undefined as Partial<SileoOptions> | undefined,\n\n\temit() {\n\t\tfor (const fn of this.listeners) fn(this.toasts);\n\t},\n\n\tupdate(fn: (prev: SileoItem[]) => SileoItem[]) {\n\t\tthis.toasts = fn(this.toasts);\n\t\tthis.emit();\n\t},\n};\n\nlet idCounter = 0;\nconst generateId = () =>\n\t`${++idCounter}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;\n\nconst timeoutKey = (t: SileoItem) => `${t.id}:${t.instanceId}`;\n\n/* ------------------------------- Toast API -------------------------------- */\n\nconst dismissToast = (id: string) => {\n\tconst item = store.toasts.find((t) => t.id === id);\n\tif (!item || item.exiting) return;\n\n\tstore.update((prev) =>\n\t\tprev.map((t) => (t.id === id ? { ...t, exiting: true } : t)),\n\t);\n\n\tsetTimeout(\n\t\t() => store.update((prev) => prev.filter((t) => t.id !== id)),\n\t\tEXIT_DURATION,\n\t);\n};\n\nconst resolveAutopilot = (\n\topts: InternalSileoOptions,\n\tduration: number | null,\n): { expandDelayMs?: number; collapseDelayMs?: number } => {\n\tif (opts.autopilot === false || !duration || duration <= 0) return {};\n\tconst cfg = typeof opts.autopilot === \"object\" ? opts.autopilot : undefined;\n\tconst clamp = (v: number) => Math.min(duration, Math.max(0, v));\n\treturn {\n\t\texpandDelayMs: clamp(cfg?.expand ?? AUTO_EXPAND_DELAY),\n\t\tcollapseDelayMs: clamp(cfg?.collapse ?? AUTO_COLLAPSE_DELAY),\n\t};\n};\n\nconst mergeOptions = (options: InternalSileoOptions) => ({\n\t...store.options,\n\t...options,\n\tstyles: { ...store.options?.styles, ...options.styles },\n});\n\nconst buildSileoItem = (\n\tmerged: InternalSileoOptions,\n\tid: string,\n\tfallbackPosition?: SileoPosition,\n): SileoItem => {\n\tconst duration = merged.duration ?? DEFAULT_DURATION;\n\tconst auto = resolveAutopilot(merged, duration);\n\treturn {\n\t\t...merged,\n\t\tid,\n\t\tinstanceId: generateId(),\n\t\tposition: merged.position ?? fallbackPosition ?? store.position,\n\t\tautoExpandDelayMs: auto.expandDelayMs,\n\t\tautoCollapseDelayMs: auto.collapseDelayMs,\n\t};\n};\n\nconst createToast = (options: InternalSileoOptions) => {\n\tconst live = store.toasts.filter((t) => !t.exiting);\n\tconst merged = mergeOptions(options);\n\n\tconst id = merged.id ?? \"sileo-default\";\n\tconst prev = live.find((t) => t.id === id);\n\tconst item = buildSileoItem(merged, id, prev?.position);\n\n\tif (prev) {\n\t\tstore.update((p) => p.map((t) => (t.id === id ? item : t)));\n\t} else {\n\t\tstore.update((p) => [...p.filter((t) => t.id !== id), item]);\n\t}\n\treturn { id, duration: merged.duration ?? DEFAULT_DURATION };\n};\n\nconst updateToast = (id: string, options: InternalSileoOptions) => {\n\tconst existing = store.toasts.find((t) => t.id === id);\n\tif (!existing) return;\n\n\tconst item = buildSileoItem(mergeOptions(options), id, existing.position);\n\tstore.update((prev) => prev.map((t) => (t.id === id ? item : t)));\n};\n\nexport interface SileoPromiseOptions<T = unknown> {\n\tloading: Pick<SileoOptions, \"title\" | \"icon\">;\n\tsuccess: SileoOptions | ((data: T) => SileoOptions);\n\terror: SileoOptions | ((err: unknown) => SileoOptions);\n\taction?: SileoOptions | ((data: T) => SileoOptions);\n\tposition?: SileoPosition;\n}\n\nexport const sileo = {\n\tshow: (opts: SileoOptions) => createToast(opts).id,\n\tsuccess: (opts: SileoOptions) =>\n\t\tcreateToast({ ...opts, state: \"success\" }).id,\n\terror: (opts: SileoOptions) => createToast({ ...opts, state: \"error\" }).id,\n\twarning: (opts: SileoOptions) =>\n\t\tcreateToast({ ...opts, state: \"warning\" }).id,\n\tinfo: (opts: SileoOptions) => createToast({ ...opts, state: \"info\" }).id,\n\taction: (opts: SileoOptions) => createToast({ ...opts, state: \"action\" }).id,\n\n\tpromise: <T,>(\n\t\tpromise: Promise<T> | (() => Promise<T>),\n\t\topts: SileoPromiseOptions<T>,\n\t): Promise<T> => {\n\t\tconst { id } = createToast({\n\t\t\t...opts.loading,\n\t\t\tstate: \"loading\",\n\t\t\tduration: null,\n\t\t\tposition: opts.position,\n\t\t});\n\n\t\tconst p = typeof promise === \"function\" ? promise() : promise;\n\n\t\tp.then((data) => {\n\t\t\tif (opts.action) {\n\t\t\t\tconst actionOpts =\n\t\t\t\t\ttypeof opts.action === \"function\" ? opts.action(data) : opts.action;\n\t\t\t\tupdateToast(id, { ...actionOpts, state: \"action\", id });\n\t\t\t} else {\n\t\t\t\tconst successOpts =\n\t\t\t\t\ttypeof opts.success === \"function\"\n\t\t\t\t\t\t? opts.success(data)\n\t\t\t\t\t\t: opts.success;\n\t\t\t\tupdateToast(id, { ...successOpts, state: \"success\", id });\n\t\t\t}\n\t\t}).catch((err) => {\n\t\t\tconst errorOpts =\n\t\t\t\ttypeof opts.error === \"function\" ? opts.error(err) : opts.error;\n\t\t\tupdateToast(id, { ...errorOpts, state: \"error\", id });\n\t\t});\n\n\t\treturn p;\n\t},\n\n\tdismiss: dismissToast,\n\n\tclear: (position?: SileoPosition) =>\n\t\tstore.update((prev) =>\n\t\t\tposition ? prev.filter((t) => t.position !== position) : [],\n\t\t),\n};\n\n/* ------------------------------ Toaster Component ------------------------- */\n\nexport function Toaster({\n\tchildren,\n\tposition = \"top-right\",\n\toffset,\n\toptions,\n}: SileoToasterProps) {\n\tconst [toasts, setToasts] = useState<SileoItem[]>(store.toasts);\n\tconst [activeId, setActiveId] = useState<string>();\n\n\tconst hoverRef = useRef(false);\n\tconst timersRef = useRef(new Map<string, number>());\n\tconst listRef = useRef(toasts);\n\tconst latestRef = useRef<string | undefined>(undefined);\n\tconst handlersCache = useRef(\n\t\tnew Map<\n\t\t\tstring,\n\t\t\t{\n\t\t\t\tenter: MouseEventHandler<HTMLButtonElement>;\n\t\t\t\tleave: MouseEventHandler<HTMLButtonElement>;\n\t\t\t\tdismiss: () => void;\n\t\t\t}\n\t\t>(),\n\t);\n\n\tuseEffect(() => {\n\t\tstore.position = position;\n\t\tstore.options = options;\n\t}, [position, options]);\n\n\tconst clearAllTimers = useCallback(() => {\n\t\tfor (const t of timersRef.current.values()) clearTimeout(t);\n\t\ttimersRef.current.clear();\n\t}, []);\n\n\tconst schedule = useCallback((items: SileoItem[]) => {\n\t\tif (hoverRef.current) return;\n\n\t\tfor (const item of items) {\n\t\t\tif (item.exiting) continue;\n\t\t\tconst key = timeoutKey(item);\n\t\t\tif (timersRef.current.has(key)) continue;\n\n\t\t\tconst dur = item.duration ?? DEFAULT_DURATION;\n\t\t\tif (dur === null || dur <= 0) continue;\n\n\t\t\ttimersRef.current.set(\n\t\t\t\tkey,\n\t\t\t\twindow.setTimeout(() => dismissToast(item.id), dur),\n\t\t\t);\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\tconst listener: SileoListener = (next) => setToasts(next);\n\t\tstore.listeners.add(listener);\n\t\treturn () => {\n\t\t\tstore.listeners.delete(listener);\n\t\t\tclearAllTimers();\n\t\t};\n\t}, [clearAllTimers]);\n\n\tuseEffect(() => {\n\t\tlistRef.current = toasts;\n\n\t\tconst toastKeys = new Set(toasts.map(timeoutKey));\n\t\tconst toastIds = new Set(toasts.map((t) => t.id));\n\t\tfor (const [key, timer] of timersRef.current) {\n\t\t\tif (!toastKeys.has(key)) {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\ttimersRef.current.delete(key);\n\t\t\t}\n\t\t}\n\t\tfor (const id of handlersCache.current.keys()) {\n\t\t\tif (!toastIds.has(id)) handlersCache.current.delete(id);\n\t\t}\n\n\t\tschedule(toasts);\n\t}, [toasts, schedule]);\n\n\tconst handleMouseEnterRef =\n\t\tuseRef<MouseEventHandler<HTMLButtonElement>>(null);\n\tconst handleMouseLeaveRef =\n\t\tuseRef<MouseEventHandler<HTMLButtonElement>>(null);\n\n\thandleMouseEnterRef.current = useCallback<\n\t\tMouseEventHandler<HTMLButtonElement>\n\t>(() => {\n\t\tif (hoverRef.current) return;\n\t\thoverRef.current = true;\n\t\tclearAllTimers();\n\t}, [clearAllTimers]);\n\n\thandleMouseLeaveRef.current = useCallback<\n\t\tMouseEventHandler<HTMLButtonElement>\n\t>(() => {\n\t\tif (!hoverRef.current) return;\n\t\thoverRef.current = false;\n\t\tschedule(listRef.current);\n\t}, [schedule]);\n\n\tconst latest = useMemo(() => {\n\t\tfor (let i = toasts.length - 1; i >= 0; i--) {\n\t\t\tif (!toasts[i].exiting) return toasts[i].id;\n\t\t}\n\t\treturn undefined;\n\t}, [toasts]);\n\n\tuseEffect(() => {\n\t\tlatestRef.current = latest;\n\t\tsetActiveId(latest);\n\t}, [latest]);\n\n\tconst getHandlers = useCallback((toastId: string) => {\n\t\tlet cached = handlersCache.current.get(toastId);\n\t\tif (cached) return cached;\n\n\t\tcached = {\n\t\t\tenter: ((e) => {\n\t\t\t\tsetActiveId((prev) => (prev === toastId ? prev : toastId));\n\t\t\t\thandleMouseEnterRef.current?.(e);\n\t\t\t}) as MouseEventHandler<HTMLButtonElement>,\n\t\t\tleave: ((e) => {\n\t\t\t\tsetActiveId((prev) =>\n\t\t\t\t\tprev === latestRef.current ? prev : latestRef.current,\n\t\t\t\t);\n\t\t\t\thandleMouseLeaveRef.current?.(e);\n\t\t\t}) as MouseEventHandler<HTMLButtonElement>,\n\t\t\tdismiss: () => dismissToast(toastId),\n\t\t};\n\n\t\thandlersCache.current.set(toastId, cached);\n\t\treturn cached;\n\t}, []);\n\n\tconst getViewportStyle = useCallback(\n\t\t(pos: SileoPosition): CSSProperties | undefined => {\n\t\t\tif (offset === undefined) return undefined;\n\n\t\t\tconst o =\n\t\t\t\ttypeof offset === \"object\"\n\t\t\t\t\t? offset\n\t\t\t\t\t: { top: offset, right: offset, bottom: offset, left: offset };\n\n\t\t\tconst s: CSSProperties = {};\n\t\t\tconst px = (v: SileoOffsetValue) =>\n\t\t\t\ttypeof v === \"number\" ? `${v}px` : v;\n\n\t\t\tif (pos.startsWith(\"top\") && o.top) s.top = px(o.top);\n\t\t\tif (pos.startsWith(\"bottom\") && o.bottom) s.bottom = px(o.bottom);\n\t\t\tif (pos.endsWith(\"left\") && o.left) s.left = px(o.left);\n\t\t\tif (pos.endsWith(\"right\") && o.right) s.right = px(o.right);\n\n\t\t\treturn s;\n\t\t},\n\t\t[offset],\n\t);\n\n\tconst byPosition = useMemo(() => {\n\t\tconst map = {} as Partial<Record<SileoPosition, SileoItem[]>>;\n\t\tfor (const t of toasts) {\n\t\t\tconst pos = t.position ?? position;\n\t\t\tconst arr = map[pos];\n\t\t\tif (arr) {\n\t\t\t\tarr.push(t);\n\t\t\t} else {\n\t\t\t\tmap[pos] = [t];\n\t\t\t}\n\t\t}\n\t\treturn map;\n\t}, [toasts, position]);\n\n\treturn (\n\t\t<>\n\t\t\t{children}\n\t\t\t{SILEO_POSITIONS.map((pos) => {\n\t\t\t\tconst items = byPosition[pos];\n\t\t\t\tif (!items?.length) return null;\n\n\t\t\t\tconst pill = pillAlign(pos);\n\t\t\t\tconst expand = expandDir(pos);\n\n\t\t\t\treturn (\n\t\t\t\t\t<section\n\t\t\t\t\t\tkey={pos}\n\t\t\t\t\t\tdata-sileo-viewport\n\t\t\t\t\t\tdata-position={pos}\n\t\t\t\t\t\taria-live=\"polite\"\n\t\t\t\t\t\tstyle={getViewportStyle(pos)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{items.map((item) => {\n\t\t\t\t\t\t\tconst h = getHandlers(item.id);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<Sileo\n\t\t\t\t\t\t\t\t\tkey={item.id}\n\t\t\t\t\t\t\t\t\tid={item.id}\n\t\t\t\t\t\t\t\t\tstate={item.state}\n\t\t\t\t\t\t\t\t\ttitle={item.title}\n\t\t\t\t\t\t\t\t\tdescription={item.description}\n\t\t\t\t\t\t\t\t\tposition={pill}\n\t\t\t\t\t\t\t\t\texpand={expand}\n\t\t\t\t\t\t\t\t\ticon={item.icon}\n\t\t\t\t\t\t\t\t\tfill={item.fill}\n\t\t\t\t\t\t\t\t\tstyles={item.styles}\n\t\t\t\t\t\t\t\t\tbutton={item.button}\n\t\t\t\t\t\t\t\t\troundness={item.roundness}\n\t\t\t\t\t\t\t\t\texiting={item.exiting}\n\t\t\t\t\t\t\t\t\tautoExpandDelayMs={item.autoExpandDelayMs}\n\t\t\t\t\t\t\t\t\tautoCollapseDelayMs={item.autoCollapseDelayMs}\n\t\t\t\t\t\t\t\t\trefreshKey={item.instanceId}\n\t\t\t\t\t\t\t\t\tcanExpand={activeId === undefined || activeId === item.id}\n\t\t\t\t\t\t\t\t\tonMouseEnter={h.enter}\n\t\t\t\t\t\t\t\t\tonMouseLeave={h.leave}\n\t\t\t\t\t\t\t\t\tonDismiss={h.dismiss}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</section>\n\t\t\t\t);\n\t\t\t})}\n\t\t</>\n\t);\n}\n"],"names":[],"mappings":";;AACO,KAAA,UAAA;AACA,UAAA,WAAA;AACP;AACA;AACA;AACA;AACA;AACO,UAAA,WAAA;AACP;AACA;AACA;AACO,cAAA,eAAA;AACA,KAAA,aAAA,WAAA,eAAA;AACA,UAAA,YAAA;AACP;AACA,kBAAA,SAAA;AACA,eAAA,aAAA;AACA;AACA,WAAA,SAAA;AACA,aAAA,WAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAA,WAAA;AACA;;AC1BA,KAAA,gBAAA;AACA,KAAA,iBAAA,GAAA,OAAA,CAAA,MAAA,sCAAA,gBAAA;AACO,UAAA,iBAAA;AACP,eAAA,SAAA;AACA,eAAA,aAAA;AACA,aAAA,gBAAA,GAAA,iBAAA;AACA,cAAA,OAAA,CAAA,YAAA;AACA;AACO,UAAA,mBAAA;AACP,aAAA,IAAA,CAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,WAAA,YAAA,sBAAA,YAAA;AACA,aAAA,YAAA,iBAAA,YAAA;AACA,eAAA,aAAA;AACA;AACO,cAAA,KAAA;AACP,iBAAA,YAAA;AACA,oBAAA,YAAA;AACA,kBAAA,YAAA;AACA,oBAAA,YAAA;AACA,iBAAA,YAAA;AACA,mBAAA,YAAA;AACA,0BAAA,OAAA,aAAA,OAAA,YAAA,mBAAA,QAAA,OAAA;AACA;AACA,uBAAA,aAAA;AACA;AACO,iBAAA,OAAA,2CAAA,iBAAA;;;;"}
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var cc = require('./cc-DGff5sSY.js');
14
14
  var jsxRuntime = require('react/jsx-runtime');
15
15
  var react = require('react');
16
16
 
17
- __insertCSS(":root{--sileo-spring-easing:linear(\n\t\t0,\n\t\t0.002 0.6%,\n\t\t0.007 1.2%,\n\t\t0.015 1.8%,\n\t\t0.025 2.4%,\n\t\t0.057 3.7%,\n\t\t0.104 5.2%,\n\t\t0.151 6.5%,\n\t\t0.208 7.9%,\n\t\t0.455 13.6%,\n\t\t0.566 16.3%,\n\t\t0.619 17.7%,\n\t\t0.669 19.1%,\n\t\t0.715 20.5%,\n\t\t0.755 21.8%,\n\t\t0.794 23.2%,\n\t\t0.829 24.6%,\n\t\t0.861 26%,\n\t\t0.889 27.4%,\n\t\t0.914 28.8%,\n\t\t0.937 30.3%,\n\t\t0.956 31.8%,\n\t\t0.974 33.4%,\n\t\t0.987 34.8%,\n\t\t0.997 36.2%,\n\t\t1.014 39.2%,\n\t\t1.024 42.5%,\n\t\t1.028 46.3%,\n\t\t1.026 51.9%,\n\t\t1.01 66.1%,\n\t\t1.003 74.9%,\n\t\t1 85.2%,\n\t\t1\n\t);--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)}[data-sileo-toast]{position:relative;cursor:pointer;pointer-events:auto;border:0;background:0 0;padding:0;width:var(--sileo-width);height:var(--sileo-button-height,var(--sileo-height));opacity:0;transform:translateZ(0) scale(.95);transform-origin:center;filter:drop-shadow(0 0 8px rgba(0, 0, 0, .05));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),margin-bottom calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),margin-top 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-body],[data-sileo-pill]{transform-box:fill-box;transform-origin:50% 0%}[data-sileo-toast][data-ready=true] [data-sileo-pill]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),width var(--sileo-duration) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing),x var(--sileo-duration) var(--sileo-spring-easing),fill var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-toast][data-ready=true][data-expanded=true] [data-sileo-pill]{transition-delay:calc(var(--sileo-duration) * 0.08)}[data-sileo-toast][data-ready=true] [data-sileo-body]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),opacity var(--sileo-duration) var(--sileo-spring-easing),fill var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-header]{position:absolute;z-index:20;display:flex;align-items:center;padding:.5rem;height:var(--sileo-height);overflow:hidden}[data-sileo-toast][data-ready=true] [data-sileo-header]{max-width:var(--sileo-pill-width);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;opacity:1;filter:blur(0px)}[data-sileo-header-inner][data-layer=current]{animation:sileo-header-enter 1s var(--sileo-spring-easing) both}[data-sileo-header-inner][data-layer=prev]{position:absolute;left:0;top:0;pointer-events:none}[data-sileo-header-inner][data-exiting=true]{animation:sileo-header-exit 150ms ease forwards}[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-state]{--_c:var(--sileo-state-success);--sileo-tone:var(--_c);--sileo-tone-bg:color-mix(in oklch, var(--_c) 20%, transparent)}:is([data-sileo-badge],[data-sileo-title])[data-state=loading]{--_c:var(--sileo-state-loading)}:is([data-sileo-badge],[data-sileo-title])[data-state=error]{--_c:var(--sileo-state-error)}:is([data-sileo-badge],[data-sileo-title])[data-state=warning]{--_c:var(--sileo-state-warning)}:is([data-sileo-badge],[data-sileo-title])[data-state=info]{--_c:var(--sileo-state-info)}:is([data-sileo-badge],[data-sileo-title])[data-state=action]{--_c:var(--sileo-state-action)}[data-sileo-content]{position:absolute;left:0;z-index:10;width:100%;pointer-events:none}[data-sileo-toast][data-ready=true] [data-sileo-content]{transition:opacity calc(var(--sileo-duration) * .08) var(--sileo-spring-easing) 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 var(--sileo-duration) var(--sileo-spring-easing) calc(var(--sileo-duration) * .25)}[data-sileo-description]{width:100%;text-align:left;padding:1rem;font-size:.875rem;line-height:1.25rem;contain:layout style;content-visibility:auto}[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}[data-sileo-button]:hover{background-color:var(--sileo-btn-bg-hover,transparent)}[data-sileo-button][data-state]{--_c:var(--sileo-state-success);--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-button][data-state=loading]{--_c:var(--sileo-state-loading)}[data-sileo-button][data-state=error]{--_c:var(--sileo-state-error)}[data-sileo-button][data-state=warning]{--_c:var(--sileo-state-warning)}[data-sileo-button][data-state=info]{--_c:var(--sileo-state-info)}[data-sileo-button][data-state=action]{--_c:var(--sileo-state-action)}[data-sileo-icon=spin]{animation:sileo-spin 1s linear infinite}@keyframes sileo-spin{to{rotate:360deg}}@keyframes sileo-header-enter{from{opacity:0;filter:blur(6px)}to{opacity:1;filter:blur(0px)}}@keyframes sileo-header-exit{from{opacity:1;filter:blur(0px)}to{opacity:0;filter:blur(6px)}}[data-sileo-viewport]{position:fixed;z-index:50;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}@media (prefers-reduced-motion:no-preference){[data-sileo-toast][data-ready=true]{will-change:transform,opacity}[data-sileo-toast][data-ready=true][data-expanded=true]{will-change:transform,opacity,height}[data-sileo-body],[data-sileo-pill]{will-change:transform}[data-sileo-canvas]{will-change:filter}}@media (prefers-reduced-motion:reduce){*,::after,::before{animation-duration:0s;animation-iteration-count:1;transition-duration:0s}}");
17
+ __insertCSS(":root{--sileo-spring-easing:linear(\n\t\t0,\n\t\t0.002 0.6%,\n\t\t0.007 1.2%,\n\t\t0.015 1.8%,\n\t\t0.025 2.4%,\n\t\t0.057 3.7%,\n\t\t0.104 5.2%,\n\t\t0.151 6.5%,\n\t\t0.208 7.9%,\n\t\t0.455 13.6%,\n\t\t0.566 16.3%,\n\t\t0.619 17.7%,\n\t\t0.669 19.1%,\n\t\t0.715 20.5%,\n\t\t0.755 21.8%,\n\t\t0.794 23.2%,\n\t\t0.829 24.6%,\n\t\t0.861 26%,\n\t\t0.889 27.4%,\n\t\t0.914 28.8%,\n\t\t0.937 30.3%,\n\t\t0.956 31.8%,\n\t\t0.974 33.4%,\n\t\t0.987 34.8%,\n\t\t0.997 36.2%,\n\t\t1.014 39.2%,\n\t\t1.024 42.5%,\n\t\t1.028 46.3%,\n\t\t1.026 51.9%,\n\t\t1.01 66.1%,\n\t\t1.003 74.9%,\n\t\t1 85.2%,\n\t\t1\n\t);--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)}[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(--sileo-button-height,var(--sileo-height));opacity:0;transform:translateZ(0) scale(.95);transform-origin:center;filter:drop-shadow(0 0 8px rgba(0, 0, 0, .05));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),margin-bottom calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),margin-top 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-body],[data-sileo-pill]{transform-box:fill-box;transform-origin:50% 0%}[data-sileo-toast][data-ready=true] [data-sileo-pill]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),width var(--sileo-duration) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing),x var(--sileo-duration) var(--sileo-spring-easing),fill var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-toast][data-ready=true][data-expanded=true] [data-sileo-pill]{transition-delay:calc(var(--sileo-duration) * 0.08)}[data-sileo-toast][data-ready=true] [data-sileo-body]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),opacity var(--sileo-duration) var(--sileo-spring-easing),fill var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-header]{position:absolute;z-index:20;display:flex;align-items:center;padding:.5rem;height:var(--sileo-height);overflow:hidden}[data-sileo-toast][data-ready=true] [data-sileo-header]{max-width:var(--sileo-pill-width);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;opacity:1;filter:blur(0px)}[data-sileo-header-inner][data-layer=current]{animation:sileo-header-enter 1s var(--sileo-spring-easing) both}[data-sileo-header-inner][data-layer=prev]{position:absolute;left:0;top:0;pointer-events:none}[data-sileo-header-inner][data-exiting=true]{animation:sileo-header-exit 150ms ease forwards}[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-state]{--_c:var(--sileo-state-success);--sileo-tone:var(--_c);--sileo-tone-bg:color-mix(in oklch, var(--_c) 20%, transparent)}:is([data-sileo-badge],[data-sileo-title])[data-state=loading]{--_c:var(--sileo-state-loading)}:is([data-sileo-badge],[data-sileo-title])[data-state=error]{--_c:var(--sileo-state-error)}:is([data-sileo-badge],[data-sileo-title])[data-state=warning]{--_c:var(--sileo-state-warning)}:is([data-sileo-badge],[data-sileo-title])[data-state=info]{--_c:var(--sileo-state-info)}:is([data-sileo-badge],[data-sileo-title])[data-state=action]{--_c:var(--sileo-state-action)}[data-sileo-content]{position:absolute;left:0;z-index:10;width:100%;pointer-events:none}[data-sileo-toast][data-ready=true] [data-sileo-content]{transition:opacity calc(var(--sileo-duration) * .08) var(--sileo-spring-easing) 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 var(--sileo-duration) var(--sileo-spring-easing) calc(var(--sileo-duration) * .25)}[data-sileo-description]{width:100%;text-align:left;padding:1rem;font-size:.875rem;line-height:1.25rem;contain:layout style;content-visibility:auto}[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}[data-sileo-button]:hover{background-color:var(--sileo-btn-bg-hover,transparent)}[data-sileo-button][data-state]{--_c:var(--sileo-state-success);--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-button][data-state=loading]{--_c:var(--sileo-state-loading)}[data-sileo-button][data-state=error]{--_c:var(--sileo-state-error)}[data-sileo-button][data-state=warning]{--_c:var(--sileo-state-warning)}[data-sileo-button][data-state=info]{--_c:var(--sileo-state-info)}[data-sileo-button][data-state=action]{--_c:var(--sileo-state-action)}[data-sileo-icon=spin]{animation:sileo-spin 1s linear infinite}@keyframes sileo-spin{to{rotate:360deg}}@keyframes sileo-header-enter{from{opacity:0;filter:blur(6px)}to{opacity:1;filter:blur(0px)}}@keyframes sileo-header-exit{from{opacity:1;filter:blur(0px)}to{opacity:0;filter:blur(6px)}}[data-sileo-viewport]{position:fixed;z-index:50;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}@media (prefers-reduced-motion:no-preference){[data-sileo-toast][data-ready=true]{will-change:transform,opacity}[data-sileo-toast][data-ready=true][data-expanded=true]{will-change:transform,opacity,height}[data-sileo-body],[data-sileo-pill]{will-change:transform}[data-sileo-canvas]{will-change:filter}}@media (prefers-reduced-motion:reduce){*,::after,::before{animation-duration:0s;animation-iteration-count:1;transition-duration:0s}}");
18
18
 
19
19
  const ArrowRight = ()=>/*#__PURE__*/ jsxRuntime.jsxs("svg", {
20
20
  xmlns: "http://www.w3.org/2000/svg",
@@ -219,7 +219,7 @@ const HEADER_EXIT_MS = 150;
219
219
  })
220
220
  });
221
221
  });
222
- /* ------------------------------- Component -------------------------------- */ const Sileo = /*#__PURE__*/ react.memo(function Sileo({ id, fill = "#FFFFFF", state = "success", title = state, description, position = "left", expand = "bottom", className, icon, styles, button, roundness, exiting = false, autoExpandDelayMs, autoCollapseDelayMs, canExpand, interruptKey, refreshKey, onMouseEnter, onMouseLeave }) {
222
+ /* ------------------------------- Component -------------------------------- */ const Sileo = /*#__PURE__*/ react.memo(function Sileo({ id, fill = "#FFFFFF", state = "success", title = state, description, position = "left", expand = "bottom", className, icon, styles, button, roundness, exiting = false, autoExpandDelayMs, autoCollapseDelayMs, canExpand, interruptKey, refreshKey, onMouseEnter, onMouseLeave, onDismiss }) {
223
223
  var _headerLayer_current_view_icon, _headerLayer_prev_view_icon;
224
224
  var _headerLayer_current_view_styles, _headerLayer_current_view_styles1, _headerLayer_prev_view_styles, _headerLayer_prev_view_styles1, _view_styles, _view_styles1;
225
225
  const next = react.useMemo(()=>({
@@ -503,7 +503,48 @@ const HEADER_EXIT_MS = 150;
503
503
  }, [
504
504
  open
505
505
  ]);
506
+ /* -------------------------------- Swipe ----------------------------------- */ const SWIPE_DISMISS = 30;
507
+ const SWIPE_MAX = 20;
508
+ const buttonRef = react.useRef(null);
509
+ const pointerStartRef = react.useRef(null);
510
+ const onDismissRef = react.useRef(onDismiss);
511
+ onDismissRef.current = onDismiss;
512
+ react.useEffect(()=>{
513
+ const el = buttonRef.current;
514
+ if (!el) return;
515
+ const onMove = (e)=>{
516
+ if (pointerStartRef.current === null) return;
517
+ const dy = e.clientY - pointerStartRef.current;
518
+ const sign = dy > 0 ? 1 : -1;
519
+ const clamped = Math.min(Math.abs(dy), SWIPE_MAX) * sign;
520
+ el.style.transform = `translateY(${clamped}px)`;
521
+ };
522
+ const onUp = (e)=>{
523
+ if (pointerStartRef.current === null) return;
524
+ const dy = e.clientY - pointerStartRef.current;
525
+ pointerStartRef.current = null;
526
+ el.style.transform = "";
527
+ if (Math.abs(dy) > SWIPE_DISMISS) {
528
+ onDismissRef.current == null ? void 0 : onDismissRef.current.call(onDismissRef);
529
+ }
530
+ };
531
+ el.addEventListener("pointermove", onMove);
532
+ el.addEventListener("pointerup", onUp);
533
+ return ()=>{
534
+ el.removeEventListener("pointermove", onMove);
535
+ el.removeEventListener("pointerup", onUp);
536
+ };
537
+ }, []);
538
+ const handlePointerDown = react.useCallback((e)=>{
539
+ if (exiting || !onDismiss) return;
540
+ pointerStartRef.current = e.clientY;
541
+ e.currentTarget.setPointerCapture(e.pointerId);
542
+ }, [
543
+ exiting,
544
+ onDismiss
545
+ ]);
506
546
  /* --------------------------------- Render --------------------------------- */ return /*#__PURE__*/ jsxRuntime.jsxs("button", {
547
+ ref: buttonRef,
507
548
  type: "button",
508
549
  "data-sileo-toast": true,
509
550
  "data-ready": ready,
@@ -517,6 +558,7 @@ const HEADER_EXIT_MS = 150;
517
558
  onMouseEnter: handleEnter,
518
559
  onMouseLeave: handleLeave,
519
560
  onTransitionEnd: handleTransitionEnd,
561
+ onPointerDown: handlePointerDown,
520
562
  children: [
521
563
  /*#__PURE__*/ jsxRuntime.jsx("div", {
522
564
  "data-sileo-canvas": true,
@@ -719,7 +761,7 @@ const createToast = (options)=>{
719
761
  store.update((p)=>p.map((t)=>t.id === id ? item : t));
720
762
  } else {
721
763
  store.update((p)=>[
722
- ...p,
764
+ ...p.filter((t)=>t.id !== id),
723
765
  item
724
766
  ]);
725
767
  }
@@ -754,7 +796,8 @@ const sileo = {
754
796
  promise: (promise, opts)=>{
755
797
  const { id } = createToast(cc._extends({}, opts.loading, {
756
798
  state: "loading",
757
- duration: null
799
+ duration: null,
800
+ position: opts.position
758
801
  }));
759
802
  const p = typeof promise === "function" ? promise() : promise;
760
803
  p.then((data)=>{
@@ -883,7 +926,8 @@ const sileo = {
883
926
  leave: (e)=>{
884
927
  setActiveId((prev)=>prev === latestRef.current ? prev : latestRef.current);
885
928
  handleMouseLeaveRef.current == null ? void 0 : handleMouseLeaveRef.current.call(handleMouseLeaveRef, e);
886
- }
929
+ },
930
+ dismiss: ()=>dismissToast(toastId)
887
931
  };
888
932
  handlersCache.current.set(toastId, cached);
889
933
  return cached;
@@ -958,7 +1002,8 @@ const sileo = {
958
1002
  refreshKey: item.instanceId,
959
1003
  canExpand: activeId === undefined || activeId === item.id,
960
1004
  onMouseEnter: h.enter,
961
- onMouseLeave: h.leave
1005
+ onMouseLeave: h.leave,
1006
+ onDismiss: h.dismiss
962
1007
  }, item.id);
963
1008
  })
964
1009
  }, pos);
package/dist/index.mjs CHANGED
@@ -12,7 +12,7 @@ import { _ as _extends } from './cc-2Yt7NqMX.mjs';
12
12
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
13
13
  import { memo, useMemo, useState, useRef, useLayoutEffect, useEffect, useCallback } from 'react';
14
14
 
15
- __insertCSS(":root{--sileo-spring-easing:linear(\n\t\t0,\n\t\t0.002 0.6%,\n\t\t0.007 1.2%,\n\t\t0.015 1.8%,\n\t\t0.025 2.4%,\n\t\t0.057 3.7%,\n\t\t0.104 5.2%,\n\t\t0.151 6.5%,\n\t\t0.208 7.9%,\n\t\t0.455 13.6%,\n\t\t0.566 16.3%,\n\t\t0.619 17.7%,\n\t\t0.669 19.1%,\n\t\t0.715 20.5%,\n\t\t0.755 21.8%,\n\t\t0.794 23.2%,\n\t\t0.829 24.6%,\n\t\t0.861 26%,\n\t\t0.889 27.4%,\n\t\t0.914 28.8%,\n\t\t0.937 30.3%,\n\t\t0.956 31.8%,\n\t\t0.974 33.4%,\n\t\t0.987 34.8%,\n\t\t0.997 36.2%,\n\t\t1.014 39.2%,\n\t\t1.024 42.5%,\n\t\t1.028 46.3%,\n\t\t1.026 51.9%,\n\t\t1.01 66.1%,\n\t\t1.003 74.9%,\n\t\t1 85.2%,\n\t\t1\n\t);--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)}[data-sileo-toast]{position:relative;cursor:pointer;pointer-events:auto;border:0;background:0 0;padding:0;width:var(--sileo-width);height:var(--sileo-button-height,var(--sileo-height));opacity:0;transform:translateZ(0) scale(.95);transform-origin:center;filter:drop-shadow(0 0 8px rgba(0, 0, 0, .05));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),margin-bottom calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),margin-top 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-body],[data-sileo-pill]{transform-box:fill-box;transform-origin:50% 0%}[data-sileo-toast][data-ready=true] [data-sileo-pill]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),width var(--sileo-duration) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing),x var(--sileo-duration) var(--sileo-spring-easing),fill var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-toast][data-ready=true][data-expanded=true] [data-sileo-pill]{transition-delay:calc(var(--sileo-duration) * 0.08)}[data-sileo-toast][data-ready=true] [data-sileo-body]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),opacity var(--sileo-duration) var(--sileo-spring-easing),fill var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-header]{position:absolute;z-index:20;display:flex;align-items:center;padding:.5rem;height:var(--sileo-height);overflow:hidden}[data-sileo-toast][data-ready=true] [data-sileo-header]{max-width:var(--sileo-pill-width);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;opacity:1;filter:blur(0px)}[data-sileo-header-inner][data-layer=current]{animation:sileo-header-enter 1s var(--sileo-spring-easing) both}[data-sileo-header-inner][data-layer=prev]{position:absolute;left:0;top:0;pointer-events:none}[data-sileo-header-inner][data-exiting=true]{animation:sileo-header-exit 150ms ease forwards}[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-state]{--_c:var(--sileo-state-success);--sileo-tone:var(--_c);--sileo-tone-bg:color-mix(in oklch, var(--_c) 20%, transparent)}:is([data-sileo-badge],[data-sileo-title])[data-state=loading]{--_c:var(--sileo-state-loading)}:is([data-sileo-badge],[data-sileo-title])[data-state=error]{--_c:var(--sileo-state-error)}:is([data-sileo-badge],[data-sileo-title])[data-state=warning]{--_c:var(--sileo-state-warning)}:is([data-sileo-badge],[data-sileo-title])[data-state=info]{--_c:var(--sileo-state-info)}:is([data-sileo-badge],[data-sileo-title])[data-state=action]{--_c:var(--sileo-state-action)}[data-sileo-content]{position:absolute;left:0;z-index:10;width:100%;pointer-events:none}[data-sileo-toast][data-ready=true] [data-sileo-content]{transition:opacity calc(var(--sileo-duration) * .08) var(--sileo-spring-easing) 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 var(--sileo-duration) var(--sileo-spring-easing) calc(var(--sileo-duration) * .25)}[data-sileo-description]{width:100%;text-align:left;padding:1rem;font-size:.875rem;line-height:1.25rem;contain:layout style;content-visibility:auto}[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}[data-sileo-button]:hover{background-color:var(--sileo-btn-bg-hover,transparent)}[data-sileo-button][data-state]{--_c:var(--sileo-state-success);--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-button][data-state=loading]{--_c:var(--sileo-state-loading)}[data-sileo-button][data-state=error]{--_c:var(--sileo-state-error)}[data-sileo-button][data-state=warning]{--_c:var(--sileo-state-warning)}[data-sileo-button][data-state=info]{--_c:var(--sileo-state-info)}[data-sileo-button][data-state=action]{--_c:var(--sileo-state-action)}[data-sileo-icon=spin]{animation:sileo-spin 1s linear infinite}@keyframes sileo-spin{to{rotate:360deg}}@keyframes sileo-header-enter{from{opacity:0;filter:blur(6px)}to{opacity:1;filter:blur(0px)}}@keyframes sileo-header-exit{from{opacity:1;filter:blur(0px)}to{opacity:0;filter:blur(6px)}}[data-sileo-viewport]{position:fixed;z-index:50;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}@media (prefers-reduced-motion:no-preference){[data-sileo-toast][data-ready=true]{will-change:transform,opacity}[data-sileo-toast][data-ready=true][data-expanded=true]{will-change:transform,opacity,height}[data-sileo-body],[data-sileo-pill]{will-change:transform}[data-sileo-canvas]{will-change:filter}}@media (prefers-reduced-motion:reduce){*,::after,::before{animation-duration:0s;animation-iteration-count:1;transition-duration:0s}}");
15
+ __insertCSS(":root{--sileo-spring-easing:linear(\n\t\t0,\n\t\t0.002 0.6%,\n\t\t0.007 1.2%,\n\t\t0.015 1.8%,\n\t\t0.025 2.4%,\n\t\t0.057 3.7%,\n\t\t0.104 5.2%,\n\t\t0.151 6.5%,\n\t\t0.208 7.9%,\n\t\t0.455 13.6%,\n\t\t0.566 16.3%,\n\t\t0.619 17.7%,\n\t\t0.669 19.1%,\n\t\t0.715 20.5%,\n\t\t0.755 21.8%,\n\t\t0.794 23.2%,\n\t\t0.829 24.6%,\n\t\t0.861 26%,\n\t\t0.889 27.4%,\n\t\t0.914 28.8%,\n\t\t0.937 30.3%,\n\t\t0.956 31.8%,\n\t\t0.974 33.4%,\n\t\t0.987 34.8%,\n\t\t0.997 36.2%,\n\t\t1.014 39.2%,\n\t\t1.024 42.5%,\n\t\t1.028 46.3%,\n\t\t1.026 51.9%,\n\t\t1.01 66.1%,\n\t\t1.003 74.9%,\n\t\t1 85.2%,\n\t\t1\n\t);--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)}[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(--sileo-button-height,var(--sileo-height));opacity:0;transform:translateZ(0) scale(.95);transform-origin:center;filter:drop-shadow(0 0 8px rgba(0, 0, 0, .05));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),margin-bottom calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),margin-top 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-body],[data-sileo-pill]{transform-box:fill-box;transform-origin:50% 0%}[data-sileo-toast][data-ready=true] [data-sileo-pill]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),width var(--sileo-duration) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing),x var(--sileo-duration) var(--sileo-spring-easing),fill var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-toast][data-ready=true][data-expanded=true] [data-sileo-pill]{transition-delay:calc(var(--sileo-duration) * 0.08)}[data-sileo-toast][data-ready=true] [data-sileo-body]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),opacity var(--sileo-duration) var(--sileo-spring-easing),fill var(--sileo-duration) var(--sileo-spring-easing)}[data-sileo-header]{position:absolute;z-index:20;display:flex;align-items:center;padding:.5rem;height:var(--sileo-height);overflow:hidden}[data-sileo-toast][data-ready=true] [data-sileo-header]{max-width:var(--sileo-pill-width);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;opacity:1;filter:blur(0px)}[data-sileo-header-inner][data-layer=current]{animation:sileo-header-enter 1s var(--sileo-spring-easing) both}[data-sileo-header-inner][data-layer=prev]{position:absolute;left:0;top:0;pointer-events:none}[data-sileo-header-inner][data-exiting=true]{animation:sileo-header-exit 150ms ease forwards}[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-state]{--_c:var(--sileo-state-success);--sileo-tone:var(--_c);--sileo-tone-bg:color-mix(in oklch, var(--_c) 20%, transparent)}:is([data-sileo-badge],[data-sileo-title])[data-state=loading]{--_c:var(--sileo-state-loading)}:is([data-sileo-badge],[data-sileo-title])[data-state=error]{--_c:var(--sileo-state-error)}:is([data-sileo-badge],[data-sileo-title])[data-state=warning]{--_c:var(--sileo-state-warning)}:is([data-sileo-badge],[data-sileo-title])[data-state=info]{--_c:var(--sileo-state-info)}:is([data-sileo-badge],[data-sileo-title])[data-state=action]{--_c:var(--sileo-state-action)}[data-sileo-content]{position:absolute;left:0;z-index:10;width:100%;pointer-events:none}[data-sileo-toast][data-ready=true] [data-sileo-content]{transition:opacity calc(var(--sileo-duration) * .08) var(--sileo-spring-easing) 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 var(--sileo-duration) var(--sileo-spring-easing) calc(var(--sileo-duration) * .25)}[data-sileo-description]{width:100%;text-align:left;padding:1rem;font-size:.875rem;line-height:1.25rem;contain:layout style;content-visibility:auto}[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}[data-sileo-button]:hover{background-color:var(--sileo-btn-bg-hover,transparent)}[data-sileo-button][data-state]{--_c:var(--sileo-state-success);--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-button][data-state=loading]{--_c:var(--sileo-state-loading)}[data-sileo-button][data-state=error]{--_c:var(--sileo-state-error)}[data-sileo-button][data-state=warning]{--_c:var(--sileo-state-warning)}[data-sileo-button][data-state=info]{--_c:var(--sileo-state-info)}[data-sileo-button][data-state=action]{--_c:var(--sileo-state-action)}[data-sileo-icon=spin]{animation:sileo-spin 1s linear infinite}@keyframes sileo-spin{to{rotate:360deg}}@keyframes sileo-header-enter{from{opacity:0;filter:blur(6px)}to{opacity:1;filter:blur(0px)}}@keyframes sileo-header-exit{from{opacity:1;filter:blur(0px)}to{opacity:0;filter:blur(6px)}}[data-sileo-viewport]{position:fixed;z-index:50;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}@media (prefers-reduced-motion:no-preference){[data-sileo-toast][data-ready=true]{will-change:transform,opacity}[data-sileo-toast][data-ready=true][data-expanded=true]{will-change:transform,opacity,height}[data-sileo-body],[data-sileo-pill]{will-change:transform}[data-sileo-canvas]{will-change:filter}}@media (prefers-reduced-motion:reduce){*,::after,::before{animation-duration:0s;animation-iteration-count:1;transition-duration:0s}}");
16
16
 
17
17
  const ArrowRight = ()=>/*#__PURE__*/ jsxs("svg", {
18
18
  xmlns: "http://www.w3.org/2000/svg",
@@ -217,7 +217,7 @@ const HEADER_EXIT_MS = 150;
217
217
  })
218
218
  });
219
219
  });
220
- /* ------------------------------- Component -------------------------------- */ const Sileo = /*#__PURE__*/ memo(function Sileo({ id, fill = "#FFFFFF", state = "success", title = state, description, position = "left", expand = "bottom", className, icon, styles, button, roundness, exiting = false, autoExpandDelayMs, autoCollapseDelayMs, canExpand, interruptKey, refreshKey, onMouseEnter, onMouseLeave }) {
220
+ /* ------------------------------- Component -------------------------------- */ const Sileo = /*#__PURE__*/ memo(function Sileo({ id, fill = "#FFFFFF", state = "success", title = state, description, position = "left", expand = "bottom", className, icon, styles, button, roundness, exiting = false, autoExpandDelayMs, autoCollapseDelayMs, canExpand, interruptKey, refreshKey, onMouseEnter, onMouseLeave, onDismiss }) {
221
221
  var _headerLayer_current_view_icon, _headerLayer_prev_view_icon;
222
222
  var _headerLayer_current_view_styles, _headerLayer_current_view_styles1, _headerLayer_prev_view_styles, _headerLayer_prev_view_styles1, _view_styles, _view_styles1;
223
223
  const next = useMemo(()=>({
@@ -501,7 +501,48 @@ const HEADER_EXIT_MS = 150;
501
501
  }, [
502
502
  open
503
503
  ]);
504
+ /* -------------------------------- Swipe ----------------------------------- */ const SWIPE_DISMISS = 30;
505
+ const SWIPE_MAX = 20;
506
+ const buttonRef = useRef(null);
507
+ const pointerStartRef = useRef(null);
508
+ const onDismissRef = useRef(onDismiss);
509
+ onDismissRef.current = onDismiss;
510
+ useEffect(()=>{
511
+ const el = buttonRef.current;
512
+ if (!el) return;
513
+ const onMove = (e)=>{
514
+ if (pointerStartRef.current === null) return;
515
+ const dy = e.clientY - pointerStartRef.current;
516
+ const sign = dy > 0 ? 1 : -1;
517
+ const clamped = Math.min(Math.abs(dy), SWIPE_MAX) * sign;
518
+ el.style.transform = `translateY(${clamped}px)`;
519
+ };
520
+ const onUp = (e)=>{
521
+ if (pointerStartRef.current === null) return;
522
+ const dy = e.clientY - pointerStartRef.current;
523
+ pointerStartRef.current = null;
524
+ el.style.transform = "";
525
+ if (Math.abs(dy) > SWIPE_DISMISS) {
526
+ onDismissRef.current == null ? void 0 : onDismissRef.current.call(onDismissRef);
527
+ }
528
+ };
529
+ el.addEventListener("pointermove", onMove);
530
+ el.addEventListener("pointerup", onUp);
531
+ return ()=>{
532
+ el.removeEventListener("pointermove", onMove);
533
+ el.removeEventListener("pointerup", onUp);
534
+ };
535
+ }, []);
536
+ const handlePointerDown = useCallback((e)=>{
537
+ if (exiting || !onDismiss) return;
538
+ pointerStartRef.current = e.clientY;
539
+ e.currentTarget.setPointerCapture(e.pointerId);
540
+ }, [
541
+ exiting,
542
+ onDismiss
543
+ ]);
504
544
  /* --------------------------------- Render --------------------------------- */ return /*#__PURE__*/ jsxs("button", {
545
+ ref: buttonRef,
505
546
  type: "button",
506
547
  "data-sileo-toast": true,
507
548
  "data-ready": ready,
@@ -515,6 +556,7 @@ const HEADER_EXIT_MS = 150;
515
556
  onMouseEnter: handleEnter,
516
557
  onMouseLeave: handleLeave,
517
558
  onTransitionEnd: handleTransitionEnd,
559
+ onPointerDown: handlePointerDown,
518
560
  children: [
519
561
  /*#__PURE__*/ jsx("div", {
520
562
  "data-sileo-canvas": true,
@@ -717,7 +759,7 @@ const createToast = (options)=>{
717
759
  store.update((p)=>p.map((t)=>t.id === id ? item : t));
718
760
  } else {
719
761
  store.update((p)=>[
720
- ...p,
762
+ ...p.filter((t)=>t.id !== id),
721
763
  item
722
764
  ]);
723
765
  }
@@ -752,7 +794,8 @@ const sileo = {
752
794
  promise: (promise, opts)=>{
753
795
  const { id } = createToast(_extends({}, opts.loading, {
754
796
  state: "loading",
755
- duration: null
797
+ duration: null,
798
+ position: opts.position
756
799
  }));
757
800
  const p = typeof promise === "function" ? promise() : promise;
758
801
  p.then((data)=>{
@@ -881,7 +924,8 @@ const sileo = {
881
924
  leave: (e)=>{
882
925
  setActiveId((prev)=>prev === latestRef.current ? prev : latestRef.current);
883
926
  handleMouseLeaveRef.current == null ? void 0 : handleMouseLeaveRef.current.call(handleMouseLeaveRef, e);
884
- }
927
+ },
928
+ dismiss: ()=>dismissToast(toastId)
885
929
  };
886
930
  handlersCache.current.set(toastId, cached);
887
931
  return cached;
@@ -956,7 +1000,8 @@ const sileo = {
956
1000
  refreshKey: item.instanceId,
957
1001
  canExpand: activeId === undefined || activeId === item.id,
958
1002
  onMouseEnter: h.enter,
959
- onMouseLeave: h.leave
1003
+ onMouseLeave: h.leave,
1004
+ onDismiss: h.dismiss
960
1005
  }, item.id);
961
1006
  })
962
1007
  }, pos);
package/dist/styles.css CHANGED
@@ -55,6 +55,7 @@
55
55
  position: relative;
56
56
  cursor: pointer;
57
57
  pointer-events: auto;
58
+ touch-action: none;
58
59
  border: 0;
59
60
  background: transparent;
60
61
  padding: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sileo",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "An opinionated, physics based toast notification library for react.",
5
5
  "license": "MIT",
6
6
  "repository": {