@composy/keyboard-core 0.0.1 → 0.0.2

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.
Files changed (339) hide show
  1. package/README.md +156 -37
  2. package/dist/index.js +1540 -1395
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.min.js +2981 -1
  5. package/dist/index.min.js.map +1 -0
  6. package/es/.dts-output-manifest.json +1 -0
  7. package/es/.dts.tsbuildinfo +1 -0
  8. package/es/icons.d.ts +0 -1
  9. package/es/icons.js +1 -1
  10. package/es/icons.js.map +1 -1
  11. package/es/index.d.ts +3 -4
  12. package/es/index.js +15 -15
  13. package/es/layouts/amount.d.ts +0 -1
  14. package/es/layouts/amount.js +1 -1
  15. package/es/layouts/amount.js.map +1 -1
  16. package/es/layouts/bankcard.d.ts +0 -1
  17. package/es/layouts/bankcard.js +1 -1
  18. package/es/layouts/bankcard.js.map +1 -1
  19. package/es/layouts/email.d.ts +0 -1
  20. package/es/layouts/email.js +1 -1
  21. package/es/layouts/email.js.map +1 -1
  22. package/es/layouts/idcard.d.ts +0 -1
  23. package/es/layouts/idcard.js +1 -1
  24. package/es/layouts/idcard.js.map +1 -1
  25. package/es/layouts/index.d.ts +11 -12
  26. package/es/layouts/index.js +23 -23
  27. package/es/layouts/index.js.map +1 -1
  28. package/es/layouts/license-plate.d.ts +0 -1
  29. package/es/layouts/license-plate.js +1 -1
  30. package/es/layouts/license-plate.js.map +1 -1
  31. package/es/layouts/number.d.ts +0 -1
  32. package/es/layouts/number.js +1 -1
  33. package/es/layouts/number.js.map +1 -1
  34. package/es/layouts/phone.d.ts +0 -1
  35. package/es/layouts/phone.js +1 -1
  36. package/es/layouts/phone.js.map +1 -1
  37. package/es/layouts/punct.d.ts +0 -1
  38. package/es/layouts/punct.js +1 -1
  39. package/es/layouts/punct.js.map +1 -1
  40. package/es/layouts/qwerty.d.ts +0 -1
  41. package/es/layouts/qwerty.js +1 -1
  42. package/es/layouts/qwerty.js.map +1 -1
  43. package/es/layouts/search.d.ts +0 -1
  44. package/es/layouts/search.js +1 -1
  45. package/es/layouts/search.js.map +1 -1
  46. package/es/layouts/symbol.d.ts +0 -1
  47. package/es/layouts/symbol.js +1 -1
  48. package/es/layouts/symbol.js.map +1 -1
  49. package/es/layouts/url.d.ts +0 -1
  50. package/es/layouts/url.js +1 -1
  51. package/es/layouts/url.js.map +1 -1
  52. package/es/layouts/verifycode.d.ts +0 -1
  53. package/es/layouts/verifycode.js +1 -1
  54. package/es/layouts/verifycode.js.map +1 -1
  55. package/es/managers/index.d.ts +1 -2
  56. package/es/managers/index.js +1 -1
  57. package/es/managers/keyboard-manager.d.ts +2 -1
  58. package/es/managers/keyboard-manager.js +79 -22
  59. package/es/managers/keyboard-manager.js.map +1 -1
  60. package/es/managers/keyboard-renderer.d.ts +3 -2
  61. package/es/managers/keyboard-renderer.js +81 -39
  62. package/es/managers/keyboard-renderer.js.map +1 -1
  63. package/es/managers/position-manager.d.ts +4 -3
  64. package/es/managers/position-manager.js +42 -21
  65. package/es/managers/position-manager.js.map +1 -1
  66. package/es/themes/index.d.ts +1 -2
  67. package/es/themes/index.js +2 -1
  68. package/es/themes/index.js.map +1 -1
  69. package/es/themes/presets.d.ts +0 -1
  70. package/es/themes/presets.js +1 -1
  71. package/es/themes/presets.js.map +1 -1
  72. package/es/themes/types.d.ts +1 -1
  73. package/es/themes/types.js +1 -1
  74. package/es/types/index.d.ts +0 -1
  75. package/es/types/index.js +1 -1
  76. package/es/types/keyboard.d.ts +0 -1
  77. package/es/types/keyboard.js +1 -1
  78. package/es/types/position.d.ts +0 -1
  79. package/es/types/position.js +1 -1
  80. package/es/utils/device.d.ts +0 -1
  81. package/es/utils/device.js +11 -7
  82. package/es/utils/device.js.map +1 -1
  83. package/es/utils/dom.d.ts +4 -1
  84. package/es/utils/dom.js +25 -7
  85. package/es/utils/dom.js.map +1 -1
  86. package/es/utils/event-emitter.d.ts +0 -1
  87. package/es/utils/event-emitter.js +2 -1
  88. package/es/utils/event-emitter.js.map +1 -1
  89. package/es/utils/formatters.d.ts +0 -1
  90. package/es/utils/formatters.js +4 -4
  91. package/es/utils/formatters.js.map +1 -1
  92. package/es/utils/index.d.ts +2 -3
  93. package/es/utils/index.js +5 -5
  94. package/es/utils/random.d.ts +4 -1
  95. package/es/utils/random.js +2 -2
  96. package/es/utils/random.js.map +1 -1
  97. package/esm/icons.js +1 -1
  98. package/esm/icons.js.map +1 -1
  99. package/esm/index.js +15 -15
  100. package/esm/layouts/amount.js +1 -1
  101. package/esm/layouts/amount.js.map +1 -1
  102. package/esm/layouts/bankcard.js +1 -1
  103. package/esm/layouts/bankcard.js.map +1 -1
  104. package/esm/layouts/email.js +1 -1
  105. package/esm/layouts/email.js.map +1 -1
  106. package/esm/layouts/idcard.js +1 -1
  107. package/esm/layouts/idcard.js.map +1 -1
  108. package/esm/layouts/index.js +23 -23
  109. package/esm/layouts/index.js.map +1 -1
  110. package/esm/layouts/license-plate.js +1 -1
  111. package/esm/layouts/license-plate.js.map +1 -1
  112. package/esm/layouts/number.js +1 -1
  113. package/esm/layouts/number.js.map +1 -1
  114. package/esm/layouts/phone.js +1 -1
  115. package/esm/layouts/phone.js.map +1 -1
  116. package/esm/layouts/punct.js +1 -1
  117. package/esm/layouts/punct.js.map +1 -1
  118. package/esm/layouts/qwerty.js +1 -1
  119. package/esm/layouts/qwerty.js.map +1 -1
  120. package/esm/layouts/search.js +1 -1
  121. package/esm/layouts/search.js.map +1 -1
  122. package/esm/layouts/symbol.js +1 -1
  123. package/esm/layouts/symbol.js.map +1 -1
  124. package/esm/layouts/url.js +1 -1
  125. package/esm/layouts/url.js.map +1 -1
  126. package/esm/layouts/verifycode.js +1 -1
  127. package/esm/layouts/verifycode.js.map +1 -1
  128. package/esm/managers/index.js +1 -1
  129. package/esm/managers/keyboard-manager.js +79 -22
  130. package/esm/managers/keyboard-manager.js.map +1 -1
  131. package/esm/managers/keyboard-renderer.js +81 -39
  132. package/esm/managers/keyboard-renderer.js.map +1 -1
  133. package/esm/managers/position-manager.js +42 -21
  134. package/esm/managers/position-manager.js.map +1 -1
  135. package/esm/themes/index.js +2 -1
  136. package/esm/themes/index.js.map +1 -1
  137. package/esm/themes/presets.js +1 -1
  138. package/esm/themes/presets.js.map +1 -1
  139. package/esm/utils/device.js +11 -7
  140. package/esm/utils/device.js.map +1 -1
  141. package/esm/utils/dom.js +25 -7
  142. package/esm/utils/dom.js.map +1 -1
  143. package/esm/utils/event-emitter.js +2 -1
  144. package/esm/utils/event-emitter.js.map +1 -1
  145. package/esm/utils/formatters.js +4 -4
  146. package/esm/utils/formatters.js.map +1 -1
  147. package/esm/utils/random.js +2 -2
  148. package/esm/utils/random.js.map +1 -1
  149. package/lib/icons.cjs +1 -1
  150. package/lib/icons.cjs.map +1 -1
  151. package/{esm/icons.d.ts → lib/icons.d.cts} +0 -1
  152. package/lib/index.cjs +14 -13
  153. package/{esm/index.d.ts → lib/index.d.cts} +3 -4
  154. package/lib/layouts/amount.cjs +1 -1
  155. package/lib/layouts/amount.cjs.map +1 -1
  156. package/{esm/layouts/amount.d.ts → lib/layouts/amount.d.cts} +0 -1
  157. package/lib/layouts/bankcard.cjs +1 -1
  158. package/lib/layouts/bankcard.cjs.map +1 -1
  159. package/{esm/layouts/bankcard.d.ts → lib/layouts/bankcard.d.cts} +0 -1
  160. package/lib/layouts/email.cjs +1 -1
  161. package/lib/layouts/email.cjs.map +1 -1
  162. package/lib/layouts/{email.d.ts → email.d.cts} +0 -1
  163. package/lib/layouts/idcard.cjs +1 -1
  164. package/lib/layouts/idcard.cjs.map +1 -1
  165. package/{esm/layouts/idcard.d.ts → lib/layouts/idcard.d.cts} +0 -1
  166. package/lib/layouts/index.cjs +23 -23
  167. package/lib/layouts/index.cjs.map +1 -1
  168. package/{esm/layouts/index.d.ts → lib/layouts/index.d.cts} +11 -12
  169. package/lib/layouts/license-plate.cjs +1 -1
  170. package/lib/layouts/license-plate.cjs.map +1 -1
  171. package/{esm/layouts/license-plate.d.ts → lib/layouts/license-plate.d.cts} +0 -1
  172. package/lib/layouts/number.cjs +1 -1
  173. package/lib/layouts/number.cjs.map +1 -1
  174. package/{esm/layouts/number.d.ts → lib/layouts/number.d.cts} +0 -1
  175. package/lib/layouts/phone.cjs +1 -1
  176. package/lib/layouts/phone.cjs.map +1 -1
  177. package/lib/layouts/{phone.d.ts → phone.d.cts} +0 -1
  178. package/lib/layouts/punct.cjs +1 -1
  179. package/lib/layouts/punct.cjs.map +1 -1
  180. package/lib/layouts/{punct.d.ts → punct.d.cts} +0 -1
  181. package/lib/layouts/qwerty.cjs +1 -1
  182. package/lib/layouts/qwerty.cjs.map +1 -1
  183. package/{esm/layouts/qwerty.d.ts → lib/layouts/qwerty.d.cts} +0 -1
  184. package/lib/layouts/search.cjs +1 -1
  185. package/lib/layouts/search.cjs.map +1 -1
  186. package/{esm/layouts/search.d.ts → lib/layouts/search.d.cts} +0 -1
  187. package/lib/layouts/symbol.cjs +1 -1
  188. package/lib/layouts/symbol.cjs.map +1 -1
  189. package/{esm/layouts/symbol.d.ts → lib/layouts/symbol.d.cts} +0 -1
  190. package/lib/layouts/url.cjs +1 -1
  191. package/lib/layouts/url.cjs.map +1 -1
  192. package/{esm/layouts/url.d.ts → lib/layouts/url.d.cts} +0 -1
  193. package/lib/layouts/verifycode.cjs +1 -1
  194. package/lib/layouts/verifycode.cjs.map +1 -1
  195. package/lib/layouts/{verifycode.d.ts → verifycode.d.cts} +0 -1
  196. package/lib/managers/index.cjs +1 -1
  197. package/lib/managers/{index.d.ts → index.d.cts} +1 -2
  198. package/lib/managers/keyboard-manager.cjs +79 -22
  199. package/lib/managers/keyboard-manager.cjs.map +1 -1
  200. package/lib/managers/{keyboard-manager.d.ts → keyboard-manager.d.cts} +2 -1
  201. package/lib/managers/keyboard-renderer.cjs +80 -38
  202. package/lib/managers/keyboard-renderer.cjs.map +1 -1
  203. package/lib/managers/{keyboard-renderer.d.ts → keyboard-renderer.d.cts} +3 -2
  204. package/lib/managers/position-manager.cjs +42 -21
  205. package/lib/managers/position-manager.cjs.map +1 -1
  206. package/lib/managers/{position-manager.d.ts → position-manager.d.cts} +4 -3
  207. package/lib/themes/index.cjs +2 -1
  208. package/lib/themes/index.cjs.map +1 -1
  209. package/lib/themes/{index.d.ts → index.d.cts} +1 -2
  210. package/lib/themes/presets.cjs +1 -1
  211. package/lib/themes/presets.cjs.map +1 -1
  212. package/lib/themes/{presets.d.ts → presets.d.cts} +0 -1
  213. package/lib/themes/types.cjs +1 -1
  214. package/{esm/themes/types.d.ts → lib/themes/types.d.cts} +1 -1
  215. package/lib/types/index.cjs +1 -1
  216. package/{esm/types/index.d.ts → lib/types/index.d.cts} +0 -1
  217. package/lib/types/keyboard.cjs +1 -1
  218. package/{esm/types/keyboard.d.ts → lib/types/keyboard.d.cts} +0 -1
  219. package/lib/types/position.cjs +1 -1
  220. package/lib/types/{position.d.ts → position.d.cts} +0 -1
  221. package/lib/utils/device.cjs +11 -7
  222. package/lib/utils/device.cjs.map +1 -1
  223. package/{esm/utils/device.d.ts → lib/utils/device.d.cts} +0 -1
  224. package/lib/utils/dom.cjs +25 -6
  225. package/lib/utils/dom.cjs.map +1 -1
  226. package/{esm/utils/dom.d.ts → lib/utils/dom.d.cts} +4 -1
  227. package/lib/utils/event-emitter.cjs +2 -1
  228. package/lib/utils/event-emitter.cjs.map +1 -1
  229. package/{esm/utils/event-emitter.d.ts → lib/utils/event-emitter.d.cts} +0 -1
  230. package/lib/utils/formatters.cjs +4 -4
  231. package/lib/utils/formatters.cjs.map +1 -1
  232. package/{esm/utils/formatters.d.ts → lib/utils/formatters.d.cts} +0 -1
  233. package/lib/utils/index.cjs +4 -3
  234. package/{esm/utils/index.d.ts → lib/utils/index.d.cts} +2 -3
  235. package/lib/utils/random.cjs +2 -2
  236. package/lib/utils/random.cjs.map +1 -1
  237. package/{esm/utils/random.d.ts → lib/utils/random.d.cts} +4 -1
  238. package/package.json +28 -14
  239. package/es/icons.d.ts.map +0 -1
  240. package/es/index.d.ts.map +0 -1
  241. package/es/layouts/amount.d.ts.map +0 -1
  242. package/es/layouts/bankcard.d.ts.map +0 -1
  243. package/es/layouts/email.d.ts.map +0 -1
  244. package/es/layouts/idcard.d.ts.map +0 -1
  245. package/es/layouts/index.d.ts.map +0 -1
  246. package/es/layouts/license-plate.d.ts.map +0 -1
  247. package/es/layouts/number.d.ts.map +0 -1
  248. package/es/layouts/phone.d.ts.map +0 -1
  249. package/es/layouts/punct.d.ts.map +0 -1
  250. package/es/layouts/qwerty.d.ts.map +0 -1
  251. package/es/layouts/search.d.ts.map +0 -1
  252. package/es/layouts/symbol.d.ts.map +0 -1
  253. package/es/layouts/url.d.ts.map +0 -1
  254. package/es/layouts/verifycode.d.ts.map +0 -1
  255. package/es/managers/index.d.ts.map +0 -1
  256. package/es/managers/keyboard-manager.d.ts.map +0 -1
  257. package/es/managers/keyboard-renderer.d.ts.map +0 -1
  258. package/es/managers/position-manager.d.ts.map +0 -1
  259. package/es/themes/index.d.ts.map +0 -1
  260. package/es/themes/presets.d.ts.map +0 -1
  261. package/es/themes/types.d.ts.map +0 -1
  262. package/es/types/index.d.ts.map +0 -1
  263. package/es/types/keyboard.d.ts.map +0 -1
  264. package/es/types/position.d.ts.map +0 -1
  265. package/es/utils/device.d.ts.map +0 -1
  266. package/es/utils/dom.d.ts.map +0 -1
  267. package/es/utils/event-emitter.d.ts.map +0 -1
  268. package/es/utils/formatters.d.ts.map +0 -1
  269. package/es/utils/index.d.ts.map +0 -1
  270. package/es/utils/random.d.ts.map +0 -1
  271. package/esm/icons.d.ts.map +0 -1
  272. package/esm/index.d.ts.map +0 -1
  273. package/esm/layouts/amount.d.ts.map +0 -1
  274. package/esm/layouts/bankcard.d.ts.map +0 -1
  275. package/esm/layouts/email.d.ts +0 -11
  276. package/esm/layouts/email.d.ts.map +0 -1
  277. package/esm/layouts/idcard.d.ts.map +0 -1
  278. package/esm/layouts/index.d.ts.map +0 -1
  279. package/esm/layouts/license-plate.d.ts.map +0 -1
  280. package/esm/layouts/number.d.ts.map +0 -1
  281. package/esm/layouts/phone.d.ts +0 -15
  282. package/esm/layouts/phone.d.ts.map +0 -1
  283. package/esm/layouts/punct.d.ts +0 -7
  284. package/esm/layouts/punct.d.ts.map +0 -1
  285. package/esm/layouts/qwerty.d.ts.map +0 -1
  286. package/esm/layouts/search.d.ts.map +0 -1
  287. package/esm/layouts/symbol.d.ts.map +0 -1
  288. package/esm/layouts/url.d.ts.map +0 -1
  289. package/esm/layouts/verifycode.d.ts +0 -15
  290. package/esm/layouts/verifycode.d.ts.map +0 -1
  291. package/esm/managers/index.d.ts +0 -8
  292. package/esm/managers/index.d.ts.map +0 -1
  293. package/esm/managers/keyboard-manager.d.ts +0 -130
  294. package/esm/managers/keyboard-manager.d.ts.map +0 -1
  295. package/esm/managers/keyboard-renderer.d.ts +0 -115
  296. package/esm/managers/keyboard-renderer.d.ts.map +0 -1
  297. package/esm/managers/position-manager.d.ts +0 -72
  298. package/esm/managers/position-manager.d.ts.map +0 -1
  299. package/esm/themes/index.d.ts +0 -70
  300. package/esm/themes/index.d.ts.map +0 -1
  301. package/esm/themes/presets.d.ts +0 -39
  302. package/esm/themes/presets.d.ts.map +0 -1
  303. package/esm/themes/types.d.ts.map +0 -1
  304. package/esm/themes/types.js +0 -10
  305. package/esm/types/index.d.ts.map +0 -1
  306. package/esm/types/index.js +0 -10
  307. package/esm/types/keyboard.d.ts.map +0 -1
  308. package/esm/types/keyboard.js +0 -10
  309. package/esm/types/position.d.ts +0 -139
  310. package/esm/types/position.d.ts.map +0 -1
  311. package/esm/types/position.js +0 -10
  312. package/esm/utils/device.d.ts.map +0 -1
  313. package/esm/utils/dom.d.ts.map +0 -1
  314. package/esm/utils/event-emitter.d.ts.map +0 -1
  315. package/esm/utils/formatters.d.ts.map +0 -1
  316. package/esm/utils/index.d.ts.map +0 -1
  317. package/esm/utils/index.js +0 -16
  318. package/esm/utils/random.d.ts.map +0 -1
  319. package/lib/icons.d.ts +0 -58
  320. package/lib/index.d.ts +0 -12
  321. package/lib/layouts/amount.d.ts +0 -14
  322. package/lib/layouts/bankcard.d.ts +0 -15
  323. package/lib/layouts/idcard.d.ts +0 -16
  324. package/lib/layouts/index.d.ts +0 -54
  325. package/lib/layouts/license-plate.d.ts +0 -35
  326. package/lib/layouts/number.d.ts +0 -14
  327. package/lib/layouts/qwerty.d.ts +0 -10
  328. package/lib/layouts/search.d.ts +0 -7
  329. package/lib/layouts/symbol.d.ts +0 -14
  330. package/lib/layouts/url.d.ts +0 -7
  331. package/lib/themes/types.d.ts +0 -118
  332. package/lib/types/index.d.ts +0 -7
  333. package/lib/types/keyboard.d.ts +0 -253
  334. package/lib/utils/device.d.ts +0 -45
  335. package/lib/utils/dom.d.ts +0 -69
  336. package/lib/utils/event-emitter.d.ts +0 -52
  337. package/lib/utils/formatters.d.ts +0 -51
  338. package/lib/utils/index.d.ts +0 -10
  339. package/lib/utils/random.d.ts +0 -71
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-renderer.js","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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"}
1
+ {"version":3,"file":"keyboard-renderer.js","names":[],"sources":["../../src/managers/keyboard-renderer.ts"],"sourcesContent":["/**\r\n * 键盘 DOM 渲染器\r\n * 框架无关的键盘渲染器,用于原生 JS 项目\r\n * @module managers/keyboard-renderer\r\n */\r\n\r\nimport type { ThemeConfig } from '../themes/types'\r\nimport type {\r\n KeyboardConfig,\r\n KeyboardOptions,\r\n KeyboardType,\r\n KeyDefinition,\r\n PopupPositionConfig,\r\n} from '../types'\r\nimport { defaultTheme, getTheme, themeToCSS } from '../themes'\r\nimport { isMobile } from '../utils/device'\r\nimport { onClickOutside, sanitizeSvgIcon } from '../utils/dom'\r\nimport { KeyboardManager } from './keyboard-manager'\r\nimport { PositionManager } from './position-manager'\r\n\r\n/**\r\n * 渲染器配置\r\n */\r\nexport interface KeyboardRendererOptions extends KeyboardOptions {\r\n /** 挂载容器(默认 document.body) */\r\n container?: HTMLElement\r\n /** 主题名称或主题配置 */\r\n theme?: string | ThemeConfig\r\n /** 弹出层配置 */\r\n position?: PopupPositionConfig\r\n /** 点击外部关闭 */\r\n closeOnClickOutside?: boolean\r\n /** 确认回调 */\r\n onConfirm?: (value: string) => void\r\n /** 输入变化回调 */\r\n onInput?: (value: string) => void\r\n /** 关闭回调 */\r\n onClose?: () => void\r\n}\r\n\r\ninterface RendererStyleEntry {\r\n element: HTMLStyleElement\r\n users: number\r\n}\r\n\r\nconst rendererStyles = new WeakMap<Document, RendererStyleEntry>()\r\n\r\n/**\r\n * 键盘 DOM 渲染器\r\n * 提供完整的键盘 DOM 创建、事件绑定、位置管理\r\n */\r\nexport class KeyboardRenderer {\r\n /** 键盘管理器 */\r\n readonly manager: KeyboardManager\r\n\r\n /** 位置管理器 */\r\n private positionManager: PositionManager\r\n\r\n /** 配置 */\r\n private options: KeyboardRendererOptions\r\n\r\n /** DOM 元素 */\r\n private rootEl: HTMLElement | null = null\r\n private maskEl: HTMLElement | null = null\r\n private keyboardEl: HTMLElement | null = null\r\n\r\n /** 挂载容器 */\r\n private container: HTMLElement\r\n\r\n /** 主题配置 */\r\n private theme: ThemeConfig\r\n\r\n /** 当前目标元素 */\r\n private targetEl: HTMLInputElement | HTMLTextAreaElement | null = null\r\n\r\n /** 清理函数 */\r\n private cleanupFns: Array<() => void> = []\r\n private inputCleanupFns = new Set<() => void>()\r\n private styleDocument: Document | null = null\r\n\r\n /** 是否已销毁 */\r\n private destroyed = false\r\n\r\n constructor(options: KeyboardRendererOptions = {}) {\r\n this.options = options\r\n const container =\r\n options.container ??\r\n (typeof document !== 'undefined' ? document.body : null)\r\n if (!container) {\r\n throw new Error(\r\n '[KeyboardRenderer] A DOM container is required in non-browser environments'\r\n )\r\n }\r\n this.container = container\r\n this.manager = new KeyboardManager(options)\r\n this.positionManager = new PositionManager(options.position)\r\n\r\n // 解析主题\r\n if (typeof options.theme === 'string') {\r\n this.theme = getTheme(options.theme)\r\n } else if (options.theme) {\r\n this.theme = options.theme\r\n } else {\r\n this.theme = defaultTheme\r\n }\r\n\r\n // 绑定事件\r\n this.setupEvents()\r\n }\r\n\r\n /**\r\n * 设置事件监听\r\n */\r\n private setupEvents(): void {\r\n this.manager.on('stateChange', state => {\r\n if (state.visible) {\r\n this.render()\r\n } else {\r\n this.unmount()\r\n }\r\n })\r\n\r\n this.manager.on('input', ({ value }) => {\r\n this.options.onInput?.(value)\r\n })\r\n\r\n this.manager.on('confirm', ({ value }) => {\r\n this.options.onConfirm?.(value)\r\n })\r\n }\r\n\r\n /**\r\n * 打开键盘\r\n */\r\n open(target?: HTMLInputElement | HTMLTextAreaElement): void {\r\n if (this.destroyed) return\r\n this.targetEl = target ?? null\r\n this.manager.show(target)\r\n }\r\n\r\n /**\r\n * 关闭键盘\r\n */\r\n close(): void {\r\n if (this.destroyed) return\r\n const wasVisible = this.manager.getState().visible\r\n this.manager.hide()\r\n if (wasVisible) this.options.onClose?.()\r\n }\r\n\r\n /**\r\n * 切换键盘类型\r\n */\r\n switchType(type: KeyboardType | string): void {\r\n if (this.destroyed) return\r\n this.manager.switchType(type)\r\n }\r\n\r\n /**\r\n * 设置主题\r\n */\r\n setTheme(theme: string | ThemeConfig): void {\r\n if (this.destroyed) return\r\n if (typeof theme === 'string') {\r\n this.theme = getTheme(theme)\r\n } else {\r\n this.theme = theme\r\n }\r\n if (this.manager.getState().visible) {\r\n this.render()\r\n }\r\n }\r\n\r\n /**\r\n * 获取当前值\r\n */\r\n getValue(): string {\r\n return this.manager.getState().value\r\n }\r\n\r\n /**\r\n * 设置值\r\n */\r\n setValue(value: string): void {\r\n if (this.destroyed) return\r\n this.manager.setValue(value)\r\n }\r\n\r\n /**\r\n * 渲染键盘 DOM\r\n */\r\n private render(): void {\r\n if (this.destroyed) return\r\n const layout = this.manager.getLayout()\r\n if (!layout) return\r\n\r\n // 清理旧的 DOM\r\n this.unmount()\r\n\r\n // 创建根元素\r\n this.rootEl = document.createElement('div')\r\n this.rootEl.className = 'lk-renderer-root'\r\n\r\n const isMobileDevice = isMobile()\r\n\r\n // 移动端创建遮罩\r\n if (isMobileDevice) {\r\n this.maskEl = document.createElement('div')\r\n this.maskEl.className = 'lk-mask'\r\n this.maskEl.addEventListener('click', () => {\r\n if (this.positionManager.isMaskClosable()) this.close()\r\n })\r\n this.rootEl.appendChild(this.maskEl)\r\n }\r\n\r\n // 创建键盘容器\r\n this.keyboardEl = document.createElement('div')\r\n this.keyboardEl.className = `lk-popup lk-popup--${isMobileDevice ? 'mobile' : 'desktop'}`\r\n\r\n // 应用主题 CSS 变量\r\n const cssVars = themeToCSS(this.theme)\r\n const keyboardElement = this.keyboardEl\r\n if (!keyboardElement) return\r\n Object.entries(cssVars).forEach(([key, value]) => {\r\n if (value) {\r\n keyboardElement.style.setProperty(key, value)\r\n }\r\n })\r\n\r\n // 渲染键盘内容\r\n const contentEl = this.renderKeyboard(layout)\r\n this.keyboardEl.appendChild(contentEl)\r\n\r\n this.rootEl.appendChild(this.keyboardEl)\r\n this.container.appendChild(this.rootEl)\r\n\r\n // 计算位置\r\n if (!isMobileDevice && this.targetEl) {\r\n const rect = this.keyboardEl.getBoundingClientRect()\r\n const result = this.positionManager.calculate(this.targetEl, {\r\n width: rect.width,\r\n height: rect.height,\r\n })\r\n this.keyboardEl.style.position = 'fixed'\r\n this.keyboardEl.style.left = `${result.position.x}px`\r\n this.keyboardEl.style.top = `${result.position.y}px`\r\n this.keyboardEl.style.zIndex = '10000'\r\n } else if (isMobileDevice) {\r\n this.keyboardEl.style.position = 'fixed'\r\n this.keyboardEl.style.left = '0'\r\n this.keyboardEl.style.right = '0'\r\n this.keyboardEl.style.bottom = '0'\r\n this.keyboardEl.style.zIndex = '10000'\r\n }\r\n\r\n // 添加动画类\r\n requestAnimationFrame(() => {\r\n this.keyboardEl?.classList.add('lk-popup--visible')\r\n this.maskEl?.classList.add('lk-mask--visible')\r\n })\r\n\r\n // 点击外部关闭\r\n if (this.options.closeOnClickOutside !== false && this.keyboardEl) {\r\n const cleanup = onClickOutside(this.keyboardEl, event => {\r\n if (\r\n this.targetEl &&\r\n event.target instanceof Node &&\r\n this.targetEl.contains(event.target)\r\n )\r\n return\r\n this.close()\r\n })\r\n this.cleanupFns.push(cleanup)\r\n }\r\n\r\n // 注入样式\r\n this.injectStyles()\r\n }\r\n\r\n /**\r\n * 渲染键盘内容\r\n */\r\n private renderKeyboard(layout: KeyboardConfig): HTMLElement {\r\n const el = document.createElement('div')\r\n el.className = 'lk-keyboard'\r\n\r\n const state = this.manager.getState()\r\n\r\n layout.rows.forEach(row => {\r\n const rowEl = document.createElement('div')\r\n rowEl.className = `lk-row ${row.className || ''}`\r\n\r\n const isNumpad = row.className === 'numpad-row'\r\n\r\n row.keys.forEach(key => {\r\n const wrapperEl = document.createElement('div')\r\n wrapperEl.className = 'lk-key-wrapper'\r\n\r\n if (isNumpad) {\r\n wrapperEl.style.flex = String(key.width ?? 1)\r\n } else if (key.width) {\r\n const baseW = 36\r\n const gap = this.theme.sizes?.keyGap ?? 6\r\n const w = baseW * key.width + (key.width - 1) * gap\r\n wrapperEl.style.width = `${w}px`\r\n }\r\n\r\n const btnEl = document.createElement('button')\r\n btnEl.type = 'button'\r\n btnEl.className = this.getKeyClass(key, state.isUpperCase)\r\n\r\n if (key.disabled) {\r\n btnEl.disabled = true\r\n }\r\n\r\n // 内容\r\n if (key.icon) {\r\n const safeIcon = sanitizeSvgIcon(key.icon)\r\n if (safeIcon) btnEl.innerHTML = safeIcon\r\n else btnEl.textContent = key.icon\r\n } else {\r\n const label =\r\n typeof key.label === 'string'\r\n ? key.label\r\n : state.isUpperCase\r\n ? key.label.upper\r\n : key.label.lower\r\n btnEl.textContent = label\r\n }\r\n\r\n // 事件\r\n const handler = (e: Event) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n if (!key.disabled) {\r\n this.manager.handleKey(key)\r\n }\r\n }\r\n\r\n btnEl.addEventListener('mousedown', handler)\r\n btnEl.addEventListener('touchstart', handler, { passive: false })\r\n\r\n wrapperEl.appendChild(btnEl)\r\n rowEl.appendChild(wrapperEl)\r\n })\r\n\r\n el.appendChild(rowEl)\r\n })\r\n\r\n return el\r\n }\r\n\r\n /**\r\n * 获取按键 CSS 类名\r\n */\r\n private getKeyClass(key: KeyDefinition, isUpperCase: boolean): string {\r\n const classes = ['lk-key', `lk-key--${key.type}`]\r\n if (key.className) classes.push(key.className)\r\n if (key.disabled) classes.push('lk-key--disabled')\r\n if (key.type === 'shift' && isUpperCase) classes.push('lk-key--active')\r\n return classes.join(' ')\r\n }\r\n\r\n /**\r\n * 卸载 DOM\r\n */\r\n private unmount(): void {\r\n this.cleanupFns.forEach(fn => fn())\r\n this.cleanupFns = []\r\n\r\n if (this.rootEl && this.rootEl.parentNode) {\r\n this.rootEl.parentNode.removeChild(this.rootEl)\r\n }\r\n this.rootEl = null\r\n this.maskEl = null\r\n this.keyboardEl = null\r\n }\r\n\r\n /**\r\n * 注入样式(单例)\r\n */\r\n private injectStyles(): void {\r\n const styleDocument = this.container.ownerDocument\r\n if (this.styleDocument === styleDocument) return\r\n const existing = rendererStyles.get(styleDocument)\r\n if (existing) {\r\n existing.users += 1\r\n this.styleDocument = styleDocument\r\n return\r\n }\r\n\r\n const style = styleDocument.createElement('style')\r\n style.textContent = `\r\n .lk-renderer-root { font-family: var(--ld-font-family); }\r\n .lk-mask { position: fixed; inset: 0; z-index: 9999; background: var(--keyboard-mask-bg); opacity: 0; transition: opacity var(--ld-duration-fast); }\r\n .lk-mask--visible { opacity: 1; }\r\n .lk-popup { overflow: hidden; background: var(--keyboard-bg); box-shadow: var(--ld-shadow-md, none); transform: translateY(var(--ld-px-20)); opacity: 0; transition: transform var(--ld-duration-fast), opacity var(--ld-duration-fast); }\r\n .lk-popup--visible { transform: translateY(0); opacity: 1; }\r\n .lk-popup--desktop { min-width: var(--ld-px-280); border-radius: var(--ld-radius-lg); box-shadow: var(--ld-shadow-lg, none); }\r\n .lk-popup--mobile { border-top-left-radius: var(--ld-radius-lg); border-top-right-radius: var(--ld-radius-lg); padding-bottom: env(safe-area-inset-bottom, var(--ld-px-0)); }\r\n .lk-keyboard { display: flex; flex-direction: column; gap: var(--keyboard-key-gap); padding: var(--keyboard-padding); user-select: none; -webkit-user-select: none; font-family: var(--ld-font-family); }\r\n .lk-row { display: flex; justify-content: center; gap: var(--keyboard-key-gap); padding: 0 var(--ld-px-2); }\r\n .lk-row.numpad-row { justify-content: stretch; }\r\n .lk-row.numpad-row .lk-key-wrapper { flex: 1; }\r\n .lk-key-wrapper { position: relative; display: inline-flex; }\r\n .lk-key { display: inline-flex; align-items: center; justify-content: center; width: 100%; min-width: var(--ld-px-32); height: var(--keyboard-key-height); padding: 0 var(--ld-spacing-xs); border: 0; border-radius: var(--keyboard-key-radius); background: var(--keyboard-key-bg); color: var(--keyboard-key-color); font-size: var(--keyboard-font-size); font-weight: 500; cursor: pointer; touch-action: manipulation; box-shadow: var(--keyboard-key-shadow); transition: background-color var(--ld-duration-fast), transform var(--ld-duration-fast); }\r\n .lk-key:active { background: var(--keyboard-key-active-bg); transform: scale(0.95); }\r\n .lk-key--disabled { opacity: 0.4; cursor: not-allowed; }\r\n .lk-key--disabled:active { transform: none; background: var(--keyboard-key-bg); }\r\n .lk-key--backspace, .lk-key--shift, .lk-key--tab, .lk-key--clear, .lk-key--close { background: var(--keyboard-fn-key-bg); color: var(--keyboard-fn-key-color); font-size: var(--keyboard-fn-font-size); }\r\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); }\r\n .lk-key--active { background: var(--keyboard-key-active-bg) !important; color: var(--ld-text-inverse) !important; }\r\n .lk-key--done, .lk-key--enter { background: var(--keyboard-primary-bg); color: var(--keyboard-primary-color); font-size: var(--keyboard-fn-font-size); }\r\n .lk-key--done:active, .lk-key--enter:active { background: var(--keyboard-primary-active-bg); }\r\n .lk-key--space { background: var(--keyboard-key-bg); }\r\n .lk-key svg { pointer-events: none; }\r\n `\r\n style.id = 'lk-renderer-styles'\r\n styleDocument.head?.appendChild(style)\r\n rendererStyles.set(styleDocument, { element: style, users: 1 })\r\n this.styleDocument = styleDocument\r\n }\r\n\r\n /**\r\n * 绑定到输入框(聚焦时自动打开键盘)\r\n */\r\n bindInput(input: HTMLInputElement | HTMLTextAreaElement): () => void {\r\n if (this.destroyed) return () => undefined\r\n const handleFocus = () => this.open(input)\r\n input.addEventListener('focus', handleFocus)\r\n\r\n // 阻止移动端原生键盘\r\n input.setAttribute('readonly', 'readonly')\r\n input.style.caretColor = 'transparent'\r\n\r\n let cleaned = false\r\n const cleanup = () => {\r\n if (cleaned) return\r\n cleaned = true\r\n input.removeEventListener('focus', handleFocus)\r\n input.removeAttribute('readonly')\r\n input.style.caretColor = ''\r\n }\r\n this.inputCleanupFns.add(cleanup)\r\n return cleanup\r\n }\r\n\r\n /**\r\n * 销毁渲染器\r\n */\r\n destroy(): void {\r\n if (this.destroyed) return\r\n this.destroyed = true\r\n this.unmount()\r\n this.inputCleanupFns.forEach(cleanup => cleanup())\r\n this.inputCleanupFns.clear()\r\n this.manager.destroy()\r\n\r\n // 移除样式\r\n if (this.styleDocument) {\r\n const entry = rendererStyles.get(this.styleDocument)\r\n if (entry) {\r\n entry.users -= 1\r\n if (entry.users <= 0) {\r\n entry.element.remove()\r\n rendererStyles.delete(this.styleDocument)\r\n }\r\n }\r\n this.styleDocument = null\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * 创建键盘渲染器\r\n */\r\nexport function createKeyboardRenderer(\r\n options?: KeyboardRendererOptions\r\n): KeyboardRenderer {\r\n return new KeyboardRenderer(options)\r\n}\r\n"],"mappings":"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"}
@@ -22,6 +22,8 @@ export declare class PositionManager {
22
22
  * 计算弹出位置
23
23
  * @param targetElement - 目标元素(如 input)
24
24
  * @param popupSize - 弹出层尺寸
25
+ * @param popupSize.width - Popup width
26
+ * @param popupSize.height - Popup height
25
27
  * @returns 位置计算结果
26
28
  */
27
29
  calculate(targetElement: HTMLElement | null, popupSize: {
@@ -47,11 +49,11 @@ export declare class PositionManager {
47
49
  /**
48
50
  * 获取弹出层最终样式(动画结束)
49
51
  */
50
- getFinalStyles(placement: PopupPlacement): Partial<CSSStyleDeclaration>;
52
+ getFinalStyles(_placement: PopupPlacement): Partial<CSSStyleDeclaration>;
51
53
  /**
52
54
  * 获取弹出层退出样式
53
55
  */
54
- getExitStyles(placement: PopupPlacement): Partial<CSSStyleDeclaration>;
56
+ getExitStyles(_placement: PopupPlacement): Partial<CSSStyleDeclaration>;
55
57
  /**
56
58
  * 获取动画时长
57
59
  */
@@ -69,4 +71,3 @@ export declare class PositionManager {
69
71
  * 创建位置管理器实例
70
72
  */
71
73
  export declare function createPositionManager(config?: PopupPositionConfig): PositionManager;
72
- //# sourceMappingURL=position-manager.d.ts.map
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/keyboard-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:29 *
5
+ * Build time: 2024-10-12 17:29:15 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -23,29 +23,48 @@ const DEFAULT_CONFIG = {
23
23
  maskClosable: true,
24
24
  animationDuration: 300
25
25
  };
26
+ function mergeConfig(config = {}) {
27
+ return {
28
+ ...DEFAULT_CONFIG,
29
+ ...config,
30
+ offset: {
31
+ ...DEFAULT_CONFIG.offset,
32
+ ...config.offset
33
+ }
34
+ };
35
+ }
26
36
  var PositionManager = class {
27
37
  constructor(config = {}) {
28
- this.config = {
29
- ...DEFAULT_CONFIG,
30
- ...config
31
- };
38
+ this.config = mergeConfig(config);
32
39
  }
33
40
  /**
34
41
  * 更新配置
35
42
  */
36
43
  updateConfig(config) {
37
- Object.assign(this.config, config);
44
+ this.config = mergeConfig({
45
+ ...this.config,
46
+ ...config,
47
+ offset: {
48
+ ...this.config.offset,
49
+ ...config.offset
50
+ }
51
+ });
38
52
  }
39
53
  /**
40
54
  * 获取配置
41
55
  */
42
56
  getConfig() {
43
- return { ...this.config };
57
+ return {
58
+ ...this.config,
59
+ offset: { ...this.config.offset }
60
+ };
44
61
  }
45
62
  /**
46
63
  * 计算弹出位置
47
64
  * @param targetElement - 目标元素(如 input)
48
65
  * @param popupSize - 弹出层尺寸
66
+ * @param popupSize.width - Popup width
67
+ * @param popupSize.height - Popup height
49
68
  * @returns 位置计算结果
50
69
  */
51
70
  calculate(targetElement, popupSize) {
@@ -63,15 +82,17 @@ var PositionManager = class {
63
82
  */
64
83
  calculateBottomSheet(viewport, popupSize) {
65
84
  const safeAreaBottom = viewport.safeArea?.bottom ?? 0;
85
+ const maxHeight = Math.max(0, viewport.height * .6 - safeAreaBottom);
86
+ const visibleHeight = Math.min(Math.max(0, popupSize.height), maxHeight);
66
87
  return {
67
88
  position: {
68
89
  x: 0,
69
- y: viewport.height - popupSize.height - safeAreaBottom
90
+ y: Math.max(0, viewport.height - visibleHeight - safeAreaBottom)
70
91
  },
71
92
  placement: "bottom-sheet",
72
93
  flipped: false,
73
- maxHeight: viewport.height * .6,
74
- maxWidth: viewport.width,
94
+ maxHeight,
95
+ maxWidth: Math.max(0, viewport.width),
75
96
  transform: "translateY(0)"
76
97
  };
77
98
  }
@@ -81,8 +102,8 @@ var PositionManager = class {
81
102
  calculateCentered(viewport, popupSize) {
82
103
  return {
83
104
  position: {
84
- x: (viewport.width - popupSize.width) / 2,
85
- y: (viewport.height - popupSize.height) / 2
105
+ x: Math.max(0, (viewport.width - popupSize.width) / 2),
106
+ y: Math.max(0, (viewport.height - popupSize.height) / 2)
86
107
  },
87
108
  placement: "bottom",
88
109
  flipped: false
@@ -124,16 +145,16 @@ var PositionManager = class {
124
145
  }
125
146
  }
126
147
  if (autoAdjust) {
127
- if (position.x < viewportPadding) position.x = viewportPadding;
128
- if (position.x + popupSize.width > viewport.width - viewportPadding) position.x = viewport.width - popupSize.width - viewportPadding;
148
+ const maxX = Math.max(viewportPadding, viewport.width - popupSize.width - viewportPadding);
149
+ position.x = Math.min(Math.max(position.x, viewportPadding), maxX);
129
150
  }
130
151
  const maxHeight = placement === "bottom" ? viewport.height - position.y - viewportPadding : targetRect.y - viewportPadding;
131
152
  return {
132
153
  position,
133
154
  placement,
134
155
  flipped,
135
- maxHeight: Math.max(200, maxHeight),
136
- maxWidth: viewport.width - viewportPadding * 2
156
+ maxHeight: Math.max(0, maxHeight),
157
+ maxWidth: Math.max(0, viewport.width - viewportPadding * 2)
137
158
  };
138
159
  }
139
160
  /**
@@ -146,11 +167,11 @@ var PositionManager = class {
146
167
  opacity: "1"
147
168
  };
148
169
  case "bottom": return {
149
- transform: "translateY(-10px)",
170
+ transform: "translateY(calc(-1 * var(--ld-px-10)))",
150
171
  opacity: "0"
151
172
  };
152
173
  case "top": return {
153
- transform: "translateY(10px)",
174
+ transform: "translateY(var(--ld-px-10))",
154
175
  opacity: "0"
155
176
  };
156
177
  default: return { opacity: "0" };
@@ -159,7 +180,7 @@ var PositionManager = class {
159
180
  /**
160
181
  * 获取弹出层最终样式(动画结束)
161
182
  */
162
- getFinalStyles(placement) {
183
+ getFinalStyles(_placement) {
163
184
  return {
164
185
  transform: "translateY(0)",
165
186
  opacity: "1"
@@ -168,8 +189,8 @@ var PositionManager = class {
168
189
  /**
169
190
  * 获取弹出层退出样式
170
191
  */
171
- getExitStyles(placement) {
172
- return this.getInitialStyles(placement);
192
+ getExitStyles(_placement) {
193
+ return this.getInitialStyles(_placement);
173
194
  }
174
195
  /**
175
196
  * 获取动画时长
@@ -1 +1 @@
1
- {"version":3,"file":"position-manager.js","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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"}
1
+ {"version":3,"file":"position-manager.js","names":[],"sources":["../../src/managers/position-manager.ts"],"sourcesContent":["/**\r\n * 弹出位置管理器\r\n * @module managers/position-manager\r\n */\r\n\r\nimport type {\r\n BoundingRect,\r\n PopupPlacement,\r\n PopupPositionConfig,\r\n PopupPositionResult,\r\n Position,\r\n} from '../types'\r\nimport { getViewportInfo, isMobile } from '../utils/device'\r\nimport { getBoundingRect } from '../utils/dom'\r\n\r\n/**\r\n * 默认位置配置\r\n */\r\nconst DEFAULT_CONFIG: Required<PopupPositionConfig> = {\r\n offset: { x: 0, y: 8 },\r\n autoAdjust: true,\r\n viewportPadding: 8,\r\n desktopPlacement: 'bottom',\r\n mobilePlacement: 'bottom-sheet',\r\n showMask: true,\r\n maskClosable: true,\r\n animationDuration: 300,\r\n}\r\n\r\nfunction mergeConfig(\r\n config: PopupPositionConfig = {}\r\n): Required<PopupPositionConfig> {\r\n return {\r\n ...DEFAULT_CONFIG,\r\n ...config,\r\n offset: {\r\n ...DEFAULT_CONFIG.offset,\r\n ...config.offset,\r\n },\r\n }\r\n}\r\n\r\n/**\r\n * 位置管理器类\r\n * 计算键盘弹出层的位置,支持 PC 和移动端自适应\r\n */\r\nexport class PositionManager {\r\n private config: Required<PopupPositionConfig>\r\n\r\n constructor(config: PopupPositionConfig = {}) {\r\n this.config = mergeConfig(config)\r\n }\r\n\r\n /**\r\n * 更新配置\r\n */\r\n updateConfig(config: Partial<PopupPositionConfig>): void {\r\n this.config = mergeConfig({\r\n ...this.config,\r\n ...config,\r\n offset: { ...this.config.offset, ...config.offset },\r\n })\r\n }\r\n\r\n /**\r\n * 获取配置\r\n */\r\n getConfig(): Readonly<Required<PopupPositionConfig>> {\r\n return { ...this.config, offset: { ...this.config.offset } }\r\n }\r\n\r\n /**\r\n * 计算弹出位置\r\n * @param targetElement - 目标元素(如 input)\r\n * @param popupSize - 弹出层尺寸\r\n * @param popupSize.width - Popup width\r\n * @param popupSize.height - Popup height\r\n * @returns 位置计算结果\r\n */\r\n calculate(\r\n targetElement: HTMLElement | null,\r\n popupSize: { width: number; height: number }\r\n ): PopupPositionResult {\r\n const viewport = getViewportInfo()\r\n const isMobileDevice = isMobile()\r\n\r\n // 移动端使用 bottom-sheet\r\n if (isMobileDevice && this.config.mobilePlacement === 'bottom-sheet') {\r\n return this.calculateBottomSheet(viewport, popupSize)\r\n }\r\n\r\n // PC 端或移动端非 bottom-sheet\r\n if (!targetElement) {\r\n // 无目标元素,居中显示\r\n return this.calculateCentered(viewport, popupSize)\r\n }\r\n\r\n const targetRect = getBoundingRect(targetElement)\r\n const placement = isMobileDevice\r\n ? this.config.mobilePlacement\r\n : this.config.desktopPlacement\r\n\r\n // bottom-sheet 已经在前面处理,这里只会是 'bottom' 或 'top'\r\n const relativePlace = placement === 'bottom-sheet' ? 'bottom' : placement\r\n return this.calculateRelativeToTarget(\r\n targetRect,\r\n popupSize,\r\n viewport,\r\n relativePlace\r\n )\r\n }\r\n\r\n /**\r\n * 计算 bottom-sheet 位置(移动端从底部滑出)\r\n */\r\n private calculateBottomSheet(\r\n viewport: {\r\n width: number\r\n height: number\r\n scrollY: number\r\n safeArea?: { bottom: number }\r\n },\r\n popupSize: { width: number; height: number }\r\n ): PopupPositionResult {\r\n const safeAreaBottom = viewport.safeArea?.bottom ?? 0\r\n const maxHeight = Math.max(0, viewport.height * 0.6 - safeAreaBottom)\r\n const visibleHeight = Math.min(Math.max(0, popupSize.height), maxHeight)\r\n\r\n return {\r\n position: {\r\n x: 0,\r\n y: Math.max(0, viewport.height - visibleHeight - safeAreaBottom),\r\n },\r\n placement: 'bottom-sheet',\r\n flipped: false,\r\n maxHeight,\r\n maxWidth: Math.max(0, viewport.width),\r\n transform: 'translateY(0)',\r\n }\r\n }\r\n\r\n /**\r\n * 计算居中位置\r\n */\r\n private calculateCentered(\r\n viewport: { width: number; height: number },\r\n popupSize: { width: number; height: number }\r\n ): PopupPositionResult {\r\n return {\r\n position: {\r\n x: Math.max(0, (viewport.width - popupSize.width) / 2),\r\n y: Math.max(0, (viewport.height - popupSize.height) / 2),\r\n },\r\n placement: 'bottom',\r\n flipped: false,\r\n }\r\n }\r\n\r\n /**\r\n * 计算相对于目标元素的位置\r\n */\r\n private calculateRelativeToTarget(\r\n targetRect: BoundingRect,\r\n popupSize: { width: number; height: number },\r\n viewport: {\r\n width: number\r\n height: number\r\n scrollX: number\r\n scrollY: number\r\n },\r\n preferredPlacement: Exclude<PopupPlacement, 'bottom-sheet'>\r\n ): PopupPositionResult {\r\n const { offset, viewportPadding, autoAdjust } = this.config\r\n\r\n let position: Position\r\n let placement = preferredPlacement\r\n let flipped = false\r\n\r\n // 计算 bottom 位置(左对齐)\r\n const bottomPosition: Position = {\r\n x: targetRect.x + (offset.x ?? 0),\r\n y: targetRect.y + targetRect.height + (offset.y ?? 8),\r\n }\r\n\r\n // 计算 top 位置(左对齐)\r\n const topPosition: Position = {\r\n x: targetRect.x + (offset.x ?? 0),\r\n y: targetRect.y - popupSize.height - (offset.y ?? 8),\r\n }\r\n\r\n if (preferredPlacement === 'bottom') {\r\n position = bottomPosition\r\n\r\n // 检查是否需要翻转到顶部\r\n if (\r\n autoAdjust &&\r\n position.y + popupSize.height > viewport.height - viewportPadding\r\n ) {\r\n // 检查顶部是否有足够空间\r\n if (topPosition.y >= viewportPadding) {\r\n position = topPosition\r\n placement = 'top'\r\n flipped = true\r\n }\r\n }\r\n } else {\r\n position = topPosition\r\n\r\n // 检查是否需要翻转到底部\r\n if (autoAdjust && position.y < viewportPadding) {\r\n // 检查底部是否有足够空间\r\n if (\r\n bottomPosition.y + popupSize.height <=\r\n viewport.height - viewportPadding\r\n ) {\r\n position = bottomPosition\r\n placement = 'bottom'\r\n flipped = true\r\n }\r\n }\r\n }\r\n\r\n // 水平方向调整,确保不超出视口\r\n if (autoAdjust) {\r\n const maxX = Math.max(\r\n viewportPadding,\r\n viewport.width - popupSize.width - viewportPadding\r\n )\r\n position.x = Math.min(Math.max(position.x, viewportPadding), maxX)\r\n }\r\n\r\n // 计算最大可用高度\r\n const maxHeight =\r\n placement === 'bottom'\r\n ? viewport.height - position.y - viewportPadding\r\n : targetRect.y - viewportPadding\r\n\r\n return {\r\n position,\r\n placement,\r\n flipped,\r\n maxHeight: Math.max(0, maxHeight),\r\n maxWidth: Math.max(0, viewport.width - viewportPadding * 2),\r\n }\r\n }\r\n\r\n /**\r\n * 获取弹出层初始样式(用于动画)\r\n */\r\n getInitialStyles(placement: PopupPlacement): Partial<CSSStyleDeclaration> {\r\n switch (placement) {\r\n case 'bottom-sheet':\r\n return {\r\n transform: 'translateY(100%)',\r\n opacity: '1',\r\n }\r\n case 'bottom':\r\n return {\r\n transform: 'translateY(calc(-1 * var(--ld-px-10)))',\r\n opacity: '0',\r\n }\r\n case 'top':\r\n return {\r\n transform: 'translateY(var(--ld-px-10))',\r\n opacity: '0',\r\n }\r\n default:\r\n return {\r\n opacity: '0',\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * 获取弹出层最终样式(动画结束)\r\n */\r\n getFinalStyles(_placement: PopupPlacement): Partial<CSSStyleDeclaration> {\r\n return {\r\n transform: 'translateY(0)',\r\n opacity: '1',\r\n }\r\n }\r\n\r\n /**\r\n * 获取弹出层退出样式\r\n */\r\n getExitStyles(_placement: PopupPlacement): Partial<CSSStyleDeclaration> {\r\n return this.getInitialStyles(_placement)\r\n }\r\n\r\n /**\r\n * 获取动画时长\r\n */\r\n getAnimationDuration(): number {\r\n return this.config.animationDuration\r\n }\r\n\r\n /**\r\n * 检查是否显示遮罩\r\n */\r\n shouldShowMask(): boolean {\r\n return isMobile() && this.config.showMask\r\n }\r\n\r\n /**\r\n * 检查遮罩是否可点击关闭\r\n */\r\n isMaskClosable(): boolean {\r\n return this.config.maskClosable\r\n }\r\n}\r\n\r\n/**\r\n * 创建位置管理器实例\r\n */\r\nexport function createPositionManager(\r\n config?: PopupPositionConfig\r\n): PositionManager {\r\n return new PositionManager(config)\r\n}\r\n"],"mappings":"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"}
@@ -3,8 +3,8 @@
3
3
  * @module themes
4
4
  */
5
5
  import type { ThemeConfig, ThemeCSSVariables, ThemeName } from './types';
6
- export * from './types';
7
6
  export * from './presets';
7
+ export * from './types';
8
8
  /**
9
9
  * 获取主题配置
10
10
  * @param name - 主题名称
@@ -67,4 +67,3 @@ export declare function mergeTheme(base: ThemeConfig, overrides: Partial<ThemeCo
67
67
  * @param customization - 自定义配置
68
68
  */
69
69
  export declare function createTheme(name: string, baseTheme: ThemeName | string, customization: Partial<Omit<ThemeConfig, 'name'>>): ThemeConfig;
70
- //# sourceMappingURL=index.d.ts.map
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/keyboard-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:29 *
5
+ * Build time: 2024-10-12 17:29:15 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -49,6 +49,7 @@ function themeToCSS(theme) {
49
49
  "--keyboard-fn-font-size": `${theme.sizes.fnFontSize}px`,
50
50
  "--keyboard-padding": `${theme.sizes.padding}px`,
51
51
  "--keyboard-key-shadow": theme.effects.keyShadow,
52
+ "--keyboard-mask-bg": "color-mix(in srgb, var(--ld-black) 40%, var(--ld-transparent))",
52
53
  "--keyboard-transition-duration": `${theme.effects.transitionDuration}ms`,
53
54
  "--keyboard-border-color": theme.colors.borderColor,
54
55
  "--keyboard-divider-color": theme.colors.dividerColor
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/themes/index.ts"],"sourcesContent":["/**\n * 主题管理模块\n * @module themes\n */\n\nimport type { ThemeConfig, ThemeCSSVariables, ThemeName } from './types'\nimport { defaultTheme, presetThemes } from './presets'\n\nexport * from './types'\nexport * from './presets'\n\n/**\n * 自定义主题存储\n */\nconst customThemes: Map<string, ThemeConfig> = new Map()\n\n/**\n * 当前激活的主题\n */\nlet currentTheme: ThemeConfig = defaultTheme\n\n/**\n * 获取主题配置\n * @param name - 主题名称\n * @returns 主题配置,如果不存在则返回默认主题\n */\nexport function getTheme(name: ThemeName | string): ThemeConfig {\n return customThemes.get(name) || presetThemes[name] || defaultTheme\n}\n\n/**\n * 注册自定义主题\n * @param theme - 主题配置\n */\nexport function registerTheme(theme: ThemeConfig): void {\n customThemes.set(theme.name, theme)\n}\n\n/**\n * 获取所有可用主题\n * @returns 主题名称列表\n */\nexport function getAvailableThemes(): string[] {\n return [...Object.keys(presetThemes), ...customThemes.keys()]\n}\n\n/**\n * 获取所有主题配置\n * @returns 主题配置列表\n */\nexport function getAllThemes(): ThemeConfig[] {\n return [\n ...Object.values(presetThemes),\n ...customThemes.values(),\n ]\n}\n\n/**\n * 设置当前主题\n * @param name - 主题名称\n */\nexport function setCurrentTheme(name: ThemeName | string): ThemeConfig {\n currentTheme = getTheme(name)\n return currentTheme\n}\n\n/**\n * 获取当前主题\n */\nexport function getCurrentTheme(): ThemeConfig {\n return currentTheme\n}\n\n/**\n * 将主题配置转换为 CSS 变量\n * @param theme - 主题配置\n * @returns CSS 变量对象\n */\nexport function themeToCSS(theme: ThemeConfig): ThemeCSSVariables {\n return {\n '--keyboard-bg': theme.colors.background,\n '--keyboard-key-bg': theme.colors.keyBackground,\n '--keyboard-key-color': theme.colors.keyColor,\n '--keyboard-key-active-bg': theme.colors.keyActiveBackground,\n '--keyboard-fn-key-bg': theme.colors.fnKeyBackground,\n '--keyboard-fn-key-color': theme.colors.fnKeyColor,\n '--keyboard-fn-key-active-bg': theme.colors.fnKeyActiveBackground,\n '--keyboard-primary-bg': theme.colors.primaryBackground,\n '--keyboard-primary-active-bg': theme.colors.primaryActiveBackground,\n '--keyboard-primary-color': theme.colors.primaryColor,\n '--keyboard-key-height': `${theme.sizes.keyHeight}px`,\n '--keyboard-key-radius': `${theme.sizes.keyRadius}px`,\n '--keyboard-key-gap': `${theme.sizes.keyGap}px`,\n '--keyboard-font-size': `${theme.sizes.fontSize}px`,\n '--keyboard-fn-font-size': `${theme.sizes.fnFontSize}px`,\n '--keyboard-padding': `${theme.sizes.padding}px`,\n '--keyboard-key-shadow': theme.effects.keyShadow,\n '--keyboard-transition-duration': `${theme.effects.transitionDuration}ms`,\n '--keyboard-border-color': theme.colors.borderColor,\n '--keyboard-divider-color': theme.colors.dividerColor,\n }\n}\n\n/**\n * 将 CSS 变量应用到元素\n * @param element - 目标元素\n * @param theme - 主题配置\n */\nexport function applyTheme(element: HTMLElement, theme: ThemeConfig): void {\n const cssVars = themeToCSS(theme)\n Object.entries(cssVars).forEach(([key, value]) => {\n if (value) {\n element.style.setProperty(key, value)\n }\n })\n\n // 添加主题类名\n element.classList.remove('keyboard-theme-light', 'keyboard-theme-dark')\n element.classList.add(theme.isDark ? 'keyboard-theme-dark' : 'keyboard-theme-light')\n element.dataset.theme = theme.name\n}\n\n/**\n * 生成主题的内联样式字符串\n * @param theme - 主题配置\n * @returns 内联样式字符串\n */\nexport function generateThemeStyle(theme: ThemeConfig): string {\n const cssVars = themeToCSS(theme)\n return Object.entries(cssVars)\n .filter(([, value]) => value)\n .map(([key, value]) => `${key}: ${value}`)\n .join('; ')\n}\n\n/**\n * 合并主题配置\n * @param base - 基础主题\n * @param overrides - 覆盖配置\n * @returns 合并后的主题\n */\nexport function mergeTheme(\n base: ThemeConfig,\n overrides: Partial<ThemeConfig>,\n): ThemeConfig {\n return {\n ...base,\n ...overrides,\n colors: { ...base.colors, ...overrides.colors },\n sizes: { ...base.sizes, ...overrides.sizes },\n effects: { ...base.effects, ...overrides.effects },\n }\n}\n\n/**\n * 创建自定义主题\n * @param name - 主题名称\n * @param baseTheme - 基础主题名称\n * @param customization - 自定义配置\n */\nexport function createTheme(\n name: string,\n baseTheme: ThemeName | string,\n customization: Partial<Omit<ThemeConfig, 'name'>>,\n): ThemeConfig {\n const base = getTheme(baseTheme)\n const theme = mergeTheme(base, {\n ...customization,\n name,\n })\n registerTheme(theme)\n return theme\n}\n"],"mappings":";;;;;;;;;;;AAcA,MAAM,+BAAyC,IAAI,IAAI;AAKvD,IAAI,eAA4B;AAOzB,SAAS,SAAS,MAAuC;CAC9D,OAAO,aAAa,IAAI,IAAI,KAAK,aAAa,SAAS;AACzD;AAMO,SAAS,cAAc,OAA0B;CACtD,aAAa,IAAI,MAAM,MAAM,KAAK;AACpC;AAMO,SAAS,qBAA+B;CAC7C,OAAO,CAAC,GAAG,OAAO,KAAK,YAAY,GAAG,GAAG,aAAa,KAAK,CAAC;AAC9D;AAMO,SAAS,eAA8B;CAC5C,OAAO,CACL,GAAG,OAAO,OAAO,YAAY,GAC7B,GAAG,aAAa,OAAO,CACzB;AACF;AAMO,SAAS,gBAAgB,MAAuC;CACrE,eAAe,SAAS,IAAI;CAC5B,OAAO;AACT;AAKO,SAAS,kBAA+B;CAC7C,OAAO;AACT;AAOO,SAAS,WAAW,OAAuC;CAChE,OAAO;EACL,iBAAiB,MAAM,OAAO;EAC9B,qBAAqB,MAAM,OAAO;EAClC,wBAAwB,MAAM,OAAO;EACrC,4BAA4B,MAAM,OAAO;EACzC,wBAAwB,MAAM,OAAO;EACrC,2BAA2B,MAAM,OAAO;EACxC,+BAA+B,MAAM,OAAO;EAC5C,yBAAyB,MAAM,OAAO;EACtC,gCAAgC,MAAM,OAAO;EAC7C,4BAA4B,MAAM,OAAO;EACzC,yBAAyB,GAAG,MAAM,MAAM,UAAS;EACjD,yBAAyB,GAAG,MAAM,MAAM,UAAS;EACjD,sBAAsB,GAAG,MAAM,MAAM,OAAM;EAC3C,wBAAwB,GAAG,MAAM,MAAM,SAAQ;EAC/C,2BAA2B,GAAG,MAAM,MAAM,WAAU;EACpD,sBAAsB,GAAG,MAAM,MAAM,QAAO;EAC5C,yBAAyB,MAAM,QAAQ;EACvC,kCAAkC,GAAG,MAAM,QAAQ,mBAAkB;EACrE,2BAA2B,MAAM,OAAO;EACxC,4BAA4B,MAAM,OAAO;CAC3C;AACF;AAOO,SAAS,WAAW,SAAsB,OAA0B;CACzE,MAAM,UAAU,WAAW,KAAK;CAChC,OAAO,QAAQ,OAAO,CAAA,CAAE,SAAS,CAAC,KAAK,WAAW;EAChD,IAAI,OACF,QAAQ,MAAM,YAAY,KAAK,KAAK;CAExC,CAAC;CAGD,QAAQ,UAAU,OAAO,wBAAwB,qBAAqB;CACtE,QAAQ,UAAU,IAAI,MAAM,SAAS,wBAAwB,sBAAsB;CACnF,QAAQ,QAAQ,QAAQ,MAAM;AAChC;AAOO,SAAS,mBAAmB,OAA4B;CAC7D,MAAM,UAAU,WAAW,KAAK;CAChC,OAAO,OAAO,QAAQ,OAAO,CAAA,CAC1B,QAAQ,GAAG,WAAW,KAAK,CAAA,CAC3B,KAAK,CAAC,KAAK,WAAW,GAAG,IAAG,IAAK,OAAO,CAAA,CACxC,KAAK,IAAI;AACd;AAQO,SAAS,WACd,MACA,WACa;CACb,OAAO;EACL,GAAG;EACH,GAAG;EACH,QAAQ;GAAE,GAAG,KAAK;GAAQ,GAAG,UAAU;EAAO;EAC9C,OAAO;GAAE,GAAG,KAAK;GAAO,GAAG,UAAU;EAAM;EAC3C,SAAS;GAAE,GAAG,KAAK;GAAS,GAAG,UAAU;EAAQ;CACnD;AACF;AAQO,SAAS,YACd,MACA,WACA,eACa;CAEb,MAAM,QAAQ,WADD,SAAS,SACG,GAAM;EAC7B,GAAG;EACH;CACF,CAAC;CACD,cAAc,KAAK;CACnB,OAAO;AACT"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/themes/index.ts"],"sourcesContent":["/**\r\n * 主题管理模块\r\n * @module themes\r\n */\r\n\r\nimport type { ThemeConfig, ThemeCSSVariables, ThemeName } from './types'\r\nimport { defaultTheme, presetThemes } from './presets'\r\n\r\nexport * from './presets'\r\nexport * from './types'\r\n\r\n/**\r\n * 自定义主题存储\r\n */\r\nconst customThemes: Map<string, ThemeConfig> = new Map()\r\n\r\n/**\r\n * 当前激活的主题\r\n */\r\nlet currentTheme: ThemeConfig = defaultTheme\r\n\r\n/**\r\n * 获取主题配置\r\n * @param name - 主题名称\r\n * @returns 主题配置,如果不存在则返回默认主题\r\n */\r\nexport function getTheme(name: ThemeName | string): ThemeConfig {\r\n return customThemes.get(name) || presetThemes[name] || defaultTheme\r\n}\r\n\r\n/**\r\n * 注册自定义主题\r\n * @param theme - 主题配置\r\n */\r\nexport function registerTheme(theme: ThemeConfig): void {\r\n customThemes.set(theme.name, theme)\r\n}\r\n\r\n/**\r\n * 获取所有可用主题\r\n * @returns 主题名称列表\r\n */\r\nexport function getAvailableThemes(): string[] {\r\n return [...Object.keys(presetThemes), ...customThemes.keys()]\r\n}\r\n\r\n/**\r\n * 获取所有主题配置\r\n * @returns 主题配置列表\r\n */\r\nexport function getAllThemes(): ThemeConfig[] {\r\n return [...Object.values(presetThemes), ...customThemes.values()]\r\n}\r\n\r\n/**\r\n * 设置当前主题\r\n * @param name - 主题名称\r\n */\r\nexport function setCurrentTheme(name: ThemeName | string): ThemeConfig {\r\n currentTheme = getTheme(name)\r\n return currentTheme\r\n}\r\n\r\n/**\r\n * 获取当前主题\r\n */\r\nexport function getCurrentTheme(): ThemeConfig {\r\n return currentTheme\r\n}\r\n\r\n/**\r\n * 将主题配置转换为 CSS 变量\r\n * @param theme - 主题配置\r\n * @returns CSS 变量对象\r\n */\r\nexport function themeToCSS(theme: ThemeConfig): ThemeCSSVariables {\r\n return {\r\n '--keyboard-bg': theme.colors.background,\r\n '--keyboard-key-bg': theme.colors.keyBackground,\r\n '--keyboard-key-color': theme.colors.keyColor,\r\n '--keyboard-key-active-bg': theme.colors.keyActiveBackground,\r\n '--keyboard-fn-key-bg': theme.colors.fnKeyBackground,\r\n '--keyboard-fn-key-color': theme.colors.fnKeyColor,\r\n '--keyboard-fn-key-active-bg': theme.colors.fnKeyActiveBackground,\r\n '--keyboard-primary-bg': theme.colors.primaryBackground,\r\n '--keyboard-primary-active-bg': theme.colors.primaryActiveBackground,\r\n '--keyboard-primary-color': theme.colors.primaryColor,\r\n '--keyboard-key-height': `${theme.sizes.keyHeight}px`,\r\n '--keyboard-key-radius': `${theme.sizes.keyRadius}px`,\r\n '--keyboard-key-gap': `${theme.sizes.keyGap}px`,\r\n '--keyboard-font-size': `${theme.sizes.fontSize}px`,\r\n '--keyboard-fn-font-size': `${theme.sizes.fnFontSize}px`,\r\n '--keyboard-padding': `${theme.sizes.padding}px`,\r\n '--keyboard-key-shadow': theme.effects.keyShadow,\r\n '--keyboard-mask-bg':\r\n 'color-mix(in srgb, var(--ld-black) 40%, var(--ld-transparent))',\r\n '--keyboard-transition-duration': `${theme.effects.transitionDuration}ms`,\r\n '--keyboard-border-color': theme.colors.borderColor,\r\n '--keyboard-divider-color': theme.colors.dividerColor,\r\n }\r\n}\r\n\r\n/**\r\n * 将 CSS 变量应用到元素\r\n * @param element - 目标元素\r\n * @param theme - 主题配置\r\n */\r\nexport function applyTheme(element: HTMLElement, theme: ThemeConfig): void {\r\n const cssVars = themeToCSS(theme)\r\n Object.entries(cssVars).forEach(([key, value]) => {\r\n if (value) {\r\n element.style.setProperty(key, value)\r\n }\r\n })\r\n\r\n // 添加主题类名\r\n element.classList.remove('keyboard-theme-light', 'keyboard-theme-dark')\r\n element.classList.add(\r\n theme.isDark ? 'keyboard-theme-dark' : 'keyboard-theme-light'\r\n )\r\n element.dataset.theme = theme.name\r\n}\r\n\r\n/**\r\n * 生成主题的内联样式字符串\r\n * @param theme - 主题配置\r\n * @returns 内联样式字符串\r\n */\r\nexport function generateThemeStyle(theme: ThemeConfig): string {\r\n const cssVars = themeToCSS(theme)\r\n return Object.entries(cssVars)\r\n .filter(([, value]) => value)\r\n .map(([key, value]) => `${key}: ${value}`)\r\n .join('; ')\r\n}\r\n\r\n/**\r\n * 合并主题配置\r\n * @param base - 基础主题\r\n * @param overrides - 覆盖配置\r\n * @returns 合并后的主题\r\n */\r\nexport function mergeTheme(\r\n base: ThemeConfig,\r\n overrides: Partial<ThemeConfig>\r\n): ThemeConfig {\r\n return {\r\n ...base,\r\n ...overrides,\r\n colors: { ...base.colors, ...overrides.colors },\r\n sizes: { ...base.sizes, ...overrides.sizes },\r\n effects: { ...base.effects, ...overrides.effects },\r\n }\r\n}\r\n\r\n/**\r\n * 创建自定义主题\r\n * @param name - 主题名称\r\n * @param baseTheme - 基础主题名称\r\n * @param customization - 自定义配置\r\n */\r\nexport function createTheme(\r\n name: string,\r\n baseTheme: ThemeName | string,\r\n customization: Partial<Omit<ThemeConfig, 'name'>>\r\n): ThemeConfig {\r\n const base = getTheme(baseTheme)\r\n const theme = mergeTheme(base, {\r\n ...customization,\r\n name,\r\n })\r\n registerTheme(theme)\r\n return theme\r\n}\r\n"],"mappings":";;;;;;;;;;;AAcA,MAAM,+BAAyC,IAAI,IAAI;AAKvD,IAAI,eAA4B;AAOzB,SAAS,SAAS,MAAuC;CAC9D,OAAO,aAAa,IAAI,IAAI,KAAK,aAAa,SAAS;AACzD;AAMO,SAAS,cAAc,OAA0B;CACtD,aAAa,IAAI,MAAM,MAAM,KAAK;AACpC;AAMO,SAAS,qBAA+B;CAC7C,OAAO,CAAC,GAAG,OAAO,KAAK,YAAY,GAAG,GAAG,aAAa,KAAK,CAAC;AAC9D;AAMO,SAAS,eAA8B;CAC5C,OAAO,CAAC,GAAG,OAAO,OAAO,YAAY,GAAG,GAAG,aAAa,OAAO,CAAC;AAClE;AAMO,SAAS,gBAAgB,MAAuC;CACrE,eAAe,SAAS,IAAI;CAC5B,OAAO;AACT;AAKO,SAAS,kBAA+B;CAC7C,OAAO;AACT;AAOO,SAAS,WAAW,OAAuC;CAChE,OAAO;EACL,iBAAiB,MAAM,OAAO;EAC9B,qBAAqB,MAAM,OAAO;EAClC,wBAAwB,MAAM,OAAO;EACrC,4BAA4B,MAAM,OAAO;EACzC,wBAAwB,MAAM,OAAO;EACrC,2BAA2B,MAAM,OAAO;EACxC,+BAA+B,MAAM,OAAO;EAC5C,yBAAyB,MAAM,OAAO;EACtC,gCAAgC,MAAM,OAAO;EAC7C,4BAA4B,MAAM,OAAO;EACzC,yBAAyB,GAAG,MAAM,MAAM,UAAS;EACjD,yBAAyB,GAAG,MAAM,MAAM,UAAS;EACjD,sBAAsB,GAAG,MAAM,MAAM,OAAM;EAC3C,wBAAwB,GAAG,MAAM,MAAM,SAAQ;EAC/C,2BAA2B,GAAG,MAAM,MAAM,WAAU;EACpD,sBAAsB,GAAG,MAAM,MAAM,QAAO;EAC5C,yBAAyB,MAAM,QAAQ;EACvC,sBACE;EACF,kCAAkC,GAAG,MAAM,QAAQ,mBAAkB;EACrE,2BAA2B,MAAM,OAAO;EACxC,4BAA4B,MAAM,OAAO;CAC3C;AACF;AAOO,SAAS,WAAW,SAAsB,OAA0B;CACzE,MAAM,UAAU,WAAW,KAAK;CAChC,OAAO,QAAQ,OAAO,CAAA,CAAE,SAAS,CAAC,KAAK,WAAW;EAChD,IAAI,OACF,QAAQ,MAAM,YAAY,KAAK,KAAK;CAExC,CAAC;CAGD,QAAQ,UAAU,OAAO,wBAAwB,qBAAqB;CACtE,QAAQ,UAAU,IAChB,MAAM,SAAS,wBAAwB,sBACzC;CACA,QAAQ,QAAQ,QAAQ,MAAM;AAChC;AAOO,SAAS,mBAAmB,OAA4B;CAC7D,MAAM,UAAU,WAAW,KAAK;CAChC,OAAO,OAAO,QAAQ,OAAO,CAAA,CAC1B,QAAQ,GAAG,WAAW,KAAK,CAAA,CAC3B,KAAK,CAAC,KAAK,WAAW,GAAG,IAAG,IAAK,OAAO,CAAA,CACxC,KAAK,IAAI;AACd;AAQO,SAAS,WACd,MACA,WACa;CACb,OAAO;EACL,GAAG;EACH,GAAG;EACH,QAAQ;GAAE,GAAG,KAAK;GAAQ,GAAG,UAAU;EAAO;EAC9C,OAAO;GAAE,GAAG,KAAK;GAAO,GAAG,UAAU;EAAM;EAC3C,SAAS;GAAE,GAAG,KAAK;GAAS,GAAG,UAAU;EAAQ;CACnD;AACF;AAQO,SAAS,YACd,MACA,WACA,eACa;CAEb,MAAM,QAAQ,WADD,SAAS,SACG,GAAM;EAC7B,GAAG;EACH;CACF,CAAC;CACD,cAAc,KAAK;CACnB,OAAO;AACT"}
@@ -36,4 +36,3 @@ export declare const presetThemes: Record<string, ThemeConfig>;
36
36
  * 默认主题
37
37
  */
38
38
  export declare const defaultTheme: ThemeConfig;
39
- //# sourceMappingURL=presets.d.ts.map
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/keyboard-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:29 *
5
+ * Build time: 2024-10-12 17:29:15 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -1 +1 @@
1
- {"version":3,"file":"presets.js","names":[],"sources":["../../src/themes/presets.ts"],"sourcesContent":["/**\n * 预设主题配置\n * @module themes/presets\n */\n\nimport type { ThemeConfig } from './types'\n\n/**\n * MIUI 小米风格主题(默认)\n * 特点:纯净、扁平、简洁、无多余装饰\n */\nexport const miuiTheme: ThemeConfig = {\n name: 'miui',\n displayName: 'MIUI',\n description: '小米风格 - 纯净简洁',\n isDark: false,\n colors: {\n background: '#f5f5f5',\n keyBackground: '#ffffff',\n keyColor: '#1a1a1a',\n keyActiveBackground: '#e8e8e8',\n fnKeyBackground: '#e0e0e0',\n fnKeyColor: '#666666',\n fnKeyActiveBackground: '#d0d0d0',\n primaryBackground: '#ff6700', // 小米橙\n primaryActiveBackground: '#e55d00',\n primaryColor: '#ffffff',\n borderColor: 'transparent',\n dividerColor: '#eeeeee',\n },\n sizes: {\n keyHeight: 46,\n keyRadius: 8,\n keyGap: 6,\n fontSize: 18,\n fnFontSize: 14,\n padding: 8,\n },\n effects: {\n keyShadow: 'none',\n keyActiveShadow: 'none',\n keyActiveScale: 0.96,\n transitionDuration: 100,\n enableBlur: false,\n blurAmount: 0,\n enableRipple: false,\n },\n}\n\n/**\n * iOS 风格主题\n * 特点:毛玻璃、圆润阴影、精致\n */\nexport const iosTheme: ThemeConfig = {\n name: 'ios',\n displayName: 'iOS',\n description: 'Apple 风格 - 精致优雅',\n isDark: false,\n colors: {\n background: '#d1d3d9',\n keyBackground: '#ffffff',\n keyColor: '#000000',\n keyActiveBackground: '#b8bac2',\n fnKeyBackground: '#a8aab4',\n fnKeyColor: '#000000',\n fnKeyActiveBackground: '#9699a3',\n primaryBackground: '#007aff',\n primaryActiveBackground: '#0062cc',\n primaryColor: '#ffffff',\n borderColor: 'transparent',\n dividerColor: '#c8cad0',\n },\n sizes: {\n keyHeight: 43,\n keyRadius: 5,\n keyGap: 6,\n fontSize: 22,\n fnFontSize: 15,\n padding: 4,\n },\n effects: {\n keyShadow: '0 1px 0 rgba(0,0,0,0.35)',\n keyActiveShadow: '0 0 0 rgba(0,0,0,0)',\n keyActiveScale: 1,\n transitionDuration: 80,\n enableBlur: true,\n blurAmount: 20,\n enableRipple: false,\n },\n}\n\n/**\n * Material Design 主题\n * 特点:深色、涟漪动效、层次感\n */\nexport const materialTheme: ThemeConfig = {\n name: 'material',\n displayName: 'Material',\n description: 'Google 风格 - 深邃现代',\n isDark: true,\n colors: {\n background: '#263238',\n keyBackground: '#37474f',\n keyColor: '#ffffff',\n keyActiveBackground: '#455a64',\n fnKeyBackground: '#455a64',\n fnKeyColor: '#b0bec5',\n fnKeyActiveBackground: '#546e7a',\n primaryBackground: '#4caf50', // Material Green\n primaryActiveBackground: '#43a047',\n primaryColor: '#ffffff',\n borderColor: 'transparent',\n dividerColor: '#37474f',\n },\n sizes: {\n keyHeight: 48,\n keyRadius: 4,\n keyGap: 4,\n fontSize: 20,\n fnFontSize: 14,\n padding: 6,\n },\n effects: {\n keyShadow: '0 1px 3px rgba(0,0,0,0.2)',\n keyActiveShadow: '0 2px 6px rgba(0,0,0,0.3)',\n keyActiveScale: 1,\n transitionDuration: 150,\n enableBlur: false,\n blurAmount: 0,\n enableRipple: true,\n },\n}\n\n/**\n * 极简主题\n * 特点:无边框、超简洁、透明感\n */\nexport const minimalTheme: ThemeConfig = {\n name: 'minimal',\n displayName: 'Minimal',\n description: '极简风格 - 轻盈透明',\n isDark: false,\n colors: {\n background: 'rgba(250, 250, 250, 0.95)',\n keyBackground: 'transparent',\n keyColor: '#333333',\n keyActiveBackground: 'rgba(0, 0, 0, 0.08)',\n fnKeyBackground: 'transparent',\n fnKeyColor: '#888888',\n fnKeyActiveBackground: 'rgba(0, 0, 0, 0.08)',\n primaryBackground: '#333333',\n primaryActiveBackground: '#000000',\n primaryColor: '#ffffff',\n borderColor: '#eeeeee',\n dividerColor: '#eeeeee',\n },\n sizes: {\n keyHeight: 44,\n keyRadius: 12,\n keyGap: 8,\n fontSize: 17,\n fnFontSize: 13,\n padding: 12,\n },\n effects: {\n keyShadow: 'none',\n keyActiveShadow: 'none',\n keyActiveScale: 0.92,\n transitionDuration: 120,\n enableBlur: true,\n blurAmount: 30,\n enableRipple: false,\n },\n}\n\n/**\n * 深色主题\n * 特点:护眼、现代、高对比\n */\nexport const darkTheme: ThemeConfig = {\n name: 'dark',\n displayName: 'Dark',\n description: '深色模式 - 护眼舒适',\n isDark: true,\n colors: {\n background: '#1c1c1e',\n keyBackground: '#3a3a3c',\n keyColor: '#ffffff',\n keyActiveBackground: '#4a4a4c',\n fnKeyBackground: '#2c2c2e',\n fnKeyColor: '#ebebf5',\n fnKeyActiveBackground: '#3c3c3e',\n primaryBackground: '#0a84ff',\n primaryActiveBackground: '#0070e0',\n primaryColor: '#ffffff',\n borderColor: '#3a3a3c',\n dividerColor: '#3a3a3c',\n },\n sizes: {\n keyHeight: 45,\n keyRadius: 8,\n keyGap: 6,\n fontSize: 18,\n fnFontSize: 14,\n padding: 8,\n },\n effects: {\n keyShadow: '0 1px 2px rgba(0,0,0,0.4)',\n keyActiveShadow: '0 0 8px rgba(10,132,255,0.3)',\n keyActiveScale: 0.97,\n transitionDuration: 100,\n enableBlur: true,\n blurAmount: 20,\n enableRipple: false,\n },\n}\n\n/**\n * 所有预设主题\n */\nexport const presetThemes: Record<string, ThemeConfig> = {\n miui: miuiTheme,\n ios: iosTheme,\n material: materialTheme,\n minimal: minimalTheme,\n dark: darkTheme,\n}\n\n/**\n * 默认主题\n */\nexport const defaultTheme = miuiTheme\n"],"mappings":"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"}
1
+ {"version":3,"file":"presets.js","names":[],"sources":["../../src/themes/presets.ts"],"sourcesContent":["/**\r\n * 预设主题配置\r\n * @module themes/presets\r\n */\r\n\r\nimport type { ThemeConfig } from './types'\r\n\r\n/**\r\n * MIUI 小米风格主题(默认)\r\n * 特点:纯净、扁平、简洁、无多余装饰\r\n */\r\nexport const miuiTheme: ThemeConfig = {\r\n name: 'miui',\r\n displayName: 'MIUI',\r\n description: '小米风格 - 纯净简洁',\r\n isDark: false,\r\n colors: {\r\n background: '#f5f5f5',\r\n keyBackground: '#ffffff',\r\n keyColor: '#1a1a1a',\r\n keyActiveBackground: '#e8e8e8',\r\n fnKeyBackground: '#e0e0e0',\r\n fnKeyColor: '#666666',\r\n fnKeyActiveBackground: '#d0d0d0',\r\n primaryBackground: '#ff6700', // 小米橙\r\n primaryActiveBackground: '#e55d00',\r\n primaryColor: '#ffffff',\r\n borderColor: 'transparent',\r\n dividerColor: '#eeeeee',\r\n },\r\n sizes: {\r\n keyHeight: 46,\r\n keyRadius: 8,\r\n keyGap: 6,\r\n fontSize: 18,\r\n fnFontSize: 14,\r\n padding: 8,\r\n },\r\n effects: {\r\n keyShadow: 'none',\r\n keyActiveShadow: 'none',\r\n keyActiveScale: 0.96,\r\n transitionDuration: 100,\r\n enableBlur: false,\r\n blurAmount: 0,\r\n enableRipple: false,\r\n },\r\n}\r\n\r\n/**\r\n * iOS 风格主题\r\n * 特点:毛玻璃、圆润阴影、精致\r\n */\r\nexport const iosTheme: ThemeConfig = {\r\n name: 'ios',\r\n displayName: 'iOS',\r\n description: 'Apple 风格 - 精致优雅',\r\n isDark: false,\r\n colors: {\r\n background: '#d1d3d9',\r\n keyBackground: '#ffffff',\r\n keyColor: '#000000',\r\n keyActiveBackground: '#b8bac2',\r\n fnKeyBackground: '#a8aab4',\r\n fnKeyColor: '#000000',\r\n fnKeyActiveBackground: '#9699a3',\r\n primaryBackground: '#007aff',\r\n primaryActiveBackground: '#0062cc',\r\n primaryColor: '#ffffff',\r\n borderColor: 'transparent',\r\n dividerColor: '#c8cad0',\r\n },\r\n sizes: {\r\n keyHeight: 43,\r\n keyRadius: 5,\r\n keyGap: 6,\r\n fontSize: 22,\r\n fnFontSize: 15,\r\n padding: 4,\r\n },\r\n effects: {\r\n keyShadow: '0 1px 0 rgba(0,0,0,0.35)',\r\n keyActiveShadow: '0 0 0 rgba(0,0,0,0)',\r\n keyActiveScale: 1,\r\n transitionDuration: 80,\r\n enableBlur: true,\r\n blurAmount: 20,\r\n enableRipple: false,\r\n },\r\n}\r\n\r\n/**\r\n * Material Design 主题\r\n * 特点:深色、涟漪动效、层次感\r\n */\r\nexport const materialTheme: ThemeConfig = {\r\n name: 'material',\r\n displayName: 'Material',\r\n description: 'Google 风格 - 深邃现代',\r\n isDark: true,\r\n colors: {\r\n background: '#263238',\r\n keyBackground: '#37474f',\r\n keyColor: '#ffffff',\r\n keyActiveBackground: '#455a64',\r\n fnKeyBackground: '#455a64',\r\n fnKeyColor: '#b0bec5',\r\n fnKeyActiveBackground: '#546e7a',\r\n primaryBackground: '#4caf50', // Material Green\r\n primaryActiveBackground: '#43a047',\r\n primaryColor: '#ffffff',\r\n borderColor: 'transparent',\r\n dividerColor: '#37474f',\r\n },\r\n sizes: {\r\n keyHeight: 48,\r\n keyRadius: 4,\r\n keyGap: 4,\r\n fontSize: 20,\r\n fnFontSize: 14,\r\n padding: 6,\r\n },\r\n effects: {\r\n keyShadow: '0 1px 3px rgba(0,0,0,0.2)',\r\n keyActiveShadow: '0 2px 6px rgba(0,0,0,0.3)',\r\n keyActiveScale: 1,\r\n transitionDuration: 150,\r\n enableBlur: false,\r\n blurAmount: 0,\r\n enableRipple: true,\r\n },\r\n}\r\n\r\n/**\r\n * 极简主题\r\n * 特点:无边框、超简洁、透明感\r\n */\r\nexport const minimalTheme: ThemeConfig = {\r\n name: 'minimal',\r\n displayName: 'Minimal',\r\n description: '极简风格 - 轻盈透明',\r\n isDark: false,\r\n colors: {\r\n background: 'rgba(250, 250, 250, 0.95)',\r\n keyBackground: 'transparent',\r\n keyColor: '#333333',\r\n keyActiveBackground: 'rgba(0, 0, 0, 0.08)',\r\n fnKeyBackground: 'transparent',\r\n fnKeyColor: '#888888',\r\n fnKeyActiveBackground: 'rgba(0, 0, 0, 0.08)',\r\n primaryBackground: '#333333',\r\n primaryActiveBackground: '#000000',\r\n primaryColor: '#ffffff',\r\n borderColor: '#eeeeee',\r\n dividerColor: '#eeeeee',\r\n },\r\n sizes: {\r\n keyHeight: 44,\r\n keyRadius: 12,\r\n keyGap: 8,\r\n fontSize: 17,\r\n fnFontSize: 13,\r\n padding: 12,\r\n },\r\n effects: {\r\n keyShadow: 'none',\r\n keyActiveShadow: 'none',\r\n keyActiveScale: 0.92,\r\n transitionDuration: 120,\r\n enableBlur: true,\r\n blurAmount: 30,\r\n enableRipple: false,\r\n },\r\n}\r\n\r\n/**\r\n * 深色主题\r\n * 特点:护眼、现代、高对比\r\n */\r\nexport const darkTheme: ThemeConfig = {\r\n name: 'dark',\r\n displayName: 'Dark',\r\n description: '深色模式 - 护眼舒适',\r\n isDark: true,\r\n colors: {\r\n background: '#1c1c1e',\r\n keyBackground: '#3a3a3c',\r\n keyColor: '#ffffff',\r\n keyActiveBackground: '#4a4a4c',\r\n fnKeyBackground: '#2c2c2e',\r\n fnKeyColor: '#ebebf5',\r\n fnKeyActiveBackground: '#3c3c3e',\r\n primaryBackground: '#0a84ff',\r\n primaryActiveBackground: '#0070e0',\r\n primaryColor: '#ffffff',\r\n borderColor: '#3a3a3c',\r\n dividerColor: '#3a3a3c',\r\n },\r\n sizes: {\r\n keyHeight: 45,\r\n keyRadius: 8,\r\n keyGap: 6,\r\n fontSize: 18,\r\n fnFontSize: 14,\r\n padding: 8,\r\n },\r\n effects: {\r\n keyShadow: '0 1px 2px rgba(0,0,0,0.4)',\r\n keyActiveShadow: '0 0 8px rgba(10,132,255,0.3)',\r\n keyActiveScale: 0.97,\r\n transitionDuration: 100,\r\n enableBlur: true,\r\n blurAmount: 20,\r\n enableRipple: false,\r\n },\r\n}\r\n\r\n/**\r\n * 所有预设主题\r\n */\r\nexport const presetThemes: Record<string, ThemeConfig> = {\r\n miui: miuiTheme,\r\n ios: iosTheme,\r\n material: materialTheme,\r\n minimal: minimalTheme,\r\n dark: darkTheme,\r\n}\r\n\r\n/**\r\n * 默认主题\r\n */\r\nexport const defaultTheme = miuiTheme\r\n"],"mappings":"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"}
@@ -111,8 +111,8 @@ export interface ThemeCSSVariables {
111
111
  '--keyboard-fn-font-size': string;
112
112
  '--keyboard-padding': string;
113
113
  '--keyboard-key-shadow': string;
114
+ '--keyboard-mask-bg'?: string;
114
115
  '--keyboard-transition-duration': string;
115
116
  '--keyboard-border-color'?: string;
116
117
  '--keyboard-divider-color'?: string;
117
118
  }
118
- //# sourceMappingURL=types.d.ts.map
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/keyboard-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:29 *
5
+ * Build time: 2024-10-12 17:29:15 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -4,4 +4,3 @@
4
4
  */
5
5
  export * from './keyboard';
6
6
  export * from './position';
7
- //# sourceMappingURL=index.d.ts.map
package/es/types/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/keyboard-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:29 *
5
+ * Build time: 2024-10-12 17:29:15 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -250,4 +250,3 @@ export interface KeyboardOptions {
250
250
  */
251
251
  customLayouts?: Record<string, KeyboardConfig>;
252
252
  }
253
- //# sourceMappingURL=keyboard.d.ts.map
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/keyboard-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:29 *
5
+ * Build time: 2024-10-12 17:29:15 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -136,4 +136,3 @@ export interface ViewportInfo {
136
136
  left: number;
137
137
  };
138
138
  }
139
- //# sourceMappingURL=position.d.ts.map
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/keyboard-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:29 *
5
+ * Build time: 2024-10-12 17:29:15 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -42,4 +42,3 @@ export declare function vibrate(pattern?: number | number[]): boolean;
42
42
  * @returns 取消监听函数
43
43
  */
44
44
  export declare function onViewportChange(callback: (info: ViewportInfo) => void): () => void;
45
- //# sourceMappingURL=device.d.ts.map
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/keyboard-core v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:33:29 *
5
+ * Build time: 2024-10-12 17:29:15 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -12,10 +12,7 @@ const MOBILE_UA_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Oper
12
12
  let cachedIsTouchDevice = null;
13
13
  function isTouchDevice() {
14
14
  if (cachedIsTouchDevice !== null) return cachedIsTouchDevice;
15
- if (typeof window === "undefined") {
16
- cachedIsTouchDevice = false;
17
- return false;
18
- }
15
+ if (typeof window === "undefined") return false;
19
16
  cachedIsTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
20
17
  return cachedIsTouchDevice;
21
18
  }
@@ -75,7 +72,14 @@ function getSafeAreaInsets() {
75
72
  pointer-events: none;
76
73
  visibility: hidden;
77
74
  `;
78
- document.body.appendChild(div);
75
+ const body = document.body;
76
+ if (!body) return {
77
+ top: 0,
78
+ right: 0,
79
+ bottom: 0,
80
+ left: 0
81
+ };
82
+ body.appendChild(div);
79
83
  const computed = getComputedStyle(div);
80
84
  const safeArea = {
81
85
  top: Number.parseFloat(computed.top) || 0,
@@ -83,7 +87,7 @@ function getSafeAreaInsets() {
83
87
  bottom: Number.parseFloat(computed.bottom) || 0,
84
88
  left: Number.parseFloat(computed.left) || 0
85
89
  };
86
- document.body.removeChild(div);
90
+ body.removeChild(div);
87
91
  return safeArea;
88
92
  }
89
93
  function supportsVibrate() {