js_lis 1.0.5 → 1.0.7
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/VirtualKeyboard.js +437 -0
- package/layouts.js +44 -352
- package/main.js +4 -0
- package/package.json +1 -1
- package/keyboard.js +0 -514
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { layouts } from './layouts.js';
|
|
2
|
+
|
|
3
|
+
export class VirtualKeyboard {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.currentLayout = 'en';
|
|
6
|
+
this.isVisible = false;
|
|
7
|
+
this.container = document.getElementById('keyboard-container');
|
|
8
|
+
this.currentInput = null;
|
|
9
|
+
this.layouts = layouts;
|
|
10
|
+
this.isDragging = false;
|
|
11
|
+
this.offsetX = 0;
|
|
12
|
+
this.offsetY = 0;
|
|
13
|
+
this.shiftActive = false;
|
|
14
|
+
this.capsLockActive = false;
|
|
15
|
+
|
|
16
|
+
this.render();
|
|
17
|
+
this.initializeInputListeners();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getLayoutName(layout) {
|
|
21
|
+
switch (layout) {
|
|
22
|
+
case 'en': return 'English Keyboard';
|
|
23
|
+
case 'enSc': return 'English scrambled';
|
|
24
|
+
case 'th': return 'Thai keyboard';
|
|
25
|
+
case 'thSc': return 'Thai scrambled';
|
|
26
|
+
case 'numpad': return 'Numpad Keyboard';
|
|
27
|
+
case 'scNum': return 'Scrambled Keyboard';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
initializeInputListeners() {
|
|
32
|
+
// เพิ่ม event listener สำหรับทุก input และ textarea
|
|
33
|
+
document.addEventListener('click', (e) => {
|
|
34
|
+
const target = e.target;
|
|
35
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
|
|
36
|
+
this.setCurrentInput(target);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// เพิ่ม event listener สำหรับ focus
|
|
41
|
+
document.addEventListener('focus', (e) => {
|
|
42
|
+
const target = e.target;
|
|
43
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
|
|
44
|
+
this.setCurrentInput(target);
|
|
45
|
+
}
|
|
46
|
+
}, true);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
setCurrentInput(inputElement) {
|
|
50
|
+
// ถ้ามี input เก่า ให้ลบ class active
|
|
51
|
+
if (this.currentInput) {
|
|
52
|
+
this.currentInput.classList.remove('keyboard-active');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// เซ็ต input ใหม่และเพิ่ม class active
|
|
56
|
+
this.currentInput = inputElement;
|
|
57
|
+
this.currentInput.classList.add('keyboard-active');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
render() {
|
|
61
|
+
const keyboard = document.createElement('div');
|
|
62
|
+
keyboard.className = 'virtual-keyboard';
|
|
63
|
+
keyboard.style.display = this.isVisible ? 'block' : 'none';
|
|
64
|
+
|
|
65
|
+
// Add the ID for easy reference in dragging
|
|
66
|
+
keyboard.id = 'keyboard';
|
|
67
|
+
|
|
68
|
+
// New: Add Layout selector at the top inside the keyboard div
|
|
69
|
+
const controlsContainer = document.createElement('div');
|
|
70
|
+
controlsContainer.className = 'controls';
|
|
71
|
+
controlsContainer.style.display = 'flex';
|
|
72
|
+
controlsContainer.style.justifyContent = 'center';
|
|
73
|
+
controlsContainer.style.alignItems = 'center';
|
|
74
|
+
controlsContainer.style.marginBottom = '10px'; // Optional: Add some space below the selector
|
|
75
|
+
|
|
76
|
+
const layoutSelector = document.createElement('select');
|
|
77
|
+
layoutSelector.id = 'layout-selector';
|
|
78
|
+
layoutSelector.onchange = (e) => this.changeLayout(e.target.value);
|
|
79
|
+
|
|
80
|
+
const layouts = ['en', 'enSc', 'th', 'thSc', 'numpad', 'scNum'];
|
|
81
|
+
layouts.forEach(layout => {
|
|
82
|
+
const option = document.createElement('option');
|
|
83
|
+
option.value = layout;
|
|
84
|
+
option.innerText = this.getLayoutName(layout);
|
|
85
|
+
layoutSelector.appendChild(option);
|
|
86
|
+
});
|
|
87
|
+
layoutSelector.value = this.currentLayout;
|
|
88
|
+
controlsContainer.appendChild(layoutSelector);
|
|
89
|
+
|
|
90
|
+
keyboard.appendChild(controlsContainer);
|
|
91
|
+
|
|
92
|
+
const layout = this.layouts[this.currentLayout];
|
|
93
|
+
|
|
94
|
+
layout.forEach(row => {
|
|
95
|
+
const rowElement = document.createElement('div');
|
|
96
|
+
rowElement.className = 'keyboard-row';
|
|
97
|
+
|
|
98
|
+
row.forEach(key => {
|
|
99
|
+
const keyElement = document.createElement('button');
|
|
100
|
+
keyElement.className = 'keyboard-key key'; // Add key class
|
|
101
|
+
keyElement.textContent = key;
|
|
102
|
+
keyElement.type = 'button';
|
|
103
|
+
|
|
104
|
+
// Add data-key to each key for reference
|
|
105
|
+
keyElement.dataset.key = key;
|
|
106
|
+
|
|
107
|
+
if (key === 'Space') {
|
|
108
|
+
keyElement.className += ' space';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (key === 'backspace') {
|
|
112
|
+
keyElement.className += ' backspacew';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
keyElement.onclick = (e) => {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
const keyPressed = keyElement.dataset.key || keyElement.textContent;
|
|
118
|
+
if (keyPressed) {
|
|
119
|
+
this.handleKeyPress(keyPressed);
|
|
120
|
+
} else {
|
|
121
|
+
console.error("The key element does not have a valid key value.");
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
rowElement.appendChild(keyElement);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
keyboard.appendChild(rowElement);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
this.container.innerHTML = '';
|
|
132
|
+
this.container.appendChild(keyboard);
|
|
133
|
+
|
|
134
|
+
if (this.currentLayout === "scNum") {
|
|
135
|
+
this.scrambleKeyboard();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (this.currentLayout === "enSc") {
|
|
139
|
+
this.scrambleEnglishKeys();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (this.currentLayout === "thSc") {
|
|
143
|
+
this.scrambleThaiKeys();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Add drag functionality to the keyboard
|
|
147
|
+
keyboard.addEventListener('mousedown', (event) => this.startDrag(event));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
handleKeyPress(keyPressed) {
|
|
153
|
+
if (!this.currentInput) return;
|
|
154
|
+
|
|
155
|
+
const start = this.currentInput.selectionStart;
|
|
156
|
+
const end = this.currentInput.selectionEnd;
|
|
157
|
+
const value = this.currentInput.value;
|
|
158
|
+
|
|
159
|
+
const isCapsActive = this.capsLockActive;
|
|
160
|
+
const isShiftActive = this.shiftActive;
|
|
161
|
+
|
|
162
|
+
const convertToCorrectCase = (char) => {
|
|
163
|
+
if (isCapsActive || isShiftActive) {
|
|
164
|
+
return char.toUpperCase();
|
|
165
|
+
}
|
|
166
|
+
return char.toLowerCase();
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// ตรวจสอบค่า keyPressed
|
|
170
|
+
if (!keyPressed) {
|
|
171
|
+
console.error("Invalid key pressed.");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
switch(keyPressed) {
|
|
176
|
+
case 'Backspace':
|
|
177
|
+
case 'backspace':
|
|
178
|
+
if (start === end && start > 0) {
|
|
179
|
+
this.currentInput.value = value.slice(0, start - 1) + value.slice(end);
|
|
180
|
+
this.currentInput.selectionStart = this.currentInput.selectionEnd = start - 1;
|
|
181
|
+
} else {
|
|
182
|
+
this.currentInput.value = value.slice(0, start) + value.slice(end);
|
|
183
|
+
this.currentInput.selectionStart = this.currentInput.selectionEnd = start;
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
|
|
187
|
+
case 'Space':
|
|
188
|
+
this.insertText(' ');
|
|
189
|
+
break;
|
|
190
|
+
|
|
191
|
+
case 'Tab':
|
|
192
|
+
this.insertText('\t');
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case 'Enter':
|
|
196
|
+
if (this.currentInput.tagName === 'TEXTAREA') {
|
|
197
|
+
this.insertText('\n');
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
|
|
201
|
+
case 'Caps':
|
|
202
|
+
this.toggleCapsLock();
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
case 'Shift':
|
|
206
|
+
this.toggleShift();
|
|
207
|
+
break;
|
|
208
|
+
|
|
209
|
+
default:
|
|
210
|
+
this.insertText(convertToCorrectCase(keyPressed));
|
|
211
|
+
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (isShiftActive && !isCapsActive) {
|
|
215
|
+
this.toggleShift();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
this.currentInput.focus();
|
|
219
|
+
const event = new Event('input', { bubbles: true });
|
|
220
|
+
this.currentInput.dispatchEvent(event);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
toggleCapsLock() {
|
|
224
|
+
this.capsLockActive = !this.capsLockActive;
|
|
225
|
+
const capsKey = document.querySelector('.key[data-key="Caps"]');
|
|
226
|
+
capsKey.classList.toggle("active", this.capsLockActive);
|
|
227
|
+
capsKey.classList.toggle("bg-gray-400", this.capsLockActive);
|
|
228
|
+
|
|
229
|
+
document.querySelectorAll(".key").forEach((key) => {
|
|
230
|
+
if (key.dataset.key.length === 1 && /[a-zA-Zก-๙]/.test(key.dataset.key)) {
|
|
231
|
+
key.textContent = this.capsLockActive
|
|
232
|
+
? key.dataset.key.toUpperCase()
|
|
233
|
+
: key.dataset.key.toLowerCase();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const keyboardKeys = document.querySelectorAll(".key:not([data-key='Shift'])");
|
|
238
|
+
keyboardKeys.forEach((key) => {
|
|
239
|
+
const currentChar = key.textContent.trim();
|
|
240
|
+
if (
|
|
241
|
+
this.capsLockActive &&
|
|
242
|
+
this.currentLayout === "th" &&
|
|
243
|
+
this.ThaiAlphabetShift[currentChar]
|
|
244
|
+
) {
|
|
245
|
+
key.textContent = this.ThaiAlphabetShift[currentChar];
|
|
246
|
+
key.dataset.key = this.ThaiAlphabetShift[currentChar];
|
|
247
|
+
} else if (
|
|
248
|
+
!this.capsLockActive &&
|
|
249
|
+
this.currentLayout === "th" &&
|
|
250
|
+
Object.values(this.ThaiAlphabetShift).includes(currentChar)
|
|
251
|
+
) {
|
|
252
|
+
const originalKey = Object.keys(this.ThaiAlphabetShift).find(
|
|
253
|
+
(key) => this.ThaiAlphabetShift[key] === currentChar
|
|
254
|
+
);
|
|
255
|
+
if (originalKey) {
|
|
256
|
+
key.textContent = originalKey;
|
|
257
|
+
key.dataset.key = originalKey;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
toggleShift() {
|
|
264
|
+
this.shiftActive = !this.shiftActive;
|
|
265
|
+
const shiftKey = document.querySelector('.key[data-key="Shift"]');
|
|
266
|
+
shiftKey.classList.toggle("active", this.shiftActive);
|
|
267
|
+
shiftKey.classList.toggle("bg-gray-400", this.shiftActive);
|
|
268
|
+
|
|
269
|
+
document.querySelectorAll(".key").forEach((key) => {
|
|
270
|
+
if (key.dataset.key.length === 1 && /[a-zA-Zก-๙]/.test(key.dataset.key)) {
|
|
271
|
+
// แสดงผลตัวพิมพ์ใหญ่หรือเล็กตามค่า Shift
|
|
272
|
+
key.textContent = this.shiftActive
|
|
273
|
+
? key.dataset.key.toUpperCase()
|
|
274
|
+
: key.dataset.key.toLowerCase();
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const keyboardKeys = document.querySelectorAll(".key:not([data-key='Shift'])");
|
|
279
|
+
keyboardKeys.forEach((key) => {
|
|
280
|
+
const currentChar = key.textContent.trim();
|
|
281
|
+
if (
|
|
282
|
+
this.shiftActive &&
|
|
283
|
+
this.currentLayout === "th" && // ตรวจสอบว่ากำลังใช้เลย์เอาต์ภาษาไทย
|
|
284
|
+
this.ThaiAlphabetShift[currentChar]
|
|
285
|
+
) {
|
|
286
|
+
key.textContent = this.ThaiAlphabetShift[currentChar]; // แสดงอักษรจาก shift
|
|
287
|
+
key.dataset.key = this.ThaiAlphabetShift[currentChar]; // อัปเดต dataset.key
|
|
288
|
+
} else if (
|
|
289
|
+
!this.shiftActive &&
|
|
290
|
+
this.currentLayout === "th" &&
|
|
291
|
+
Object.values(this.ThaiAlphabetShift).includes(currentChar)
|
|
292
|
+
) {
|
|
293
|
+
const originalKey = Object.keys(this.ThaiAlphabetShift).find(
|
|
294
|
+
(key) => this.ThaiAlphabetShift[key] === currentChar
|
|
295
|
+
);
|
|
296
|
+
if (originalKey) {
|
|
297
|
+
key.textContent = originalKey;
|
|
298
|
+
key.dataset.key = originalKey;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
ThaiAlphabetShift = {
|
|
305
|
+
_: "%",
|
|
306
|
+
ๅ: "+",
|
|
307
|
+
"/": "๑",
|
|
308
|
+
"-": "๒",
|
|
309
|
+
ภ: "๓",
|
|
310
|
+
ถ: "๔",
|
|
311
|
+
"ุ": "ู",
|
|
312
|
+
"ึ": "฿",
|
|
313
|
+
ค: "๕",
|
|
314
|
+
ต: "๖",
|
|
315
|
+
จ: "๗",
|
|
316
|
+
ข: "๘",
|
|
317
|
+
ช: "๙",
|
|
318
|
+
ๆ: "๐",
|
|
319
|
+
ไ: '"',
|
|
320
|
+
ำ: "ฎ",
|
|
321
|
+
พ: "ฑ",
|
|
322
|
+
ะ: "ธ",
|
|
323
|
+
"ั": "ํ",
|
|
324
|
+
"ี": "๋",
|
|
325
|
+
ร: "ณ",
|
|
326
|
+
น: "ฯ",
|
|
327
|
+
ย: "ญ",
|
|
328
|
+
บ: "ฐ",
|
|
329
|
+
ล: ",",
|
|
330
|
+
ฃ: "ฅ",
|
|
331
|
+
ฟ: "ฤ",
|
|
332
|
+
ห: "ฆ",
|
|
333
|
+
ก: "ฏ",
|
|
334
|
+
ด: "โ",
|
|
335
|
+
เ: "ฌ",
|
|
336
|
+
"้": "็",
|
|
337
|
+
"่": "๋",
|
|
338
|
+
า: "ษ",
|
|
339
|
+
ส: "ศ",
|
|
340
|
+
ว: "ซ",
|
|
341
|
+
ง: ".",
|
|
342
|
+
ผ: "(",
|
|
343
|
+
ป: ")",
|
|
344
|
+
แ: "ฉ",
|
|
345
|
+
อ: "ฮ",
|
|
346
|
+
"ิ": "ฺ",
|
|
347
|
+
"ื": "์",
|
|
348
|
+
ท: "?",
|
|
349
|
+
ม: "ฒ",
|
|
350
|
+
ใ: "ฬ",
|
|
351
|
+
ฝ: "ฦ",
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
insertText(text) {
|
|
355
|
+
const start = this.currentInput.selectionStart;
|
|
356
|
+
const end = this.currentInput.selectionEnd;
|
|
357
|
+
this.currentInput.value = this.currentInput.value.slice(0, start) + text + this.currentInput.value.slice(end);
|
|
358
|
+
this.currentInput.selectionStart = this.currentInput.selectionEnd = start + text.length;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
toggle() {
|
|
362
|
+
this.isVisible = !this.isVisible;
|
|
363
|
+
this.render();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
changeLayout(layout) {
|
|
367
|
+
this.currentLayout = layout;
|
|
368
|
+
this.render();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
shuffleArray(array) {
|
|
372
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
373
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
374
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
scrambleKeyboard() {
|
|
379
|
+
const keys = document.querySelectorAll(
|
|
380
|
+
".key:not([data-key=backspace]):not([data-key='+']):not([data-key='-']):not([data-key='*']):not([data-key='/']):not([data-key='%']):not([data-key='=']):not([data-key='.']):not([data-key='00'])"
|
|
381
|
+
);
|
|
382
|
+
const numbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"];
|
|
383
|
+
this.shuffleArray(numbers);
|
|
384
|
+
keys.forEach((key, index) => {
|
|
385
|
+
key.textContent = numbers[index];
|
|
386
|
+
key.dataset.key = numbers[index];
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
scrambleEnglishKeys() {
|
|
391
|
+
const keys = document.querySelectorAll(
|
|
392
|
+
".key:not([data-key='Space']):not([data-key='Backspace']):not([data-key='Caps']):not([data-key='Shift']):not([data-key='Enter']):not([data-key='Tab']):not([data-key='`']):not([data-key='1']):not([data-key='2']):not([data-key='3']):not([data-key='4']):not([data-key='5']):not([data-key='6']):not([data-key='7']):not([data-key='8']):not([data-key='9']):not([data-key='0']):not([data-key='-']):not([data-key='+']):not([data-key='='])"
|
|
393
|
+
);
|
|
394
|
+
const englishAlphabet = "abcdefghijklmnopqrstuvwxyz".split("");
|
|
395
|
+
this.shuffleArray(englishAlphabet);
|
|
396
|
+
keys.forEach((key, index) => {
|
|
397
|
+
key.textContent = englishAlphabet[index];
|
|
398
|
+
key.dataset.key = englishAlphabet[index];
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
scrambleThaiKeys() {
|
|
403
|
+
const keys = document.querySelectorAll(
|
|
404
|
+
".key:not([data-key='Backspace']):not([data-key='Caps']):not([data-key='Shift']):not([data-key='Enter']):not([data-key='Space'])"
|
|
405
|
+
);
|
|
406
|
+
const ThaiAlphabet = "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ".split("");
|
|
407
|
+
this.shuffleArray(ThaiAlphabet); // สับเปลี่ยนลำดับของตัวอักษร
|
|
408
|
+
|
|
409
|
+
keys.forEach((key, index) => {
|
|
410
|
+
// อัพเดต textContent และ dataset.key ให้ตรงกับค่าใหม่ที่สับเปลี่ยน
|
|
411
|
+
key.textContent = ThaiAlphabet[index];
|
|
412
|
+
key.dataset.key = ThaiAlphabet[index]; // อัพเดต dataset.key ด้วยค่าใหม่
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
// จัดการการลากคีย์บอร์ด
|
|
418
|
+
startDrag(event) {
|
|
419
|
+
this.isDragging = true;
|
|
420
|
+
this.offsetX = event.clientX - document.getElementById("keyboard").offsetLeft;
|
|
421
|
+
this.offsetY = event.clientY - document.getElementById("keyboard").offsetTop;
|
|
422
|
+
|
|
423
|
+
document.addEventListener("mousemove", this.drag.bind(this)); // Use bind to preserve `this` context
|
|
424
|
+
document.addEventListener("mouseup", () => {
|
|
425
|
+
this.isDragging = false;
|
|
426
|
+
document.removeEventListener("mousemove", this.drag.bind(this));
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
drag(event) {
|
|
431
|
+
if (this.isDragging) {
|
|
432
|
+
const keyboard = document.getElementById("keyboard");
|
|
433
|
+
keyboard.style.left = `${event.clientX - this.offsetX}px`;
|
|
434
|
+
keyboard.style.top = `${event.clientY - this.offsetY}px`;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|