first upload

This commit is contained in:
2026-02-24 12:43:16 +08:00
commit c05aaa5e08
88904 changed files with 3936503 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 07akioni
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,44 @@
# Vueuc
[![codecov](https://codecov.io/gh/07akioni/vueuc/branch/main/graph/badge.svg)](https://codecov.io/gh/07akioni/vueuc)
[![Total alerts](https://img.shields.io/lgtm/alerts/g/07akioni/vueuc.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/07akioni/vueuc/alerts/)
[![License](https://img.shields.io/badge/license-MIT-blue)](https://img.shields.io/badge/license-MIT-blue)
Util Components for Vue.
## Preview
[https://vueuc.vercel.app](https://vueuc.vercel.app)
## Components
### VBinder
Includes `v-binder`, `v-follower` and `v-target`.
Content in `v-follower` will track `v-target`. Use `v-binder` to wrap 1 `v-target` and 1 or more `v-target`.
For more API please read the source code. I'm too lazy to write them since I'm the only one that uses the library.
```tsx
<v-binder>
<v-target>
<target-element />
</v-target>
<v-follower show placement="top">
<follwer-element-1 />
<v-follower>
<v-follower show placement="bottom">
<follwer-element-1 />
<v-follower>
</v-binder>
```
### VVirtialList
A simple virtual list which supports flexable-height items
### VXScroll
All content inside it to be scroll in X direction when you wheel at Y direction.
### VResizeObserver
```html
<v-resize-observer :on-resize="handleResize">
<el />
</v-resize-observer>
```

View File

@@ -0,0 +1,24 @@
declare const Binder: import("vue").DefineComponent<{
syncTargetWithParent: BooleanConstructor;
syncTarget: {
type: BooleanConstructor;
default: boolean;
};
}, {
targetRef: import("vue").Ref<HTMLElement | null>;
setTargetRef: (el: HTMLElement | null) => void;
addScrollListener: (listener: () => void) => void;
removeScrollListener: (listener: () => void) => void;
addResizeListener: (listener: () => void) => void;
removeResizeListener: (listener: () => void) => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
syncTargetWithParent: BooleanConstructor;
syncTarget: {
type: BooleanConstructor;
default: boolean;
};
}>>, {
syncTargetWithParent: boolean;
syncTarget: boolean;
}, {}>;
export default Binder;

View File

@@ -0,0 +1,135 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { defineComponent, provide, ref, inject, getCurrentInstance, onBeforeUnmount } from 'vue';
import { beforeNextFrameOnce } from 'seemly';
import { on, off } from 'evtd';
import { getSlot } from '../../shared/v-node';
import { getScrollParent } from './utils';
const Binder = defineComponent({
name: 'Binder',
props: {
syncTargetWithParent: Boolean,
syncTarget: {
type: Boolean,
default: true
}
},
setup(props) {
var _a;
provide('VBinder', (_a = getCurrentInstance()) === null || _a === void 0 ? void 0 : _a.proxy);
const VBinder = inject('VBinder', null);
const targetRef = ref(null);
/**
* If there's no nested vbinder, we can simply set the target ref.
*
* However, when it comes to:
* <VBinder> <- syncTarget = false
*
* Should hold target DOM ref, but can't get it directly from
* its VTarget. So if there are nested VBinder, we should:
* 1. Stop setting target DOM from level-1 VTarget
* 2. Set target DOM from level-2 VTarget
* For (1), we need `syncTarget` to `false`
* For (2), we need to set `syncTargetWithParent` to `true` on
* level-2 VBinder
* <VTarget>
* <VBinder> <- syncTargetWithParent = true
* <VTarget>target</VTarget>
* </VBinder>
* <VFollower>
* content1
* </VFollower>
* </VTarget>
* <VFollower>
* content2
* </VFollower>
* </VBinder>
*/
const setTargetRef = (el) => {
targetRef.value = el;
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (VBinder && props.syncTargetWithParent) {
VBinder.setTargetRef(el);
}
};
// scroll related
let scrollableNodes = [];
const ensureScrollListener = () => {
let cursor = targetRef.value;
while (true) {
cursor = getScrollParent(cursor);
if (cursor === null)
break;
scrollableNodes.push(cursor);
}
for (const el of scrollableNodes) {
on('scroll', el, onScroll, true);
}
};
const removeScrollListeners = () => {
for (const el of scrollableNodes) {
off('scroll', el, onScroll, true);
}
scrollableNodes = [];
};
const followerScrollListeners = new Set();
const addScrollListener = (listener) => {
if (followerScrollListeners.size === 0) {
ensureScrollListener();
}
if (!followerScrollListeners.has(listener)) {
followerScrollListeners.add(listener);
}
};
const removeScrollListener = (listener) => {
if (followerScrollListeners.has(listener)) {
followerScrollListeners.delete(listener);
}
if (followerScrollListeners.size === 0) {
removeScrollListeners();
}
};
const onScroll = () => {
beforeNextFrameOnce(onScrollRaf);
};
const onScrollRaf = () => {
followerScrollListeners.forEach((listener) => listener());
};
// resize related
const followerResizeListeners = new Set();
const addResizeListener = (listener) => {
if (followerResizeListeners.size === 0) {
on('resize', window, onResize);
}
if (!followerResizeListeners.has(listener)) {
followerResizeListeners.add(listener);
}
};
const removeResizeListener = (listener) => {
if (followerResizeListeners.has(listener)) {
followerResizeListeners.delete(listener);
}
if (followerResizeListeners.size === 0) {
off('resize', window, onResize);
}
};
const onResize = () => {
followerResizeListeners.forEach((listener) => listener());
};
onBeforeUnmount(() => {
off('resize', window, onResize);
removeScrollListeners();
});
return {
targetRef,
setTargetRef,
addScrollListener,
removeScrollListener,
addResizeListener,
removeResizeListener
};
},
render() {
return getSlot('binder', this.$slots);
}
});
export default Binder;

View File

@@ -0,0 +1,88 @@
import { PropType } from 'vue';
import { BinderInstance, Placement } from './interface';
export interface FollowerInst {
syncPosition: () => void;
}
declare const _default: import("vue").DefineComponent<{
show: BooleanConstructor;
enabled: {
type: PropType<boolean | undefined>;
default: undefined;
};
placement: {
type: PropType<Placement>;
default: string;
};
syncTrigger: {
type: PropType<("resize" | "scroll")[]>;
default: string[];
};
to: PropType<string | HTMLElement>;
flip: {
type: BooleanConstructor;
default: boolean;
};
internalShift: BooleanConstructor;
x: NumberConstructor;
y: NumberConstructor;
width: PropType<string>;
minWidth: PropType<string>;
containerClass: StringConstructor;
teleportDisabled: BooleanConstructor;
zindexable: {
type: BooleanConstructor;
default: boolean;
};
zIndex: NumberConstructor;
overlap: BooleanConstructor;
}, {
VBinder: BinderInstance;
mergedEnabled: import("vue").ComputedRef<boolean>;
offsetContainerRef: import("vue").Ref<HTMLElement | null>;
followerRef: import("vue").Ref<HTMLElement | null>;
mergedTo: import("vue").ComputedRef<string | HTMLElement | undefined>;
syncPosition: () => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
show: BooleanConstructor;
enabled: {
type: PropType<boolean | undefined>;
default: undefined;
};
placement: {
type: PropType<Placement>;
default: string;
};
syncTrigger: {
type: PropType<("resize" | "scroll")[]>;
default: string[];
};
to: PropType<string | HTMLElement>;
flip: {
type: BooleanConstructor;
default: boolean;
};
internalShift: BooleanConstructor;
x: NumberConstructor;
y: NumberConstructor;
width: PropType<string>;
minWidth: PropType<string>;
containerClass: StringConstructor;
teleportDisabled: BooleanConstructor;
zindexable: {
type: BooleanConstructor;
default: boolean;
};
zIndex: NumberConstructor;
overlap: BooleanConstructor;
}>>, {
show: boolean;
enabled: boolean | undefined;
placement: Placement;
syncTrigger: ("resize" | "scroll")[];
flip: boolean;
internalShift: boolean;
teleportDisabled: boolean;
zindexable: boolean;
overlap: boolean;
}, {}>;
export default _default;

View File

@@ -0,0 +1,261 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { h, defineComponent, inject, nextTick, watch, toRef, ref, onMounted, onBeforeUnmount, withDirectives } from 'vue';
import { zindexable } from 'vdirs';
import { useMemo, useIsMounted, onFontsReady } from 'vooks';
import { useSsrAdapter } from '@css-render/vue3-ssr';
import { c, cssrAnchorMetaName } from '../../shared';
import LazyTeleport from '../../lazy-teleport/src/index';
import { getPlacementAndOffsetOfFollower, getProperTransformOrigin, getOffset } from './get-placement-style';
import { getPointRect, getRect } from './utils';
const style = c([
c('.v-binder-follower-container', {
position: 'absolute',
left: '0',
right: '0',
top: '0',
height: '0',
pointerEvents: 'none',
zIndex: 'auto'
}),
c('.v-binder-follower-content', {
position: 'absolute',
zIndex: 'auto'
}, [
c('> *', {
pointerEvents: 'all'
})
])
]);
export default defineComponent({
name: 'Follower',
inheritAttrs: false,
props: {
show: Boolean,
enabled: {
type: Boolean,
default: undefined
},
placement: {
type: String,
default: 'bottom'
},
syncTrigger: {
type: Array,
default: ['resize', 'scroll']
},
to: [String, Object],
flip: {
type: Boolean,
default: true
},
internalShift: Boolean,
x: Number,
y: Number,
width: String,
minWidth: String,
containerClass: String,
teleportDisabled: Boolean,
zindexable: {
type: Boolean,
default: true
},
zIndex: Number,
overlap: Boolean
},
setup(props) {
const VBinder = inject('VBinder');
const mergedEnabledRef = useMemo(() => {
return props.enabled !== undefined ? props.enabled : props.show;
});
const followerRef = ref(null);
const offsetContainerRef = ref(null);
const ensureListeners = () => {
const { syncTrigger } = props;
if (syncTrigger.includes('scroll')) {
VBinder.addScrollListener(syncPosition);
}
if (syncTrigger.includes('resize')) {
VBinder.addResizeListener(syncPosition);
}
};
const removeListeners = () => {
VBinder.removeScrollListener(syncPosition);
VBinder.removeResizeListener(syncPosition);
};
onMounted(() => {
if (mergedEnabledRef.value) {
syncPosition();
ensureListeners();
}
});
const ssrAdapter = useSsrAdapter();
style.mount({
id: 'vueuc/binder',
head: true,
anchorMetaName: cssrAnchorMetaName,
ssr: ssrAdapter
});
onBeforeUnmount(() => {
removeListeners();
});
onFontsReady(() => {
if (mergedEnabledRef.value) {
syncPosition();
}
});
const syncPosition = () => {
if (!mergedEnabledRef.value) {
return;
}
const follower = followerRef.value;
// sometimes watched props change before its dom is ready
// for example: show=false, x=undefined, y=undefined
// show=true, x=0, y=0
// will cause error
// I may optimize the watch start point later
if (follower === null)
return;
const target = VBinder.targetRef;
const { x, y, overlap } = props;
const targetRect = x !== undefined && y !== undefined
? getPointRect(x, y)
: getRect(target);
follower.style.setProperty('--v-target-width', `${Math.round(targetRect.width)}px`);
follower.style.setProperty('--v-target-height', `${Math.round(targetRect.height)}px`);
const { width, minWidth, placement, internalShift, flip } = props;
follower.setAttribute('v-placement', placement);
if (overlap) {
follower.setAttribute('v-overlap', '');
}
else {
follower.removeAttribute('v-overlap');
}
const { style } = follower;
if (width === 'target') {
style.width = `${targetRect.width}px`;
}
else if (width !== undefined) {
style.width = width;
}
else {
style.width = '';
}
if (minWidth === 'target') {
style.minWidth = `${targetRect.width}px`;
}
else if (minWidth !== undefined) {
style.minWidth = minWidth;
}
else {
style.minWidth = '';
}
const followerRect = getRect(follower);
const offsetContainerRect = getRect(offsetContainerRef.value);
const { left: offsetLeftToStandardPlacement, top: offsetTopToStandardPlacement, placement: properPlacement } = getPlacementAndOffsetOfFollower(placement, targetRect, followerRect, internalShift, flip, overlap);
const properTransformOrigin = getProperTransformOrigin(properPlacement, overlap);
const { left, top, transform } = getOffset(properPlacement, offsetContainerRect, targetRect, offsetTopToStandardPlacement, offsetLeftToStandardPlacement, overlap);
// we assume that the content size doesn't change after flip,
// nor we need to make sync logic more complex
follower.setAttribute('v-placement', properPlacement);
follower.style.setProperty('--v-offset-left', `${Math.round(offsetLeftToStandardPlacement)}px`);
follower.style.setProperty('--v-offset-top', `${Math.round(offsetTopToStandardPlacement)}px`);
follower.style.transform = `translateX(${left}) translateY(${top}) ${transform}`;
follower.style.setProperty('--v-transform-origin', properTransformOrigin);
follower.style.transformOrigin = properTransformOrigin;
};
watch(mergedEnabledRef, (value) => {
if (value) {
ensureListeners();
syncOnNextTick();
}
else {
removeListeners();
}
});
const syncOnNextTick = () => {
nextTick()
.then(syncPosition)
.catch((e) => console.error(e));
};
[
'placement',
'x',
'y',
'internalShift',
'flip',
'width',
'overlap',
'minWidth'
].forEach((prop) => {
watch(toRef(props, prop), syncPosition);
});
['teleportDisabled'].forEach((prop) => {
watch(toRef(props, prop), syncOnNextTick);
});
watch(toRef(props, 'syncTrigger'), (value) => {
if (!value.includes('resize')) {
VBinder.removeResizeListener(syncPosition);
}
else {
VBinder.addResizeListener(syncPosition);
}
if (!value.includes('scroll')) {
VBinder.removeScrollListener(syncPosition);
}
else {
VBinder.addScrollListener(syncPosition);
}
});
const isMountedRef = useIsMounted();
const mergedToRef = useMemo(() => {
const { to } = props;
if (to !== undefined)
return to;
if (isMountedRef.value) {
// TODO: find proper container
return undefined;
}
return undefined;
});
return {
VBinder,
mergedEnabled: mergedEnabledRef,
offsetContainerRef,
followerRef,
mergedTo: mergedToRef,
syncPosition
};
},
render() {
return h(LazyTeleport, {
show: this.show,
to: this.mergedTo,
disabled: this.teleportDisabled
}, {
default: () => {
var _a, _b;
const vNode = h('div', {
class: ['v-binder-follower-container', this.containerClass],
ref: 'offsetContainerRef'
}, [
h('div', {
class: 'v-binder-follower-content',
ref: 'followerRef'
}, (_b = (_a = this.$slots).default) === null || _b === void 0 ? void 0 : _b.call(_a))
]);
if (this.zindexable) {
return withDirectives(vNode, [
[
zindexable,
{
enabled: this.mergedEnabled,
zIndex: this.zIndex
}
]
]);
}
return vNode;
}
});
}
});

View File

@@ -0,0 +1,8 @@
declare const _default: import("vue").DefineComponent<{}, {
syncTarget: boolean;
setTargetDirective: {
mounted: (el: HTMLElement | null) => void;
updated: (el: HTMLElement | null) => void;
};
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}, {}>;
export default _default;

View File

@@ -0,0 +1,32 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { defineComponent, inject, withDirectives } from 'vue';
import { getFirstVNode } from '../../shared/v-node';
export default defineComponent({
name: 'Target',
setup() {
const { setTargetRef, syncTarget } = inject('VBinder');
const setTargetDirective = {
mounted: setTargetRef,
updated: setTargetRef
};
return {
syncTarget,
setTargetDirective
};
},
render() {
const { syncTarget, setTargetDirective } = this;
/**
* If you are using VBinder as a child of VBinder, the children wouldn't be
* a valid DOM or component that can be attached to by directive.
* So we won't sync target on those kind of situation and control the
* target sync logic manually.
*/
if (syncTarget) {
return withDirectives(getFirstVNode('follower', this.$slots), [
[setTargetDirective]
]);
}
return getFirstVNode('follower', this.$slots);
}
});

View File

@@ -0,0 +1,15 @@
import { Placement, Rect, TransformOrigin } from './interface';
interface PlacementAndOffset {
top: number;
left: number;
placement: Placement;
}
export declare function getPlacementAndOffsetOfFollower(placement: Placement, targetRect: Rect, followerRect: Rect, shift: boolean, flip: boolean, overlap: boolean): PlacementAndOffset;
export declare function getProperTransformOrigin(placement: Placement, overlap: boolean): TransformOrigin;
interface PlacementOffset {
top: string;
left: string;
transform: string;
}
export declare function getOffset(placement: Placement, offsetRect: Rect, targetRect: Rect, offsetTopToStandardPlacement: number, offsetLeftToStandardPlacement: number, overlap: boolean): PlacementOffset;
export {};

View File

@@ -0,0 +1,430 @@
const oppositionPositions = {
top: 'bottom',
bottom: 'top',
left: 'right',
right: 'left'
};
const oppositeAligns = {
start: 'end',
center: 'center',
end: 'start'
};
const propToCompare = {
top: 'height',
bottom: 'height',
left: 'width',
right: 'width'
};
const transformOrigins = {
'bottom-start': 'top left',
bottom: 'top center',
'bottom-end': 'top right',
'top-start': 'bottom left',
top: 'bottom center',
'top-end': 'bottom right',
'right-start': 'top left',
right: 'center left',
'right-end': 'bottom left',
'left-start': 'top right',
left: 'center right',
'left-end': 'bottom right'
};
const overlapTransformOrigin = {
'bottom-start': 'bottom left',
bottom: 'bottom center',
'bottom-end': 'bottom right',
'top-start': 'top left',
top: 'top center',
'top-end': 'top right',
'right-start': 'top right',
right: 'center right',
'right-end': 'bottom right',
'left-start': 'top left',
left: 'center left',
'left-end': 'bottom left'
};
const oppositeAlignCssPositionProps = {
'bottom-start': 'right',
'bottom-end': 'left',
'top-start': 'right',
'top-end': 'left',
'right-start': 'bottom',
'right-end': 'top',
'left-start': 'bottom',
'left-end': 'top'
};
const keepOffsetDirection = {
top: true,
bottom: false,
left: true,
right: false // left--
};
const cssPositionToOppositeAlign = {
top: 'end',
bottom: 'start',
left: 'end',
right: 'start'
};
export function getPlacementAndOffsetOfFollower(placement, targetRect, followerRect, shift, flip, overlap) {
if (!flip || overlap) {
return { placement: placement, top: 0, left: 0 };
}
const [position, align] = placement.split('-');
let properAlign = align !== null && align !== void 0 ? align : 'center';
let properOffset = {
top: 0,
left: 0
};
const deriveOffset = (oppositeAlignCssSizeProp, alignCssPositionProp, offsetVertically) => {
let left = 0;
let top = 0;
const diff = followerRect[oppositeAlignCssSizeProp] -
targetRect[alignCssPositionProp] -
targetRect[oppositeAlignCssSizeProp];
if (diff > 0 && shift) {
if (offsetVertically) {
// screen border
// |-----------------------------------------|
// | | f | |
// | | o | |
// | | l | |
// | | l |---- |
// | | o |tar | |
// | | w |get | |
// | | e | | |
// | | r |---- |
// | ---- |
// |-----------------------------------------|
top = keepOffsetDirection[alignCssPositionProp] ? diff : -diff;
}
else {
// screen border
// |----------------------------------------|
// | |
// | ---------- |
// | | target | |
// | ----------------------------------
// | | follower |
// | ----------------------------------
// | |
// |----------------------------------------|
left = keepOffsetDirection[alignCssPositionProp] ? diff : -diff;
}
}
return {
left,
top
};
};
const offsetVertically = position === 'left' || position === 'right';
// choose proper placement for non-center align
if (properAlign !== 'center') {
const oppositeAlignCssPositionProp = oppositeAlignCssPositionProps[placement];
const currentAlignCssPositionProp = oppositionPositions[oppositeAlignCssPositionProp];
const oppositeAlignCssSizeProp = propToCompare[oppositeAlignCssPositionProp];
// if follower rect is larger than target rect in align direction
// ----------[ target ]---------|
// ----------[ follower ]
if (followerRect[oppositeAlignCssSizeProp] >
targetRect[oppositeAlignCssSizeProp]) {
if (
// current space is not enough
// ----------[ target ]---------|
// -------[ follower ]
targetRect[oppositeAlignCssPositionProp] +
targetRect[oppositeAlignCssSizeProp] <
followerRect[oppositeAlignCssSizeProp]) {
const followerOverTargetSize = (followerRect[oppositeAlignCssSizeProp] -
targetRect[oppositeAlignCssSizeProp]) /
2;
if (targetRect[oppositeAlignCssPositionProp] < followerOverTargetSize ||
targetRect[currentAlignCssPositionProp] < followerOverTargetSize) {
// opposite align has larger space
// -------[ target ]-----------|
// -------[ follower ]-|
if (targetRect[oppositeAlignCssPositionProp] <
targetRect[currentAlignCssPositionProp]) {
properAlign = oppositeAligns[align];
properOffset = deriveOffset(oppositeAlignCssSizeProp, currentAlignCssPositionProp, offsetVertically);
}
else {
// ----------------[ target ]----|
// --------[ follower ]----|
properOffset = deriveOffset(oppositeAlignCssSizeProp, oppositeAlignCssPositionProp, offsetVertically);
}
}
else {
// 'center' align is better
// ------------[ target ]--------|
// -------[ follower ]--|
properAlign = 'center';
}
}
}
else if (followerRect[oppositeAlignCssSizeProp] <
targetRect[oppositeAlignCssSizeProp]) {
// TODO: maybe center is better
if (targetRect[currentAlignCssPositionProp] < 0 &&
// opposite align has larger space
// ------------[ target ]
// ----------------[follower]
targetRect[oppositeAlignCssPositionProp] >
targetRect[currentAlignCssPositionProp]) {
properAlign = oppositeAligns[align];
}
}
}
else {
const possibleAlternativeAlignCssPositionProp1 = position === 'bottom' || position === 'top' ? 'left' : 'top';
const possibleAlternativeAlignCssPositionProp2 = oppositionPositions[possibleAlternativeAlignCssPositionProp1];
const alternativeAlignCssSizeProp = propToCompare[possibleAlternativeAlignCssPositionProp1];
const followerOverTargetSize = (followerRect[alternativeAlignCssSizeProp] -
targetRect[alternativeAlignCssSizeProp]) /
2;
if (
// center is not enough
// ----------- [ target ]--|
// -------[ follower ]
targetRect[possibleAlternativeAlignCssPositionProp1] <
followerOverTargetSize ||
targetRect[possibleAlternativeAlignCssPositionProp2] <
followerOverTargetSize) {
// alternative 2 position's space is larger
if (targetRect[possibleAlternativeAlignCssPositionProp1] >
targetRect[possibleAlternativeAlignCssPositionProp2]) {
properAlign =
cssPositionToOppositeAlign[possibleAlternativeAlignCssPositionProp1];
properOffset = deriveOffset(alternativeAlignCssSizeProp, possibleAlternativeAlignCssPositionProp1, offsetVertically);
}
else {
// alternative 1 position's space is larger
properAlign =
cssPositionToOppositeAlign[possibleAlternativeAlignCssPositionProp2];
properOffset = deriveOffset(alternativeAlignCssSizeProp, possibleAlternativeAlignCssPositionProp2, offsetVertically);
}
}
}
let properPosition = position;
if (
// space is not enough
targetRect[position] < followerRect[propToCompare[position]] &&
// opposite position's space is larger
targetRect[position] < targetRect[oppositionPositions[position]]) {
properPosition = oppositionPositions[position];
}
return {
placement: properAlign !== 'center'
? `${properPosition}-${properAlign}`
: properPosition,
left: properOffset.left,
top: properOffset.top
};
}
export function getProperTransformOrigin(placement, overlap) {
if (overlap)
return overlapTransformOrigin[placement];
return transformOrigins[placement];
}
// ------------
// | offset |
// | |
// | [target] |
// | |
// ------------
// TODO: refactor it to remove dup logic
export function getOffset(placement, offsetRect, targetRect, offsetTopToStandardPlacement, offsetLeftToStandardPlacement, overlap) {
if (overlap) {
switch (placement) {
case 'bottom-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
transform: 'translateY(-100%)'
};
case 'bottom-end':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
transform: 'translateX(-100%) translateY(-100%)'
};
case 'top-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
transform: ''
};
case 'top-end':
return {
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
transform: 'translateX(-100%)'
};
case 'right-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
transform: 'translateX(-100%)'
};
case 'right-end':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
transform: 'translateX(-100%) translateY(-100%)'
};
case 'left-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
transform: ''
};
case 'left-end':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
transform: 'translateY(-100%)'
};
case 'top':
return {
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width / 2)}px`,
transform: 'translateX(-50%)'
};
case 'right':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height / 2)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
transform: 'translateX(-100%) translateY(-50%)'
};
case 'left':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height / 2)}px`,
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
transform: 'translateY(-50%)'
};
case 'bottom':
default:
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width / 2)}px`,
transform: 'translateX(-50%) translateY(-100%)'
};
}
}
switch (placement) {
case 'bottom-start':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
transform: ''
};
case 'bottom-end':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width +
offsetLeftToStandardPlacement)}px`,
transform: 'translateX(-100%)'
};
case 'top-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
transform: 'translateY(-100%)'
};
case 'top-end':
return {
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width +
offsetLeftToStandardPlacement)}px`,
transform: 'translateX(-100%) translateY(-100%)'
};
case 'right-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width +
offsetLeftToStandardPlacement)}px`,
transform: ''
};
case 'right-end':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width +
offsetLeftToStandardPlacement)}px`,
transform: 'translateY(-100%)'
};
case 'left-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
transform: 'translateX(-100%)'
};
case 'left-end':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
transform: 'translateX(-100%) translateY(-100%)'
};
case 'top':
return {
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width / 2 +
offsetLeftToStandardPlacement)}px`,
transform: 'translateY(-100%) translateX(-50%)'
};
case 'right':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height / 2 +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width +
offsetLeftToStandardPlacement)}px`,
transform: 'translateY(-50%)'
};
case 'left':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height / 2 +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
transform: 'translateY(-50%) translateX(-100%)'
};
case 'bottom':
default:
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width / 2 +
offsetLeftToStandardPlacement)}px`,
transform: 'translateX(-50%)'
};
}
}

