import { createApp, h, ref, nextTick, onMounted, onBeforeUnmount, version } from 'vue'; const NOTE_TYPE = 'docs.note'; const NOTES_PATH = 'interaction.notes'; export const BOARD = 'interaction-board'; export function registerVueBoard(runtime, { copy, signal, onReady, onReceived, onError }) { let app; let view; let mounting; const aborted = () => signal?.reason || new DOMException('Interaction board disposed', 'AbortError'); const ensure = () => { if (signal?.aborted) throw aborted(); }; const dispose = () => { mounting?.reject(aborted()); app?.unmount(); app = null; view = null; mounting = null; }; runtime.define(BOARD, { async mount(container, { wu: scoped }) { ensure(); mounting = {}; const ready = new Promise((resolve, reject) => Object.assign(mounting, { resolve, reject })); app = createApp({ setup() { const notes = ref(scoped.store.get(NOTES_PATH) || []); const element = ref(null); let live = true; let releaseDrop; const stopNotes = scoped.store.on(NOTES_PATH, value => { notes.value = [...(value || [])]; }); const abort = () => mounting?.reject(aborted()); signal?.addEventListener('abort', abort, { once: true }); onMounted(async () => { try { view = element.value; await view.updateComplete; ensure(); if (!live) throw aborted(); releaseDrop = await scoped.drop.accept(view.dropElement, { type: NOTE_TYPE, dropEffect: 'copy', validate(payload) { if (!payload || typeof payload.id !== 'string' || typeof payload.text !== 'string' || !payload.text.trim()) { throw new Error(copy.validationEmpty); } if (payload.text.trim().length > 280) throw new Error(copy.validationLong); return { id: payload.id, text: payload.text.trim() }; }, async onDrop(note, info) { ensure(); if (!live) throw aborted(); const next = [...(scoped.store.get(NOTES_PATH) || []), note]; scoped.store.set(NOTES_PATH, next); await nextTick(); await view.updateComplete; if (!live || signal?.aborted) throw aborted(); await onReceived?.(next, info); return { accepted: true, id: note.id }; }, onReject(info) { // deliver() rejects to the React sender, which reports its // error there. Native drops have no awaiting caller. if (live && !signal?.aborted && info.event) onError?.(info.error); }, }); if (!live || signal?.aborted) { releaseDrop(); throw aborted(); } onReady?.(view); mounting?.resolve(view); } catch (error) { mounting?.reject(error); } }); onBeforeUnmount(() => { live = false; releaseDrop?.(); stopNotes(); signal?.removeEventListener('abort', abort); mounting?.reject(aborted()); }); return () => h('wu-interaction-board', { ref: element, '.copy': copy, '.notes': notes.value, 'data-renderer': 'vue', 'data-renderer-version': version, }); }, }); app.mount(container); await ready; }, unmount: dispose, }); return { get element() { return view; }, dispose }; }