Tracking de l'application VApp (IHM du jeu)

This commit is contained in:
2025-05-11 18:04:12 +02:00
commit 89e9db9b62
17763 changed files with 3718499 additions and 0 deletions

View File

@ -0,0 +1,64 @@
.v-overlay-container {
contain: layout;
left: 0;
pointer-events: none;
position: absolute;
top: 0;
display: contents;
}
.v-overlay-scroll-blocked {
padding-inline-end: var(--v-scrollbar-offset);
}
.v-overlay-scroll-blocked:not(html) {
overflow-y: hidden !important;
}
html.v-overlay-scroll-blocked {
position: fixed;
top: var(--v-body-scroll-y);
left: var(--v-body-scroll-x);
width: 100%;
height: 100%;
}
.v-overlay {
border-radius: inherit;
display: flex;
left: 0;
pointer-events: none;
position: fixed;
top: 0;
bottom: 0;
right: 0;
}
.v-overlay__content {
outline: none;
position: absolute;
pointer-events: auto;
contain: layout;
}
.v-overlay__scrim {
pointer-events: auto;
background: rgb(var(--v-theme-on-surface));
border-radius: inherit;
bottom: 0;
left: 0;
opacity: var(--v-overlay-opacity, 0.32);
position: fixed;
right: 0;
top: 0;
}
.v-overlay--absolute {
position: absolute;
}
.v-overlay--contained .v-overlay__scrim {
position: absolute;
}
.v-overlay--scroll-blocked {
padding-inline-end: var(--v-scrollbar-offset);
}

View File

@ -0,0 +1,289 @@
import { withDirectives as _withDirectives, resolveDirective as _resolveDirective, vShow as _vShow, Fragment as _Fragment, createVNode as _createVNode, mergeProps as _mergeProps } from "vue";
// Styles
import "./VOverlay.css";
// Composables
import { makeLocationStrategyProps, useLocationStrategies } from "./locationStrategies.mjs";
import { makeScrollStrategyProps, useScrollStrategies } from "./scrollStrategies.mjs";
import { makeActivatorProps, useActivator } from "./useActivator.mjs";
import { useBackgroundColor } from "../../composables/color.mjs";
import { makeComponentProps } from "../../composables/component.mjs";
import { makeDimensionProps, useDimension } from "../../composables/dimensions.mjs";
import { useHydration } from "../../composables/hydration.mjs";
import { makeLazyProps, useLazy } from "../../composables/lazy.mjs";
import { useRtl } from "../../composables/locale.mjs";
import { useProxiedModel } from "../../composables/proxiedModel.mjs";
import { useBackButton, useRouter } from "../../composables/router.mjs";
import { useScopeId } from "../../composables/scopeId.mjs";
import { useStack } from "../../composables/stack.mjs";
import { useTeleport } from "../../composables/teleport.mjs";
import { makeThemeProps, provideTheme } from "../../composables/theme.mjs";
import { useToggleScope } from "../../composables/toggleScope.mjs";
import { makeTransitionProps, MaybeTransition } from "../../composables/transition.mjs"; // Directives
import { ClickOutside } from "../../directives/click-outside/index.mjs"; // Utilities
import { computed, mergeProps, onBeforeUnmount, ref, Teleport, toRef, Transition, watch } from 'vue';
import { animate, convertToUnit, genericComponent, getScrollParent, IN_BROWSER, propsFactory, standardEasing, useRender } from "../../util/index.mjs"; // Types
function Scrim(props) {
const {
modelValue,
color,
...rest
} = props;
return _createVNode(Transition, {
"name": "fade-transition",
"appear": true
}, {
default: () => [props.modelValue && _createVNode("div", _mergeProps({
"class": ['v-overlay__scrim', props.color.backgroundColorClasses.value],
"style": props.color.backgroundColorStyles.value
}, rest), null)]
});
}
export const makeVOverlayProps = propsFactory({
absolute: Boolean,
attach: [Boolean, String, Object],
closeOnBack: {
type: Boolean,
default: true
},
contained: Boolean,
contentClass: null,
contentProps: null,
disabled: Boolean,
opacity: [Number, String],
noClickAnimation: Boolean,
modelValue: Boolean,
persistent: Boolean,
scrim: {
type: [Boolean, String],
default: true
},
zIndex: {
type: [Number, String],
default: 2000
},
...makeActivatorProps(),
...makeComponentProps(),
...makeDimensionProps(),
...makeLazyProps(),
...makeLocationStrategyProps(),
...makeScrollStrategyProps(),
...makeThemeProps(),
...makeTransitionProps()
}, 'VOverlay');
export const VOverlay = genericComponent()({
name: 'VOverlay',
directives: {
ClickOutside
},
inheritAttrs: false,
props: {
_disableGlobalStack: Boolean,
...makeVOverlayProps()
},
emits: {
'click:outside': e => true,
'update:modelValue': value => true,
afterLeave: () => true
},
setup(props, _ref) {
let {
slots,
attrs,
emit
} = _ref;
const model = useProxiedModel(props, 'modelValue');
const isActive = computed({
get: () => model.value,
set: v => {
if (!(v && props.disabled)) model.value = v;
}
});
const {
teleportTarget
} = useTeleport(computed(() => props.attach || props.contained));
const {
themeClasses
} = provideTheme(props);
const {
rtlClasses,
isRtl
} = useRtl();
const {
hasContent,
onAfterLeave: _onAfterLeave
} = useLazy(props, isActive);
const scrimColor = useBackgroundColor(computed(() => {
return typeof props.scrim === 'string' ? props.scrim : null;
}));
const {
globalTop,
localTop,
stackStyles
} = useStack(isActive, toRef(props, 'zIndex'), props._disableGlobalStack);
const {
activatorEl,
activatorRef,
target,
targetEl,
targetRef,
activatorEvents,
contentEvents,
scrimEvents
} = useActivator(props, {
isActive,
isTop: localTop
});
const {
dimensionStyles
} = useDimension(props);
const isMounted = useHydration();
const {
scopeId
} = useScopeId();
watch(() => props.disabled, v => {
if (v) isActive.value = false;
});
const root = ref();
const contentEl = ref();
const {
contentStyles,
updateLocation
} = useLocationStrategies(props, {
isRtl,
contentEl,
target,
isActive
});
useScrollStrategies(props, {
root,
contentEl,
targetEl,
isActive,
updateLocation
});
function onClickOutside(e) {
emit('click:outside', e);
if (!props.persistent) isActive.value = false;else animateClick();
}
function closeConditional() {
return isActive.value && globalTop.value;
}
IN_BROWSER && watch(isActive, val => {
if (val) {
window.addEventListener('keydown', onKeydown);
} else {
window.removeEventListener('keydown', onKeydown);
}
}, {
immediate: true
});
onBeforeUnmount(() => {
if (!IN_BROWSER) return;
window.removeEventListener('keydown', onKeydown);
});
function onKeydown(e) {
if (e.key === 'Escape' && globalTop.value) {
if (!props.persistent) {
isActive.value = false;
if (contentEl.value?.contains(document.activeElement)) {
activatorEl.value?.focus();
}
} else animateClick();
}
}
const router = useRouter();
useToggleScope(() => props.closeOnBack, () => {
useBackButton(router, next => {
if (globalTop.value && isActive.value) {
next(false);
if (!props.persistent) isActive.value = false;else animateClick();
} else {
next();
}
});
});
const top = ref();
watch(() => isActive.value && (props.absolute || props.contained) && teleportTarget.value == null, val => {
if (val) {
const scrollParent = getScrollParent(root.value);
if (scrollParent && scrollParent !== document.scrollingElement) {
top.value = scrollParent.scrollTop;
}
}
});
// Add a quick "bounce" animation to the content
function animateClick() {
if (props.noClickAnimation) return;
contentEl.value && animate(contentEl.value, [{
transformOrigin: 'center'
}, {
transform: 'scale(1.03)'
}, {
transformOrigin: 'center'
}], {
duration: 150,
easing: standardEasing
});
}
function onAfterLeave() {
_onAfterLeave();
emit('afterLeave');
}
useRender(() => _createVNode(_Fragment, null, [slots.activator?.({
isActive: isActive.value,
props: mergeProps({
ref: activatorRef,
targetRef
}, activatorEvents.value, props.activatorProps)
}), isMounted.value && hasContent.value && _createVNode(Teleport, {
"disabled": !teleportTarget.value,
"to": teleportTarget.value
}, {
default: () => [_createVNode("div", _mergeProps({
"class": ['v-overlay', {
'v-overlay--absolute': props.absolute || props.contained,
'v-overlay--active': isActive.value,
'v-overlay--contained': props.contained
}, themeClasses.value, rtlClasses.value, props.class],
"style": [stackStyles.value, {
'--v-overlay-opacity': props.opacity,
top: convertToUnit(top.value)
}, props.style],
"ref": root
}, scopeId, attrs), [_createVNode(Scrim, _mergeProps({
"color": scrimColor,
"modelValue": isActive.value && !!props.scrim
}, scrimEvents.value), null), _createVNode(MaybeTransition, {
"appear": true,
"persisted": true,
"transition": props.transition,
"target": target.value,
"onAfterLeave": onAfterLeave
}, {
default: () => [_withDirectives(_createVNode("div", _mergeProps({
"ref": contentEl,
"class": ['v-overlay__content', props.contentClass],
"style": [dimensionStyles.value, contentStyles.value]
}, contentEvents.value, props.contentProps), [slots.default?.({
isActive
})]), [[_vShow, isActive.value], [_resolveDirective("click-outside"), {
handler: onClickOutside,
closeConditional,
include: () => [activatorEl.value]
}]])]
})])]
})]));
return {
activatorEl,
target,
animateClick,
contentEl,
globalTop,
localTop,
updateLocation
};
}
});
//# sourceMappingURL=VOverlay.mjs.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,62 @@
@use 'sass:selector'
@use './variables' as *
// Block
.v-overlay-container
contain: layout
left: 0
pointer-events: none
position: absolute
top: 0
display: contents
.v-overlay-scroll-blocked
padding-inline-end: var(--v-scrollbar-offset)
&:not(html)
overflow-y: hidden !important
@at-root #{selector.append(html, &)}
position: fixed
top: var(--v-body-scroll-y)
left: var(--v-body-scroll-x)
width: 100%
height: 100%
.v-overlay
border-radius: inherit
display: flex
left: 0
pointer-events: none
position: fixed
top: 0
bottom: 0
right: 0
// Element
.v-overlay__content
outline: none
position: absolute
pointer-events: auto
contain: layout
.v-overlay__scrim
pointer-events: auto
background: $overlay-scrim-background
border-radius: inherit
bottom: 0
left: 0
opacity: $overlay-opacity
position: fixed
right: 0
top: 0
// Modifier
.v-overlay--absolute
position: absolute
.v-overlay--contained .v-overlay__scrim
position: absolute
.v-overlay--scroll-blocked
padding-inline-end: var(--v-scrollbar-offset)