View File

@@ -0,0 +1,5 @@
export { default as Binder, default as VBinder } from './Binder';
export { default as Target, default as VTarget } from './Target';
export { default as Follower, default as VFollower } from './Follower';
export type { FollowerInst } from './Follower';
export type { Placement as FollowerPlacement, ExposedBinderInstance as BinderInst } from './interface';

View File

@@ -0,0 +1,3 @@
export { default as Binder, default as VBinder } from './Binder';
export { default as Target, default as VTarget } from './Target';
export { default as Follower, default as VFollower } from './Follower';

View File

@@ -0,0 +1,26 @@
export interface ExposedBinderInstance {
targetRef: HTMLElement | null;
}
export interface BinderInstance extends ExposedBinderInstance {
syncTargetWithParent: boolean;
syncTarget: boolean;
setTargetRef: (el: HTMLElement | null) => void;
addScrollListener: (listener: () => void) => void;
removeScrollListener: (listener: () => void) => void;
addResizeListener: (listener: () => void) => void;
removeResizeListener: (listener: () => void) => void;
}
export declare type Placement = 'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end';
export declare type NonCenterPlacement = 'top-start' | 'top-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end';
export interface Rect {
left: number;
right: number;
top: number;
bottom: number;
width: number;
height: number;
}
export declare type Align = 'start' | 'end' | 'center';
export declare type Position = 'left' | 'right' | 'top' | 'bottom';
export declare type TransformOrigin = 'top left' | 'top center' | 'top right' | 'bottom left' | 'bottom center' | 'bottom right' | 'top left' | 'center left' | 'bottom left' | 'top right' | 'center right' | 'bottom right';
export declare type FlipLevel = 1 | 2;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,2 @@
import { Placement } from './interface';
export declare const placements: Placement[];

View File

@@ -0,0 +1,14 @@
export const placements = [
'top',
'bottom',
'left',
'right',
'top-start',
'top-end',
'left-start',
'left-end',
'right-start',
'right-end',
'bottom-start',
'bottom-end'
];

View File

@@ -0,0 +1,6 @@
import { Rect } from './interface';
export declare function ensureViewBoundingRect(): DOMRect;
export declare function getPointRect(x: number, y: number): Rect;
export declare function getRect(el: HTMLElement): Rect;
export declare function getParentNode(node: Node): Node | null;
export declare function getScrollParent(node: Node | null): HTMLElement | Document | null;

View File

@@ -0,0 +1,71 @@
let viewMeasurer = null;
export function ensureViewBoundingRect() {
if (viewMeasurer === null) {
viewMeasurer = document.getElementById('v-binder-view-measurer');
if (viewMeasurer === null) {
viewMeasurer = document.createElement('div');
viewMeasurer.id = 'v-binder-view-measurer';
const { style } = viewMeasurer;
style.position = 'fixed';
style.left = '0';
style.right = '0';
style.top = '0';
style.bottom = '0';
style.pointerEvents = 'none';
style.visibility = 'hidden';
document.body.appendChild(viewMeasurer);
}
}
return viewMeasurer.getBoundingClientRect();
}
export function getPointRect(x, y) {
const viewRect = ensureViewBoundingRect();
return {
top: y,
left: x,
height: 0,
width: 0,
right: viewRect.width - x,
bottom: viewRect.height - y
};
}
export function getRect(el) {
const elRect = el.getBoundingClientRect();
const viewRect = ensureViewBoundingRect();
return {
left: elRect.left - viewRect.left,
top: elRect.top - viewRect.top,
bottom: viewRect.height + viewRect.top - elRect.bottom,
right: viewRect.width + viewRect.left - elRect.right,
width: elRect.width,
height: elRect.height
};
}
export function getParentNode(node) {
// document type
if (node.nodeType === 9) {
return null;
}
return node.parentNode;
}
export function getScrollParent(node) {
if (node === null)
return null;
const parentNode = getParentNode(node);
if (parentNode === null) {
return null;
}
// Document
if (parentNode.nodeType === 9) {
return document;
}
// Element
if (parentNode.nodeType === 1) {
// Firefox want us to check `-x` and `-y` variations as well
const { overflow, overflowX, overflowY } = getComputedStyle(parentNode);
if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) {
return parentNode;
}
}
return getScrollParent(parentNode);
}

View File

@@ -0,0 +1 @@
export { FocusTrap } from './src';

View File

@@ -0,0 +1 @@
export { FocusTrap } from './src';

View File

@@ -0,0 +1,41 @@
import { PropType } from 'vue';
export declare const FocusTrap: import("vue").DefineComponent<{
disabled: BooleanConstructor;
active: BooleanConstructor;
autoFocus: {
type: BooleanConstructor;
default: boolean;
};
onEsc: PropType<(e: KeyboardEvent) => void>;
initialFocusTo: StringConstructor;
finalFocusTo: StringConstructor;
returnFocusOnDeactivated: {
type: BooleanConstructor;
default: boolean;
};
}, {
focusableStartRef: import("vue").Ref<HTMLElement | null>;
focusableEndRef: import("vue").Ref<HTMLElement | null>;
focusableStyle: string;
handleStartFocus: (e: FocusEvent) => void;
handleEndFocus: (e: FocusEvent) => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
disabled: BooleanConstructor;
active: BooleanConstructor;
autoFocus: {
type: BooleanConstructor;
default: boolean;
};
onEsc: PropType<(e: KeyboardEvent) => void>;
initialFocusTo: StringConstructor;
finalFocusTo: StringConstructor;
returnFocusOnDeactivated: {
type: BooleanConstructor;
default: boolean;
};
}>>, {
disabled: boolean;
active: boolean;
autoFocus: boolean;
returnFocusOnDeactivated: boolean;
}, {}>;

View File

