@junerver/vue-touch-ime 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 junerver
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,157 @@
1
- # Temporary Holding Version
1
+ # vue-touch-ime
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Vue 3 触摸屏软键盘(内置中文拼音输入法)——零接线焦点接管、按输入类型限制,适配无物理键盘的触屏工控 / 车载 / 自助终端 / 便携采集设备
4
+
5
+ ![screenshot](media/screenshot.png)
6
+
7
+ ## 功能特性
8
+
9
+ - **零接线接管** - 组件挂到页面根部后,文档级焦点监听自动接管所有文本输入框(聚焦弹出、失焦收起),无需逐个绑定
10
+ - **中文拼音输入** - 内置拼音词典(421 音节、汉字按字频排序 + 147 条常用短语),短语候选优先,支持一次上屏
11
+ - **输入类型限制** - 从 input 自身属性推断:`type=number` + 小数 `step` → 仅数字与一个小数点;整数 `step` → 仅数字;`inputmode` 同样生效;`data-vti-kb` 可显式覆盖
12
+ - **中 / 英 / 数 三模式** - 拼音、英文(单次大写)、数字键盘;数字与标点在拼音模式下直接上屏
13
+ - **v-model 友好** - 写值自动派发 `input` 事件,兼容 `v-model` 与 `@input` 受控写法
14
+ - **可换肤** - 一组 CSS 变量覆盖主色 / 背景 / 按键尺寸 / 圆角 / 字体
15
+ - **零运行时依赖** - 仅 peer 依赖 `vue`,拼音词典静态编译进包
16
+ - **单例 / 多实例** - 默认单例开箱即用,也可 `createSoftKeyboard()` 创建独立实例互不干扰
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ npm install @junerver/vue-touch-ime
22
+ ```
23
+
24
+ ## 快速上手
25
+
26
+ ```vue
27
+ <script setup lang="ts">
28
+ import { SoftKeyboard } from '@junerver/vue-touch-ime'
29
+ import '@junerver/vue-touch-ime/style.css'
30
+ import { ref } from 'vue'
31
+
32
+ const name = ref('')
33
+ const weight = ref<number | null>(null)
34
+ </script>
35
+
36
+ <template>
37
+ <input v-model="name" type="text" placeholder="名称(试试输入 dahuang)" />
38
+ <input v-model="weight" type="number" step="0.1" placeholder="体重(仅数字+小数点)" />
39
+ <!-- 放在页面根部即可,输入框自动接管 -->
40
+ <SoftKeyboard />
41
+ </template>
42
+ ```
43
+
44
+ > 键盘只在挂载了 `<SoftKeyboard />` 的页面生效;不挂载的页面(如桌面大屏版)完全不受影响。
45
+
46
+ ## 输入类型限制
47
+
48
+ | 输入框写法 | 键盘行为 |
49
+ | --- | --- |
50
+ | `<input type="number" step="0.1">` | 数字键盘,仅数字 + 一个小数点 |
51
+ | `<input type="number">`(整数 step) | 数字键盘,仅数字 |
52
+ | `<input type="text" inputmode="decimal">` | 数字键盘,仅数字 + 一个小数点 |
53
+ | `<input type="text" inputmode="numeric\|tel">` | 数字键盘,仅数字 |
54
+ | 其余文本输入框 | 拼音(可切英文 / 数字) |
55
+
56
+ ### `data-vti-kb` 显式覆盖
57
+
58
+ 属性推断不符时用 `data-vti-kb="text|decimal|digits"` 强制指定:
59
+
60
+ ```vue
61
+ <!-- 编号输入框强制只接受数字(含前导零) -->
62
+ <input v-model="code" type="text" data-vti-kb="digits" placeholder="0083" />
63
+ ```
64
+
65
+ > 注意:`type=number` 的输入框受浏览器值消毒约束(非数字内容会被直接清空),
66
+ > 中文请用文本输入框。
67
+
68
+ ## 拼音输入说明
69
+
70
+ - 字母进入候选缓冲(顶部回显拼音,不进输入框),空格或点击候选上屏
71
+ - 候选顺序:短语优先(`dahuang` → 「大黄」),再按音节前缀从长到短出单字,单字按字频排序(`da` → 大 打 达…)
72
+ - `ü` 用 `v` 输入(`nv` → 女)
73
+ - 「完成」:整段拼音能被单个候选吃掉则上屏候选,否则按原始拉丁字母上屏(方便输入编号 / 编码);随后收起键盘并触发输入框 `blur`
74
+ - 退格先删拼音缓冲,缓冲清空后删输入框内容
75
+
76
+ ## 主题定制
77
+
78
+ ### 快捷换肤:`accent` / `background` prop
79
+
80
+ ```vue
81
+ <!-- 主色调换成橙色:完成键 / 高亮 / 派生色自动跟随 -->
82
+ <SoftKeyboard accent="#ff5500" background="rgba(20, 16, 12, 0.97)" />
83
+ ```
84
+
85
+ | Prop | 类型 | 默认值 | 说明 |
86
+ | --- | --- | --- | --- |
87
+ | `instance` | `SoftKeyboardInstance` | 模块单例 | 自定义键盘实例(多键盘并存时用 `createSoftKeyboard()` 创建) |
88
+ | `accent` | `string` | `#3ef2c0` | 主色调(完成键 / 高亮 / 派生色自动跟随) |
89
+ | `background` | `string` | `rgba(7,20,24,0.97)` | 面板背景色 |
90
+
91
+ ### 细粒度:CSS 变量
92
+
93
+ 覆盖 `.vti-kb` 上的 CSS 变量即可(`--vti-accent-strong` 默认由 `--vti-accent`
94
+ 自动派生,一般无需单独覆盖):
95
+
96
+ ```css
97
+ .vti-kb {
98
+ --vti-accent: #3ef2c0; /* 主色(高亮 / 完成键) */
99
+ --vti-accent-strong: color-mix(in srgb, var(--vti-accent) 72%, #000); /* 派生暗端 */
100
+ --vti-on-accent: #06241c; /* 主色按键上的文字色 */
101
+ --vti-bg: rgba(7, 20, 24, 0.97); /* 面板背景 */
102
+ --vti-key-bg: linear-gradient(180deg, rgba(24, 56, 60, 0.95), rgba(13, 34, 38, 0.95));
103
+ --vti-key-fg: #d7e2e0; /* 按键文字 */
104
+ --vti-key-h: 54px; /* 按键高度 */
105
+ --vti-key-radius: 10px; /* 按键圆角 */
106
+ --vti-font: "Microsoft YaHei", sans-serif;
107
+ }
108
+ ```
109
+
110
+ ## API
111
+
112
+ ### 组合式
113
+
114
+ ```ts
115
+ import { useSoftKeyboard, createSoftKeyboard } from '@junerver/vue-touch-ime'
116
+
117
+ const kb = useSoftKeyboard() // 模块单例
118
+ const other = createSoftKeyboard() // 独立实例
119
+
120
+ kb.state // reactive:visible / mode / kind / buffer / draft / replaceAll / shift
121
+ kb.candidates // 当前拼音候选(ComputedRef<SoftCandidate[]>)
122
+ kb.press('a') // 按键:单字符 | 'backspace' | 'space' | 'done' | 'shift' | 'mode:zh|en|num|back'
123
+ kb.pickCandidate(candidate) // 候选上屏
124
+ kb.openFor(inputElement) // 手动接管指定输入框
125
+ kb.close(blur?) // 收起键盘
126
+ kb.attach(root) / kb.detach() // 焦点监听挂载/卸载(组件内部已自动处理)
127
+ ```
128
+
129
+ ### 纯逻辑(`@junerver/vue-touch-ime/ime`)
130
+
131
+ 不依赖 Vue,可在任意框架 / 原生页面复用:`resolveSoftKind` / `resolveSoftKindWithOverride` /
132
+ `candidatesFor` / `resolveBufferCommit` / `insertIntoTextInput` / `backspaceTextInput` /
133
+ `writeInputValue` / 数字草稿工具(`applyCharToDraft` / `draftToDomValue` 等)。
134
+
135
+ ### 词典(`@junerver/vue-touch-ime/dict`)
136
+
137
+ `PINYIN_CHARS`(音节 → 汉字串,串序即候选序)与 `PINYIN_PHRASES`(拼音 → 短语)。
138
+
139
+ ## 词典再生成
140
+
141
+ 词典是构建期生成的静态资产(`src/pinyinDict.ts`,勿手工编辑):
142
+
143
+ ```bash
144
+ python scripts/gen-pinyin-dict.py
145
+ ```
146
+
147
+ 原料:Jun Da 现代汉语单字字频表(决定候选顺序)+ `pinyin` 包单字表(补全读音)+
148
+ 脚本内维护的常用短语表。短语表可按业务领域增删后重新生成。
149
+
150
+ ## 浏览器兼容
151
+
152
+ 现代浏览器(Chrome / Edge / Safari),依赖 Pointer Events 与 CSS `color-mix`。
153
+ 面向触屏设备使用;桌面端鼠标操作同样可用。
154
+
155
+ ## License
156
+
157
+ [MIT](LICENSE) © junerver
@@ -0,0 +1,12 @@
1
+ import { type SoftKeyboard } from './useSoftKeyboard';
2
+ type __VLS_Props = {
3
+ /** 自定义键盘实例(缺省用模块单例;多键盘并存时传入 createSoftKeyboard() 的产物) */
4
+ instance?: SoftKeyboard;
5
+ /** 主色调(覆盖 --vti-accent,完成键/高亮/派生色自动跟随) */
6
+ accent?: string;
7
+ /** 面板背景色(覆盖 --vti-bg) */
8
+ background?: string;
9
+ };
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const _default: typeof __VLS_export;
12
+ export default _default;
package/dist/ime.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./pinyinDict.cjs");var t=6,n=20,r=[`text`,`decimal`,`digits`];function i(e){return r.includes(e??``)}function a(e){let t=String(e.type||`text`).toLowerCase(),n=String(e.inputMode||``).toLowerCase();if(t===`number`){let t=String(e.step??``).trim();if(t===`any`)return`decimal`;let n=Number(t);return t!==``&&Number.isFinite(n)&&Math.abs(n%1)>1e-9?`decimal`:`digits`}return n===`decimal`?`decimal`:n===`numeric`||n===`tel`||t===`tel`?`digits`:`text`}function o(e,t){return i(t)?t:a(e)}function s(e,t){return t.length===1?e===`text`?!0:e===`digits`?t>=`0`&&t<=`9`:t>=`0`&&t<=`9`||t===`.`:!1}function c(e,t,n,r){let i=`${r} ${n}`;t.has(i)||(t.add(i),e.push({text:n,consumed:r}))}function l(r,i=n){if(!r)return[];let a=[],o=new Set,s=[];for(let t of Object.keys(e.PINYIN_PHRASES))r.startsWith(t)&&s.push({text:e.PINYIN_PHRASES[t],consumed:t.length});s.sort((e,t)=>t.consumed-e.consumed);for(let e of s)c(a,o,e.text,e.consumed);for(let n=Math.min(t,r.length);n>=1;n--){let t=e.PINYIN_CHARS[r.slice(0,n)];if(!t)continue;let s=Math.min(t.length,Math.max(1,i));for(let e=0;e<s;e++)c(a,o,t[e],n)}return a}function u(e){if(!e)return null;let t=l(e,1)[0];return t&&t.consumed===e.length?t.text:e}function d(e,t){let n=t;if(e===`digits`)return n.replace(/[^0-9]/g,``);if(e===`decimal`){n=n.replace(/[^0-9.]/g,``);let e=n.indexOf(`.`);return e>=0&&(n=n.slice(0,e+1)+n.slice(e+1).replace(/\./g,``)),n}return n}function f(e,t){let n=d(e,t);return e===`decimal`&&n.endsWith(`.`)?n.slice(0,-1):n}function p(e,t,n,r){let i=r?``:t;return e===`decimal`&&n===`.`?i.includes(`.`)?d(e,i):d(e,i===``?`0.`:i+`.`):d(e,i+n)}function m(e,t){return t?``:e.slice(0,-1)}function h(e){try{let t=e.selectionStart,n=e.selectionEnd;return t==null||n==null?null:{start:t,end:n}}catch{return null}}function g(e,t){try{e.setSelectionRange(t,t)}catch{}}function _(e){return e.maxLength>0?e.maxLength:1/0}function v(e,t){e.value!==t&&(e.value=t,e.dispatchEvent(new Event(`input`,{bubbles:!0})))}function y(e){let t=h(e);return t?e.value.length>0&&t.start===0&&t.end===e.value.length:e.value.length>0}function b(e,t,n=!1){let r=_(e),i=e.value,a=h(e);if(n||a!=null&&i.length>0&&a.start===0&&a.end===i.length){v(e,t.slice(0,r)),g(e,Math.min(t.length,r));return}if(!a){v(e,(i+t).slice(0,r));return}let o=(i.slice(0,a.start)+t+i.slice(a.end)).slice(0,r);v(e,o),g(e,Math.min(a.start+t.length,o.length))}function x(e){let t=e.value,n=h(e);n?n.start===n.end?n.start<=0||(v(e,t.slice(0,n.start-1)+t.slice(n.start)),g(e,n.start-1)):(v(e,t.slice(0,n.start)+t.slice(n.end)),g(e,n.start)):v(e,t.slice(0,-1))}function S(e,t,n){v(e,f(t,n))}exports.KIND_OVERRIDE_VALUES=r,exports.applyCharToDraft=p,exports.backspaceDraft=m,exports.backspaceTextInput=x,exports.candidatesFor=l,exports.draftToDomValue=f,exports.hasFullSelection=y,exports.insertIntoTextInput=b,exports.isCharAllowed=s,exports.normalizeDraft=d,exports.resolveBufferCommit=u,exports.resolveSoftKind=a,exports.resolveSoftKindWithOverride=o,exports.writeDraft=S,exports.writeInputValue=v;
package/dist/ime.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ export type SoftKbKind = 'text' | 'decimal' | 'digits';
2
+ export type SoftKbMode = 'zh' | 'en' | 'num';
3
+ export interface SoftCandidate {
4
+ /** 候选文本(短语或单字) */
5
+ text: string;
6
+ /** 上屏后应从拼音缓冲里吃掉的字母数 */
7
+ consumed: number;
8
+ }
9
+ /** `data-vti-kb` 显式覆盖值 */
10
+ export declare const KIND_OVERRIDE_VALUES: readonly ["text", "decimal", "digits"];
11
+ /**
12
+ * 从输入框属性推断限制类型。
13
+ * - type=number + step 含小数 → decimal(体重 step=0.1);
14
+ * - type=number 整数 step / 缺省 step → digits(月龄);
15
+ * - inputmode=decimal/numeric/tel → 对应 decimal/digits;
16
+ * - 其余(text/search…)→ text。
17
+ */
18
+ export declare function resolveSoftKind(el: {
19
+ type?: string | null;
20
+ step?: string | null;
21
+ inputMode?: string | null;
22
+ }): SoftKbKind;
23
+ /**
24
+ * 解析输入限制:`data-vti-kb="text|decimal|digits"` 优先于属性推断。
25
+ * 传入带 data 集的元素即可(HTMLInputElement.dataset.vtiKb)。
26
+ */
27
+ export declare function resolveSoftKindWithOverride(el: {
28
+ type?: string | null;
29
+ step?: string | null;
30
+ inputMode?: string | null;
31
+ }, override?: string | null): SoftKbKind;
32
+ /** 该限制类型允许的单字符键(软键盘按此过滤) */
33
+ export declare function isCharAllowed(kind: SoftKbKind, ch: string): boolean;
34
+ /**
35
+ * 拼音缓冲 → 候选列表。
36
+ * 1) 短语:拼音为缓冲前缀的短语,长的在前("dahuangniu" 先出「大黄」);
37
+ * 2) 单字:音节前缀从长到短("xian" 先出「现…」再出「夏…」再出「西…」),
38
+ * 单字顺序即词典字频顺序。
39
+ */
40
+ export declare function candidatesFor(buffer: string, perGroupLimit?: number): SoftCandidate[];
41
+ /**
42
+ * 拼音缓冲上屏(完成键):整个缓冲能被单个候选吃掉就上屏候选,
43
+ * 否则按用户原始拉丁字母上屏(方便输入编号/编码类内容)。
44
+ */
45
+ export declare function resolveBufferCommit(buffer: string): string | null;
46
+ /** 草稿净化:digits 只留数字;decimal 留数字且至多一个小数点 */
47
+ export declare function normalizeDraft(kind: SoftKbKind, draft: string): string;
48
+ /** 草稿 → 可写进 DOM 的值(剥掉尾部未完成的小数点,浏览器会把它消毒成 "") */
49
+ export declare function draftToDomValue(kind: SoftKbKind, draft: string): string;
50
+ /** 数字草稿按键:追加一个字符(replaceAll 时先清空旧值) */
51
+ export declare function applyCharToDraft(kind: SoftKbKind, draft: string, ch: string, replaceAll: boolean): string;
52
+ /** 数字草稿退格:只支持尾部删除(type=number 拿不到光标) */
53
+ export declare function backspaceDraft(draft: string, replaceAll: boolean): string;
54
+ /** 写值并派发 input 事件,让 v-model / @input 同步 */
55
+ export declare function writeInputValue(el: HTMLInputElement, value: string): void;
56
+ /** 当前是否为「全选」状态(行内编辑进场即全选的输入框) */
57
+ export declare function hasFullSelection(el: HTMLInputElement): boolean;
58
+ /** 文本类插入:按原生光标插入(全选/replaceAll 时整体替换) */
59
+ export declare function insertIntoTextInput(el: HTMLInputElement, text: string, replaceAll?: boolean): void;
60
+ /** 文本类退格:有选区删选区,否则删光标前一个字符 */
61
+ export declare function backspaceTextInput(el: HTMLInputElement): void;
62
+ /** 数字类写值:把草稿净化后落 DOM(含 pending 小数点处理) */
63
+ export declare function writeDraft(el: HTMLInputElement, kind: SoftKbKind, draft: string): void;
package/dist/ime.js ADDED
@@ -0,0 +1,124 @@
1
+ import { PINYIN_CHARS as e, PINYIN_PHRASES as t } from "./pinyinDict.js";
2
+ //#region src/ime.ts
3
+ var n = 6, r = 20, i = [
4
+ "text",
5
+ "decimal",
6
+ "digits"
7
+ ];
8
+ function a(e) {
9
+ return i.includes(e ?? "");
10
+ }
11
+ function o(e) {
12
+ let t = String(e.type || "text").toLowerCase(), n = String(e.inputMode || "").toLowerCase();
13
+ if (t === "number") {
14
+ let t = String(e.step ?? "").trim();
15
+ if (t === "any") return "decimal";
16
+ let n = Number(t);
17
+ return t !== "" && Number.isFinite(n) && Math.abs(n % 1) > 1e-9 ? "decimal" : "digits";
18
+ }
19
+ return n === "decimal" ? "decimal" : n === "numeric" || n === "tel" || t === "tel" ? "digits" : "text";
20
+ }
21
+ function s(e, t) {
22
+ return a(t) ? t : o(e);
23
+ }
24
+ function c(e, t) {
25
+ return t.length === 1 ? e === "text" ? !0 : e === "digits" ? t >= "0" && t <= "9" : t >= "0" && t <= "9" || t === "." : !1;
26
+ }
27
+ function l(e, t, n, r) {
28
+ let i = `${r} ${n}`;
29
+ t.has(i) || (t.add(i), e.push({
30
+ text: n,
31
+ consumed: r
32
+ }));
33
+ }
34
+ function u(i, a = r) {
35
+ if (!i) return [];
36
+ let o = [], s = /* @__PURE__ */ new Set(), c = [];
37
+ for (let e of Object.keys(t)) i.startsWith(e) && c.push({
38
+ text: t[e],
39
+ consumed: e.length
40
+ });
41
+ c.sort((e, t) => t.consumed - e.consumed);
42
+ for (let e of c) l(o, s, e.text, e.consumed);
43
+ for (let t = Math.min(n, i.length); t >= 1; t--) {
44
+ let n = e[i.slice(0, t)];
45
+ if (!n) continue;
46
+ let r = Math.min(n.length, Math.max(1, a));
47
+ for (let e = 0; e < r; e++) l(o, s, n[e], t);
48
+ }
49
+ return o;
50
+ }
51
+ function d(e) {
52
+ if (!e) return null;
53
+ let t = u(e, 1)[0];
54
+ return t && t.consumed === e.length ? t.text : e;
55
+ }
56
+ function f(e, t) {
57
+ let n = t;
58
+ if (e === "digits") return n.replace(/[^0-9]/g, "");
59
+ if (e === "decimal") {
60
+ n = n.replace(/[^0-9.]/g, "");
61
+ let e = n.indexOf(".");
62
+ return e >= 0 && (n = n.slice(0, e + 1) + n.slice(e + 1).replace(/\./g, "")), n;
63
+ }
64
+ return n;
65
+ }
66
+ function p(e, t) {
67
+ let n = f(e, t);
68
+ return e === "decimal" && n.endsWith(".") ? n.slice(0, -1) : n;
69
+ }
70
+ function m(e, t, n, r) {
71
+ let i = r ? "" : t;
72
+ return e === "decimal" && n === "." ? i.includes(".") ? f(e, i) : f(e, i === "" ? "0." : i + ".") : f(e, i + n);
73
+ }
74
+ function h(e, t) {
75
+ return t ? "" : e.slice(0, -1);
76
+ }
77
+ function g(e) {
78
+ try {
79
+ let t = e.selectionStart, n = e.selectionEnd;
80
+ return t == null || n == null ? null : {
81
+ start: t,
82
+ end: n
83
+ };
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+ function _(e, t) {
89
+ try {
90
+ e.setSelectionRange(t, t);
91
+ } catch {}
92
+ }
93
+ function v(e) {
94
+ return e.maxLength > 0 ? e.maxLength : Infinity;
95
+ }
96
+ function y(e, t) {
97
+ e.value !== t && (e.value = t, e.dispatchEvent(new Event("input", { bubbles: !0 })));
98
+ }
99
+ function b(e) {
100
+ let t = g(e);
101
+ return t ? e.value.length > 0 && t.start === 0 && t.end === e.value.length : e.value.length > 0;
102
+ }
103
+ function x(e, t, n = !1) {
104
+ let r = v(e), i = e.value, a = g(e);
105
+ if (n || a != null && i.length > 0 && a.start === 0 && a.end === i.length) {
106
+ y(e, t.slice(0, r)), _(e, Math.min(t.length, r));
107
+ return;
108
+ }
109
+ if (!a) {
110
+ y(e, (i + t).slice(0, r));
111
+ return;
112
+ }
113
+ let o = (i.slice(0, a.start) + t + i.slice(a.end)).slice(0, r);
114
+ y(e, o), _(e, Math.min(a.start + t.length, o.length));
115
+ }
116
+ function S(e) {
117
+ let t = e.value, n = g(e);
118
+ n ? n.start === n.end ? n.start <= 0 || (y(e, t.slice(0, n.start - 1) + t.slice(n.start)), _(e, n.start - 1)) : (y(e, t.slice(0, n.start) + t.slice(n.end)), _(e, n.start)) : y(e, t.slice(0, -1));
119
+ }
120
+ function C(e, t, n) {
121
+ y(e, p(t, n));
122
+ }
123
+ //#endregion
124
+ export { i as KIND_OVERRIDE_VALUES, m as applyCharToDraft, h as backspaceDraft, S as backspaceTextInput, u as candidatesFor, p as draftToDomValue, b as hasFullSelection, x as insertIntoTextInput, c as isCharAllowed, f as normalizeDraft, d as resolveBufferCommit, o as resolveSoftKind, s as resolveSoftKindWithOverride, C as writeDraft, y as writeInputValue };
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./pinyinDict.cjs"),t=require("./ime.cjs");let n=require("vue");var r=new Set([`text`,`search`,`tel`,`email`,`url`,`number`,``]);function i(e){return!(e instanceof HTMLInputElement)||e.disabled||e.readOnly?!1:r.has((e.type||`text`).toLowerCase())}function a(){let e=(0,n.reactive)({visible:!1,mode:`zh`,kind:`text`,buffer:``,draft:``,replaceAll:!1,shift:!1,prevTextMode:`zh`}),r=null,a=null,o=!1,s=0,c=(0,n.computed)(()=>e.visible&&e.mode===`zh`?t.candidatesFor(e.buffer):[]);function l(n){if(r===n&&e.visible)return;s+=1,r=n;let i=t.resolveSoftKindWithOverride(n,n.dataset?.vtiKb);e.kind=i,e.mode=i===`text`?`zh`:`num`,e.prevTextMode=`zh`,e.buffer=``,e.shift=!1,e.replaceAll=i===`text`?t.hasFullSelection(n):n.value.length>0,e.draft=i===`text`?``:n.value,e.visible=!0;try{n.scrollIntoView({block:`center`})}catch{}}function u(t=!1){s+=1,e.visible=!1,e.buffer=``,e.shift=!1;let n=r;if(r=null,t&&n)try{n.blur()}catch{}}function d(){return!r||!r.isConnected?(u(!1),null):r}function f(n){let r=d();if(r&&n){if(e.kind===`text`)t.insertIntoTextInput(r,n,e.replaceAll);else if(n.length===1)e.draft=t.applyCharToDraft(e.kind,e.draft,n,e.replaceAll),t.writeDraft(r,e.kind,e.draft);else{let i=e.replaceAll?``:e.draft;e.draft=t.normalizeDraft(e.kind,i+n),t.writeDraft(r,e.kind,e.draft)}e.replaceAll=!1}}function p(){let n=d();n&&(e.kind===`text`?t.backspaceTextInput(n):(e.draft=t.backspaceDraft(e.draft,e.replaceAll),t.writeDraft(n,e.kind,e.draft)),e.replaceAll=!1)}function m(){let n=t.resolveBufferCommit(e.buffer);e.buffer=``,f(n??``)}function h(t){d()&&(e.buffer=e.buffer.slice(t.consumed),f(t.text))}function g(t){e.mode===`zh`&&e.buffer&&m(),t===`num`&&e.mode!==`num`&&(e.prevTextMode=e.mode),e.mode=t,e.shift=!1}function _(n){if(!e.visible||!d())return;if(n===`backspace`){if(e.mode===`zh`&&e.buffer){e.buffer=e.buffer.slice(0,-1);return}p();return}if(n===`space`){if(e.mode===`zh`&&e.buffer){let e=c.value[0];e?h(e):m();return}e.kind===`text`&&f(` `);return}if(n===`done`){m(),u(!0);return}if(n===`shift`){e.shift=!e.shift;return}if(n===`mode:back`){g(e.prevTextMode);return}if(n.startsWith(`mode:`)){g(n.slice(5));return}let r=n;if(r.length===1&&(e.kind===`text`||t.isCharAllowed(e.kind,r))){if(e.mode===`zh`){if(r>=`a`&&r<=`z`){e.buffer+=r;return}if(r>=`A`&&r<=`Z`){e.buffer+=r.toLowerCase();return}m(),f(r)}else e.mode===`en`&&(r>=`a`&&r<=`z`||r>=`A`&&r<=`Z`)&&(r=e.shift?r.toUpperCase():r.toLowerCase(),e.shift=!1),f(r)}}function v(e){let t=e.target;i(t)&&(a&&t instanceof Node&&a.contains(t)||l(t))}function y(e){if(e.target!==r)return;let t=s;window.setTimeout(()=>{s===t&&u(!1)},0)}function b(e){a=e,!o&&(o=!0,document.addEventListener(`focusin`,v),document.addEventListener(`focusout`,y))}function x(){o&&=(document.removeEventListener(`focusin`,v),document.removeEventListener(`focusout`,y),!1),a=null,u(!1)}return{state:e,candidates:c,attach:b,detach:x,openFor:l,close:u,press:_,pickCandidate:h,getTarget:()=>r}}var o=a();function s(){return o}var c={class:`vti-kb__panel`},l={class:`vti-kb__strip`},u={class:`vti-kb__buffer`},d={key:0,class:`vti-kb__cands`},f=[`onPointerdown`],p={key:0,class:`vti-kb__hint`},m={key:1,class:`vti-kb__hint`},h=[`onPointerdown`],g={class:`vti-kb__row vti-kb__row--last`},_={key:0,type:`button`,class:`vti-kb__key vti-kb__key--ghost`,disabled:``},v={key:1,type:`button`,class:`vti-kb__key vti-kb__key--ghost`,disabled:``},y=[`onPointerdown`],b={class:`vti-kb__row vti-kb__row--num`},x=(0,n.defineComponent)({__name:`SoftKeyboard`,props:{instance:{},accent:{},background:{}},setup(e){let t=e,r=t.instance??o,i=(0,n.ref)(null),a=r.candidates,s=(0,n.computed)(()=>{let e={};return t.accent&&(e[`--vti-accent`]=t.accent),t.background&&(e[`--vti-bg`]=t.background),Object.keys(e).length>0?e:void 0});(0,n.onMounted)(()=>r.attach(i.value)),(0,n.onBeforeUnmount)(()=>r.detach());function x(e){e.preventDefault(),e.stopPropagation()}function S(e,t){x(e),r.press(t)}function C(e,t){x(e),r.pickCandidate(t)}let w=[[`q`,`w`,`e`,`r`,`t`,`y`,`u`,`i`,`o`,`p`],[`a`,`s`,`d`,`f`,`g`,`h`,`j`,`k`,`l`],[`z`,`x`,`c`,`v`,`b`,`n`,`m`]],T=[[`7`,`8`,`9`,`backspace`],[`4`,`5`,`6`,`.`],[`1`,`2`,`3`,`mode:back`]];function E(e){return r.state.mode===`en`&&r.state.shift?e.toUpperCase():e}function D(){return r.state.prevTextMode===`zh`?`拼音`:`英文`}return(e,t)=>((0,n.openBlock)(),(0,n.createElementBlock)(`div`,{ref_key:`rootRef`,ref:i,class:(0,n.normalizeClass)([`vti-kb`,{show:(0,n.unref)(r).state.visible}]),style:(0,n.normalizeStyle)(s.value),onPointerdown:x,onMousedown:x,onTouchstart:x,onClick:x,onContextmenu:t[10]||=(0,n.withModifiers)(()=>{},[`prevent`])},[(0,n.createElementVNode)(`div`,c,[(0,n.createElementVNode)(`div`,l,[(0,n.createElementVNode)(`span`,u,(0,n.toDisplayString)((0,n.unref)(r).state.mode===`zh`?(0,n.unref)(r).state.buffer:(0,n.unref)(r).state.draft),1),(0,n.unref)(r).state.mode===`zh`?((0,n.openBlock)(),(0,n.createElementBlock)(`div`,d,[((0,n.openBlock)(!0),(0,n.createElementBlock)(n.Fragment,null,(0,n.renderList)((0,n.unref)(a),(e,t)=>((0,n.openBlock)(),(0,n.createElementBlock)(`button`,{key:`${e.consumed}-${e.text}-${t}`,type:`button`,class:`vti-kb__cand`,onPointerdown:t=>C(t,e)},(0,n.toDisplayString)(e.text),41,f))),128)),(0,n.unref)(a).length===0?((0,n.openBlock)(),(0,n.createElementBlock)(`span`,p,`候选`)):(0,n.createCommentVNode)(``,!0)])):((0,n.openBlock)(),(0,n.createElementBlock)(`span`,m,(0,n.toDisplayString)((0,n.unref)(r).state.kind===`text`?`英文/数字`:(0,n.unref)(r).state.kind===`decimal`?`数字 · 小数`:`数字`),1))]),(0,n.unref)(r).state.mode===`num`?((0,n.openBlock)(),(0,n.createElementBlock)(n.Fragment,{key:1},[((0,n.openBlock)(),(0,n.createElementBlock)(n.Fragment,null,(0,n.renderList)(T,(e,t)=>(0,n.createElementVNode)(`div`,{key:`n${t}`,class:`vti-kb__row vti-kb__row--num`},[((0,n.openBlock)(!0),(0,n.createElementBlock)(n.Fragment,null,(0,n.renderList)(e,e=>((0,n.openBlock)(),(0,n.createElementBlock)(n.Fragment,{key:e},[e===`.`&&(0,n.unref)(r).state.kind===`digits`?((0,n.openBlock)(),(0,n.createElementBlock)(`button`,_,` · `)):e===`mode:back`&&(0,n.unref)(r).state.kind!==`text`?((0,n.openBlock)(),(0,n.createElementBlock)(`button`,v,` · `)):((0,n.openBlock)(),(0,n.createElementBlock)(`button`,{key:2,type:`button`,class:`vti-kb__key`,onPointerdown:t=>S(t,e)},(0,n.toDisplayString)(e===`backspace`?`退格`:e===`mode:back`?D():e),41,y))],64))),128))])),64)),(0,n.createElementVNode)(`div`,b,[(0,n.createElementVNode)(`button`,{type:`button`,class:`vti-kb__key vti-kb__key--zero`,onPointerdown:t[8]||=e=>S(e,`0`)},` 0 `,32),(0,n.createElementVNode)(`button`,{type:`button`,class:`vti-kb__key vti-kb__key--done`,onPointerdown:t[9]||=e=>S(e,`done`)},` 完成 `,32)])],64)):((0,n.openBlock)(),(0,n.createElementBlock)(n.Fragment,{key:0},[((0,n.openBlock)(),(0,n.createElementBlock)(n.Fragment,null,(0,n.renderList)(w,(e,i)=>(0,n.createElementVNode)(`div`,{key:`l${i}`,class:`vti-kb__row`},[i===2?((0,n.openBlock)(),(0,n.createElementBlock)(`button`,{key:0,type:`button`,class:(0,n.normalizeClass)([`vti-kb__key vti-kb__key--fn`,{on:(0,n.unref)(r).state.shift}]),onPointerdown:t[0]||=e=>S(e,`shift`)},` ⇧ `,34)):(0,n.createCommentVNode)(``,!0),((0,n.openBlock)(!0),(0,n.createElementBlock)(n.Fragment,null,(0,n.renderList)(e,e=>((0,n.openBlock)(),(0,n.createElementBlock)(`button`,{key:e,type:`button`,class:`vti-kb__key vti-kb__key--letter`,onPointerdown:t=>S(t,e)},(0,n.toDisplayString)(E(e)),41,h))),128)),i===2?((0,n.openBlock)(),(0,n.createElementBlock)(`button`,{key:1,type:`button`,class:`vti-kb__key vti-kb__key--fn`,onPointerdown:t[1]||=e=>S(e,`backspace`)},` 退格 `,32)):(0,n.createCommentVNode)(``,!0)])),64)),(0,n.createElementVNode)(`div`,g,[(0,n.createElementVNode)(`button`,{type:`button`,class:`vti-kb__key vti-kb__key--fn`,onPointerdown:t[2]||=e=>S(e,`mode:num`)},` 123 `,32),(0,n.createElementVNode)(`button`,{type:`button`,class:`vti-kb__key vti-kb__key--fn`,onPointerdown:t[3]||=e=>S(e,(0,n.unref)(r).state.mode===`zh`?`mode:en`:`mode:zh`)},(0,n.toDisplayString)((0,n.unref)(r).state.mode===`zh`?`英`:`拼`),33),(0,n.createElementVNode)(`button`,{type:`button`,class:`vti-kb__key vti-kb__key--space`,onPointerdown:t[4]||=e=>S(e,`space`)},` 空格 `,32),(0,n.createElementVNode)(`button`,{type:`button`,class:`vti-kb__key vti-kb__key--fn`,onPointerdown:t[5]||=e=>S(e,(0,n.unref)(r).state.mode===`zh`?`,`:`,`)},(0,n.toDisplayString)((0,n.unref)(r).state.mode===`zh`?`,`:`,`),33),(0,n.createElementVNode)(`button`,{type:`button`,class:`vti-kb__key vti-kb__key--fn`,onPointerdown:t[6]||=e=>S(e,(0,n.unref)(r).state.mode===`zh`?`。`:`.`)},(0,n.toDisplayString)((0,n.unref)(r).state.mode===`zh`?`。`:`.`),33),(0,n.createElementVNode)(`button`,{type:`button`,class:`vti-kb__key vti-kb__key--done`,onPointerdown:t[7]||=e=>S(e,`done`)},` 完成 `,32)])],64))])],38))}});exports.KIND_OVERRIDE_VALUES=t.KIND_OVERRIDE_VALUES,exports.PINYIN_CHARS=e.PINYIN_CHARS,exports.PINYIN_PHRASES=e.PINYIN_PHRASES,exports.SoftKeyboard=x,exports.applyCharToDraft=t.applyCharToDraft,exports.backspaceDraft=t.backspaceDraft,exports.backspaceTextInput=t.backspaceTextInput,exports.candidatesFor=t.candidatesFor,exports.createSoftKeyboard=a,exports.draftToDomValue=t.draftToDomValue,exports.hasFullSelection=t.hasFullSelection,exports.insertIntoTextInput=t.insertIntoTextInput,exports.isCharAllowed=t.isCharAllowed,exports.isSoftInput=i,exports.normalizeDraft=t.normalizeDraft,exports.resolveBufferCommit=t.resolveBufferCommit,exports.resolveSoftKind=t.resolveSoftKind,exports.resolveSoftKindWithOverride=t.resolveSoftKindWithOverride,exports.softKeyboard=o,exports.useSoftKeyboard=s,exports.writeDraft=t.writeDraft,exports.writeInputValue=t.writeInputValue;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * vue-touch-ime · 公开 API
3
+ *
4
+ * - SoftKeyboard:软键盘组件(挂到页面根部即可,输入框自动接管)
5
+ * - useSoftKeyboard / createSoftKeyboard:键盘会话(单例 / 独立实例)
6
+ * - 纯逻辑(也可从 `@junerver/vue-touch-ime/ime` 引入):类型推断、拼音候选、
7
+ * input 写值工具
8
+ * - 词典(也可从 `@junerver/vue-touch-ime/dict` 引入):PINYIN_CHARS / PINYIN_PHRASES
9
+ */
10
+ export { default as SoftKeyboard } from './SoftKeyboard.vue';
11
+ export { createSoftKeyboard, isSoftInput, softKeyboard, useSoftKeyboard, type SoftKeyboard as SoftKeyboardInstance, type SoftKeyboardState, } from './useSoftKeyboard';
12
+ export { applyCharToDraft, backspaceDraft, backspaceTextInput, candidatesFor, draftToDomValue, hasFullSelection, insertIntoTextInput, isCharAllowed, KIND_OVERRIDE_VALUES, normalizeDraft, resolveBufferCommit, resolveSoftKind, resolveSoftKindWithOverride, writeDraft, writeInputValue, type SoftCandidate, type SoftKbKind, type SoftKbMode, } from './ime';
13
+ export { PINYIN_CHARS, PINYIN_PHRASES } from './pinyinDict';