@composy/keyboard-core 0.0.1
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/README.md +37 -0
- package/dist/index.js +3605 -0
- package/dist/index.js.map +1 -0
- package/dist/index.min.js +1 -0
- package/es/icons.d.ts +58 -0
- package/es/icons.d.ts.map +1 -0
- package/es/icons.js +34 -0
- package/es/icons.js.map +1 -0
- package/es/index.d.ts +12 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +36 -0
- package/es/layouts/amount.d.ts +14 -0
- package/es/layouts/amount.d.ts.map +1 -0
- package/es/layouts/amount.js +175 -0
- package/es/layouts/amount.js.map +1 -0
- package/es/layouts/bankcard.d.ts +15 -0
- package/es/layouts/bankcard.d.ts.map +1 -0
- package/es/layouts/bankcard.js +99 -0
- package/es/layouts/bankcard.js.map +1 -0
- package/es/layouts/email.d.ts +11 -0
- package/es/layouts/email.d.ts.map +1 -0
- package/es/layouts/email.js +126 -0
- package/es/layouts/email.js.map +1 -0
- package/es/layouts/idcard.d.ts +16 -0
- package/es/layouts/idcard.d.ts.map +1 -0
- package/es/layouts/idcard.js +99 -0
- package/es/layouts/idcard.js.map +1 -0
- package/es/layouts/index.d.ts +54 -0
- package/es/layouts/index.d.ts.map +1 -0
- package/es/layouts/index.js +78 -0
- package/es/layouts/index.js.map +1 -0
- package/es/layouts/license-plate.d.ts +35 -0
- package/es/layouts/license-plate.d.ts.map +1 -0
- package/es/layouts/license-plate.js +290 -0
- package/es/layouts/license-plate.js.map +1 -0
- package/es/layouts/number.d.ts +14 -0
- package/es/layouts/number.d.ts.map +1 -0
- package/es/layouts/number.js +168 -0
- package/es/layouts/number.js.map +1 -0
- package/es/layouts/phone.d.ts +15 -0
- package/es/layouts/phone.d.ts.map +1 -0
- package/es/layouts/phone.js +175 -0
- package/es/layouts/phone.js.map +1 -0
- package/es/layouts/punct.d.ts +7 -0
- package/es/layouts/punct.d.ts.map +1 -0
- package/es/layouts/punct.js +112 -0
- package/es/layouts/punct.js.map +1 -0
- package/es/layouts/qwerty.d.ts +10 -0
- package/es/layouts/qwerty.d.ts.map +1 -0
- package/es/layouts/qwerty.js +116 -0
- package/es/layouts/qwerty.js.map +1 -0
- package/es/layouts/search.d.ts +7 -0
- package/es/layouts/search.d.ts.map +1 -0
- package/es/layouts/search.js +101 -0
- package/es/layouts/search.js.map +1 -0
- package/es/layouts/symbol.d.ts +14 -0
- package/es/layouts/symbol.d.ts.map +1 -0
- package/es/layouts/symbol.js +194 -0
- package/es/layouts/symbol.js.map +1 -0
- package/es/layouts/url.d.ts +7 -0
- package/es/layouts/url.d.ts.map +1 -0
- package/es/layouts/url.js +99 -0
- package/es/layouts/url.js.map +1 -0
- package/es/layouts/verifycode.d.ts +15 -0
- package/es/layouts/verifycode.d.ts.map +1 -0
- package/es/layouts/verifycode.js +100 -0
- package/es/layouts/verifycode.js.map +1 -0
- package/es/managers/index.d.ts +8 -0
- package/es/managers/index.d.ts.map +1 -0
- package/es/managers/index.js +14 -0
- package/es/managers/keyboard-manager.d.ts +130 -0
- package/es/managers/keyboard-manager.d.ts.map +1 -0
- package/es/managers/keyboard-manager.js +374 -0
- package/es/managers/keyboard-manager.js.map +1 -0
- package/es/managers/keyboard-renderer.d.ts +115 -0
- package/es/managers/keyboard-renderer.d.ts.map +1 -0
- package/es/managers/keyboard-renderer.js +283 -0
- package/es/managers/keyboard-renderer.js.map +1 -0
- package/es/managers/position-manager.d.ts +72 -0
- package/es/managers/position-manager.d.ts.map +1 -0
- package/es/managers/position-manager.js +199 -0
- package/es/managers/position-manager.js.map +1 -0
- package/es/themes/index.d.ts +70 -0
- package/es/themes/index.d.ts.map +1 -0
- package/es/themes/index.js +99 -0
- package/es/themes/index.js.map +1 -0
- package/es/themes/presets.d.ts +39 -0
- package/es/themes/presets.d.ts.map +1 -0
- package/es/themes/presets.js +207 -0
- package/es/themes/presets.js.map +1 -0
- package/es/themes/types.d.ts +118 -0
- package/es/themes/types.d.ts.map +1 -0
- package/es/themes/types.js +10 -0
- package/es/types/index.d.ts +7 -0
- package/es/types/index.d.ts.map +1 -0
- package/es/types/index.js +10 -0
- package/es/types/keyboard.d.ts +253 -0
- package/es/types/keyboard.d.ts.map +1 -0
- package/es/types/keyboard.js +10 -0
- package/es/types/position.d.ts +139 -0
- package/es/types/position.d.ts.map +1 -0
- package/es/types/position.js +10 -0
- package/es/utils/device.d.ts +45 -0
- package/es/utils/device.d.ts.map +1 -0
- package/es/utils/device.js +111 -0
- package/es/utils/device.js.map +1 -0
- package/es/utils/dom.d.ts +69 -0
- package/es/utils/dom.d.ts.map +1 -0
- package/es/utils/dom.js +134 -0
- package/es/utils/dom.js.map +1 -0
- package/es/utils/event-emitter.d.ts +52 -0
- package/es/utils/event-emitter.d.ts.map +1 -0
- package/es/utils/event-emitter.js +99 -0
- package/es/utils/event-emitter.js.map +1 -0
- package/es/utils/formatters.d.ts +51 -0
- package/es/utils/formatters.d.ts.map +1 -0
- package/es/utils/formatters.js +57 -0
- package/es/utils/formatters.js.map +1 -0
- package/es/utils/index.d.ts +10 -0
- package/es/utils/index.d.ts.map +1 -0
- package/es/utils/index.js +16 -0
- package/es/utils/random.d.ts +71 -0
- package/es/utils/random.d.ts.map +1 -0
- package/es/utils/random.js +292 -0
- package/es/utils/random.js.map +1 -0
- package/esm/icons.d.ts +58 -0
- package/esm/icons.d.ts.map +1 -0
- package/esm/icons.js +34 -0
- package/esm/icons.js.map +1 -0
- package/esm/index.d.ts +12 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +37 -0
- package/esm/layouts/amount.d.ts +14 -0
- package/esm/layouts/amount.d.ts.map +1 -0
- package/esm/layouts/amount.js +175 -0
- package/esm/layouts/amount.js.map +1 -0
- package/esm/layouts/bankcard.d.ts +15 -0
- package/esm/layouts/bankcard.d.ts.map +1 -0
- package/esm/layouts/bankcard.js +99 -0
- package/esm/layouts/bankcard.js.map +1 -0
- package/esm/layouts/email.d.ts +11 -0
- package/esm/layouts/email.d.ts.map +1 -0
- package/esm/layouts/email.js +126 -0
- package/esm/layouts/email.js.map +1 -0
- package/esm/layouts/idcard.d.ts +16 -0
- package/esm/layouts/idcard.d.ts.map +1 -0
- package/esm/layouts/idcard.js +99 -0
- package/esm/layouts/idcard.js.map +1 -0
- package/esm/layouts/index.d.ts +54 -0
- package/esm/layouts/index.d.ts.map +1 -0
- package/esm/layouts/index.js +78 -0
- package/esm/layouts/index.js.map +1 -0
- package/esm/layouts/license-plate.d.ts +35 -0
- package/esm/layouts/license-plate.d.ts.map +1 -0
- package/esm/layouts/license-plate.js +290 -0
- package/esm/layouts/license-plate.js.map +1 -0
- package/esm/layouts/number.d.ts +14 -0
- package/esm/layouts/number.d.ts.map +1 -0
- package/esm/layouts/number.js +168 -0
- package/esm/layouts/number.js.map +1 -0
- package/esm/layouts/phone.d.ts +15 -0
- package/esm/layouts/phone.d.ts.map +1 -0
- package/esm/layouts/phone.js +175 -0
- package/esm/layouts/phone.js.map +1 -0
- package/esm/layouts/punct.d.ts +7 -0
- package/esm/layouts/punct.d.ts.map +1 -0
- package/esm/layouts/punct.js +112 -0
- package/esm/layouts/punct.js.map +1 -0
- package/esm/layouts/qwerty.d.ts +10 -0
- package/esm/layouts/qwerty.d.ts.map +1 -0
- package/esm/layouts/qwerty.js +116 -0
- package/esm/layouts/qwerty.js.map +1 -0
- package/esm/layouts/search.d.ts +7 -0
- package/esm/layouts/search.d.ts.map +1 -0
- package/esm/layouts/search.js +101 -0
- package/esm/layouts/search.js.map +1 -0
- package/esm/layouts/symbol.d.ts +14 -0
- package/esm/layouts/symbol.d.ts.map +1 -0
- package/esm/layouts/symbol.js +194 -0
- package/esm/layouts/symbol.js.map +1 -0
- package/esm/layouts/url.d.ts +7 -0
- package/esm/layouts/url.d.ts.map +1 -0
- package/esm/layouts/url.js +99 -0
- package/esm/layouts/url.js.map +1 -0
- package/esm/layouts/verifycode.d.ts +15 -0
- package/esm/layouts/verifycode.d.ts.map +1 -0
- package/esm/layouts/verifycode.js +100 -0
- package/esm/layouts/verifycode.js.map +1 -0
- package/esm/managers/index.d.ts +8 -0
- package/esm/managers/index.d.ts.map +1 -0
- package/esm/managers/index.js +14 -0
- package/esm/managers/keyboard-manager.d.ts +130 -0
- package/esm/managers/keyboard-manager.d.ts.map +1 -0
- package/esm/managers/keyboard-manager.js +374 -0
- package/esm/managers/keyboard-manager.js.map +1 -0
- package/esm/managers/keyboard-renderer.d.ts +115 -0
- package/esm/managers/keyboard-renderer.d.ts.map +1 -0
- package/esm/managers/keyboard-renderer.js +283 -0
- package/esm/managers/keyboard-renderer.js.map +1 -0
- package/esm/managers/position-manager.d.ts +72 -0
- package/esm/managers/position-manager.d.ts.map +1 -0
- package/esm/managers/position-manager.js +199 -0
- package/esm/managers/position-manager.js.map +1 -0
- package/esm/themes/index.d.ts +70 -0
- package/esm/themes/index.d.ts.map +1 -0
- package/esm/themes/index.js +99 -0
- package/esm/themes/index.js.map +1 -0
- package/esm/themes/presets.d.ts +39 -0
- package/esm/themes/presets.d.ts.map +1 -0
- package/esm/themes/presets.js +207 -0
- package/esm/themes/presets.js.map +1 -0
- package/esm/themes/types.d.ts +118 -0
- package/esm/themes/types.d.ts.map +1 -0
- package/esm/themes/types.js +10 -0
- package/esm/types/index.d.ts +7 -0
- package/esm/types/index.d.ts.map +1 -0
- package/esm/types/index.js +10 -0
- package/esm/types/keyboard.d.ts +253 -0
- package/esm/types/keyboard.d.ts.map +1 -0
- package/esm/types/keyboard.js +10 -0
- package/esm/types/position.d.ts +139 -0
- package/esm/types/position.d.ts.map +1 -0
- package/esm/types/position.js +10 -0
- package/esm/utils/device.d.ts +45 -0
- package/esm/utils/device.d.ts.map +1 -0
- package/esm/utils/device.js +111 -0
- package/esm/utils/device.js.map +1 -0
- package/esm/utils/dom.d.ts +69 -0
- package/esm/utils/dom.d.ts.map +1 -0
- package/esm/utils/dom.js +134 -0
- package/esm/utils/dom.js.map +1 -0
- package/esm/utils/event-emitter.d.ts +52 -0
- package/esm/utils/event-emitter.d.ts.map +1 -0
- package/esm/utils/event-emitter.js +99 -0
- package/esm/utils/event-emitter.js.map +1 -0
- package/esm/utils/formatters.d.ts +51 -0
- package/esm/utils/formatters.d.ts.map +1 -0
- package/esm/utils/formatters.js +57 -0
- package/esm/utils/formatters.js.map +1 -0
- package/esm/utils/index.d.ts +10 -0
- package/esm/utils/index.d.ts.map +1 -0
- package/esm/utils/index.js +16 -0
- package/esm/utils/random.d.ts +71 -0
- package/esm/utils/random.d.ts.map +1 -0
- package/esm/utils/random.js +292 -0
- package/esm/utils/random.js.map +1 -0
- package/lib/icons.cjs +44 -0
- package/lib/icons.cjs.map +1 -0
- package/lib/icons.d.ts +58 -0
- package/lib/index.cjs +141 -0
- package/lib/index.d.ts +12 -0
- package/lib/layouts/amount.cjs +177 -0
- package/lib/layouts/amount.cjs.map +1 -0
- package/lib/layouts/amount.d.ts +14 -0
- package/lib/layouts/bankcard.cjs +100 -0
- package/lib/layouts/bankcard.cjs.map +1 -0
- package/lib/layouts/bankcard.d.ts +15 -0
- package/lib/layouts/email.cjs +128 -0
- package/lib/layouts/email.cjs.map +1 -0
- package/lib/layouts/email.d.ts +11 -0
- package/lib/layouts/idcard.cjs +100 -0
- package/lib/layouts/idcard.cjs.map +1 -0
- package/lib/layouts/idcard.d.ts +16 -0
- package/lib/layouts/index.cjs +107 -0
- package/lib/layouts/index.cjs.map +1 -0
- package/lib/layouts/index.d.ts +54 -0
- package/lib/layouts/license-plate.cjs +296 -0
- package/lib/layouts/license-plate.cjs.map +1 -0
- package/lib/layouts/license-plate.d.ts +35 -0
- package/lib/layouts/number.cjs +170 -0
- package/lib/layouts/number.cjs.map +1 -0
- package/lib/layouts/number.d.ts +14 -0
- package/lib/layouts/phone.cjs +177 -0
- package/lib/layouts/phone.cjs.map +1 -0
- package/lib/layouts/phone.d.ts +15 -0
- package/lib/layouts/punct.cjs +113 -0
- package/lib/layouts/punct.cjs.map +1 -0
- package/lib/layouts/punct.d.ts +7 -0
- package/lib/layouts/qwerty.cjs +117 -0
- package/lib/layouts/qwerty.cjs.map +1 -0
- package/lib/layouts/qwerty.d.ts +10 -0
- package/lib/layouts/search.cjs +102 -0
- package/lib/layouts/search.cjs.map +1 -0
- package/lib/layouts/search.d.ts +7 -0
- package/lib/layouts/symbol.cjs +196 -0
- package/lib/layouts/symbol.cjs.map +1 -0
- package/lib/layouts/symbol.d.ts +14 -0
- package/lib/layouts/url.cjs +100 -0
- package/lib/layouts/url.cjs.map +1 -0
- package/lib/layouts/url.d.ts +7 -0
- package/lib/layouts/verifycode.cjs +101 -0
- package/lib/layouts/verifycode.cjs.map +1 -0
- package/lib/layouts/verifycode.d.ts +15 -0
- package/lib/managers/index.cjs +20 -0
- package/lib/managers/index.d.ts +8 -0
- package/lib/managers/keyboard-manager.cjs +376 -0
- package/lib/managers/keyboard-manager.cjs.map +1 -0
- package/lib/managers/keyboard-manager.d.ts +130 -0
- package/lib/managers/keyboard-renderer.cjs +285 -0
- package/lib/managers/keyboard-renderer.cjs.map +1 -0
- package/lib/managers/keyboard-renderer.d.ts +115 -0
- package/lib/managers/position-manager.cjs +201 -0
- package/lib/managers/position-manager.cjs.map +1 -0
- package/lib/managers/position-manager.d.ts +72 -0
- package/lib/package.json +3 -0
- package/lib/themes/index.cjs +117 -0
- package/lib/themes/index.cjs.map +1 -0
- package/lib/themes/index.d.ts +70 -0
- package/lib/themes/presets.cjs +214 -0
- package/lib/themes/presets.cjs.map +1 -0
- package/lib/themes/presets.d.ts +39 -0
- package/lib/themes/types.cjs +11 -0
- package/lib/themes/types.d.ts +118 -0
- package/lib/types/index.cjs +11 -0
- package/lib/types/index.d.ts +7 -0
- package/lib/types/keyboard.cjs +11 -0
- package/lib/types/keyboard.d.ts +253 -0
- package/lib/types/position.cjs +11 -0
- package/lib/types/position.d.ts +139 -0
- package/lib/utils/device.cjs +120 -0
- package/lib/utils/device.cjs.map +1 -0
- package/lib/utils/device.d.ts +45 -0
- package/lib/utils/dom.cjs +148 -0
- package/lib/utils/dom.cjs.map +1 -0
- package/lib/utils/dom.d.ts +69 -0
- package/lib/utils/event-emitter.cjs +100 -0
- package/lib/utils/event-emitter.cjs.map +1 -0
- package/lib/utils/event-emitter.d.ts +52 -0
- package/lib/utils/formatters.cjs +66 -0
- package/lib/utils/formatters.cjs.map +1 -0
- package/lib/utils/formatters.d.ts +51 -0
- package/lib/utils/index.cjs +58 -0
- package/lib/utils/index.d.ts +10 -0
- package/lib/utils/random.cjs +301 -0
- package/lib/utils/random.cjs.map +1 -0
- package/lib/utils/random.d.ts +71 -0
- package/package.json +121 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/keyboard-core v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:33:29 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
11
|
+
const require_device = require("../utils/device.cjs");
|
|
12
|
+
const require_dom = require("../utils/dom.cjs");
|
|
13
|
+
const require_keyboard_manager = require("./keyboard-manager.cjs");
|
|
14
|
+
const require_position_manager = require("./position-manager.cjs");
|
|
15
|
+
const require_presets = require("../themes/presets.cjs");
|
|
16
|
+
const require_index = require("../themes/index.cjs");
|
|
17
|
+
//#region src/managers/keyboard-renderer.ts
|
|
18
|
+
var KeyboardRenderer = class {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
/** DOM 元素 */
|
|
21
|
+
this.rootEl = null;
|
|
22
|
+
this.maskEl = null;
|
|
23
|
+
this.keyboardEl = null;
|
|
24
|
+
/** 当前目标元素 */
|
|
25
|
+
this.targetEl = null;
|
|
26
|
+
/** 清理函数 */
|
|
27
|
+
this.cleanupFns = [];
|
|
28
|
+
/** 是否已销毁 */
|
|
29
|
+
this.destroyed = false;
|
|
30
|
+
this.options = options;
|
|
31
|
+
this.container = options.container || document.body;
|
|
32
|
+
this.manager = new require_keyboard_manager.KeyboardManager(options);
|
|
33
|
+
this.positionManager = new require_position_manager.PositionManager(options.position);
|
|
34
|
+
if (typeof options.theme === "string") this.theme = require_index.getTheme(options.theme);
|
|
35
|
+
else if (options.theme) this.theme = options.theme;
|
|
36
|
+
else this.theme = require_presets.defaultTheme;
|
|
37
|
+
this.setupEvents();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 设置事件监听
|
|
41
|
+
*/
|
|
42
|
+
setupEvents() {
|
|
43
|
+
this.manager.on("stateChange", (state) => {
|
|
44
|
+
if (state.visible) this.render();
|
|
45
|
+
else this.unmount();
|
|
46
|
+
});
|
|
47
|
+
this.manager.on("input", ({ value }) => {
|
|
48
|
+
this.options.onInput?.(value);
|
|
49
|
+
});
|
|
50
|
+
this.manager.on("confirm", ({ value }) => {
|
|
51
|
+
this.options.onConfirm?.(value);
|
|
52
|
+
});
|
|
53
|
+
this.manager.on("switch", () => {
|
|
54
|
+
if (this.manager.getState().visible) this.render();
|
|
55
|
+
});
|
|
56
|
+
this.manager.on("shift", () => {
|
|
57
|
+
if (this.manager.getState().visible) this.render();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 打开键盘
|
|
62
|
+
*/
|
|
63
|
+
open(target) {
|
|
64
|
+
this.targetEl = target ?? null;
|
|
65
|
+
this.manager.show(target);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 关闭键盘
|
|
69
|
+
*/
|
|
70
|
+
close() {
|
|
71
|
+
this.manager.hide();
|
|
72
|
+
this.options.onClose?.();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 切换键盘类型
|
|
76
|
+
*/
|
|
77
|
+
switchType(type) {
|
|
78
|
+
this.manager.switchType(type);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 设置主题
|
|
82
|
+
*/
|
|
83
|
+
setTheme(theme) {
|
|
84
|
+
if (typeof theme === "string") this.theme = require_index.getTheme(theme);
|
|
85
|
+
else this.theme = theme;
|
|
86
|
+
if (this.manager.getState().visible) this.render();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 获取当前值
|
|
90
|
+
*/
|
|
91
|
+
getValue() {
|
|
92
|
+
return this.manager.getState().value;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 设置值
|
|
96
|
+
*/
|
|
97
|
+
setValue(value) {
|
|
98
|
+
this.manager.setValue(value);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 渲染键盘 DOM
|
|
102
|
+
*/
|
|
103
|
+
render() {
|
|
104
|
+
const layout = this.manager.getLayout();
|
|
105
|
+
if (!layout) return;
|
|
106
|
+
this.unmount();
|
|
107
|
+
this.rootEl = document.createElement("div");
|
|
108
|
+
this.rootEl.className = "lk-renderer-root";
|
|
109
|
+
const isMobileDevice = require_device.isMobile();
|
|
110
|
+
if (isMobileDevice) {
|
|
111
|
+
this.maskEl = document.createElement("div");
|
|
112
|
+
this.maskEl.className = "lk-mask";
|
|
113
|
+
this.maskEl.addEventListener("click", () => this.close());
|
|
114
|
+
this.rootEl.appendChild(this.maskEl);
|
|
115
|
+
}
|
|
116
|
+
this.keyboardEl = document.createElement("div");
|
|
117
|
+
this.keyboardEl.className = `lk-popup lk-popup--${isMobileDevice ? "mobile" : "desktop"}`;
|
|
118
|
+
const cssVars = require_index.themeToCSS(this.theme);
|
|
119
|
+
Object.entries(cssVars).forEach(([key, value]) => {
|
|
120
|
+
if (value) this.keyboardEl.style.setProperty(key, value);
|
|
121
|
+
});
|
|
122
|
+
const contentEl = this.renderKeyboard(layout);
|
|
123
|
+
this.keyboardEl.appendChild(contentEl);
|
|
124
|
+
this.rootEl.appendChild(this.keyboardEl);
|
|
125
|
+
this.container.appendChild(this.rootEl);
|
|
126
|
+
if (!isMobileDevice && this.targetEl) {
|
|
127
|
+
const rect = this.keyboardEl.getBoundingClientRect();
|
|
128
|
+
const result = this.positionManager.calculate(this.targetEl, {
|
|
129
|
+
width: rect.width,
|
|
130
|
+
height: rect.height
|
|
131
|
+
});
|
|
132
|
+
this.keyboardEl.style.position = "fixed";
|
|
133
|
+
this.keyboardEl.style.left = `${result.position.x}px`;
|
|
134
|
+
this.keyboardEl.style.top = `${result.position.y}px`;
|
|
135
|
+
this.keyboardEl.style.zIndex = "10000";
|
|
136
|
+
} else if (isMobileDevice) {
|
|
137
|
+
this.keyboardEl.style.position = "fixed";
|
|
138
|
+
this.keyboardEl.style.left = "0";
|
|
139
|
+
this.keyboardEl.style.right = "0";
|
|
140
|
+
this.keyboardEl.style.bottom = "0";
|
|
141
|
+
this.keyboardEl.style.zIndex = "10000";
|
|
142
|
+
}
|
|
143
|
+
requestAnimationFrame(() => {
|
|
144
|
+
this.keyboardEl?.classList.add("lk-popup--visible");
|
|
145
|
+
this.maskEl?.classList.add("lk-mask--visible");
|
|
146
|
+
});
|
|
147
|
+
if (this.options.closeOnClickOutside !== false && this.keyboardEl) {
|
|
148
|
+
const cleanup = require_dom.onClickOutside(this.keyboardEl, (event) => {
|
|
149
|
+
if (this.targetEl && this.targetEl.contains(event.target)) return;
|
|
150
|
+
this.close();
|
|
151
|
+
});
|
|
152
|
+
this.cleanupFns.push(cleanup);
|
|
153
|
+
}
|
|
154
|
+
this.injectStyles();
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 渲染键盘内容
|
|
158
|
+
*/
|
|
159
|
+
renderKeyboard(layout) {
|
|
160
|
+
const el = document.createElement("div");
|
|
161
|
+
el.className = "lk-keyboard";
|
|
162
|
+
const state = this.manager.getState();
|
|
163
|
+
layout.rows.forEach((row) => {
|
|
164
|
+
const rowEl = document.createElement("div");
|
|
165
|
+
rowEl.className = `lk-row ${row.className || ""}`;
|
|
166
|
+
const isNumpad = row.className === "numpad-row";
|
|
167
|
+
row.keys.forEach((key) => {
|
|
168
|
+
const wrapperEl = document.createElement("div");
|
|
169
|
+
wrapperEl.className = "lk-key-wrapper";
|
|
170
|
+
if (isNumpad) wrapperEl.style.flex = String(key.width ?? 1);
|
|
171
|
+
else if (key.width) {
|
|
172
|
+
const baseW = 36;
|
|
173
|
+
const gap = this.theme.sizes?.keyGap ?? 6;
|
|
174
|
+
const w = baseW * key.width + (key.width - 1) * gap;
|
|
175
|
+
wrapperEl.style.width = `${w}px`;
|
|
176
|
+
}
|
|
177
|
+
const btnEl = document.createElement("button");
|
|
178
|
+
btnEl.type = "button";
|
|
179
|
+
btnEl.className = this.getKeyClass(key, state.isUpperCase);
|
|
180
|
+
if (key.disabled) btnEl.disabled = true;
|
|
181
|
+
if (key.icon) btnEl.innerHTML = key.icon;
|
|
182
|
+
else btnEl.textContent = typeof key.label === "string" ? key.label : state.isUpperCase ? key.label.upper : key.label.lower;
|
|
183
|
+
const handler = (e) => {
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
e.stopPropagation();
|
|
186
|
+
if (!key.disabled) this.manager.handleKey(key);
|
|
187
|
+
};
|
|
188
|
+
btnEl.addEventListener("mousedown", handler);
|
|
189
|
+
btnEl.addEventListener("touchstart", handler, { passive: false });
|
|
190
|
+
wrapperEl.appendChild(btnEl);
|
|
191
|
+
rowEl.appendChild(wrapperEl);
|
|
192
|
+
});
|
|
193
|
+
el.appendChild(rowEl);
|
|
194
|
+
});
|
|
195
|
+
return el;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* 获取按键 CSS 类名
|
|
199
|
+
*/
|
|
200
|
+
getKeyClass(key, isUpperCase) {
|
|
201
|
+
const classes = ["lk-key", `lk-key--${key.type}`];
|
|
202
|
+
if (key.className) classes.push(key.className);
|
|
203
|
+
if (key.disabled) classes.push("lk-key--disabled");
|
|
204
|
+
if (key.type === "shift" && isUpperCase) classes.push("lk-key--active");
|
|
205
|
+
return classes.join(" ");
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 卸载 DOM
|
|
209
|
+
*/
|
|
210
|
+
unmount() {
|
|
211
|
+
this.cleanupFns.forEach((fn) => fn());
|
|
212
|
+
this.cleanupFns = [];
|
|
213
|
+
if (this.rootEl && this.rootEl.parentNode) this.rootEl.parentNode.removeChild(this.rootEl);
|
|
214
|
+
this.rootEl = null;
|
|
215
|
+
this.maskEl = null;
|
|
216
|
+
this.keyboardEl = null;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* 注入样式(单例)
|
|
220
|
+
*/
|
|
221
|
+
injectStyles() {
|
|
222
|
+
if (document.getElementById("lk-renderer-styles")) return;
|
|
223
|
+
const style = document.createElement("style");
|
|
224
|
+
style.id = "lk-renderer-styles";
|
|
225
|
+
style.textContent = `
|
|
226
|
+
.lk-renderer-root { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
|
227
|
+
.lk-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 9999; opacity: 0; transition: opacity 0.3s ease; }
|
|
228
|
+
.lk-mask--visible { opacity: 1; }
|
|
229
|
+
.lk-popup { background: var(--keyboard-bg, #d1d5db); box-shadow: 0 -2px 16px rgba(0,0,0,0.15); overflow: hidden; transform: translateY(20px); opacity: 0; transition: transform 0.3s cubic-bezier(0.4,0,0.2,1), opacity 0.2s ease; }
|
|
230
|
+
.lk-popup--visible { transform: translateY(0); opacity: 1; }
|
|
231
|
+
.lk-popup--desktop { border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.1); min-width: 320px; }
|
|
232
|
+
.lk-popup--mobile { border-top-left-radius: 16px; border-top-right-radius: 16px; padding-bottom: env(safe-area-inset-bottom, 0); }
|
|
233
|
+
.lk-keyboard { display: flex; flex-direction: column; gap: var(--keyboard-key-gap, 6px); padding: var(--keyboard-padding, 6px); user-select: none; -webkit-user-select: none; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; }
|
|
234
|
+
.lk-row { display: flex; justify-content: center; gap: var(--keyboard-key-gap, 6px); padding: 0 2px; }
|
|
235
|
+
.lk-row.numpad-row { justify-content: stretch; }
|
|
236
|
+
.lk-row.numpad-row .lk-key-wrapper { flex: 1; }
|
|
237
|
+
.lk-key-wrapper { position: relative; display: inline-flex; }
|
|
238
|
+
.lk-key { display: inline-flex; align-items: center; justify-content: center; height: var(--keyboard-key-height, 42px); min-width: 32px; width: 100%; padding: 0 6px; border: none; border-radius: var(--keyboard-key-radius, 6px); background: var(--keyboard-key-bg, #fff); color: var(--keyboard-key-color, #333); font-size: var(--keyboard-font-size, 16px); font-weight: 500; cursor: pointer; touch-action: manipulation; box-shadow: var(--keyboard-key-shadow, 0 1px 3px rgba(0,0,0,0.1)); transition: background-color 0.1s, transform 0.1s; }
|
|
239
|
+
.lk-key:active { background: var(--keyboard-key-active-bg, #e0e0e0); transform: scale(0.95); }
|
|
240
|
+
.lk-key--disabled { opacity: 0.4; cursor: not-allowed; }
|
|
241
|
+
.lk-key--disabled:active { transform: none; background: var(--keyboard-key-bg, #fff); }
|
|
242
|
+
.lk-key--backspace, .lk-key--shift, .lk-key--tab, .lk-key--clear, .lk-key--close { background: var(--keyboard-fn-key-bg, #d4d6dc); color: var(--keyboard-fn-key-color, #333); font-size: var(--keyboard-fn-font-size, 14px); }
|
|
243
|
+
.lk-key--backspace:active, .lk-key--shift:active, .lk-key--tab:active, .lk-key--clear:active, .lk-key--close:active { background: var(--keyboard-fn-key-active-bg, #b8bac0); }
|
|
244
|
+
.lk-key--active { background: var(--keyboard-key-active-bg, #007aff) !important; color: #fff !important; }
|
|
245
|
+
.lk-key--done, .lk-key--enter { background: var(--keyboard-primary-bg, #007aff); color: var(--keyboard-primary-color, #fff); font-size: var(--keyboard-fn-font-size, 14px); }
|
|
246
|
+
.lk-key--done:active, .lk-key--enter:active { background: var(--keyboard-primary-active-bg, #0062cc); }
|
|
247
|
+
.lk-key--space { background: var(--keyboard-key-bg, #fff); }
|
|
248
|
+
.lk-key svg { pointer-events: none; }
|
|
249
|
+
`;
|
|
250
|
+
document.head.appendChild(style);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* 绑定到输入框(聚焦时自动打开键盘)
|
|
254
|
+
*/
|
|
255
|
+
bindInput(input) {
|
|
256
|
+
const handleFocus = () => this.open(input);
|
|
257
|
+
input.addEventListener("focus", handleFocus);
|
|
258
|
+
input.setAttribute("readonly", "readonly");
|
|
259
|
+
input.style.caretColor = "transparent";
|
|
260
|
+
return () => {
|
|
261
|
+
input.removeEventListener("focus", handleFocus);
|
|
262
|
+
input.removeAttribute("readonly");
|
|
263
|
+
input.style.caretColor = "";
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* 销毁渲染器
|
|
268
|
+
*/
|
|
269
|
+
destroy() {
|
|
270
|
+
if (this.destroyed) return;
|
|
271
|
+
this.destroyed = true;
|
|
272
|
+
this.unmount();
|
|
273
|
+
this.manager.destroy();
|
|
274
|
+
const styleEl = document.getElementById("lk-renderer-styles");
|
|
275
|
+
if (styleEl) styleEl.remove();
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
function createKeyboardRenderer(options) {
|
|
279
|
+
return new KeyboardRenderer(options);
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
282
|
+
exports.KeyboardRenderer = KeyboardRenderer;
|
|
283
|
+
exports.createKeyboardRenderer = createKeyboardRenderer;
|
|
284
|
+
/*! End of @composy/keyboard-core | Powered by @composy/builder */
|
|
285
|
+
//# sourceMappingURL=keyboard-renderer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard-renderer.cjs","names":[],"sources":["../../src/managers/keyboard-renderer.ts"],"sourcesContent":["/**\n * 键盘 DOM 渲染器\n * 框架无关的键盘渲染器,用于原生 JS 项目\n * @module managers/keyboard-renderer\n */\n\nimport type {\n KeyboardConfig,\n KeyboardOptions,\n KeyboardType,\n KeyDefinition,\n PopupPositionConfig,\n} from '../types'\nimport type { ThemeConfig } from '../themes/types'\nimport { KeyboardManager } from './keyboard-manager'\nimport { PositionManager } from './position-manager'\nimport { getTheme, themeToCSS, defaultTheme } from '../themes'\nimport { isMobile } from '../utils/device'\nimport { onClickOutside } from '../utils/dom'\n\n/**\n * 渲染器配置\n */\nexport interface KeyboardRendererOptions extends KeyboardOptions {\n /** 挂载容器(默认 document.body) */\n container?: HTMLElement\n /** 主题名称或主题配置 */\n theme?: string | ThemeConfig\n /** 弹出层配置 */\n position?: PopupPositionConfig\n /** 点击外部关闭 */\n closeOnClickOutside?: boolean\n /** 确认回调 */\n onConfirm?: (value: string) => void\n /** 输入变化回调 */\n onInput?: (value: string) => void\n /** 关闭回调 */\n onClose?: () => void\n}\n\n/**\n * 键盘 DOM 渲染器\n * 提供完整的键盘 DOM 创建、事件绑定、位置管理\n */\nexport class KeyboardRenderer {\n /** 键盘管理器 */\n readonly manager: KeyboardManager\n\n /** 位置管理器 */\n private positionManager: PositionManager\n\n /** 配置 */\n private options: KeyboardRendererOptions\n\n /** DOM 元素 */\n private rootEl: HTMLElement | null = null\n private maskEl: HTMLElement | null = null\n private keyboardEl: HTMLElement | null = null\n\n /** 挂载容器 */\n private container: HTMLElement\n\n /** 主题配置 */\n private theme: ThemeConfig\n\n /** 当前目标元素 */\n private targetEl: HTMLInputElement | HTMLTextAreaElement | null = null\n\n /** 清理函数 */\n private cleanupFns: Array<() => void> = []\n\n /** 是否已销毁 */\n private destroyed = false\n\n constructor(options: KeyboardRendererOptions = {}) {\n this.options = options\n this.container = options.container || document.body\n this.manager = new KeyboardManager(options)\n this.positionManager = new PositionManager(options.position)\n\n // 解析主题\n if (typeof options.theme === 'string') {\n this.theme = getTheme(options.theme)\n } else if (options.theme) {\n this.theme = options.theme\n } else {\n this.theme = defaultTheme\n }\n\n // 绑定事件\n this.setupEvents()\n }\n\n /**\n * 设置事件监听\n */\n private setupEvents(): void {\n this.manager.on('stateChange', (state) => {\n if (state.visible) {\n this.render()\n } else {\n this.unmount()\n }\n })\n\n this.manager.on('input', ({ value }) => {\n this.options.onInput?.(value)\n })\n\n this.manager.on('confirm', ({ value }) => {\n this.options.onConfirm?.(value)\n })\n\n this.manager.on('switch', () => {\n if (this.manager.getState().visible) {\n this.render()\n }\n })\n\n this.manager.on('shift', () => {\n if (this.manager.getState().visible) {\n this.render()\n }\n })\n }\n\n /**\n * 打开键盘\n */\n open(target?: HTMLInputElement | HTMLTextAreaElement): void {\n this.targetEl = target ?? null\n this.manager.show(target)\n }\n\n /**\n * 关闭键盘\n */\n close(): void {\n this.manager.hide()\n this.options.onClose?.()\n }\n\n /**\n * 切换键盘类型\n */\n switchType(type: KeyboardType | string): void {\n this.manager.switchType(type)\n }\n\n /**\n * 设置主题\n */\n setTheme(theme: string | ThemeConfig): void {\n if (typeof theme === 'string') {\n this.theme = getTheme(theme)\n } else {\n this.theme = theme\n }\n if (this.manager.getState().visible) {\n this.render()\n }\n }\n\n /**\n * 获取当前值\n */\n getValue(): string {\n return this.manager.getState().value\n }\n\n /**\n * 设置值\n */\n setValue(value: string): void {\n this.manager.setValue(value)\n }\n\n /**\n * 渲染键盘 DOM\n */\n private render(): void {\n const layout = this.manager.getLayout()\n if (!layout) return\n\n // 清理旧的 DOM\n this.unmount()\n\n // 创建根元素\n this.rootEl = document.createElement('div')\n this.rootEl.className = 'lk-renderer-root'\n\n const isMobileDevice = isMobile()\n\n // 移动端创建遮罩\n if (isMobileDevice) {\n this.maskEl = document.createElement('div')\n this.maskEl.className = 'lk-mask'\n this.maskEl.addEventListener('click', () => this.close())\n this.rootEl.appendChild(this.maskEl)\n }\n\n // 创建键盘容器\n this.keyboardEl = document.createElement('div')\n this.keyboardEl.className = `lk-popup lk-popup--${isMobileDevice ? 'mobile' : 'desktop'}`\n\n // 应用主题 CSS 变量\n const cssVars = themeToCSS(this.theme)\n Object.entries(cssVars).forEach(([key, value]) => {\n if (value) {\n this.keyboardEl!.style.setProperty(key, value)\n }\n })\n\n // 渲染键盘内容\n const contentEl = this.renderKeyboard(layout)\n this.keyboardEl.appendChild(contentEl)\n\n this.rootEl.appendChild(this.keyboardEl)\n this.container.appendChild(this.rootEl)\n\n // 计算位置\n if (!isMobileDevice && this.targetEl) {\n const rect = this.keyboardEl.getBoundingClientRect()\n const result = this.positionManager.calculate(this.targetEl, {\n width: rect.width,\n height: rect.height,\n })\n this.keyboardEl.style.position = 'fixed'\n this.keyboardEl.style.left = `${result.position.x}px`\n this.keyboardEl.style.top = `${result.position.y}px`\n this.keyboardEl.style.zIndex = '10000'\n } else if (isMobileDevice) {\n this.keyboardEl.style.position = 'fixed'\n this.keyboardEl.style.left = '0'\n this.keyboardEl.style.right = '0'\n this.keyboardEl.style.bottom = '0'\n this.keyboardEl.style.zIndex = '10000'\n }\n\n // 添加动画类\n requestAnimationFrame(() => {\n this.keyboardEl?.classList.add('lk-popup--visible')\n this.maskEl?.classList.add('lk-mask--visible')\n })\n\n // 点击外部关闭\n if (this.options.closeOnClickOutside !== false && this.keyboardEl) {\n const cleanup = onClickOutside(this.keyboardEl, (event) => {\n if (this.targetEl && this.targetEl.contains(event.target as Node)) return\n this.close()\n })\n this.cleanupFns.push(cleanup)\n }\n\n // 注入样式\n this.injectStyles()\n }\n\n /**\n * 渲染键盘内容\n */\n private renderKeyboard(layout: KeyboardConfig): HTMLElement {\n const el = document.createElement('div')\n el.className = 'lk-keyboard'\n\n const state = this.manager.getState()\n\n layout.rows.forEach((row) => {\n const rowEl = document.createElement('div')\n rowEl.className = `lk-row ${row.className || ''}`\n\n const isNumpad = row.className === 'numpad-row'\n\n row.keys.forEach((key) => {\n const wrapperEl = document.createElement('div')\n wrapperEl.className = 'lk-key-wrapper'\n\n if (isNumpad) {\n wrapperEl.style.flex = String(key.width ?? 1)\n } else if (key.width) {\n const baseW = 36\n const gap = this.theme.sizes?.keyGap ?? 6\n const w = baseW * key.width + (key.width - 1) * gap\n wrapperEl.style.width = `${w}px`\n }\n\n const btnEl = document.createElement('button')\n btnEl.type = 'button'\n btnEl.className = this.getKeyClass(key, state.isUpperCase)\n\n if (key.disabled) {\n btnEl.disabled = true\n }\n\n // 内容\n if (key.icon) {\n btnEl.innerHTML = key.icon\n } else {\n const label = typeof key.label === 'string'\n ? key.label\n : state.isUpperCase ? key.label.upper : key.label.lower\n btnEl.textContent = label\n }\n\n // 事件\n const handler = (e: Event) => {\n e.preventDefault()\n e.stopPropagation()\n if (!key.disabled) {\n this.manager.handleKey(key)\n }\n }\n\n btnEl.addEventListener('mousedown', handler)\n btnEl.addEventListener('touchstart', handler, { passive: false })\n\n wrapperEl.appendChild(btnEl)\n rowEl.appendChild(wrapperEl)\n })\n\n el.appendChild(rowEl)\n })\n\n return el\n }\n\n /**\n * 获取按键 CSS 类名\n */\n private getKeyClass(key: KeyDefinition, isUpperCase: boolean): string {\n const classes = ['lk-key', `lk-key--${key.type}`]\n if (key.className) classes.push(key.className)\n if (key.disabled) classes.push('lk-key--disabled')\n if (key.type === 'shift' && isUpperCase) classes.push('lk-key--active')\n return classes.join(' ')\n }\n\n /**\n * 卸载 DOM\n */\n private unmount(): void {\n this.cleanupFns.forEach(fn => fn())\n this.cleanupFns = []\n\n if (this.rootEl && this.rootEl.parentNode) {\n this.rootEl.parentNode.removeChild(this.rootEl)\n }\n this.rootEl = null\n this.maskEl = null\n this.keyboardEl = null\n }\n\n /**\n * 注入样式(单例)\n */\n private injectStyles(): void {\n if (document.getElementById('lk-renderer-styles')) return\n\n const style = document.createElement('style')\n style.id = 'lk-renderer-styles'\n style.textContent = `\n .lk-renderer-root { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }\n .lk-mask { position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 9999; opacity: 0; transition: opacity 0.3s ease; }\n .lk-mask--visible { opacity: 1; }\n .lk-popup { background: var(--keyboard-bg, #d1d5db); box-shadow: 0 -2px 16px rgba(0,0,0,0.15); overflow: hidden; transform: translateY(20px); opacity: 0; transition: transform 0.3s cubic-bezier(0.4,0,0.2,1), opacity 0.2s ease; }\n .lk-popup--visible { transform: translateY(0); opacity: 1; }\n .lk-popup--desktop { border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.1); min-width: 320px; }\n .lk-popup--mobile { border-top-left-radius: 16px; border-top-right-radius: 16px; padding-bottom: env(safe-area-inset-bottom, 0); }\n .lk-keyboard { display: flex; flex-direction: column; gap: var(--keyboard-key-gap, 6px); padding: var(--keyboard-padding, 6px); user-select: none; -webkit-user-select: none; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; }\n .lk-row { display: flex; justify-content: center; gap: var(--keyboard-key-gap, 6px); padding: 0 2px; }\n .lk-row.numpad-row { justify-content: stretch; }\n .lk-row.numpad-row .lk-key-wrapper { flex: 1; }\n .lk-key-wrapper { position: relative; display: inline-flex; }\n .lk-key { display: inline-flex; align-items: center; justify-content: center; height: var(--keyboard-key-height, 42px); min-width: 32px; width: 100%; padding: 0 6px; border: none; border-radius: var(--keyboard-key-radius, 6px); background: var(--keyboard-key-bg, #fff); color: var(--keyboard-key-color, #333); font-size: var(--keyboard-font-size, 16px); font-weight: 500; cursor: pointer; touch-action: manipulation; box-shadow: var(--keyboard-key-shadow, 0 1px 3px rgba(0,0,0,0.1)); transition: background-color 0.1s, transform 0.1s; }\n .lk-key:active { background: var(--keyboard-key-active-bg, #e0e0e0); transform: scale(0.95); }\n .lk-key--disabled { opacity: 0.4; cursor: not-allowed; }\n .lk-key--disabled:active { transform: none; background: var(--keyboard-key-bg, #fff); }\n .lk-key--backspace, .lk-key--shift, .lk-key--tab, .lk-key--clear, .lk-key--close { background: var(--keyboard-fn-key-bg, #d4d6dc); color: var(--keyboard-fn-key-color, #333); font-size: var(--keyboard-fn-font-size, 14px); }\n .lk-key--backspace:active, .lk-key--shift:active, .lk-key--tab:active, .lk-key--clear:active, .lk-key--close:active { background: var(--keyboard-fn-key-active-bg, #b8bac0); }\n .lk-key--active { background: var(--keyboard-key-active-bg, #007aff) !important; color: #fff !important; }\n .lk-key--done, .lk-key--enter { background: var(--keyboard-primary-bg, #007aff); color: var(--keyboard-primary-color, #fff); font-size: var(--keyboard-fn-font-size, 14px); }\n .lk-key--done:active, .lk-key--enter:active { background: var(--keyboard-primary-active-bg, #0062cc); }\n .lk-key--space { background: var(--keyboard-key-bg, #fff); }\n .lk-key svg { pointer-events: none; }\n `\n document.head.appendChild(style)\n }\n\n /**\n * 绑定到输入框(聚焦时自动打开键盘)\n */\n bindInput(input: HTMLInputElement | HTMLTextAreaElement): () => void {\n const handleFocus = () => this.open(input)\n input.addEventListener('focus', handleFocus)\n\n // 阻止移动端原生键盘\n input.setAttribute('readonly', 'readonly')\n input.style.caretColor = 'transparent'\n\n return () => {\n input.removeEventListener('focus', handleFocus)\n input.removeAttribute('readonly')\n input.style.caretColor = ''\n }\n }\n\n /**\n * 销毁渲染器\n */\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.unmount()\n this.manager.destroy()\n\n // 移除样式\n const styleEl = document.getElementById('lk-renderer-styles')\n if (styleEl) styleEl.remove()\n }\n}\n\n/**\n * 创建键盘渲染器\n */\nexport function createKeyboardRenderer(options?: KeyboardRendererOptions): KeyboardRenderer {\n return new KeyboardRenderer(options)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 键盘 DOM 渲染器
|
|
3
|
+
* 框架无关的键盘渲染器,用于原生 JS 项目
|
|
4
|
+
* @module managers/keyboard-renderer
|
|
5
|
+
*/
|
|
6
|
+
import type { KeyboardOptions, KeyboardType, PopupPositionConfig } from '../types';
|
|
7
|
+
import type { ThemeConfig } from '../themes/types';
|
|
8
|
+
import { KeyboardManager } from './keyboard-manager';
|
|
9
|
+
/**
|
|
10
|
+
* 渲染器配置
|
|
11
|
+
*/
|
|
12
|
+
export interface KeyboardRendererOptions extends KeyboardOptions {
|
|
13
|
+
/** 挂载容器(默认 document.body) */
|
|
14
|
+
container?: HTMLElement;
|
|
15
|
+
/** 主题名称或主题配置 */
|
|
16
|
+
theme?: string | ThemeConfig;
|
|
17
|
+
/** 弹出层配置 */
|
|
18
|
+
position?: PopupPositionConfig;
|
|
19
|
+
/** 点击外部关闭 */
|
|
20
|
+
closeOnClickOutside?: boolean;
|
|
21
|
+
/** 确认回调 */
|
|
22
|
+
onConfirm?: (value: string) => void;
|
|
23
|
+
/** 输入变化回调 */
|
|
24
|
+
onInput?: (value: string) => void;
|
|
25
|
+
/** 关闭回调 */
|
|
26
|
+
onClose?: () => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 键盘 DOM 渲染器
|
|
30
|
+
* 提供完整的键盘 DOM 创建、事件绑定、位置管理
|
|
31
|
+
*/
|
|
32
|
+
export declare class KeyboardRenderer {
|
|
33
|
+
/** 键盘管理器 */
|
|
34
|
+
readonly manager: KeyboardManager;
|
|
35
|
+
/** 位置管理器 */
|
|
36
|
+
private positionManager;
|
|
37
|
+
/** 配置 */
|
|
38
|
+
private options;
|
|
39
|
+
/** DOM 元素 */
|
|
40
|
+
private rootEl;
|
|
41
|
+
private maskEl;
|
|
42
|
+
private keyboardEl;
|
|
43
|
+
/** 挂载容器 */
|
|
44
|
+
private container;
|
|
45
|
+
/** 主题配置 */
|
|
46
|
+
private theme;
|
|
47
|
+
/** 当前目标元素 */
|
|
48
|
+
private targetEl;
|
|
49
|
+
/** 清理函数 */
|
|
50
|
+
private cleanupFns;
|
|
51
|
+
/** 是否已销毁 */
|
|
52
|
+
private destroyed;
|
|
53
|
+
constructor(options?: KeyboardRendererOptions);
|
|
54
|
+
/**
|
|
55
|
+
* 设置事件监听
|
|
56
|
+
*/
|
|
57
|
+
private setupEvents;
|
|
58
|
+
/**
|
|
59
|
+
* 打开键盘
|
|
60
|
+
*/
|
|
61
|
+
open(target?: HTMLInputElement | HTMLTextAreaElement): void;
|
|
62
|
+
/**
|
|
63
|
+
* 关闭键盘
|
|
64
|
+
*/
|
|
65
|
+
close(): void;
|
|
66
|
+
/**
|
|
67
|
+
* 切换键盘类型
|
|
68
|
+
*/
|
|
69
|
+
switchType(type: KeyboardType | string): void;
|
|
70
|
+
/**
|
|
71
|
+
* 设置主题
|
|
72
|
+
*/
|
|
73
|
+
setTheme(theme: string | ThemeConfig): void;
|
|
74
|
+
/**
|
|
75
|
+
* 获取当前值
|
|
76
|
+
*/
|
|
77
|
+
getValue(): string;
|
|
78
|
+
/**
|
|
79
|
+
* 设置值
|
|
80
|
+
*/
|
|
81
|
+
setValue(value: string): void;
|
|
82
|
+
/**
|
|
83
|
+
* 渲染键盘 DOM
|
|
84
|
+
*/
|
|
85
|
+
private render;
|
|
86
|
+
/**
|
|
87
|
+
* 渲染键盘内容
|
|
88
|
+
*/
|
|
89
|
+
private renderKeyboard;
|
|
90
|
+
/**
|
|
91
|
+
* 获取按键 CSS 类名
|
|
92
|
+
*/
|
|
93
|
+
private getKeyClass;
|
|
94
|
+
/**
|
|
95
|
+
* 卸载 DOM
|
|
96
|
+
*/
|
|
97
|
+
private unmount;
|
|
98
|
+
/**
|
|
99
|
+
* 注入样式(单例)
|
|
100
|
+
*/
|
|
101
|
+
private injectStyles;
|
|
102
|
+
/**
|
|
103
|
+
* 绑定到输入框(聚焦时自动打开键盘)
|
|
104
|
+
*/
|
|
105
|
+
bindInput(input: HTMLInputElement | HTMLTextAreaElement): () => void;
|
|
106
|
+
/**
|
|
107
|
+
* 销毁渲染器
|
|
108
|
+
*/
|
|
109
|
+
destroy(): void;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 创建键盘渲染器
|
|
113
|
+
*/
|
|
114
|
+
export declare function createKeyboardRenderer(options?: KeyboardRendererOptions): KeyboardRenderer;
|
|
115
|
+
//# sourceMappingURL=keyboard-renderer.d.ts.map
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/keyboard-core v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:33:29 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
11
|
+
const require_device = require("../utils/device.cjs");
|
|
12
|
+
const require_dom = require("../utils/dom.cjs");
|
|
13
|
+
//#region src/managers/position-manager.ts
|
|
14
|
+
const DEFAULT_CONFIG = {
|
|
15
|
+
offset: {
|
|
16
|
+
x: 0,
|
|
17
|
+
y: 8
|
|
18
|
+
},
|
|
19
|
+
autoAdjust: true,
|
|
20
|
+
viewportPadding: 8,
|
|
21
|
+
desktopPlacement: "bottom",
|
|
22
|
+
mobilePlacement: "bottom-sheet",
|
|
23
|
+
showMask: true,
|
|
24
|
+
maskClosable: true,
|
|
25
|
+
animationDuration: 300
|
|
26
|
+
};
|
|
27
|
+
var PositionManager = class {
|
|
28
|
+
constructor(config = {}) {
|
|
29
|
+
this.config = {
|
|
30
|
+
...DEFAULT_CONFIG,
|
|
31
|
+
...config
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 更新配置
|
|
36
|
+
*/
|
|
37
|
+
updateConfig(config) {
|
|
38
|
+
Object.assign(this.config, config);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 获取配置
|
|
42
|
+
*/
|
|
43
|
+
getConfig() {
|
|
44
|
+
return { ...this.config };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 计算弹出位置
|
|
48
|
+
* @param targetElement - 目标元素(如 input)
|
|
49
|
+
* @param popupSize - 弹出层尺寸
|
|
50
|
+
* @returns 位置计算结果
|
|
51
|
+
*/
|
|
52
|
+
calculate(targetElement, popupSize) {
|
|
53
|
+
const viewport = require_device.getViewportInfo();
|
|
54
|
+
const isMobileDevice = require_device.isMobile();
|
|
55
|
+
if (isMobileDevice && this.config.mobilePlacement === "bottom-sheet") return this.calculateBottomSheet(viewport, popupSize);
|
|
56
|
+
if (!targetElement) return this.calculateCentered(viewport, popupSize);
|
|
57
|
+
const targetRect = require_dom.getBoundingRect(targetElement);
|
|
58
|
+
const placement = isMobileDevice ? this.config.mobilePlacement : this.config.desktopPlacement;
|
|
59
|
+
const relativePlace = placement === "bottom-sheet" ? "bottom" : placement;
|
|
60
|
+
return this.calculateRelativeToTarget(targetRect, popupSize, viewport, relativePlace);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 计算 bottom-sheet 位置(移动端从底部滑出)
|
|
64
|
+
*/
|
|
65
|
+
calculateBottomSheet(viewport, popupSize) {
|
|
66
|
+
const safeAreaBottom = viewport.safeArea?.bottom ?? 0;
|
|
67
|
+
return {
|
|
68
|
+
position: {
|
|
69
|
+
x: 0,
|
|
70
|
+
y: viewport.height - popupSize.height - safeAreaBottom
|
|
71
|
+
},
|
|
72
|
+
placement: "bottom-sheet",
|
|
73
|
+
flipped: false,
|
|
74
|
+
maxHeight: viewport.height * .6,
|
|
75
|
+
maxWidth: viewport.width,
|
|
76
|
+
transform: "translateY(0)"
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 计算居中位置
|
|
81
|
+
*/
|
|
82
|
+
calculateCentered(viewport, popupSize) {
|
|
83
|
+
return {
|
|
84
|
+
position: {
|
|
85
|
+
x: (viewport.width - popupSize.width) / 2,
|
|
86
|
+
y: (viewport.height - popupSize.height) / 2
|
|
87
|
+
},
|
|
88
|
+
placement: "bottom",
|
|
89
|
+
flipped: false
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 计算相对于目标元素的位置
|
|
94
|
+
*/
|
|
95
|
+
calculateRelativeToTarget(targetRect, popupSize, viewport, preferredPlacement) {
|
|
96
|
+
const { offset, viewportPadding, autoAdjust } = this.config;
|
|
97
|
+
let position;
|
|
98
|
+
let placement = preferredPlacement;
|
|
99
|
+
let flipped = false;
|
|
100
|
+
const bottomPosition = {
|
|
101
|
+
x: targetRect.x + (offset.x ?? 0),
|
|
102
|
+
y: targetRect.y + targetRect.height + (offset.y ?? 8)
|
|
103
|
+
};
|
|
104
|
+
const topPosition = {
|
|
105
|
+
x: targetRect.x + (offset.x ?? 0),
|
|
106
|
+
y: targetRect.y - popupSize.height - (offset.y ?? 8)
|
|
107
|
+
};
|
|
108
|
+
if (preferredPlacement === "bottom") {
|
|
109
|
+
position = bottomPosition;
|
|
110
|
+
if (autoAdjust && position.y + popupSize.height > viewport.height - viewportPadding) {
|
|
111
|
+
if (topPosition.y >= viewportPadding) {
|
|
112
|
+
position = topPosition;
|
|
113
|
+
placement = "top";
|
|
114
|
+
flipped = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
position = topPosition;
|
|
119
|
+
if (autoAdjust && position.y < viewportPadding) {
|
|
120
|
+
if (bottomPosition.y + popupSize.height <= viewport.height - viewportPadding) {
|
|
121
|
+
position = bottomPosition;
|
|
122
|
+
placement = "bottom";
|
|
123
|
+
flipped = true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (autoAdjust) {
|
|
128
|
+
if (position.x < viewportPadding) position.x = viewportPadding;
|
|
129
|
+
if (position.x + popupSize.width > viewport.width - viewportPadding) position.x = viewport.width - popupSize.width - viewportPadding;
|
|
130
|
+
}
|
|
131
|
+
const maxHeight = placement === "bottom" ? viewport.height - position.y - viewportPadding : targetRect.y - viewportPadding;
|
|
132
|
+
return {
|
|
133
|
+
position,
|
|
134
|
+
placement,
|
|
135
|
+
flipped,
|
|
136
|
+
maxHeight: Math.max(200, maxHeight),
|
|
137
|
+
maxWidth: viewport.width - viewportPadding * 2
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 获取弹出层初始样式(用于动画)
|
|
142
|
+
*/
|
|
143
|
+
getInitialStyles(placement) {
|
|
144
|
+
switch (placement) {
|
|
145
|
+
case "bottom-sheet": return {
|
|
146
|
+
transform: "translateY(100%)",
|
|
147
|
+
opacity: "1"
|
|
148
|
+
};
|
|
149
|
+
case "bottom": return {
|
|
150
|
+
transform: "translateY(-10px)",
|
|
151
|
+
opacity: "0"
|
|
152
|
+
};
|
|
153
|
+
case "top": return {
|
|
154
|
+
transform: "translateY(10px)",
|
|
155
|
+
opacity: "0"
|
|
156
|
+
};
|
|
157
|
+
default: return { opacity: "0" };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 获取弹出层最终样式(动画结束)
|
|
162
|
+
*/
|
|
163
|
+
getFinalStyles(placement) {
|
|
164
|
+
return {
|
|
165
|
+
transform: "translateY(0)",
|
|
166
|
+
opacity: "1"
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 获取弹出层退出样式
|
|
171
|
+
*/
|
|
172
|
+
getExitStyles(placement) {
|
|
173
|
+
return this.getInitialStyles(placement);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 获取动画时长
|
|
177
|
+
*/
|
|
178
|
+
getAnimationDuration() {
|
|
179
|
+
return this.config.animationDuration;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 检查是否显示遮罩
|
|
183
|
+
*/
|
|
184
|
+
shouldShowMask() {
|
|
185
|
+
return require_device.isMobile() && this.config.showMask;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* 检查遮罩是否可点击关闭
|
|
189
|
+
*/
|
|
190
|
+
isMaskClosable() {
|
|
191
|
+
return this.config.maskClosable;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
function createPositionManager(config) {
|
|
195
|
+
return new PositionManager(config);
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
exports.PositionManager = PositionManager;
|
|
199
|
+
exports.createPositionManager = createPositionManager;
|
|
200
|
+
/*! End of @composy/keyboard-core | Powered by @composy/builder */
|
|
201
|
+
//# sourceMappingURL=position-manager.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"position-manager.cjs","names":[],"sources":["../../src/managers/position-manager.ts"],"sourcesContent":["/**\n * 弹出位置管理器\n * @module managers/position-manager\n */\n\nimport type {\n BoundingRect,\n PopupPlacement,\n PopupPositionConfig,\n PopupPositionResult,\n Position,\n} from '../types'\nimport { getBoundingRect } from '../utils/dom'\nimport { getViewportInfo, isMobile } from '../utils/device'\n\n/**\n * 默认位置配置\n */\nconst DEFAULT_CONFIG: Required<PopupPositionConfig> = {\n offset: { x: 0, y: 8 },\n autoAdjust: true,\n viewportPadding: 8,\n desktopPlacement: 'bottom',\n mobilePlacement: 'bottom-sheet',\n showMask: true,\n maskClosable: true,\n animationDuration: 300,\n}\n\n/**\n * 位置管理器类\n * 计算键盘弹出层的位置,支持 PC 和移动端自适应\n */\nexport class PositionManager {\n private config: Required<PopupPositionConfig>\n\n constructor(config: PopupPositionConfig = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config }\n }\n\n /**\n * 更新配置\n */\n updateConfig(config: Partial<PopupPositionConfig>): void {\n Object.assign(this.config, config)\n }\n\n /**\n * 获取配置\n */\n getConfig(): Readonly<Required<PopupPositionConfig>> {\n return { ...this.config }\n }\n\n /**\n * 计算弹出位置\n * @param targetElement - 目标元素(如 input)\n * @param popupSize - 弹出层尺寸\n * @returns 位置计算结果\n */\n calculate(\n targetElement: HTMLElement | null,\n popupSize: { width: number; height: number },\n ): PopupPositionResult {\n const viewport = getViewportInfo()\n const isMobileDevice = isMobile()\n\n // 移动端使用 bottom-sheet\n if (isMobileDevice && this.config.mobilePlacement === 'bottom-sheet') {\n return this.calculateBottomSheet(viewport, popupSize)\n }\n\n // PC 端或移动端非 bottom-sheet\n if (!targetElement) {\n // 无目标元素,居中显示\n return this.calculateCentered(viewport, popupSize)\n }\n\n const targetRect = getBoundingRect(targetElement)\n const placement = isMobileDevice\n ? this.config.mobilePlacement\n : this.config.desktopPlacement\n\n // bottom-sheet 已经在前面处理,这里只会是 'bottom' 或 'top'\n const relativePlace = placement === 'bottom-sheet' ? 'bottom' : placement\n return this.calculateRelativeToTarget(targetRect, popupSize, viewport, relativePlace)\n }\n\n /**\n * 计算 bottom-sheet 位置(移动端从底部滑出)\n */\n private calculateBottomSheet(\n viewport: { width: number; height: number; scrollY: number; safeArea?: { bottom: number } },\n popupSize: { width: number; height: number },\n ): PopupPositionResult {\n const safeAreaBottom = viewport.safeArea?.bottom ?? 0\n\n return {\n position: {\n x: 0,\n y: viewport.height - popupSize.height - safeAreaBottom,\n },\n placement: 'bottom-sheet',\n flipped: false,\n maxHeight: viewport.height * 0.6, // 最大占屏幕高度的 60%\n maxWidth: viewport.width,\n transform: 'translateY(0)',\n }\n }\n\n /**\n * 计算居中位置\n */\n private calculateCentered(\n viewport: { width: number; height: number },\n popupSize: { width: number; height: number },\n ): PopupPositionResult {\n return {\n position: {\n x: (viewport.width - popupSize.width) / 2,\n y: (viewport.height - popupSize.height) / 2,\n },\n placement: 'bottom',\n flipped: false,\n }\n }\n\n /**\n * 计算相对于目标元素的位置\n */\n private calculateRelativeToTarget(\n targetRect: BoundingRect,\n popupSize: { width: number; height: number },\n viewport: { width: number; height: number; scrollX: number; scrollY: number },\n preferredPlacement: Exclude<PopupPlacement, 'bottom-sheet'>,\n ): PopupPositionResult {\n const { offset, viewportPadding, autoAdjust } = this.config\n\n let position: Position\n let placement = preferredPlacement\n let flipped = false\n\n // 计算 bottom 位置(左对齐)\n const bottomPosition: Position = {\n x: targetRect.x + (offset.x ?? 0),\n y: targetRect.y + targetRect.height + (offset.y ?? 8),\n }\n\n // 计算 top 位置(左对齐)\n const topPosition: Position = {\n x: targetRect.x + (offset.x ?? 0),\n y: targetRect.y - popupSize.height - (offset.y ?? 8),\n }\n\n if (preferredPlacement === 'bottom') {\n position = bottomPosition\n\n // 检查是否需要翻转到顶部\n if (autoAdjust && position.y + popupSize.height > viewport.height - viewportPadding) {\n // 检查顶部是否有足够空间\n if (topPosition.y >= viewportPadding) {\n position = topPosition\n placement = 'top'\n flipped = true\n }\n }\n }\n else {\n position = topPosition\n\n // 检查是否需要翻转到底部\n if (autoAdjust && position.y < viewportPadding) {\n // 检查底部是否有足够空间\n if (bottomPosition.y + popupSize.height <= viewport.height - viewportPadding) {\n position = bottomPosition\n placement = 'bottom'\n flipped = true\n }\n }\n }\n\n // 水平方向调整,确保不超出视口\n if (autoAdjust) {\n // 左边界检查\n if (position.x < viewportPadding) {\n position.x = viewportPadding\n }\n\n // 右边界检查\n if (position.x + popupSize.width > viewport.width - viewportPadding) {\n position.x = viewport.width - popupSize.width - viewportPadding\n }\n }\n\n // 计算最大可用高度\n const maxHeight = placement === 'bottom'\n ? viewport.height - position.y - viewportPadding\n : targetRect.y - viewportPadding\n\n return {\n position,\n placement,\n flipped,\n maxHeight: Math.max(200, maxHeight), // 最小高度 200px\n maxWidth: viewport.width - viewportPadding * 2,\n }\n }\n\n /**\n * 获取弹出层初始样式(用于动画)\n */\n getInitialStyles(placement: PopupPlacement): Partial<CSSStyleDeclaration> {\n switch (placement) {\n case 'bottom-sheet':\n return {\n transform: 'translateY(100%)',\n opacity: '1',\n }\n case 'bottom':\n return {\n transform: 'translateY(-10px)',\n opacity: '0',\n }\n case 'top':\n return {\n transform: 'translateY(10px)',\n opacity: '0',\n }\n default:\n return {\n opacity: '0',\n }\n }\n }\n\n /**\n * 获取弹出层最终样式(动画结束)\n */\n getFinalStyles(placement: PopupPlacement): Partial<CSSStyleDeclaration> {\n return {\n transform: 'translateY(0)',\n opacity: '1',\n }\n }\n\n /**\n * 获取弹出层退出样式\n */\n getExitStyles(placement: PopupPlacement): Partial<CSSStyleDeclaration> {\n return this.getInitialStyles(placement)\n }\n\n /**\n * 获取动画时长\n */\n getAnimationDuration(): number {\n return this.config.animationDuration\n }\n\n /**\n * 检查是否显示遮罩\n */\n shouldShowMask(): boolean {\n return isMobile() && this.config.showMask\n }\n\n /**\n * 检查遮罩是否可点击关闭\n */\n isMaskClosable(): boolean {\n return this.config.maskClosable\n }\n}\n\n/**\n * 创建位置管理器实例\n */\nexport function createPositionManager(config?: PopupPositionConfig): PositionManager {\n return new PositionManager(config)\n}\n"],"mappings":"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"}
|