{"version":3,"file":"registry.mjs","names":[],"sources":["../../../src/Image/viewer/registry.ts"],"sourcesContent":["import { type RefObject, useCallback, useSyncExternalStore } from 'react';\n\nimport type { ImagePreviewOptions } from '../type';\nimport type { ZoomPolicy } from './geometry';\n\nexport interface ResolvedPreviewOptions extends Omit<\n  ImagePreviewOptions,\n  'autoZoomThreshold' | 'defaultZoom' | 'maxScale'\n> {\n  autoZoomThreshold: number;\n  defaultZoom: ZoomPolicy;\n  maxScale: number;\n}\n\nexport interface PreviewEntry {\n  element: HTMLImageElement;\n  options: ResolvedPreviewOptions;\n  previewSrc?: string;\n  src: string;\n}\n\nexport interface PreviewSession {\n  entries: PreviewEntry[];\n  index: number;\n  openerFocusElement: HTMLElement | null;\n  token: number;\n}\n\ninterface HostState extends PreviewSession {\n  closing: boolean;\n  onOpenChange?: (open: boolean) => void;\n}\n\nlet state: HostState | null = null;\nlet nextToken = 0;\n\nconst listeners = new Set<() => void>();\n\nconst emit = () => {\n  for (const listener of listeners) listener();\n};\n\nconst flushClosed = () => {\n  if (!state || state.closing) return;\n  state.closing = true;\n  state.onOpenChange?.(false);\n};\n\n// openerFocusElement must be captured by the caller at pointerdown, not read here:\n// clicking a non-focusable thumbnail already blurs to document.body before its\n// click handler (and therefore this call) runs.\nexport const openPreview = (\n  entry: PreviewEntry,\n  entries?: PreviewEntry[],\n  index = 0,\n  openerFocusElement: HTMLElement | null = null,\n): void => {\n  flushClosed();\n  nextToken += 1;\n  const resolvedEntries = entries && entries.length > 0 ? entries : [entry];\n  const resolvedIndex =\n    entries && entries.length > 0 ? Math.min(Math.max(index, 0), entries.length - 1) : 0;\n  state = {\n    closing: false,\n    entries: resolvedEntries,\n    index: resolvedIndex,\n    onOpenChange: entry.options.onOpenChange,\n    openerFocusElement,\n    token: nextToken,\n  };\n  emit();\n  entry.options.onOpenChange?.(true);\n};\n\nexport const beginClosePreview = (token: number): void => {\n  if (state?.token !== token) return;\n  flushClosed();\n};\n\nexport const endClosePreview = (token: number): void => {\n  if (state?.token !== token) return;\n  flushClosed();\n  state = null;\n  emit();\n};\n\nconst subscribe = (listener: () => void) => {\n  listeners.add(listener);\n  return () => {\n    listeners.delete(listener);\n  };\n};\n\nconst getServerSnapshot = (): PreviewSession | null => null;\n\nexport const usePreviewSession = (\n  elementRef: RefObject<HTMLImageElement | null>,\n): PreviewSession | null => {\n  const getSnapshot = useCallback(\n    () => (state && state.entries[state.index]?.element === elementRef.current ? state : null),\n    [elementRef],\n  );\n\n  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n};\n"],"mappings":";;AAiCA,IAAI,QAA0B;AAC9B,IAAI,YAAY;AAEhB,MAAM,4BAAY,IAAI,IAAgB;AAEtC,MAAM,aAAa;CACjB,KAAK,MAAM,YAAY,WAAW,SAAS;AAC7C;AAEA,MAAM,oBAAoB;CACxB,IAAI,CAAC,SAAS,MAAM,SAAS;CAC7B,MAAM,UAAU;CAChB,MAAM,eAAe,KAAK;AAC5B;AAKA,MAAa,eACX,OACA,SACA,QAAQ,GACR,qBAAyC,SAChC;CACT,YAAY;CACZ,aAAa;CAIb,QAAQ;EACN,SAAS;EACT,SALsB,WAAW,QAAQ,SAAS,IAAI,UAAU,CAAC,KAAK;EAMtE,OAJA,WAAW,QAAQ,SAAS,IAAI,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,QAAQ,SAAS,CAAC,IAAI;EAKnF,cAAc,MAAM,QAAQ;EAC5B;EACA,OAAO;CACT;CACA,KAAK;CACL,MAAM,QAAQ,eAAe,IAAI;AACnC;AAEA,MAAa,qBAAqB,UAAwB;CACxD,IAAI,OAAO,UAAU,OAAO;CAC5B,YAAY;AACd;AAEA,MAAa,mBAAmB,UAAwB;CACtD,IAAI,OAAO,UAAU,OAAO;CAC5B,YAAY;CACZ,QAAQ;CACR,KAAK;AACP;AAEA,MAAM,aAAa,aAAyB;CAC1C,UAAU,IAAI,QAAQ;CACtB,aAAa;EACX,UAAU,OAAO,QAAQ;CAC3B;AACF;AAEA,MAAM,0BAAiD;AAEvD,MAAa,qBACX,eAC0B;CAC1B,MAAM,cAAc,kBACX,SAAS,MAAM,QAAQ,MAAM,MAAM,EAAE,YAAY,WAAW,UAAU,QAAQ,MACrF,CAAC,UAAU,CACb;CAEA,OAAO,qBAAqB,WAAW,aAAa,iBAAiB;AACvE"}