View File

@ -0,0 +1,3 @@
// Defaults
$overlay-opacity: var(--v-overlay-opacity, 0.32) !default;
$overlay-scrim-background: rgb(var(--v-theme-on-surface)) !default;

View File

@ -0,0 +1,717 @@
import * as vue from 'vue';
import { ComponentPropsOptions, ExtractPropTypes, Ref, EffectScope, PropType } from 'vue';
declare const block: readonly ["top", "bottom"];
declare const inline: readonly ["start", "end", "left", "right"];
type Tblock = typeof block[number];
type Tinline = typeof inline[number];
type Anchor = Tblock | Tinline | 'center' | 'center center' | `${Tblock} ${Tinline | 'center'}` | `${Tinline} ${Tblock | 'center'}`;
declare class Box {
x: number;
y: number;
width: number;
height: number;
constructor({ x, y, width, height }: {
x: number;
y: number;
width: number;
height: number;
});
get top(): number;
get bottom(): number;
get left(): number;
get right(): number;
}
interface FilterPropsOptions<PropsOptions extends Readonly<ComponentPropsOptions>, Props = ExtractPropTypes<PropsOptions>> {
filterProps<T extends Partial<Props>, U extends Exclude<keyof Props, Exclude<keyof Props, keyof T>>>(props: T): Partial<Pick<T, U>>;
}
interface LocationStrategyData {
contentEl: Ref<HTMLElement | undefined>;
target: Ref<HTMLElement | [x: number, y: number] | undefined>;
isActive: Ref<boolean>;
isRtl: Ref<boolean>;
}
type LocationStrategyFn = (data: LocationStrategyData, props: StrategyProps$1, contentStyles: Ref<Record<string, string>>) => undefined | {
updateLocation: (e: Event) => void;
};
declare const locationStrategies: {
static: typeof staticLocationStrategy;
connected: typeof connectedLocationStrategy;
};
interface StrategyProps$1 {
locationStrategy: keyof typeof locationStrategies | LocationStrategyFn;
location: Anchor;
origin: Anchor | 'auto' | 'overlap';
offset?: number | string | number[];
maxHeight?: number | string;
maxWidth?: number | string;
minHeight?: number | string;
minWidth?: number | string;
}
declare function staticLocationStrategy(): void;
declare function connectedLocationStrategy(data: LocationStrategyData, props: StrategyProps$1, contentStyles: Ref<Record<string, string>>): {
updateLocation: () => {
available: {
x: number;
y: number;
};
contentBox: Box;
} | undefined;
};
interface ScrollStrategyData {
root: Ref<HTMLElement | undefined>;
contentEl: Ref<HTMLElement | undefined>;
targetEl: Ref<HTMLElement | undefined>;
isActive: Ref<boolean>;
updateLocation: Ref<((e: Event) => void) | undefined>;
}
type ScrollStrategyFn = (data: ScrollStrategyData, props: StrategyProps, scope: EffectScope) => void;
declare const scrollStrategies: {
none: null;
close: typeof closeScrollStrategy;
block: typeof blockScrollStrategy;
reposition: typeof repositionScrollStrategy;
};
interface StrategyProps {
scrollStrategy: keyof typeof scrollStrategies | ScrollStrategyFn;
contained: boolean | undefined;
}
declare function closeScrollStrategy(data: ScrollStrategyData): void;
declare function blockScrollStrategy(data: ScrollStrategyData, props: StrategyProps): void;
declare function repositionScrollStrategy(data: ScrollStrategyData, props: StrategyProps, scope: EffectScope): void;
declare const VOverlay: {
new (...args: any[]): vue.CreateComponentPublicInstance<{
absolute: boolean;
location: Anchor;
origin: "auto" | Anchor | "overlap";
transition: string | boolean | (vue.TransitionProps & {
component?: vue.Component | undefined;
});
zIndex: string | number;
style: vue.StyleValue;
eager: boolean;
disabled: boolean;
modelValue: boolean;
locationStrategy: "connected" | "static" | ((data: LocationStrategyData, props: StrategyProps$1, contentStyles: Ref<Record<string, string>>) => {
updateLocation: (e: Event) => void;
} | undefined);
scrollStrategy: "none" | "block" | "close" | ((data: ScrollStrategyData, props: StrategyProps, scope: vue.EffectScope) => void) | "reposition";
activatorProps: Record<string, any>;
openOnHover: boolean;
closeOnContentClick: boolean;
closeOnBack: boolean;
contained: boolean;
noClickAnimation: boolean;
persistent: boolean;
scrim: string | boolean;
_disableGlobalStack: boolean;
} & {
offset?: string | number | number[] | undefined;
height?: string | number | undefined;
width?: string | number | undefined;
maxHeight?: string | number | undefined;
maxWidth?: string | number | undefined;
minHeight?: string | number | undefined;
minWidth?: string | number | undefined;
opacity?: string | number | undefined;
target?: Element | "cursor" | "parent" | (string & {}) | vue.ComponentPublicInstance | [x: number, y: number] | undefined;
class?: any;
theme?: string | undefined;
contentClass?: any;
activator?: Element | "parent" | (string & {}) | vue.ComponentPublicInstance | undefined;
closeDelay?: string | number | undefined;
openDelay?: string | number | undefined;
openOnClick?: boolean | undefined;
openOnFocus?: boolean | undefined;
contentProps?: any;
attach?: string | boolean | Element | undefined;
} & {
$children?: vue.VNodeChild | {
default?: ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
activator?: ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild);
'v-slots'?: {
default?: false | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
activator?: false | ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} | undefined;
} & {
"v-slot:default"?: false | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
"v-slot:activator"?: false | ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} & {
onAfterLeave?: (() => any) | undefined;
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
"onClick:outside"?: ((e: MouseEvent) => any) | undefined;
}, {
activatorEl: Ref<HTMLElement | undefined>;
target: vue.ComputedRef<HTMLElement | [x: number, y: number] | undefined>;
animateClick: () => void;
contentEl: Ref<HTMLElement | undefined>;
globalTop: Readonly<Ref<boolean>>;
localTop: vue.ComputedRef<boolean>;
updateLocation: Ref<((e: Event) => void) | undefined>;
}, unknown, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
'click:outside': (e: MouseEvent) => true;
'update:modelValue': (value: boolean) => true;
afterLeave: () => true;
}, vue.VNodeProps & vue.AllowedComponentProps & vue.ComponentCustomProps & {
absolute: boolean;
location: Anchor;
origin: "auto" | Anchor | "overlap";
transition: string | boolean | (vue.TransitionProps & {
component?: vue.Component | undefined;
});
zIndex: string | number;
style: vue.StyleValue;
eager: boolean;
disabled: boolean;
modelValue: boolean;
locationStrategy: "connected" | "static" | ((data: LocationStrategyData, props: StrategyProps$1, contentStyles: Ref<Record<string, string>>) => {
updateLocation: (e: Event) => void;
} | undefined);
scrollStrategy: "none" | "block" | "close" | ((data: ScrollStrategyData, props: StrategyProps, scope: vue.EffectScope) => void) | "reposition";
activatorProps: Record<string, any>;
openOnHover: boolean;
closeOnContentClick: boolean;
closeOnBack: boolean;
contained: boolean;
noClickAnimation: boolean;
persistent: boolean;
scrim: string | boolean;
_disableGlobalStack: boolean;
} & {
offset?: string | number | number[] | undefined;
height?: string | number | undefined;
width?: string | number | undefined;
maxHeight?: string | number | undefined;
maxWidth?: string | number | undefined;
minHeight?: string | number | undefined;
minWidth?: string | number | undefined;
opacity?: string | number | undefined;
target?: Element | "cursor" | "parent" | (string & {}) | vue.ComponentPublicInstance | [x: number, y: number] | undefined;
class?: any;
theme?: string | undefined;
contentClass?: any;
activator?: Element | "parent" | (string & {}) | vue.ComponentPublicInstance | undefined;
closeDelay?: string | number | undefined;
openDelay?: string | number | undefined;
openOnClick?: boolean | undefined;
openOnFocus?: boolean | undefined;
contentProps?: any;
attach?: string | boolean | Element | undefined;
} & {
$children?: vue.VNodeChild | {
default?: ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
activator?: ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild);
'v-slots'?: {
default?: false | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
activator?: false | ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} | undefined;
} & {
"v-slot:default"?: false | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
"v-slot:activator"?: false | ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} & {
onAfterLeave?: (() => any) | undefined;
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
"onClick:outside"?: ((e: MouseEvent) => any) | undefined;
}, {
absolute: boolean;
location: Anchor;
origin: "auto" | Anchor | "overlap";
transition: string | boolean | (vue.TransitionProps & {
component?: vue.Component | undefined;
});
zIndex: string | number;
style: vue.StyleValue;
eager: boolean;
disabled: boolean;
modelValue: boolean;
locationStrategy: "connected" | "static" | ((data: LocationStrategyData, props: StrategyProps$1, contentStyles: Ref<Record<string, string>>) => {
updateLocation: (e: Event) => void;
} | undefined);
scrollStrategy: "none" | "block" | "close" | ((data: ScrollStrategyData, props: StrategyProps, scope: vue.EffectScope) => void) | "reposition";
activatorProps: Record<string, any>;
openOnClick: boolean;
openOnHover: boolean;
openOnFocus: boolean;
closeOnContentClick: boolean;
closeOnBack: boolean;
contained: boolean;
noClickAnimation: boolean;
persistent: boolean;
scrim: string | boolean;
_disableGlobalStack: boolean;
}, true, {}, vue.SlotsType<Partial<{
default: (arg: {
isActive: Ref<boolean>;
}) => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>[];
activator: (arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>[];
}>>, {
P: {};
B: {};
D: {};
C: {};
M: {};
Defaults: {};
}, {
absolute: boolean;
location: Anchor;
origin: "auto" | Anchor | "overlap";
transition: string | boolean | (vue.TransitionProps & {
component?: vue.Component | undefined;
});
zIndex: string | number;
style: vue.StyleValue;
eager: boolean;
disabled: boolean;
modelValue: boolean;
locationStrategy: "connected" | "static" | ((data: LocationStrategyData, props: StrategyProps$1, contentStyles: Ref<Record<string, string>>) => {
updateLocation: (e: Event) => void;
} | undefined);
scrollStrategy: "none" | "block" | "close" | ((data: ScrollStrategyData, props: StrategyProps, scope: vue.EffectScope) => void) | "reposition";
activatorProps: Record<string, any>;
openOnHover: boolean;
closeOnContentClick: boolean;
closeOnBack: boolean;
contained: boolean;
noClickAnimation: boolean;
persistent: boolean;
scrim: string | boolean;
_disableGlobalStack: boolean;
} & {
offset?: string | number | number[] | undefined;
height?: string | number | undefined;
width?: string | number | undefined;
maxHeight?: string | number | undefined;
maxWidth?: string | number | undefined;
minHeight?: string | number | undefined;
minWidth?: string | number | undefined;
opacity?: string | number | undefined;
target?: Element | "cursor" | "parent" | (string & {}) | vue.ComponentPublicInstance | [x: number, y: number] | undefined;
class?: any;
theme?: string | undefined;
contentClass?: any;
activator?: Element | "parent" | (string & {}) | vue.ComponentPublicInstance | undefined;
closeDelay?: string | number | undefined;
openDelay?: string | number | undefined;
openOnClick?: boolean | undefined;
openOnFocus?: boolean | undefined;
contentProps?: any;
attach?: string | boolean | Element | undefined;
} & {
$children?: vue.VNodeChild | {
default?: ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
activator?: ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild);
'v-slots'?: {
default?: false | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
activator?: false | ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} | undefined;
} & {
"v-slot:default"?: false | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
"v-slot:activator"?: false | ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} & {
onAfterLeave?: (() => any) | undefined;
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
"onClick:outside"?: ((e: MouseEvent) => any) | undefined;
}, {
activatorEl: Ref<HTMLElement | undefined>;
target: vue.ComputedRef<HTMLElement | [x: number, y: number] | undefined>;
animateClick: () => void;
contentEl: Ref<HTMLElement | undefined>;
globalTop: Readonly<Ref<boolean>>;
localTop: vue.ComputedRef<boolean>;
updateLocation: Ref<((e: Event) => void) | undefined>;
}, {}, {}, {}, {
absolute: boolean;
location: Anchor;
origin: "auto" | Anchor | "overlap";
transition: string | boolean | (vue.TransitionProps & {
component?: vue.Component | undefined;
});
zIndex: string | number;
style: vue.StyleValue;
eager: boolean;
disabled: boolean;
modelValue: boolean;
locationStrategy: "connected" | "static" | ((data: LocationStrategyData, props: StrategyProps$1, contentStyles: Ref<Record<string, string>>) => {
updateLocation: (e: Event) => void;
} | undefined);
scrollStrategy: "none" | "block" | "close" | ((data: ScrollStrategyData, props: StrategyProps, scope: vue.EffectScope) => void) | "reposition";
activatorProps: Record<string, any>;
openOnClick: boolean;
openOnHover: boolean;
openOnFocus: boolean;
closeOnContentClick: boolean;
closeOnBack: boolean;
contained: boolean;
noClickAnimation: boolean;
persistent: boolean;
scrim: string | boolean;
_disableGlobalStack: boolean;
}>;
__isFragment?: undefined;
__isTeleport?: undefined;
__isSuspense?: undefined;
} & vue.ComponentOptionsBase<{
absolute: boolean;
location: Anchor;
origin: "auto" | Anchor | "overlap";
transition: string | boolean | (vue.TransitionProps & {
component?: vue.Component | undefined;
});
zIndex: string | number;
style: vue.StyleValue;
eager: boolean;
disabled: boolean;
modelValue: boolean;
locationStrategy: "connected" | "static" | ((data: LocationStrategyData, props: StrategyProps$1, contentStyles: Ref<Record<string, string>>) => {
updateLocation: (e: Event) => void;
} | undefined);
scrollStrategy: "none" | "block" | "close" | ((data: ScrollStrategyData, props: StrategyProps, scope: vue.EffectScope) => void) | "reposition";
activatorProps: Record<string, any>;
openOnHover: boolean;
closeOnContentClick: boolean;
closeOnBack: boolean;
contained: boolean;
noClickAnimation: boolean;
persistent: boolean;
scrim: string | boolean;
_disableGlobalStack: boolean;
} & {
offset?: string | number | number[] | undefined;
height?: string | number | undefined;
width?: string | number | undefined;
maxHeight?: string | number | undefined;
maxWidth?: string | number | undefined;
minHeight?: string | number | undefined;
minWidth?: string | number | undefined;
opacity?: string | number | undefined;
target?: Element | "cursor" | "parent" | (string & {}) | vue.ComponentPublicInstance | [x: number, y: number] | undefined;
class?: any;
theme?: string | undefined;
contentClass?: any;
activator?: Element | "parent" | (string & {}) | vue.ComponentPublicInstance | undefined;
closeDelay?: string | number | undefined;
openDelay?: string | number | undefined;
openOnClick?: boolean | undefined;
openOnFocus?: boolean | undefined;
contentProps?: any;
attach?: string | boolean | Element | undefined;
} & {
$children?: vue.VNodeChild | {
default?: ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
activator?: ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild);
'v-slots'?: {
default?: false | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
activator?: false | ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} | undefined;
} & {
"v-slot:default"?: false | ((arg: {
isActive: Ref<boolean>;
}) => vue.VNodeChild) | undefined;
"v-slot:activator"?: false | ((arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNodeChild) | undefined;
} & {
onAfterLeave?: (() => any) | undefined;
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
"onClick:outside"?: ((e: MouseEvent) => any) | undefined;
}, {
activatorEl: Ref<HTMLElement | undefined>;
target: vue.ComputedRef<HTMLElement | [x: number, y: number] | undefined>;
animateClick: () => void;
contentEl: Ref<HTMLElement | undefined>;
globalTop: Readonly<Ref<boolean>>;
localTop: vue.ComputedRef<boolean>;
updateLocation: Ref<((e: Event) => void) | undefined>;
}, unknown, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
'click:outside': (e: MouseEvent) => true;
'update:modelValue': (value: boolean) => true;
afterLeave: () => true;
}, string, {
absolute: boolean;
location: Anchor;
origin: "auto" | Anchor | "overlap";
transition: string | boolean | (vue.TransitionProps & {
component?: vue.Component | undefined;
});
zIndex: string | number;
style: vue.StyleValue;
eager: boolean;
disabled: boolean;
modelValue: boolean;
locationStrategy: "connected" | "static" | ((data: LocationStrategyData, props: StrategyProps$1, contentStyles: Ref<Record<string, string>>) => {
updateLocation: (e: Event) => void;
} | undefined);
scrollStrategy: "none" | "block" | "close" | ((data: ScrollStrategyData, props: StrategyProps, scope: vue.EffectScope) => void) | "reposition";
activatorProps: Record<string, any>;
openOnClick: boolean;
openOnHover: boolean;
openOnFocus: boolean;
closeOnContentClick: boolean;
closeOnBack: boolean;
contained: boolean;
noClickAnimation: boolean;
persistent: boolean;
scrim: string | boolean;
_disableGlobalStack: boolean;
}, {}, string, vue.SlotsType<Partial<{
default: (arg: {
isActive: Ref<boolean>;
}) => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>[];
activator: (arg: {
isActive: boolean;
props: Record<string, any>;
}) => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}>[];
}>>> & vue.VNodeProps & vue.AllowedComponentProps & vue.ComponentCustomProps & FilterPropsOptions<{
transition: {
type: PropType<string | boolean | (vue.TransitionProps & {
component?: vue.Component | undefined;
})>;
default: string;
validator: (val: unknown) => boolean;
};
theme: StringConstructor;
scrollStrategy: {
type: PropType<"none" | "block" | "close" | ((data: ScrollStrategyData, props: StrategyProps, scope: vue.EffectScope) => void) | "reposition">;
default: string;
validator: (val: any) => boolean;
};
locationStrategy: {
type: PropType<"connected" | "static" | ((data: LocationStrategyData, props: StrategyProps$1, contentStyles: Ref<Record<string, string>>) => {
updateLocation: (e: Event) => void;
} | undefined)>;
default: string;
validator: (val: any) => boolean;
};
location: {
type: PropType<Anchor>;
default: string;
};
origin: {
type: PropType<"auto" | Anchor | "overlap">;
default: string;
};
offset: PropType<string | number | number[] | undefined>;
eager: BooleanConstructor;
height: (StringConstructor | NumberConstructor)[];
maxHeight: (StringConstructor | NumberConstructor)[];
maxWidth: (StringConstructor | NumberConstructor)[];
minHeight: (StringConstructor | NumberConstructor)[];
minWidth: (StringConstructor | NumberConstructor)[];
width: (StringConstructor | NumberConstructor)[];
class: PropType<any>;
style: {
type: PropType<vue.StyleValue>;
default: null;
};
closeDelay: (StringConstructor | NumberConstructor)[];
openDelay: (StringConstructor | NumberConstructor)[];
target: PropType<Element | "cursor" | "parent" | (string & {}) | vue.ComponentPublicInstance | [x: number, y: number] | undefined>;
activator: PropType<Element | "parent" | (string & {}) | vue.ComponentPublicInstance | undefined>;
activatorProps: {
type: PropType<Record<string, any>>;
default: () => {};
};
openOnClick: {
type: BooleanConstructor;
default: undefined;
};
openOnHover: BooleanConstructor;
openOnFocus: {
type: BooleanConstructor;
default: undefined;
};
closeOnContentClick: BooleanConstructor;
absolute: BooleanConstructor;
attach: PropType<string | boolean | Element>;
closeOnBack: {
type: BooleanConstructor;
default: boolean;
};
contained: BooleanConstructor;
contentClass: null;
contentProps: null;
disabled: BooleanConstructor;
opacity: (StringConstructor | NumberConstructor)[];
noClickAnimation: BooleanConstructor;
modelValue: BooleanConstructor;
persistent: BooleanConstructor;
scrim: {
type: (StringConstructor | BooleanConstructor)[];
default: boolean;
};
zIndex: {
type: (StringConstructor | NumberConstructor)[];
default: number;
};
_disableGlobalStack: BooleanConstructor;
}, vue.ExtractPropTypes<{
transition: {
type: PropType<string | boolean | (vue.TransitionProps & {
component?: vue.Component | undefined;
})>;
default: string;
validator: (val: unknown) => boolean;
};
theme: StringConstructor;
scrollStrategy: {
type: PropType<"none" | "block" | "close" | ((data: ScrollStrategyData, props: StrategyProps, scope: vue.EffectScope) => void) | "reposition">;
default: string;
validator: (val: any) => boolean;
};
locationStrategy: {
type: PropType<"connected" | "static" | ((data: LocationStrategyData, props: StrategyProps$1, contentStyles: Ref<Record<string, string>>) => {
updateLocation: (e: Event) => void;
} | undefined)>;
default: string;
validator: (val: any) => boolean;
};
location: {
type: PropType<Anchor>;
default: string;
};
origin: {
type: PropType<"auto" | Anchor | "overlap">;
default: string;
};
offset: PropType<string | number | number[] | undefined>;
eager: BooleanConstructor;
height: (StringConstructor | NumberConstructor)[];
maxHeight: (StringConstructor | NumberConstructor)[];
maxWidth: (StringConstructor | NumberConstructor)[];
minHeight: (StringConstructor | NumberConstructor)[];
minWidth: (StringConstructor | NumberConstructor)[];
width: (StringConstructor | NumberConstructor)[];
class: PropType<any>;
style: {
type: PropType<vue.StyleValue>;
default: null;
};
closeDelay: (StringConstructor | NumberConstructor)[];
openDelay: (StringConstructor | NumberConstructor)[];
target: PropType<Element | "cursor" | "parent" | (string & {}) | vue.ComponentPublicInstance | [x: number, y: number] | undefined>;
activator: PropType<Element | "parent" | (string & {}) | vue.ComponentPublicInstance | undefined>;
activatorProps: {
type: PropType<Record<string, any>>;
default: () => {};
};
openOnClick: {
type: BooleanConstructor;
default: undefined;
};
openOnHover: BooleanConstructor;
openOnFocus: {
type: BooleanConstructor;
default: undefined;
};
closeOnContentClick: BooleanConstructor;
absolute: BooleanConstructor;
attach: PropType<string | boolean | Element>;
closeOnBack: {
type: BooleanConstructor;
default: boolean;
};
contained: BooleanConstructor;
contentClass: null;
contentProps: null;
disabled: BooleanConstructor;
opacity: (StringConstructor | NumberConstructor)[];
noClickAnimation: BooleanConstructor;
modelValue: BooleanConstructor;
persistent: BooleanConstructor;
scrim: {
type: (StringConstructor | BooleanConstructor)[];
default: boolean;
};
zIndex: {
type: (StringConstructor | NumberConstructor)[];
default: number;
};
_disableGlobalStack: BooleanConstructor;
}>>;
type VOverlay = InstanceType<typeof VOverlay>;
export { VOverlay };

