hongluan-ui
Version:
Hongluan Component Library for Vue 3
233 lines (226 loc) • 7 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var vue = require('vue');
var shared = require('@vue/shared');
var computeScrollIntoView = require('compute-scroll-into-view');
require('../../../utils/index.js');
require('../../../hooks/index.js');
var utils = require('./utils.js');
var types$1 = require('./types.js');
var pluginVue_exportHelper = require('../../../_virtual/plugin-vue_export-helper.js');
var types = require('../../../utils/types.js');
var index = require('../../../hooks/use-namespace/index.js');
var raf = require('../../../utils/raf.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var computeScrollIntoView__default = /*#__PURE__*/_interopDefaultLegacy(computeScrollIntoView);
const BOUNDARY_POSITIONS = ["start", "end", "center", "nearest"];
const _sfc_main = vue.defineComponent({
name: "Anchor",
props: {
boundary: {
type: [Number, String],
default: "start",
validator: (value) => {
return types.isNumber(value) || BOUNDARY_POSITIONS.includes(value);
}
},
sliderLess: {
type: Boolean,
default: false
},
scrollContainer: {
type: [String, Object]
},
changeHash: {
type: Boolean,
default: true
},
smooth: {
type: Boolean,
default: true
},
right: Boolean
},
emits: [
"select",
"change"
],
setup(props, { emit }) {
const { namespace } = index.useNamespace("anchor");
const anchorRef = vue.ref(null);
const lineSliderRef = vue.ref(null);
const links = vue.reactive({});
const currentLink = vue.ref("");
const isScrolling = vue.ref(false);
const addLink = (hash, node) => {
if (!hash)
return;
links[hash] = node;
};
const removeLink = (hash) => {
delete links[hash];
};
const getContainer = (targetContainer) => {
let scrollContainer = targetContainer;
if (!scrollContainer) {
scrollContainer = props.scrollContainer;
}
if (shared.isString(scrollContainer)) {
return utils.findNode(document, scrollContainer);
}
return scrollContainer || window;
};
const getContainerElement = () => {
var _a;
if (shared.isString(props.scrollContainer)) {
return utils.findNode(document, props.scrollContainer);
}
if (props.scrollContainer && props.scrollContainer !== window) {
return props.scrollContainer;
}
return (_a = document.documentElement) != null ? _a : document.body;
};
const handleClick = (e, hash) => {
if (!props.changeHash) {
e.preventDefault();
}
handleAnchorChange(hash);
scrollIntoView(hash);
emit("select", hash, currentLink.value);
};
const scrollIntoView = (hash) => {
if (!hash)
return;
try {
const node = utils.findNode(document, hash);
if (!node)
return;
let block = props.boundary;
let diff = 0;
if (types.isNumber(props.boundary)) {
block = "start";
diff = props.boundary;
}
const actions = computeScrollIntoView__default["default"](node, { block });
if (!actions.length)
return;
const { el, top } = actions[0];
const targetTop = top - diff;
utils.slide(el, targetTop, () => {
isScrolling.value = false;
});
isScrolling.value = true;
} catch (e) {
console.error(e);
}
};
const handleScroll = raf.throttleByRaf(() => {
if (isScrolling.value)
return;
const element = getFirstInViewportEle();
if (element && element.id) {
const hash = `#${element.id}`;
handleAnchorChange(hash);
}
});
const handleAnchorChange = (hash) => {
if (!hash)
return;
if (!links[hash] && anchorRef.value) {
const node = utils.findNode(anchorRef.value, `a[data-href='${hash}']`);
if (!node)
return;
links[hash] = node;
}
if (hash !== currentLink.value) {
currentLink.value = hash;
vue.nextTick(() => {
emit("change", hash);
});
}
};
const getFirstInViewportEle = () => {
const boundary = types.isNumber(props.boundary) ? props.boundary : 0;
const container = getContainerElement();
const containerRect = container.getBoundingClientRect();
const { clientHeight } = document.documentElement;
const ele = getContainer();
for (const hash of Object.keys(links)) {
const node = utils.findNode(document, hash);
if (node) {
const { top } = node.getBoundingClientRect();
if (ele === window) {
const offsetTop = top - boundary;
if (offsetTop >= 0 && offsetTop <= clientHeight / 2) {
return node;
}
} else {
const offsetTop = top - containerRect.top - boundary;
if (offsetTop >= 0 && offsetTop <= containerRect.height / 2) {
return node;
}
}
}
}
return void 0;
};
vue.onMounted(() => {
handleScroll();
bindScrollEvent();
});
vue.onBeforeUnmount(() => {
unbindScrollEvent();
});
vue.watch(currentLink, () => {
const link = links[currentLink.value];
if (!props.sliderLess && link && lineSliderRef.value) {
lineSliderRef.value.style.top = `${link.offsetTop}px`;
}
});
const bindScrollEvent = (scrollContainer) => {
getContainer(scrollContainer).addEventListener("scroll", handleScroll);
};
const unbindScrollEvent = (scrollContainer) => {
getContainer(scrollContainer).removeEventListener("scroll", handleScroll);
};
vue.provide(types$1.anchorInjectionKey, vue.reactive({
name: "Anchor",
currentLink,
addLink,
removeLink,
handleClick
}));
const cls = vue.computed(() => [
namespace.value,
{
"slider-less": props.sliderLess,
"right": props.right
}
]);
return {
cls,
anchorRef,
lineSliderRef
};
}
});
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return vue.openBlock(), vue.createElementBlock("div", {
ref: "anchorRef",
class: vue.normalizeClass(_ctx.cls)
}, [
!_ctx.sliderLess ? (vue.openBlock(), vue.createElementBlock("div", {
key: 0,
ref: "lineSliderRef",
class: vue.normalizeClass(`slider-bar`)
}, null, 512)) : vue.createCommentVNode("v-if", true),
vue.createElementVNode("div", {
class: vue.normalizeClass(`anchor-list`)
}, [
vue.renderSlot(_ctx.$slots, "default")
])
], 2);
}
var Anchor = /* @__PURE__ */ pluginVue_exportHelper["default"](_sfc_main, [["render", _sfc_render]]);
exports["default"] = Anchor;
//# sourceMappingURL=anchor.js.map