UNPKG

omii-ui

Version:

使用OMII开发的WebComponents库,样式采用bootstrap。可以作为标准的esmodule使用

263 lines (244 loc) 6.9 kB
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> ); } }