@antha/input 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,815 @@
1
+ import { assertWrap } from '@augment-vir/assert';
2
+ import { clamp } from '@augment-vir/common';
3
+ import { nav, navAttribute, NavValue } from 'device-navigation';
4
+ import { classMap, css, defineElement, defineElementEvent, html, listen, nothing, onDomCreated, } from 'element-vir';
5
+ import { noNativeSpacing, noUserSelect, viraAnimationDurations, ViraBoldText, ViraInput, ViraTextArea, viraTheme, } from 'vira';
6
+ /** @category Internal */
7
+ export var AnthaKeyboardSpecialKey;
8
+ (function (AnthaKeyboardSpecialKey) {
9
+ AnthaKeyboardSpecialKey["Backspace"] = "backspace";
10
+ AnthaKeyboardSpecialKey["Enter"] = "enter";
11
+ AnthaKeyboardSpecialKey["Tab"] = "tab";
12
+ AnthaKeyboardSpecialKey["NavLeft"] = "nav-left";
13
+ AnthaKeyboardSpecialKey["NavRight"] = "nav-right";
14
+ AnthaKeyboardSpecialKey["Paste"] = "paste";
15
+ AnthaKeyboardSpecialKey["HideKeyboard"] = "hide-keyboard";
16
+ AnthaKeyboardSpecialKey["CapsLock"] = "caps-lock";
17
+ AnthaKeyboardSpecialKey["LeftShift"] = "left-shift";
18
+ AnthaKeyboardSpecialKey["RightShift"] = "right-shift";
19
+ AnthaKeyboardSpecialKey["ClearAll"] = "clear-all";
20
+ })(AnthaKeyboardSpecialKey || (AnthaKeyboardSpecialKey = {}));
21
+ /** @category Internal */
22
+ export var ToggleKey;
23
+ (function (ToggleKey) {
24
+ ToggleKey["Shift"] = "shift";
25
+ ToggleKey["CapsLock"] = "caps-lock";
26
+ })(ToggleKey || (ToggleKey = {}));
27
+ var SpecialKeyLabelAlignment;
28
+ (function (SpecialKeyLabelAlignment) {
29
+ SpecialKeyLabelAlignment["Left"] = "left";
30
+ SpecialKeyLabelAlignment["Right"] = "right";
31
+ SpecialKeyLabelAlignment["Center"] = "center";
32
+ })(SpecialKeyLabelAlignment || (SpecialKeyLabelAlignment = {}));
33
+ const keyboardToggleHandlers = {
34
+ [ToggleKey.Shift]({ toggled }) {
35
+ return {
36
+ ...toggled,
37
+ [ToggleKey.Shift]: !toggled[ToggleKey.Shift],
38
+ };
39
+ },
40
+ [ToggleKey.CapsLock]({ toggled }) {
41
+ return {
42
+ ...toggled,
43
+ [ToggleKey.Shift]: false,
44
+ [ToggleKey.CapsLock]: !toggled[ToggleKey.CapsLock],
45
+ };
46
+ },
47
+ };
48
+ function pressKeyboardKey({ key, isUppercase, toggled, }) {
49
+ const keyPress = key.special
50
+ ? {
51
+ special: key.special,
52
+ }
53
+ : {
54
+ typedCharacter: key.shiftedKey
55
+ ? toggled[ToggleKey.Shift] && key.shiftedKey
56
+ ? key.shiftedKey
57
+ : key.key
58
+ : isUppercase
59
+ ? key.key.toUpperCase()
60
+ : key.key,
61
+ };
62
+ const toggledFromSpecial = key.toggleKey
63
+ ? keyboardToggleHandlers[key.toggleKey]({
64
+ toggled,
65
+ })
66
+ : undefined;
67
+ const toggledAfterShift = toggled[ToggleKey.Shift] && (key.shiftedKey || !key.special)
68
+ ? {
69
+ ...(toggledFromSpecial || toggled),
70
+ [ToggleKey.Shift]: false,
71
+ }
72
+ : toggledFromSpecial;
73
+ return {
74
+ keyPress,
75
+ toggled: toggledAfterShift,
76
+ };
77
+ }
78
+ /**
79
+ * An on-screen keyboard that works with `AnthaMenuNavMod` to allow navigation by controller.
80
+ *
81
+ * @category Pre-Build Mods
82
+ */
83
+ export const AnthaKeyboard = defineElement()({
84
+ tagName: 'antha-keyboard',
85
+ events: {
86
+ keyPress: defineElementEvent(),
87
+ valueChange: defineElementEvent(),
88
+ },
89
+ styles: css `
90
+ :host {
91
+ display: flex;
92
+ flex-direction: column;
93
+ gap: 16px;
94
+ font-family: sans-serif;
95
+ }
96
+
97
+ ${ViraTextArea}, ${ViraInput} {
98
+ width: 100%;
99
+ }
100
+
101
+ .entered-text {
102
+ cursor: pointer;
103
+ box-sizing: border-box;
104
+ width: 0;
105
+ max-width: 100%;
106
+ min-width: 100%;
107
+ overflow: hidden;
108
+ white-space: pre;
109
+ padding: 8px 16px;
110
+ height: calc(1em + 20px);
111
+ background: ${viraTheme.colors['vira-grey-behind-fg-highest-contrast'].background
112
+ .value};
113
+ border: 2px solid
114
+ ${viraTheme.colors['vira-grey-foreground-decoration'].foreground.value};
115
+ border-radius: 8px;
116
+
117
+ &:hover {
118
+ border-color: ${viraTheme.colors['vira-brand-foreground-header'].foreground.value};
119
+ }
120
+ }
121
+
122
+ .beam {
123
+ display: inline-block;
124
+ width: 3px;
125
+ height: calc(1em + 4px);
126
+ margin-top: -1px;
127
+ margin-right: -3px;
128
+ background: red;
129
+ vertical-align: middle;
130
+ animation: blink 1s linear infinite;
131
+ }
132
+ @keyframes blink {
133
+ 60% {
134
+ opacity: 1;
135
+ }
136
+ 80% {
137
+ opacity: 0.2;
138
+ }
139
+ 100% {
140
+ opacity: 1;
141
+ }
142
+ }
143
+
144
+ .keyboard-wrapper {
145
+ display: flex;
146
+ flex-direction: column;
147
+ gap: 4px;
148
+ ${noUserSelect}
149
+
150
+ & .row {
151
+ display: flex;
152
+ gap: inherit;
153
+ }
154
+
155
+ & button {
156
+ ${noNativeSpacing};
157
+ min-width: 40px;
158
+ height: 40px;
159
+ border: 1px solid
160
+ ${viraTheme.colors['vira-grey-foreground-header'].foreground.value};
161
+ border-radius: 4px;
162
+ background-color: ${viraTheme.colors['vira-grey-behind-fg-highest-contrast']
163
+ .background.value};
164
+ font: inherit;
165
+ cursor: pointer;
166
+ ${noUserSelect}
167
+
168
+ &.left-aligned {
169
+ text-align: left;
170
+ }
171
+
172
+ &.right-aligned {
173
+ text-align: right;
174
+ }
175
+
176
+ &.with-label {
177
+ padding: 0 8px;
178
+ }
179
+
180
+ &.toggled-on {
181
+ background-color: ${viraTheme.colors['vira-blue-behind-fg-small-body']
182
+ .background.value};
183
+ font-weight: bold;
184
+ }
185
+
186
+ & * {
187
+ pointer-events: none;
188
+ }
189
+
190
+ & ${ViraBoldText} {
191
+ ${noUserSelect}
192
+ }
193
+
194
+ & .key-label {
195
+ display: flex;
196
+ flex-direction: column;
197
+ align-items: center;
198
+ gap: 2px;
199
+
200
+ & .secondary-label {
201
+ font-size: 0.6em;
202
+ opacity: 0.6;
203
+ }
204
+ > span {
205
+ transition: font-size
206
+ ${viraAnimationDurations['vira-interaction-animation-duration'].value};
207
+ }
208
+ }
209
+ }
210
+
211
+ &
212
+ ${navAttribute.css({
213
+ baseSelector: 'button',
214
+ navValue: NavValue.Focused,
215
+ })} {
216
+ font-weight: bold;
217
+ outline: 4px solid
218
+ ${viraTheme.colors['vira-brand-foreground-header'].foreground.value};
219
+ outline-offset: -1px;
220
+
221
+ &:not(.toggled-on) {
222
+ background-color: ${viraTheme.colors['vira-grey-behind-fg-small-body']
223
+ .background.value};
224
+ }
225
+ }
226
+
227
+ &
228
+ ${navAttribute.css({
229
+ baseSelector: 'button',
230
+ navValue: NavValue.Active,
231
+ })} {
232
+ font-weight: bold;
233
+ outline: 4px solid
234
+ ${viraTheme.colors['vira-brand-foreground-non-body'].foreground.value};
235
+ outline-offset: -1px;
236
+ margin-top: 1px;
237
+ margin-bottom: -1px;
238
+
239
+ &:not(.toggled-on) {
240
+ background-color: ${viraTheme.colors['vira-grey-behind-fg-body'].background
241
+ .value};
242
+ }
243
+ }
244
+
245
+ & .wide-key {
246
+ padding: 0 10px;
247
+ flex-grow: 1;
248
+ }
249
+ }
250
+ `,
251
+ state() {
252
+ return {
253
+ value: '',
254
+ toggled: {},
255
+ cursorPosition: 0,
256
+ beamElement: undefined,
257
+ arrowRepeatDelay: undefined,
258
+ arrowRepeatInterval: undefined,
259
+ };
260
+ },
261
+ render({ dispatch, events, inputs, state, updateState }) {
262
+ const keyboardRows = [
263
+ [
264
+ {
265
+ key: '`',
266
+ shiftedKey: '~',
267
+ navX: 1,
268
+ },
269
+ {
270
+ key: '1',
271
+ shiftedKey: '!',
272
+ navX: 2,
273
+ },
274
+ {
275
+ key: '2',
276
+ shiftedKey: '@',
277
+ navX: 3,
278
+ },
279
+ {
280
+ key: '3',
281
+ shiftedKey: '#',
282
+ navX: 4,
283
+ },
284
+ {
285
+ key: '4',
286
+ shiftedKey: '$',
287
+ navX: 5,
288
+ },
289
+ {
290
+ key: '5',
291
+ shiftedKey: '%',
292
+ navX: 6,
293
+ },
294
+ {
295
+ key: '6',
296
+ shiftedKey: '^',
297
+ navX: 7,
298
+ },
299
+ {
300
+ key: '7',
301
+ shiftedKey: '&',
302
+ navX: 8,
303
+ },
304
+ {
305
+ key: '8',
306
+ shiftedKey: '*',
307
+ navX: 9,
308
+ },
309
+ {
310
+ key: '9',
311
+ shiftedKey: '(',
312
+ navX: 10,
313
+ },
314
+ {
315
+ key: '0',
316
+ shiftedKey: ')',
317
+ navX: 11,
318
+ },
319
+ {
320
+ key: '-',
321
+ shiftedKey: '_',
322
+ navX: 12,
323
+ },
324
+ {
325
+ key: '=',
326
+ shiftedKey: '+',
327
+ navX: 13,
328
+ },
329
+ {
330
+ special: AnthaKeyboardSpecialKey.Backspace,
331
+ isWide: true,
332
+ label: 'Backspace',
333
+ alignment: SpecialKeyLabelAlignment.Right,
334
+ navX: 14,
335
+ },
336
+ ],
337
+ [
338
+ {
339
+ special: AnthaKeyboardSpecialKey.Tab,
340
+ isWide: true,
341
+ alignment: SpecialKeyLabelAlignment.Left,
342
+ label: 'Tab',
343
+ navX: 1,
344
+ },
345
+ {
346
+ key: 'q',
347
+ navX: 3,
348
+ },
349
+ {
350
+ key: 'w',
351
+ navX: 4,
352
+ },
353
+ {
354
+ key: 'e',
355
+ navX: 5,
356
+ },
357
+ {
358
+ key: 'r',
359
+ navX: 6,
360
+ },
361
+ {
362
+ key: 't',
363
+ navX: 7,
364
+ },
365
+ {
366
+ key: 'y',
367
+ navX: 8,
368
+ },
369
+ {
370
+ key: 'u',
371
+ navX: 9,
372
+ },
373
+ {
374
+ key: 'i',
375
+ navX: 10,
376
+ },
377
+ {
378
+ key: 'o',
379
+ navX: 11,
380
+ },
381
+ {
382
+ key: 'p',
383
+ navX: 12,
384
+ },
385
+ {
386
+ key: '[',
387
+ shiftedKey: '{',
388
+ navX: 13,
389
+ },
390
+ {
391
+ key: ']',
392
+ shiftedKey: '}',
393
+ navX: 14,
394
+ },
395
+ {
396
+ key: '\\',
397
+ shiftedKey: '|',
398
+ navX: 15,
399
+ },
400
+ ],
401
+ [
402
+ {
403
+ special: AnthaKeyboardSpecialKey.CapsLock,
404
+ alignment: SpecialKeyLabelAlignment.Left,
405
+ toggleKey: ToggleKey.CapsLock,
406
+ isWide: true,
407
+ label: 'Caps',
408
+ navX: 1,
409
+ },
410
+ {
411
+ key: 'a',
412
+ navX: 3,
413
+ },
414
+ {
415
+ key: 's',
416
+ navX: 4,
417
+ },
418
+ {
419
+ key: 'd',
420
+ navX: 5,
421
+ },
422
+ {
423
+ key: 'f',
424
+ navX: 6,
425
+ },
426
+ {
427
+ key: 'g',
428
+ navX: 7,
429
+ },
430
+ {
431
+ key: 'h',
432
+ navX: 8,
433
+ },
434
+ {
435
+ key: 'j',
436
+ navX: 9,
437
+ },
438
+ {
439
+ key: 'k',
440
+ navX: 10,
441
+ },
442
+ {
443
+ key: 'l',
444
+ navX: 11,
445
+ },
446
+ {
447
+ key: ';',
448
+ shiftedKey: ':',
449
+ navX: 12,
450
+ },
451
+ {
452
+ key: "'",
453
+ shiftedKey: '"',
454
+ navX: 13,
455
+ },
456
+ {
457
+ special: AnthaKeyboardSpecialKey.Enter,
458
+ alignment: SpecialKeyLabelAlignment.Right,
459
+ isWide: true,
460
+ label: 'Enter',
461
+ navX: 14,
462
+ },
463
+ ],
464
+ [
465
+ {
466
+ special: AnthaKeyboardSpecialKey.LeftShift,
467
+ alignment: SpecialKeyLabelAlignment.Left,
468
+ toggleKey: ToggleKey.Shift,
469
+ isWide: true,
470
+ label: 'Shift',
471
+ navX: 1,
472
+ },
473
+ {
474
+ key: 'z',
475
+ navX: 3,
476
+ },
477
+ {
478
+ key: 'x',
479
+ navX: 4,
480
+ },
481
+ {
482
+ key: 'c',
483
+ navX: 5,
484
+ },
485
+ {
486
+ key: 'v',
487
+ navX: 6,
488
+ },
489
+ {
490
+ key: 'b',
491
+ navX: 7,
492
+ },
493
+ {
494
+ key: 'n',
495
+ navX: 8,
496
+ },
497
+ {
498
+ key: 'm',
499
+ navX: 9,
500
+ },
501
+ {
502
+ key: ',',
503
+ shiftedKey: '<',
504
+ navX: 10,
505
+ },
506
+ {
507
+ key: '.',
508
+ shiftedKey: '>',
509
+ navX: 11,
510
+ },
511
+ {
512
+ key: '/',
513
+ shiftedKey: '?',
514
+ navX: 12,
515
+ },
516
+ {
517
+ special: AnthaKeyboardSpecialKey.RightShift,
518
+ alignment: SpecialKeyLabelAlignment.Right,
519
+ toggleKey: ToggleKey.Shift,
520
+ label: 'Shift',
521
+ isWide: true,
522
+ navX: 14,
523
+ navWidth: 2,
524
+ },
525
+ ],
526
+ [
527
+ {
528
+ special: AnthaKeyboardSpecialKey.ClearAll,
529
+ alignment: SpecialKeyLabelAlignment.Left,
530
+ label: 'Clear',
531
+ navX: 0,
532
+ },
533
+ {
534
+ key: ' ',
535
+ navX: 1,
536
+ navWidth: 11,
537
+ isWide: true,
538
+ label: 'Space',
539
+ },
540
+ {
541
+ special: AnthaKeyboardSpecialKey.NavLeft,
542
+ alignment: SpecialKeyLabelAlignment.Center,
543
+ label: '←',
544
+ navX: 12,
545
+ },
546
+ {
547
+ special: AnthaKeyboardSpecialKey.NavRight,
548
+ alignment: SpecialKeyLabelAlignment.Center,
549
+ label: '→',
550
+ navX: 14,
551
+ },
552
+ {
553
+ special: AnthaKeyboardSpecialKey.Paste,
554
+ alignment: SpecialKeyLabelAlignment.Center,
555
+ label: 'Paste',
556
+ navX: 15,
557
+ },
558
+ {
559
+ special: AnthaKeyboardSpecialKey.HideKeyboard,
560
+ alignment: SpecialKeyLabelAlignment.Center,
561
+ label: 'Hide',
562
+ navX: 15,
563
+ hidden: !inputs.showHideButton,
564
+ },
565
+ ],
566
+ ];
567
+ const isUppercase = state.toggled[ToggleKey.CapsLock] || !!state.toggled[ToggleKey.Shift];
568
+ async function performKeyPress(key) {
569
+ const result = pressKeyboardKey({
570
+ key,
571
+ isUppercase,
572
+ toggled: state.toggled,
573
+ });
574
+ if (result.toggled) {
575
+ updateState({
576
+ toggled: result.toggled,
577
+ });
578
+ }
579
+ const oldValue = state.value;
580
+ const newValue = await handleKeyPress({
581
+ keyPress: result.keyPress,
582
+ currentValue: oldValue,
583
+ cursorPosition: state.cursorPosition,
584
+ maxLength: Math.round(Math.abs(inputs.maxLength || 0)) || 128,
585
+ });
586
+ updateState({
587
+ value: newValue,
588
+ });
589
+ if (result.keyPress.special === AnthaKeyboardSpecialKey.NavLeft) {
590
+ updateState({
591
+ cursorPosition: Math.max(state.cursorPosition - 1, 0),
592
+ });
593
+ }
594
+ else if (result.keyPress.special === AnthaKeyboardSpecialKey.NavRight) {
595
+ updateState({
596
+ cursorPosition: Math.min(state.cursorPosition + 1, state.value.length),
597
+ });
598
+ }
599
+ if (oldValue !== newValue) {
600
+ const lengthDiff = newValue.length - oldValue.length;
601
+ updateState({
602
+ cursorPosition: state.cursorPosition + lengthDiff,
603
+ });
604
+ dispatch(new events.valueChange(newValue));
605
+ }
606
+ if (state.beamElement) {
607
+ scrollBeamIntoView(state.beamElement);
608
+ }
609
+ dispatch(new events.keyPress(result.keyPress));
610
+ }
611
+ function stopArrowRepeat() {
612
+ if (state.arrowRepeatDelay != undefined) {
613
+ globalThis.clearTimeout(state.arrowRepeatDelay);
614
+ }
615
+ if (state.arrowRepeatInterval != undefined) {
616
+ globalThis.clearInterval(state.arrowRepeatInterval);
617
+ }
618
+ updateState({
619
+ arrowRepeatDelay: undefined,
620
+ arrowRepeatInterval: undefined,
621
+ });
622
+ }
623
+ function startArrowRepeat(key) {
624
+ /** Don't start a second timer if one is already running for the held key. */
625
+ if (state.arrowRepeatDelay != undefined || state.arrowRepeatInterval != undefined) {
626
+ return;
627
+ }
628
+ /** Hold duration before auto-repeat kicks in, then the interval between repeats. */
629
+ updateState({
630
+ arrowRepeatDelay: globalThis.setTimeout(() => {
631
+ updateState({
632
+ arrowRepeatInterval: globalThis.setInterval(() => {
633
+ void performKeyPress(key);
634
+ }, 80),
635
+ });
636
+ }, 400),
637
+ });
638
+ }
639
+ const keyboardTemplate = keyboardRows.map((row, y) => {
640
+ return html `
641
+ <div class="row">
642
+ ${row.map((key) => {
643
+ if (key.hidden) {
644
+ return;
645
+ }
646
+ const contents = key.label
647
+ ? html `
648
+ <${ViraBoldText.assign({
649
+ /** Boldness will be applied via CSS. */
650
+ bold: false,
651
+ text: key.label,
652
+ })}></${ViraBoldText}>
653
+ `
654
+ : key.shiftedKey
655
+ ? html `
656
+ <span class="key-label">
657
+ <span
658
+ class=${classMap({
659
+ 'secondary-label': !state.toggled[ToggleKey.Shift],
660
+ })}
661
+ >
662
+ ${key.shiftedKey}
663
+ </span>
664
+ <span
665
+ class=${classMap({
666
+ 'secondary-label': !!state.toggled[ToggleKey.Shift],
667
+ })}
668
+ >
669
+ ${key.key}
670
+ </span>
671
+ </span>
672
+ `
673
+ : isUppercase
674
+ ? assertWrap.isDefined(key.key).toUpperCase()
675
+ : assertWrap.isDefined(key.key);
676
+ return html `
677
+ <button
678
+ class=${classMap({
679
+ 'wide-key': !!key.isWide,
680
+ 'with-label': !!key.label,
681
+ 'toggled-on': !!key.toggleKey && !!state.toggled[key.toggleKey],
682
+ 'left-aligned': key.alignment === SpecialKeyLabelAlignment.Left,
683
+ 'right-aligned': key.alignment === SpecialKeyLabelAlignment.Right,
684
+ })}
685
+ ${nav(inputs.navController, {
686
+ x: key.navX,
687
+ width: key.navWidth,
688
+ y,
689
+ listeners: {
690
+ async activate({ enabled }) {
691
+ if (enabled) {
692
+ await performKeyPress(key);
693
+ if (key.special ===
694
+ AnthaKeyboardSpecialKey.NavLeft ||
695
+ key.special === AnthaKeyboardSpecialKey.NavRight) {
696
+ startArrowRepeat(key);
697
+ }
698
+ }
699
+ else {
700
+ stopArrowRepeat();
701
+ }
702
+ },
703
+ },
704
+ })}
705
+ >
706
+ ${contents}
707
+ </button>
708
+ `;
709
+ })}
710
+ </div>
711
+ `;
712
+ });
713
+ // prettier-ignore
714
+ const textContent = html `${state.value.slice(0, state.cursorPosition)}<span class="beam"
715
+ ${onDomCreated((element) => {
716
+ updateState({
717
+ beamElement: assertWrap.instanceOf(element, HTMLElement),
718
+ });
719
+ })}></span>${state.value.slice(state.cursorPosition)}`;
720
+ const textElement = inputs.hideText
721
+ ? nothing
722
+ : // prettier-ignore
723
+ html `
724
+ <div
725
+ class="entered-text"
726
+ ${listen('click', (event) => {
727
+ event.stopImmediatePropagation();
728
+ const newValue = prompt('', state.value);
729
+ if (newValue == null) {
730
+ return;
731
+ }
732
+ updateState({
733
+ value: newValue,
734
+ cursorPosition: newValue.length,
735
+ });
736
+ if (state.beamElement) {
737
+ scrollBeamIntoView(state.beamElement);
738
+ }
739
+ })}
740
+ >${textContent}</div>
741
+ `;
742
+ return html `
743
+ ${textElement}
744
+ <div class="keyboard-wrapper">${keyboardTemplate}</div>
745
+ `;
746
+ },
747
+ });
748
+ /**
749
+ * Scrolls the entered-text container so the cursor (beam) stays within the visible band, keeping
750
+ * `edgePadding` of space between the beam and either edge. Reads the beam's live layout position
751
+ * rather than measuring text substrings.
752
+ */
753
+ function scrollBeamIntoView(beam) {
754
+ requestAnimationFrame(() => {
755
+ const container = beam.parentElement;
756
+ /* c8 ignore next 3 */
757
+ if (!container) {
758
+ return;
759
+ }
760
+ const edgePadding = 24;
761
+ const beamRect = beam.getBoundingClientRect();
762
+ const containerRect = container.getBoundingClientRect();
763
+ if (beamRect.left < containerRect.left + edgePadding) {
764
+ container.scrollLeft -= containerRect.left + edgePadding - beamRect.left;
765
+ }
766
+ else if (beamRect.right > containerRect.right - edgePadding) {
767
+ container.scrollLeft += beamRect.right - (containerRect.right - edgePadding);
768
+ }
769
+ });
770
+ }
771
+ /**
772
+ * A helper for handling key press events from {@link AnthaKeyboard}. This is used internally in
773
+ * {@link AnthaKeyboard} but is useful if you want to hide the keyboard's built-in text pane and use
774
+ * your own processing.
775
+ *
776
+ * @category Internal
777
+ */
778
+ export async function handleKeyPress({ currentValue, keyPress, cursorPosition, maxLength, }) {
779
+ cursorPosition = clamp(cursorPosition, {
780
+ max: currentValue.length,
781
+ min: 0,
782
+ });
783
+ if (keyPress.special === AnthaKeyboardSpecialKey.ClearAll) {
784
+ return '';
785
+ }
786
+ else if (keyPress.special === AnthaKeyboardSpecialKey.Backspace) {
787
+ return [
788
+ currentValue.slice(0, Math.max(0, cursorPosition - 1)),
789
+ currentValue.slice(cursorPosition),
790
+ ].join('');
791
+ }
792
+ else if (currentValue.length >= maxLength) {
793
+ return currentValue;
794
+ }
795
+ else if (keyPress.typedCharacter) {
796
+ return [
797
+ currentValue.slice(0, cursorPosition),
798
+ keyPress.typedCharacter,
799
+ currentValue.slice(cursorPosition),
800
+ ].join('');
801
+ }
802
+ else if (keyPress.special === AnthaKeyboardSpecialKey.Paste) {
803
+ const maxAllowedPasteLength = maxLength - currentValue.length;
804
+ return [
805
+ currentValue.slice(0, cursorPosition),
806
+ (await globalThis.navigator.clipboard.readText())
807
+ .replaceAll(/[\n\r]/g, ' ')
808
+ .slice(0, maxAllowedPasteLength),
809
+ currentValue.slice(cursorPosition),
810
+ ].join('');
811
+ }
812
+ else {
813
+ return currentValue;
814
+ }
815
+ }