DuyTa's picture
Upload folder using huggingface_hub
bc20498 verified
import { usePopper } from '../../internal/actions/index.js';
import { addMeltEventListener, makeElement, createElHelpers, derivedVisible, effect, executeCallbacks, getPortalDestination, getTabbableNodes, isBrowser, isElement, isFocusVisible, isHTMLElement, isTouch, noop, overridable, sleep, styleToString, toWritableStores, portalAttr, } from '../../internal/helpers/index.js';
import { safeOnMount } from '../../internal/helpers/lifecycle.js';
import { withGet } from '../../internal/helpers/withGet.js';
import { writable } from 'svelte/store';
import { generateIds } from '../../internal/helpers/id.js';
import { omit } from '../../internal/helpers/object.js';
import { tick } from 'svelte';
const { name } = createElHelpers('hover-card');
const defaults = {
defaultOpen: false,
openDelay: 1000,
closeDelay: 100,
positioning: {
placement: 'bottom',
},
arrowSize: 8,
closeOnOutsideClick: true,
forceVisible: false,
portal: undefined,
closeOnEscape: true,
onOutsideClick: undefined,
};
export const linkPreviewIdParts = ['trigger', 'content'];
export function createLinkPreview(props = {}) {
const withDefaults = { ...defaults, ...props };
const openWritable = withDefaults.open ?? writable(withDefaults.defaultOpen);
const open = overridable(openWritable, withDefaults?.onOpenChange);
const hasSelection = withGet.writable(false);
const isPointerDownOnContent = withGet.writable(false);
const containSelection = writable(false);
const activeTrigger = writable(null);
// type OpenReason = 'pointer' | 'focus';
// const openReason = writable<null | OpenReason>(null);
const options = toWritableStores(omit(withDefaults, 'ids'));
const { openDelay, closeDelay, positioning, arrowSize, closeOnOutsideClick, forceVisible, portal, closeOnEscape, onOutsideClick, } = options;
const ids = toWritableStores({ ...generateIds(linkPreviewIdParts), ...withDefaults.ids });
let timeout = null;
let originalBodyUserSelect;
const handleOpen = withGet.derived(openDelay, ($openDelay) => {
return () => {
if (timeout) {
window.clearTimeout(timeout);
timeout = null;
}
timeout = window.setTimeout(() => {
open.set(true);
}, $openDelay);
};
});
const handleClose = withGet.derived([closeDelay, isPointerDownOnContent, hasSelection], ([$closeDelay, $isPointerDownOnContent, $hasSelection]) => {
return () => {
if (timeout) {
window.clearTimeout(timeout);
timeout = null;
}
if (!$isPointerDownOnContent && !$hasSelection) {
timeout = window.setTimeout(() => {
open.set(false);
}, $closeDelay);
}
};
});
const trigger = makeElement(name('trigger'), {
stores: [open, ids.trigger, ids.content],
returned: ([$open, $triggerId, $contentId]) => {
return {
role: 'button',
'aria-haspopup': 'dialog',
'aria-expanded': $open,
'data-state': $open ? 'open' : 'closed',
'aria-controls': $contentId,
id: $triggerId,
};
},
action: (node) => {
const unsub = executeCallbacks(addMeltEventListener(node, 'pointerenter', (e) => {
if (isTouch(e))
return;
handleOpen.get()();
}), addMeltEventListener(node, 'pointerleave', (e) => {
if (isTouch(e))
return;
handleClose.get()();
}), addMeltEventListener(node, 'focus', (e) => {
if (!isElement(e.currentTarget) || !isFocusVisible(e.currentTarget))
return;
handleOpen.get()();
}), addMeltEventListener(node, 'blur', () => handleClose.get()()));
return {
destroy: unsub,
};
},
});
const isVisible = derivedVisible({ open, forceVisible, activeTrigger });
const content = makeElement(name('content'), {
stores: [isVisible, portal, ids.content],
returned: ([$isVisible, $portal, $contentId]) => {
return {
hidden: $isVisible ? undefined : true,
tabindex: -1,
style: styleToString({
'pointer-events': $isVisible ? undefined : 'none',
opacity: $isVisible ? 1 : 0,
userSelect: 'text',
WebkitUserSelect: 'text',
}),
id: $contentId,
'data-state': $isVisible ? 'open' : 'closed',
'data-portal': portalAttr($portal),
};
},
action: (node) => {
let unsub = noop;
const unsubTimers = () => {
if (timeout) {
window.clearTimeout(timeout);
}
};
let unsubPopper = noop;
const unsubDerived = effect([isVisible, activeTrigger, positioning, closeOnOutsideClick, portal, closeOnEscape], ([$isVisible, $activeTrigger, $positioning, $closeOnOutsideClick, $portal, $closeOnEscape,]) => {
unsubPopper();
if (!$isVisible || !$activeTrigger)
return;
tick().then(() => {
const popper = usePopper(node, {
anchorElement: $activeTrigger,
open: open,
options: {
floating: $positioning,
modal: {
closeOnInteractOutside: $closeOnOutsideClick,
onClose: () => {
open.set(false);
$activeTrigger.focus();
},
shouldCloseOnInteractOutside: (e) => {
onOutsideClick.get()?.(e);
if (e.defaultPrevented)
return false;
if (isHTMLElement($activeTrigger) &&
$activeTrigger.contains(e.target))
return false;
return true;
},
open: $isVisible,
},
portal: getPortalDestination(node, $portal),
focusTrap: null,
escapeKeydown: $closeOnEscape ? undefined : null,
},
});
if (popper && popper.destroy) {
unsubPopper = popper.destroy;
}
});
});
unsub = executeCallbacks(addMeltEventListener(node, 'pointerdown', (e) => {
const currentTarget = e.currentTarget;
const target = e.target;
if (!isHTMLElement(currentTarget) || !isHTMLElement(target))
return;
if (currentTarget.contains(target)) {
containSelection.set(true);
}
hasSelection.set(false);
isPointerDownOnContent.set(true);
}), addMeltEventListener(node, 'pointerenter', (e) => {
if (isTouch(e))
return;
handleOpen.get()();
}), addMeltEventListener(node, 'pointerleave', (e) => {
if (isTouch(e))
return;
handleClose.get()();
}), addMeltEventListener(node, 'focusout', (e) => {
e.preventDefault();
}));
return {
destroy() {
unsub();
unsubPopper();
unsubTimers();
unsubDerived();
},
};
},
});
const arrow = makeElement(name('arrow'), {
stores: arrowSize,
returned: ($arrowSize) => ({
'data-arrow': true,
style: styleToString({
position: 'absolute',
width: `var(--arrow-size, ${$arrowSize}px)`,
height: `var(--arrow-size, ${$arrowSize}px)`,
}),
}),
});
effect([containSelection], ([$containSelection]) => {
if (!isBrowser || !$containSelection)
return;
const body = document.body;
const contentElement = document.getElementById(ids.content.get());
if (!contentElement)
return;
// prefix for safari
originalBodyUserSelect = body.style.userSelect || body.style.webkitUserSelect;
const originalContentUserSelect = contentElement.style.userSelect || contentElement.style.webkitUserSelect;
body.style.userSelect = 'none';
body.style.webkitUserSelect = 'none';
contentElement.style.userSelect = 'text';
contentElement.style.webkitUserSelect = 'text';
return () => {
body.style.userSelect = originalBodyUserSelect;
body.style.webkitUserSelect = originalBodyUserSelect;
contentElement.style.userSelect = originalContentUserSelect;
contentElement.style.webkitUserSelect = originalContentUserSelect;
};
});
safeOnMount(() => {
const triggerEl = document.getElementById(ids.trigger.get());
if (!triggerEl)
return;
activeTrigger.set(triggerEl);
});
effect([open], ([$open]) => {
if (!isBrowser || !$open) {
hasSelection.set(false);
return;
}
const handlePointerUp = () => {
containSelection.set(false);
isPointerDownOnContent.set(false);
sleep(1).then(() => {
const isSelection = document.getSelection()?.toString() !== '';
if (isSelection) {
hasSelection.set(true);
}
});
};
document.addEventListener('pointerup', handlePointerUp);
const contentElement = document.getElementById(ids.content.get());
if (!contentElement)
return;
const tabbables = getTabbableNodes(contentElement);
tabbables.forEach((tabbable) => tabbable.setAttribute('tabindex', '-1'));
return () => {
document.removeEventListener('pointerup', handlePointerUp);
hasSelection.set(false);
isPointerDownOnContent.set(false);
};
});
return {
ids,
elements: {
trigger,
content,
arrow,
},
states: {
open,
},
options,
};
}