@@ -0,0 +1,218 @@
import { h, defineComponent, ref, Fragment, onMounted, onBeforeUnmount, watch } from 'vue';
import { createId, getPreciseEventTarget } from 'seemly';
import { on, off } from 'evtd';
import { focusFirstDescendant, focusLastDescendant } from './utils';
import { resolveTo } from '../../shared';
let stack = [];
export const FocusTrap = defineComponent({
name: 'FocusTrap',
props: {
disabled: Boolean,
active: Boolean,
autoFocus: {
type: Boolean,
default: true
},
onEsc: Function,
initialFocusTo: String,
finalFocusTo: String,
returnFocusOnDeactivated: {
type: Boolean,
default: true
}
},
setup(props) {
const id = createId();
const focusableStartRef = ref(null);
const focusableEndRef = ref(null);
let activated = false;
let ignoreInternalFocusChange = false;
const lastFocusedElement = typeof document === 'undefined' ? null : document.activeElement;
function isCurrentActive() {
const currentActiveId = stack[stack.length - 1];
return currentActiveId === id;
}
function handleDocumentKeydown(e) {
var _a;
if (e.code === 'Escape') {
if (isCurrentActive()) {
(_a = props.onEsc) === null || _a === void 0 ? void 0 : _a.call(props, e);
}
}
}
onMounted(() => {
watch(() => props.active, (value) => {
if (value) {
activate();
on('keydown', document, handleDocumentKeydown);
}
else {
off('keydown', document, handleDocumentKeydown);
if (activated) {
deactivate();
}
}
}, {
immediate: true
});
});
onBeforeUnmount(() => {
off('keydown', document, handleDocumentKeydown);
if (activated)
deactivate();
});
function handleDocumentFocus(e) {
if (ignoreInternalFocusChange)
return;
if (isCurrentActive()) {
const mainEl = getMainEl();
if (mainEl === null)
return;
if (mainEl.contains(getPreciseEventTarget(e)))
return;
// I don't handle shift + tab status since it's too tricky to handle
// Not impossible but I need to sleep
resetFocusTo('first');
}
}
function getMainEl() {
const focusableStartEl = focusableStartRef.value;
if (focusableStartEl === null)
return null;
let mainEl = focusableStartEl;
while (true) {
mainEl = mainEl.nextSibling;
if (mainEl === null)
break;
if (mainEl instanceof Element && mainEl.tagName === 'DIV') {
break;
}
}
return mainEl;
}
function activate() {
var _a;
if (props.disabled)
return;
stack.push(id);
if (props.autoFocus) {
const { initialFocusTo } = props;
if (initialFocusTo === undefined) {
resetFocusTo('first');
}
else {
(_a = resolveTo(initialFocusTo)) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true });
}
}
activated = true;
document.addEventListener('focus', handleDocumentFocus, true);
}
function deactivate() {
var _a;
if (props.disabled)
return;
document.removeEventListener('focus', handleDocumentFocus, true);
stack = stack.filter((idInStack) => idInStack !== id);
if (isCurrentActive())
return;
const { finalFocusTo } = props;
if (finalFocusTo !== undefined) {
(_a = resolveTo(finalFocusTo)) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true });
}
else if (props.returnFocusOnDeactivated) {
if (lastFocusedElement instanceof HTMLElement) {
ignoreInternalFocusChange = true;
lastFocusedElement.focus({ preventScroll: true });
ignoreInternalFocusChange = false;
}
}
}
function resetFocusTo(target) {
if (!isCurrentActive())
return;
if (props.active) {
const focusableStartEl = focusableStartRef.value;
const focusableEndEl = focusableEndRef.value;
if (focusableStartEl !== null && focusableEndEl !== null) {
const mainEl = getMainEl();
if (mainEl == null || mainEl === focusableEndEl) {
ignoreInternalFocusChange = true;
focusableStartEl.focus({ preventScroll: true });
ignoreInternalFocusChange = false;
return;
}
ignoreInternalFocusChange = true;
const focused = target === 'first'
? focusFirstDescendant(mainEl)
: focusLastDescendant(mainEl);
ignoreInternalFocusChange = false;
if (!focused) {
ignoreInternalFocusChange = true;
focusableStartEl.focus({ preventScroll: true });
ignoreInternalFocusChange = false;
}
}
}
}
function handleStartFocus(e) {
if (ignoreInternalFocusChange)
return;
const mainEl = getMainEl();
if (mainEl === null)
return;
if (e.relatedTarget !== null && mainEl.contains(e.relatedTarget)) {
// if it comes from inner, focus last
resetFocusTo('last');
}
else {
// otherwise focus first
resetFocusTo('first');
}
}
function handleEndFocus(e) {
if (ignoreInternalFocusChange)
return;
if (e.relatedTarget !== null &&
e.relatedTarget === focusableStartRef.value) {
// if it comes from first, focus last
resetFocusTo('last');
}
else {
// otherwise focus first
resetFocusTo('first');
}
}
return {
focusableStartRef,
focusableEndRef,
focusableStyle: 'position: absolute; height: 0; width: 0;',
handleStartFocus,
handleEndFocus
};
},
render() {
const { default: defaultSlot } = this.$slots;
if (defaultSlot === undefined)
return null;
if (this.disabled)
return defaultSlot();
const { active, focusableStyle } = this;
return h(Fragment, null, [
h('div', {
'aria-hidden': 'true',
tabindex: active ? '0' : '-1',
ref: 'focusableStartRef',
style: focusableStyle,
onFocus: this.handleStartFocus
}),
defaultSlot(),
h('div', {
'aria-hidden': 'true',
style: focusableStyle,
ref: 'focusableEndRef',
tabindex: active ? '0' : '-1',
onFocus: this.handleEndFocus
})
]);
}
});

View File

@@ -0,0 +1,2 @@
export declare function focusFirstDescendant(node: Node): boolean;
export declare function focusLastDescendant(element: Node): boolean;

View File

@@ -0,0 +1,60 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
// ref https://www.w3.org/TR/wai-aria-practices-1.1/examples/dialog-modal/js/dialog.js
function isHTMLElement(node) {
return node instanceof HTMLElement;
}
export function focusFirstDescendant(node) {
for (let i = 0; i < node.childNodes.length; i++) {
const child = node.childNodes[i];
if (isHTMLElement(child)) {
if (attemptFocus(child) || focusFirstDescendant(child)) {
return true;
}
}
}
return false;
}
export function focusLastDescendant(element) {
for (let i = element.childNodes.length - 1; i >= 0; i--) {
const child = element.childNodes[i];
if (isHTMLElement(child)) {
if (attemptFocus(child) || focusLastDescendant(child)) {
return true;
}
}
}
return false;
}
function attemptFocus(element) {
if (!isFocusable(element)) {
return false;
}
try {
element.focus({ preventScroll: true });
}
catch (e) { }
return document.activeElement === element;
}
function isFocusable(element) {
if (element.tabIndex > 0 ||
(element.tabIndex === 0 && element.getAttribute('tabIndex') !== null)) {
return true;
}
if (element.getAttribute('disabled')) {
return false;
}
switch (element.nodeName) {
case 'A':
return (!!element.href &&
element.rel !== 'ignore');
case 'INPUT':
return (element.type !== 'hidden' &&
element.type !== 'file');
case 'BUTTON':
case 'SELECT':
case 'TEXTAREA':
return true;
default:
return false;
}
}

View File

@@ -0,0 +1,12 @@
export * from './binder/src';
export { default as VirtualList, default as VVirtualList } from './virtual-list/src';
export type { VirtualListInst as VVirtualListInst, VirtualListScrollTo as VVirtualListScrollTo, VirtualListScrollToOptions as VVirtualListScrollToOptions, VirtualListItemData as VVirtualListItemData, VVirtualListColumn, VVirtualListRenderCol, VVirtualListRenderItemWithCols } from './virtual-list/src';
export { default as LazyTeleport, default as VLazyTeleport } from './lazy-teleport/src';
export { default as ResizeObserver, default as VResizeObserver, resizeObserverManager } from './resize-observer/src';
export type { VResizeObserverOnResize } from './resize-observer/src';
export { default as XScroll, default as VXScroll } from './x-scroll/src';
export type { VXScrollInst } from './x-scroll/src';
export { VOverflow, Overflow } from './overflow';
export type { VOverflowInst } from './overflow';
export { FocusTrap, FocusTrap as VFocusTrap } from './focus-trap';
export type { VirtualListInst, VirtualListScrollTo, VirtualListScrollToOptions, VirtualListItemData } from './virtual-list/src';

View File

@@ -0,0 +1,7 @@
export * from './binder/src';
export { default as VirtualList, default as VVirtualList } from './virtual-list/src';
export { default as LazyTeleport, default as VLazyTeleport } from './lazy-teleport/src';
export { default as ResizeObserver, default as VResizeObserver, resizeObserverManager } from './resize-observer/src';
export { default as XScroll, default as VXScroll } from './x-scroll/src';
export { VOverflow, Overflow } from './overflow';
export { FocusTrap, FocusTrap as VFocusTrap } from './focus-trap';

View File

@@ -0,0 +1,29 @@
import { PropType } from 'vue';
declare const _default: import("vue").DefineComponent<{
to: {
type: PropType<string | HTMLElement>;
default: undefined;
};
disabled: BooleanConstructor;
show: {
type: BooleanConstructor;
required: true;
};
}, {
showTeleport: Readonly<import("vue").Ref<boolean>>;
mergedTo: import("vue").ComputedRef<string | HTMLElement>;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
to: {
type: PropType<string | HTMLElement>;
default: undefined;
};
disabled: BooleanConstructor;
show: {
type: BooleanConstructor;
required: true;
};
}>>, {
to: string | HTMLElement;
disabled: boolean;
}, {}>;
export default _default;

View File

@@ -0,0 +1,36 @@
import { Teleport, h, toRef, computed, defineComponent } from 'vue';
import { useFalseUntilTruthy } from 'vooks';
import { getSlot } from '../../shared/v-node';
export default defineComponent({
name: 'LazyTeleport',
props: {
to: {
type: [String, Object],
default: undefined
},
disabled: Boolean,
show: {
type: Boolean,
required: true
}
},
setup(props) {
return {
showTeleport: useFalseUntilTruthy(toRef(props, 'show')),
mergedTo: computed(() => {
const { to } = props;
return to !== null && to !== void 0 ? to : 'body';
})
};
},
render() {
return this.showTeleport
? this.disabled
? getSlot('lazy-teleport', this.$slots)
: h(Teleport, {
disabled: this.disabled,
to: this.mergedTo
}, getSlot('lazy-teleport', this.$slots))
: null;
}
});

View File

@@ -0,0 +1,3 @@
export { default as VOverflow } from './src';
export { default as Overflow } from './src';
export type { VOverflowInst } from './src';

View File

@@ -0,0 +1,2 @@
export { default as VOverflow } from './src';
export { default as Overflow } from './src';

View File

@@ -0,0 +1,26 @@
import { PropType } from 'vue';
export interface VOverflowInst {
sync: (options: {
showAllItemsBeforeCalculate: boolean;
}) => void;
}
declare const _default: import("vue").DefineComponent<{
getCounter: PropType<() => HTMLElement | null>;
getTail: PropType<() => HTMLElement | null>;
updateCounter: PropType<(count: number) => void>;
onUpdateCount: PropType<(count: number) => void>;
onUpdateOverflow: PropType<(overflow: boolean) => void>;
}, {
selfRef: import("vue").Ref<HTMLElement | null>;
counterRef: import("vue").Ref<HTMLElement | null>;
sync: (options: {
showAllItemsBeforeCalculate: boolean;
}) => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
getCounter: PropType<() => HTMLElement | null>;
getTail: PropType<() => HTMLElement | null>;
updateCounter: PropType<(count: number) => void>;
onUpdateCount: PropType<(count: number) => void>;
onUpdateOverflow: PropType<(overflow: boolean) => void>;
}>>, {}, {}>;
export default _default;

View File

@@ -0,0 +1,154 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { defineComponent, renderSlot, h, onMounted, ref, nextTick } from 'vue';
import { useSsrAdapter } from '@css-render/vue3-ssr';
import { c, cssrAnchorMetaName } from '../../shared';
const hiddenAttr = 'v-hidden';
const style = c('[v-hidden]', {
display: 'none!important'
});
export default defineComponent({
name: 'Overflow',
props: {
getCounter: Function,
getTail: Function,
updateCounter: Function,
onUpdateCount: Function,
onUpdateOverflow: Function
},
setup(props, { slots }) {
const selfRef = ref(null);
const counterRef = ref(null);
function deriveCounter(options) {
const { value: self } = selfRef;
const { getCounter, getTail } = props;
let counter;
if (getCounter !== undefined)
counter = getCounter();
else {
counter = counterRef.value;
}
if (!self || !counter)
return;
if (counter.hasAttribute(hiddenAttr)) {
counter.removeAttribute(hiddenAttr);
}
const { children } = self;
if (options.showAllItemsBeforeCalculate) {
for (const child of children) {
if (child.hasAttribute(hiddenAttr)) {
child.removeAttribute(hiddenAttr);
}
}
}
const containerWidth = self.offsetWidth;
const childWidths = [];
const tail = slots.tail ? getTail === null || getTail === void 0 ? void 0 : getTail() : null;
let childWidthSum = tail ? tail.offsetWidth : 0;
let overflow = false;
const len = self.children.length - (slots.tail ? 1 : 0);
for (let i = 0; i < len - 1; ++i) {
if (i < 0)
continue;
const child = children[i];
if (overflow) {
if (!child.hasAttribute(hiddenAttr)) {
child.setAttribute(hiddenAttr, '');
}
continue;
}
else if (child.hasAttribute(hiddenAttr)) {
child.removeAttribute(hiddenAttr);
}
const childWidth = child.offsetWidth;
childWidthSum += childWidth;
childWidths[i] = childWidth;
if (childWidthSum > containerWidth) {
const { updateCounter } = props;
for (let j = i; j >= 0; --j) {
const restCount = len - 1 - j;
if (updateCounter !== undefined) {
updateCounter(restCount);
}
else {
counter.textContent = `${restCount}`;
}
const counterWidth = counter.offsetWidth;
childWidthSum -= childWidths[j];
if (childWidthSum + counterWidth <= containerWidth || j === 0) {
overflow = true;
i = j - 1;
if (tail) {
// tail too long or 1st element too long
// we only consider tail now
if (i === -1) {
tail.style.maxWidth = `${containerWidth - counterWidth}px`;
tail.style.boxSizing = 'border-box';
}
else {
tail.style.maxWidth = '';
}
}
const { onUpdateCount } = props;
if (onUpdateCount)
onUpdateCount(restCount);
break;
}
}
}
}
const { onUpdateOverflow } = props;
if (!overflow) {
if (onUpdateOverflow !== undefined) {
onUpdateOverflow(false);
}
counter.setAttribute(hiddenAttr, '');
}
else {
if (onUpdateOverflow !== undefined) {
onUpdateOverflow(true);
}
}
}
const ssrAdapter = useSsrAdapter();
style.mount({
id: 'vueuc/overflow',
head: true,
anchorMetaName: cssrAnchorMetaName,
ssr: ssrAdapter
});
onMounted(() => deriveCounter({
showAllItemsBeforeCalculate: false
}));
// besides onMounted, other case should be manually triggered, or we shoud watch items
return {
selfRef,
counterRef,
sync: deriveCounter
};
},
render() {
const { $slots } = this;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
nextTick(() => this.sync({
showAllItemsBeforeCalculate: false
}));
// It shouldn't have border
return h('div', {
class: 'v-overflow',
ref: 'selfRef'
}, [
renderSlot($slots, 'default'),
// $slots.counter should only has 1 element
$slots.counter
? $slots.counter()
: h('span', {
style: {
display: 'inline-block'
},
ref: 'counterRef'
}),
// $slots.tail should only has 1 element
$slots.tail ? $slots.tail() : null
]);
}
});

View File

@@ -0,0 +1,43 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { mount, sleepFrame } from '@/test-shared';
import { defineComponent, h } from 'vue';
import { VResizeObserver } from '../..';
describe('resize-observer', () => {
it('works', () => __awaiter(void 0, void 0, void 0, function* () {
let resizeCount = 0;
const onResize = () => {
resizeCount++;
};
const wrapper = mount(defineComponent({
render() {
return h(VResizeObserver, {
onResize
}, {
default: () => h('div', {
ref: 'cool',
style: {
width: '200px',
height: '200px'
}
})
});
}
}), { attach: true });
yield sleepFrame();
yield sleepFrame();
expect(resizeCount).toEqual(1);
wrapper.instance.$refs.cool.style.width = '300px';
yield sleepFrame();
yield sleepFrame();
expect(resizeCount).toEqual(2);
wrapper.unmount();
}));
});

View File

@@ -0,0 +1,8 @@
import { PropType } from 'vue';
export declare type VResizeObserverOnResize = (entry: ResizeObserverEntry) => void;
declare const _default: import("vue").DefineComponent<{
onResize: PropType<VResizeObserverOnResize>;
}, void, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
onResize: PropType<VResizeObserverOnResize>;
}>>, {}, {}>;
export default _default;

View File

