GCAuth-OAuth/js/mdui.esm.js
2022-05-14 01:18:45 +08:00

5977 lines
182 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*!
* mdui 1.0.1 (https://mdui.org)
* Copyright 2016-2020 zdhxiong
* Licensed under MIT
*/
function isFunction(target) {
return typeof target === 'function';
}
function isString(target) {
return typeof target === 'string';
}
function isNumber(target) {
return typeof target === 'number';
}
function isBoolean(target) {
return typeof target === 'boolean';
}
function isUndefined(target) {
return typeof target === 'undefined';
}
function isNull(target) {
return target === null;
}
function isWindow(target) {
return target instanceof Window;
}
function isDocument(target) {
return target instanceof Document;
}
function isElement(target) {
return target instanceof Element;
}
function isNode(target) {
return target instanceof Node;
}
/**
* 是否是 IE 浏览器
*/
function isIE() {
// @ts-ignore
return !!window.document.documentMode;
}
function isArrayLike(target) {
if (isFunction(target) || isWindow(target)) {
return false;
}
return isNumber(target.length);
}
function isObjectLike(target) {
return typeof target === 'object' && target !== null;
}
function toElement(target) {
return isDocument(target) ? target.documentElement : target;
}
/**
* 把用 - 分隔的字符串转为驼峰(如 box-sizing 转换为 boxSizing
* @param string
*/
function toCamelCase(string) {
return string
.replace(/^-ms-/, 'ms-')
.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}
/**
* 把驼峰法转为用 - 分隔的字符串(如 boxSizing 转换为 box-sizing
* @param string
*/
function toKebabCase(string) {
return string.replace(/[A-Z]/g, (replacer) => '-' + replacer.toLowerCase());
}
/**
* 获取元素的样式值
* @param element
* @param name
*/
function getComputedStyleValue(element, name) {
return window.getComputedStyle(element).getPropertyValue(toKebabCase(name));
}
/**
* 检查元素的 box-sizing 是否是 border-box
* @param element
*/
function isBorderBox(element) {
return getComputedStyleValue(element, 'box-sizing') === 'border-box';
}
/**
* 获取元素的 padding, border, margin 宽度两侧宽度的和单位为px
* @param element
* @param direction
* @param extra
*/
function getExtraWidth(element, direction, extra) {
const position = direction === 'width' ? ['Left', 'Right'] : ['Top', 'Bottom'];
return [0, 1].reduce((prev, _, index) => {
let prop = extra + position[index];
if (extra === 'border') {
prop += 'Width';
}
return prev + parseFloat(getComputedStyleValue(element, prop) || '0');
}, 0);
}
/**
* 获取元素的样式值,对 width 和 height 进行过处理
* @param element
* @param name
*/
function getStyle(element, name) {
// width、height 属性使用 getComputedStyle 得到的值不准确,需要使用 getBoundingClientRect 获取
if (name === 'width' || name === 'height') {
const valueNumber = element.getBoundingClientRect()[name];
if (isBorderBox(element)) {
return `${valueNumber}px`;
}
return `${valueNumber -
getExtraWidth(element, name, 'border') -
getExtraWidth(element, name, 'padding')}px`;
}
return getComputedStyleValue(element, name);
}
/**
* 获取子节点组成的数组
* @param target
* @param parent
*/
function getChildNodesArray(target, parent) {
const tempParent = document.createElement(parent);
tempParent.innerHTML = target;
return [].slice.call(tempParent.childNodes);
}
/**
* 始终返回 false 的函数
*/
function returnFalse() {
return false;
}
/**
* 数值单位的 CSS 属性
*/
const cssNumber = [
'animationIterationCount',
'columnCount',
'fillOpacity',
'flexGrow',
'flexShrink',
'fontWeight',
'gridArea',
'gridColumn',
'gridColumnEnd',
'gridColumnStart',
'gridRow',
'gridRowEnd',
'gridRowStart',
'lineHeight',
'opacity',
'order',
'orphans',
'widows',
'zIndex',
'zoom',
];
function each(target, callback) {
if (isArrayLike(target)) {
for (let i = 0; i < target.length; i += 1) {
if (callback.call(target[i], i, target[i]) === false) {
return target;
}
}
}
else {
const keys = Object.keys(target);
for (let i = 0; i < keys.length; i += 1) {
if (callback.call(target[keys[i]], keys[i], target[keys[i]]) === false) {
return target;
}
}
}
return target;
}
/**
* 为了使用模块扩充,这里不能使用默认导出
*/
class JQ {
constructor(arr) {
this.length = 0;
if (!arr) {
return this;
}
each(arr, (i, item) => {
// @ts-ignore
this[i] = item;
});
this.length = arr.length;
return this;
}
}
function get$() {
const $ = function (selector) {
if (!selector) {
return new JQ();
}
// JQ
if (selector instanceof JQ) {
return selector;
}
// function
if (isFunction(selector)) {
if (/complete|loaded|interactive/.test(document.readyState) &&
document.body) {
selector.call(document, $);
}
else {
document.addEventListener('DOMContentLoaded', () => selector.call(document, $), false);
}
return new JQ([document]);
}
// String
if (isString(selector)) {
const html = selector.trim();
// 根据 HTML 字符串创建 JQ 对象
if (html[0] === '<' && html[html.length - 1] === '>') {
let toCreate = 'div';
const tags = {
li: 'ul',
tr: 'tbody',
td: 'tr',
th: 'tr',
tbody: 'table',
option: 'select',
};
each(tags, (childTag, parentTag) => {
if (html.indexOf(`<${childTag}`) === 0) {
toCreate = parentTag;
return false;
}
return;
});
return new JQ(getChildNodesArray(html, toCreate));
}
// 根据 CSS 选择器创建 JQ 对象
const isIdSelector = selector[0] === '#' && !selector.match(/[ .<>:~]/);
if (!isIdSelector) {
return new JQ(document.querySelectorAll(selector));
}
const element = document.getElementById(selector.slice(1));
if (element) {
return new JQ([element]);
}
return new JQ();
}
if (isArrayLike(selector) && !isNode(selector)) {
return new JQ(selector);
}
return new JQ([selector]);
};
$.fn = JQ.prototype;
return $;
}
const $ = get$();
// 避免页面加载完后直接执行css动画
// https://css-tricks.com/transitions-only-after-page-load/
setTimeout(() => $('body').addClass('mdui-loaded'));
const mdui = {
$: $,
};
$.fn.each = function (callback) {
return each(this, callback);
};
/**
* 检查 container 元素内是否包含 contains 元素
* @param container 父元素
* @param contains 子元素
* @example
```js
contains( document, document.body ); // true
contains( document.getElementById('test'), document ); // false
contains( $('.container').get(0), $('.contains').get(0) ); // false
```
*/
function contains(container, contains) {
return container !== contains && toElement(container).contains(contains);
}
/**
* 把第二个数组的元素追加到第一个数组中,并返回合并后的数组
* @param first 第一个数组
* @param second 该数组的元素将被追加到第一个数组中
* @example
```js
merge( [ 0, 1, 2 ], [ 2, 3, 4 ] )
// [ 0, 1, 2, 2, 3, 4 ]
```
*/
function merge(first, second) {
each(second, (_, value) => {
first.push(value);
});
return first;
}
$.fn.get = function (index) {
return index === undefined
? [].slice.call(this)
: this[index >= 0 ? index : index + this.length];
};
$.fn.find = function (selector) {
const foundElements = [];
this.each((_, element) => {
merge(foundElements, $(element.querySelectorAll(selector)).get());
});
return new JQ(foundElements);
};
// 存储事件
const handlers = {};
// 元素ID
let mduiElementId = 1;
/**
* 为元素赋予一个唯一的ID
*/
function getElementId(element) {
const key = '_mduiEventId';
// @ts-ignore
if (!element[key]) {
// @ts-ignore
element[key] = ++mduiElementId;
}
// @ts-ignore
return element[key];
}
/**
* 解析事件名中的命名空间
*/
function parse(type) {
const parts = type.split('.');
return {
type: parts[0],
ns: parts.slice(1).sort().join(' '),
};
}
/**
* 命名空间匹配规则
*/
function matcherFor(ns) {
return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)');
}
/**
* 获取匹配的事件
* @param element
* @param type
* @param func
* @param selector
*/
function getHandlers(element, type, func, selector) {
const event = parse(type);
return (handlers[getElementId(element)] || []).filter((handler) => handler &&
(!event.type || handler.type === event.type) &&
(!event.ns || matcherFor(event.ns).test(handler.ns)) &&
(!func || getElementId(handler.func) === getElementId(func)) &&
(!selector || handler.selector === selector));
}
/**
* 添加事件监听
* @param element
* @param types
* @param func
* @param data
* @param selector
*/
function add(element, types, func, data, selector) {
const elementId = getElementId(element);
if (!handlers[elementId]) {
handlers[elementId] = [];
}
// 传入 data.useCapture 来设置 useCapture: true
let useCapture = false;
if (isObjectLike(data) && data.useCapture) {
useCapture = true;
}
types.split(' ').forEach((type) => {
if (!type) {
return;
}
const event = parse(type);
function callFn(e, elem) {
// 因为鼠标事件模拟事件的 detail 属性是只读的,因此在 e._detail 中存储参数
const result = func.apply(elem,
// @ts-ignore
e._detail === undefined ? [e] : [e].concat(e._detail));
if (result === false) {
e.preventDefault();
e.stopPropagation();
}
}
function proxyFn(e) {
// @ts-ignore
if (e._ns && !matcherFor(e._ns).test(event.ns)) {
return;
}
// @ts-ignore
e._data = data;
if (selector) {
// 事件代理
$(element)
.find(selector)
.get()
.reverse()
.forEach((elem) => {
if (elem === e.target ||
contains(elem, e.target)) {
callFn(e, elem);
}
});
}
else {
// 不使用事件代理
callFn(e, element);
}
}
const handler = {
type: event.type,
ns: event.ns,
func,
selector,
id: handlers[elementId].length,
proxy: proxyFn,
};
handlers[elementId].push(handler);
element.addEventListener(handler.type, proxyFn, useCapture);
});
}
/**
* 移除事件监听
* @param element
* @param types
* @param func
* @param selector
*/
function remove(element, types, func, selector) {
const handlersInElement = handlers[getElementId(element)] || [];
const removeEvent = (handler) => {
delete handlersInElement[handler.id];
element.removeEventListener(handler.type, handler.proxy, false);
};
if (!types) {
handlersInElement.forEach((handler) => removeEvent(handler));
}
else {
types.split(' ').forEach((type) => {
if (type) {
getHandlers(element, type, func, selector).forEach((handler) => removeEvent(handler));
}
});
}
}
$.fn.trigger = function (type, extraParameters) {
const event = parse(type);
let eventObject;
const eventParams = {
bubbles: true,
cancelable: true,
};
const isMouseEvent = ['click', 'mousedown', 'mouseup', 'mousemove'].indexOf(event.type) > -1;
if (isMouseEvent) {
// Note: MouseEvent 无法传入 detail 参数
eventObject = new MouseEvent(event.type, eventParams);
}
else {
eventParams.detail = extraParameters;
eventObject = new CustomEvent(event.type, eventParams);
}
// @ts-ignore
eventObject._detail = extraParameters;
// @ts-ignore
eventObject._ns = event.ns;
return this.each(function () {
this.dispatchEvent(eventObject);
});
};
function extend(target, object1, ...objectN) {
objectN.unshift(object1);
each(objectN, (_, object) => {
each(object, (prop, value) => {
if (!isUndefined(value)) {
target[prop] = value;
}
});
});
return target;
}
/**
* 将数组或对象序列化,序列化后的字符串可作为 URL 查询字符串使用
*
* 若传入数组,则格式必须和 serializeArray 方法的返回值一样
* @param obj 对象或数组
* @example
```js
param({ width: 1680, height: 1050 });
// width=1680&height=1050
```
* @example
```js
param({ foo: { one: 1, two: 2 }})
// foo[one]=1&foo[two]=2
```
* @example
```js
param({ids: [1, 2, 3]})
// ids[]=1&ids[]=2&ids[]=3
```
* @example
```js
param([
{"name":"name","value":"mdui"},
{"name":"password","value":"123456"}
])
// name=mdui&password=123456
```
*/
function param(obj) {
if (!isObjectLike(obj) && !Array.isArray(obj)) {
return '';
}
const args = [];
function destructure(key, value) {
let keyTmp;
if (isObjectLike(value)) {
each(value, (i, v) => {
if (Array.isArray(value) && !isObjectLike(v)) {
keyTmp = '';
}
else {
keyTmp = i;
}
destructure(`${key}[${keyTmp}]`, v);
});
}
else {
if (value == null || value === '') {
keyTmp = '=';
}
else {
keyTmp = `=${encodeURIComponent(value)}`;
}
args.push(encodeURIComponent(key) + keyTmp);
}
}
if (Array.isArray(obj)) {
each(obj, function () {
destructure(this.name, this.value);
});
}
else {
each(obj, destructure);
}
return args.join('&');
}
// 全局配置参数
const globalOptions = {};
// 全局事件名
const ajaxEvents = {
ajaxStart: 'start.mdui.ajax',
ajaxSuccess: 'success.mdui.ajax',
ajaxError: 'error.mdui.ajax',
ajaxComplete: 'complete.mdui.ajax',
};
/**
* 判断此请求方法是否通过查询字符串提交参数
* @param method 请求方法,大写
*/
function isQueryStringData(method) {
return ['GET', 'HEAD'].indexOf(method) >= 0;
}
/**
* 添加参数到 URL 上,且 URL 中不存在 ? 时,自动把第一个 & 替换为 ?
* @param url
* @param query
*/
function appendQuery(url, query) {
return `${url}&${query}`.replace(/[&?]{1,2}/, '?');
}
/**
* 合并请求参数参数优先级options > globalOptions > defaults
* @param options
*/
function mergeOptions(options) {
// 默认参数
const defaults = {
url: '',
method: 'GET',
data: '',
processData: true,
async: true,
cache: true,
username: '',
password: '',
headers: {},
xhrFields: {},
statusCode: {},
dataType: 'text',
contentType: 'application/x-www-form-urlencoded',
timeout: 0,
global: true,
};
// globalOptions 中的回调函数不合并
each(globalOptions, (key, value) => {
const callbacks = [
'beforeSend',
'success',
'error',
'complete',
'statusCode',
];
// @ts-ignore
if (callbacks.indexOf(key) < 0 && !isUndefined(value)) {
defaults[key] = value;
}
});
return extend({}, defaults, options);
}
/**
* 发送 ajax 请求
* @param options
* @example
```js
ajax({
method: "POST",
url: "some.php",
data: { name: "John", location: "Boston" }
}).then(function( msg ) {
alert( "Data Saved: " + msg );
});
```
*/
function ajax(options) {
// 是否已取消请求
let isCanceled = false;
// 事件参数
const eventParams = {};
// 参数合并
const mergedOptions = mergeOptions(options);
let url = mergedOptions.url || window.location.toString();
const method = mergedOptions.method.toUpperCase();
let data = mergedOptions.data;
const processData = mergedOptions.processData;
const async = mergedOptions.async;
const cache = mergedOptions.cache;
const username = mergedOptions.username;
const password = mergedOptions.password;
const headers = mergedOptions.headers;
const xhrFields = mergedOptions.xhrFields;
const statusCode = mergedOptions.statusCode;
const dataType = mergedOptions.dataType;
const contentType = mergedOptions.contentType;
const timeout = mergedOptions.timeout;
const global = mergedOptions.global;
// 需要发送的数据
// GET/HEAD 请求和 processData 为 true 时,转换为查询字符串格式,特殊格式不转换
if (data &&
(isQueryStringData(method) || processData) &&
!isString(data) &&
!(data instanceof ArrayBuffer) &&
!(data instanceof Blob) &&
!(data instanceof Document) &&
!(data instanceof FormData)) {
data = param(data);
}
// 对于 GET、HEAD 类型的请求,把 data 数据添加到 URL 中
if (data && isQueryStringData(method)) {
// 查询字符串拼接到 URL 中
url = appendQuery(url, data);
data = null;
}
/**
* 触发事件和回调函数
* @param event
* @param params
* @param callback
* @param args
*/
function trigger(event, params, callback, ...args) {
// 触发全局事件
if (global) {
$(document).trigger(event, params);
}
// 触发 ajax 回调和事件
let result1;
let result2;
if (callback) {
// 全局回调
if (callback in globalOptions) {
// @ts-ignore
result1 = globalOptions[callback](...args);
}
// 自定义回调
if (mergedOptions[callback]) {
// @ts-ignore
result2 = mergedOptions[callback](...args);
}
// beforeSend 回调返回 false 时取消 ajax 请求
if (callback === 'beforeSend' &&
(result1 === false || result2 === false)) {
isCanceled = true;
}
}
}
// XMLHttpRequest 请求
function XHR() {
let textStatus;
return new Promise((resolve, reject) => {
// GET/HEAD 请求的缓存处理
if (isQueryStringData(method) && !cache) {
url = appendQuery(url, `_=${Date.now()}`);
}
// 创建 XHR
const xhr = new XMLHttpRequest();
xhr.open(method, url, async, username, password);
if (contentType ||
(data && !isQueryStringData(method) && contentType !== false)) {
xhr.setRequestHeader('Content-Type', contentType);
}
// 设置 Accept
if (dataType === 'json') {
xhr.setRequestHeader('Accept', 'application/json, text/javascript');
}
// 添加 headers
if (headers) {
each(headers, (key, value) => {
// undefined 值不发送string 和 null 需要发送
if (!isUndefined(value)) {
xhr.setRequestHeader(key, value + ''); // 把 null 转换成字符串
}
});
}
// 检查是否是跨域请求,跨域请求时不添加 X-Requested-With
const crossDomain = /^([\w-]+:)?\/\/([^/]+)/.test(url) &&
RegExp.$2 !== window.location.host;
if (!crossDomain) {
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
}
if (xhrFields) {
each(xhrFields, (key, value) => {
// @ts-ignore
xhr[key] = value;
});
}
eventParams.xhr = xhr;
eventParams.options = mergedOptions;
let xhrTimeout;
xhr.onload = function () {
if (xhrTimeout) {
clearTimeout(xhrTimeout);
}
// AJAX 返回的 HTTP 响应码是否表示成功
const isHttpStatusSuccess = (xhr.status >= 200 && xhr.status < 300) ||
xhr.status === 304 ||
xhr.status === 0;
let responseData;
if (isHttpStatusSuccess) {
if (xhr.status === 204 || method === 'HEAD') {
textStatus = 'nocontent';
}
else if (xhr.status === 304) {
textStatus = 'notmodified';
}
else {
textStatus = 'success';
}
if (dataType === 'json') {
try {
responseData =
method === 'HEAD' ? undefined : JSON.parse(xhr.responseText);
eventParams.data = responseData;
}
catch (err) {
textStatus = 'parsererror';
trigger(ajaxEvents.ajaxError, eventParams, 'error', xhr, textStatus);
reject(new Error(textStatus));
}
if (textStatus !== 'parsererror') {
trigger(ajaxEvents.ajaxSuccess, eventParams, 'success', responseData, textStatus, xhr);
resolve(responseData);
}
}
else {
responseData =
method === 'HEAD'
? undefined
: xhr.responseType === 'text' || xhr.responseType === ''
? xhr.responseText
: xhr.response;
eventParams.data = responseData;
trigger(ajaxEvents.ajaxSuccess, eventParams, 'success', responseData, textStatus, xhr);
resolve(responseData);
}
}
else {
textStatus = 'error';
trigger(ajaxEvents.ajaxError, eventParams, 'error', xhr, textStatus);
reject(new Error(textStatus));
}
// statusCode
each([globalOptions.statusCode, statusCode], (_, func) => {
if (func && func[xhr.status]) {
if (isHttpStatusSuccess) {
func[xhr.status](responseData, textStatus, xhr);
}
else {
func[xhr.status](xhr, textStatus);
}
}
});
trigger(ajaxEvents.ajaxComplete, eventParams, 'complete', xhr, textStatus);
};
xhr.onerror = function () {
if (xhrTimeout) {
clearTimeout(xhrTimeout);
}
trigger(ajaxEvents.ajaxError, eventParams, 'error', xhr, xhr.statusText);
trigger(ajaxEvents.ajaxComplete, eventParams, 'complete', xhr, 'error');
reject(new Error(xhr.statusText));
};
xhr.onabort = function () {
let statusText = 'abort';
if (xhrTimeout) {
statusText = 'timeout';
clearTimeout(xhrTimeout);
}
trigger(ajaxEvents.ajaxError, eventParams, 'error', xhr, statusText);
trigger(ajaxEvents.ajaxComplete, eventParams, 'complete', xhr, statusText);
reject(new Error(statusText));
};
// ajax start 回调
trigger(ajaxEvents.ajaxStart, eventParams, 'beforeSend', xhr);
if (isCanceled) {
reject(new Error('cancel'));
return;
}
// Timeout
if (timeout > 0) {
xhrTimeout = setTimeout(() => {
xhr.abort();
}, timeout);
}
// 发送 XHR
xhr.send(data);
});
}
return XHR();
}
$.ajax = ajax;
/**
* 为 Ajax 请求设置全局配置参数
* @param options 键值对参数
* @example
```js
ajaxSetup({
dataType: 'json',
method: 'POST',
});
```
*/
function ajaxSetup(options) {
return extend(globalOptions, options);
}
$.ajaxSetup = ajaxSetup;
$.contains = contains;
const dataNS = '_mduiElementDataStorage';
/**
* 在元素上设置键值对数据
* @param element
* @param object
*/
function setObjectToElement(element, object) {
// @ts-ignore
if (!element[dataNS]) {
// @ts-ignore
element[dataNS] = {};
}
each(object, (key, value) => {
// @ts-ignore
element[dataNS][toCamelCase(key)] = value;
});
}
function data(element, key, value) {
// 根据键值对设置值
// data(element, { 'key' : 'value' })
if (isObjectLike(key)) {
setObjectToElement(element, key);
return key;
}
// 根据 key、value 设置值
// data(element, 'key', 'value')
if (!isUndefined(value)) {
setObjectToElement(element, { [key]: value });
return value;
}
// 获取所有值
// data(element)
if (isUndefined(key)) {
// @ts-ignore
return element[dataNS] ? element[dataNS] : {};
}
// 从 dataNS 中获取指定值
// data(element, 'key')
key = toCamelCase(key);
// @ts-ignore
if (element[dataNS] && key in element[dataNS]) {
// @ts-ignore
return element[dataNS][key];
}
return undefined;
}
$.data = data;
$.each = each;
$.extend = function (...objectN) {
if (objectN.length === 1) {
each(objectN[0], (prop, value) => {
this[prop] = value;
});
return this;
}
return extend(objectN.shift(), objectN.shift(), ...objectN);
};
function map(elements, callback) {
let value;
const ret = [];
each(elements, (i, element) => {
value = callback.call(window, element, i);
if (value != null) {
ret.push(value);
}
});
return [].concat(...ret);
}
$.map = map;
$.merge = merge;
$.param = param;
/**
* 移除指定元素上存放的数据
* @param element 存放数据的元素
* @param name
* 数据键名
*
* 若未指定键名,将移除元素上所有数据
*
* 多个键名可以用空格分隔,或者用数组表示多个键名
@example
```js
// 移除元素上键名为 name 的数据
removeData(document.body, 'name');
```
* @example
```js
// 移除元素上键名为 name1 和 name2 的数据
removeData(document.body, 'name1 name2');
```
* @example
```js
// 移除元素上键名为 name1 和 name2 的数据
removeData(document.body, ['name1', 'name2']);
```
* @example
```js
// 移除元素上所有数据
removeData(document.body);
```
*/
function removeData(element, name) {
// @ts-ignore
if (!element[dataNS]) {
return;
}
const remove = (nameItem) => {
nameItem = toCamelCase(nameItem);
// @ts-ignore
if (element[dataNS][nameItem]) {
// @ts-ignore
element[dataNS][nameItem] = null;
// @ts-ignore
delete element[dataNS][nameItem];
}
};
if (isUndefined(name)) {
// @ts-ignore
element[dataNS] = null;
// @ts-ignore
delete element[dataNS];
// @ts-ignore
}
else if (isString(name)) {
name
.split(' ')
.filter((nameItem) => nameItem)
.forEach((nameItem) => remove(nameItem));
}
else {
each(name, (_, nameItem) => remove(nameItem));
}
}
$.removeData = removeData;
/**
* 过滤掉数组中的重复元素
* @param arr 数组
* @example
```js
unique([1, 2, 12, 3, 2, 1, 2, 1, 1]);
// [1, 2, 12, 3]
```
*/
function unique(arr) {
const result = [];
each(arr, (_, val) => {
if (result.indexOf(val) === -1) {
result.push(val);
}
});
return result;
}
$.unique = unique;
$.fn.add = function (selector) {
return new JQ(unique(merge(this.get(), $(selector).get())));
};
each(['add', 'remove', 'toggle'], (_, name) => {
$.fn[`${name}Class`] = function (className) {
if (name === 'remove' && !arguments.length) {
return this.each((_, element) => {
element.setAttribute('class', '');
});
}
return this.each((i, element) => {
if (!isElement(element)) {
return;
}
const classes = (isFunction(className)
? className.call(element, i, element.getAttribute('class') || '')
: className)
.split(' ')
.filter((name) => name);
each(classes, (_, cls) => {
element.classList[name](cls);
});
});
};
});
each(['insertBefore', 'insertAfter'], (nameIndex, name) => {
$.fn[name] = function (target) {
const $element = nameIndex ? $(this.get().reverse()) : this; // 顺序和 jQuery 保持一致
const $target = $(target);
const result = [];
$target.each((index, target) => {
if (!target.parentNode) {
return;
}
$element.each((_, element) => {
const newItem = index
? element.cloneNode(true)
: element;
const existingItem = nameIndex ? target.nextSibling : target;
result.push(newItem);
target.parentNode.insertBefore(newItem, existingItem);
});
});
return $(nameIndex ? result.reverse() : result);
};
});
/**
* 是否不是 HTML 字符串(包裹在 <> 中)
* @param target
*/
function isPlainText(target) {
return (isString(target) && (target[0] !== '<' || target[target.length - 1] !== '>'));
}
each(['before', 'after'], (nameIndex, name) => {
$.fn[name] = function (...args) {
// after 方法,多个参数需要按参数顺序添加到元素后面,所以需要将参数顺序反向处理
if (nameIndex === 1) {
args = args.reverse();
}
return this.each((index, element) => {
const targets = isFunction(args[0])
? [args[0].call(element, index, element.innerHTML)]
: args;
each(targets, (_, target) => {
let $target;
if (isPlainText(target)) {
$target = $(getChildNodesArray(target, 'div'));
}
else if (index && isElement(target)) {
$target = $(target.cloneNode(true));
}
else {
$target = $(target);
}
$target[nameIndex ? 'insertAfter' : 'insertBefore'](element);
});
});
};
});
$.fn.off = function (types, selector, callback) {
// types 是对象
if (isObjectLike(types)) {
each(types, (type, fn) => {
// this.off('click', undefined, function () {})
// this.off('click', '.box', function () {})
this.off(type, selector, fn);
});
return this;
}
// selector 不存在
if (selector === false || isFunction(selector)) {
callback = selector;
selector = undefined;
// this.off('click', undefined, function () {})
}
// callback 传入 `false`,相当于 `return false`
if (callback === false) {
callback = returnFalse;
}
return this.each(function () {
remove(this, types, callback, selector);
});
};
$.fn.on = function (types, selector, data, callback, one) {
// types 可以是 type/func 对象
if (isObjectLike(types)) {
// (types-Object, selector, data)
if (!isString(selector)) {
// (types-Object, data)
data = data || selector;
selector = undefined;
}
each(types, (type, fn) => {
// selector 和 data 都可能是 undefined
// @ts-ignore
this.on(type, selector, data, fn, one);
});
return this;
}
if (data == null && callback == null) {
// (types, fn)
callback = selector;
data = selector = undefined;
}
else if (callback == null) {
if (isString(selector)) {
// (types, selector, fn)
callback = data;
data = undefined;
}
else {
// (types, data, fn)
callback = data;
data = selector;
selector = undefined;
}
}
if (callback === false) {
callback = returnFalse;
}
else if (!callback) {
return this;
}
// $().one()
if (one) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const _this = this;
const origCallback = callback;
callback = function (event) {
_this.off(event.type, selector, callback);
// eslint-disable-next-line prefer-rest-params
return origCallback.apply(this, arguments);
};
}
return this.each(function () {
add(this, types, callback, data, selector);
});
};
each(ajaxEvents, (name, eventName) => {
$.fn[name] = function (fn) {
return this.on(eventName, (e, params) => {
fn(e, params.xhr, params.options, params.data);
});
};
});
$.fn.map = function (callback) {
return new JQ(map(this, (element, i) => callback.call(element, i, element)));
};
$.fn.clone = function () {
return this.map(function () {
return this.cloneNode(true);
});
};
$.fn.is = function (selector) {
let isMatched = false;
if (isFunction(selector)) {
this.each((index, element) => {
if (selector.call(element, index, element)) {
isMatched = true;
}
});
return isMatched;
}
if (isString(selector)) {
this.each((_, element) => {
if (isDocument(element) || isWindow(element)) {
return;
}
// @ts-ignore
const matches = element.matches || element.msMatchesSelector;
if (matches.call(element, selector)) {
isMatched = true;
}
});
return isMatched;
}
const $compareWith = $(selector);
this.each((_, element) => {
$compareWith.each((_, compare) => {
if (element === compare) {
isMatched = true;
}
});
});
return isMatched;
};
$.fn.remove = function (selector) {
return this.each((_, element) => {
if (element.parentNode && (!selector || $(element).is(selector))) {
element.parentNode.removeChild(element);
}
});
};
each(['prepend', 'append'], (nameIndex, name) => {
$.fn[name] = function (...args) {
return this.each((index, element) => {
const childNodes = element.childNodes;
const childLength = childNodes.length;
const child = childLength
? childNodes[nameIndex ? childLength - 1 : 0]
: document.createElement('div');
if (!childLength) {
element.appendChild(child);
}
let contents = isFunction(args[0])
? [args[0].call(element, index, element.innerHTML)]
: args;
// 如果不是字符串,则仅第一个元素使用原始元素,其他的都克隆自第一个元素
if (index) {
contents = contents.map((content) => {
return isString(content) ? content : $(content).clone();
});
}
$(child)[nameIndex ? 'after' : 'before'](...contents);
if (!childLength) {
element.removeChild(child);
}
});
};
});
each(['appendTo', 'prependTo'], (nameIndex, name) => {
$.fn[name] = function (target) {
const extraChilds = [];
const $target = $(target).map((_, element) => {
const childNodes = element.childNodes;
const childLength = childNodes.length;
if (childLength) {
return childNodes[nameIndex ? 0 : childLength - 1];
}
const child = document.createElement('div');
element.appendChild(child);
extraChilds.push(child);
return child;
});
const $result = this[nameIndex ? 'insertBefore' : 'insertAfter']($target);
$(extraChilds).remove();
return $result;
};
});
each(['attr', 'prop', 'css'], (nameIndex, name) => {
function set(element, key, value) {
// 值为 undefined 时,不修改
if (isUndefined(value)) {
return;
}
switch (nameIndex) {
// attr
case 0:
if (isNull(value)) {
element.removeAttribute(key);
}
else {
element.setAttribute(key, value);
}
break;
// prop
case 1:
// @ts-ignore
element[key] = value;
break;
// css
default:
key = toCamelCase(key);
// @ts-ignore
element.style[key] = isNumber(value)
? `${value}${cssNumber.indexOf(key) > -1 ? '' : 'px'}`
: value;
break;
}
}
function get(element, key) {
switch (nameIndex) {
// attr
case 0:
// 属性不存在时,原生 getAttribute 方法返回 null而 jquery 返回 undefined。这里和 jquery 保持一致
const value = element.getAttribute(key);
return isNull(value) ? undefined : value;
// prop
case 1:
// @ts-ignore
return element[key];
// css
default:
return getStyle(element, key);
}
}
$.fn[name] = function (key, value) {
if (isObjectLike(key)) {
each(key, (k, v) => {
// @ts-ignore
this[name](k, v);
});
return this;
}
if (arguments.length === 1) {
const element = this[0];
return isElement(element) ? get(element, key) : undefined;
}
return this.each((i, element) => {
set(element, key, isFunction(value) ? value.call(element, i, get(element, key)) : value);
});
};
});
$.fn.children = function (selector) {
const children = [];
this.each((_, element) => {
each(element.childNodes, (__, childNode) => {
if (!isElement(childNode)) {
return;
}
if (!selector || $(childNode).is(selector)) {
children.push(childNode);
}
});
});
return new JQ(unique(children));
};
$.fn.slice = function (...args) {
return new JQ([].slice.apply(this, args));
};
$.fn.eq = function (index) {
const ret = index === -1 ? this.slice(index) : this.slice(index, +index + 1);
return new JQ(ret);
};
function dir($elements, nameIndex, node, selector, filter) {
const ret = [];
let target;
$elements.each((_, element) => {
target = element[node];
// 不能包含最顶层的 document 元素
while (target && isElement(target)) {
// prevUntil, nextUntil, parentsUntil
if (nameIndex === 2) {
if (selector && $(target).is(selector)) {
break;
}
if (!filter || $(target).is(filter)) {
ret.push(target);
}
}
// prev, next, parent
else if (nameIndex === 0) {
if (!selector || $(target).is(selector)) {
ret.push(target);
}
break;
}
// prevAll, nextAll, parents
else {
if (!selector || $(target).is(selector)) {
ret.push(target);
}
}
// @ts-ignore
target = target[node];
}
});
return new JQ(unique(ret));
}
each(['', 's', 'sUntil'], (nameIndex, name) => {
$.fn[`parent${name}`] = function (selector, filter) {
// parents、parentsUntil 需要把元素的顺序反向处理,以便和 jQuery 的结果一致
const $nodes = !nameIndex ? this : $(this.get().reverse());
return dir($nodes, nameIndex, 'parentNode', selector, filter);
};
});
$.fn.closest = function (selector) {
if (this.is(selector)) {
return this;
}
const matched = [];
this.parents().each((_, element) => {
if ($(element).is(selector)) {
matched.push(element);
return false;
}
});
return new JQ(matched);
};
const rbrace = /^(?:{[\w\W]*\}|\[[\w\W]*\])$/;
// 从 `data-*` 中获取的值,需要经过该函数转换
function getData(value) {
if (value === 'true') {
return true;
}
if (value === 'false') {
return false;
}
if (value === 'null') {
return null;
}
if (value === +value + '') {
return +value;
}
if (rbrace.test(value)) {
return JSON.parse(value);
}
return value;
}
// 若 value 不存在,则从 `data-*` 中获取值
function dataAttr(element, key, value) {
if (isUndefined(value) && element.nodeType === 1) {
const name = 'data-' + toKebabCase(key);
value = element.getAttribute(name);
if (isString(value)) {
try {
value = getData(value);
}
catch (e) { }
}
else {
value = undefined;
}
}
return value;
}
$.fn.data = function (key, value) {
// 获取所有值
if (isUndefined(key)) {
if (!this.length) {
return undefined;
}
const element = this[0];
const resultData = data(element);
// window, document 上不存在 `data-*` 属性
if (element.nodeType !== 1) {
return resultData;
}
// 从 `data-*` 中获取值
const attrs = element.attributes;
let i = attrs.length;
while (i--) {
if (attrs[i]) {
let name = attrs[i].name;
if (name.indexOf('data-') === 0) {
name = toCamelCase(name.slice(5));
resultData[name] = dataAttr(element, name, resultData[name]);
}
}
}
return resultData;
}
// 同时设置多个值
if (isObjectLike(key)) {
return this.each(function () {
data(this, key);
});
}
// value 传入了 undefined
if (arguments.length === 2 && isUndefined(value)) {
return this;
}
// 设置值
if (!isUndefined(value)) {
return this.each(function () {
data(this, key, value);
});
}
// 获取值
if (!this.length) {
return undefined;
}
return dataAttr(this[0], key, data(this[0], key));
};
$.fn.empty = function () {
return this.each(function () {
this.innerHTML = '';
});
};
$.fn.extend = function (obj) {
each(obj, (prop, value) => {
// 在 JQ 对象上扩展方法时,需要自己添加 typescript 的类型定义
$.fn[prop] = value;
});
return this;
};
$.fn.filter = function (selector) {
if (isFunction(selector)) {
return this.map((index, element) => selector.call(element, index, element) ? element : undefined);
}
if (isString(selector)) {
return this.map((_, element) => $(element).is(selector) ? element : undefined);
}
const $selector = $(selector);
return this.map((_, element) => $selector.get().indexOf(element) > -1 ? element : undefined);
};
$.fn.first = function () {
return this.eq(0);
};
$.fn.has = function (selector) {
const $targets = isString(selector) ? this.find(selector) : $(selector);
const { length } = $targets;
return this.map(function () {
for (let i = 0; i < length; i += 1) {
if (contains(this, $targets[i])) {
return this;
}
}
return;
});
};
$.fn.hasClass = function (className) {
return this[0].classList.contains(className);
};
/**
* 值上面的 padding、border、margin 处理
* @param element
* @param name
* @param value
* @param funcIndex
* @param includeMargin
* @param multiply
*/
function handleExtraWidth(element, name, value, funcIndex, includeMargin, multiply) {
// 获取元素的 padding, border, margin 宽度(两侧宽度的和)
const getExtraWidthValue = (extra) => {
return (getExtraWidth(element, name.toLowerCase(), extra) *
multiply);
};
if (funcIndex === 2 && includeMargin) {
value += getExtraWidthValue('margin');
}
if (isBorderBox(element)) {
// IE 为 box-sizing: border-box 时,得到的值不含 border 和 padding这里先修复
// 仅获取时需要处理multiply === 1 为 get
if (isIE() && multiply === 1) {
value += getExtraWidthValue('border');
value += getExtraWidthValue('padding');
}
if (funcIndex === 0) {
value -= getExtraWidthValue('border');
}
if (funcIndex === 1) {
value -= getExtraWidthValue('border');
value -= getExtraWidthValue('padding');
}
}
else {
if (funcIndex === 0) {
value += getExtraWidthValue('padding');
}
if (funcIndex === 2) {
value += getExtraWidthValue('border');
value += getExtraWidthValue('padding');
}
}
return value;
}
/**
* 获取元素的样式值
* @param element
* @param name
* @param funcIndex 0: innerWidth, innerHeight; 1: width, height; 2: outerWidth, outerHeight
* @param includeMargin
*/
function get(element, name, funcIndex, includeMargin) {
const clientProp = `client${name}`;
const scrollProp = `scroll${name}`;
const offsetProp = `offset${name}`;
const innerProp = `inner${name}`;
// $(window).width()
if (isWindow(element)) {
// outerWidth, outerHeight 需要包含滚动条的宽度
return funcIndex === 2
? element[innerProp]
: toElement(document)[clientProp];
}
// $(document).width()
if (isDocument(element)) {
const doc = toElement(element);
return Math.max(
// @ts-ignore
element.body[scrollProp], doc[scrollProp],
// @ts-ignore
element.body[offsetProp], doc[offsetProp], doc[clientProp]);
}
const value = parseFloat(getComputedStyleValue(element, name.toLowerCase()) || '0');
return handleExtraWidth(element, name, value, funcIndex, includeMargin, 1);
}
/**
* 设置元素的样式值
* @param element
* @param elementIndex
* @param name
* @param funcIndex 0: innerWidth, innerHeight; 1: width, height; 2: outerWidth, outerHeight
* @param includeMargin
* @param value
*/
function set(element, elementIndex, name, funcIndex, includeMargin, value) {
let computedValue = isFunction(value)
? value.call(element, elementIndex, get(element, name, funcIndex, includeMargin))
: value;
if (computedValue == null) {
return;
}
const $element = $(element);
const dimension = name.toLowerCase();
// 特殊的值,不需要计算 padding、border、margin
if (['auto', 'inherit', ''].indexOf(computedValue) > -1) {
$element.css(dimension, computedValue);
return;
}
// 其他值保留原始单位。注意:如果不使用 px 作为单位,则算出的值一般是不准确的
const suffix = computedValue.toString().replace(/\b[0-9.]*/, '');
const numerical = parseFloat(computedValue);
computedValue =
handleExtraWidth(element, name, numerical, funcIndex, includeMargin, -1) +
(suffix || 'px');
$element.css(dimension, computedValue);
}
each(['Width', 'Height'], (_, name) => {
each([`inner${name}`, name.toLowerCase(), `outer${name}`], (funcIndex, funcName) => {
$.fn[funcName] = function (margin, value) {
// 是否是赋值操作
const isSet = arguments.length && (funcIndex < 2 || !isBoolean(margin));
const includeMargin = margin === true || value === true;
// 获取第一个元素的值
if (!isSet) {
return this.length
? get(this[0], name, funcIndex, includeMargin)
: undefined;
}
// 设置每个元素的值
return this.each((index, element) => set(element, index, name, funcIndex, includeMargin, margin));
};
});
});
$.fn.hide = function () {
return this.each(function () {
this.style.display = 'none';
});
};
each(['val', 'html', 'text'], (nameIndex, name) => {
const props = {
0: 'value',
1: 'innerHTML',
2: 'textContent',
};
const propName = props[nameIndex];
function get($elements) {
// text() 获取所有元素的文本
if (nameIndex === 2) {
// @ts-ignore
return map($elements, (element) => toElement(element)[propName]).join('');
}
// 空集合时val() 和 html() 返回 undefined
if (!$elements.length) {
return undefined;
}
// val() 和 html() 仅获取第一个元素的内容
const firstElement = $elements[0];
// select multiple 返回数组
if (nameIndex === 0 && $(firstElement).is('select[multiple]')) {
return map($(firstElement).find('option:checked'), (element) => element.value);
}
// @ts-ignore
return firstElement[propName];
}
function set(element, value) {
// text() 和 html() 赋值为 undefined则保持原内容不变
// val() 赋值为 undefined 则赋值为空
if (isUndefined(value)) {
if (nameIndex !== 0) {
return;
}
value = '';
}
if (nameIndex === 1 && isElement(value)) {
value = value.outerHTML;
}
// @ts-ignore
element[propName] = value;
}
$.fn[name] = function (value) {
// 获取值
if (!arguments.length) {
return get(this);
}
// 设置值
return this.each((i, element) => {
const computedValue = isFunction(value)
? value.call(element, i, get($(element)))
: value;
// value 是数组,则选中数组中的元素,反选不在数组中的元素
if (nameIndex === 0 && Array.isArray(computedValue)) {
// select[multiple]
if ($(element).is('select[multiple]')) {
map($(element).find('option'), (option) => (option.selected =
computedValue.indexOf(option.value) >
-1));
}
// 其他 checkbox, radio 等元素
else {
element.checked =
computedValue.indexOf(element.value) > -1;
}
}
else {
set(element, computedValue);
}
});
};
});
$.fn.index = function (selector) {
if (!arguments.length) {
return this.eq(0).parent().children().get().indexOf(this[0]);
}
if (isString(selector)) {
return $(selector).get().indexOf(this[0]);
}
return this.get().indexOf($(selector)[0]);
};
$.fn.last = function () {
return this.eq(-1);
};
each(['', 'All', 'Until'], (nameIndex, name) => {
$.fn[`next${name}`] = function (selector, filter) {
return dir(this, nameIndex, 'nextElementSibling', selector, filter);
};
});
$.fn.not = function (selector) {
const $excludes = this.filter(selector);
return this.map((_, element) => $excludes.index(element) > -1 ? undefined : element);
};
/**
* 返回最近的用于定位的父元素
*/
$.fn.offsetParent = function () {
return this.map(function () {
let offsetParent = this.offsetParent;
while (offsetParent && $(offsetParent).css('position') === 'static') {
offsetParent = offsetParent.offsetParent;
}
return offsetParent || document.documentElement;
});
};
function floatStyle($element, name) {
return parseFloat($element.css(name));
}
$.fn.position = function () {
if (!this.length) {
return undefined;
}
const $element = this.eq(0);
let currentOffset;
let parentOffset = {
left: 0,
top: 0,
};
if ($element.css('position') === 'fixed') {
currentOffset = $element[0].getBoundingClientRect();
}
else {
currentOffset = $element.offset();
const $offsetParent = $element.offsetParent();
parentOffset = $offsetParent.offset();
parentOffset.top += floatStyle($offsetParent, 'border-top-width');
parentOffset.left += floatStyle($offsetParent, 'border-left-width');
}
return {
top: currentOffset.top - parentOffset.top - floatStyle($element, 'margin-top'),
left: currentOffset.left -
parentOffset.left -
floatStyle($element, 'margin-left'),
};
};
function get$1(element) {
if (!element.getClientRects().length) {
return { top: 0, left: 0 };
}
const rect = element.getBoundingClientRect();
const win = element.ownerDocument.defaultView;
return {
top: rect.top + win.pageYOffset,
left: rect.left + win.pageXOffset,
};
}
function set$1(element, value, index) {
const $element = $(element);
const position = $element.css('position');
if (position === 'static') {
$element.css('position', 'relative');
}
const currentOffset = get$1(element);
const currentTopString = $element.css('top');
const currentLeftString = $element.css('left');
let currentTop;
let currentLeft;
const calculatePosition = (position === 'absolute' || position === 'fixed') &&
(currentTopString + currentLeftString).indexOf('auto') > -1;
if (calculatePosition) {
const currentPosition = $element.position();
currentTop = currentPosition.top;
currentLeft = currentPosition.left;
}
else {
currentTop = parseFloat(currentTopString);
currentLeft = parseFloat(currentLeftString);
}
const computedValue = isFunction(value)
? value.call(element, index, extend({}, currentOffset))
: value;
$element.css({
top: computedValue.top != null
? computedValue.top - currentOffset.top + currentTop
: undefined,
left: computedValue.left != null
? computedValue.left - currentOffset.left + currentLeft
: undefined,
});
}
$.fn.offset = function (value) {
// 获取坐标
if (!arguments.length) {
if (!this.length) {
return undefined;
}
return get$1(this[0]);
}
// 设置坐标
return this.each(function (index) {
set$1(this, value, index);
});
};
$.fn.one = function (types, selector, data, callback) {
// @ts-ignore
return this.on(types, selector, data, callback, true);
};
each(['', 'All', 'Until'], (nameIndex, name) => {
$.fn[`prev${name}`] = function (selector, filter) {
// prevAll、prevUntil 需要把元素的顺序倒序处理,以便和 jQuery 的结果一致
const $nodes = !nameIndex ? this : $(this.get().reverse());
return dir($nodes, nameIndex, 'previousElementSibling', selector, filter);
};
});
$.fn.removeAttr = function (attributeName) {
const names = attributeName.split(' ').filter((name) => name);
return this.each(function () {
each(names, (_, name) => {
this.removeAttribute(name);
});
});
};
$.fn.removeData = function (name) {
return this.each(function () {
removeData(this, name);
});
};
$.fn.removeProp = function (name) {
return this.each(function () {
try {
// @ts-ignore
delete this[name];
}
catch (e) { }
});
};
$.fn.replaceWith = function (newContent) {
this.each((index, element) => {
let content = newContent;
if (isFunction(content)) {
content = content.call(element, index, element.innerHTML);
}
else if (index && !isString(content)) {
content = $(content).clone();
}
$(element).before(content);
});
return this.remove();
};
$.fn.replaceAll = function (target) {
return $(target).map((index, element) => {
$(element).replaceWith(index ? this.clone() : this);
return this.get();
});
};
/**
* 将表单元素的值组合成键值对数组
* @returns {Array}
*/
$.fn.serializeArray = function () {
const result = [];
this.each((_, element) => {
const elements = element instanceof HTMLFormElement ? element.elements : [element];
$(elements).each((_, element) => {
const $element = $(element);
const type = element.type;
const nodeName = element.nodeName.toLowerCase();
if (nodeName !== 'fieldset' &&
element.name &&
!element.disabled &&
['input', 'select', 'textarea', 'keygen'].indexOf(nodeName) > -1 &&
['submit', 'button', 'image', 'reset', 'file'].indexOf(type) === -1 &&
(['radio', 'checkbox'].indexOf(type) === -1 ||
element.checked)) {
const value = $element.val();
const valueArr = Array.isArray(value) ? value : [value];
valueArr.forEach((value) => {
result.push({
name: element.name,
value,
});
});
}
});
});
return result;
};
$.fn.serialize = function () {
return param(this.serializeArray());
};
const elementDisplay = {};
/**
* 获取元素的初始 display 值,用于 .show() 方法
* @param nodeName
*/
function defaultDisplay(nodeName) {
let element;
let display;
if (!elementDisplay[nodeName]) {
element = document.createElement(nodeName);
document.body.appendChild(element);
display = getStyle(element, 'display');
element.parentNode.removeChild(element);
if (display === 'none') {
display = 'block';
}
elementDisplay[nodeName] = display;
}
return elementDisplay[nodeName];
}
/**
* 显示指定元素
* @returns {JQ}
*/
$.fn.show = function () {
return this.each(function () {
if (this.style.display === 'none') {
this.style.display = '';
}
if (getStyle(this, 'display') === 'none') {
this.style.display = defaultDisplay(this.nodeName);
}
});
};
/**
* 取得同辈元素的集合
* @param selector {String=}
* @returns {JQ}
*/
$.fn.siblings = function (selector) {
return this.prevAll(selector).add(this.nextAll(selector));
};
/**
* 切换元素的显示状态
*/
$.fn.toggle = function () {
return this.each(function () {
getStyle(this, 'display') === 'none' ? $(this).show() : $(this).hide();
});
};
$.fn.reflow = function () {
return this.each(function () {
return this.clientLeft;
});
};
$.fn.transition = function (duration) {
if (isNumber(duration)) {
duration = `${duration}ms`;
}
return this.each(function () {
this.style.webkitTransitionDuration = duration;
this.style.transitionDuration = duration;
});
};
$.fn.transitionEnd = function (callback) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
const events = ['webkitTransitionEnd', 'transitionend'];
function fireCallback(e) {
if (e.target !== this) {
return;
}
// @ts-ignore
callback.call(this, e);
each(events, (_, event) => {
that.off(event, fireCallback);
});
}
each(events, (_, event) => {
that.on(event, fireCallback);
});
return this;
};
$.fn.transformOrigin = function (transformOrigin) {
return this.each(function () {
this.style.webkitTransformOrigin = transformOrigin;
this.style.transformOrigin = transformOrigin;
});
};
$.fn.transform = function (transform) {
return this.each(function () {
this.style.webkitTransform = transform;
this.style.transform = transform;
});
};
/**
* CSS 选择器和初始化函数组成的对象
*/
const entries = {};
/**
* 注册并执行初始化函数
* @param selector CSS 选择器
* @param apiInit 初始化函数
* @param i 元素索引
* @param element 元素
*/
function mutation(selector, apiInit, i, element) {
let selectors = data(element, '_mdui_mutation');
if (!selectors) {
selectors = [];
data(element, '_mdui_mutation', selectors);
}
if (selectors.indexOf(selector) === -1) {
selectors.push(selector);
apiInit.call(element, i, element);
}
}
$.fn.mutation = function () {
return this.each((i, element) => {
const $this = $(element);
each(entries, (selector, apiInit) => {
if ($this.is(selector)) {
mutation(selector, apiInit, i, element);
}
$this.find(selector).each((i, element) => {
mutation(selector, apiInit, i, element);
});
});
});
};
$.showOverlay = function (zIndex) {
let $overlay = $('.mdui-overlay');
if ($overlay.length) {
$overlay.data('_overlay_is_deleted', false);
if (!isUndefined(zIndex)) {
$overlay.css('z-index', zIndex);
}
}
else {
if (isUndefined(zIndex)) {
zIndex = 2000;
}
$overlay = $('<div class="mdui-overlay">')
.appendTo(document.body)
.reflow()
.css('z-index', zIndex);
}
let level = $overlay.data('_overlay_level') || 0;
return $overlay.data('_overlay_level', ++level).addClass('mdui-overlay-show');
};
$.hideOverlay = function (force = false) {
const $overlay = $('.mdui-overlay');
if (!$overlay.length) {
return;
}
let level = force ? 1 : $overlay.data('_overlay_level');
if (level > 1) {
$overlay.data('_overlay_level', --level);
return;
}
$overlay
.data('_overlay_level', 0)
.removeClass('mdui-overlay-show')
.data('_overlay_is_deleted', true)
.transitionEnd(() => {
if ($overlay.data('_overlay_is_deleted')) {
$overlay.remove();
}
});
};
$.lockScreen = function () {
const $body = $('body');
// 不直接把 body 设为 box-sizing: border-box避免污染全局样式
const newBodyWidth = $body.width();
let level = $body.data('_lockscreen_level') || 0;
$body
.addClass('mdui-locked')
.width(newBodyWidth)
.data('_lockscreen_level', ++level);
};
$.unlockScreen = function (force = false) {
const $body = $('body');
let level = force ? 1 : $body.data('_lockscreen_level');
if (level > 1) {
$body.data('_lockscreen_level', --level);
return;
}
$body.data('_lockscreen_level', 0).removeClass('mdui-locked').width('');
};
$.throttle = function (fn, delay = 16) {
let timer = null;
return function (...args) {
if (isNull(timer)) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
};
const GUID = {};
$.guid = function (name) {
if (!isUndefined(name) && !isUndefined(GUID[name])) {
return GUID[name];
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
const guid = '_' +
s4() +
s4() +
'-' +
s4() +
'-' +
s4() +
'-' +
s4() +
'-' +
s4() +
s4() +
s4();
if (!isUndefined(name)) {
GUID[name] = guid;
}
return guid;
};
mdui.mutation = function (selector, apiInit) {
if (isUndefined(selector) || isUndefined(apiInit)) {
$(document).mutation();
return;
}
entries[selector] = apiInit;
$(selector).each((i, element) => mutation(selector, apiInit, i, element));
};
/**
* 触发组件上的事件
* @param eventName 事件名
* @param componentName 组件名
* @param target 在该元素上触发事件
* @param instance 组件实例
* @param parameters 事件参数
*/
function componentEvent(eventName, componentName, target, instance, parameters) {
if (!parameters) {
parameters = {};
}
// @ts-ignore
parameters.inst = instance;
const fullEventName = `${eventName}.mdui.${componentName}`;
// jQuery 事件
// @ts-ignore
if (typeof jQuery !== 'undefined') {
// @ts-ignore
jQuery(target).trigger(fullEventName, parameters);
}
const $target = $(target);
// mdui.jq 事件
$target.trigger(fullEventName, parameters);
const eventParams = {
bubbles: true,
cancelable: true,
detail: parameters,
};
const eventObject = new CustomEvent(fullEventName, eventParams);
// @ts-ignore
eventObject._detail = parameters;
$target[0].dispatchEvent(eventObject);
}
const $document = $(document);
const $window = $(window);
const $body = $('body');
const DEFAULT_OPTIONS = {
tolerance: 5,
offset: 0,
initialClass: 'mdui-headroom',
pinnedClass: 'mdui-headroom-pinned-top',
unpinnedClass: 'mdui-headroom-unpinned-top',
};
class Headroom {
constructor(selector, options = {}) {
/**
* 配置参数
*/
this.options = extend({}, DEFAULT_OPTIONS);
/**
* 当前 headroom 的状态
*/
this.state = 'pinned';
/**
* 当前是否启用
*/
this.isEnable = false;
/**
* 上次滚动后,垂直方向的距离
*/
this.lastScrollY = 0;
/**
* AnimationFrame ID
*/
this.rafId = 0;
this.$element = $(selector).first();
extend(this.options, options);
// tolerance 参数若为数值,转换为对象
const tolerance = this.options.tolerance;
if (isNumber(tolerance)) {
this.options.tolerance = {
down: tolerance,
up: tolerance,
};
}
this.enable();
}
/**
* 滚动时的处理
*/
onScroll() {
this.rafId = window.requestAnimationFrame(() => {
const currentScrollY = window.pageYOffset;
const direction = currentScrollY > this.lastScrollY ? 'down' : 'up';
const tolerance = this.options.tolerance[direction];
const scrolled = Math.abs(currentScrollY - this.lastScrollY);
const toleranceExceeded = scrolled >= tolerance;
if (currentScrollY > this.lastScrollY &&
currentScrollY >= this.options.offset &&
toleranceExceeded) {
this.unpin();
}
else if ((currentScrollY < this.lastScrollY && toleranceExceeded) ||
currentScrollY <= this.options.offset) {
this.pin();
}
this.lastScrollY = currentScrollY;
});
}
/**
* 触发组件事件
* @param name
*/
triggerEvent(name) {
componentEvent(name, 'headroom', this.$element, this);
}
/**
* 动画结束的回调
*/
transitionEnd() {
if (this.state === 'pinning') {
this.state = 'pinned';
this.triggerEvent('pinned');
}
if (this.state === 'unpinning') {
this.state = 'unpinned';
this.triggerEvent('unpinned');
}
}
/**
* 使元素固定住
*/
pin() {
if (this.state === 'pinning' ||
this.state === 'pinned' ||
!this.$element.hasClass(this.options.initialClass)) {
return;
}
this.triggerEvent('pin');
this.state = 'pinning';
this.$element
.removeClass(this.options.unpinnedClass)
.addClass(this.options.pinnedClass)
.transitionEnd(() => this.transitionEnd());
}
/**
* 使元素隐藏
*/
unpin() {
if (this.state === 'unpinning' ||
this.state === 'unpinned' ||
!this.$element.hasClass(this.options.initialClass)) {
return;
}
this.triggerEvent('unpin');
this.state = 'unpinning';
this.$element
.removeClass(this.options.pinnedClass)
.addClass(this.options.unpinnedClass)
.transitionEnd(() => this.transitionEnd());
}
/**
* 启用 headroom 插件
*/
enable() {
if (this.isEnable) {
return;
}
this.isEnable = true;
this.state = 'pinned';
this.$element
.addClass(this.options.initialClass)
.removeClass(this.options.pinnedClass)
.removeClass(this.options.unpinnedClass);
this.lastScrollY = window.pageYOffset;
$window.on('scroll', () => this.onScroll());
}
/**
* 禁用 headroom 插件
*/
disable() {
if (!this.isEnable) {
return;
}
this.isEnable = false;
this.$element
.removeClass(this.options.initialClass)
.removeClass(this.options.pinnedClass)
.removeClass(this.options.unpinnedClass);
$window.off('scroll', () => this.onScroll());
window.cancelAnimationFrame(this.rafId);
}
/**
* 获取当前状态。共包含四种状态:`pinning`、`pinned`、`unpinning`、`unpinned`
*/
getState() {
return this.state;
}
}
mdui.Headroom = Headroom;
/**
* 解析 DATA API 参数
* @param element 元素
* @param name 属性名
*/
function parseOptions(element, name) {
const attr = $(element).attr(name);
if (!attr) {
return {};
}
return new Function('', `var json = ${attr}; return JSON.parse(JSON.stringify(json));`)();
}
const customAttr = 'mdui-headroom';
$(() => {
mdui.mutation(`[${customAttr}]`, function () {
new mdui.Headroom(this, parseOptions(this, customAttr));
});
});
const DEFAULT_OPTIONS$1 = {
accordion: false,
};
class CollapseAbstract {
constructor(selector, options = {}) {
/**
* 配置参数
*/
this.options = extend({}, DEFAULT_OPTIONS$1);
// CSS 类名
const classPrefix = `mdui-${this.getNamespace()}-item`;
this.classItem = classPrefix;
this.classItemOpen = `${classPrefix}-open`;
this.classHeader = `${classPrefix}-header`;
this.classBody = `${classPrefix}-body`;
this.$element = $(selector).first();
extend(this.options, options);
this.bindEvent();
}
/**
* 绑定事件
*/
bindEvent() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
// 点击 header 时,打开/关闭 item
this.$element.on('click', `.${this.classHeader}`, function () {
const $header = $(this);
const $item = $header.parent();
const $items = that.getItems();
$items.each((_, item) => {
if ($item.is(item)) {
that.toggle(item);
}
});
});
// 点击关闭按钮时,关闭 item
this.$element.on('click', `[mdui-${this.getNamespace()}-item-close]`, function () {
const $target = $(this);
const $item = $target.parents(`.${that.classItem}`).first();
that.close($item);
});
}
/**
* 指定 item 是否处于打开状态
* @param $item
*/
isOpen($item) {
return $item.hasClass(this.classItemOpen);
}
/**
* 获取所有 item
*/
getItems() {
return this.$element.children(`.${this.classItem}`);
}
/**
* 获取指定 item
* @param item
*/
getItem(item) {
if (isNumber(item)) {
return this.getItems().eq(item);
}
return $(item).first();
}
/**
* 触发组件事件
* @param name 事件名
* @param $item 事件触发的目标 item
*/
triggerEvent(name, $item) {
componentEvent(name, this.getNamespace(), $item, this);
}
/**
* 动画结束回调
* @param $content body 元素
* @param $item item 元素
*/
transitionEnd($content, $item) {
if (this.isOpen($item)) {
$content.transition(0).height('auto').reflow().transition('');
this.triggerEvent('opened', $item);
}
else {
$content.height('');
this.triggerEvent('closed', $item);
}
}
/**
* 打开指定面板项
* @param item 面板项的索引号、或 CSS 选择器、或 DOM 元素、或 JQ 对象
*/
open(item) {
const $item = this.getItem(item);
if (this.isOpen($item)) {
return;
}
// 关闭其他项
if (this.options.accordion) {
this.$element.children(`.${this.classItemOpen}`).each((_, element) => {
const $element = $(element);
if (!$element.is($item)) {
this.close($element);
}
});
}
const $content = $item.children(`.${this.classBody}`);
$content
.height($content[0].scrollHeight)
.transitionEnd(() => this.transitionEnd($content, $item));
this.triggerEvent('open', $item);
$item.addClass(this.classItemOpen);
}
/**
* 关闭指定面板项
* @param item 面板项的索引号、或 CSS 选择器、或 DOM 元素、或 JQ 对象
*/
close(item) {
const $item = this.getItem(item);
if (!this.isOpen($item)) {
return;
}
const $content = $item.children(`.${this.classBody}`);
this.triggerEvent('close', $item);
$item.removeClass(this.classItemOpen);
$content
.transition(0)
.height($content[0].scrollHeight)
.reflow()
.transition('')
.height('')
.transitionEnd(() => this.transitionEnd($content, $item));
}
/**
* 切换指定面板项的打开状态
* @param item 面板项的索引号、或 CSS 选择器、或 DOM 元素、或 JQ 对象
*/
toggle(item) {
const $item = this.getItem(item);
this.isOpen($item) ? this.close($item) : this.open($item);
}
/**
* 打开所有面板项
*/
openAll() {
this.getItems().each((_, element) => this.open(element));
}
/**
* 关闭所有面板项
*/
closeAll() {
this.getItems().each((_, element) => this.close(element));
}
}
class Collapse extends CollapseAbstract {
getNamespace() {
return 'collapse';
}
}
mdui.Collapse = Collapse;
const customAttr$1 = 'mdui-collapse';
$(() => {
mdui.mutation(`[${customAttr$1}]`, function () {
new mdui.Collapse(this, parseOptions(this, customAttr$1));
});
});
class Panel extends CollapseAbstract {
getNamespace() {
return 'panel';
}
}
mdui.Panel = Panel;
const customAttr$2 = 'mdui-panel';
$(() => {
mdui.mutation(`[${customAttr$2}]`, function () {
new mdui.Panel(this, parseOptions(this, customAttr$2));
});
});
class Table {
constructor(selector) {
/**
* 表头 tr 元素
*/
this.$thRow = $();
/**
* 表格 body 中的 tr 元素
*/
this.$tdRows = $();
/**
* 表头的 checkbox 元素
*/
this.$thCheckbox = $();
/**
* 表格 body 中的 checkbox 元素
*/
this.$tdCheckboxs = $();
/**
* 表格行是否可选择
*/
this.selectable = false;
/**
* 已选中的行数
*/
this.selectedRow = 0;
this.$element = $(selector).first();
this.init();
}
/**
* 初始化表格
*/
init() {
this.$thRow = this.$element.find('thead tr');
this.$tdRows = this.$element.find('tbody tr');
this.selectable = this.$element.hasClass('mdui-table-selectable');
this.updateThCheckbox();
this.updateTdCheckbox();
this.updateNumericCol();
}
/**
* 生成 checkbox 的 HTML 结构
* @param tag 标签名
*/
createCheckboxHTML(tag) {
return (`<${tag} class="mdui-table-cell-checkbox">` +
'<label class="mdui-checkbox">' +
'<input type="checkbox"/>' +
'<i class="mdui-checkbox-icon"></i>' +
'</label>' +
`</${tag}>`);
}
/**
* 更新表头 checkbox 的状态
*/
updateThCheckboxStatus() {
const checkbox = this.$thCheckbox[0];
const selectedRow = this.selectedRow;
const tdRowsLength = this.$tdRows.length;
checkbox.checked = selectedRow === tdRowsLength;
checkbox.indeterminate = !!selectedRow && selectedRow !== tdRowsLength;
}
/**
* 更新表格行的 checkbox
*/
updateTdCheckbox() {
const rowSelectedClass = 'mdui-table-row-selected';
this.$tdRows.each((_, row) => {
const $row = $(row);
// 移除旧的 checkbox
$row.find('.mdui-table-cell-checkbox').remove();
if (!this.selectable) {
return;
}
// 创建 DOM
const $checkbox = $(this.createCheckboxHTML('td'))
.prependTo($row)
.find('input[type="checkbox"]');
// 默认选中的行
if ($row.hasClass(rowSelectedClass)) {
$checkbox[0].checked = true;
this.selectedRow++;
}
this.updateThCheckboxStatus();
// 绑定事件
$checkbox.on('change', () => {
if ($checkbox[0].checked) {
$row.addClass(rowSelectedClass);
this.selectedRow++;
}
else {
$row.removeClass(rowSelectedClass);
this.selectedRow--;
}
this.updateThCheckboxStatus();
});
this.$tdCheckboxs = this.$tdCheckboxs.add($checkbox);
});
}
/**
* 更新表头的 checkbox
*/
updateThCheckbox() {
// 移除旧的 checkbox
this.$thRow.find('.mdui-table-cell-checkbox').remove();
if (!this.selectable) {
return;
}
this.$thCheckbox = $(this.createCheckboxHTML('th'))
.prependTo(this.$thRow)
.find('input[type="checkbox"]')
.on('change', () => {
const isCheckedAll = this.$thCheckbox[0].checked;
this.selectedRow = isCheckedAll ? this.$tdRows.length : 0;
this.$tdCheckboxs.each((_, checkbox) => {
checkbox.checked = isCheckedAll;
});
this.$tdRows.each((_, row) => {
isCheckedAll
? $(row).addClass('mdui-table-row-selected')
: $(row).removeClass('mdui-table-row-selected');
});
});
}
/**
* 更新数值列
*/
updateNumericCol() {
const numericClass = 'mdui-table-col-numeric';
this.$thRow.find('th').each((i, th) => {
const isNumericCol = $(th).hasClass(numericClass);
this.$tdRows.each((_, row) => {
const $td = $(row).find('td').eq(i);
isNumericCol
? $td.addClass(numericClass)
: $td.removeClass(numericClass);
});
});
}
}
const dataName = '_mdui_table';
$(() => {
mdui.mutation('.mdui-table', function () {
const $element = $(this);
if (!$element.data(dataName)) {
$element.data(dataName, new Table($element));
}
});
});
mdui.updateTables = function (selector) {
const $elements = isUndefined(selector) ? $('.mdui-table') : $(selector);
$elements.each((_, element) => {
const $element = $(element);
const instance = $element.data(dataName);
if (instance) {
instance.init();
}
else {
$element.data(dataName, new Table($element));
}
});
};
/**
* touch 事件后的 500ms 内禁用 mousedown 事件
*
* 不支持触控的屏幕上事件顺序为 mousedown -> mouseup -> click
* 支持触控的屏幕上事件顺序为 touchstart -> touchend -> mousedown -> mouseup -> click
*
* 在每一个事件中都使用 TouchHandler.isAllow(event) 判断事件是否可执行
* 在 touchstart 和 touchmove、touchend、touchcancel
*
* (function () {
* $document
* .on(start, function (e) {
* if (!isAllow(e)) {
* return;
* }
* register(e);
* console.log(e.type);
* })
* .on(move, function (e) {
* if (!isAllow(e)) {
* return;
* }
* console.log(e.type);
* })
* .on(end, function (e) {
* if (!isAllow(e)) {
* return;
* }
* console.log(e.type);
* })
* .on(unlock, register);
* })();
*/
const startEvent = 'touchstart mousedown';
const moveEvent = 'touchmove mousemove';
const endEvent = 'touchend mouseup';
const cancelEvent = 'touchcancel mouseleave';
const unlockEvent = 'touchend touchmove touchcancel';
let touches = 0;
/**
* 该事件是否被允许,在执行事件前调用该方法判断事件是否可以执行
* 若已触发 touch 事件,则阻止之后的鼠标事件
* @param event
*/
function isAllow(event) {
return !(touches &&
[
'mousedown',
'mouseup',
'mousemove',
'click',
'mouseover',
'mouseout',
'mouseenter',
'mouseleave',
].indexOf(event.type) > -1);
}
/**
* 在 touchstart 和 touchmove、touchend、touchcancel 事件中调用该方法注册事件
* @param event
*/
function register(event) {
if (event.type === 'touchstart') {
// 触发了 touch 事件
touches += 1;
}
else if (['touchmove', 'touchend', 'touchcancel'].indexOf(event.type) > -1) {
// touch 事件结束 500ms 后解除对鼠标事件的阻止
setTimeout(function () {
if (touches) {
touches -= 1;
}
}, 500);
}
}
/**
* Inspired by https://github.com/nolimits4web/Framework7/blob/master/src/js/fast-clicks.js
* https://github.com/nolimits4web/Framework7/blob/master/LICENSE
*
* Inspired by https://github.com/fians/Waves
*/
/**
* 显示涟漪动画
* @param event
* @param $ripple
*/
function show(event, $ripple) {
// 鼠标右键不产生涟漪
if (event instanceof MouseEvent && event.button === 2) {
return;
}
// 点击位置坐标
const touchPosition = typeof TouchEvent !== 'undefined' &&
event instanceof TouchEvent &&
event.touches.length
? event.touches[0]
: event;
const touchStartX = touchPosition.pageX;
const touchStartY = touchPosition.pageY;
// 涟漪位置
const offset = $ripple.offset();
const height = $ripple.innerHeight();
const width = $ripple.innerWidth();
const center = {
x: touchStartX - offset.left,
y: touchStartY - offset.top,
};
const diameter = Math.max(Math.pow(Math.pow(height, 2) + Math.pow(width, 2), 0.5), 48);
// 涟漪扩散动画
const translate = `translate3d(${-center.x + width / 2}px,` +
`${-center.y + height / 2}px, 0) scale(1)`;
// 涟漪的 DOM 结构,并缓存动画效果
$(`<div class="mdui-ripple-wave" ` +
`style="width:${diameter}px;height:${diameter}px;` +
`margin-top:-${diameter / 2}px;margin-left:-${diameter / 2}px;` +
`left:${center.x}px;top:${center.y}px;"></div>`)
.data('_ripple_wave_translate', translate)
.prependTo($ripple)
.reflow()
.transform(translate);
}
/**
* 隐藏并移除涟漪
* @param $wave
*/
function removeRipple($wave) {
if (!$wave.length || $wave.data('_ripple_wave_removed')) {
return;
}
$wave.data('_ripple_wave_removed', true);
let removeTimer = setTimeout(() => $wave.remove(), 400);
const translate = $wave.data('_ripple_wave_translate');
$wave
.addClass('mdui-ripple-wave-fill')
.transform(translate.replace('scale(1)', 'scale(1.01)'))
.transitionEnd(() => {
clearTimeout(removeTimer);
$wave
.addClass('mdui-ripple-wave-out')
.transform(translate.replace('scale(1)', 'scale(1.01)'));
removeTimer = setTimeout(() => $wave.remove(), 700);
setTimeout(() => {
$wave.transitionEnd(() => {
clearTimeout(removeTimer);
$wave.remove();
});
}, 0);
});
}
/**
* 隐藏涟漪动画
* @param this
*/
function hide() {
const $ripple = $(this);
$ripple.children('.mdui-ripple-wave').each((_, wave) => {
removeRipple($(wave));
});
$ripple.off(`${moveEvent} ${endEvent} ${cancelEvent}`, hide);
}
/**
* 显示涟漪,并绑定 touchend 等事件
* @param event
*/
function showRipple(event) {
if (!isAllow(event)) {
return;
}
register(event);
// Chrome 59 点击滚动条时,会在 document 上触发事件
if (event.target === document) {
return;
}
const $target = $(event.target);
// 获取含 .mdui-ripple 类的元素
const $ripple = $target.hasClass('mdui-ripple')
? $target
: $target.parents('.mdui-ripple').first();
if (!$ripple.length) {
return;
}
// 禁用状态的元素上不产生涟漪效果
if ($ripple.prop('disabled') || !isUndefined($ripple.attr('disabled'))) {
return;
}
if (event.type === 'touchstart') {
let hidden = false;
// touchstart 触发指定时间后开始涟漪动画,避免手指滑动时也触发涟漪
let timer = setTimeout(() => {
timer = 0;
show(event, $ripple);
}, 200);
const hideRipple = () => {
// 如果手指没有移动,且涟漪动画还没有开始,则开始涟漪动画
if (timer) {
clearTimeout(timer);
timer = 0;
show(event, $ripple);
}
if (!hidden) {
hidden = true;
hide.call($ripple);
}
};
// 手指移动后,移除涟漪动画
const touchMove = () => {
if (timer) {
clearTimeout(timer);
timer = 0;
}
hideRipple();
};
$ripple.on('touchmove', touchMove).on('touchend touchcancel', hideRipple);
}
else {
show(event, $ripple);
$ripple.on(`${moveEvent} ${endEvent} ${cancelEvent}`, hide);
}
}
$(() => {
$document.on(startEvent, showRipple).on(unlockEvent, register);
});
const defaultData = {
reInit: false,
domLoadedEvent: false,
};
/**
* 输入框事件
* @param event
* @param data
*/
function inputEvent(event, data = {}) {
data = extend({}, defaultData, data);
const input = event.target;
const $input = $(input);
const eventType = event.type;
const value = $input.val();
// 文本框类型
const inputType = $input.attr('type') || '';
if (['checkbox', 'button', 'submit', 'range', 'radio', 'image'].indexOf(inputType) > -1) {
return;
}
const $textfield = $input.parent('.mdui-textfield');
// 输入框是否聚焦
if (eventType === 'focus') {
$textfield.addClass('mdui-textfield-focus');
}
if (eventType === 'blur') {
$textfield.removeClass('mdui-textfield-focus');
}
// 输入框是否为空
if (eventType === 'blur' || eventType === 'input') {
value
? $textfield.addClass('mdui-textfield-not-empty')
: $textfield.removeClass('mdui-textfield-not-empty');
}
// 输入框是否禁用
input.disabled
? $textfield.addClass('mdui-textfield-disabled')
: $textfield.removeClass('mdui-textfield-disabled');
// 表单验证
if ((eventType === 'input' || eventType === 'blur') &&
!data.domLoadedEvent &&
input.validity) {
input.validity.valid
? $textfield.removeClass('mdui-textfield-invalid-html5')
: $textfield.addClass('mdui-textfield-invalid-html5');
}
// textarea 高度自动调整
if ($input.is('textarea')) {
// IE bugtextarea 的值仅为多个换行不含其他内容时textarea 的高度不准确
// 此时,在计算高度前,在值的开头加入一个空格,计算完后,移除空格
const inputValue = value;
let hasExtraSpace = false;
if (inputValue.replace(/[\r\n]/g, '') === '') {
$input.val(' ' + inputValue);
hasExtraSpace = true;
}
// 设置 textarea 高度
$input.outerHeight('');
const height = $input.outerHeight();
const scrollHeight = input.scrollHeight;
if (scrollHeight > height) {
$input.outerHeight(scrollHeight);
}
// 计算完,还原 textarea 的值
if (hasExtraSpace) {
$input.val(inputValue);
}
}
// 实时字数统计
if (data.reInit) {
$textfield.find('.mdui-textfield-counter').remove();
}
const maxLength = $input.attr('maxlength');
if (maxLength) {
if (data.reInit || data.domLoadedEvent) {
$('<div class="mdui-textfield-counter">' +
`<span class="mdui-textfield-counter-inputed"></span> / ${maxLength}` +
'</div>').appendTo($textfield);
}
$textfield
.find('.mdui-textfield-counter-inputed')
.text(value.length.toString());
}
// 含 帮助文本、错误提示、字数统计 时,增加文本框底部内边距
if ($textfield.find('.mdui-textfield-helper').length ||
$textfield.find('.mdui-textfield-error').length ||
maxLength) {
$textfield.addClass('mdui-textfield-has-bottom');
}
}
$(() => {
// 绑定事件
$document.on('input focus blur', '.mdui-textfield-input', { useCapture: true }, inputEvent);
// 可展开文本框展开
$document.on('click', '.mdui-textfield-expandable .mdui-textfield-icon', function () {
$(this)
.parents('.mdui-textfield')
.addClass('mdui-textfield-expanded')
.find('.mdui-textfield-input')[0]
.focus();
});
// 可展开文本框关闭
$document.on('click', '.mdui-textfield-expanded .mdui-textfield-close', function () {
$(this)
.parents('.mdui-textfield')
.removeClass('mdui-textfield-expanded')
.find('.mdui-textfield-input')
.val('');
});
/**
* 初始化文本框
*/
mdui.mutation('.mdui-textfield', function () {
$(this).find('.mdui-textfield-input').trigger('input', {
domLoadedEvent: true,
});
});
});
mdui.updateTextFields = function (selector) {
const $elements = isUndefined(selector) ? $('.mdui-textfield') : $(selector);
$elements.each((_, element) => {
$(element).find('.mdui-textfield-input').trigger('input', {
reInit: true,
});
});
};
/**
* 滑块的值改变后修改滑块样式
* @param $slider
*/
function updateValueStyle($slider) {
const data = $slider.data();
const $track = data._slider_$track;
const $fill = data._slider_$fill;
const $thumb = data._slider_$thumb;
const $input = data._slider_$input;
const min = data._slider_min;
const max = data._slider_max;
const isDisabled = data._slider_disabled;
const isDiscrete = data._slider_discrete;
const $thumbText = data._slider_$thumbText;
const value = $input.val();
const percent = ((value - min) / (max - min)) * 100;
$fill.width(`${percent}%`);
$track.width(`${100 - percent}%`);
if (isDisabled) {
$fill.css('padding-right', '6px');
$track.css('padding-left', '6px');
}
$thumb.css('left', `${percent}%`);
if (isDiscrete) {
$thumbText.text(value);
}
percent === 0
? $slider.addClass('mdui-slider-zero')
: $slider.removeClass('mdui-slider-zero');
}
/**
* 重新初始化滑块
* @param $slider
*/
function reInit($slider) {
const $track = $('<div class="mdui-slider-track"></div>');
const $fill = $('<div class="mdui-slider-fill"></div>');
const $thumb = $('<div class="mdui-slider-thumb"></div>');
const $input = $slider.find('input[type="range"]');
const isDisabled = $input[0].disabled;
const isDiscrete = $slider.hasClass('mdui-slider-discrete');
// 禁用状态
isDisabled
? $slider.addClass('mdui-slider-disabled')
: $slider.removeClass('mdui-slider-disabled');
// 重新填充 HTML
$slider.find('.mdui-slider-track').remove();
$slider.find('.mdui-slider-fill').remove();
$slider.find('.mdui-slider-thumb').remove();
$slider.append($track).append($fill).append($thumb);
// 间续型滑块
let $thumbText = $();
if (isDiscrete) {
$thumbText = $('<span></span>');
$thumb.empty().append($thumbText);
}
$slider.data('_slider_$track', $track);
$slider.data('_slider_$fill', $fill);
$slider.data('_slider_$thumb', $thumb);
$slider.data('_slider_$input', $input);
$slider.data('_slider_min', $input.attr('min'));
$slider.data('_slider_max', $input.attr('max'));
$slider.data('_slider_disabled', isDisabled);
$slider.data('_slider_discrete', isDiscrete);
$slider.data('_slider_$thumbText', $thumbText);
// 设置默认值
updateValueStyle($slider);
}
const rangeSelector = '.mdui-slider input[type="range"]';
$(() => {
// 滑块滑动事件
$document.on('input change', rangeSelector, function () {
const $slider = $(this).parent();
updateValueStyle($slider);
});
// 开始触摸滑块事件
$document.on(startEvent, rangeSelector, function (event) {
if (!isAllow(event)) {
return;
}
register(event);
if (this.disabled) {
return;
}
const $slider = $(this).parent();
$slider.addClass('mdui-slider-focus');
});
// 结束触摸滑块事件
$document.on(endEvent, rangeSelector, function (event) {
if (!isAllow(event)) {
return;
}
if (this.disabled) {
return;
}
const $slider = $(this).parent();
$slider.removeClass('mdui-slider-focus');
});
$document.on(unlockEvent, rangeSelector, register);
/**
* 初始化滑块
*/
mdui.mutation('.mdui-slider', function () {
reInit($(this));
});
});
mdui.updateSliders = function (selector) {
const $elements = isUndefined(selector) ? $('.mdui-slider') : $(selector);
$elements.each((_, element) => {
reInit($(element));
});
};
const DEFAULT_OPTIONS$2 = {
trigger: 'hover',
};
class Fab {
constructor(selector, options = {}) {
/**
* 配置参数
*/
this.options = extend({}, DEFAULT_OPTIONS$2);
/**
* 当前 fab 的状态
*/
this.state = 'closed';
this.$element = $(selector).first();
extend(this.options, options);
this.$btn = this.$element.find('.mdui-fab');
this.$dial = this.$element.find('.mdui-fab-dial');
this.$dialBtns = this.$dial.find('.mdui-fab');
if (this.options.trigger === 'hover') {
this.$btn.on('touchstart mouseenter', () => this.open());
this.$element.on('mouseleave', () => this.close());
}
if (this.options.trigger === 'click') {
this.$btn.on(startEvent, () => this.open());
}
// 触摸屏幕其他地方关闭快速拨号
$document.on(startEvent, (event) => {
if ($(event.target).parents('.mdui-fab-wrapper').length) {
return;
}
this.close();
});
}
/**
* 触发组件事件
* @param name
*/
triggerEvent(name) {
componentEvent(name, 'fab', this.$element, this);
}
/**
* 当前是否为打开状态
*/
isOpen() {
return this.state === 'opening' || this.state === 'opened';
}
/**
* 打开快速拨号菜单
*/
open() {
if (this.isOpen()) {
return;
}
// 为菜单中的按钮添加不同的 transition-delay
this.$dialBtns.each((index, btn) => {
const delay = `${15 * (this.$dialBtns.length - index)}ms`;
btn.style.transitionDelay = delay;
btn.style.webkitTransitionDelay = delay;
});
this.$dial.css('height', 'auto').addClass('mdui-fab-dial-show');
// 如果按钮中存在 .mdui-fab-opened 的图标,则进行图标切换
if (this.$btn.find('.mdui-fab-opened').length) {
this.$btn.addClass('mdui-fab-opened');
}
this.state = 'opening';
this.triggerEvent('open');
// 打开顺序为从下到上逐个打开,最上面的打开后才表示动画完成
this.$dialBtns.first().transitionEnd(() => {
if (this.$btn.hasClass('mdui-fab-opened')) {
this.state = 'opened';
this.triggerEvent('opened');
}
});
}
/**
* 关闭快速拨号菜单
*/
close() {
if (!this.isOpen()) {
return;
}
// 为菜单中的按钮添加不同的 transition-delay
this.$dialBtns.each((index, btn) => {
const delay = `${15 * index}ms`;
btn.style.transitionDelay = delay;
btn.style.webkitTransitionDelay = delay;
});
this.$dial.removeClass('mdui-fab-dial-show');
this.$btn.removeClass('mdui-fab-opened');
this.state = 'closing';
this.triggerEvent('close');
// 从上往下依次关闭,最后一个关闭后才表示动画完成
this.$dialBtns.last().transitionEnd(() => {
if (this.$btn.hasClass('mdui-fab-opened')) {
return;
}
this.state = 'closed';
this.triggerEvent('closed');
this.$dial.css('height', 0);
});
}
/**
* 切换快速拨号菜单的打开状态
*/
toggle() {
this.isOpen() ? this.close() : this.open();
}
/**
* 以动画的形式显示整个浮动操作按钮
*/
show() {
this.$element.removeClass('mdui-fab-hide');
}
/**
* 以动画的形式隐藏整个浮动操作按钮
*/
hide() {
this.$element.addClass('mdui-fab-hide');
}
/**
* 返回当前快速拨号菜单的打开状态。共包含四种状态:`opening`、`opened`、`closing`、`closed`
*/
getState() {
return this.state;
}
}
mdui.Fab = Fab;
const customAttr$3 = 'mdui-fab';
$(() => {
// mouseenter 不冒泡,无法进行事件委托,这里用 mouseover 代替。
// 不管是 click 、 mouseover 还是 touchstart ,都先初始化。
$document.on('touchstart mousedown mouseover', `[${customAttr$3}]`, function () {
new mdui.Fab(this, parseOptions(this, customAttr$3));
});
});
/**
* 最终生成的元素结构为:
* <select class="mdui-select" mdui-select="{position: 'top'}" style="display: none;"> // $native
* <option value="1">State 1</option>
* <option value="2">State 2</option>
* <option value="3" disabled="">State 3</option>
* </select>
* <div class="mdui-select mdui-select-position-top" style="" id="88dec0e4-d4a2-c6d0-0e7f-1ba4501e0553"> // $element
* <span class="mdui-select-selected">State 1</span> // $selected
* <div class="mdui-select-menu" style="transform-origin: center 100% 0px;"> // $menu
* <div class="mdui-select-menu-item mdui-ripple" selected="">State 1</div> // $items
* <div class="mdui-select-menu-item mdui-ripple">State 2</div>
* <div class="mdui-select-menu-item mdui-ripple" disabled="">State 3</div>
* </div>
* </div>
*/
const DEFAULT_OPTIONS$3 = {
position: 'auto',
gutter: 16,
};
class Select {
constructor(selector, options = {}) {
/**
* 生成的 `<div class="mdui-select">` 元素的 JQ 对象
*/
this.$element = $();
/**
* 配置参数
*/
this.options = extend({}, DEFAULT_OPTIONS$3);
/**
* select 的 size 属性的值,根据该值设置 select 的高度
*/
this.size = 0;
/**
* 占位元素,显示已选中菜单项的文本
*/
this.$selected = $();
/**
* 菜单项的外层元素的 JQ 对象
*/
this.$menu = $();
/**
* 菜单项数组的 JQ 对象
*/
this.$items = $();
/**
* 当前选中的菜单项的索引号
*/
this.selectedIndex = 0;
/**
* 当前选中菜单项的文本
*/
this.selectedText = '';
/**
* 当前选中菜单项的值
*/
this.selectedValue = '';
/**
* 当前 select 的状态
*/
this.state = 'closed';
this.$native = $(selector).first();
this.$native.hide();
extend(this.options, options);
// 为当前 select 生成唯一 ID
this.uniqueID = $.guid();
// 生成 select
this.handleUpdate();
// 点击 select 外面区域关闭
$document.on('click touchstart', (event) => {
const $target = $(event.target);
if (this.isOpen() &&
!$target.is(this.$element) &&
!contains(this.$element[0], $target[0])) {
this.close();
}
});
}
/**
* 调整菜单位置
*/
readjustMenu() {
const windowHeight = $window.height();
// mdui-select 高度
const elementHeight = this.$element.height();
// 菜单项高度
const $itemFirst = this.$items.first();
const itemHeight = $itemFirst.height();
const itemMargin = parseInt($itemFirst.css('margin-top'));
// 菜单高度
const menuWidth = this.$element.innerWidth() + 0.01; // 必须比真实宽度多一点,不然会出现省略号
let menuHeight = itemHeight * this.size + itemMargin * 2;
// mdui-select 在窗口中的位置
const elementTop = this.$element[0].getBoundingClientRect().top;
let transformOriginY;
let menuMarginTop;
if (this.options.position === 'bottom') {
menuMarginTop = elementHeight;
transformOriginY = '0px';
}
else if (this.options.position === 'top') {
menuMarginTop = -menuHeight - 1;
transformOriginY = '100%';
}
else {
// 菜单高度不能超过窗口高度
const menuMaxHeight = windowHeight - this.options.gutter * 2;
if (menuHeight > menuMaxHeight) {
menuHeight = menuMaxHeight;
}
// 菜单的 margin-top
menuMarginTop = -(itemMargin +
this.selectedIndex * itemHeight +
(itemHeight - elementHeight) / 2);
const menuMaxMarginTop = -(itemMargin +
(this.size - 1) * itemHeight +
(itemHeight - elementHeight) / 2);
if (menuMarginTop < menuMaxMarginTop) {
menuMarginTop = menuMaxMarginTop;
}
// 菜单不能超出窗口
const menuTop = elementTop + menuMarginTop;
if (menuTop < this.options.gutter) {
// 不能超出窗口上方
menuMarginTop = -(elementTop - this.options.gutter);
}
else if (menuTop + menuHeight + this.options.gutter > windowHeight) {
// 不能超出窗口下方
menuMarginTop = -(elementTop +
menuHeight +
this.options.gutter -
windowHeight);
}
// transform 的 Y 轴坐标
transformOriginY = `${this.selectedIndex * itemHeight + itemHeight / 2 + itemMargin}px`;
}
// 设置样式
this.$element.innerWidth(menuWidth);
this.$menu
.innerWidth(menuWidth)
.height(menuHeight)
.css({
'margin-top': menuMarginTop + 'px',
'transform-origin': 'center ' + transformOriginY + ' 0',
});
}
/**
* select 是否为打开状态
*/
isOpen() {
return this.state === 'opening' || this.state === 'opened';
}
/**
* 对原生 select 组件进行了修改后,需要调用该方法
*/
handleUpdate() {
if (this.isOpen()) {
this.close();
}
this.selectedValue = this.$native.val();
const itemsData = [];
this.$items = $();
// 生成 HTML
this.$native.find('option').each((index, option) => {
const text = option.textContent || '';
const value = option.value;
const disabled = option.disabled;
const selected = this.selectedValue === value;
itemsData.push({
value,
text,
disabled,
selected,
index,
});
if (selected) {
this.selectedText = text;
this.selectedIndex = index;
}
this.$items = this.$items.add('<div class="mdui-select-menu-item mdui-ripple"' +
(disabled ? ' disabled' : '') +
(selected ? ' selected' : '') +
`>${text}</div>`);
});
this.$selected = $(`<span class="mdui-select-selected">${this.selectedText}</span>`);
this.$element = $(`<div class="mdui-select mdui-select-position-${this.options.position}" ` +
`style="${this.$native.attr('style')}" ` +
`id="${this.uniqueID}"></div>`)
.show()
.append(this.$selected);
this.$menu = $('<div class="mdui-select-menu"></div>')
.appendTo(this.$element)
.append(this.$items);
$(`#${this.uniqueID}`).remove();
this.$native.after(this.$element);
// 根据 select 的 size 属性设置高度
this.size = parseInt(this.$native.attr('size') || '0');
if (this.size <= 0) {
this.size = this.$items.length;
if (this.size > 8) {
this.size = 8;
}
}
// 点击选项时关闭下拉菜单
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
this.$items.on('click', function () {
if (that.state === 'closing') {
return;
}
const $item = $(this);
const index = $item.index();
const data = itemsData[index];
if (data.disabled) {
return;
}
that.$selected.text(data.text);
that.$native.val(data.value);
that.$items.removeAttr('selected');
$item.attr('selected', '');
that.selectedIndex = data.index;
that.selectedValue = data.value;
that.selectedText = data.text;
that.$native.trigger('change');
that.close();
});
// 点击 $element 时打开下拉菜单
this.$element.on('click', (event) => {
const $target = $(event.target);
// 在菜单上点击时不打开
if ($target.is('.mdui-select-menu') ||
$target.is('.mdui-select-menu-item')) {
return;
}
this.toggle();
});
}
/**
* 动画结束的回调
*/
transitionEnd() {
this.$element.removeClass('mdui-select-closing');
if (this.state === 'opening') {
this.state = 'opened';
this.triggerEvent('opened');
this.$menu.css('overflow-y', 'auto');
}
if (this.state === 'closing') {
this.state = 'closed';
this.triggerEvent('closed');
// 恢复样式
this.$element.innerWidth('');
this.$menu.css({
'margin-top': '',
height: '',
width: '',
});
}
}
/**
* 触发组件事件
* @param name
*/
triggerEvent(name) {
componentEvent(name, 'select', this.$native, this);
}
/**
* 切换下拉菜单的打开状态
*/
toggle() {
this.isOpen() ? this.close() : this.open();
}
/**
* 打开下拉菜单
*/
open() {
if (this.isOpen()) {
return;
}
this.state = 'opening';
this.triggerEvent('open');
this.readjustMenu();
this.$element.addClass('mdui-select-open');
this.$menu.transitionEnd(() => this.transitionEnd());
}
/**
* 关闭下拉菜单
*/
close() {
if (!this.isOpen()) {
return;
}
this.state = 'closing';
this.triggerEvent('close');
this.$menu.css('overflow-y', '');
this.$element
.removeClass('mdui-select-open')
.addClass('mdui-select-closing');
this.$menu.transitionEnd(() => this.transitionEnd());
}
/**
* 获取当前菜单的状态。共包含四种状态:`opening`、`opened`、`closing`、`closed`
*/
getState() {
return this.state;
}
}
mdui.Select = Select;
const customAttr$4 = 'mdui-select';
$(() => {
mdui.mutation(`[${customAttr$4}]`, function () {
new mdui.Select(this, parseOptions(this, customAttr$4));
});
});
$(() => {
// 滚动时隐藏应用栏
mdui.mutation('.mdui-appbar-scroll-hide', function () {
new mdui.Headroom(this);
});
// 滚动时只隐藏应用栏中的工具栏
mdui.mutation('.mdui-appbar-scroll-toolbar-hide', function () {
new mdui.Headroom(this, {
pinnedClass: 'mdui-headroom-pinned-toolbar',
unpinnedClass: 'mdui-headroom-unpinned-toolbar',
});
});
});
const DEFAULT_OPTIONS$4 = {
trigger: 'click',
loop: false,
};
class Tab {
constructor(selector, options = {}) {
/**
* 配置参数
*/
this.options = extend({}, DEFAULT_OPTIONS$4);
/**
* 当前激活的 tab 的索引号。为 -1 时表示没有激活的选项卡,或不存在选项卡
*/
this.activeIndex = -1;
this.$element = $(selector).first();
extend(this.options, options);
this.$tabs = this.$element.children('a');
this.$indicator = $('<div class="mdui-tab-indicator"></div>').appendTo(this.$element);
// 根据 url hash 获取默认激活的选项卡
const hash = window.location.hash;
if (hash) {
this.$tabs.each((index, tab) => {
if ($(tab).attr('href') === hash) {
this.activeIndex = index;
return false;
}
return true;
});
}
// 含 .mdui-tab-active 的元素默认激活
if (this.activeIndex === -1) {
this.$tabs.each((index, tab) => {
if ($(tab).hasClass('mdui-tab-active')) {
this.activeIndex = index;
return false;
}
return true;
});
}
// 存在选项卡时,默认激活第一个选项卡
if (this.$tabs.length && this.activeIndex === -1) {
this.activeIndex = 0;
}
// 设置激活状态选项卡
this.setActive();
// 监听窗口大小变化事件,调整指示器位置
$window.on('resize', $.throttle(() => this.setIndicatorPosition(), 100));
// 监听点击选项卡事件
this.$tabs.each((_, tab) => {
this.bindTabEvent(tab);
});
}
/**
* 指定选项卡是否已禁用
* @param $tab
*/
isDisabled($tab) {
return $tab.attr('disabled') !== undefined;
}
/**
* 绑定在 Tab 上点击或悬浮的事件
* @param tab
*/
bindTabEvent(tab) {
const $tab = $(tab);
// 点击或鼠标移入触发的事件
const clickEvent = () => {
// 禁用状态的选项卡无法选中
if (this.isDisabled($tab)) {
return false;
}
this.activeIndex = this.$tabs.index(tab);
this.setActive();
};
// 无论 trigger 是 click 还是 hover都会响应 click 事件
$tab.on('click', clickEvent);
// trigger 为 hover 时,额外响应 mouseenter 事件
if (this.options.trigger === 'hover') {
$tab.on('mouseenter', clickEvent);
}
// 阻止链接的默认点击动作
$tab.on('click', () => {
if (($tab.attr('href') || '').indexOf('#') === 0) {
return false;
}
});
}
/**
* 触发组件事件
* @param name
* @param $element
* @param parameters
*/
triggerEvent(name, $element, parameters = {}) {
componentEvent(name, 'tab', $element, this, parameters);
}
/**
* 设置激活状态的选项卡
*/
setActive() {
this.$tabs.each((index, tab) => {
const $tab = $(tab);
const targetId = $tab.attr('href') || '';
// 设置选项卡激活状态
if (index === this.activeIndex && !this.isDisabled($tab)) {
if (!$tab.hasClass('mdui-tab-active')) {
this.triggerEvent('change', this.$element, {
index: this.activeIndex,
id: targetId.substr(1),
});
this.triggerEvent('show', $tab);
$tab.addClass('mdui-tab-active');
}
$(targetId).show();
this.setIndicatorPosition();
}
else {
$tab.removeClass('mdui-tab-active');
$(targetId).hide();
}
});
}
/**
* 设置选项卡指示器的位置
*/
setIndicatorPosition() {
// 选项卡数量为 0 时,不显示指示器
if (this.activeIndex === -1) {
this.$indicator.css({
left: 0,
width: 0,
});
return;
}
const $activeTab = this.$tabs.eq(this.activeIndex);
if (this.isDisabled($activeTab)) {
return;
}
const activeTabOffset = $activeTab.offset();
this.$indicator.css({
left: `${activeTabOffset.left +
this.$element[0].scrollLeft -
this.$element[0].getBoundingClientRect().left}px`,
width: `${$activeTab.innerWidth()}px`,
});
}
/**
* 切换到下一个选项卡
*/
next() {
if (this.activeIndex === -1) {
return;
}
if (this.$tabs.length > this.activeIndex + 1) {
this.activeIndex++;
}
else if (this.options.loop) {
this.activeIndex = 0;
}
this.setActive();
}
/**
* 切换到上一个选项卡
*/
prev() {
if (this.activeIndex === -1) {
return;
}
if (this.activeIndex > 0) {
this.activeIndex--;
}
else if (this.options.loop) {
this.activeIndex = this.$tabs.length - 1;
}
this.setActive();
}
/**
* 显示指定索引号、或指定id的选项卡
* @param index 索引号、或id
*/
show(index) {
if (this.activeIndex === -1) {
return;
}
if (isNumber(index)) {
this.activeIndex = index;
}
else {
this.$tabs.each((i, tab) => {
if (tab.id === index) {
this.activeIndex === i;
return false;
}
});
}
this.setActive();
}
/**
* 在父元素的宽度变化时,需要调用该方法重新调整指示器位置
* 在添加或删除选项卡时,需要调用该方法
*/
handleUpdate() {
const $oldTabs = this.$tabs; // 旧的 tabs JQ对象
const $newTabs = this.$element.children('a'); // 新的 tabs JQ对象
const oldTabsElement = $oldTabs.get(); // 旧的 tabs 元素数组
const newTabsElement = $newTabs.get(); // 新的 tabs 元素数组
if (!$newTabs.length) {
this.activeIndex = -1;
this.$tabs = $newTabs;
this.setIndicatorPosition();
return;
}
// 重新遍历选项卡,找出新增的选项卡
$newTabs.each((index, tab) => {
// 有新增的选项卡
if (oldTabsElement.indexOf(tab) < 0) {
this.bindTabEvent(tab);
if (this.activeIndex === -1) {
this.activeIndex = 0;
}
else if (index <= this.activeIndex) {
this.activeIndex++;
}
}
});
// 找出被移除的选项卡
$oldTabs.each((index, tab) => {
// 有被移除的选项卡
if (newTabsElement.indexOf(tab) < 0) {
if (index < this.activeIndex) {
this.activeIndex--;
}
else if (index === this.activeIndex) {
this.activeIndex = 0;
}
}
});
this.$tabs = $newTabs;
this.setActive();
}
}
mdui.Tab = Tab;
const customAttr$5 = 'mdui-tab';
$(() => {
mdui.mutation(`[${customAttr$5}]`, function () {
new mdui.Tab(this, parseOptions(this, customAttr$5));
});
});
/**
* 在桌面设备上默认显示抽屉栏,不显示遮罩层
* 在手机和平板设备上默认不显示抽屉栏,始终显示遮罩层,且覆盖导航栏
*/
const DEFAULT_OPTIONS$5 = {
overlay: false,
swipe: false,
};
class Drawer {
constructor(selector, options = {}) {
/**
* 配置参数
*/
this.options = extend({}, DEFAULT_OPTIONS$5);
/**
* 当前是否显示着遮罩层
*/
this.overlay = false;
this.$element = $(selector).first();
extend(this.options, options);
this.position = this.$element.hasClass('mdui-drawer-right')
? 'right'
: 'left';
if (this.$element.hasClass('mdui-drawer-close')) {
this.state = 'closed';
}
else if (this.$element.hasClass('mdui-drawer-open')) {
this.state = 'opened';
}
else if (this.isDesktop()) {
this.state = 'opened';
}
else {
this.state = 'closed';
}
// 浏览器窗口大小调整时
$window.on('resize', $.throttle(() => {
if (this.isDesktop()) {
// 由手机平板切换到桌面时
// 如果显示着遮罩,则隐藏遮罩
if (this.overlay && !this.options.overlay) {
$.hideOverlay();
this.overlay = false;
$.unlockScreen();
}
// 没有强制关闭,则状态为打开状态
if (!this.$element.hasClass('mdui-drawer-close')) {
this.state = 'opened';
}
}
else if (!this.overlay && this.state === 'opened') {
// 由桌面切换到手机平板时。如果抽屉栏是打开着的且没有遮罩层,则关闭抽屉栏
if (this.$element.hasClass('mdui-drawer-open')) {
$.showOverlay();
this.overlay = true;
$.lockScreen();
$('.mdui-overlay').one('click', () => this.close());
}
else {
this.state = 'closed';
}
}
}, 100));
// 绑定关闭按钮事件
this.$element.find('[mdui-drawer-close]').each((_, close) => {
$(close).on('click', () => this.close());
});
this.swipeSupport();
}
/**
* 是否是桌面设备
*/
isDesktop() {
return $window.width() >= 1024;
}
/**
* 滑动手势支持
*/
swipeSupport() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
// 抽屉栏滑动手势控制
let openNavEventHandler;
let touchStartX;
let touchStartY;
let swipeStartX;
let swiping = null;
let maybeSwiping = false;
const $body = $('body');
// 手势触发的范围
const swipeAreaWidth = 24;
function setPosition(translateX) {
const rtlTranslateMultiplier = that.position === 'right' ? -1 : 1;
const transformCSS = `translate(${-1 * rtlTranslateMultiplier * translateX}px, 0) !important;`;
const transitionCSS = 'initial !important;';
that.$element.css('cssText', `transform: ${transformCSS}; transition: ${transitionCSS};`);
}
function cleanPosition() {
that.$element[0].style.transform = '';
that.$element[0].style.webkitTransform = '';
that.$element[0].style.transition = '';
that.$element[0].style.webkitTransition = '';
}
function getMaxTranslateX() {
return that.$element.width() + 10;
}
function getTranslateX(currentX) {
return Math.min(Math.max(swiping === 'closing'
? swipeStartX - currentX
: getMaxTranslateX() + swipeStartX - currentX, 0), getMaxTranslateX());
}
function onBodyTouchEnd(event) {
if (swiping) {
let touchX = event.changedTouches[0].pageX;
if (that.position === 'right') {
touchX = $body.width() - touchX;
}
const translateRatio = getTranslateX(touchX) / getMaxTranslateX();
maybeSwiping = false;
const swipingState = swiping;
swiping = null;
if (swipingState === 'opening') {
if (translateRatio < 0.92) {
cleanPosition();
that.open();
}
else {
cleanPosition();
}
}
else {
if (translateRatio > 0.08) {
cleanPosition();
that.close();
}
else {
cleanPosition();
}
}
$.unlockScreen();
}
else {
maybeSwiping = false;
}
$body.off({
// eslint-disable-next-line @typescript-eslint/no-use-before-define
touchmove: onBodyTouchMove,
touchend: onBodyTouchEnd,
// eslint-disable-next-line @typescript-eslint/no-use-before-define
touchcancel: onBodyTouchMove,
});
}
function onBodyTouchMove(event) {
let touchX = event.touches[0].pageX;
if (that.position === 'right') {
touchX = $body.width() - touchX;
}
const touchY = event.touches[0].pageY;
if (swiping) {
setPosition(getTranslateX(touchX));
}
else if (maybeSwiping) {
const dXAbs = Math.abs(touchX - touchStartX);
const dYAbs = Math.abs(touchY - touchStartY);
const threshold = 8;
if (dXAbs > threshold && dYAbs <= threshold) {
swipeStartX = touchX;
swiping = that.state === 'opened' ? 'closing' : 'opening';
$.lockScreen();
setPosition(getTranslateX(touchX));
}
else if (dXAbs <= threshold && dYAbs > threshold) {
onBodyTouchEnd();
}
}
}
function onBodyTouchStart(event) {
touchStartX = event.touches[0].pageX;
if (that.position === 'right') {
touchStartX = $body.width() - touchStartX;
}
touchStartY = event.touches[0].pageY;
if (that.state !== 'opened') {
if (touchStartX > swipeAreaWidth ||
openNavEventHandler !== onBodyTouchStart) {
return;
}
}
maybeSwiping = true;
$body.on({
touchmove: onBodyTouchMove,
touchend: onBodyTouchEnd,
touchcancel: onBodyTouchMove,
});
}
function enableSwipeHandling() {
if (!openNavEventHandler) {
$body.on('touchstart', onBodyTouchStart);
openNavEventHandler = onBodyTouchStart;
}
}
if (this.options.swipe) {
enableSwipeHandling();
}
}
/**
* 触发组件事件
* @param name
*/
triggerEvent(name) {
componentEvent(name, 'drawer', this.$element, this);
}
/**
* 动画结束回调
*/
transitionEnd() {
if (this.$element.hasClass('mdui-drawer-open')) {
this.state = 'opened';
this.triggerEvent('opened');
}
else {
this.state = 'closed';
this.triggerEvent('closed');
}
}
/**
* 是否处于打开状态
*/
isOpen() {
return this.state === 'opening' || this.state === 'opened';
}
/**
* 打开抽屉栏
*/
open() {
if (this.isOpen()) {
return;
}
this.state = 'opening';
this.triggerEvent('open');
if (!this.options.overlay) {
$('body').addClass(`mdui-drawer-body-${this.position}`);
}
this.$element
.removeClass('mdui-drawer-close')
.addClass('mdui-drawer-open')
.transitionEnd(() => this.transitionEnd());
if (!this.isDesktop() || this.options.overlay) {
this.overlay = true;
$.showOverlay().one('click', () => this.close());
$.lockScreen();
}
}
/**
* 关闭抽屉栏
*/
close() {
if (!this.isOpen()) {
return;
}
this.state = 'closing';
this.triggerEvent('close');
if (!this.options.overlay) {
$('body').removeClass(`mdui-drawer-body-${this.position}`);
}
this.$element
.addClass('mdui-drawer-close')
.removeClass('mdui-drawer-open')
.transitionEnd(() => this.transitionEnd());
if (this.overlay) {
$.hideOverlay();
this.overlay = false;
$.unlockScreen();
}
}
/**
* 切换抽屉栏打开/关闭状态
*/
toggle() {
this.isOpen() ? this.close() : this.open();
}
/**
* 返回当前抽屉栏的状态。共包含四种状态:`opening`、`opened`、`closing`、`closed`
*/
getState() {
return this.state;
}
}
mdui.Drawer = Drawer;
const customAttr$6 = 'mdui-drawer';
$(() => {
mdui.mutation(`[${customAttr$6}]`, function () {
const $element = $(this);
const options = parseOptions(this, customAttr$6);
const selector = options.target;
// @ts-ignore
delete options.target;
const $drawer = $(selector).first();
const instance = new mdui.Drawer($drawer, options);
$element.on('click', () => instance.toggle());
});
});
const container = {};
function queue(name, func) {
if (isUndefined(container[name])) {
container[name] = [];
}
if (isUndefined(func)) {
return container[name];
}
container[name].push(func);
}
/**
* 从队列中移除第一个函数,并执行该函数
* @param name 队列满
*/
function dequeue(name) {
if (isUndefined(container[name])) {
return;
}
if (!container[name].length) {
return;
}
const func = container[name].shift();
func();
}
const DEFAULT_OPTIONS$6 = {
history: true,
overlay: true,
modal: false,
closeOnEsc: true,
closeOnCancel: true,
closeOnConfirm: true,
destroyOnClosed: false,
};
/**
* 当前显示的对话框实例
*/
let currentInst = null;
/**
* 队列名
*/
const queueName = '_mdui_dialog';
/**
* 窗口是否已锁定
*/
let isLockScreen = false;
/**
* 遮罩层元素
*/
let $overlay;
class Dialog {
constructor(selector, options = {}) {
/**
* 配置参数
*/
this.options = extend({}, DEFAULT_OPTIONS$6);
/**
* 当前 dialog 的状态
*/
this.state = 'closed';
/**
* dialog 元素是否是动态添加的
*/
this.append = false;
this.$element = $(selector).first();
// 如果对话框元素没有在当前文档中,则需要添加
if (!contains(document.body, this.$element[0])) {
this.append = true;
$('body').append(this.$element);
}
extend(this.options, options);
// 绑定取消按钮事件
this.$element.find('[mdui-dialog-cancel]').each((_, cancel) => {
$(cancel).on('click', () => {
this.triggerEvent('cancel');
if (this.options.closeOnCancel) {
this.close();
}
});
});
// 绑定确认按钮事件
this.$element.find('[mdui-dialog-confirm]').each((_, confirm) => {
$(confirm).on('click', () => {
this.triggerEvent('confirm');
if (this.options.closeOnConfirm) {
this.close();
}
});
});
// 绑定关闭按钮事件
this.$element.find('[mdui-dialog-close]').each((_, close) => {
$(close).on('click', () => this.close());
});
}
/**
* 触发组件事件
* @param name
*/
triggerEvent(name) {
componentEvent(name, 'dialog', this.$element, this);
}
/**
* 窗口宽度变化,或对话框内容变化时,调整对话框位置和对话框内的滚动条
*/
readjust() {
if (!currentInst) {
return;
}
const $element = currentInst.$element;
const $title = $element.children('.mdui-dialog-title');
const $content = $element.children('.mdui-dialog-content');
const $actions = $element.children('.mdui-dialog-actions');
// 调整 dialog 的 top 和 height 值
$element.height('');
$content.height('');
const elementHeight = $element.height();
$element.css({
top: `${($window.height() - elementHeight) / 2}px`,
height: `${elementHeight}px`,
});
// 调整 mdui-dialog-content 的高度
$content.innerHeight(elementHeight -
($title.innerHeight() || 0) -
($actions.innerHeight() || 0));
}
/**
* hashchange 事件触发时关闭对话框
*/
hashchangeEvent() {
if (window.location.hash.substring(1).indexOf('mdui-dialog') < 0) {
currentInst.close(true);
}
}
/**
* 点击遮罩层关闭对话框
* @param event
*/
overlayClick(event) {
if ($(event.target).hasClass('mdui-overlay') &&
currentInst) {
currentInst.close();
}
}
/**
* 动画结束回调
*/
transitionEnd() {
if (this.$element.hasClass('mdui-dialog-open')) {
this.state = 'opened';
this.triggerEvent('opened');
}
else {
this.state = 'closed';
this.triggerEvent('closed');
this.$element.hide();
// 所有对话框都关闭,且当前没有打开的对话框时,解锁屏幕
if (!queue(queueName).length && !currentInst && isLockScreen) {
$.unlockScreen();
isLockScreen = false;
}
$window.off('resize', $.throttle(this.readjust, 100));
if (this.options.destroyOnClosed) {
this.destroy();
}
}
}
/**
* 打开指定对话框
*/
doOpen() {
currentInst = this;
if (!isLockScreen) {
$.lockScreen();
isLockScreen = true;
}
this.$element.show();
this.readjust();
$window.on('resize', $.throttle(this.readjust, 100));
// 打开消息框
this.state = 'opening';
this.triggerEvent('open');
this.$element
.addClass('mdui-dialog-open')
.transitionEnd(() => this.transitionEnd());
// 不存在遮罩层元素时,添加遮罩层
if (!$overlay) {
$overlay = $.showOverlay(5100);
}
// 点击遮罩层时是否关闭对话框
if (this.options.modal) {
$overlay.off('click', this.overlayClick);
}
else {
$overlay.on('click', this.overlayClick);
}
// 是否显示遮罩层,不显示时,把遮罩层背景透明
$overlay.css('opacity', this.options.overlay ? '' : 0);
if (this.options.history) {
// 如果 hash 中原来就有 mdui-dialog先删除避免后退历史纪录后仍然有 mdui-dialog 导致无法关闭
// 包括 mdui-dialog 和 &mdui-dialog 和 ?mdui-dialog
let hash = window.location.hash.substring(1);
if (hash.indexOf('mdui-dialog') > -1) {
hash = hash.replace(/[&?]?mdui-dialog/g, '');
}
// 后退按钮关闭对话框
if (hash) {
window.location.hash = `${hash}${hash.indexOf('?') > -1 ? '&' : '?'}mdui-dialog`;
}
else {
window.location.hash = 'mdui-dialog';
}
$window.on('hashchange', this.hashchangeEvent);
}
}
/**
* 当前对话框是否为打开状态
*/
isOpen() {
return this.state === 'opening' || this.state === 'opened';
}
/**
* 打开对话框
*/
open() {
if (this.isOpen()) {
return;
}
// 如果当前有正在打开或已经打开的对话框,或队列不为空,则先加入队列,等旧对话框开始关闭时再打开
if ((currentInst &&
(currentInst.state === 'opening' || currentInst.state === 'opened')) ||
queue(queueName).length) {
queue(queueName, () => this.doOpen());
return;
}
this.doOpen();
}
/**
* 关闭对话框
*/
close(historyBack = false) {
// historyBack 是否需要后退历史纪录,默认为 `false`。该参数仅内部使用
// 为 `false` 时是通过 js 关闭,需要后退一个历史记录
// 为 `true` 时是通过后退按钮关闭,不需要后退历史记录
// setTimeout 的作用是:
// 当同时关闭一个对话框,并打开另一个对话框时,使打开对话框的操作先执行,以使需要打开的对话框先加入队列
setTimeout(() => {
if (!this.isOpen()) {
return;
}
currentInst = null;
this.state = 'closing';
this.triggerEvent('close');
// 所有对话框都关闭,且当前没有打开的对话框时,隐藏遮罩
if (!queue(queueName).length && $overlay) {
$.hideOverlay();
$overlay = null;
// 若仍存在遮罩,恢复遮罩的 z-index
$('.mdui-overlay').css('z-index', 2000);
}
this.$element
.removeClass('mdui-dialog-open')
.transitionEnd(() => this.transitionEnd());
if (this.options.history && !queue(queueName).length) {
if (!historyBack) {
window.history.back();
}
$window.off('hashchange', this.hashchangeEvent);
}
// 关闭旧对话框,打开新对话框。
// 加一点延迟,仅仅为了视觉效果更好。不加延时也不影响功能
setTimeout(() => {
dequeue(queueName);
}, 100);
});
}
/**
* 切换对话框打开/关闭状态
*/
toggle() {
this.isOpen() ? this.close() : this.open();
}
/**
* 获取对话框状态。共包含四种状态:`opening`、`opened`、`closing`、`closed`
*/
getState() {
return this.state;
}
/**
* 销毁对话框
*/
destroy() {
if (this.append) {
this.$element.remove();
}
if (!queue(queueName).length && !currentInst) {
if ($overlay) {
$.hideOverlay();
$overlay = null;
}
if (isLockScreen) {
$.unlockScreen();
isLockScreen = false;
}
}
}
/**
* 对话框内容变化时,需要调用该方法来调整对话框位置和滚动条高度
*/
handleUpdate() {
this.readjust();
}
}
// esc 按下时关闭对话框
$document.on('keydown', (event) => {
if (currentInst &&
currentInst.options.closeOnEsc &&
currentInst.state === 'opened' &&
event.keyCode === 27) {
currentInst.close();
}
});
mdui.Dialog = Dialog;
const customAttr$7 = 'mdui-dialog';
const dataName$1 = '_mdui_dialog';
$(() => {
$document.on('click', `[${customAttr$7}]`, function () {
const options = parseOptions(this, customAttr$7);
const selector = options.target;
// @ts-ignore
delete options.target;
const $dialog = $(selector).first();
let instance = $dialog.data(dataName$1);
if (!instance) {
instance = new mdui.Dialog($dialog, options);
$dialog.data(dataName$1, instance);
}
instance.open();
});
});
const DEFAULT_BUTTON = {
text: '',
bold: false,
close: true,
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClick: () => { },
};
const DEFAULT_OPTIONS$7 = {
title: '',
content: '',
buttons: [],
stackedButtons: false,
cssClass: '',
history: true,
overlay: true,
modal: false,
closeOnEsc: true,
destroyOnClosed: true,
// eslint-disable-next-line @typescript-eslint/no-empty-function
onOpen: () => { },
// eslint-disable-next-line @typescript-eslint/no-empty-function
onOpened: () => { },
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClose: () => { },
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClosed: () => { },
};
mdui.dialog = function (options) {
var _a, _b;
// 合并配置参数
options = extend({}, DEFAULT_OPTIONS$7, options);
each(options.buttons, (i, button) => {
options.buttons[i] = extend({}, DEFAULT_BUTTON, button);
});
// 按钮的 HTML
let buttonsHTML = '';
if ((_a = options.buttons) === null || _a === void 0 ? void 0 : _a.length) {
buttonsHTML = `<div class="mdui-dialog-actions${options.stackedButtons ? ' mdui-dialog-actions-stacked' : ''}">`;
each(options.buttons, (_, button) => {
buttonsHTML +=
'<a href="javascript:void(0)" ' +
`class="mdui-btn mdui-ripple mdui-text-color-primary ${button.bold ? 'mdui-btn-bold' : ''}">${button.text}</a>`;
});
buttonsHTML += '</div>';
}
// Dialog 的 HTML
const HTML = `<div class="mdui-dialog ${options.cssClass}">` +
(options.title
? `<div class="mdui-dialog-title">${options.title}</div>`
: '') +
(options.content
? `<div class="mdui-dialog-content">${options.content}</div>`
: '') +
buttonsHTML +
'</div>';
// 实例化 Dialog
const instance = new mdui.Dialog(HTML, {
history: options.history,
overlay: options.overlay,
modal: options.modal,
closeOnEsc: options.closeOnEsc,
destroyOnClosed: options.destroyOnClosed,
});
// 绑定按钮事件
if ((_b = options.buttons) === null || _b === void 0 ? void 0 : _b.length) {
instance.$element
.find('.mdui-dialog-actions .mdui-btn')
.each((index, button) => {
$(button).on('click', () => {
options.buttons[index].onClick(instance);
if (options.buttons[index].close) {
instance.close();
}
});
});
}
// 绑定打开关闭事件
instance.$element
.on('open.mdui.dialog', () => {
options.onOpen(instance);
})
.on('opened.mdui.dialog', () => {
options.onOpened(instance);
})
.on('close.mdui.dialog', () => {
options.onClose(instance);
})
.on('closed.mdui.dialog', () => {
options.onClosed(instance);
});
instance.open();
return instance;
};
const DEFAULT_OPTIONS$8 = {
confirmText: 'ok',
history: true,
modal: false,
closeOnEsc: true,
closeOnConfirm: true,
};
mdui.alert = function (text, title, onConfirm, options) {
if (isFunction(title)) {
options = onConfirm;
onConfirm = title;
title = '';
}
if (isUndefined(onConfirm)) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
onConfirm = () => { };
}
if (isUndefined(options)) {
options = {};
}
options = extend({}, DEFAULT_OPTIONS$8, options);
return mdui.dialog({
title: title,
content: text,
buttons: [
{
text: options.confirmText,
bold: false,
close: options.closeOnConfirm,
onClick: onConfirm,
},
],
cssClass: 'mdui-dialog-alert',
history: options.history,
modal: options.modal,
closeOnEsc: options.closeOnEsc,
});
};
const DEFAULT_OPTIONS$9 = {
confirmText: 'ok',
cancelText: 'cancel',
history: true,
modal: false,
closeOnEsc: true,
closeOnCancel: true,
closeOnConfirm: true,
};
mdui.confirm = function (text, title, onConfirm, onCancel, options) {
if (isFunction(title)) {
options = onCancel;
onCancel = onConfirm;
onConfirm = title;
title = '';
}
if (isUndefined(onConfirm)) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
onConfirm = () => { };
}
if (isUndefined(onCancel)) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
onCancel = () => { };
}
if (isUndefined(options)) {
options = {};
}
options = extend({}, DEFAULT_OPTIONS$9, options);
return mdui.dialog({
title: title,
content: text,
buttons: [
{
text: options.cancelText,
bold: false,
close: options.closeOnCancel,
onClick: onCancel,
},
{
text: options.confirmText,
bold: false,
close: options.closeOnConfirm,
onClick: onConfirm,
},
],
cssClass: 'mdui-dialog-confirm',
history: options.history,
modal: options.modal,
closeOnEsc: options.closeOnEsc,
});
};
const DEFAULT_OPTIONS$a = {
confirmText: 'ok',
cancelText: 'cancel',
history: true,
modal: false,
closeOnEsc: true,
closeOnCancel: true,
closeOnConfirm: true,
type: 'text',
maxlength: 0,
defaultValue: '',
confirmOnEnter: false,
};
mdui.prompt = function (label, title, onConfirm, onCancel, options) {
if (isFunction(title)) {
options = onCancel;
onCancel = onConfirm;
onConfirm = title;
title = '';
}
if (isUndefined(onConfirm)) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
onConfirm = () => { };
}
if (isUndefined(onCancel)) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
onCancel = () => { };
}
if (isUndefined(options)) {
options = {};
}
options = extend({}, DEFAULT_OPTIONS$a, options);
const content = '<div class="mdui-textfield">' +
(label ? `<label class="mdui-textfield-label">${label}</label>` : '') +
(options.type === 'text'
? `<input class="mdui-textfield-input" type="text" value="${options.defaultValue}" ${options.maxlength ? 'maxlength="' + options.maxlength + '"' : ''}/>`
: '') +
(options.type === 'textarea'
? `<textarea class="mdui-textfield-input" ${options.maxlength ? 'maxlength="' + options.maxlength + '"' : ''}>${options.defaultValue}</textarea>`
: '') +
'</div>';
const onCancelClick = (dialog) => {
const value = dialog.$element.find('.mdui-textfield-input').val();
onCancel(value, dialog);
};
const onConfirmClick = (dialog) => {
const value = dialog.$element.find('.mdui-textfield-input').val();
onConfirm(value, dialog);
};
return mdui.dialog({
title,
content,
buttons: [
{
text: options.cancelText,
bold: false,
close: options.closeOnCancel,
onClick: onCancelClick,
},
{
text: options.confirmText,
bold: false,
close: options.closeOnConfirm,
onClick: onConfirmClick,
},
],
cssClass: 'mdui-dialog-prompt',
history: options.history,
modal: options.modal,
closeOnEsc: options.closeOnEsc,
onOpen: (dialog) => {
// 初始化输入框
const $input = dialog.$element.find('.mdui-textfield-input');
mdui.updateTextFields($input);
// 聚焦到输入框
$input[0].focus();
// 捕捉文本框回车键,在单行文本框的情况下触发回调
if (options.type !== 'textarea' && options.confirmOnEnter === true) {
$input.on('keydown', (event) => {
if (event.keyCode === 13) {
const value = dialog.$element.find('.mdui-textfield-input').val();
onConfirm(value, dialog);
if (options.closeOnConfirm) {
dialog.close();
}
return false;
}
return;
});
}
// 如果是多行输入框,监听输入框的 input 事件,更新对话框高度
if (options.type === 'textarea') {
$input.on('input', () => dialog.handleUpdate());
}
// 有字符数限制时,加载完文本框后 DOM 会变化,需要更新对话框高度
if (options.maxlength) {
dialog.handleUpdate();
}
},
});
};
const DEFAULT_OPTIONS$b = {
position: 'auto',
delay: 0,
content: '',
};
class Tooltip {
constructor(selector, options = {}) {
/**
* 配置参数
*/
this.options = extend({}, DEFAULT_OPTIONS$b);
/**
* 当前 tooltip 的状态
*/
this.state = 'closed';
/**
* setTimeout 的返回值
*/
this.timeoutId = null;
this.$target = $(selector).first();
extend(this.options, options);
// 创建 Tooltip HTML
this.$element = $(`<div class="mdui-tooltip" id="${$.guid()}">${this.options.content}</div>`).appendTo(document.body);
// 绑定事件。元素处于 disabled 状态时无法触发鼠标事件,为了统一,把 touch 事件也禁用
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
this.$target
.on('touchstart mouseenter', function (event) {
if (that.isDisabled(this)) {
return;
}
if (!isAllow(event)) {
return;
}
register(event);
that.open();
})
.on('touchend mouseleave', function (event) {
if (that.isDisabled(this)) {
return;
}
if (!isAllow(event)) {
return;
}
that.close();
})
.on(unlockEvent, function (event) {
if (that.isDisabled(this)) {
return;
}
register(event);
});
}
/**
* 元素是否已禁用
* @param element
*/
isDisabled(element) {
return (element.disabled ||
$(element).attr('disabled') !== undefined);
}
/**
* 是否是桌面设备
*/
isDesktop() {
return $window.width() > 1024;
}
/**
* 设置 Tooltip 的位置
*/
setPosition() {
let marginLeft;
let marginTop;
// 触发的元素
const targetProps = this.$target[0].getBoundingClientRect();
// 触发的元素和 Tooltip 之间的距离
const targetMargin = this.isDesktop() ? 14 : 24;
// Tooltip 的宽度和高度
const tooltipWidth = this.$element[0].offsetWidth;
const tooltipHeight = this.$element[0].offsetHeight;
// Tooltip 的方向
let position = this.options.position;
// 自动判断位置,加 2px使 Tooltip 距离窗口边框至少有 2px 的间距
if (position === 'auto') {
if (targetProps.top +
targetProps.height +
targetMargin +
tooltipHeight +
2 <
$window.height()) {
position = 'bottom';
}
else if (targetMargin + tooltipHeight + 2 < targetProps.top) {
position = 'top';
}
else if (targetMargin + tooltipWidth + 2 < targetProps.left) {
position = 'left';
}
else if (targetProps.width + targetMargin + tooltipWidth + 2 <
$window.width() - targetProps.left) {
position = 'right';
}
else {
position = 'bottom';
}
}
// 设置位置
switch (position) {
case 'bottom':
marginLeft = -1 * (tooltipWidth / 2);
marginTop = targetProps.height / 2 + targetMargin;
this.$element.transformOrigin('top center');
break;
case 'top':
marginLeft = -1 * (tooltipWidth / 2);
marginTop =
-1 * (tooltipHeight + targetProps.height / 2 + targetMargin);
this.$element.transformOrigin('bottom center');
break;
case 'left':
marginLeft = -1 * (tooltipWidth + targetProps.width / 2 + targetMargin);
marginTop = -1 * (tooltipHeight / 2);
this.$element.transformOrigin('center right');
break;
case 'right':
marginLeft = targetProps.width / 2 + targetMargin;
marginTop = -1 * (tooltipHeight / 2);
this.$element.transformOrigin('center left');
break;
}
const targetOffset = this.$target.offset();
this.$element.css({
top: `${targetOffset.top + targetProps.height / 2}px`,
left: `${targetOffset.left + targetProps.width / 2}px`,
'margin-left': `${marginLeft}px`,
'margin-top': `${marginTop}px`,
});
}
/**
* 触发组件事件
* @param name
*/
triggerEvent(name) {
componentEvent(name, 'tooltip', this.$target, this);
}
/**
* 动画结束回调
*/
transitionEnd() {
if (this.$element.hasClass('mdui-tooltip-open')) {
this.state = 'opened';
this.triggerEvent('opened');
}
else {
this.state = 'closed';
this.triggerEvent('closed');
}
}
/**
* 当前 tooltip 是否为打开状态
*/
isOpen() {
return this.state === 'opening' || this.state === 'opened';
}
/**
* 执行打开 tooltip
*/
doOpen() {
this.state = 'opening';
this.triggerEvent('open');
this.$element
.addClass('mdui-tooltip-open')
.transitionEnd(() => this.transitionEnd());
}
/**
* 打开 Tooltip
* @param options 允许每次打开时设置不同的参数
*/
open(options) {
if (this.isOpen()) {
return;
}
const oldOptions = extend({}, this.options);
if (options) {
extend(this.options, options);
}
// tooltip 的内容有更新
if (oldOptions.content !== this.options.content) {
this.$element.html(this.options.content);
}
this.setPosition();
if (this.options.delay) {
this.timeoutId = setTimeout(() => this.doOpen(), this.options.delay);
}
else {
this.timeoutId = null;
this.doOpen();
}
}
/**
* 关闭 Tooltip
*/
close() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
if (!this.isOpen()) {
return;
}
this.state = 'closing';
this.triggerEvent('close');
this.$element
.removeClass('mdui-tooltip-open')
.transitionEnd(() => this.transitionEnd());
}
/**
* 切换 Tooltip 的打开状态
*/
toggle() {
this.isOpen() ? this.close() : this.open();
}
/**
* 获取 Tooltip 状态。共包含四种状态:`opening`、`opened`、`closing`、`closed`
*/
getState() {
return this.state;
}
}
mdui.Tooltip = Tooltip;
const customAttr$8 = 'mdui-tooltip';
const dataName$2 = '_mdui_tooltip';
$(() => {
// mouseenter 不能冒泡,所以这里用 mouseover 代替
$document.on('touchstart mouseover', `[${customAttr$8}]`, function () {
const $target = $(this);
let instance = $target.data(dataName$2);
if (!instance) {
instance = new mdui.Tooltip(this, parseOptions(this, customAttr$8));
$target.data(dataName$2, instance);
}
});
});
const DEFAULT_OPTIONS$c = {
message: '',
timeout: 4000,
position: 'bottom',
buttonText: '',
buttonColor: '',
closeOnButtonClick: true,
closeOnOutsideClick: true,
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClick: () => { },
// eslint-disable-next-line @typescript-eslint/no-empty-function
onButtonClick: () => { },
// eslint-disable-next-line @typescript-eslint/no-empty-function
onOpen: () => { },
// eslint-disable-next-line @typescript-eslint/no-empty-function
onOpened: () => { },
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClose: () => { },
// eslint-disable-next-line @typescript-eslint/no-empty-function
onClosed: () => { },
};
/**
* 当前打开着的 Snackbar
*/
let currentInst$1 = null;
/**
* 队列名
*/
const queueName$1 = '_mdui_snackbar';
class Snackbar {
constructor(options) {
/**
* 配置参数
*/
this.options = extend({}, DEFAULT_OPTIONS$c);
/**
* 当前 Snackbar 的状态
*/
this.state = 'closed';
/**
* setTimeout 的 ID
*/
this.timeoutId = null;
extend(this.options, options);
// 按钮颜色
let buttonColorStyle = '';
let buttonColorClass = '';
if (this.options.buttonColor.indexOf('#') === 0 ||
this.options.buttonColor.indexOf('rgb') === 0) {
buttonColorStyle = `style="color:${this.options.buttonColor}"`;
}
else if (this.options.buttonColor !== '') {
buttonColorClass = `mdui-text-color-${this.options.buttonColor}`;
}
// 添加 HTML
this.$element = $('<div class="mdui-snackbar">' +
`<div class="mdui-snackbar-text">${this.options.message}</div>` +
(this.options.buttonText
? `<a href="javascript:void(0)" class="mdui-snackbar-action mdui-btn mdui-ripple mdui-ripple-white ${buttonColorClass}" ${buttonColorStyle}>${this.options.buttonText}</a>`
: '') +
'</div>').appendTo(document.body);
// 设置位置
this.setPosition('close');
this.$element.reflow().addClass(`mdui-snackbar-${this.options.position}`);
}
/**
* 点击 Snackbar 外面的区域关闭
* @param event
*/
closeOnOutsideClick(event) {
const $target = $(event.target);
if (!$target.hasClass('mdui-snackbar') &&
!$target.parents('.mdui-snackbar').length) {
currentInst$1.close();
}
}
/**
* 设置 Snackbar 的位置
* @param state
*/
setPosition(state) {
const snackbarHeight = this.$element[0].clientHeight;
const position = this.options.position;
let translateX;
let translateY;
// translateX
if (position === 'bottom' || position === 'top') {
translateX = '-50%';
}
else {
translateX = '0';
}
// translateY
if (state === 'open') {
translateY = '0';
}
else {
if (position === 'bottom') {
translateY = snackbarHeight;
}
if (position === 'top') {
translateY = -snackbarHeight;
}
if (position === 'left-top' || position === 'right-top') {
translateY = -snackbarHeight - 24;
}
if (position === 'left-bottom' || position === 'right-bottom') {
translateY = snackbarHeight + 24;
}
}
this.$element.transform(`translate(${translateX},${translateY}px`);
}
/**
* 打开 Snackbar
*/
open() {
if (this.state === 'opening' || this.state === 'opened') {
return;
}
// 如果当前有正在显示的 Snackbar则先加入队列等旧 Snackbar 关闭后再打开
if (currentInst$1) {
queue(queueName$1, () => this.open());
return;
}
currentInst$1 = this;
// 开始打开
this.state = 'opening';
this.options.onOpen(this);
this.setPosition('open');
this.$element.transitionEnd(() => {
if (this.state !== 'opening') {
return;
}
this.state = 'opened';
this.options.onOpened(this);
// 有按钮时绑定事件
if (this.options.buttonText) {
this.$element.find('.mdui-snackbar-action').on('click', () => {
this.options.onButtonClick(this);
if (this.options.closeOnButtonClick) {
this.close();
}
});
}
// 点击 snackbar 的事件
this.$element.on('click', (event) => {
if (!$(event.target).hasClass('mdui-snackbar-action')) {
this.options.onClick(this);
}
});
// 点击 Snackbar 外面的区域关闭
if (this.options.closeOnOutsideClick) {
$document.on(startEvent, this.closeOnOutsideClick);
}
// 超时后自动关闭
if (this.options.timeout) {
this.timeoutId = setTimeout(() => this.close(), this.options.timeout);
}
});
}
/**
* 关闭 Snackbar
*/
close() {
if (this.state === 'closing' || this.state === 'closed') {
return;
}
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
if (this.options.closeOnOutsideClick) {
$document.off(startEvent, this.closeOnOutsideClick);
}
this.state = 'closing';
this.options.onClose(this);
this.setPosition('close');
this.$element.transitionEnd(() => {
if (this.state !== 'closing') {
return;
}
currentInst$1 = null;
this.state = 'closed';
this.options.onClosed(this);
this.$element.remove();
dequeue(queueName$1);
});
}
}
mdui.snackbar = function (message, options = {}) {
if (isString(message)) {
options.message = message;
}
else {
options = message;
}
const instance = new Snackbar(options);
instance.open();
return instance;
};
$(() => {
// 切换导航项
$document.on('click', '.mdui-bottom-nav>a', function () {
const $item = $(this);
const $bottomNav = $item.parent();
$bottomNav.children('a').each((index, item) => {
const isThis = $item.is(item);
if (isThis) {
componentEvent('change', 'bottomNav', $bottomNav[0], undefined, {
index,
});
}
isThis
? $(item).addClass('mdui-bottom-nav-active')
: $(item).removeClass('mdui-bottom-nav-active');
});
});
// 滚动时隐藏 mdui-bottom-nav-scroll-hide
mdui.mutation('.mdui-bottom-nav-scroll-hide', function () {
new mdui.Headroom(this, {
pinnedClass: 'mdui-headroom-pinned-down',
unpinnedClass: 'mdui-headroom-unpinned-down',
});
});
});
/**
* layer 的 HTML 结构
* @param index
*/
function layerHTML(index = false) {
return (`<div class="mdui-spinner-layer ${index ? `mdui-spinner-layer-${index}` : ''}">` +
'<div class="mdui-spinner-circle-clipper mdui-spinner-left">' +
'<div class="mdui-spinner-circle"></div>' +
'</div>' +
'<div class="mdui-spinner-gap-patch">' +
'<div class="mdui-spinner-circle"></div>' +
'</div>' +
'<div class="mdui-spinner-circle-clipper mdui-spinner-right">' +
'<div class="mdui-spinner-circle"></div>' +
'</div>' +
'</div>');
}
/**
* 填充 HTML
* @param spinner
*/
function fillHTML(spinner) {
const $spinner = $(spinner);
const layer = $spinner.hasClass('mdui-spinner-colorful')
? layerHTML(1) + layerHTML(2) + layerHTML(3) + layerHTML(4)
: layerHTML();
$spinner.html(layer);
}
$(() => {
// 页面加载完后自动填充 HTML 结构
mdui.mutation('.mdui-spinner', function () {
fillHTML(this);
});
});
mdui.updateSpinners = function (selector) {
const $elements = isUndefined(selector) ? $('.mdui-spinner') : $(selector);
$elements.each(function () {
fillHTML(this);
});
};
const DEFAULT_OPTIONS$d = {
position: 'auto',
align: 'auto',
gutter: 16,
fixed: false,
covered: 'auto',
subMenuTrigger: 'hover',
subMenuDelay: 200,
};
class Menu {
constructor(anchorSelector, menuSelector, options = {}) {
/**
* 配置参数
*/
this.options = extend({}, DEFAULT_OPTIONS$d);
/**
* 当前菜单状态
*/
this.state = 'closed';
this.$anchor = $(anchorSelector).first();
this.$element = $(menuSelector).first();
// 触发菜单的元素 和 菜单必须是同级的元素,否则菜单可能不能定位
if (!this.$anchor.parent().is(this.$element.parent())) {
throw new Error('anchorSelector and menuSelector must be siblings');
}
extend(this.options, options);
// 是否是级联菜单
this.isCascade = this.$element.hasClass('mdui-menu-cascade');
// covered 参数处理
this.isCovered =
this.options.covered === 'auto' ? !this.isCascade : this.options.covered;
// 点击触发菜单切换
this.$anchor.on('click', () => this.toggle());
// 点击菜单外面区域关闭菜单
$document.on('click touchstart', (event) => {
const $target = $(event.target);
if (this.isOpen() &&
!$target.is(this.$element) &&
!contains(this.$element[0], $target[0]) &&
!$target.is(this.$anchor) &&
!contains(this.$anchor[0], $target[0])) {
this.close();
}
});
// 点击不含子菜单的菜单条目关闭菜单
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
$document.on('click', '.mdui-menu-item', function () {
const $item = $(this);
if (!$item.find('.mdui-menu').length &&
$item.attr('disabled') === undefined) {
that.close();
}
});
// 绑定点击或鼠标移入含子菜单的条目的事件
this.bindSubMenuEvent();
// 窗口大小变化时,重新调整菜单位置
$window.on('resize', $.throttle(() => this.readjust(), 100));
}
/**
* 是否为打开状态
*/
isOpen() {
return this.state === 'opening' || this.state === 'opened';
}
/**
* 触发组件事件
* @param name
*/
triggerEvent(name) {
componentEvent(name, 'menu', this.$element, this);
}
/**
* 调整主菜单位置
*/
readjust() {
let menuLeft;
let menuTop;
// 菜单位置和方向
let position;
let align;
// window 窗口的宽度和高度
const windowHeight = $window.height();
const windowWidth = $window.width();
// 配置参数
const gutter = this.options.gutter;
const isCovered = this.isCovered;
const isFixed = this.options.fixed;
// 动画方向参数
let transformOriginX;
let transformOriginY;
// 菜单的原始宽度和高度
const menuWidth = this.$element.width();
const menuHeight = this.$element.height();
// 触发菜单的元素在窗口中的位置
const anchorRect = this.$anchor[0].getBoundingClientRect();
const anchorTop = anchorRect.top;
const anchorLeft = anchorRect.left;
const anchorHeight = anchorRect.height;
const anchorWidth = anchorRect.width;
const anchorBottom = windowHeight - anchorTop - anchorHeight;
const anchorRight = windowWidth - anchorLeft - anchorWidth;
// 触发元素相对其拥有定位属性的父元素的位置
const anchorOffsetTop = this.$anchor[0].offsetTop;
const anchorOffsetLeft = this.$anchor[0].offsetLeft;
// 自动判断菜单位置
if (this.options.position === 'auto') {
if (anchorBottom + (isCovered ? anchorHeight : 0) > menuHeight + gutter) {
// 判断下方是否放得下菜单
position = 'bottom';
}
else if (anchorTop + (isCovered ? anchorHeight : 0) >
menuHeight + gutter) {
// 判断上方是否放得下菜单
position = 'top';
}
else {
// 上下都放不下,居中显示
position = 'center';
}
}
else {
position = this.options.position;
}
// 自动判断菜单对齐方式
if (this.options.align === 'auto') {
if (anchorRight + anchorWidth > menuWidth + gutter) {
// 判断右侧是否放得下菜单
align = 'left';
}
else if (anchorLeft + anchorWidth > menuWidth + gutter) {
// 判断左侧是否放得下菜单
align = 'right';
}
else {
// 左右都放不下,居中显示
align = 'center';
}
}
else {
align = this.options.align;
}
// 设置菜单位置
if (position === 'bottom') {
transformOriginY = '0';
menuTop =
(isCovered ? 0 : anchorHeight) +
(isFixed ? anchorTop : anchorOffsetTop);
}
else if (position === 'top') {
transformOriginY = '100%';
menuTop =
(isCovered ? anchorHeight : 0) +
(isFixed ? anchorTop - menuHeight : anchorOffsetTop - menuHeight);
}
else {
transformOriginY = '50%';
// =====================在窗口中居中
// 显示的菜单的高度,简单菜单高度不超过窗口高度,若超过了则在菜单内部显示滚动条
// 级联菜单内部不允许出现滚动条
let menuHeightTemp = menuHeight;
// 简单菜单比窗口高时,限制菜单高度
if (!this.isCascade) {
if (menuHeight + gutter * 2 > windowHeight) {
menuHeightTemp = windowHeight - gutter * 2;
this.$element.height(menuHeightTemp);
}
}
menuTop =
(windowHeight - menuHeightTemp) / 2 +
(isFixed ? 0 : anchorOffsetTop - anchorTop);
}
this.$element.css('top', `${menuTop}px`);
// 设置菜单对齐方式
if (align === 'left') {
transformOriginX = '0';
menuLeft = isFixed ? anchorLeft : anchorOffsetLeft;
}
else if (align === 'right') {
transformOriginX = '100%';
menuLeft = isFixed
? anchorLeft + anchorWidth - menuWidth
: anchorOffsetLeft + anchorWidth - menuWidth;
}
else {
transformOriginX = '50%';
//=======================在窗口中居中
// 显示的菜单的宽度,菜单宽度不能超过窗口宽度
let menuWidthTemp = menuWidth;
// 菜单比窗口宽,限制菜单宽度
if (menuWidth + gutter * 2 > windowWidth) {
menuWidthTemp = windowWidth - gutter * 2;
this.$element.width(menuWidthTemp);
}
menuLeft =
(windowWidth - menuWidthTemp) / 2 +
(isFixed ? 0 : anchorOffsetLeft - anchorLeft);
}
this.$element.css('left', `${menuLeft}px`);
// 设置菜单动画方向
this.$element.transformOrigin(`${transformOriginX} ${transformOriginY}`);
}
/**
* 调整子菜单的位置
* @param $submenu
*/
readjustSubmenu($submenu) {
const $item = $submenu.parent('.mdui-menu-item');
let submenuTop;
let submenuLeft;
// 子菜单位置和方向
let position;
let align;
// window 窗口的宽度和高度
const windowHeight = $window.height();
const windowWidth = $window.width();
// 动画方向参数
let transformOriginX;
let transformOriginY;
// 子菜单的原始宽度和高度
const submenuWidth = $submenu.width();
const submenuHeight = $submenu.height();
// 触发子菜单的菜单项的宽度高度
const itemRect = $item[0].getBoundingClientRect();
const itemWidth = itemRect.width;
const itemHeight = itemRect.height;
const itemLeft = itemRect.left;
const itemTop = itemRect.top;
// 判断菜单上下位置
if (windowHeight - itemTop > submenuHeight) {
// 判断下方是否放得下菜单
position = 'bottom';
}
else if (itemTop + itemHeight > submenuHeight) {
// 判断上方是否放得下菜单
position = 'top';
}
else {
// 默认放在下方
position = 'bottom';
}
// 判断菜单左右位置
if (windowWidth - itemLeft - itemWidth > submenuWidth) {
// 判断右侧是否放得下菜单
align = 'left';
}
else if (itemLeft > submenuWidth) {
// 判断左侧是否放得下菜单
align = 'right';
}
else {
// 默认放在右侧
align = 'left';
}
// 设置菜单位置
if (position === 'bottom') {
transformOriginY = '0';
submenuTop = '0';
}
else if (position === 'top') {
transformOriginY = '100%';
submenuTop = -submenuHeight + itemHeight;
}
$submenu.css('top', `${submenuTop}px`);
// 设置菜单对齐方式
if (align === 'left') {
transformOriginX = '0';
submenuLeft = itemWidth;
}
else if (align === 'right') {
transformOriginX = '100%';
submenuLeft = -submenuWidth;
}
$submenu.css('left', `${submenuLeft}px`);
// 设置菜单动画方向
$submenu.transformOrigin(`${transformOriginX} ${transformOriginY}`);
}
/**
* 打开子菜单
* @param $submenu
*/
openSubMenu($submenu) {
this.readjustSubmenu($submenu);
$submenu
.addClass('mdui-menu-open')
.parent('.mdui-menu-item')
.addClass('mdui-menu-item-active');
}
/**
* 关闭子菜单,及其嵌套的子菜单
* @param $submenu
*/
closeSubMenu($submenu) {
// 关闭子菜单
$submenu
.removeClass('mdui-menu-open')
.addClass('mdui-menu-closing')
.transitionEnd(() => $submenu.removeClass('mdui-menu-closing'))
// 移除激活状态的样式
.parent('.mdui-menu-item')
.removeClass('mdui-menu-item-active');
// 循环关闭嵌套的子菜单
$submenu.find('.mdui-menu').each((_, menu) => {
const $subSubmenu = $(menu);
$subSubmenu
.removeClass('mdui-menu-open')
.addClass('mdui-menu-closing')
.transitionEnd(() => $subSubmenu.removeClass('mdui-menu-closing'))
.parent('.mdui-menu-item')
.removeClass('mdui-menu-item-active');
});
}
/**
* 切换子菜单状态
* @param $submenu
*/
toggleSubMenu($submenu) {
$submenu.hasClass('mdui-menu-open')
? this.closeSubMenu($submenu)
: this.openSubMenu($submenu);
}
/**
* 绑定子菜单事件
*/
bindSubMenuEvent() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
// 点击打开子菜单
this.$element.on('click', '.mdui-menu-item', function (event) {
const $item = $(this);
const $target = $(event.target);
// 禁用状态菜单不操作
if ($item.attr('disabled') !== undefined) {
return;
}
// 没有点击在子菜单的菜单项上时,不操作(点在了子菜单的空白区域、或分隔线上)
if ($target.is('.mdui-menu') || $target.is('.mdui-divider')) {
return;
}
// 阻止冒泡,点击菜单项时只在最后一级的 mdui-menu-item 上生效,不向上冒泡
if (!$target.parents('.mdui-menu-item').first().is($item)) {
return;
}
// 当前菜单的子菜单
const $submenu = $item.children('.mdui-menu');
// 先关闭除当前子菜单外的所有同级子菜单
$item
.parent('.mdui-menu')
.children('.mdui-menu-item')
.each((_, item) => {
const $tmpSubmenu = $(item).children('.mdui-menu');
if ($tmpSubmenu.length &&
(!$submenu.length || !$tmpSubmenu.is($submenu))) {
that.closeSubMenu($tmpSubmenu);
}
});
// 切换当前子菜单
if ($submenu.length) {
that.toggleSubMenu($submenu);
}
});
if (this.options.subMenuTrigger === 'hover') {
// 临时存储 setTimeout 对象
let timeout = null;
let timeoutOpen = null;
this.$element.on('mouseover mouseout', '.mdui-menu-item', function (event) {
const $item = $(this);
const eventType = event.type;
const $relatedTarget = $(event.relatedTarget);
// 禁用状态的菜单不操作
if ($item.attr('disabled') !== undefined) {
return;
}
// 用 mouseover 模拟 mouseenter
if (eventType === 'mouseover') {
if (!$item.is($relatedTarget) &&
contains($item[0], $relatedTarget[0])) {
return;
}
}
// 用 mouseout 模拟 mouseleave
else if (eventType === 'mouseout') {
if ($item.is($relatedTarget) ||
contains($item[0], $relatedTarget[0])) {
return;
}
}
// 当前菜单项下的子菜单,未必存在
const $submenu = $item.children('.mdui-menu');
// 鼠标移入菜单项时,显示菜单项下的子菜单
if (eventType === 'mouseover') {
if ($submenu.length) {
// 当前子菜单准备打开时,如果当前子菜单正准备着关闭,不用再关闭了
const tmpClose = $submenu.data('timeoutClose.mdui.menu');
if (tmpClose) {
clearTimeout(tmpClose);
}
// 如果当前子菜单已经打开,不操作
if ($submenu.hasClass('mdui-menu-open')) {
return;
}
// 当前子菜单准备打开时,其他准备打开的子菜单不用再打开了
clearTimeout(timeoutOpen);
// 准备打开当前子菜单
timeout = timeoutOpen = setTimeout(() => that.openSubMenu($submenu), that.options.subMenuDelay);
$submenu.data('timeoutOpen.mdui.menu', timeout);
}
}
// 鼠标移出菜单项时,关闭菜单项下的子菜单
else if (eventType === 'mouseout') {
if ($submenu.length) {
// 鼠标移出菜单项时,如果当前菜单项下的子菜单正准备打开,不用再打开了
const tmpOpen = $submenu.data('timeoutOpen.mdui.menu');
if (tmpOpen) {
clearTimeout(tmpOpen);
}
// 准备关闭当前子菜单
timeout = setTimeout(() => that.closeSubMenu($submenu), that.options.subMenuDelay);
$submenu.data('timeoutClose.mdui.menu', timeout);
}
}
});
}
}
/**
* 动画结束回调
*/
transitionEnd() {
this.$element.removeClass('mdui-menu-closing');
if (this.state === 'opening') {
this.state = 'opened';
this.triggerEvent('opened');
}
if (this.state === 'closing') {
this.state = 'closed';
this.triggerEvent('closed');
// 关闭后,恢复菜单样式到默认状态,并恢复 fixed 定位
this.$element.css({
top: '',
left: '',
width: '',
position: 'fixed',
});
}
}
/**
* 切换菜单状态
*/
toggle() {
this.isOpen() ? this.close() : this.open();
}
/**
* 打开菜单
*/
open() {
if (this.isOpen()) {
return;
}
this.state = 'opening';
this.triggerEvent('open');
this.readjust();
this.$element
// 菜单隐藏状态使用使用 fixed 定位。
.css('position', this.options.fixed ? 'fixed' : 'absolute')
.addClass('mdui-menu-open')
.transitionEnd(() => this.transitionEnd());
}
/**
* 关闭菜单
*/
close() {
if (!this.isOpen()) {
return;
}
this.state = 'closing';
this.triggerEvent('close');
// 菜单开始关闭时,关闭所有子菜单
this.$element.find('.mdui-menu').each((_, submenu) => {
this.closeSubMenu($(submenu));
});
this.$element
.removeClass('mdui-menu-open')
.addClass('mdui-menu-closing')
.transitionEnd(() => this.transitionEnd());
}
}
mdui.Menu = Menu;
const customAttr$9 = 'mdui-menu';
const dataName$3 = '_mdui_menu';
$(() => {
$document.on('click', `[${customAttr$9}]`, function () {
const $this = $(this);
let instance = $this.data(dataName$3);
if (!instance) {
const options = parseOptions(this, customAttr$9);
const menuSelector = options.target;
// @ts-ignore
delete options.target;
instance = new mdui.Menu($this, menuSelector, options);
$this.data(dataName$3, instance);
instance.toggle();
}
});
});
export default mdui;
//# sourceMappingURL=mdui.esm.js.map