View File

@ -0,0 +1,2 @@
export { VOverlay } from "./VOverlay.mjs";
//# sourceMappingURL=index.mjs.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.mjs","names":["VOverlay"],"sources":["../../../src/components/VOverlay/index.ts"],"sourcesContent":["export { VOverlay } from './VOverlay'\n"],"mappings":"SAASA,QAAQ"}

View File

@ -0,0 +1,386 @@
// Composables
import { useToggleScope } from "../../composables/toggleScope.mjs"; // Utilities
import { computed, nextTick, onScopeDispose, ref, watch } from 'vue';
import { anchorToPoint, getOffset } from "./util/point.mjs";
import { clamp, consoleError, convertToUnit, destructComputed, flipAlign, flipCorner, flipSide, getAxis, getScrollParents, IN_BROWSER, isFixedPosition, nullifyTransforms, parseAnchor, propsFactory } from "../../util/index.mjs";
import { Box, getOverflow, getTargetBox } from "../../util/box.mjs"; // Types
const locationStrategies = {
static: staticLocationStrategy,
// specific viewport position, usually centered
connected: connectedLocationStrategy // connected to a certain element
};
export const makeLocationStrategyProps = propsFactory({
locationStrategy: {
type: [String, Function],
default: 'static',
validator: val => typeof val === 'function' || val in locationStrategies
},
location: {
type: String,
default: 'bottom'
},
origin: {
type: String,
default: 'auto'
},
offset: [Number, String, Array]
}, 'VOverlay-location-strategies');
export function useLocationStrategies(props, data) {
const contentStyles = ref({});
const updateLocation = ref();
if (IN_BROWSER) {
useToggleScope(() => !!(data.isActive.value && props.locationStrategy), reset => {
watch(() => props.locationStrategy, reset);
onScopeDispose(() => {
window.removeEventListener('resize', onResize);
updateLocation.value = undefined;
});
window.addEventListener('resize', onResize, {
passive: true
});
if (typeof props.locationStrategy === 'function') {
updateLocation.value = props.locationStrategy(data, props, contentStyles)?.updateLocation;
} else {
updateLocation.value = locationStrategies[props.locationStrategy](data, props, contentStyles)?.updateLocation;
}
});
}
function onResize(e) {
updateLocation.value?.(e);
}
return {
contentStyles,
updateLocation
};
}
function staticLocationStrategy() {
// TODO
}
/** Get size of element ignoring max-width/max-height */
function getIntrinsicSize(el, isRtl) {
// const scrollables = new Map<Element, [number, number]>()
// el.querySelectorAll('*').forEach(el => {
// const x = el.scrollLeft
// const y = el.scrollTop
// if (x || y) {
// scrollables.set(el, [x, y])
// }
// })
// const initialMaxWidth = el.style.maxWidth
// const initialMaxHeight = el.style.maxHeight
// el.style.removeProperty('max-width')
// el.style.removeProperty('max-height')
if (isRtl) {
el.style.removeProperty('left');
} else {
el.style.removeProperty('right');
}
/* eslint-disable-next-line sonarjs/prefer-immediate-return */
const contentBox = nullifyTransforms(el);
if (isRtl) {
contentBox.x += parseFloat(el.style.right || 0);
} else {
contentBox.x -= parseFloat(el.style.left || 0);
}
contentBox.y -= parseFloat(el.style.top || 0);
// el.style.maxWidth = initialMaxWidth
// el.style.maxHeight = initialMaxHeight
// scrollables.forEach((position, el) => {
// el.scrollTo(...position)
// })
return contentBox;
}
function connectedLocationStrategy(data, props, contentStyles) {
const activatorFixed = Array.isArray(data.target.value) || isFixedPosition(data.target.value);
if (activatorFixed) {
Object.assign(contentStyles.value, {
position: 'fixed',
top: 0,
[data.isRtl.value ? 'right' : 'left']: 0
});
}
const {
preferredAnchor,
preferredOrigin
} = destructComputed(() => {
const parsedAnchor = parseAnchor(props.location, data.isRtl.value);
const parsedOrigin = props.origin === 'overlap' ? parsedAnchor : props.origin === 'auto' ? flipSide(parsedAnchor) : parseAnchor(props.origin, data.isRtl.value);
// Some combinations of props may produce an invalid origin
if (parsedAnchor.side === parsedOrigin.side && parsedAnchor.align === flipAlign(parsedOrigin).align) {
return {
preferredAnchor: flipCorner(parsedAnchor),
preferredOrigin: flipCorner(parsedOrigin)
};
} else {
return {
preferredAnchor: parsedAnchor,
preferredOrigin: parsedOrigin
};
}
});
const [minWidth, minHeight, maxWidth, maxHeight] = ['minWidth', 'minHeight', 'maxWidth', 'maxHeight'].map(key => {
return computed(() => {
const val = parseFloat(props[key]);
return isNaN(val) ? Infinity : val;
});
});
const offset = computed(() => {
if (Array.isArray(props.offset)) {
return props.offset;
}
if (typeof props.offset === 'string') {
const offset = props.offset.split(' ').map(parseFloat);
if (offset.length < 2) offset.push(0);
return offset;
}
return typeof props.offset === 'number' ? [props.offset, 0] : [0, 0];
});
let observe = false;
const observer = new ResizeObserver(() => {
if (observe) updateLocation();
});
watch([data.target, data.contentEl], (_ref, _ref2) => {
let [newTarget, newContentEl] = _ref;
let [oldTarget, oldContentEl] = _ref2;
if (oldTarget && !Array.isArray(oldTarget)) observer.unobserve(oldTarget);
if (newTarget && !Array.isArray(newTarget)) observer.observe(newTarget);
if (oldContentEl) observer.unobserve(oldContentEl);
if (newContentEl) observer.observe(newContentEl);
}, {
immediate: true
});
onScopeDispose(() => {
observer.disconnect();
});
// eslint-disable-next-line max-statements
function updateLocation() {
observe = false;
requestAnimationFrame(() => observe = true);
if (!data.target.value || !data.contentEl.value) return;
const targetBox = getTargetBox(data.target.value);
const contentBox = getIntrinsicSize(data.contentEl.value, data.isRtl.value);
const scrollParents = getScrollParents(data.contentEl.value);
const viewportMargin = 12;
if (!scrollParents.length) {
scrollParents.push(document.documentElement);
if (!(data.contentEl.value.style.top && data.contentEl.value.style.left)) {
contentBox.x -= parseFloat(document.documentElement.style.getPropertyValue('--v-body-scroll-x') || 0);
contentBox.y -= parseFloat(document.documentElement.style.getPropertyValue('--v-body-scroll-y') || 0);
}
}
const viewport = scrollParents.reduce((box, el) => {
const rect = el.getBoundingClientRect();
const scrollBox = new Box({
x: el === document.documentElement ? 0 : rect.x,
y: el === document.documentElement ? 0 : rect.y,
width: el.clientWidth,
height: el.clientHeight
});
if (box) {
return new Box({
x: Math.max(box.left, scrollBox.left),
y: Math.max(box.top, scrollBox.top),
width: Math.min(box.right, scrollBox.right) - Math.max(box.left, scrollBox.left),
height: Math.min(box.bottom, scrollBox.bottom) - Math.max(box.top, scrollBox.top)
});
}
return scrollBox;
}, undefined);
viewport.x += viewportMargin;
viewport.y += viewportMargin;
viewport.width -= viewportMargin * 2;
viewport.height -= viewportMargin * 2;
let placement = {
anchor: preferredAnchor.value,
origin: preferredOrigin.value
};
function checkOverflow(_placement) {
const box = new Box(contentBox);
const targetPoint = anchorToPoint(_placement.anchor, targetBox);
const contentPoint = anchorToPoint(_placement.origin, box);
let {
x,
y
} = getOffset(targetPoint, contentPoint);
switch (_placement.anchor.side) {
case 'top':
y -= offset.value[0];
break;
case 'bottom':
y += offset.value[0];
break;
case 'left':
x -= offset.value[0];
break;
case 'right':
x += offset.value[0];
break;
}
switch (_placement.anchor.align) {
case 'top':
y -= offset.value[1];
break;
case 'bottom':
y += offset.value[1];
break;
case 'left':
x -= offset.value[1];
break;
case 'right':
x += offset.value[1];
break;
}
box.x += x;
box.y += y;
box.width = Math.min(box.width, maxWidth.value);
box.height = Math.min(box.height, maxHeight.value);
const overflows = getOverflow(box, viewport);
return {
overflows,
x,
y
};
}
let x = 0;
let y = 0;
const available = {
x: 0,
y: 0
};
const flipped = {
x: false,
y: false
};
let resets = -1;
while (true) {
if (resets++ > 10) {
consoleError('Infinite loop detected in connectedLocationStrategy');
break;
}
const {
x: _x,
y: _y,
overflows
} = checkOverflow(placement);
x += _x;
y += _y;
contentBox.x += _x;
contentBox.y += _y;
// flip
{
const axis = getAxis(placement.anchor);
const hasOverflowX = overflows.x.before || overflows.x.after;
const hasOverflowY = overflows.y.before || overflows.y.after;
let reset = false;
['x', 'y'].forEach(key => {
if (key === 'x' && hasOverflowX && !flipped.x || key === 'y' && hasOverflowY && !flipped.y) {
const newPlacement = {
anchor: {
...placement.anchor
},
origin: {
...placement.origin
}
};
const flip = key === 'x' ? axis === 'y' ? flipAlign : flipSide : axis === 'y' ? flipSide : flipAlign;
newPlacement.anchor = flip(newPlacement.anchor);
newPlacement.origin = flip(newPlacement.origin);
const {
overflows: newOverflows
} = checkOverflow(newPlacement);
if (newOverflows[key].before <= overflows[key].before && newOverflows[key].after <= overflows[key].after || newOverflows[key].before + newOverflows[key].after < (overflows[key].before + overflows[key].after) / 2) {
placement = newPlacement;
reset = flipped[key] = true;
}
}
});
if (reset) continue;
}
// shift
if (overflows.x.before) {
x += overflows.x.before;
contentBox.x += overflows.x.before;
}
if (overflows.x.after) {
x -= overflows.x.after;
contentBox.x -= overflows.x.after;
}
if (overflows.y.before) {
y += overflows.y.before;
contentBox.y += overflows.y.before;
}
if (overflows.y.after) {
y -= overflows.y.after;
contentBox.y -= overflows.y.after;
}
// size
{
const overflows = getOverflow(contentBox, viewport);
available.x = viewport.width - overflows.x.before - overflows.x.after;
available.y = viewport.height - overflows.y.before - overflows.y.after;
x += overflows.x.before;
contentBox.x += overflows.x.before;
y += overflows.y.before;
contentBox.y += overflows.y.before;
}
break;
}
const axis = getAxis(placement.anchor);
Object.assign(contentStyles.value, {
'--v-overlay-anchor-origin': `${placement.anchor.side} ${placement.anchor.align}`,
transformOrigin: `${placement.origin.side} ${placement.origin.align}`,
// transform: `translate(${pixelRound(x)}px, ${pixelRound(y)}px)`,
top: convertToUnit(pixelRound(y)),
left: data.isRtl.value ? undefined : convertToUnit(pixelRound(x)),
right: data.isRtl.value ? convertToUnit(pixelRound(-x)) : undefined,
minWidth: convertToUnit(axis === 'y' ? Math.min(minWidth.value, targetBox.width) : minWidth.value),
maxWidth: convertToUnit(pixelCeil(clamp(available.x, minWidth.value === Infinity ? 0 : minWidth.value, maxWidth.value))),
maxHeight: convertToUnit(pixelCeil(clamp(available.y, minHeight.value === Infinity ? 0 : minHeight.value, maxHeight.value)))
});
return {
available,
contentBox
};
}
watch(() => [preferredAnchor.value, preferredOrigin.value, props.offset, props.minWidth, props.minHeight, props.maxWidth, props.maxHeight], () => updateLocation());
nextTick(() => {
const result = updateLocation();
// TODO: overflowing content should only require a single updateLocation call
// Icky hack to make sure the content is positioned consistently
if (!result) return;
const {
available,
contentBox
} = result;
if (contentBox.height > available.y) {
requestAnimationFrame(() => {
updateLocation();
requestAnimationFrame(() => {
updateLocation();
});
});
}
});
return {
updateLocation
};
}
function pixelRound(val) {
return Math.round(val * devicePixelRatio) / devicePixelRatio;
}
function pixelCeil(val) {
return Math.ceil(val * devicePixelRatio) / devicePixelRatio;
}
//# sourceMappingURL=locationStrategies.mjs.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,27 @@
let clean = true;
const frames = [];
/**
* Schedule a task to run in an animation frame on its own
* This is useful for heavy tasks that may cause jank if all ran together
*/
export function requestNewFrame(cb) {
if (!clean || frames.length) {
frames.push(cb);
run();
} else {
clean = false;
cb();
run();
}
}
let raf = -1;
function run() {
cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
const frame = frames.shift();
if (frame) frame();
if (frames.length) run();else clean = true;
});
}
//# sourceMappingURL=requestNewFrame.mjs.map

