omii-ui
Version:
使用OMII开发的WebComponents库,样式采用bootstrap。可以作为标准的esmodule使用
263 lines (244 loc) • 6.9 kB
JSX
const { h, classNames, extractClass } = omii;
import uiBase from "../uiBase";
import { getCSSStyleSheets, timingClassNames } from "../css";
import css from "./index.scss";
import { isVisible, triggerTransitionEnd } from "bootstrap/js/src/util";
import { reflow } from "../utils";
const CLASS_NAME_CAROUSEL = "carousel";
const CLASS_NAME_ACTIVE = "active";
const CLASS_NAME_SLIDE = "slide";
const CLASS_NAME_END = "carousel-item-end";
const CLASS_NAME_START = "carousel-item-start";
const CLASS_NAME_NEXT = "carousel-item-next";
const CLASS_NAME_PREV = "carousel-item-prev";
const SELECTOR_ACTIVE = ".active";
const SELECTOR_ITEM = ".carousel-item";
const SELECTOR_ACTIVE_ITEM = SELECTOR_ACTIVE + SELECTOR_ITEM;
export default class extends uiBase {
static css = [
() => getCSSStyleSheets("reboot", "utilities", "carousel"),
css,
];
static defaultProps = {
autoSlide: true,
pauseEvent: "hover",
interval: 2.3,
duration: 0.6, //动画时长
items: [],
prevCtrl: (host) => (
<button
class="carousel-control-prev"
type="button"
onClick={(evt) => {
host.slide(false);
}}
>
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
),
nextCtrl: (host) => (
<button
class="carousel-control-next"
type="button"
onClick={(evt) => {
host.slide();
}}
>
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
),
indicator: ({ index, host }) => (
<button
type="button"
onClick={(evt) => host.slide(index)}
class={classNames({ active: index == 0 })}
></button>
),
};
static propTypes = {
autoSlide: Boolean,
interval: Number,
duration: Number,
pauseEvent: String,
};
css() {
return `
.carousel-item {
transition-duration: ${this.$props.duration}s;
}
`;
}
#activeIndex = 0;
get activeIndex() {
return this.#activeIndex;
}
#isSliding = false;
get isSliding() {
return this.#isSliding;
}
get $items() {
return this.$$(".carousel-item");
}
get $activeItem() {
return this.$(".carousel-item.active");
}
pause() {
if (this.#isSliding) {
triggerTransitionEnd(this);
}
clearInterval(this.#interval);
this.#interval = null;
}
get indicators() {
return [...this.$("slot[name=indicators]").children];
}
#slideQueue = [];
slide(isNext = true) {
//boolean,number,element
if (this.#isSliding) return false;
let {
$activeItem,
$items,
$props: { duration },
} = this;
let activeIndex = $items.indexOf($activeItem);
let nextIndex, $nextItem;
if (isNext instanceof HTMLElement) {
$nextItem = isNext;
nextIndex = $items.indexOf($nextItem);
isNext = nextIndex > activeIndex;
} else if (typeof isNext == "number") {
nextIndex = (isNext + $items.length) % $items.length;
$nextItem = $items[nextIndex];
isNext = nextIndex > activeIndex;
} else {
nextIndex = (activeIndex + 1) % $items.length;
if (!isNext) {
nextIndex = (activeIndex - 1 + $items.length) % $items.length;
}
$nextItem = $items[nextIndex];
}
if (this.#activeIndex == nextIndex) return false;
let directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END;
let orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV;
this.#isSliding = true;
let waiting = Number.parseFloat($activeItem.getAttribute("waiting")) ?? 0;
timingClassNames($nextItem, [
{
classNames: [],
duration: waiting,
},
{
classNames: [orderClassName],
callback: () => {
reflow($nextItem);
},
},
{
classNames: [orderClassName, directionalClassName],
duration,
},
{
classNames: ["active"],
callback: () => {
this.indicators.forEach((indicator, index) => {
if (nextIndex == index) {
indicator.classList.add("active");
} else {
indicator.classList.remove("active");
}
});
},
},
]);
timingClassNames($activeItem, [
{
classNames: [],
duration: waiting,
},
{
classNames: [directionalClassName, "active"],
duration,
},
{
classNames: [],
callback: () => {
this.#isSliding = false;
this.#activeIndex = nextIndex;
let func;
while ((func = this.#slideQueue.pop())) {
func();
}
},
},
]);
}
nextWhenVisible() {
if (!document.hidden && isVisible(this)) {
this.slide();
}
}
#interval;
cycle() {
if (this.#interval) clearInterval(this.#interval);
this.#interval = setInterval(
() => this.nextWhenVisible(),
this.$props.interval * 1000
);
}
installed() {
let $items = this.$items;
if (!this.$activeItem && $items.length > 0) {
$items[0].classList.add("active");
}
this.#activeIndex = $items.indexOf(this.$activeItem);
if (this.$props.autoSlide && this.$activeItem) {
this.cycle();
}
this.addEventListener("mouseenter", () => {
this.pause();
});
this.addEventListener("mouseleave", () => {
if (this.$props.autoSlide) {
if (this.#isSliding) {
this.#slideQueue.push(() => {
this.cycle();
});
} else {
this.cycle();
}
}
});
}
render(props) {
let { items, nextCtrl, prevCtrl, indicator } = this.$props;
return (
<div {...extractClass(props, "carousel")}>
<slot class="carousel-inner">
{items.map((item) => (
<div
className={classNames("carousel-item")}
waiting={item.attributes?.waiting}
>
{item}
</div>
))}
</slot>
<div class="carousel-indicators">
<slot name="indicators">
{typeof indicator == "function" &&
items.map((item, index) =>
indicator({ item, index, host: this })
)}
</slot>
</div>
<slot name="actions">
{typeof nextCtrl == "function" && nextCtrl(this)}
{typeof prevCtrl == "function" && prevCtrl(this)}
</slot>
</div>
);
}
}