import { SectionsWidget } from './SectionsWidget'; import { render, createPortal } from 'react-dom'; import { Fragment, memo, useEffect, useState } from 'react'; import { Provider as CacheContextProvider, type Cache } from './cache'; const EVENT_MOUNT = 'wpj:sections-autocomplete:mount'; export type RuntimeProps = { runtimeTarget: HTMLElement; onInit?: () => void; }; export type WidgetProps = { // Buttons onAddSection?: (sectionId: number) => void; onAddWithSubsections?: (topsectionId: number) => void; // Row click onSelect?: (idSection: number) => void; shouldHideAfterSelect?: boolean; }; export type MountEvent = CustomEvent<{ mountTarget: HTMLElement; } & WidgetProps>; let CacheInitialized = false; export function RuntimeComponent({ options }: { options: RuntimeProps }) { const { runtimeTarget, onInit } = options; const [portals, setPortals] = useState([]); const [cache, setCache] = useState({ topLevelSections: [], _query: {}, _fetchTopLevelSections: fetchTopLevelSections, }); function fetchTopLevelSections() { if (CacheInitialized) { return; } const url = new URL('/admin/autocomplete/sections', `${window.location.protocol}//${window.location.host}`); fetch(url.href) .then(r => r.json()) .then(sections => { setCache(p => ({ ...p, topLevelSections: sections })); CacheInitialized = true; }) .catch(alert); } useEffect(() => { function mountEventHandler(ev: Event | MountEvent) { if (ev.type !== EVENT_MOUNT) { return; } const mountEvent = ev as MountEvent; setPortals(previous => [...previous, mountEvent.detail]); } runtimeTarget.addEventListener(EVENT_MOUNT, mountEventHandler); if (onInit) { onInit(); } return () => { runtimeTarget.removeEventListener(EVENT_MOUNT, mountEventHandler); }; }, []); return ( ); } const PortalMounter = memo(({ portals }: { portals: MountEvent['detail'][] }) => { return ( <> {portals.map((props, index) => ( {createPortal(, props.mountTarget, index.toString())} ))} ); }); export function prepareRuntime(runtimeTarget?: HTMLElement) { if (!runtimeTarget) { const elem = document.createElement('meta'); document.body.insertAdjacentElement('beforeend', elem); runtimeTarget = elem; } runtimeTarget.dataset.sectionsAutocompleteRuntime = ''; return new Promise((resolve) => { const options = { runtimeTarget, onInit: () => { resolve(runtimeTarget!) } } as RuntimeProps; render( , options.runtimeTarget, ); }); } export function mountAutocomplete(target: HTMLElement, options: WidgetProps = { shouldHideAfterSelect: true }) { const event: MountEvent = new CustomEvent(EVENT_MOUNT, { detail: { mountTarget: target, ...options, }, bubbles: true, }); const runtimeElement = document.querySelector('[data-sections-autocomplete-runtime]'); if (!runtimeElement) { throw new Error('SectionsAutocomplete runtime not found'); } runtimeElement.dispatchEvent(event); }