UNPKG

motion-dom

Version:
1 lines • 10.9 kB
{"version":3,"file":"index.mjs","sources":["../../../src/scope/index.ts"],"sourcesContent":["import { removeItem } from \"motion-utils\"\nimport { releaseElement } from \"../animation/animate/element\"\nimport type { AnimationOptions } from \"../animation/types\"\nimport { ElementOrSelector, resolveElements } from \"../utils/resolve-elements\"\nimport { activeScope, ActiveScope, addToScope } from \"./active\"\nimport { toMediaQuery } from \"./media\"\n\n/**\n * A media query or shorthand (see `toMediaQuery`), or named conditions.\n * With named conditions, setup runs when any of them match, and their\n * state is available as `scope.media`.\n */\nexport type MediaConditions = string | Record<string, string>\n\nexport interface ScopeOptions {\n /**\n * Default transition for every `animate()` call made in this scope.\n */\n transition?: AnimationOptions\n\n /**\n * Only run setup while these conditions match. When they change, the\n * scope is cancelled and setup runs again.\n */\n matchMedia?: MediaConditions\n}\n\nexport type ScopeState = \"active\" | \"stopped\" | \"cancelled\"\n\nexport interface MotionScope {\n /**\n * `\"active\"` until `stop()` or `cancel()` is called. Lets async work\n * check whether its scope has gone before it starts animating.\n */\n state: ScopeState\n\n /**\n * Whether each named `matchMedia` condition matched when setup last ran.\n */\n media: Record<string, boolean>\n\n /**\n * Run `callback` in this scope, so animations, scroll and view\n * listeners and gestures it starts are collected.\n */\n run<R>(callback: () => R): R\n\n /**\n * Return a function that runs `callback` in this scope, for instance\n * as an event handler.\n */\n bind<F extends (...args: any[]) => any>(callback: F): F\n\n /**\n * Run `callback` outside of any scope, so what it starts is not\n * collected and survives this scope being stopped or cancelled.\n */\n ignore<R>(callback: () => R): R\n\n /**\n * Stop everything in this scope and remove its media listeners.\n * Styles are left as they are.\n */\n stop(): void\n\n /**\n * As `stop()`, and restore the inline styles of every element\n * animated in this scope to what they were before.\n */\n cancel(): void\n\n /**\n * Cancel, then run setup again if the media conditions match.\n */\n refresh(): void\n}\n\nexport type ScopeSetup = (scope: MotionScope) => void | VoidFunction\n\n/**\n * Create a scope that collects the animations, scroll and view\n * listeners, gestures and resize observers started inside it, so they\n * can be stopped or cancelled together.\n *\n * An optional root element or selector comes first. Selectors used\n * inside the scope resolve within it.\n *\n * ```js\n * const scope = scopeMotion(\n * \".list\",\n * ({ media }) => {\n * animate(\"li\", { x: media.desktop ? 100 : 0 })\n * },\n * { transition: { duration: 0.5 }, matchMedia: { desktop: \">=800\" } }\n * )\n * ```\n *\n * @public\n */\nexport function scopeMotion(\n setup?: ScopeSetup,\n options?: ScopeOptions\n): MotionScope\nexport function scopeMotion(\n root: ElementOrSelector,\n setup?: ScopeSetup,\n options?: ScopeOptions\n): MotionScope\nexport function scopeMotion(...args: [any?, any?, any?]): MotionScope {\n const root: ElementOrSelector =\n typeof args[0] === \"function\" ? undefined : args.shift()\n const [setup, options = {}] = args as [ScopeSetup?, ScopeOptions?]\n\n const { transition, matchMedia } = options\n const queries: [string, MediaQueryList][] = []\n const styles = new Map<Element, string | null>()\n let cleanups: VoidFunction[] = []\n let isPending = false\n\n const active: ActiveScope = {\n root: resolveElements(root)[0],\n transition,\n add(cleanup) {\n cleanups.push(cleanup)\n return () => removeItem(cleanups, cleanup)\n },\n bind: (callback) =>\n ((...args: any[]) => {\n const result = scope.run(() => callback(...args))\n return typeof result === \"function\"\n ? active.bind(result)\n : result\n }) as typeof callback,\n track(element) {\n styles.has(element) ||\n styles.set(element, element.getAttribute(\"style\"))\n },\n }\n\n const clear = (restore?: boolean) => {\n const current = cleanups\n cleanups = []\n for (let i = current.length - 1; i >= 0; i--) current[i]()\n\n restore &&\n styles.forEach((style, element) => {\n releaseElement(element)\n style === null\n ? element.removeAttribute(\"style\")\n : element.setAttribute(\"style\", style)\n })\n styles.clear()\n }\n\n const start = () => {\n scope.state = \"active\"\n let matches = !queries.length\n scope.media = {}\n\n for (const [name, query] of queries) {\n matches = (scope.media[name] = query.matches) || matches\n }\n\n if (matches && setup) {\n const cleanup = scope.run(() => setup(scope))\n typeof cleanup === \"function\" && active.add(cleanup)\n }\n }\n\n const onChange = () => {\n if (isPending) return\n isPending = true\n\n /**\n * Several conditions can change together, for instance two\n * breakpoints crossed by one resize. Refresh once.\n */\n queueMicrotask(() => {\n isPending = false\n queries.length && scope.refresh()\n })\n }\n\n const kill = (restore?: boolean) => {\n scope.state = restore ? \"cancelled\" : \"stopped\"\n for (const [, query] of queries) {\n query.removeEventListener(\"change\", onChange)\n }\n queries.length = 0\n clear(restore)\n }\n\n const runIn = <R>(target: ActiveScope | undefined, callback: () => R) => {\n const prev = activeScope.current\n activeScope.current = target\n try {\n return callback()\n } finally {\n activeScope.current = prev\n }\n }\n\n const scope: MotionScope = {\n state: \"active\",\n media: {},\n run: (callback) => runIn(active, callback),\n ignore: (callback) => runIn(undefined, callback),\n bind: active.bind,\n stop: () => kill(),\n cancel: () => kill(true),\n refresh() {\n clear(true)\n start()\n },\n }\n\n if (matchMedia && typeof window !== \"undefined\") {\n const conditions =\n typeof matchMedia === \"string\"\n ? { matches: matchMedia }\n : matchMedia\n\n for (const name in conditions) {\n const query = window.matchMedia(toMediaQuery(conditions[name]))\n query.addEventListener(\"change\", onChange)\n queries.push([name, query])\n }\n }\n\n /**\n * A scope created inside another is cancelled with it.\n */\n addToScope(scope.cancel)\n\n start()\n\n return scope\n}\n"],"names":[],"mappings":"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"}