View File

@ -0,0 +1 @@
{"version":3,"file":"requestNewFrame.mjs","names":["clean","frames","requestNewFrame","cb","length","push","run","raf","cancelAnimationFrame","requestAnimationFrame","frame","shift"],"sources":["../../../src/components/VOverlay/requestNewFrame.ts"],"sourcesContent":["let clean = true\nconst frames = [] as any[]\n\n/**\n * Schedule a task to run in an animation frame on its own\n * This is useful for heavy tasks that may cause jank if all ran together\n */\nexport function requestNewFrame (cb: () => void) {\n if (!clean || frames.length) {\n frames.push(cb)\n run()\n } else {\n clean = false\n cb()\n run()\n }\n}\n\nlet raf = -1\nfunction run () {\n cancelAnimationFrame(raf)\n raf = requestAnimationFrame(() => {\n const frame = frames.shift()\n if (frame) frame()\n\n if (frames.length) run()\n else clean = true\n })\n}\n"],"mappings":"AAAA,IAAIA,KAAK,GAAG,IAAI;AAChB,MAAMC,MAAM,GAAG,EAAW;;AAE1B;AACA;AACA;AACA;AACA,OAAO,SAASC,eAAeA,CAAEC,EAAc,EAAE;EAC/C,IAAI,CAACH,KAAK,IAAIC,MAAM,CAACG,MAAM,EAAE;IAC3BH,MAAM,CAACI,IAAI,CAACF,EAAE,CAAC;IACfG,GAAG,CAAC,CAAC;EACP,CAAC,MAAM;IACLN,KAAK,GAAG,KAAK;IACbG,EAAE,CAAC,CAAC;IACJG,GAAG,CAAC,CAAC;EACP;AACF;AAEA,IAAIC,GAAG,GAAG,CAAC,CAAC;AACZ,SAASD,GAAGA,CAAA,EAAI;EACdE,oBAAoB,CAACD,GAAG,CAAC;EACzBA,GAAG,GAAGE,qBAAqB,CAAC,MAAM;IAChC,MAAMC,KAAK,GAAGT,MAAM,CAACU,KAAK,CAAC,CAAC;IAC5B,IAAID,KAAK,EAAEA,KAAK,CAAC,CAAC;IAElB,IAAIT,MAAM,CAACG,MAAM,EAAEE,GAAG,CAAC,CAAC,MACnBN,KAAK,GAAG,IAAI;EACnB,CAAC,CAAC;AACJ"}