@@ -0,0 +1,44 @@
import { defineComponent, renderSlot, getCurrentInstance, onMounted, onBeforeUnmount } from 'vue';
import delegate from './delegate';
import { warn } from '../../shared';
export default defineComponent({
name: 'ResizeObserver',
props: {
onResize: Function
},
setup(props) {
let registered = false;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const proxy = getCurrentInstance().proxy;
function handleResize(entry) {
const { onResize } = props;
if (onResize !== undefined)
onResize(entry);
}
onMounted(() => {
const el = proxy.$el;
if (el === undefined) {
warn('resize-observer', '$el does not exist.');
return;
}
if (el.nextElementSibling !== el.nextSibling) {
if (el.nodeType === 3 && el.nodeValue !== '') {
warn('resize-observer', '$el can not be observed (it may be a text node).');
return;
}
}
if (el.nextElementSibling !== null) {
delegate.registerHandler(el.nextElementSibling, handleResize);
registered = true;
}
});
onBeforeUnmount(() => {
if (registered) {
delegate.unregisterHandler(proxy.$el.nextElementSibling);
}
});
},
render() {
return renderSlot(this.$slots, 'default');
}
});

View File

@@ -0,0 +1,12 @@
import { ResizeObserver as PolyfillResizeObserver } from '@juggle/resize-observer';
declare type ResizeHandler = (entry: ResizeObserverEntry) => void;
declare class ResizeObserverDelegate {
elHandlersMap: Map<Element, ResizeHandler>;
observer: PolyfillResizeObserver;
constructor();
handleResize(this: ResizeObserverDelegate, entries: ResizeObserverEntry[]): void;
registerHandler(el: Element, handler: ResizeHandler): void;
unregisterHandler(el: Element): void;
}
declare const _default: ResizeObserverDelegate;
export default _default;

View File

@@ -0,0 +1,30 @@
import { ResizeObserver as PolyfillResizeObserver } from '@juggle/resize-observer';
class ResizeObserverDelegate {
constructor() {
this.handleResize = this.handleResize.bind(this);
this.observer = new ((typeof window !== 'undefined' &&
window.ResizeObserver) ||
PolyfillResizeObserver)(this.handleResize);
this.elHandlersMap = new Map();
}
handleResize(entries) {
for (const entry of entries) {
const handler = this.elHandlersMap.get(entry.target);
if (handler !== undefined) {
handler(entry);
}
}
}
registerHandler(el, handler) {
this.elHandlersMap.set(el, handler);
this.observer.observe(el);
}
unregisterHandler(el) {
if (!this.elHandlersMap.has(el)) {
return;
}
this.elHandlersMap.delete(el);
this.observer.unobserve(el);
}
}
export default new ResizeObserverDelegate();

View File

@@ -0,0 +1,3 @@
export { default } from './VResizeObserver';
export type { VResizeObserverOnResize } from './VResizeObserver';
export { default as resizeObserverManager } from './delegate';

View File

@@ -0,0 +1,2 @@
export { default } from './VResizeObserver';
export { default as resizeObserverManager } from './delegate';

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,33 @@
import { FinweckTree } from '../finweck-tree';
describe('finweck tree', () => {
it('#sum', () => {
const ft = new FinweckTree(10, 5);
expect(ft.sum(0)).toEqual(0);
expect(ft.sum(1)).toEqual(5);
expect(ft.sum(9)).toEqual(45);
expect(ft.sum(10)).toEqual(50);
expect(() => ft.sum(11)).toThrow();
});
it('#add', () => {
const ft = new FinweckTree(10, 5);
ft.add(0, 1);
expect(ft.sum(0)).toEqual(0);
expect(ft.sum(1)).toEqual(6);
expect(ft.sum(2)).toEqual(11);
expect(ft.sum(3)).toEqual(16);
expect(ft.sum(9)).toEqual(46);
expect(ft.sum(10)).toEqual(51);
expect(() => ft.sum(11)).toThrow();
});
it('#threshold', () => {
const ft = new FinweckTree(10, 5);
expect(ft.getBound(-1)).toEqual(0);
expect(ft.getBound(0)).toEqual(0);
expect(ft.getBound(9.5)).toEqual(1);
expect(ft.getBound(10)).toEqual(2);
expect(ft.getBound(10.5)).toEqual(2);
expect(ft.getBound(49.5)).toEqual(9);
expect(ft.getBound(50)).toEqual(10);
expect(ft.getBound(10000)).toEqual(10);
});
});

View File

@@ -0,0 +1,3 @@
declare const c: import("css-render").createCNode<import("css-render").CSelector>;
export { c };
export declare const cssrAnchorMetaName = "vueuc-style";

View File

@@ -0,0 +1,4 @@
import { CssRender } from 'css-render';
const { c } = CssRender();
export { c };
export const cssrAnchorMetaName = 'vueuc-style';

View File

@@ -0,0 +1,34 @@
export declare class FinweckTree {
l: number;
min: number;
ft: number[];
/**
* @param l length of the array
* @param min min value of the array
*/
constructor(l: number, min: number);
/**
* Add arr[i] by n, start from 0
* @param i the index of the element to be added
* @param n the value to be added
*/
add(i: number, n: number): void;
/**
* Get the value of index i
* @param i index
* @returns value of the index
*/
get(i: number): number;
/**
* Get the sum of first i elements
* @param i count of head elements to be added
* @returns the sum of first i elements
*/
sum(i?: number): number;
/**
* Get the largest count of head elements whose sum are <= threshold
* @param threshold
* @returns the largest count of head elements whose sum are <= threshold
*/
getBound(threshold: number): number;
}

View File

@@ -0,0 +1,90 @@
function lowBit(n) {
return n & -n;
}
export class FinweckTree {
/**
* @param l length of the array
* @param min min value of the array
*/
constructor(l, min) {
this.l = l;
this.min = min;
const ft = new Array(l + 1);
for (let i = 0; i < l + 1; ++i) {
ft[i] = 0;
}
this.ft = ft;
}
/**
* Add arr[i] by n, start from 0
* @param i the index of the element to be added
* @param n the value to be added
*/
add(i, n) {
if (n === 0)
return;
const { l, ft } = this;
i += 1;
while (i <= l) {
ft[i] += n;
i += lowBit(i);
}
}
/**
* Get the value of index i
* @param i index
* @returns value of the index
*/
get(i) {
return this.sum(i + 1) - this.sum(i);
}
/**
* Get the sum of first i elements
* @param i count of head elements to be added
* @returns the sum of first i elements
*/
sum(i) {
if (i === undefined)
i = this.l;
if (i <= 0)
return 0;
const { ft, min, l } = this;
if (i > l)
throw new Error('[FinweckTree.sum]: `i` is larger than length.');
let ret = i * min;
while (i > 0) {
ret += ft[i];
i -= lowBit(i);
}
return ret;
}
/**
* Get the largest count of head elements whose sum are <= threshold
* @param threshold
* @returns the largest count of head elements whose sum are <= threshold
*/
getBound(threshold) {
let l = 0;
let r = this.l;
while (r > l) {
const m = Math.floor((l + r) / 2);
const sumM = this.sum(m);
if (sumM > threshold) {
r = m;
continue;
}
else if (sumM < threshold) {
if (l === m) {
if (this.sum(l + 1) <= threshold)
return l + 1;
return m;
}
l = m;
}
else {
return m;
}
}
return l;
}
}

View File

@@ -0,0 +1,5 @@
export { warn } from './warn';
export { c, cssrAnchorMetaName } from './cssr';
export { getFirstVNode, getSlot } from './v-node';
export { FinweckTree } from './finweck-tree';
export { resolveTo } from './resolve-to';

View File

@@ -0,0 +1,5 @@
export { warn } from './warn';
export { c, cssrAnchorMetaName } from './cssr';
export { getFirstVNode, getSlot } from './v-node';
export { FinweckTree } from './finweck-tree';
export { resolveTo } from './resolve-to';

View File

@@ -0,0 +1 @@
export declare function resolveTo(selector: string | (() => HTMLElement | null)): HTMLElement | null;

View File

@@ -0,0 +1,6 @@
export function resolveTo(selector) {
if (typeof selector === 'string') {
return document.querySelector(selector);
}
return selector();
}

View File

@@ -0,0 +1,4 @@
import { VNodeChild, VNode, Slots } from 'vue';
export declare function getSlot(scope: string, slots: Slots, slotName?: string): VNode[];
export declare function flatten(vNodes: VNodeChild[], filterCommentNode?: boolean, result?: VNode[]): VNode[];
export declare function getFirstVNode(scope: string, slots: Slots, slotName?: string): VNode;

View File

@@ -0,0 +1,51 @@
import { Fragment, createTextVNode, Comment } from 'vue';
export function getSlot(scope, slots, slotName = 'default') {
const slot = slots[slotName];
if (slot === undefined) {
throw new Error(`[vueuc/${scope}]: slot[${slotName}] is empty.`);
}
return slot();
}
// o(n) flatten
export function flatten(vNodes, filterCommentNode = true, result = []) {
vNodes.forEach((vNode) => {
if (vNode === null)
return;
if (typeof vNode !== 'object') {
if (typeof vNode === 'string' || typeof vNode === 'number') {
result.push(createTextVNode(String(vNode)));
}
return;
}
if (Array.isArray(vNode)) {
flatten(vNode, filterCommentNode, result);
return;
}
if (vNode.type === Fragment) {
if (vNode.children === null)
return;
if (Array.isArray(vNode.children)) {
flatten(vNode.children, filterCommentNode, result);
}
// rawSlot
}
else if (vNode.type !== Comment) {
result.push(vNode);
}
});
return result;
}
export function getFirstVNode(scope, slots, slotName = 'default') {
const slot = slots[slotName];
if (slot === undefined) {
throw new Error(`[vueuc/${scope}]: slot[${slotName}] is empty.`);
}
const content = flatten(slot());
// vue will normalize the slot, so slot must be an array
if (content.length === 1) {
return content[0];
}
else {
throw new Error(`[vueuc/${scope}]: slot[${slotName}] should have exactly one child.`);
}
}

View File

@@ -0,0 +1 @@
export declare function warn(location: string, message: string): void;

View File

@@ -0,0 +1,3 @@
export function warn(location, message) {
console.error(`[vueuc/${location}]: ${message}`);
}

View File

@@ -0,0 +1,14 @@
import { Component, App, ComponentPublicInstance } from 'vue';
interface Wrapper {
app: App;
instance: ComponentPublicInstance;
unmount: () => void;
}
interface MountOptions {
props?: Record<string, any>;
attach?: boolean;
}
export declare function mount(comp: Component, options?: MountOptions): Wrapper;
export declare function sleep(ms: number): Promise<void>;
export declare function sleepFrame(): Promise<void>;
export {};

View File

@@ -0,0 +1,45 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { createApp } from 'vue';
export function mount(comp, options = {}) {
const { props = {}, attach = false } = options;
const div = document.createElement('div');
if (attach) {
document.body.appendChild(div);
}
const app = createApp(Object.assign({ render() {
return null;
} }, comp), props);
const instance = app.mount(div);
return {
app,
instance,
unmount: () => {
app.unmount();
if (attach) {
document.body.removeChild(div);
}
}
};
}
export function sleep(ms) {
return __awaiter(this, void 0, void 0, function* () {
return yield new Promise((resolve) => {
setTimeout(resolve, ms);
});
});
}
export function sleepFrame() {
return __awaiter(this, void 0, void 0, function* () {
return yield new Promise((resolve) => {
requestAnimationFrame(() => resolve());
});
});
}

View File

@@ -0,0 +1,13 @@
import { VirtualList } from '@/index';
import { mount } from '@/test-shared/index';
describe('virtual-list', () => {
it('needs tests', () => {
mount(VirtualList, {
props: {
items: [],
itemSize: 34
}
});
expect('').toEqual('');
});
});

View File

@@ -0,0 +1,153 @@
import { PropType, CSSProperties } from 'vue';
import { ItemData, VScrollToOptions, VVirtualListColumn, VVirtualListRenderCol, VVirtualListRenderItemWithCols } from './type';
export interface CommonScrollToOptions {
behavior?: ScrollBehavior;
debounce?: boolean;
}
export interface ScrollTo {
(x: number, y: number): void;
(options: {
left?: number;
top?: number;
} & CommonScrollToOptions): void;
(options: {
index: number;
} & CommonScrollToOptions): void;
(options: {
key: string | number;
} & CommonScrollToOptions): void;
(options: {
position: 'top' | 'bottom';
} & CommonScrollToOptions): void;
(options: VScrollToOptions): void;
}
export interface VirtualListInst {
listElRef: HTMLElement;
itemsElRef: HTMLElement | null;
scrollTo: ScrollTo;
}
declare const _default: import("vue").DefineComponent<{
showScrollbar: {
type: BooleanConstructor;
default: boolean;
};
columns: {
type: PropType<VVirtualListColumn[]>;
default: () => never[];
};
renderCol: PropType<VVirtualListRenderCol>;
renderItemWithCols: PropType<VVirtualListRenderItemWithCols>;
items: {
type: PropType<ItemData[]>;
default: () => never[];
};
itemSize: {
type: NumberConstructor;
required: true;
};
itemResizable: BooleanConstructor;
itemsStyle: PropType<string | CSSProperties>;
visibleItemsTag: {
type: PropType<string | object>;
default: string;
};
visibleItemsProps: ObjectConstructor;
ignoreItemResize: BooleanConstructor;
onScroll: PropType<(event: Event) => void>;
onWheel: PropType<(event: WheelEvent) => void>;
onResize: PropType<(entry: ResizeObserverEntry) => void>;
defaultScrollKey: (StringConstructor | NumberConstructor)[];
defaultScrollIndex: NumberConstructor;
keyField: {
type: StringConstructor;
default: string;
};
paddingTop: {
type: (StringConstructor | NumberConstructor)[];
default: number;
};
paddingBottom: {
type: (StringConstructor | NumberConstructor)[];
default: number;
};
}, {
listHeight: import("vue").Ref<number | undefined>;
listStyle: {
overflow: string;
};
keyToIndex: import("vue").ComputedRef<Map<any, any>>;
itemsStyle: import("vue").ComputedRef<(string | CSSProperties | {
boxSizing: string;
width: string | undefined;
height: string;
minHeight: string;
paddingTop: string;
paddingBottom: string;
} | undefined)[]>;
visibleItemsStyle: import("vue").ComputedRef<{
transform: string;
}>;
viewportItems: import("vue").ComputedRef<ItemData[]>;
listElRef: import("vue").Ref<HTMLElement | null>;
itemsElRef: import("vue").Ref<Element | null>;
scrollTo: ScrollTo;
handleListResize: (entry: ResizeObserverEntry) => void;
handleListScroll: (e: UIEvent) => void;
handleListWheel: (e: WheelEvent) => void;
handleItemResize: (key: string | number, entry: ResizeObserverEntry) => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
showScrollbar: {
type: BooleanConstructor;
default: boolean;
};
columns: {
type: PropType<VVirtualListColumn[]>;
default: () => never[];
};
renderCol: PropType<VVirtualListRenderCol>;
renderItemWithCols: PropType<VVirtualListRenderItemWithCols>;
items: {
type: PropType<ItemData[]>;
default: () => never[];
};
itemSize: {
type: NumberConstructor;
required: true;
};
itemResizable: BooleanConstructor;
itemsStyle: PropType<string | CSSProperties>;
visibleItemsTag: {
type: PropType<string | object>;
default: string;
};
visibleItemsProps: ObjectConstructor;
ignoreItemResize: BooleanConstructor;
onScroll: PropType<(event: Event) => void>;
onWheel: PropType<(event: WheelEvent) => void>;
onResize: PropType<(entry: ResizeObserverEntry) => void>;
defaultScrollKey: (StringConstructor | NumberConstructor)[];
defaultScrollIndex: NumberConstructor;
keyField: {
type: StringConstructor;
default: string;
};
paddingTop: {
type: (StringConstructor | NumberConstructor)[];
default: number;
};
paddingBottom: {
type: (StringConstructor | NumberConstructor)[];
default: number;
};
}>>, {
columns: VVirtualListColumn[];
showScrollbar: boolean;
items: ItemData[];
itemResizable: boolean;
visibleItemsTag: string | object;
ignoreItemResize: boolean;
keyField: string;
paddingTop: string | number;
paddingBottom: string | number;
}, {}>;
export default _default;

View File

