@suyouwanggang/p-ui
Version:
`p-ui`是一套使用原生`Web Components`规范开发的跨框架UI组件库,基于`lit-elment`库开发。 [github项目地址](https://github.com/suyouwanggang/p-ui)
482 lines (471 loc) • 20.6 kB
text/typescript
import { css, customElement, html, LitElement, property } from 'lit-element';
import './p-button';
import './p-tips';
('p-pop')
class Ppop extends LitElement {
static get styles() {
return css`
:host {
display:inline-block;
position:relative;
overflow:visible;
--distanceValue: var(--distance,10px);
}
:host([dir="top"]) ::slotted(p-pop-content){
bottom:100%;
left:50%;
transform:translate(-50%,calc( -1 * var(--distanceValue) ) ) scale(0);
transform-origin: center bottom;
}
:host([dir="top"]) ::slotted(p-pop-content[open]),
:host([dir="top"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="top"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(-50%,calc( -1 * var(--distanceValue) ) ) scale(1);
}
:host([dir="right"]) ::slotted(p-pop-content){
left:100%;
top:50%;
transform:translate(calc( 1 * var(--distanceValue) ),-50%) scale(0);
transform-origin: left;
}
:host([dir="right"]) ::slotted(p-pop-content[open]),
:host([dir="right"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="right"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(calc( 1 * var(--distanceValue) ),-50%) scale(1);
}
:host([dir="bottom"]) ::slotted(p-pop-content){
top:100%;
left:50%;
transform:translate(-50%,calc( 1 * var(--distanceValue) )) scale(0);
transform-origin: center top;
}
:host([dir="bottom"]) ::slotted(p-pop-content[open]),
:host([dir="bottom"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="bottom"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(-50%,calc( 1 * var(--distanceValue) )) scale(1);
}
:host([dir="left"]) ::slotted(p-pop-content){
right:100%;
top:50%;
transform:translate(calc( -1 * var(--distanceValue) ),-50%) scale(0);
transform-origin: right;
}
:host([dir="left"]) ::slotted(p-pop-content[open]),
:host([dir="left"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="left"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(calc( -1 * var(--distanceValue) ),-50%) scale(1);
}
:host([dir="lefttop"]) ::slotted(p-pop-content){
right:100%;
top:0;
transform:translate(calc( -1 * var(--distanceValue) )) scale(0);
transform-origin: right top;
}
:host([dir="lefttop"]) ::slotted(p-pop-content[open]),
:host([dir="lefttop"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="lefttop"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(calc( -1 * var(--distanceValue) )) scale(1);
}
:host([dir="leftbottom"]) ::slotted(p-pop-content){
right:100%;
bottom:0;
transform:translate(calc( -1 * var(--distanceValue) )) scale(0);
transform-origin: right bottom;
}
:host([dir="leftbottom"]) ::slotted(p-pop-content[open]),
:host([dir="leftbottom"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="leftbottom"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(calc( -1 * var(--distanceValue) )) scale(1);
}
:host([dir="topleft"]) ::slotted(p-pop-content){
bottom:100%;
left:0;
transform:translate(0,calc( -1 * var(--distanceValue) )) scale(0);
transform-origin: left bottom;
}
:host([dir="topleft"]) ::slotted(p-pop-content[open]),
:host([dir="topleft"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="topleft"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(0,calc( -1 * var(--distanceValue) )) scale(1);
}
:host([dir="topright"]) ::slotted(p-pop-content){
bottom:100%;
right:0;
transform:translate(0,calc( -1 * var(--distanceValue) )) scale(0);
transform-origin: right bottom;
}
:host([dir="topright"]) ::slotted(p-pop-content[open]),
:host([dir="topright"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="topright"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(0,calc( -1 * var(--distanceValue) )) scale(1);
}
:host([dir="righttop"]) ::slotted(p-pop-content){
left:100%;
top:0;
transform:translate(calc( 1 * var(--distanceValue) )) scale(0);
transform-origin: left top;
}
:host([dir="righttop"]) ::slotted(p-pop-content[open]),
:host([dir="righttop"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="righttop"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(calc( 1 * var(--distanceValue) )) scale(1);
}
:host([dir="rightbottom"]) ::slotted(p-pop-content){
left:100%;
bottom:0;
transform:translate(10px) scale(0);
transform-origin: left bottom;
}
:host([dir="rightbottom"]) ::slotted(p-pop-content[open]),
:host([dir="rightbottom"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="rightbottom"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(calc( 1 * var(--distanceValue) )) scale(1);
}
:host([dir="bottomleft"]) ::slotted(p-pop-content),
:host(:not([dir])) ::slotted(p-pop-content){
left:0;
top:100%;
transform:translate(0,calc( 1 * var(--distanceValue) )) scale(0);
transform-origin: left top;
}
:host(:not([dir])) ::slotted(p-pop-content[open]),
:host(:not([dir])[trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host(:not([dir])[trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content),
:host([dir="bottomleft"]) ::slotted(p-pop-content[open]),
:host([dir="bottomleft"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="bottomleft"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(0,calc( 1 * var(--distanceValue) )) scale(1);
}
:host([dir="bottomright"]) ::slotted(p-pop-content){
right:0;
top:100%;
transform:translate(0,calc( 1 * var(--distanceValue) )) scale(0);
transform-origin: right top;
}
:host([dir="bottomright"]) ::slotted(p-pop-content[open]),
:host([dir="bottomright"][trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([dir="bottomright"][trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
transform:translate(0,calc( 1 * var(--distanceValue) )) scale(1);
}
:host([trigger="contextmenu"]) ::slotted(p-pop-content){
right:auto;
bottom:auto;
left:var(--x,0);
top:var(--y,100%);
transform-origin: left top;
transform:translate(5px,5px) scale(0);
transition: .15s;
}
:host([trigger="contextmenu"]) ::slotted(p-pop-content[open]){
transform:translate(5px,5px) scale(1);
}
:host ::slotted(p-pop-content[open]),
:host([trigger="hover"]:not([disabled]):hover) ::slotted(p-pop-content),
:host([trigger="focus"]:not([disabled]):focus-within) ::slotted(p-pop-content){
opacity:1;
visibility:visible;
}
:host([dir='center']) ::slotted(p-pop-content){
position:fixed;
right:auto;
bottom:auto;
top:50%;
left:50%;
transition: .15s;
transform:translate(-50%, -50%) scale(0);
}
:host([dir='center']) ::slotted(p-pop-content[open]){
transform:translate(-50%, -50%) scale(1);
}
`;
}
({ type: Boolean, reflect: true }) disabled: boolean = false;
({ type: String, reflect: true }) type: string = undefined;
({ type: String, reflect: true }) tipContent: string = undefined;
({ type: String, reflect: true }) tipTitle: string = undefined;
({ type: String, reflect: true }) okText: string = '确定';
({ type: String, reflect: true }) cancelText: string = '取消';
({ type: String, reflect: true }) trigger: string = 'click';
({ type: Boolean, reflect: true }) accomplish: boolean = false;
render() {
return html`<slot></slot>`;
}
_show(ev: MouseEvent) {
const popContent = this.popContent;
const event = ev as any;
if (!this.disabled) {
if (this.trigger === 'contextmenu') {
const { left, top } = this.getBoundingClientRect();
popContent!.style.setProperty('--x', ev.clientX - left + 'px');
popContent!.style.setProperty('--y', ev.clientY - top + 'px');
popContent!.open = true;
} else {
const path = event.path || (event.composedPath && ev.composedPath());
if (!path.includes(popContent)) {
if (this.accomplish) {
popContent!.open = true;
} else {
popContent!.open = !popContent.open;
}
}
}
}
}
get popContent(): PPopContent {
let popContent: PPopContent = this.querySelector('p-pop-content');
if (popContent === null) {
popContent = new PPopContent();
popContent.type = this.type;
if (this.tipContent) {
const div = document.createElement('div');
div.textContent = this.tipContent;
popContent.appendChild(div);
}
(popContent as any).isAutoCreate = true;
popContent.tipTitle = this.tipTitle;
popContent.okText = this.okText;
popContent.cancelText = this.cancelText;
this.appendChild(popContent);
}
return popContent;
}
private _bindShowHanlder = (ev: MouseEvent) => {
if (this.trigger !== 'contextmenu') {
this._show(ev);
} else {
const event = ev as any;
ev.preventDefault();
const path = event.path || (ev.composedPath && ev.composedPath());
if (!path.includes(this.popContent)) {
this._show(ev);
}
}
}
private _bindHiddenHanlder = (ev: MouseEvent) => {
const popContent = this.popContent;
popContent.open = false;
}
private _bindDocumentAutoHidde = (ev: any) => {
const popContent = this.popContent;
const path = ev.path || (ev.composedPath && ev.composedPath());
if (popContent && !path.includes(popContent) && !popContent.loading && !path.includes(this.children[0]) || (this.trigger === 'contextmenu') && !path.includes(popContent) && ev.which === '1') {
popContent.open = false;
}
}
private _bindTriggerEvent(oldTirgger?: string) {
if (oldTirgger !== undefined) {
if (oldTirgger === 'hover') {
this.removeEventListener('mouseenter', this._bindShowHanlder);
this.removeEventListener('mouseleave', this._bindHiddenHanlder);
} else {
this.removeEventListener(oldTirgger, this._bindShowHanlder);
}
}
if (this.trigger !== undefined && this.trigger !== 'hover') {
this.addEventListener(this.trigger, this._bindShowHanlder);
} else if ( 'hover' === this.trigger ) {
this.addEventListener('mouseenter', this._bindShowHanlder);
this.addEventListener('mouseleave', this._bindHiddenHanlder);
}
}
private firstUpdateFlag = false;
firstUpdated() {
this._bindTriggerEvent();
document.addEventListener('mousedown', this._bindDocumentAutoHidde);
this.firstUpdateFlag = true;
}
update(changedProperties: Map<string | number | symbol, unknown>) {
super.update(changedProperties);
if (this.firstUpdateFlag) {
const trigger = changedProperties.get('trigger') as string;
if (trigger !== undefined) {
this._bindTriggerEvent(trigger);
}
const pop = this.popContent as any;
if (pop && pop.isAutoCreate) {
if (changedProperties.has('tipContent')) {
let firstChild = pop.firstChild;
if (!firstChild) {
firstChild = document.createElement('div');
pop.appendChild(firstChild);
}
firstChild.textContent = this.tipContent;
}
if (changedProperties.has('tipTitle')) {
pop.tipTitle = this.tipTitle;
}
}
}
}
disconnectedCallback() {
document.removeEventListener('mousedown', this._bindDocumentAutoHidde);
}
}
('p-pop-content')
class PPopContent extends LitElement {
static get styles() {
return css`
:host{
position:absolute;
display:flex;
flex-direction:column;
box-shadow: 2px 2px 15px rgba(0,0,0,0.15);
box-sizing: border-box;
transform:scale(0);
opacity:0.5;
border-radius: 3px;
z-index:10;
transition:.3s cubic-bezier(.645, .045, .355, 1);
transform-origin:inherit;
background:#fff;
visibility:hidden;
}
div[part=popTitle] {
display:flex;
}
div[part=popTitle] >div[part=popTitleInner]{
flex:1;
margin-left:0.5em;
line-height: 30px;
font-size: 1.2em;
color: #4c5161;
user-select: none;
cursor: default;
}
div[part=popBody]{
width:max-content;
}
p-button[part="popClose"] {
margin-right:5px;
border:0;
}
:host([thinBar]) div[part=popBody] {
flex: 1;
overflow:auto;
width:max-content;
scrollbar-color: #DBDBDB #FFF;
scrollbar-width: thin;
}
:host([thinBar]) div[part=popBody] :hover{
scrollbar-color: rgb(189,189,189) #FFF ;
}
:host([thinBar]) div[part=popBody]::-webkit-scrollbar {
width:7px;
height: 7px;
}
/* 滚动槽 */
:host([thinBar]) div[part=popBody]::-webkit-scrollbar-track {
background-color:#FFF;
}
/* 滚动条滑块 */
:host([thinBar]) div[part=popBody]::-webkit-scrollbar-thumb {
border-radius:3px;
background:#DBDBDB;
}
:host([thinBar]) div[part=popBody]::-webkit-scrollbar-thumb:hover {
background-color: #BDBDBD;
}
p-icon[part=popIcon]{
flex:auto;
font-size:1.2em;
color:var(--waringColor,#faad14);
margin: 1em 0px 0px 0.8em;
align-self:flex-start;
}
div[part=popFooter]{
margin-top:8px;
text-align: right;
white-space: nowrap;
}
#btn-cancel,#btn-submit {
margin-left: 0.6em;
margin-right:0.6em;
margin-bottom:8px;
cursor:pointer;
}
#btn-submit {
margin-right:1.5em;
}
:host([type="confirm"]){
min-width:250px;
}
:host([type="confirm"]) div[part=popBody] {
margin-left:0.8em;
margin-right:0.8em;
}`
;
}
({ type: Boolean, reflect: true }) open: boolean = false;
({ type: Boolean, reflect: true }) loading: boolean = false;
({ type: Boolean, reflect: true }) thinBar: boolean = false;
({ type: Boolean, reflect: true }) hiddenClose: boolean = false;
({ type: String, reflect: true }) type: string = undefined;
({ type: String, reflect: true }) tipTitle: string = undefined;
({ type: String, reflect: true }) okText: string = undefined;
({ type: String, reflect: true }) cancelText: string = undefined;
render() {
return html`
<div part="popTitle" id="title">
<div class='title' part="popTitleInner">${this.tipTitle} <slot name="title"></slot></div>
${this.hiddenClose ? '' : html`<p-button type="flat" shape='circle' id="btn-close" part="popClose" icon="close" @click='${this._toCloseEvent}'></p-button>`}
</div>
<div part="popBody" >
<slot></slot>
</div>
<div part="popFooter">
<slot name="footer">
${this.type === 'confirm' ?
html`<p-button id="btn-cancel" @click="${this._cancleClick}">${this.cancelText === undefined ? '取消' : this.cancelText}</p-button>
<p-button id="btn-submit" type="primary" @click="${this._submitClick}">${this.okText === undefined ? '确定' : this.okText}</p-button>`
: ''}
</slot>
</div>
`;
}
private _toCloseEvent(ev: Event) {
this.open = false;
this.dispatchEvent(new CustomEvent('close'));
// await this.updateComplete;
}
private _cancleClick(ev: Event) {
this.open = false;
this.dispatchEvent(new CustomEvent('cancel'));
// await this.updateComplete;
}
private _submitClick(ev: Event) {
this.dispatchEvent(new CustomEvent('submit'));
if (!this.loading) {
this.open = false;
}
}
firstUpdated() {
const btnClose = this.shadowRoot.getElementById('btn-close');
const btnSubmit = this.shadowRoot.getElementById('btn-submit');
this.addEventListener('transitionend', (ev) => {
//console.log('transitionend===');
if (ev.propertyName === 'transform' && this.open) {
// console.log('transform===');
if (this.type === 'confirm') {
if (btnSubmit) {
btnSubmit.focus();
}
} else {
if (btnClose) {
btnClose.focus();
}
}
this.dispatchEvent(new CustomEvent('open'));
}
if (ev.propertyName === 'transform' && !this.open) {
this.dispatchEvent(new CustomEvent('close'));
}
});
this.addEventListener('click', (ev: Event) => {
const el = ev.target as HTMLElement;
if (el && el.closest('[autoclose]')) {
this.open = false;
}
});
}
}
export { Ppop, PPopContent };