@mdui/shared 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +14 -0
- package/controllers/form.d.ts +53 -0
- package/controllers/form.js +214 -0
- package/controllers/has-slot.d.ts +17 -0
- package/controllers/has-slot.js +46 -0
- package/controllers/hover.d.ts +34 -0
- package/controllers/hover.js +62 -0
- package/decorators/default-value.d.ts +16 -0
- package/decorators/default-value.js +40 -0
- package/decorators/watch.d.ts +17 -0
- package/decorators/watch.js +37 -0
- package/helpers/animate.d.ts +2 -0
- package/helpers/animate.js +37 -0
- package/helpers/breakpoint.d.ts +49 -0
- package/helpers/breakpoint.js +102 -0
- package/helpers/decorator.d.ts +16 -0
- package/helpers/decorator.js +18 -0
- package/helpers/delay.d.ts +4 -0
- package/helpers/delay.js +6 -0
- package/helpers/event.d.ts +7 -0
- package/helpers/event.js +17 -0
- package/helpers/modal.d.ts +12 -0
- package/helpers/modal.js +52 -0
- package/helpers/motion.d.ts +13 -0
- package/helpers/motion.js +20 -0
- package/helpers/observeResize.d.ts +16 -0
- package/helpers/observeResize.js +57 -0
- package/helpers/queue.d.ts +18 -0
- package/helpers/queue.js +25 -0
- package/helpers/scroll.d.ts +14 -0
- package/helpers/scroll.js +28 -0
- package/helpers/slot.d.ts +10 -0
- package/helpers/slot.js +31 -0
- package/helpers/tabbable.d.ts +8 -0
- package/helpers/tabbable.js +82 -0
- package/helpers/template.d.ts +2 -0
- package/helpers/template.js +2 -0
- package/helpers/throttle.d.ts +14 -0
- package/helpers/throttle.js +28 -0
- package/helpers/uniqueId.d.ts +4 -0
- package/helpers/uniqueId.js +7 -0
- package/icons/arrow-right.d.ts +11 -0
- package/icons/arrow-right.js +14 -0
- package/icons/cancel--outlined.d.ts +11 -0
- package/icons/cancel--outlined.js +14 -0
- package/icons/check-box-outline-blank.d.ts +11 -0
- package/icons/check-box-outline-blank.js +14 -0
- package/icons/check-box.d.ts +11 -0
- package/icons/check-box.js +14 -0
- package/icons/check.d.ts +11 -0
- package/icons/check.js +14 -0
- package/icons/circle.d.ts +11 -0
- package/icons/circle.js +14 -0
- package/icons/clear.d.ts +11 -0
- package/icons/clear.js +14 -0
- package/icons/error.d.ts +11 -0
- package/icons/error.js +14 -0
- package/icons/indeterminate-check-box.d.ts +11 -0
- package/icons/indeterminate-check-box.js +14 -0
- package/icons/radio-button-unchecked.d.ts +11 -0
- package/icons/radio-button-unchecked.js +14 -0
- package/icons/shared/style.d.ts +1 -0
- package/icons/shared/style.js +2 -0
- package/icons/shared/svg-tag.d.ts +2 -0
- package/icons/shared/svg-tag.js +3 -0
- package/icons/visibility-off.d.ts +11 -0
- package/icons/visibility-off.js +14 -0
- package/icons/visibility.d.ts +11 -0
- package/icons/visibility.js +14 -0
- package/lit-styles/component-style.d.ts +1 -0
- package/lit-styles/component-style.js +2 -0
- package/mixin.less +380 -0
- package/mixins/anchor.d.ts +22 -0
- package/mixins/anchor.js +25 -0
- package/mixins/focusable.d.ts +19 -0
- package/mixins/focusable.js +251 -0
- package/mixins/scrollBehavior.d.ts +24 -0
- package/mixins/scrollBehavior.js +202 -0
- package/package.json +35 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 断点相关的 js 函数
|
|
3
|
+
* 这些函数直接导出到 mdui 全局对象下,供应用使用。框架内部使用时,为避免循环依赖,从 @mdui/shared 包内导入
|
|
4
|
+
*/
|
|
5
|
+
import { getDocument, getWindow } from 'ssr-window';
|
|
6
|
+
import { $ } from '@mdui/jq/$.js';
|
|
7
|
+
import '@mdui/jq/methods/innerWidth.js';
|
|
8
|
+
import { isElement, isNumber } from '@mdui/jq/shared/helper.js';
|
|
9
|
+
/**
|
|
10
|
+
* 获取断点对象,通过返回的对象可用于判断指定宽度、或指定元素的宽度、或当前窗口宽度与各个断点值的关系
|
|
11
|
+
*
|
|
12
|
+
* * 未传入参数时,获取的是 `window` 的宽度对应的断点对象
|
|
13
|
+
* * 若传入数值,则获取的是该数值宽度对应的断点对象
|
|
14
|
+
* * 若传入 CSS 选择器,则获取的是该选择器对应元素的宽度对应的断点对象
|
|
15
|
+
* * 若传入 HTML 元素,则获取的是该元素的宽度对应的断点对象
|
|
16
|
+
* * 若传入 JQ 对象,则获取的是该 JQ 对象中的元素的宽度对应的断点对象
|
|
17
|
+
*
|
|
18
|
+
* 返回的对象包含以下方法:
|
|
19
|
+
*
|
|
20
|
+
* * `up(breakpoint)`:判断当前宽度是否大于指定断点值
|
|
21
|
+
* * `down(breakpoint)`:判断当前宽度是否小于指定断点值
|
|
22
|
+
* * `only(breakpoint)`:判断当前宽度是否在指定断点值内
|
|
23
|
+
* * `not(breakpoint)`:判断当前宽度是否不在指定断点值内
|
|
24
|
+
* * `between(startBreakpoint, endBreakpoint)`:判断当前宽度是否在指定断点值之间
|
|
25
|
+
*/
|
|
26
|
+
export const breakpoint = (width) => {
|
|
27
|
+
const window = getWindow();
|
|
28
|
+
const document = getDocument();
|
|
29
|
+
const computedStyle = window.getComputedStyle(document.documentElement);
|
|
30
|
+
// 容器的宽度
|
|
31
|
+
const containerWidth = isElement(width)
|
|
32
|
+
? $(width).innerWidth()
|
|
33
|
+
: isNumber(width)
|
|
34
|
+
? width
|
|
35
|
+
: $(window).innerWidth();
|
|
36
|
+
// 断点对应的宽度值
|
|
37
|
+
const getBreakpointValue = (breakpoint) => {
|
|
38
|
+
const width = computedStyle
|
|
39
|
+
.getPropertyValue(`--mdui-breakpoint-${breakpoint}`)
|
|
40
|
+
.toLowerCase();
|
|
41
|
+
return parseFloat(width);
|
|
42
|
+
};
|
|
43
|
+
// 获取比指定断点更大的一个断点
|
|
44
|
+
const getNextBreakpoint = (breakpoint) => {
|
|
45
|
+
switch (breakpoint) {
|
|
46
|
+
case 'xs':
|
|
47
|
+
return 'sm';
|
|
48
|
+
case 'sm':
|
|
49
|
+
return 'md';
|
|
50
|
+
case 'md':
|
|
51
|
+
return 'lg';
|
|
52
|
+
case 'lg':
|
|
53
|
+
return 'xl';
|
|
54
|
+
case 'xl':
|
|
55
|
+
return 'xxl';
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
/**
|
|
60
|
+
* 当前宽度是否大于指定断点值
|
|
61
|
+
* @param breakpoint
|
|
62
|
+
*/
|
|
63
|
+
up(breakpoint) {
|
|
64
|
+
return containerWidth >= getBreakpointValue(breakpoint);
|
|
65
|
+
},
|
|
66
|
+
/**
|
|
67
|
+
* 当前宽度是否小于指定断点值
|
|
68
|
+
* @param breakpoint
|
|
69
|
+
*/
|
|
70
|
+
down(breakpoint) {
|
|
71
|
+
return containerWidth < getBreakpointValue(breakpoint);
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* 当前宽度是否在指定断点值内
|
|
75
|
+
* @param breakpoint
|
|
76
|
+
*/
|
|
77
|
+
only(breakpoint) {
|
|
78
|
+
if (breakpoint === 'xxl') {
|
|
79
|
+
return this.up(breakpoint);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
return this.up(breakpoint) && this.down(getNextBreakpoint(breakpoint));
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
/**
|
|
86
|
+
* 当前宽度是否不在指定断点值内
|
|
87
|
+
* @param breakpoint
|
|
88
|
+
*/
|
|
89
|
+
not(breakpoint) {
|
|
90
|
+
return !this.only(breakpoint);
|
|
91
|
+
},
|
|
92
|
+
/**
|
|
93
|
+
* 当前宽度是否在指定断点值之间
|
|
94
|
+
* @param startBreakpoint
|
|
95
|
+
* @param endBreakpoint
|
|
96
|
+
* @returns
|
|
97
|
+
*/
|
|
98
|
+
between(startBreakpoint, endBreakpoint) {
|
|
99
|
+
return this.up(startBreakpoint) && this.down(endBreakpoint);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 在原生的 HTML 中,布尔属性只要添加了属性名,不论属性值设置成什么,属性值都是 true
|
|
3
|
+
* 但这里设置了 attr="false" 时,要把属性设置为 false
|
|
4
|
+
*
|
|
5
|
+
* 原因是:
|
|
6
|
+
* 在 vue3 中,通过 :attr="value" 设置属性时,vue 会优先从 DOM 属性中寻找是否存在 attr 属性名,
|
|
7
|
+
* 若存在,则设置对应的 DOM 属性,否则设置对应的 attribute 属性
|
|
8
|
+
* 但在 vue 的服务端渲染(ssr)时,不存在 DOM 对象,所以会把 attribute 属性设置成 attr="true" 或 attr="false"
|
|
9
|
+
* 所以在 attribute 属性 attr="false" 时,需要把属性值转换为布尔值 false
|
|
10
|
+
*
|
|
11
|
+
* 这段代码不能封装成函数,否则生成 custom-elements.json 会识别不了
|
|
12
|
+
* 这段注释仅在这里写一次,其他地方不再重复
|
|
13
|
+
*
|
|
14
|
+
* @see https://v3-migration.vuejs.org/zh/breaking-changes/attribute-coercion.html
|
|
15
|
+
*/
|
|
16
|
+
export declare const booleanConverter: (value: string | null) => boolean;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 在原生的 HTML 中,布尔属性只要添加了属性名,不论属性值设置成什么,属性值都是 true
|
|
3
|
+
* 但这里设置了 attr="false" 时,要把属性设置为 false
|
|
4
|
+
*
|
|
5
|
+
* 原因是:
|
|
6
|
+
* 在 vue3 中,通过 :attr="value" 设置属性时,vue 会优先从 DOM 属性中寻找是否存在 attr 属性名,
|
|
7
|
+
* 若存在,则设置对应的 DOM 属性,否则设置对应的 attribute 属性
|
|
8
|
+
* 但在 vue 的服务端渲染(ssr)时,不存在 DOM 对象,所以会把 attribute 属性设置成 attr="true" 或 attr="false"
|
|
9
|
+
* 所以在 attribute 属性 attr="false" 时,需要把属性值转换为布尔值 false
|
|
10
|
+
*
|
|
11
|
+
* 这段代码不能封装成函数,否则生成 custom-elements.json 会识别不了
|
|
12
|
+
* 这段注释仅在这里写一次,其他地方不再重复
|
|
13
|
+
*
|
|
14
|
+
* @see https://v3-migration.vuejs.org/zh/breaking-changes/attribute-coercion.html
|
|
15
|
+
*/
|
|
16
|
+
export const booleanConverter = (value) => {
|
|
17
|
+
return value !== null && value !== 'false';
|
|
18
|
+
};
|
package/helpers/delay.js
ADDED
package/helpers/event.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 触发自定义事件
|
|
3
|
+
* @param element
|
|
4
|
+
* @param type
|
|
5
|
+
* @param options 通常只用到 cancelable 和 detail;bubbles、composed 统一不用
|
|
6
|
+
*/
|
|
7
|
+
export const emit = (element, type, options) => {
|
|
8
|
+
const event = new CustomEvent(type, {
|
|
9
|
+
bubbles: true,
|
|
10
|
+
cancelable: false,
|
|
11
|
+
composed: true,
|
|
12
|
+
detail: {},
|
|
13
|
+
...options,
|
|
14
|
+
});
|
|
15
|
+
element.dispatchEvent(event);
|
|
16
|
+
return event;
|
|
17
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class Modal {
|
|
2
|
+
private readonly element;
|
|
3
|
+
private tabDirection;
|
|
4
|
+
constructor(element: HTMLElement);
|
|
5
|
+
activate(): void;
|
|
6
|
+
deactivate(): void;
|
|
7
|
+
private isActive;
|
|
8
|
+
private checkFocus;
|
|
9
|
+
private handleFocusIn;
|
|
10
|
+
private handleKeyDown;
|
|
11
|
+
private handleKeyUp;
|
|
12
|
+
}
|
package/helpers/modal.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** https://github.com/shoelace-style/shoelace/blob/next/src/internal/modal.ts */
|
|
2
|
+
import { getTabbableBoundary } from './tabbable.js';
|
|
3
|
+
let activeModals = [];
|
|
4
|
+
export class Modal {
|
|
5
|
+
constructor(element) {
|
|
6
|
+
this.tabDirection = 'forward';
|
|
7
|
+
this.element = element;
|
|
8
|
+
this.handleFocusIn = this.handleFocusIn.bind(this);
|
|
9
|
+
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
10
|
+
this.handleKeyUp = this.handleKeyUp.bind(this);
|
|
11
|
+
}
|
|
12
|
+
activate() {
|
|
13
|
+
activeModals.push(this.element);
|
|
14
|
+
document.addEventListener('focusin', this.handleFocusIn);
|
|
15
|
+
document.addEventListener('keydown', this.handleKeyDown);
|
|
16
|
+
document.addEventListener('keyup', this.handleKeyUp);
|
|
17
|
+
}
|
|
18
|
+
deactivate() {
|
|
19
|
+
activeModals = activeModals.filter((modal) => modal !== this.element);
|
|
20
|
+
document.removeEventListener('focusin', this.handleFocusIn);
|
|
21
|
+
document.removeEventListener('keydown', this.handleKeyDown);
|
|
22
|
+
document.removeEventListener('keyup', this.handleKeyUp);
|
|
23
|
+
}
|
|
24
|
+
isActive() {
|
|
25
|
+
// The "active" modal is always the most recent one shown
|
|
26
|
+
return activeModals[activeModals.length - 1] === this.element;
|
|
27
|
+
}
|
|
28
|
+
checkFocus() {
|
|
29
|
+
if (this.isActive()) {
|
|
30
|
+
if (!this.element.matches(':focus-within')) {
|
|
31
|
+
const { start, end } = getTabbableBoundary(this.element);
|
|
32
|
+
const target = this.tabDirection === 'forward' ? start : end;
|
|
33
|
+
if (typeof (target === null || target === void 0 ? void 0 : target.focus) === 'function') {
|
|
34
|
+
target.focus({ preventScroll: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
handleFocusIn() {
|
|
40
|
+
this.checkFocus();
|
|
41
|
+
}
|
|
42
|
+
handleKeyDown(event) {
|
|
43
|
+
if (event.key === 'Tab' && event.shiftKey) {
|
|
44
|
+
this.tabDirection = 'backward';
|
|
45
|
+
}
|
|
46
|
+
// Ensure focus remains trapped after they key is pressed
|
|
47
|
+
requestAnimationFrame(() => this.checkFocus());
|
|
48
|
+
}
|
|
49
|
+
handleKeyUp() {
|
|
50
|
+
this.tabDirection = 'forward';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import '@mdui/jq/methods/css.js';
|
|
2
|
+
/**
|
|
3
|
+
* 获取由 CSS 变量定义的缓动曲线值
|
|
4
|
+
* @param element 在指定元素上获取值;若需要获取全局值,则传入 document.body
|
|
5
|
+
* @param name 缓动曲线名称
|
|
6
|
+
*/
|
|
7
|
+
export declare const getEasing: (element: HTMLElement, name: 'linear' | 'standard' | 'standard-accelerate' | 'standard-decelerate' | 'emphasized' | 'emphasized-accelerate' | 'emphasized-decelerate') => string;
|
|
8
|
+
/**
|
|
9
|
+
* 获取由 CSS 变量定义的动画持续时间
|
|
10
|
+
* @param element 在指定元素上获取值;若需要获取全局值,则传入 document.body
|
|
11
|
+
* @param name 持续时间名称
|
|
12
|
+
*/
|
|
13
|
+
export declare const getDuration: (element: HTMLElement, name: 'short1' | 'short2' | 'short3' | 'short4' | 'medium1' | 'medium2' | 'medium3' | 'medium4' | 'long1' | 'long2' | 'long3' | 'long4' | 'extra-long1' | 'extra-long2' | 'extra-long3' | 'extra-long4') => number;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { $ } from '@mdui/jq/$.js';
|
|
2
|
+
import '@mdui/jq/methods/css.js';
|
|
3
|
+
/**
|
|
4
|
+
* 获取由 CSS 变量定义的缓动曲线值
|
|
5
|
+
* @param element 在指定元素上获取值;若需要获取全局值,则传入 document.body
|
|
6
|
+
* @param name 缓动曲线名称
|
|
7
|
+
*/
|
|
8
|
+
export const getEasing = (element, name) => {
|
|
9
|
+
const cssVariableName = `--mdui-motion-easing-${name}`;
|
|
10
|
+
return $(element).css(cssVariableName).trim();
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* 获取由 CSS 变量定义的动画持续时间
|
|
14
|
+
* @param element 在指定元素上获取值;若需要获取全局值,则传入 document.body
|
|
15
|
+
* @param name 持续时间名称
|
|
16
|
+
*/
|
|
17
|
+
export const getDuration = (element, name) => {
|
|
18
|
+
const cssVariableName = `--mdui-motion-duration-${name}`;
|
|
19
|
+
return parseFloat($(element).css(cssVariableName).trim());
|
|
20
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import '@mdui/jq/methods/each.js';
|
|
2
|
+
import type { JQ } from '@mdui/jq/shared/core.js';
|
|
3
|
+
export interface ObserveResize {
|
|
4
|
+
/**
|
|
5
|
+
* 取消监听
|
|
6
|
+
*/
|
|
7
|
+
unobserve: () => void;
|
|
8
|
+
}
|
|
9
|
+
type Callback = (this: ObserveResize, entry: ResizeObserverEntry, observer: ObserveResize) => void;
|
|
10
|
+
/**
|
|
11
|
+
* 监听元素的尺寸变化
|
|
12
|
+
* @param target 监听该元素的尺寸变化
|
|
13
|
+
* @param callback 尺寸变化时执行的回调函数,`this` 指向监听的元素
|
|
14
|
+
*/
|
|
15
|
+
export declare const observeResize: (target: string | HTMLElement | JQ<HTMLElement>, callback: Callback) => ObserveResize;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { $ } from '@mdui/jq/$.js';
|
|
2
|
+
import '@mdui/jq/methods/each.js';
|
|
3
|
+
import { uniqueId } from './uniqueId.js';
|
|
4
|
+
let weakMap;
|
|
5
|
+
// ResizeObserver 实例,所有 resizeObserver 函数内部共用一个 ResizeObserver 实例
|
|
6
|
+
let observer;
|
|
7
|
+
/**
|
|
8
|
+
* 监听元素的尺寸变化
|
|
9
|
+
* @param target 监听该元素的尺寸变化
|
|
10
|
+
* @param callback 尺寸变化时执行的回调函数,`this` 指向监听的元素
|
|
11
|
+
*/
|
|
12
|
+
export const observeResize = (target, callback) => {
|
|
13
|
+
const $target = $(target);
|
|
14
|
+
const key = uniqueId();
|
|
15
|
+
// 取消监听函数
|
|
16
|
+
const result = {
|
|
17
|
+
unobserve: () => {
|
|
18
|
+
$target.each((_, target) => {
|
|
19
|
+
var _a;
|
|
20
|
+
const coArr = (_a = weakMap.get(target)) !== null && _a !== void 0 ? _a : [];
|
|
21
|
+
const index = coArr.findIndex((co) => co.key === key);
|
|
22
|
+
if (index !== -1) {
|
|
23
|
+
coArr.splice(index, 1);
|
|
24
|
+
}
|
|
25
|
+
if (!coArr.length) {
|
|
26
|
+
observer.unobserve(target);
|
|
27
|
+
weakMap.delete(target);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
weakMap.set(target, coArr);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
// 初始化
|
|
36
|
+
if (!weakMap) {
|
|
37
|
+
weakMap = new WeakMap();
|
|
38
|
+
observer = new ResizeObserver((entries) => {
|
|
39
|
+
entries.forEach((entry) => {
|
|
40
|
+
const target = entry.target;
|
|
41
|
+
const coArr = weakMap.get(target);
|
|
42
|
+
coArr.forEach((co) => {
|
|
43
|
+
co.callback.call(result, entry, result);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// 添加监听
|
|
49
|
+
$target.each((_, target) => {
|
|
50
|
+
var _a;
|
|
51
|
+
observer.observe(target);
|
|
52
|
+
const coArr = (_a = weakMap.get(target)) !== null && _a !== void 0 ? _a : [];
|
|
53
|
+
coArr.push({ callback, key });
|
|
54
|
+
weakMap.set(target, coArr);
|
|
55
|
+
});
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type Func = () => void;
|
|
2
|
+
/**
|
|
3
|
+
* 根据队列名,获取队列中所有函数
|
|
4
|
+
* @param name 队列名
|
|
5
|
+
*/
|
|
6
|
+
export declare function queue(name: string): Func[];
|
|
7
|
+
/**
|
|
8
|
+
* 写入队列
|
|
9
|
+
* @param name 队列名
|
|
10
|
+
* @param func 函数
|
|
11
|
+
*/
|
|
12
|
+
export declare function queue(name: string, func: Func): void;
|
|
13
|
+
/**
|
|
14
|
+
* 从队列中移除第一个函数,并执行该函数
|
|
15
|
+
* @param name 队列名
|
|
16
|
+
*/
|
|
17
|
+
export declare function dequeue(name: string): void;
|
|
18
|
+
export {};
|
package/helpers/queue.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { isUndefined } from '@mdui/jq/shared/helper.js';
|
|
2
|
+
const container = {};
|
|
3
|
+
export function queue(name, func) {
|
|
4
|
+
if (isUndefined(container[name])) {
|
|
5
|
+
container[name] = [];
|
|
6
|
+
}
|
|
7
|
+
if (isUndefined(func)) {
|
|
8
|
+
return container[name];
|
|
9
|
+
}
|
|
10
|
+
container[name].push(func);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 从队列中移除第一个函数,并执行该函数
|
|
14
|
+
* @param name 队列名
|
|
15
|
+
*/
|
|
16
|
+
export function dequeue(name) {
|
|
17
|
+
if (isUndefined(container[name])) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!container[name].length) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const func = container[name].shift();
|
|
24
|
+
func();
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import '@mdui/jq/methods/addClass.js';
|
|
2
|
+
import '@mdui/jq/methods/removeClass.js';
|
|
3
|
+
/**
|
|
4
|
+
* 锁定指定元素,禁止滚动。对同一个元素多次调用此方法,只会锁定一次
|
|
5
|
+
* @param element
|
|
6
|
+
* @param target 锁定该元素的滚动状态,默认为 body
|
|
7
|
+
*/
|
|
8
|
+
export declare const lockScreen: (element: HTMLElement, target?: HTMLElement) => void;
|
|
9
|
+
/**
|
|
10
|
+
* 解除指定元素的滚动状态锁定。
|
|
11
|
+
* @param element
|
|
12
|
+
* @param target 锁定该元素的滚动状态,默认为 body
|
|
13
|
+
*/
|
|
14
|
+
export declare const unlockScreen: (element: HTMLElement, target?: HTMLElement) => void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getDocument } from 'ssr-window';
|
|
2
|
+
import { $ } from '@mdui/jq/$.js';
|
|
3
|
+
import '@mdui/jq/methods/addClass.js';
|
|
4
|
+
import '@mdui/jq/methods/removeClass.js';
|
|
5
|
+
const locks = new Set();
|
|
6
|
+
const CSS_CLASS = 'mdui-lock-screen';
|
|
7
|
+
/**
|
|
8
|
+
* 锁定指定元素,禁止滚动。对同一个元素多次调用此方法,只会锁定一次
|
|
9
|
+
* @param element
|
|
10
|
+
* @param target 锁定该元素的滚动状态,默认为 body
|
|
11
|
+
*/
|
|
12
|
+
export const lockScreen = (element, target) => {
|
|
13
|
+
const document = getDocument();
|
|
14
|
+
locks.add(element);
|
|
15
|
+
$(target || document.body).addClass(CSS_CLASS);
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* 解除指定元素的滚动状态锁定。
|
|
19
|
+
* @param element
|
|
20
|
+
* @param target 锁定该元素的滚动状态,默认为 body
|
|
21
|
+
*/
|
|
22
|
+
export const unlockScreen = (element, target) => {
|
|
23
|
+
const document = getDocument();
|
|
24
|
+
locks.delete(element);
|
|
25
|
+
if (locks.size === 0) {
|
|
26
|
+
$(target || document.body).removeClass(CSS_CLASS);
|
|
27
|
+
}
|
|
28
|
+
};
|
package/helpers/slot.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取一个 slot 中的所有内容
|
|
3
|
+
* @param slot
|
|
4
|
+
*/
|
|
5
|
+
export const getInnerHtmlFromSlot = (slot) => {
|
|
6
|
+
const nodes = slot.assignedNodes({ flatten: true });
|
|
7
|
+
let html = '';
|
|
8
|
+
[...nodes].forEach((node) => {
|
|
9
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
10
|
+
html += node.outerHTML;
|
|
11
|
+
}
|
|
12
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
13
|
+
html += node.textContent;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return html;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* 获取一个 slot 中的所有文本内容
|
|
20
|
+
* @param slot
|
|
21
|
+
*/
|
|
22
|
+
export const getTextContentFromSlot = (slot) => {
|
|
23
|
+
const nodes = slot.assignedNodes({ flatten: true });
|
|
24
|
+
let text = '';
|
|
25
|
+
[...nodes].forEach((node) => {
|
|
26
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
27
|
+
text += node.textContent;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return text;
|
|
31
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the first and last bounding elements that are tabbable. This is more performant than checking every single
|
|
3
|
+
* element because it short-circuits after finding the first and last ones.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getTabbableBoundary(root: HTMLElement | ShadowRoot): {
|
|
6
|
+
start: HTMLElement | null;
|
|
7
|
+
end: HTMLElement | null;
|
|
8
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { getWindow } from 'ssr-window';
|
|
2
|
+
/** https://github.com/shoelace-style/shoelace/blob/next/src/internal/tabbable.ts */
|
|
3
|
+
/** Determines if the specified element is tabbable using heuristics inspired by https://github.com/focus-trap/tabbable */
|
|
4
|
+
function isTabbable(el) {
|
|
5
|
+
const window = getWindow();
|
|
6
|
+
const tag = el.tagName.toLowerCase();
|
|
7
|
+
// Elements with a -1 tab index are not tabbable
|
|
8
|
+
if (el.getAttribute('tabindex') === '-1') {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
// Elements with a disabled attribute are not tabbable
|
|
12
|
+
if (el.hasAttribute('disabled')) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
// Elements with aria-disabled are not tabbable
|
|
16
|
+
if (el.hasAttribute('aria-disabled') &&
|
|
17
|
+
el.getAttribute('aria-disabled') !== 'false') {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
// Radios without a checked attribute are not tabbable
|
|
21
|
+
if (tag === 'input' &&
|
|
22
|
+
el.getAttribute('type') === 'radio' &&
|
|
23
|
+
!el.hasAttribute('checked')) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
// Elements that are hidden have no offsetParent and are not tabbable
|
|
27
|
+
if (el.offsetParent === null) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
// Elements without visibility are not tabbable
|
|
31
|
+
if (window.getComputedStyle(el).visibility === 'hidden') {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
// Audio and video elements with the controls attribute are tabbable
|
|
35
|
+
if ((tag === 'audio' || tag === 'video') && el.hasAttribute('controls')) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
// Elements with a tabindex other than -1 are tabbable
|
|
39
|
+
if (el.hasAttribute('tabindex')) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
// Elements with a contenteditable attribute are tabbable
|
|
43
|
+
if (el.hasAttribute('contenteditable') &&
|
|
44
|
+
el.getAttribute('contenteditable') !== 'false') {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
// At this point, the following elements are considered tabbable
|
|
48
|
+
return [
|
|
49
|
+
'button',
|
|
50
|
+
'input',
|
|
51
|
+
'select',
|
|
52
|
+
'textarea',
|
|
53
|
+
'a',
|
|
54
|
+
'audio',
|
|
55
|
+
'video',
|
|
56
|
+
'summary',
|
|
57
|
+
].includes(tag);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Returns the first and last bounding elements that are tabbable. This is more performant than checking every single
|
|
61
|
+
* element because it short-circuits after finding the first and last ones.
|
|
62
|
+
*/
|
|
63
|
+
export function getTabbableBoundary(root) {
|
|
64
|
+
var _a, _b;
|
|
65
|
+
const allElements = [];
|
|
66
|
+
function walk(el) {
|
|
67
|
+
if (el instanceof HTMLElement) {
|
|
68
|
+
allElements.push(el);
|
|
69
|
+
if (el.shadowRoot !== null && el.shadowRoot.mode === 'open') {
|
|
70
|
+
walk(el.shadowRoot);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const children = el.children;
|
|
74
|
+
[...children].forEach((e) => walk(e));
|
|
75
|
+
}
|
|
76
|
+
// Collect all elements including the root
|
|
77
|
+
walk(root);
|
|
78
|
+
// Find the first and last tabbable elements
|
|
79
|
+
const start = (_a = allElements.find((el) => isTabbable(el))) !== null && _a !== void 0 ? _a : null;
|
|
80
|
+
const end = (_b = allElements.reverse().find((el) => isTabbable(el))) !== null && _b !== void 0 ? _b : null;
|
|
81
|
+
return { start, end };
|
|
82
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type ThrottledFunc<T extends (...args: any) => any> = (...args: Parameters<T>) => ReturnType<T>;
|
|
2
|
+
/**
|
|
3
|
+
* 创建一个节流函数
|
|
4
|
+
* @param func 要执行的函数
|
|
5
|
+
* @param wait 最多多少毫秒执行一次
|
|
6
|
+
* @example
|
|
7
|
+
```js
|
|
8
|
+
window.addEventListener('scroll', throttle(() => {
|
|
9
|
+
console.log('这个函数最多 100ms 执行一次');
|
|
10
|
+
}, 100));
|
|
11
|
+
```
|
|
12
|
+
*/
|
|
13
|
+
export declare const throttle: <T extends (...args: any) => any>(func: T, wait?: number) => ThrottledFunc<T>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getWindow } from 'ssr-window';
|
|
2
|
+
/**
|
|
3
|
+
* 创建一个节流函数
|
|
4
|
+
* @param func 要执行的函数
|
|
5
|
+
* @param wait 最多多少毫秒执行一次
|
|
6
|
+
* @example
|
|
7
|
+
```js
|
|
8
|
+
window.addEventListener('scroll', throttle(() => {
|
|
9
|
+
console.log('这个函数最多 100ms 执行一次');
|
|
10
|
+
}, 100));
|
|
11
|
+
```
|
|
12
|
+
*/
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
export const throttle = (func, wait = 0) => {
|
|
15
|
+
const window = getWindow();
|
|
16
|
+
let timer;
|
|
17
|
+
let result;
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
+
return function (...args) {
|
|
20
|
+
if (timer === undefined) {
|
|
21
|
+
timer = window.setTimeout(() => {
|
|
22
|
+
result = func.apply(this, args);
|
|
23
|
+
timer = undefined;
|
|
24
|
+
}, wait);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import type { TemplateResult, CSSResultGroup } from 'lit';
|
|
3
|
+
export declare class IconArrowRight extends LitElement {
|
|
4
|
+
static styles: CSSResultGroup;
|
|
5
|
+
protected render(): TemplateResult;
|
|
6
|
+
}
|
|
7
|
+
declare global {
|
|
8
|
+
interface HTMLElementTagNameMap {
|
|
9
|
+
'mdui-icon-arrow-right': IconArrowRight;
|
|
10
|
+
}
|
|
11
|
+
}
|