View File

@ -0,0 +1,131 @@
// Utilities
import { effectScope, nextTick, onScopeDispose, watchEffect } from 'vue';
import { requestNewFrame } from "./requestNewFrame.mjs";
import { convertToUnit, getScrollParents, hasScrollbar, IN_BROWSER, propsFactory } from "../../util/index.mjs"; // Types
const scrollStrategies = {
none: null,
close: closeScrollStrategy,
block: blockScrollStrategy,
reposition: repositionScrollStrategy
};
export const makeScrollStrategyProps = propsFactory({
scrollStrategy: {
type: [String, Function],
default: 'block',
validator: val => typeof val === 'function' || val in scrollStrategies
}
}, 'VOverlay-scroll-strategies');
export function useScrollStrategies(props, data) {
if (!IN_BROWSER) return;
let scope;
watchEffect(async () => {
scope?.stop();
if (!(data.isActive.value && props.scrollStrategy)) return;
scope = effectScope();
await nextTick();
scope.active && scope.run(() => {
if (typeof props.scrollStrategy === 'function') {
props.scrollStrategy(data, props, scope);
} else {
scrollStrategies[props.scrollStrategy]?.(data, props, scope);
}
});
});
onScopeDispose(() => {
scope?.stop();
});
}
function closeScrollStrategy(data) {
function onScroll(e) {
data.isActive.value = false;
}
bindScroll(data.targetEl.value ?? data.contentEl.value, onScroll);
}
function blockScrollStrategy(data, props) {
const offsetParent = data.root.value?.offsetParent;
const scrollElements = [...new Set([...getScrollParents(data.targetEl.value, props.contained ? offsetParent : undefined), ...getScrollParents(data.contentEl.value, props.contained ? offsetParent : undefined)])].filter(el => !el.classList.contains('v-overlay-scroll-blocked'));
const scrollbarWidth = window.innerWidth - document.documentElement.offsetWidth;
const scrollableParent = (el => hasScrollbar(el) && el)(offsetParent || document.documentElement);
if (scrollableParent) {
data.root.value.classList.add('v-overlay--scroll-blocked');
}
scrollElements.forEach((el, i) => {
el.style.setProperty('--v-body-scroll-x', convertToUnit(-el.scrollLeft));
el.style.setProperty('--v-body-scroll-y', convertToUnit(-el.scrollTop));
if (el !== document.documentElement) {
el.style.setProperty('--v-scrollbar-offset', convertToUnit(scrollbarWidth));
}
el.classList.add('v-overlay-scroll-blocked');
});
onScopeDispose(() => {
scrollElements.forEach((el, i) => {
const x = parseFloat(el.style.getPropertyValue('--v-body-scroll-x'));
const y = parseFloat(el.style.getPropertyValue('--v-body-scroll-y'));
const scrollBehavior = el.style.scrollBehavior;
el.style.scrollBehavior = 'auto';
el.style.removeProperty('--v-body-scroll-x');
el.style.removeProperty('--v-body-scroll-y');
el.style.removeProperty('--v-scrollbar-offset');
el.classList.remove('v-overlay-scroll-blocked');
el.scrollLeft = -x;
el.scrollTop = -y;
el.style.scrollBehavior = scrollBehavior;
});
if (scrollableParent) {
data.root.value.classList.remove('v-overlay--scroll-blocked');
}
});
}
function repositionScrollStrategy(data, props, scope) {
let slow = false;
let raf = -1;
let ric = -1;
function update(e) {
requestNewFrame(() => {
const start = performance.now();
data.updateLocation.value?.(e);
const time = performance.now() - start;
slow = time / (1000 / 60) > 2;
});
}
ric = (typeof requestIdleCallback === 'undefined' ? cb => cb() : requestIdleCallback)(() => {
scope.run(() => {
bindScroll(data.targetEl.value ?? data.contentEl.value, e => {
if (slow) {
// If the position calculation is slow,
// defer updates until scrolling is finished.
// Browsers usually fire one scroll event per frame so
// we just wait until we've got two frames without an event
cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
raf = requestAnimationFrame(() => {
update(e);
});
});
} else {
update(e);
}
});
});
});
onScopeDispose(() => {
typeof cancelIdleCallback !== 'undefined' && cancelIdleCallback(ric);
cancelAnimationFrame(raf);
});
}
/** @private */
function bindScroll(el, onScroll) {
const scrollElements = [document, ...getScrollParents(el)];
scrollElements.forEach(el => {
el.addEventListener('scroll', onScroll, {
passive: true
});
});
onScopeDispose(() => {
scrollElements.forEach(el => {
el.removeEventListener('scroll', onScroll);
});
});
}
//# sourceMappingURL=scrollStrategies.mjs.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,269 @@
// Components
import { VMenuSymbol } from "../VMenu/shared.mjs"; // Composables
import { makeDelayProps, useDelay } from "../../composables/delay.mjs"; // Utilities
import { computed, effectScope, inject, mergeProps, nextTick, onScopeDispose, ref, watch, watchEffect } from 'vue';
import { bindProps, getCurrentInstance, IN_BROWSER, matchesSelector, propsFactory, refElement, unbindProps } from "../../util/index.mjs"; // Types
export const makeActivatorProps = propsFactory({
target: [String, Object],
activator: [String, Object],
activatorProps: {
type: Object,
default: () => ({})
},
openOnClick: {
type: Boolean,
default: undefined
},
openOnHover: Boolean,
openOnFocus: {
type: Boolean,
default: undefined
},
closeOnContentClick: Boolean,
...makeDelayProps()
}, 'VOverlay-activator');
export function useActivator(props, _ref) {
let {
isActive,
isTop
} = _ref;
const vm = getCurrentInstance('useActivator');
const activatorEl = ref();
let isHovered = false;
let isFocused = false;
let firstEnter = true;
const openOnFocus = computed(() => props.openOnFocus || props.openOnFocus == null && props.openOnHover);
const openOnClick = computed(() => props.openOnClick || props.openOnClick == null && !props.openOnHover && !openOnFocus.value);
const {
runOpenDelay,
runCloseDelay
} = useDelay(props, value => {
if (value === (props.openOnHover && isHovered || openOnFocus.value && isFocused) && !(props.openOnHover && isActive.value && !isTop.value)) {
if (isActive.value !== value) {
firstEnter = true;
}
isActive.value = value;
}
});
const cursorTarget = ref();
const availableEvents = {
onClick: e => {
e.stopPropagation();
activatorEl.value = e.currentTarget || e.target;
if (!isActive.value) {
cursorTarget.value = [e.clientX, e.clientY];
}
isActive.value = !isActive.value;
},
onMouseenter: e => {
if (e.sourceCapabilities?.firesTouchEvents) return;
isHovered = true;
activatorEl.value = e.currentTarget || e.target;
runOpenDelay();
},
onMouseleave: e => {
isHovered = false;
runCloseDelay();
},
onFocus: e => {
if (matchesSelector(e.target, ':focus-visible') === false) return;
isFocused = true;
e.stopPropagation();
activatorEl.value = e.currentTarget || e.target;
runOpenDelay();
},
onBlur: e => {
isFocused = false;
e.stopPropagation();
runCloseDelay();
}
};
const activatorEvents = computed(() => {
const events = {};
if (openOnClick.value) {
events.onClick = availableEvents.onClick;
}
if (props.openOnHover) {
events.onMouseenter = availableEvents.onMouseenter;
events.onMouseleave = availableEvents.onMouseleave;
}
if (openOnFocus.value) {
events.onFocus = availableEvents.onFocus;
events.onBlur = availableEvents.onBlur;
}
return events;
});
const contentEvents = computed(() => {
const events = {};
if (props.openOnHover) {
events.onMouseenter = () => {
isHovered = true;
runOpenDelay();
};
events.onMouseleave = () => {
isHovered = false;
runCloseDelay();
};
}
if (openOnFocus.value) {
events.onFocusin = () => {
isFocused = true;
runOpenDelay();
};
events.onFocusout = () => {
isFocused = false;
runCloseDelay();
};
}
if (props.closeOnContentClick) {
const menu = inject(VMenuSymbol, null);
events.onClick = () => {
isActive.value = false;
menu?.closeParents();
};
}
return events;
});
const scrimEvents = computed(() => {
const events = {};
if (props.openOnHover) {
events.onMouseenter = () => {
if (firstEnter) {
isHovered = true;
firstEnter = false;
runOpenDelay();
}
};
events.onMouseleave = () => {
isHovered = false;
runCloseDelay();
};
}
return events;
});
watch(isTop, val => {
if (val && (props.openOnHover && !isHovered && (!openOnFocus.value || !isFocused) || openOnFocus.value && !isFocused && (!props.openOnHover || !isHovered))) {
isActive.value = false;
}
});
watch(isActive, val => {
if (!val) {
setTimeout(() => {
cursorTarget.value = undefined;
});
}
}, {
flush: 'post'
});
const activatorRef = ref();
watchEffect(() => {
if (!activatorRef.value) return;
nextTick(() => {
activatorEl.value = refElement(activatorRef.value);
});
});
const targetRef = ref();
const target = computed(() => {
if (props.target === 'cursor' && cursorTarget.value) return cursorTarget.value;
if (targetRef.value) return refElement(targetRef.value);
return getTarget(props.target, vm) || activatorEl.value;
});
const targetEl = computed(() => {
return Array.isArray(target.value) ? undefined : target.value;
});
let scope;
watch(() => !!props.activator, val => {
if (val && IN_BROWSER) {
scope = effectScope();
scope.run(() => {
_useActivator(props, vm, {
activatorEl,
activatorEvents
});
});
} else if (scope) {
scope.stop();
}
}, {
flush: 'post',
immediate: true
});
onScopeDispose(() => {
scope?.stop();
});
return {
activatorEl,
activatorRef,
target,
targetEl,
targetRef,
activatorEvents,
contentEvents,
scrimEvents
};
}
function _useActivator(props, vm, _ref2) {
let {
activatorEl,
activatorEvents
} = _ref2;
watch(() => props.activator, (val, oldVal) => {
if (oldVal && val !== oldVal) {
const activator = getActivator(oldVal);
activator && unbindActivatorProps(activator);
}
if (val) {
nextTick(() => bindActivatorProps());
}
}, {
immediate: true
});
watch(() => props.activatorProps, () => {
bindActivatorProps();
});
onScopeDispose(() => {
unbindActivatorProps();
});
function bindActivatorProps() {
let el = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getActivator();
let _props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : props.activatorProps;
if (!el) return;
bindProps(el, mergeProps(activatorEvents.value, _props));
}
function unbindActivatorProps() {
let el = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getActivator();
let _props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : props.activatorProps;
if (!el) return;
unbindProps(el, mergeProps(activatorEvents.value, _props));
}
function getActivator() {
let selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : props.activator;
const activator = getTarget(selector, vm);
// The activator should only be a valid element (Ignore comments and text nodes)
activatorEl.value = activator?.nodeType === Node.ELEMENT_NODE ? activator : undefined;
return activatorEl.value;
}
}
function getTarget(selector, vm) {
if (!selector) return;
let target;
if (selector === 'parent') {
let el = vm?.proxy?.$el?.parentNode;
while (el?.hasAttribute('data-no-activator')) {
el = el.parentNode;
}
target = el;
} else if (typeof selector === 'string') {
// Selector
target = document.querySelector(selector);
} else if ('$el' in selector) {
// Component (ref)
target = selector.$el;
} else {
// HTMLElement | Element | [x, y]
target = selector;
}
return target;
}
//# sourceMappingURL=useActivator.mjs.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,57 @@
// Types
/** Convert a point in local space to viewport space */
export function elementToViewport(point, offset) {
return {
x: point.x + offset.x,
y: point.y + offset.y
};
}
/** Convert a point in viewport space to local space */
export function viewportToElement(point, offset) {
return {
x: point.x - offset.x,
y: point.y - offset.y
};
}
/** Get the difference between two points */
export function getOffset(a, b) {
return {
x: a.x - b.x,
y: a.y - b.y
};
}
/** Convert an anchor object to a point in local space */
export function anchorToPoint(anchor, box) {
if (anchor.side === 'top' || anchor.side === 'bottom') {
const {
side,
align
} = anchor;
const x = align === 'left' ? 0 : align === 'center' ? box.width / 2 : align === 'right' ? box.width : align;
const y = side === 'top' ? 0 : side === 'bottom' ? box.height : side;
return elementToViewport({
x,
y
}, box);
} else if (anchor.side === 'left' || anchor.side === 'right') {
const {
side,
align
} = anchor;
const x = side === 'left' ? 0 : side === 'right' ? box.width : side;
const y = align === 'top' ? 0 : align === 'center' ? box.height / 2 : align === 'bottom' ? box.height : align;
return elementToViewport({
x,
y
}, box);
}
return elementToViewport({
x: box.width / 2,
y: box.height / 2
}, box);
}
//# sourceMappingURL=point.mjs.map

