feat(vue): expand @robonen/vue composable collection

Composables, tests, category barrels, and README for @robonen/vue.
This commit is contained in:
2026-06-08 15:51:16 +07:00
parent 9a912f7a77
commit 59e995d0b5
369 changed files with 36554 additions and 188 deletions
@@ -0,0 +1,99 @@
import { watch } from 'vue';
import { noop } from '@robonen/stdlib';
import type { VoidFunction } from '@robonen/stdlib';
import type { ConfigurableDocument, ConfigurableFlush, ConfigurableWindow } from '@/types';
import { defaultWindow } from '@/types';
import type { MaybeComputedElementRef } from '@/composables/component/unrefElement';
import { unrefElement } from '@/composables/component/unrefElement';
import { useMutationObserver } from '@/composables/elements/useMutationObserver';
export interface OnElementRemovalOptions extends ConfigurableWindow, ConfigurableDocument, ConfigurableFlush {}
export type OnElementRemovalCallback = (mutationRecords: MutationRecord[]) => void;
export type OnElementRemovalReturn = VoidFunction;
/**
* @name onElementRemoval
* @category Elements
* @description Fire a callback when the target element — or any ancestor containing it — is
* removed from the DOM. Backed by a single `childList`/`subtree` `MutationObserver` on the
* element's owning document, so it also catches removal of a parent further up the tree.
*
* @param {MaybeComputedElementRef} target Element (or ref/getter) to watch for removal
* @param {OnElementRemovalCallback} callback Invoked with the mutation records that removed the element
* @param {OnElementRemovalOptions} [options={}] `window`, `document`, and watcher `flush` timing
* @returns {OnElementRemovalReturn} Stop handle that tears down the watcher and observer
*
* @example
* const el = useTemplateRef<HTMLElement>('el');
* onElementRemoval(el, () => console.log('gone'));
*
* @example
* const stop = onElementRemoval(el, (records) => report(records), { flush: 'post' });
*
* @since 0.0.15
*/
export function onElementRemoval(
target: MaybeComputedElementRef,
callback: OnElementRemovalCallback,
options: OnElementRemovalOptions = {},
): OnElementRemovalReturn {
const {
window = defaultWindow,
document = window?.document,
flush = 'sync',
} = options;
// SSR
if (!window || !document)
return noop;
let stopObserver: VoidFunction | undefined;
const disconnect = () => {
stopObserver?.();
stopObserver = undefined;
};
const stopWatch = watch(
() => unrefElement(target),
(el) => {
disconnect();
if (!el)
return;
// Observe the element's owning document so removal of any ancestor is caught,
// and so the correct root is used for elements inside iframes / shadow trees.
const root = el.ownerDocument ?? document;
const { stop } = useMutationObserver(
root.documentElement ?? (root as unknown as Element),
(mutationRecords) => {
const removed = mutationRecords.some(record =>
Array.prototype.some.call(record.removedNodes, (node: Node) => node === el || node.contains(el)),
);
if (removed)
callback(mutationRecords);
},
{
window,
childList: true,
subtree: true,
},
);
stopObserver = stop;
},
{ immediate: true, flush },
);
const stop: VoidFunction = () => {
stopWatch();
disconnect();
};
return stop;
}