@@ -33,19 +33,21 @@ type Press = {
3333} ;
3434
3535const DEFAULT_LOCALE : Locale = "en" ;
36- const DOUBLE_TAP_MS = 300 ;
37- const LONG_PRESS_MS = 350 ;
38- const REPEAT_INITIAL_MS = 450 ;
39- const REPEAT_INTERVAL_MS = 35 ;
36+ const DEFAULT_DOUBLE_TAP_MS = 300 ;
37+ const DEFAULT_LONG_PRESS_MS = 350 ;
38+ const DEFAULT_REPEAT_INITIAL_MS = 450 ;
39+ const DEFAULT_REPEAT_INTERVAL_MS = 35 ;
4040
4141/** Hold-to-repeat binder.
42- * `fire` runs once on pointerdown, then every REPEAT_INTERVAL_MS after
43- * REPEAT_INITIAL_MS until pointerup/cancel, the signal aborts, or `fire`
44- * returns `false`. `true` or `void` means "keep repeating". */
42+ * `fire` runs once on pointerdown, then every `intervalMs` after `initialMs`
43+ * until pointerup/cancel, the signal aborts, or `fire` returns `false`.
44+ * `true` or `void` means "keep repeating". */
4545const attachRepeat = (
4646 btn : HTMLElement ,
4747 fire : ( ) => boolean | void ,
4848 signal : AbortSignal ,
49+ initialMs : number ,
50+ intervalMs : number ,
4951) : void => {
5052 let initial : number | null = null ;
5153 let interval : number | null = null ;
@@ -63,8 +65,8 @@ const attachRepeat = (
6365 if ( fire ( ) === false ) return ;
6466 initial = window . setTimeout ( ( ) => {
6567 initial = null ;
66- interval = window . setInterval ( run , REPEAT_INTERVAL_MS ) ;
67- } , REPEAT_INITIAL_MS ) ;
68+ interval = window . setInterval ( run , intervalMs ) ;
69+ } , initialMs ) ;
6870 } , { signal } ) ;
6971 btn . addEventListener ( "pointerup" , stop , { signal } ) ;
7072 btn . addEventListener ( "pointercancel" , stop , { signal } ) ;
@@ -82,7 +84,13 @@ const isRepeatableTopbar = (action: TopbarKey["action"]): boolean => {
8284} ;
8385
8486export class VirtualKeyboard extends HTMLElement {
85- static observedAttributes = [ "locale" ] ;
87+ static observedAttributes = [
88+ "locale" ,
89+ "double-tap-ms" ,
90+ "long-press-ms" ,
91+ "repeat-initial-ms" ,
92+ "repeat-interval-ms" ,
93+ ] ;
8694
8795 #state: State = {
8896 locale : DEFAULT_LOCALE ,
@@ -96,6 +104,10 @@ export class VirtualKeyboard extends HTMLElement {
96104 #adapter: OutputAdapter = nativeAdapter ( ) ;
97105 #press: Press | null = null ;
98106 #controller: AbortController | null = null ;
107+ #doubleTapMs = DEFAULT_DOUBLE_TAP_MS ;
108+ #longPressMs = DEFAULT_LONG_PRESS_MS ;
109+ #repeatInitialMs = DEFAULT_REPEAT_INITIAL_MS ;
110+ #repeatIntervalMs = DEFAULT_REPEAT_INTERVAL_MS ;
99111
100112 constructor ( ) {
101113 super ( ) ;
@@ -122,6 +134,23 @@ export class VirtualKeyboard extends HTMLElement {
122134 this . #state. layer = "letters" ;
123135 this . #state. shift = "off" ;
124136 if ( this . isConnected ) this . #render( ) ;
137+ return ;
138+ }
139+ const parsed = value === null ? NaN : Number ( value ) ;
140+ const ms = Number . isFinite ( parsed ) && parsed > 0 ? parsed : null ;
141+ switch ( name ) {
142+ case "double-tap-ms" :
143+ this . #doubleTapMs = ms ?? DEFAULT_DOUBLE_TAP_MS ;
144+ return ;
145+ case "long-press-ms" :
146+ this . #longPressMs = ms ?? DEFAULT_LONG_PRESS_MS ;
147+ return ;
148+ case "repeat-initial-ms" :
149+ this . #repeatInitialMs = ms ?? DEFAULT_REPEAT_INITIAL_MS ;
150+ return ;
151+ case "repeat-interval-ms" :
152+ this . #repeatIntervalMs = ms ?? DEFAULT_REPEAT_INTERVAL_MS ;
153+ return ;
125154 }
126155 }
127156
@@ -210,7 +239,7 @@ export class VirtualKeyboard extends HTMLElement {
210239 }
211240 const signal = this . #controller! . signal ;
212241 if ( isRepeatableTopbar ( key . action ) ) {
213- attachRepeat ( btn , ( ) : boolean | void => this . #handleTopbar( key ) , signal ) ;
242+ attachRepeat ( btn , ( ) : boolean | void => this . #handleTopbar( key ) , signal , this . #repeatInitialMs , this . #repeatIntervalMs ) ;
214243 } else {
215244 let startX = 0 ;
216245 let startY = 0 ;
@@ -277,7 +306,7 @@ export class VirtualKeyboard extends HTMLElement {
277306 #toggleModifier( modifier : Modifier ) : void {
278307 const now = performance . now ( ) ;
279308 const prev = this . #lastModifierTap. get ( modifier ) ?? 0 ;
280- const doubleTap = now - prev < DOUBLE_TAP_MS ;
309+ const doubleTap = now - prev < this . #doubleTapMs ;
281310 this . #lastModifierTap. set ( modifier , now ) ;
282311 const current = this . #state. modifiers . get ( modifier ) ;
283312 if ( doubleTap && current === "armed" ) {
@@ -441,7 +470,7 @@ export class VirtualKeyboard extends HTMLElement {
441470 btn . addEventListener ( "pointerup" , ( e ) => this . #onPointerUp( e ) , { signal } ) ;
442471 btn . addEventListener ( "pointercancel" , ( ) => this . #cancelPress( ) , { signal } ) ;
443472 } else if ( isRepeatableKey ( key ) ) {
444- attachRepeat ( btn , ( ) => this . #handle( key ) , signal ) ;
473+ attachRepeat ( btn , ( ) => this . #handle( key ) , signal , this . #repeatInitialMs , this . #repeatIntervalMs ) ;
445474 } else {
446475 btn . addEventListener ( "pointerdown" , ( ) => this . #handle( key ) , { signal } ) ;
447476 }
@@ -486,7 +515,7 @@ export class VirtualKeyboard extends HTMLElement {
486515 selectedIndex : 0 ,
487516 committed : false ,
488517 } ;
489- press . timer = window . setTimeout ( ( ) => this . #openPopover( ) , LONG_PRESS_MS ) ;
518+ press . timer = window . setTimeout ( ( ) => this . #openPopover( ) , this . #longPressMs ) ;
490519 this . #press = press ;
491520 }
492521
@@ -648,7 +677,7 @@ export class VirtualKeyboard extends HTMLElement {
648677
649678 #toggleShift( ) : void {
650679 const now = performance . now ( ) ;
651- const isDoubleTap = now - this . #lastShiftTap < DOUBLE_TAP_MS ;
680+ const isDoubleTap = now - this . #lastShiftTap < this . #doubleTapMs ;
652681 this . #lastShiftTap = now ;
653682
654683 if ( isDoubleTap && this . #state. shift === "on" ) {
0 commit comments