View File

@ -0,0 +1 @@
{"version":3,"file":"point.mjs","names":["elementToViewport","point","offset","x","y","viewportToElement","getOffset","a","b","anchorToPoint","anchor","box","side","align","width","height"],"sources":["../../../../src/components/VOverlay/util/point.ts"],"sourcesContent":["// Types\nimport type { ParsedAnchor } from '@/util'\nimport type { Box } from '@/util/box'\n\ntype Point = { x: number, y: number }\ndeclare class As<T extends string> {\n private as: T\n}\ntype ElementPoint = Point & As<'element'>\ntype ViewportPoint = Point & As<'viewport'>\ntype Offset = Point & As<'offset'>\n\n/** Convert a point in local space to viewport space */\nexport function elementToViewport (point: ElementPoint, offset: Offset | Box) {\n return {\n x: point.x + offset.x,\n y: point.y + offset.y,\n } as ViewportPoint\n}\n\n/** Convert a point in viewport space to local space */\nexport function viewportToElement (point: ViewportPoint, offset: Offset | Box) {\n return {\n x: point.x - offset.x,\n y: point.y - offset.y,\n } as ElementPoint\n}\n\n/** Get the difference between two points */\nexport function getOffset<T extends Point> (a: T, b: T) {\n return {\n x: a.x - b.x,\n y: a.y - b.y,\n } as Offset\n}\n\n/** Convert an anchor object to a point in local space */\nexport function anchorToPoint (anchor: ParsedAnchor, box: Box): ViewportPoint {\n if (anchor.side === 'top' || anchor.side === 'bottom') {\n const { side, align } = anchor\n\n const x: number =\n align === 'left' ? 0\n : align === 'center' ? box.width / 2\n : align === 'right' ? box.width\n : align\n const y: number =\n side === 'top' ? 0\n : side === 'bottom' ? box.height\n : side\n\n return elementToViewport({ x, y } as ElementPoint, box)\n } else if (anchor.side === 'left' || anchor.side === 'right') {\n const { side, align } = anchor\n\n const x: number =\n side === 'left' ? 0\n : side === 'right' ? box.width\n : side\n const y: number =\n align === 'top' ? 0\n : align === 'center' ? box.height / 2\n : align === 'bottom' ? box.height\n : align\n\n return elementToViewport({ x, y } as ElementPoint, box)\n }\n\n return elementToViewport({\n x: box.width / 2,\n y: box.height / 2,\n } as ElementPoint, box)\n}\n"],"mappings":"AAAA;;AAYA;AACA,OAAO,SAASA,iBAAiBA,CAAEC,KAAmB,EAAEC,MAAoB,EAAE;EAC5E,OAAO;IACLC,CAAC,EAAEF,KAAK,CAACE,CAAC,GAAGD,MAAM,CAACC,CAAC;IACrBC,CAAC,EAAEH,KAAK,CAACG,CAAC,GAAGF,MAAM,CAACE;EACtB,CAAC;AACH;;AAEA;AACA,OAAO,SAASC,iBAAiBA,CAAEJ,KAAoB,EAAEC,MAAoB,EAAE;EAC7E,OAAO;IACLC,CAAC,EAAEF,KAAK,CAACE,CAAC,GAAGD,MAAM,CAACC,CAAC;IACrBC,CAAC,EAAEH,KAAK,CAACG,CAAC,GAAGF,MAAM,CAACE;EACtB,CAAC;AACH;;AAEA;AACA,OAAO,SAASE,SAASA,CAAmBC,CAAI,EAAEC,CAAI,EAAE;EACtD,OAAO;IACLL,CAAC,EAAEI,CAAC,CAACJ,CAAC,GAAGK,CAAC,CAACL,CAAC;IACZC,CAAC,EAAEG,CAAC,CAACH,CAAC,GAAGI,CAAC,CAACJ;EACb,CAAC;AACH;;AAEA;AACA,OAAO,SAASK,aAAaA,CAAEC,MAAoB,EAAEC,GAAQ,EAAiB;EAC5E,IAAID,MAAM,CAACE,IAAI,KAAK,KAAK,IAAIF,MAAM,CAACE,IAAI,KAAK,QAAQ,EAAE;IACrD,MAAM;MAAEA,IAAI;MAAEC;IAAM,CAAC,GAAGH,MAAM;IAE9B,MAAMP,CAAS,GACbU,KAAK,KAAK,MAAM,GAAG,CAAC,GAClBA,KAAK,KAAK,QAAQ,GAAGF,GAAG,CAACG,KAAK,GAAG,CAAC,GAClCD,KAAK,KAAK,OAAO,GAAGF,GAAG,CAACG,KAAK,GAC7BD,KAAK;IACT,MAAMT,CAAS,GACbQ,IAAI,KAAK,KAAK,GAAG,CAAC,GAChBA,IAAI,KAAK,QAAQ,GAAGD,GAAG,CAACI,MAAM,GAC9BH,IAAI;IAER,OAAOZ,iBAAiB,CAAC;MAAEG,CAAC;MAAEC;IAAE,CAAC,EAAkBO,GAAG,CAAC;EACzD,CAAC,MAAM,IAAID,MAAM,CAACE,IAAI,KAAK,MAAM,IAAIF,MAAM,CAACE,IAAI,KAAK,OAAO,EAAE;IAC5D,MAAM;MAAEA,IAAI;MAAEC;IAAM,CAAC,GAAGH,MAAM;IAE9B,MAAMP,CAAS,GACbS,IAAI,KAAK,MAAM,GAAG,CAAC,GACjBA,IAAI,KAAK,OAAO,GAAGD,GAAG,CAACG,KAAK,GAC5BF,IAAI;IACR,MAAMR,CAAS,GACbS,KAAK,KAAK,KAAK,GAAG,CAAC,GACjBA,KAAK,KAAK,QAAQ,GAAGF,GAAG,CAACI,MAAM,GAAG,CAAC,GACnCF,KAAK,KAAK,QAAQ,GAAGF,GAAG,CAACI,MAAM,GAC/BF,KAAK;IAET,OAAOb,iBAAiB,CAAC;MAAEG,CAAC;MAAEC;IAAE,CAAC,EAAkBO,GAAG,CAAC;EACzD;EAEA,OAAOX,iBAAiB,CAAC;IACvBG,CAAC,EAAEQ,GAAG,CAACG,KAAK,GAAG,CAAC;IAChBV,CAAC,EAAEO,GAAG,CAACI,MAAM,GAAG;EAClB,CAAC,EAAkBJ,GAAG,CAAC;AACzB"}