@@ -0,0 +1,482 @@
/* eslint-disable no-void */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
import { mergeProps, computed, defineComponent, ref, onMounted, h, onActivated, onDeactivated, toRef } from 'vue';
import { beforeNextFrameOnce, depx, pxfy } from 'seemly';
import { useMemo } from 'vooks';
import { useSsrAdapter } from '@css-render/vue3-ssr';
import VResizeObserver from '../../resize-observer/src/VResizeObserver';
import { c, cssrAnchorMetaName, FinweckTree } from '../../shared';
import { ensureMaybeTouch, ensureWheelScale } from './config';
import { setupXScroll } from './xScroll';
import { VirtualListRow } from './VirtualListRow';
const styles = c('.v-vl', {
maxHeight: 'inherit',
height: '100%',
overflow: 'auto',
minWidth: '1px' // a zero width container won't be scrollable
}, [
c('&:not(.v-vl--show-scrollbar)', {
scrollbarWidth: 'none'
}, [
c('&::-webkit-scrollbar, &::-webkit-scrollbar-track-piece, &::-webkit-scrollbar-thumb', {
width: 0,
height: 0,
display: 'none'
})
])
]);
export default defineComponent({
name: 'VirtualList',
inheritAttrs: false,
props: {
showScrollbar: {
type: Boolean,
default: true
},
columns: {
type: Array,
default: () => []
},
renderCol: Function,
renderItemWithCols: Function,
items: {
type: Array,
default: () => []
},
// it is suppose to be the min height
itemSize: {
type: Number,
required: true
},
itemResizable: Boolean,
itemsStyle: [String, Object],
visibleItemsTag: {
type: [String, Object],
default: 'div'
},
visibleItemsProps: Object,
ignoreItemResize: Boolean,
onScroll: Function,
onWheel: Function,
onResize: Function,
defaultScrollKey: [Number, String],
defaultScrollIndex: Number,
keyField: {
type: String,
default: 'key'
},
// Whether it is a good API?
// ResizeObserver + footer & header is not enough.
// Too complex for simple case
paddingTop: {
type: [Number, String],
default: 0
},
paddingBottom: {
type: [Number, String],
default: 0
}
},
setup(props) {
const ssrAdapter = useSsrAdapter();
styles.mount({
id: 'vueuc/virtual-list',
head: true,
anchorMetaName: cssrAnchorMetaName,
ssr: ssrAdapter
});
onMounted(() => {
const { defaultScrollIndex, defaultScrollKey } = props;
if (defaultScrollIndex !== undefined && defaultScrollIndex !== null) {
scrollTo({ index: defaultScrollIndex });
}
else if (defaultScrollKey !== undefined && defaultScrollKey !== null) {
scrollTo({ key: defaultScrollKey });
}
});
let isDeactivated = false;
let activateStateInitialized = false;
onActivated(() => {
isDeactivated = false;
if (!activateStateInitialized) {
activateStateInitialized = true;
return;
}
// remount
scrollTo({ top: scrollTopRef.value, left: scrollLeftRef.value });
});
onDeactivated(() => {
isDeactivated = true;
if (!activateStateInitialized) {
activateStateInitialized = true;
}
});
const totalWidthRef = useMemo(() => {
if (props.renderCol == null && props.renderItemWithCols == null) {
return undefined;
}
if (props.columns.length === 0)
return undefined;
let width = 0;
props.columns.forEach((column) => {
width += column.width;
});
return width;
});
const keyIndexMapRef = computed(() => {
const map = new Map();
const { keyField } = props;
props.items.forEach((item, index) => {
map.set(item[keyField], index);
});
return map;
});
const { scrollLeftRef, listWidthRef } = setupXScroll({
columnsRef: toRef(props, 'columns'),
renderColRef: toRef(props, 'renderCol'),
renderItemWithColsRef: toRef(props, 'renderItemWithCols')
});
const listElRef = ref(null);
const listHeightRef = ref(undefined);
const keyToHeightOffset = new Map();
const finweckTreeRef = computed(() => {
const { items, itemSize, keyField } = props;
const ft = new FinweckTree(items.length, itemSize);
items.forEach((item, index) => {
const key = item[keyField];
const heightOffset = keyToHeightOffset.get(key);
if (heightOffset !== undefined) {
ft.add(index, heightOffset);
}
});
return ft;
});
const finweckTreeUpdateTrigger = ref(0);
const scrollTopRef = ref(0);
const startIndexRef = useMemo(() => {
return Math.max(finweckTreeRef.value.getBound(scrollTopRef.value - depx(props.paddingTop)) - 1, 0);
});
const viewportItemsRef = computed(() => {
const { value: listHeight } = listHeightRef;
if (listHeight === undefined)
return [];
const { items, itemSize } = props;
const startIndex = startIndexRef.value;
const endIndex = Math.min(startIndex + Math.ceil(listHeight / itemSize + 1), items.length - 1);
const viewportItems = [];
for (let i = startIndex; i <= endIndex; ++i) {
viewportItems.push(items[i]);
}
return viewportItems;
});
const scrollTo = (options, y) => {
if (typeof options === 'number') {
scrollToPosition(options, y, 'auto');
return;
}
const { left, top, index, key, position, behavior, debounce = true } = options;
if (left !== undefined || top !== undefined) {
scrollToPosition(left, top, behavior);
}
else if (index !== undefined) {
scrollToIndex(index, behavior, debounce);
}
else if (key !== undefined) {
const toIndex = keyIndexMapRef.value.get(key);
if (toIndex !== undefined)
scrollToIndex(toIndex, behavior, debounce);
}
else if (position === 'bottom') {
scrollToPosition(0, Number.MAX_SAFE_INTEGER, behavior);
}
else if (position === 'top') {
scrollToPosition(0, 0, behavior);
}
};
let anchorIndex;
let anchorTimerId = null;
function scrollToIndex(index, behavior, debounce) {
const { value: ft } = finweckTreeRef;
const targetTop = ft.sum(index) + depx(props.paddingTop);
if (!debounce) {
listElRef.value.scrollTo({
left: 0,
top: targetTop,
behavior
});
}
else {
anchorIndex = index;
if (anchorTimerId !== null) {
window.clearTimeout(anchorTimerId);
}
anchorTimerId = window.setTimeout(() => {
anchorIndex = undefined;
anchorTimerId = null;
}, 16); // use 0 ms may be ealier than resize callback...
const { scrollTop, offsetHeight } = listElRef.value;
if (targetTop > scrollTop) {
const itemSize = ft.get(index);
if (targetTop + itemSize <= scrollTop + offsetHeight) {
// do nothing
}
else {
listElRef.value.scrollTo({
left: 0,
top: targetTop + itemSize - offsetHeight,
behavior
});
}
}
else {
listElRef.value.scrollTo({
left: 0,
top: targetTop,
behavior
});
}
}
}
function scrollToPosition(left, top, behavior) {
listElRef.value.scrollTo({
left,
top,
behavior
});
}
function handleItemResize(key, entry) {
var _a, _b, _c;
if (isDeactivated)
return;
if (props.ignoreItemResize)
return;
if (isHideByVShow(entry.target))
return;
const { value: ft } = finweckTreeRef;
const index = keyIndexMapRef.value.get(key);
const previousHeight = ft.get(index);
const height = (_c = (_b = (_a = entry.borderBoxSize) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.blockSize) !== null && _c !== void 0 ? _c : entry.contentRect.height;
if (height === previousHeight)
return;
// height offset based on itemSize
// used when rebuild the finweck tree
const offset = height - props.itemSize;
if (offset === 0) {
keyToHeightOffset.delete(key);
}
else {
keyToHeightOffset.set(key, height - props.itemSize);
}
// delta height based on finweck tree data
const delta = height - previousHeight;
if (delta === 0)
return;
ft.add(index, delta);
const listEl = listElRef.value;
if (listEl != null) {
if (anchorIndex === undefined) {
const previousHeightSum = ft.sum(index);
if (listEl.scrollTop > previousHeightSum) {
listEl.scrollBy(0, delta);
}
}
else {
if (index < anchorIndex) {
listEl.scrollBy(0, delta);
}
else if (index === anchorIndex) {
const previousHeightSum = ft.sum(index);
if (height + previousHeightSum >
// Note, listEl shouldn't have border, nor offsetHeight won't be
// correct
listEl.scrollTop + listEl.offsetHeight) {
listEl.scrollBy(0, delta);
}
}
}
syncViewport();
}
finweckTreeUpdateTrigger.value++;
}
const mayUseWheel = !ensureMaybeTouch();
let wheelCatched = false;
function handleListScroll(e) {
var _a;
(_a = props.onScroll) === null || _a === void 0 ? void 0 : _a.call(props, e);
if (!mayUseWheel || !wheelCatched) {
syncViewport();
}
}
function handleListWheel(e) {
var _a;
(_a = props.onWheel) === null || _a === void 0 ? void 0 : _a.call(props, e);
if (mayUseWheel) {
const listEl = listElRef.value;
if (listEl != null) {
if (e.deltaX === 0) {
if (listEl.scrollTop === 0 && e.deltaY <= 0) {
return;
}
if (listEl.scrollTop + listEl.offsetHeight >= listEl.scrollHeight &&
e.deltaY >= 0) {
return;
}
}
e.preventDefault();
listEl.scrollTop += e.deltaY / ensureWheelScale();
listEl.scrollLeft += e.deltaX / ensureWheelScale();
syncViewport();
wheelCatched = true;
beforeNextFrameOnce(() => {
wheelCatched = false;
});
}
}
}
function handleListResize(entry) {
if (isDeactivated)
return;
// List is HTMLElement
if (isHideByVShow(entry.target))
return;
// If height is same, return
if (props.renderCol == null && props.renderItemWithCols == null) {
if (entry.contentRect.height === listHeightRef.value)
return;
}
else {
if (entry.contentRect.height === listHeightRef.value &&
entry.contentRect.width === listWidthRef.value) {
return;
}
}
listHeightRef.value = entry.contentRect.height;
listWidthRef.value = entry.contentRect.width;
const { onResize } = props;
if (onResize !== undefined)
onResize(entry);
}
function syncViewport() {
const { value: listEl } = listElRef;
// sometime ref el can be null
// https://github.com/TuSimple/naive-ui/issues/811
if (listEl == null)
return;
scrollTopRef.value = listEl.scrollTop;
scrollLeftRef.value = listEl.scrollLeft;
}
function isHideByVShow(el) {
let cursor = el;
while (cursor !== null) {
if (cursor.style.display === 'none')
return true;
cursor = cursor.parentElement;
}
return false;
}
return {
listHeight: listHeightRef,
listStyle: {
overflow: 'auto'
},
keyToIndex: keyIndexMapRef,
itemsStyle: computed(() => {
const { itemResizable } = props;
const height = pxfy(finweckTreeRef.value.sum());
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
finweckTreeUpdateTrigger.value;
return [
props.itemsStyle,
{
boxSizing: 'content-box',
width: pxfy(totalWidthRef.value),
height: itemResizable ? '' : height,
minHeight: itemResizable ? height : '',
paddingTop: pxfy(props.paddingTop),
paddingBottom: pxfy(props.paddingBottom)
}
];
}),
visibleItemsStyle: computed(() => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
finweckTreeUpdateTrigger.value;
return {
transform: `translateY(${pxfy(finweckTreeRef.value.sum(startIndexRef.value))})`
};
}),
viewportItems: viewportItemsRef,
listElRef,
itemsElRef: ref(null),
scrollTo,
handleListResize,
handleListScroll,
handleListWheel,
handleItemResize
};
},
render() {
const { itemResizable, keyField, keyToIndex, visibleItemsTag } = this;
return h(VResizeObserver, {
onResize: this.handleListResize
}, {
default: () => {
var _a, _b;
return h('div', mergeProps(this.$attrs, {
class: ['v-vl', this.showScrollbar && 'v-vl--show-scrollbar'],
onScroll: this.handleListScroll,
onWheel: this.handleListWheel,
ref: 'listElRef'
}), [
this.items.length !== 0
? h('div', {
ref: 'itemsElRef',
class: 'v-vl-items',
style: this.itemsStyle
}, [
h(visibleItemsTag, Object.assign({
class: 'v-vl-visible-items',
style: this.visibleItemsStyle
}, this.visibleItemsProps), {
default: () => {
const { renderCol, renderItemWithCols } = this;
return this.viewportItems.map((item) => {
const key = item[keyField];
const index = keyToIndex.get(key);
const renderedCols = renderCol != null
? h(VirtualListRow, {
index,
item
})
: undefined;
const renderedItemWithCols = renderItemWithCols != null
? h(VirtualListRow, {
index,
item
})
: undefined;
const itemVNode = this.$slots.default({
item,
renderedCols,
renderedItemWithCols,
index
})[0];
if (itemResizable) {
return h(VResizeObserver, {
key,
onResize: (entry) => this.handleItemResize(key, entry)
}, {
default: () => itemVNode
});
}
itemVNode.key = key;
return itemVNode;
});
}
})
])
: (_b = (_a = this.$slots).empty) === null || _b === void 0 ? void 0 : _b.call(_a)
]);
}
});
}
});

View File

@@ -0,0 +1,28 @@
import { PropType } from 'vue';
import { ItemData } from './type';
export declare const VirtualListRow: import("vue").DefineComponent<{
index: {
type: NumberConstructor;
required: true;
};
item: {
type: PropType<ItemData>;
required: true;
};
}, {
startIndex: import("vue").ComputedRef<number>;
endIndex: import("vue").ComputedRef<number>;
columns: import("vue").Ref<import("./type").VVirtualListColumn[]>;
renderCol: import("vue").Ref<import("./type").VVirtualListRenderCol | undefined>;
renderItemWithCols: import("vue").Ref<import("./type").VVirtualListRenderItemWithCols | undefined>;
getLeft: (index: number) => number;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
index: {
type: NumberConstructor;
required: true;
};
item: {
type: PropType<ItemData>;
required: true;
};
}>>, {}, {}>;

View File

@@ -0,0 +1,47 @@
import { defineComponent, inject } from 'vue';
import { xScrollInjextionKey } from './context';
export const VirtualListRow = defineComponent({
name: 'VirtualListRow',
props: {
index: { type: Number, required: true },
item: {
type: Object,
required: true
}
},
setup() {
const { startIndexRef, endIndexRef, columnsRef, getLeft, renderColRef, renderItemWithColsRef } =
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
inject(xScrollInjextionKey);
return {
startIndex: startIndexRef,
endIndex: endIndexRef,
columns: columnsRef,
renderCol: renderColRef,
renderItemWithCols: renderItemWithColsRef,
getLeft
};
},
render() {
const { startIndex, endIndex, columns, renderCol, renderItemWithCols, getLeft, item } = this;
if (renderItemWithCols != null) {
return renderItemWithCols({
itemIndex: this.index,
startColIndex: startIndex,
endColIndex: endIndex,
allColumns: columns,
item,
getLeft
});
}
if (renderCol != null) {
const items = [];
for (let i = startIndex; i <= endIndex; ++i) {
const column = columns[i];
items.push(renderCol({ column, left: getLeft(i), item }));
}
return items;
}
return null;
}
});

View File

@@ -0,0 +1,2 @@
export declare function ensureMaybeTouch(): boolean;
export declare function ensureWheelScale(): number;

View File

@@ -0,0 +1,23 @@
let maybeTouch;
export function ensureMaybeTouch() {
if (typeof document === 'undefined')
return false;
if (maybeTouch === undefined) {
if ('matchMedia' in window) {
maybeTouch = window.matchMedia('(pointer:coarse)').matches;
}
else {
maybeTouch = false;
}
}
return maybeTouch;
}
let wheelScale;
export function ensureWheelScale() {
if (typeof document === 'undefined')
return 1;
if (wheelScale === undefined) {
wheelScale = 'chrome' in window ? window.devicePixelRatio : 1;
}
return wheelScale;
}

View File

@@ -0,0 +1,10 @@
import { ComputedRef, InjectionKey, Ref } from 'vue';
import { VVirtualListColumn, VVirtualListRenderCol, VVirtualListRenderItemWithCols } from './type';
export declare const xScrollInjextionKey: InjectionKey<{
startIndexRef: ComputedRef<number>;
endIndexRef: ComputedRef<number>;
columnsRef: Ref<VVirtualListColumn[]>;
renderColRef: Ref<VVirtualListRenderCol | undefined>;
renderItemWithColsRef: Ref<VVirtualListRenderItemWithCols | undefined>;
getLeft: (index: number) => number;
}>;

View File

@@ -0,0 +1 @@
export const xScrollInjextionKey = 'VVirtualListXScroll';

View File

@@ -0,0 +1,3 @@
export { default } from './VirtualList';
export type { VirtualListInst, ScrollTo as VirtualListScrollTo } from './VirtualList';
export type { VScrollToOptions as VirtualListScrollToOptions, ItemData as VirtualListItemData, VVirtualListColumn, VVirtualListRenderCol, VVirtualListRenderItemWithCols } from './type';

View File

@@ -0,0 +1 @@
export { default } from './VirtualList';

View File

