mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 10:54:44 +00:00
feat(monorepo): migrate vue packages and apply oxlint refactors
This commit is contained in:
115
vue/toolkit/src/composables/browser/useTabLeader/index.ts
Normal file
115
vue/toolkit/src/composables/browser/useTabLeader/index.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { ref, readonly } from 'vue';
|
||||
import type { Ref, DeepReadonly, ComputedRef } from 'vue';
|
||||
import { useSupported } from '@/composables/browser/useSupported';
|
||||
import { tryOnScopeDispose } from '@/composables/lifecycle/tryOnScopeDispose';
|
||||
|
||||
export interface UseTabLeaderOptions {
|
||||
/**
|
||||
* Immediately attempt to acquire leadership on creation
|
||||
* @default true
|
||||
*/
|
||||
immediate?: boolean;
|
||||
}
|
||||
|
||||
export interface UseTabLeaderReturn {
|
||||
/**
|
||||
* Whether the current tab is the leader
|
||||
*/
|
||||
isLeader: DeepReadonly<Ref<boolean>>;
|
||||
/**
|
||||
* Whether the Web Locks API is supported
|
||||
*/
|
||||
isSupported: ComputedRef<boolean>;
|
||||
/**
|
||||
* Manually acquire leadership
|
||||
*/
|
||||
acquire: () => void;
|
||||
/**
|
||||
* Manually release leadership
|
||||
*/
|
||||
release: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name useTabLeader
|
||||
* @category Browser
|
||||
* @description Elects a single leader tab using the Web Locks API.
|
||||
* Only one tab at a time holds the lock for a given key.
|
||||
* When the leader tab closes or the scope is disposed, another tab automatically becomes the leader.
|
||||
*
|
||||
* @param {string} key A unique lock name identifying the leader group
|
||||
* @param {UseTabLeaderOptions} [options={}] Options
|
||||
* @returns {UseTabLeaderReturn} Leader state and controls
|
||||
*
|
||||
* @example
|
||||
* const { isLeader } = useTabLeader('payment-polling');
|
||||
*
|
||||
* watchEffect(() => {
|
||||
* if (isLeader.value) {
|
||||
* // Only this tab performs polling
|
||||
* startPolling();
|
||||
* } else {
|
||||
* stopPolling();
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @since 0.0.13
|
||||
*/
|
||||
export function useTabLeader(key: string, options: UseTabLeaderOptions = {}): UseTabLeaderReturn {
|
||||
const { immediate = true } = options;
|
||||
|
||||
const isLeader = ref(false);
|
||||
const isSupported = useSupported(() => navigator?.locks);
|
||||
|
||||
let releaseResolve: (() => void) | null = null;
|
||||
let abortController: AbortController | null = null;
|
||||
|
||||
function acquire() {
|
||||
if (!isSupported.value || abortController) return;
|
||||
|
||||
abortController = new AbortController();
|
||||
|
||||
navigator.locks.request(
|
||||
key,
|
||||
{ signal: abortController.signal },
|
||||
() => {
|
||||
isLeader.value = true;
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
releaseResolve = resolve;
|
||||
});
|
||||
},
|
||||
).catch((error: unknown) => {
|
||||
// AbortError is expected when release() is called before lock is acquired
|
||||
if (error instanceof DOMException && error.name === 'AbortError') return;
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
function release() {
|
||||
isLeader.value = false;
|
||||
|
||||
if (releaseResolve) {
|
||||
releaseResolve();
|
||||
releaseResolve = null;
|
||||
}
|
||||
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
abortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (immediate) {
|
||||
acquire();
|
||||
}
|
||||
|
||||
tryOnScopeDispose(release);
|
||||
|
||||
return {
|
||||
isLeader: readonly(isLeader),
|
||||
isSupported,
|
||||
acquire,
|
||||
release,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user