@@ -0,0 +1,25 @@
import { VNodeChild } from 'vue';
export declare type ItemData = Record<string, any>;
export interface VScrollToOptions extends ScrollToOptions {
index?: number;
key?: number | string;
position?: 'top' | 'bottom';
debounce?: boolean;
}
export interface VVirtualListColumn extends Record<string, any> {
key?: number | string;
width: number;
}
export declare type VVirtualListRenderCol = (props: {
item: ItemData;
column: VVirtualListColumn;
left: number;
}) => VNodeChild;
export declare type VVirtualListRenderItemWithCols = (props: {
itemIndex: number;
startColIndex: number;
endColIndex: number;
allColumns: VVirtualListColumn[];
item: ItemData;
getLeft: (index: number) => number;
}) => VNodeChild;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,10 @@
import { Ref } from 'vue';
import type { VVirtualListColumn, VVirtualListRenderCol, VVirtualListRenderItemWithCols } from './type';
export declare function setupXScroll({ columnsRef, renderColRef, renderItemWithColsRef }: {
columnsRef: Ref<VVirtualListColumn[]>;
renderColRef: Ref<VVirtualListRenderCol | undefined>;
renderItemWithColsRef: Ref<VVirtualListRenderItemWithCols | undefined>;
}): {
listWidthRef: Ref<number>;
scrollLeftRef: Ref<number>;
};

View File

@@ -0,0 +1,58 @@
import { computed, provide, ref } from 'vue';
import { useMemo } from 'vooks';
import { FinweckTree } from '../../shared';
import { xScrollInjextionKey } from './context';
export function setupXScroll({ columnsRef, renderColRef, renderItemWithColsRef }) {
const listWidthRef = ref(0);
const scrollLeftRef = ref(0);
const xFinweckTreeRef = computed(() => {
const columns = columnsRef.value;
if (columns.length === 0) {
return null;
}
const ft = new FinweckTree(columns.length, 0);
columns.forEach((column, index) => {
ft.add(index, column.width);
});
return ft;
});
const startIndexRef = useMemo(() => {
const xFinweckTree = xFinweckTreeRef.value;
if (xFinweckTree !== null) {
return Math.max(xFinweckTree.getBound(scrollLeftRef.value) - 1, 0);
}
else {
return 0;
}
});
const getLeft = (index) => {
const xFinweckTree = xFinweckTreeRef.value;
if (xFinweckTree !== null) {
return xFinweckTree.sum(index);
}
else {
return 0;
}
};
const endIndexRef = useMemo(() => {
const xFinweckTree = xFinweckTreeRef.value;
if (xFinweckTree !== null) {
return Math.min(xFinweckTree.getBound(scrollLeftRef.value + listWidthRef.value) + 1, columnsRef.value.length - 1);
}
else {
return 0;
}
});
provide(xScrollInjextionKey, {
startIndexRef,
endIndexRef,
columnsRef,
renderColRef,
renderItemWithColsRef,
getLeft
});
return {
listWidthRef,
scrollLeftRef
};
}

View File

@@ -0,0 +1,19 @@
import { PropType } from 'vue';
export type { VXScrollInst } from './interface';
declare const _default: import("vue").DefineComponent<{
disabled: BooleanConstructor;
onScroll: PropType<(e: Event) => void>;
}, {
scrollTo: {
(options?: ScrollToOptions | undefined): void;
(x: number, y: number): void;
};
selfRef: import("vue").Ref<HTMLElement | null>;
handleWheel: (e: WheelEvent) => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
disabled: BooleanConstructor;
onScroll: PropType<(e: Event) => void>;
}>>, {
disabled: boolean;
}, {}>;
export default _default;

View File

@@ -0,0 +1,53 @@
import { useSsrAdapter } from '@css-render/vue3-ssr';
import { defineComponent, h, ref } from 'vue';
import { c, cssrAnchorMetaName } from '../../shared';
const styles = c('.v-x-scroll', {
overflow: 'auto',
scrollbarWidth: 'none'
}, [
c('&::-webkit-scrollbar', {
width: 0,
height: 0
})
]);
export default defineComponent({
name: 'XScroll',
props: {
disabled: Boolean,
onScroll: Function
},
setup() {
const selfRef = ref(null);
function handleWheel(e) {
const preventYWheel = e.currentTarget.offsetWidth <
e.currentTarget.scrollWidth;
if (!preventYWheel || e.deltaY === 0)
return;
e.currentTarget.scrollLeft += e.deltaY + e.deltaX;
e.preventDefault();
}
const ssrAdapter = useSsrAdapter();
styles.mount({
id: 'vueuc/x-scroll',
head: true,
anchorMetaName: cssrAnchorMetaName,
ssr: ssrAdapter
});
const exposedMethods = {
scrollTo(...args) {
var _a;
(_a = selfRef.value) === null || _a === void 0 ? void 0 : _a.scrollTo(...args);
}
};
return Object.assign({ selfRef,
handleWheel }, exposedMethods);
},
render() {
return h('div', {
ref: 'selfRef',
onScroll: this.onScroll,
onWheel: this.disabled ? undefined : this.handleWheel,
class: 'v-x-scroll'
}, this.$slots);
}
});

View File

@@ -0,0 +1,3 @@
export interface VXScrollInst {
scrollTo: HTMLElement['scrollTo'];
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,24 @@
declare const Binder: import("vue").DefineComponent<{
syncTargetWithParent: BooleanConstructor;
syncTarget: {
type: BooleanConstructor;
default: boolean;
};
}, {
targetRef: import("vue").Ref<HTMLElement | null>;
setTargetRef: (el: HTMLElement | null) => void;
addScrollListener: (listener: () => void) => void;
removeScrollListener: (listener: () => void) => void;
addResizeListener: (listener: () => void) => void;
removeResizeListener: (listener: () => void) => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
syncTargetWithParent: BooleanConstructor;
syncTarget: {
type: BooleanConstructor;
default: boolean;
};
}>>, {
syncTargetWithParent: boolean;
syncTarget: boolean;
}, {}>;
export default Binder;

View File

@@ -0,0 +1,137 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const vue_1 = require("vue");
const seemly_1 = require("seemly");
const evtd_1 = require("evtd");
const v_node_1 = require("../../shared/v-node");
const utils_1 = require("./utils");
const Binder = (0, vue_1.defineComponent)({
name: 'Binder',
props: {
syncTargetWithParent: Boolean,
syncTarget: {
type: Boolean,
default: true
}
},
setup(props) {
var _a;
(0, vue_1.provide)('VBinder', (_a = (0, vue_1.getCurrentInstance)()) === null || _a === void 0 ? void 0 : _a.proxy);
const VBinder = (0, vue_1.inject)('VBinder', null);
const targetRef = (0, vue_1.ref)(null);
/**
* If there's no nested vbinder, we can simply set the target ref.
*
* However, when it comes to:
* <VBinder> <- syncTarget = false
*
* Should hold target DOM ref, but can't get it directly from
* its VTarget. So if there are nested VBinder, we should:
* 1. Stop setting target DOM from level-1 VTarget
* 2. Set target DOM from level-2 VTarget
* For (1), we need `syncTarget` to `false`
* For (2), we need to set `syncTargetWithParent` to `true` on
* level-2 VBinder
* <VTarget>
* <VBinder> <- syncTargetWithParent = true
* <VTarget>target</VTarget>
* </VBinder>
* <VFollower>
* content1
* </VFollower>
* </VTarget>
* <VFollower>
* content2
* </VFollower>
* </VBinder>
*/
const setTargetRef = (el) => {
targetRef.value = el;
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (VBinder && props.syncTargetWithParent) {
VBinder.setTargetRef(el);
}
};
// scroll related
let scrollableNodes = [];
const ensureScrollListener = () => {
let cursor = targetRef.value;
while (true) {
cursor = (0, utils_1.getScrollParent)(cursor);
if (cursor === null)
break;
scrollableNodes.push(cursor);
}
for (const el of scrollableNodes) {
(0, evtd_1.on)('scroll', el, onScroll, true);
}
};
const removeScrollListeners = () => {
for (const el of scrollableNodes) {
(0, evtd_1.off)('scroll', el, onScroll, true);
}
scrollableNodes = [];
};
const followerScrollListeners = new Set();
const addScrollListener = (listener) => {
if (followerScrollListeners.size === 0) {
ensureScrollListener();
}
if (!followerScrollListeners.has(listener)) {
followerScrollListeners.add(listener);
}
};
const removeScrollListener = (listener) => {
if (followerScrollListeners.has(listener)) {
followerScrollListeners.delete(listener);
}
if (followerScrollListeners.size === 0) {
removeScrollListeners();
}
};
const onScroll = () => {
(0, seemly_1.beforeNextFrameOnce)(onScrollRaf);
};
const onScrollRaf = () => {
followerScrollListeners.forEach((listener) => listener());
};
// resize related
const followerResizeListeners = new Set();
const addResizeListener = (listener) => {
if (followerResizeListeners.size === 0) {
(0, evtd_1.on)('resize', window, onResize);
}
if (!followerResizeListeners.has(listener)) {
followerResizeListeners.add(listener);
}
};
const removeResizeListener = (listener) => {
if (followerResizeListeners.has(listener)) {
followerResizeListeners.delete(listener);
}
if (followerResizeListeners.size === 0) {
(0, evtd_1.off)('resize', window, onResize);
}
};
const onResize = () => {
followerResizeListeners.forEach((listener) => listener());
};
(0, vue_1.onBeforeUnmount)(() => {
(0, evtd_1.off)('resize', window, onResize);
removeScrollListeners();
});
return {
targetRef,
setTargetRef,
addScrollListener,
removeScrollListener,
addResizeListener,
removeResizeListener
};
},
render() {
return (0, v_node_1.getSlot)('binder', this.$slots);
}
});
exports.default = Binder;

View File

@@ -0,0 +1,88 @@
import { PropType } from 'vue';
import { BinderInstance, Placement } from './interface';
export interface FollowerInst {
syncPosition: () => void;
}
declare const _default: import("vue").DefineComponent<{
show: BooleanConstructor;
enabled: {
type: PropType<boolean | undefined>;
default: undefined;
};
placement: {
type: PropType<Placement>;
default: string;
};
syncTrigger: {
type: PropType<("resize" | "scroll")[]>;
default: string[];
};
to: PropType<string | HTMLElement>;
flip: {
type: BooleanConstructor;
default: boolean;
};
internalShift: BooleanConstructor;
x: NumberConstructor;
y: NumberConstructor;
width: PropType<string>;
minWidth: PropType<string>;
containerClass: StringConstructor;
teleportDisabled: BooleanConstructor;
zindexable: {
type: BooleanConstructor;
default: boolean;
};
zIndex: NumberConstructor;
overlap: BooleanConstructor;
}, {
VBinder: BinderInstance;
mergedEnabled: import("vue").ComputedRef<boolean>;
offsetContainerRef: import("vue").Ref<HTMLElement | null>;
followerRef: import("vue").Ref<HTMLElement | null>;
mergedTo: import("vue").ComputedRef<string | HTMLElement | undefined>;
syncPosition: () => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
show: BooleanConstructor;
enabled: {
type: PropType<boolean | undefined>;
default: undefined;
};
placement: {
type: PropType<Placement>;
default: string;
};
syncTrigger: {
type: PropType<("resize" | "scroll")[]>;
default: string[];
};
to: PropType<string | HTMLElement>;
flip: {
type: BooleanConstructor;
default: boolean;
};
internalShift: BooleanConstructor;
x: NumberConstructor;
y: NumberConstructor;
width: PropType<string>;
minWidth: PropType<string>;
containerClass: StringConstructor;
teleportDisabled: BooleanConstructor;
zindexable: {
type: BooleanConstructor;
default: boolean;
};
zIndex: NumberConstructor;
overlap: BooleanConstructor;
}>>, {
show: boolean;
enabled: boolean | undefined;
placement: Placement;
syncTrigger: ("resize" | "scroll")[];
flip: boolean;
internalShift: boolean;
teleportDisabled: boolean;
zindexable: boolean;
overlap: boolean;
}, {}>;
export default _default;

View File

@@ -0,0 +1,266 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const vue_1 = require("vue");
const vdirs_1 = require("vdirs");
const vooks_1 = require("vooks");
const vue3_ssr_1 = require("@css-render/vue3-ssr");
const shared_1 = require("../../shared");
const index_1 = __importDefault(require("../../lazy-teleport/src/index"));
const get_placement_style_1 = require("./get-placement-style");
const utils_1 = require("./utils");
const style = (0, shared_1.c)([
(0, shared_1.c)('.v-binder-follower-container', {
position: 'absolute',
left: '0',
right: '0',
top: '0',
height: '0',
pointerEvents: 'none',
zIndex: 'auto'
}),
(0, shared_1.c)('.v-binder-follower-content', {
position: 'absolute',
zIndex: 'auto'
}, [
(0, shared_1.c)('> *', {
pointerEvents: 'all'
})
])
]);
exports.default = (0, vue_1.defineComponent)({
name: 'Follower',
inheritAttrs: false,
props: {
show: Boolean,
enabled: {
type: Boolean,
default: undefined
},
placement: {
type: String,
default: 'bottom'
},
syncTrigger: {
type: Array,
default: ['resize', 'scroll']
},
to: [String, Object],
flip: {
type: Boolean,
default: true
},
internalShift: Boolean,
x: Number,
y: Number,
width: String,
minWidth: String,
containerClass: String,
teleportDisabled: Boolean,
zindexable: {
type: Boolean,
default: true
},
zIndex: Number,
overlap: Boolean
},
setup(props) {
const VBinder = (0, vue_1.inject)('VBinder');
const mergedEnabledRef = (0, vooks_1.useMemo)(() => {
return props.enabled !== undefined ? props.enabled : props.show;
});
const followerRef = (0, vue_1.ref)(null);
const offsetContainerRef = (0, vue_1.ref)(null);
const ensureListeners = () => {
const { syncTrigger } = props;
if (syncTrigger.includes('scroll')) {
VBinder.addScrollListener(syncPosition);
}
if (syncTrigger.includes('resize')) {
VBinder.addResizeListener(syncPosition);
}
};
const removeListeners = () => {
VBinder.removeScrollListener(syncPosition);
VBinder.removeResizeListener(syncPosition);
};
(0, vue_1.onMounted)(() => {
if (mergedEnabledRef.value) {
syncPosition();
ensureListeners();
}
});
const ssrAdapter = (0, vue3_ssr_1.useSsrAdapter)();
style.mount({
id: 'vueuc/binder',
head: true,
anchorMetaName: shared_1.cssrAnchorMetaName,
ssr: ssrAdapter
});
(0, vue_1.onBeforeUnmount)(() => {
removeListeners();
});
(0, vooks_1.onFontsReady)(() => {
if (mergedEnabledRef.value) {
syncPosition();
}
});
const syncPosition = () => {
if (!mergedEnabledRef.value) {
return;
}
const follower = followerRef.value;
// sometimes watched props change before its dom is ready
// for example: show=false, x=undefined, y=undefined
// show=true, x=0, y=0
// will cause error
// I may optimize the watch start point later
if (follower === null)
return;
const target = VBinder.targetRef;
const { x, y, overlap } = props;
const targetRect = x !== undefined && y !== undefined
? (0, utils_1.getPointRect)(x, y)
: (0, utils_1.getRect)(target);
follower.style.setProperty('--v-target-width', `${Math.round(targetRect.width)}px`);
follower.style.setProperty('--v-target-height', `${Math.round(targetRect.height)}px`);
const { width, minWidth, placement, internalShift, flip } = props;
follower.setAttribute('v-placement', placement);
if (overlap) {
follower.setAttribute('v-overlap', '');
}
else {
follower.removeAttribute('v-overlap');
}
const { style } = follower;
if (width === 'target') {
style.width = `${targetRect.width}px`;
}
else if (width !== undefined) {
style.width = width;
}
else {
style.width = '';
}
if (minWidth === 'target') {
style.minWidth = `${targetRect.width}px`;
}
else if (minWidth !== undefined) {
style.minWidth = minWidth;
}
else {
style.minWidth = '';
}
const followerRect = (0, utils_1.getRect)(follower);
const offsetContainerRect = (0, utils_1.getRect)(offsetContainerRef.value);
const { left: offsetLeftToStandardPlacement, top: offsetTopToStandardPlacement, placement: properPlacement } = (0, get_placement_style_1.getPlacementAndOffsetOfFollower)(placement, targetRect, followerRect, internalShift, flip, overlap);
const properTransformOrigin = (0, get_placement_style_1.getProperTransformOrigin)(properPlacement, overlap);
const { left, top, transform } = (0, get_placement_style_1.getOffset)(properPlacement, offsetContainerRect, targetRect, offsetTopToStandardPlacement, offsetLeftToStandardPlacement, overlap);
// we assume that the content size doesn't change after flip,
// nor we need to make sync logic more complex
follower.setAttribute('v-placement', properPlacement);
follower.style.setProperty('--v-offset-left', `${Math.round(offsetLeftToStandardPlacement)}px`);
follower.style.setProperty('--v-offset-top', `${Math.round(offsetTopToStandardPlacement)}px`);
follower.style.transform = `translateX(${left}) translateY(${top}) ${transform}`;
follower.style.setProperty('--v-transform-origin', properTransformOrigin);
follower.style.transformOrigin = properTransformOrigin;
};
(0, vue_1.watch)(mergedEnabledRef, (value) => {
if (value) {
ensureListeners();
syncOnNextTick();
}
else {
removeListeners();
}
});
const syncOnNextTick = () => {
(0, vue_1.nextTick)()
.then(syncPosition)
.catch((e) => console.error(e));
};
[
'placement',
'x',
'y',
'internalShift',
'flip',
'width',
'overlap',
'minWidth'
].forEach((prop) => {
(0, vue_1.watch)((0, vue_1.toRef)(props, prop), syncPosition);
});
['teleportDisabled'].forEach((prop) => {
(0, vue_1.watch)((0, vue_1.toRef)(props, prop), syncOnNextTick);
});
(0, vue_1.watch)((0, vue_1.toRef)(props, 'syncTrigger'), (value) => {
if (!value.includes('resize')) {
VBinder.removeResizeListener(syncPosition);
}
else {
VBinder.addResizeListener(syncPosition);
}
if (!value.includes('scroll')) {
VBinder.removeScrollListener(syncPosition);
}
else {
VBinder.addScrollListener(syncPosition);
}
});
const isMountedRef = (0, vooks_1.useIsMounted)();
const mergedToRef = (0, vooks_1.useMemo)(() => {
const { to } = props;
if (to !== undefined)
return to;
if (isMountedRef.value) {
// TODO: find proper container
return undefined;
}
return undefined;
});
return {
VBinder,
mergedEnabled: mergedEnabledRef,
offsetContainerRef,
followerRef,
mergedTo: mergedToRef,
syncPosition
};
},
render() {
return (0, vue_1.h)(index_1.default, {
show: this.show,
to: this.mergedTo,
disabled: this.teleportDisabled
}, {
default: () => {
var _a, _b;
const vNode = (0, vue_1.h)('div', {
class: ['v-binder-follower-container', this.containerClass],
ref: 'offsetContainerRef'
}, [
(0, vue_1.h)('div', {
class: 'v-binder-follower-content',
ref: 'followerRef'
}, (_b = (_a = this.$slots).default) === null || _b === void 0 ? void 0 : _b.call(_a))
]);
if (this.zindexable) {
return (0, vue_1.withDirectives)(vNode, [
[
vdirs_1.zindexable,
{
enabled: this.mergedEnabled,
zIndex: this.zIndex
}
]
]);
}
return vNode;
}
});
}
});

View File

@@ -0,0 +1,8 @@
declare const _default: import("vue").DefineComponent<{}, {
syncTarget: boolean;
setTargetDirective: {
mounted: (el: HTMLElement | null) => void;
updated: (el: HTMLElement | null) => void;
};
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}, {}>;
export default _default;

View File

@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const vue_1 = require("vue");
const v_node_1 = require("../../shared/v-node");
exports.default = (0, vue_1.defineComponent)({
name: 'Target',
setup() {
const { setTargetRef, syncTarget } = (0, vue_1.inject)('VBinder');
const setTargetDirective = {
mounted: setTargetRef,
updated: setTargetRef
};
return {
syncTarget,
setTargetDirective
};
},
render() {
const { syncTarget, setTargetDirective } = this;
/**
* If you are using VBinder as a child of VBinder, the children wouldn't be
* a valid DOM or component that can be attached to by directive.
* So we won't sync target on those kind of situation and control the
* target sync logic manually.
*/
if (syncTarget) {
return (0, vue_1.withDirectives)((0, v_node_1.getFirstVNode)('follower', this.$slots), [
[setTargetDirective]
]);
}
return (0, v_node_1.getFirstVNode)('follower', this.$slots);
}
});

View File

@@ -0,0 +1,15 @@
import { Placement, Rect, TransformOrigin } from './interface';
interface PlacementAndOffset {
top: number;
left: number;
placement: Placement;
}
export declare function getPlacementAndOffsetOfFollower(placement: Placement, targetRect: Rect, followerRect: Rect, shift: boolean, flip: boolean, overlap: boolean): PlacementAndOffset;
export declare function getProperTransformOrigin(placement: Placement, overlap: boolean): TransformOrigin;
interface PlacementOffset {
top: string;
left: string;
transform: string;
}
export declare function getOffset(placement: Placement, offsetRect: Rect, targetRect: Rect, offsetTopToStandardPlacement: number, offsetLeftToStandardPlacement: number, overlap: boolean): PlacementOffset;
export {};

View File

@@ -0,0 +1,436 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getOffset = exports.getProperTransformOrigin = exports.getPlacementAndOffsetOfFollower = void 0;
const oppositionPositions = {
top: 'bottom',
bottom: 'top',
left: 'right',
right: 'left'
};
const oppositeAligns = {
start: 'end',
center: 'center',
end: 'start'
};
const propToCompare = {
top: 'height',
bottom: 'height',
left: 'width',
right: 'width'
};
const transformOrigins = {
'bottom-start': 'top left',
bottom: 'top center',
'bottom-end': 'top right',
'top-start': 'bottom left',
top: 'bottom center',
'top-end': 'bottom right',
'right-start': 'top left',
right: 'center left',
'right-end': 'bottom left',
'left-start': 'top right',
left: 'center right',
'left-end': 'bottom right'
};
const overlapTransformOrigin = {
'bottom-start': 'bottom left',
bottom: 'bottom center',
'bottom-end': 'bottom right',
'top-start': 'top left',
top: 'top center',
'top-end': 'top right',
'right-start': 'top right',
right: 'center right',
'right-end': 'bottom right',
'left-start': 'top left',
left: 'center left',
'left-end': 'bottom left'
};
const oppositeAlignCssPositionProps = {
'bottom-start': 'right',
'bottom-end': 'left',
'top-start': 'right',
'top-end': 'left',
'right-start': 'bottom',
'right-end': 'top',
'left-start': 'bottom',
'left-end': 'top'
};
const keepOffsetDirection = {
top: true,
bottom: false,
left: true,
right: false // left--
};
const cssPositionToOppositeAlign = {
top: 'end',
bottom: 'start',
left: 'end',
right: 'start'
};
function getPlacementAndOffsetOfFollower(placement, targetRect, followerRect, shift, flip, overlap) {
if (!flip || overlap) {
return { placement: placement, top: 0, left: 0 };
}
const [position, align] = placement.split('-');
let properAlign = align !== null && align !== void 0 ? align : 'center';
let properOffset = {
top: 0,
left: 0
};
const deriveOffset = (oppositeAlignCssSizeProp, alignCssPositionProp, offsetVertically) => {
let left = 0;
let top = 0;
const diff = followerRect[oppositeAlignCssSizeProp] -
targetRect[alignCssPositionProp] -
targetRect[oppositeAlignCssSizeProp];
if (diff > 0 && shift) {
if (offsetVertically) {
// screen border
// |-----------------------------------------|
// | | f | |
// | | o | |
// | | l | |
// | | l |---- |
// | | o |tar | |
// | | w |get | |
// | | e | | |
// | | r |---- |
// | ---- |
// |-----------------------------------------|
top = keepOffsetDirection[alignCssPositionProp] ? diff : -diff;
}
else {
// screen border
// |----------------------------------------|
// | |
// | ---------- |
// | | target | |
// | ----------------------------------
// | | follower |
// | ----------------------------------
// | |
// |----------------------------------------|
left = keepOffsetDirection[alignCssPositionProp] ? diff : -diff;
}
}
return {
left,
top
};
};
const offsetVertically = position === 'left' || position === 'right';
// choose proper placement for non-center align
if (properAlign !== 'center') {
const oppositeAlignCssPositionProp = oppositeAlignCssPositionProps[placement];
const currentAlignCssPositionProp = oppositionPositions[oppositeAlignCssPositionProp];
const oppositeAlignCssSizeProp = propToCompare[oppositeAlignCssPositionProp];
// if follower rect is larger than target rect in align direction
// ----------[ target ]---------|
// ----------[ follower ]
if (followerRect[oppositeAlignCssSizeProp] >
targetRect[oppositeAlignCssSizeProp]) {
if (
// current space is not enough
// ----------[ target ]---------|
// -------[ follower ]
targetRect[oppositeAlignCssPositionProp] +
targetRect[oppositeAlignCssSizeProp] <
followerRect[oppositeAlignCssSizeProp]) {
const followerOverTargetSize = (followerRect[oppositeAlignCssSizeProp] -
targetRect[oppositeAlignCssSizeProp]) /
2;
if (targetRect[oppositeAlignCssPositionProp] < followerOverTargetSize ||
targetRect[currentAlignCssPositionProp] < followerOverTargetSize) {
// opposite align has larger space
// -------[ target ]-----------|
// -------[ follower ]-|
if (targetRect[oppositeAlignCssPositionProp] <
targetRect[currentAlignCssPositionProp]) {
properAlign = oppositeAligns[align];
properOffset = deriveOffset(oppositeAlignCssSizeProp, currentAlignCssPositionProp, offsetVertically);
}
else {
// ----------------[ target ]----|
// --------[ follower ]----|
properOffset = deriveOffset(oppositeAlignCssSizeProp, oppositeAlignCssPositionProp, offsetVertically);
}
}
else {
// 'center' align is better
// ------------[ target ]--------|
// -------[ follower ]--|
properAlign = 'center';
}
}
}
else if (followerRect[oppositeAlignCssSizeProp] <
targetRect[oppositeAlignCssSizeProp]) {
// TODO: maybe center is better
if (targetRect[currentAlignCssPositionProp] < 0 &&
// opposite align has larger space
// ------------[ target ]
// ----------------[follower]
targetRect[oppositeAlignCssPositionProp] >
targetRect[currentAlignCssPositionProp]) {
properAlign = oppositeAligns[align];
}
}
}
else {
const possibleAlternativeAlignCssPositionProp1 = position === 'bottom' || position === 'top' ? 'left' : 'top';
const possibleAlternativeAlignCssPositionProp2 = oppositionPositions[possibleAlternativeAlignCssPositionProp1];
const alternativeAlignCssSizeProp = propToCompare[possibleAlternativeAlignCssPositionProp1];
const followerOverTargetSize = (followerRect[alternativeAlignCssSizeProp] -
targetRect[alternativeAlignCssSizeProp]) /
2;
if (
// center is not enough
// ----------- [ target ]--|
// -------[ follower ]
targetRect[possibleAlternativeAlignCssPositionProp1] <
followerOverTargetSize ||
targetRect[possibleAlternativeAlignCssPositionProp2] <
followerOverTargetSize) {
// alternative 2 position's space is larger
if (targetRect[possibleAlternativeAlignCssPositionProp1] >
targetRect[possibleAlternativeAlignCssPositionProp2]) {
properAlign =
cssPositionToOppositeAlign[possibleAlternativeAlignCssPositionProp1];
properOffset = deriveOffset(alternativeAlignCssSizeProp, possibleAlternativeAlignCssPositionProp1, offsetVertically);
}
else {
// alternative 1 position's space is larger
properAlign =
cssPositionToOppositeAlign[possibleAlternativeAlignCssPositionProp2];
properOffset = deriveOffset(alternativeAlignCssSizeProp, possibleAlternativeAlignCssPositionProp2, offsetVertically);
}
}
}
let properPosition = position;
if (
// space is not enough
targetRect[position] < followerRect[propToCompare[position]] &&
// opposite position's space is larger
targetRect[position] < targetRect[oppositionPositions[position]]) {
properPosition = oppositionPositions[position];
}
return {
placement: properAlign !== 'center'
? `${properPosition}-${properAlign}`
: properPosition,
left: properOffset.left,
top: properOffset.top
};
}
exports.getPlacementAndOffsetOfFollower = getPlacementAndOffsetOfFollower;
function getProperTransformOrigin(placement, overlap) {
if (overlap)
return overlapTransformOrigin[placement];
return transformOrigins[placement];
}
exports.getProperTransformOrigin = getProperTransformOrigin;
// ------------
// | offset |
// | |
// | [target] |
// | |
// ------------
// TODO: refactor it to remove dup logic
function getOffset(placement, offsetRect, targetRect, offsetTopToStandardPlacement, offsetLeftToStandardPlacement, overlap) {
if (overlap) {
switch (placement) {
case 'bottom-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
transform: 'translateY(-100%)'
};
case 'bottom-end':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
transform: 'translateX(-100%) translateY(-100%)'
};
case 'top-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
transform: ''
};
case 'top-end':
return {
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
transform: 'translateX(-100%)'
};
case 'right-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
transform: 'translateX(-100%)'
};
case 'right-end':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
transform: 'translateX(-100%) translateY(-100%)'
};
case 'left-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
transform: ''
};
case 'left-end':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
transform: 'translateY(-100%)'
};
case 'top':
return {
top: `${Math.round(targetRect.top - offsetRect.top)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width / 2)}px`,
transform: 'translateX(-50%)'
};
case 'right':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height / 2)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width)}px`,
transform: 'translateX(-100%) translateY(-50%)'
};
case 'left':
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height / 2)}px`,
left: `${Math.round(targetRect.left - offsetRect.left)}px`,
transform: 'translateY(-50%)'
};
case 'bottom':
default:
return {
top: `${Math.round(targetRect.top - offsetRect.top + targetRect.height)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + targetRect.width / 2)}px`,
transform: 'translateX(-50%) translateY(-100%)'
};
}
}
switch (placement) {
case 'bottom-start':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
transform: ''
};
case 'bottom-end':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width +
offsetLeftToStandardPlacement)}px`,
transform: 'translateX(-100%)'
};
case 'top-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
transform: 'translateY(-100%)'
};
case 'top-end':
return {
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width +
offsetLeftToStandardPlacement)}px`,
transform: 'translateX(-100%) translateY(-100%)'
};
case 'right-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width +
offsetLeftToStandardPlacement)}px`,
transform: ''
};
case 'right-end':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width +
offsetLeftToStandardPlacement)}px`,
transform: 'translateY(-100%)'
};
case 'left-start':
return {
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
transform: 'translateX(-100%)'
};
case 'left-end':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
transform: 'translateX(-100%) translateY(-100%)'
};
case 'top':
return {
top: `${Math.round(targetRect.top - offsetRect.top + offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width / 2 +
offsetLeftToStandardPlacement)}px`,
transform: 'translateY(-100%) translateX(-50%)'
};
case 'right':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height / 2 +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width +
offsetLeftToStandardPlacement)}px`,
transform: 'translateY(-50%)'
};
case 'left':
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height / 2 +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left - offsetRect.left + offsetLeftToStandardPlacement)}px`,
transform: 'translateY(-50%) translateX(-100%)'
};
case 'bottom':
default:
return {
top: `${Math.round(targetRect.top -
offsetRect.top +
targetRect.height +
offsetTopToStandardPlacement)}px`,
left: `${Math.round(targetRect.left -
offsetRect.left +
targetRect.width / 2 +
offsetLeftToStandardPlacement)}px`,
transform: 'translateX(-50%)'
};
}
}
exports.getOffset = getOffset;

View File

@@ -0,0 +1,5 @@
export { default as Binder, default as VBinder } from './Binder';
export { default as Target, default as VTarget } from './Target';
export { default as Follower, default as VFollower } from './Follower';
export type { FollowerInst } from './Follower';
export type { Placement as FollowerPlacement, ExposedBinderInstance as BinderInst } from './interface';

View File

@@ -0,0 +1,15 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.VFollower = exports.Follower = exports.VTarget = exports.Target = exports.VBinder = exports.Binder = void 0;
var Binder_1 = require("./Binder");
Object.defineProperty(exports, "Binder", { enumerable: true, get: function () { return __importDefault(Binder_1).default; } });
Object.defineProperty(exports, "VBinder", { enumerable: true, get: function () { return __importDefault(Binder_1).default; } });
var Target_1 = require("./Target");
Object.defineProperty(exports, "Target", { enumerable: true, get: function () { return __importDefault(Target_1).default; } });
Object.defineProperty(exports, "VTarget", { enumerable: true, get: function () { return __importDefault(Target_1).default; } });
var Follower_1 = require("./Follower");
Object.defineProperty(exports, "Follower", { enumerable: true, get: function () { return __importDefault(Follower_1).default; } });
Object.defineProperty(exports, "VFollower", { enumerable: true, get: function () { return __importDefault(Follower_1).default; } });

View File

@@ -0,0 +1,26 @@
export interface ExposedBinderInstance {
targetRef: HTMLElement | null;
}
export interface BinderInstance extends ExposedBinderInstance {
syncTargetWithParent: boolean;
syncTarget: boolean;
setTargetRef: (el: HTMLElement | null) => void;
addScrollListener: (listener: () => void) => void;
removeScrollListener: (listener: () => void) => void;
addResizeListener: (listener: () => void) => void;
removeResizeListener: (listener: () => void) => void;
}
export declare type Placement = 'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end';
export declare type NonCenterPlacement = 'top-start' | 'top-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end';
export interface Rect {
left: number;
right: number;
top: number;
bottom: number;
width: number;
height: number;
}
export declare type Align = 'start' | 'end' | 'center';
export declare type Position = 'left' | 'right' | 'top' | 'bottom';
export declare type TransformOrigin = 'top left' | 'top center' | 'top right' | 'bottom left' | 'bottom center' | 'bottom right' | 'top left' | 'center left' | 'bottom left' | 'top right' | 'center right' | 'bottom right';
export declare type FlipLevel = 1 | 2;

View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,2 @@
import { Placement } from './interface';
export declare const placements: Placement[];

View File

@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.placements = void 0;
exports.placements = [
'top',
'bottom',
'left',
'right',
'top-start',
'top-end',
'left-start',
'left-end',
'right-start',
'right-end',
'bottom-start',
'bottom-end'
];

View File

@@ -0,0 +1,6 @@
import { Rect } from './interface';
export declare function ensureViewBoundingRect(): DOMRect;
export declare function getPointRect(x: number, y: number): Rect;
export declare function getRect(el: HTMLElement): Rect;
export declare function getParentNode(node: Node): Node | null;
export declare function getScrollParent(node: Node | null): HTMLElement | Document | null;

View File

@@ -0,0 +1,79 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getScrollParent = exports.getParentNode = exports.getRect = exports.getPointRect = exports.ensureViewBoundingRect = void 0;
let viewMeasurer = null;
function ensureViewBoundingRect() {
if (viewMeasurer === null) {
viewMeasurer = document.getElementById('v-binder-view-measurer');
if (viewMeasurer === null) {
viewMeasurer = document.createElement('div');
viewMeasurer.id = 'v-binder-view-measurer';
const { style } = viewMeasurer;
style.position = 'fixed';
style.left = '0';
style.right = '0';
style.top = '0';
style.bottom = '0';
style.pointerEvents = 'none';
style.visibility = 'hidden';
document.body.appendChild(viewMeasurer);
}
}
return viewMeasurer.getBoundingClientRect();
}
exports.ensureViewBoundingRect = ensureViewBoundingRect;
function getPointRect(x, y) {
const viewRect = ensureViewBoundingRect();
return {
top: y,
left: x,
height: 0,
width: 0,
right: viewRect.width - x,
bottom: viewRect.height - y
};
}
exports.getPointRect = getPointRect;
function getRect(el) {
const elRect = el.getBoundingClientRect();
const viewRect = ensureViewBoundingRect();
return {
left: elRect.left - viewRect.left,
top: elRect.top - viewRect.top,
bottom: viewRect.height + viewRect.top - elRect.bottom,
right: viewRect.width + viewRect.left - elRect.right,
width: elRect.width,
height: elRect.height
};
}
exports.getRect = getRect;
function getParentNode(node) {
// document type
if (node.nodeType === 9) {
return null;
}
return node.parentNode;
}
exports.getParentNode = getParentNode;
function getScrollParent(node) {
if (node === null)
return null;
const parentNode = getParentNode(node);
if (parentNode === null) {
return null;
}
// Document
if (parentNode.nodeType === 9) {
return document;
}
// Element
if (parentNode.nodeType === 1) {
// Firefox want us to check `-x` and `-y` variations as well
const { overflow, overflowX, overflowY } = getComputedStyle(parentNode);
if (/(auto|scroll|overlay)/.test(overflow + overflowY + overflowX)) {
return parentNode;
}
}
return getScrollParent(parentNode);
}
exports.getScrollParent = getScrollParent;

View File

@@ -0,0 +1 @@
export { FocusTrap } from './src';

View File

@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FocusTrap = void 0;
var src_1 = require("./src");
Object.defineProperty(exports, "FocusTrap", { enumerable: true, get: function () { return src_1.FocusTrap; } });

View File

@@ -0,0 +1,41 @@
import { PropType } from 'vue';
export declare const FocusTrap: import("vue").DefineComponent<{
disabled: BooleanConstructor;
active: BooleanConstructor;
autoFocus: {
type: BooleanConstructor;
default: boolean;
};
onEsc: PropType<(e: KeyboardEvent) => void>;
initialFocusTo: StringConstructor;
finalFocusTo: StringConstructor;
returnFocusOnDeactivated: {
type: BooleanConstructor;
default: boolean;
};
}, {
focusableStartRef: import("vue").Ref<HTMLElement | null>;
focusableEndRef: import("vue").Ref<HTMLElement | null>;
focusableStyle: string;
handleStartFocus: (e: FocusEvent) => void;
handleEndFocus: (e: FocusEvent) => void;
}, unknown, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
disabled: BooleanConstructor;
active: BooleanConstructor;
autoFocus: {
type: BooleanConstructor;
default: boolean;
};
onEsc: PropType<(e: KeyboardEvent) => void>;
initialFocusTo: StringConstructor;
finalFocusTo: StringConstructor;
returnFocusOnDeactivated: {
type: BooleanConstructor;
default: boolean;
};
}>>, {
disabled: boolean;
active: boolean;
autoFocus: boolean;
returnFocusOnDeactivated: boolean;
}, {}>;

View File

@@ -0,0 +1,221 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FocusTrap = void 0;
const vue_1 = require("vue");
const seemly_1 = require("seemly");
const evtd_1 = require("evtd");
const utils_1 = require("./utils");
const shared_1 = require("../../shared");
let stack = [];
exports.FocusTrap = (0, vue_1.defineComponent)({
name: 'FocusTrap',
props: {
disabled: Boolean,
active: Boolean,
autoFocus: {
type: Boolean,
default: true
},
onEsc: Function,
initialFocusTo: String,
finalFocusTo: String,
returnFocusOnDeactivated: {
type: Boolean,
default: true
}
},
setup(props) {
const id = (0, seemly_1.createId)();
const focusableStartRef = (0, vue_1.ref)(null);
const focusableEndRef = (0, vue_1.ref)(null);
let activated = false;
let ignoreInternalFocusChange = false;
const lastFocusedElement = typeof document === 'undefined' ? null : document.activeElement;
function isCurrentActive() {
const currentActiveId = stack[stack.length - 1];
return currentActiveId === id;
}
function handleDocumentKeydown(e) {
var _a;
if (e.code === 'Escape') {
if (isCurrentActive()) {
(_a = props.onEsc) === null || _a === void 0 ? void 0 : _a.call(props, e);
}
}
}
(0, vue_1.onMounted)(() => {
(0, vue_1.watch)(() => props.active, (value) => {
if (value) {
activate();
(0, evtd_1.on)('keydown', document, handleDocumentKeydown);
}
else {
(0, evtd_1.off)('keydown', document, handleDocumentKeydown);
if (activated) {
deactivate();
}
}
}, {
immediate: true
});
});
(0, vue_1.onBeforeUnmount)(() => {
(0, evtd_1.off)('keydown', document, handleDocumentKeydown);
if (activated)
deactivate();
});
function handleDocumentFocus(e) {
if (ignoreInternalFocusChange)
return;
if (isCurrentActive()) {
const mainEl = getMainEl();
if (mainEl === null)
return;
if (mainEl.contains((0, seemly_1.getPreciseEventTarget)(e)))
return;
// I don't handle shift + tab status since it's too tricky to handle
// Not impossible but I need to sleep
resetFocusTo('first');
}
}
function getMainEl() {
const focusableStartEl = focusableStartRef.value;
if (focusableStartEl === null)
return null;
let mainEl = focusableStartEl;
while (true) {
mainEl = mainEl.nextSibling;
if (mainEl === null)
break;
if (mainEl instanceof Element && mainEl.tagName === 'DIV') {
break;
}
}
return mainEl;
}
function activate() {
var _a;
if (props.disabled)
return;
stack.push(id);
if (props.autoFocus) {
const { initialFocusTo } = props;
if (initialFocusTo === undefined) {
resetFocusTo('first');
}
else {
(_a = (0, shared_1.resolveTo)(initialFocusTo)) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true });
}
}
activated = true;
document.addEventListener('focus', handleDocumentFocus, true);
}
function deactivate() {
var _a;
if (props.disabled)
return;
document.removeEventListener('focus', handleDocumentFocus, true);
stack = stack.filter((idInStack) => idInStack !== id);
if (isCurrentActive())
return;
const { finalFocusTo } = props;
if (finalFocusTo !== undefined) {
(_a = (0, shared_1.resolveTo)(finalFocusTo)) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true });
}
else if (props.returnFocusOnDeactivated) {
if (lastFocusedElement instanceof HTMLElement) {
ignoreInternalFocusChange = true;
lastFocusedElement.focus({ preventScroll: true });
ignoreInternalFocusChange = false;
}
}
}
function resetFocusTo(target) {
if (!isCurrentActive())
return;
if (props.active) {
const focusableStartEl = focusableStartRef.value;
const focusableEndEl = focusableEndRef.value;
if (focusableStartEl !== null && focusableEndEl !== null) {
const mainEl = getMainEl();
if (mainEl == null || mainEl === focusableEndEl) {
ignoreInternalFocusChange = true;
focusableStartEl.focus({ preventScroll: true });
ignoreInternalFocusChange = false;
return;
}
ignoreInternalFocusChange = true;
const focused = target === 'first'
? (0, utils_1.focusFirstDescendant)(mainEl)
: (0, utils_1.focusLastDescendant)(mainEl);
ignoreInternalFocusChange = false;
if (!focused) {
ignoreInternalFocusChange = true;
focusableStartEl.focus({ preventScroll: true });
ignoreInternalFocusChange = false;
}
}
}
}
function handleStartFocus(e) {
if (ignoreInternalFocusChange)
return;
const mainEl = getMainEl();
if (mainEl === null)
return;
if (e.relatedTarget !== null && mainEl.contains(e.relatedTarget)) {
// if it comes from inner, focus last
resetFocusTo('last');
}
else {
// otherwise focus first
resetFocusTo('first');
}
}
function handleEndFocus(e) {
if (ignoreInternalFocusChange)
return;
if (e.relatedTarget !== null &&
e.relatedTarget === focusableStartRef.value) {
// if it comes from first, focus last
resetFocusTo('last');
}
else {
// otherwise focus first
resetFocusTo('first');
}
}
return {
focusableStartRef,
focusableEndRef,
focusableStyle: 'position: absolute; height: 0; width: 0;',
handleStartFocus,
handleEndFocus
};
},
render() {
const { default: defaultSlot } = this.$slots;
if (defaultSlot === undefined)
return null;
if (this.disabled)
return defaultSlot();
const { active, focusableStyle } = this;
return (0, vue_1.h)(vue_1.Fragment, null, [
(0, vue_1.h)('div', {
'aria-hidden': 'true',
tabindex: active ? '0' : '-1',
ref: 'focusableStartRef',
style: focusableStyle,
onFocus: this.handleStartFocus
}),
defaultSlot(),
(0, vue_1.h)('div', {
'aria-hidden': 'true',
style: focusableStyle,
ref: 'focusableEndRef',
tabindex: active ? '0' : '-1',
onFocus: this.handleEndFocus
})
]);
}
});

View File

@@ -0,0 +1,2 @@
export declare function focusFirstDescendant(node: Node): boolean;
export declare function focusLastDescendant(element: Node): boolean;

View File

@@ -0,0 +1,65 @@
"use strict";
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
// ref https://www.w3.org/TR/wai-aria-practices-1.1/examples/dialog-modal/js/dialog.js
Object.defineProperty(exports, "__esModule", { value: true });
exports.focusLastDescendant = exports.focusFirstDescendant = void 0;
function isHTMLElement(node) {
return node instanceof HTMLElement;
}
function focusFirstDescendant(node) {
for (let i = 0; i < node.childNodes.length; i++) {
const child = node.childNodes[i];
if (isHTMLElement(child)) {
if (attemptFocus(child) || focusFirstDescendant(child)) {
return true;
}
}
}
return false;
}
exports.focusFirstDescendant = focusFirstDescendant;
function focusLastDescendant(element) {
for (let i = element.childNodes.length - 1; i >= 0; i--) {
const child = element.childNodes[i];
if (isHTMLElement(child)) {
if (attemptFocus(child) || focusLastDescendant(child)) {
return true;
}
}
}
return false;
}
exports.focusLastDescendant = focusLastDescendant;
function attemptFocus(element) {
if (!isFocusable(element)) {
return false;
}
try {
element.focus({ preventScroll: true });
}
catch (e) { }
return document.activeElement === element;
}
function isFocusable(element) {
if (element.tabIndex > 0 ||
(element.tabIndex === 0 && element.getAttribute('tabIndex') !== null)) {
return true;
}
if (element.getAttribute('disabled')) {
return false;
}
switch (element.nodeName) {
case 'A':
return (!!element.href &&
element.rel !== 'ignore');
case 'INPUT':
return (element.type !== 'hidden' &&
element.type !== 'file');
case 'BUTTON':
case 'SELECT':
case 'TEXTAREA':
return true;
default:
return false;
}
}

View File

@@ -0,0 +1,12 @@
export * from './binder/src';
export { default as VirtualList, default as VVirtualList } from './virtual-list/src';
export type { VirtualListInst as VVirtualListInst, VirtualListScrollTo as VVirtualListScrollTo, VirtualListScrollToOptions as VVirtualListScrollToOptions, VirtualListItemData as VVirtualListItemData, VVirtualListColumn, VVirtualListRenderCol, VVirtualListRenderItemWithCols } from './virtual-list/src';
export { default as LazyTeleport, default as VLazyTeleport } from './lazy-teleport/src';
export { default as ResizeObserver, default as VResizeObserver, resizeObserverManager } from './resize-observer/src';
export type { VResizeObserverOnResize } from './resize-observer/src';
export { default as XScroll, default as VXScroll } from './x-scroll/src';
export type { VXScrollInst } from './x-scroll/src';
export { VOverflow, Overflow } from './overflow';
export type { VOverflowInst } from './overflow';
export { FocusTrap, FocusTrap as VFocusTrap } from './focus-trap';
export type { VirtualListInst, VirtualListScrollTo, VirtualListScrollToOptions, VirtualListItemData } from './virtual-list/src';

View File

@@ -0,0 +1,40 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.VFocusTrap = exports.FocusTrap = exports.Overflow = exports.VOverflow = exports.VXScroll = exports.XScroll = exports.resizeObserverManager = exports.VResizeObserver = exports.ResizeObserver = exports.VLazyTeleport = exports.LazyTeleport = exports.VVirtualList = exports.VirtualList = void 0;
__exportStar(require("./binder/src"), exports);
var src_1 = require("./virtual-list/src");
Object.defineProperty(exports, "VirtualList", { enumerable: true, get: function () { return __importDefault(src_1).default; } });
Object.defineProperty(exports, "VVirtualList", { enumerable: true, get: function () { return __importDefault(src_1).default; } });
var src_2 = require("./lazy-teleport/src");
Object.defineProperty(exports, "LazyTeleport", { enumerable: true, get: function () { return __importDefault(src_2).default; } });
Object.defineProperty(exports, "VLazyTeleport", { enumerable: true, get: function () { return __importDefault(src_2).default; } });
var src_3 = require("./resize-observer/src");
Object.defineProperty(exports, "ResizeObserver", { enumerable: true, get: function () { return __importDefault(src_3).default; } });
Object.defineProperty(exports, "VResizeObserver", { enumerable: true, get: function () { return __importDefault(src_3).default; } });
Object.defineProperty(exports, "resizeObserverManager", { enumerable: true, get: function () { return src_3.resizeObserverManager; } });
var src_4 = require("./x-scroll/src");
Object.defineProperty(exports, "XScroll", { enumerable: true, get: function () { return __importDefault(src_4).default; } });
Object.defineProperty(exports, "VXScroll", { enumerable: true, get: function () { return __importDefault(src_4).default; } });
var overflow_1 = require("./overflow");
Object.defineProperty(exports, "VOverflow", { enumerable: true, get: function () { return overflow_1.VOverflow; } });
Object.defineProperty(exports, "Overflow", { enumerable: true, get: function () { return overflow_1.Overflow; } });
var focus_trap_1 = require("./focus-trap");
Object.defineProperty(exports, "FocusTrap", { enumerable: true, get: function () { return focus_trap_1.FocusTrap; } });
Object.defineProperty(exports, "VFocusTrap", { enumerable: true, get: function () { return focus_trap_1.FocusTrap; } });

Some files were not shown because too many files have changed in this diff Show More