virtual-keypad 5.15.2 → 5.17.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.
@@ -0,0 +1,394 @@
1
+ import { applyMaxKeySize } from "./maxKeySize";
2
+ import "./keypad.css";
3
+
4
+ class PhonePeer {
5
+ constructor(
6
+ keypadParameters = {
7
+ targetElementId: null,
8
+ visualResponseFeedback: false,
9
+ }
10
+ ) {
11
+ // Module identification
12
+ this.name = "keypad";
13
+ this.type = "keypad";
14
+ this.connectionManager = null;
15
+
16
+ console.log("targetElementId: ", keypadParameters.targetElementId);
17
+
18
+ // Keypad configuration
19
+ this.startTime = Date.now();
20
+ this.targetElement = keypadParameters.targetElementId;
21
+ this.controlButtons = keypadParameters.controlButtons ?? [
22
+ "SPACE",
23
+ "RETURN",
24
+ ];
25
+ this.visualResponseFeedback = keypadParameters.visualResponseFeedback;
26
+ this.alphabet = null;
27
+ this.font = null;
28
+ this.headerMessage = "";
29
+ this.footerMessage = "";
30
+
31
+ // Set-up sound to play on press
32
+ this.pressFeedback = new Audio(this.keypressFeedbackSound);
33
+ }
34
+
35
+ // Required method for ConnectAPeer submodule registration
36
+ register = (manager) => {
37
+ this.connectionManager = manager;
38
+ console.log("Keypad module registered with connection manager");
39
+ };
40
+
41
+ // Required method for ConnectAPeer message handling
42
+ onMessage = (data, manager) => {
43
+ console.log("Keypad received data: ", data);
44
+
45
+ if (!data || !data.message) return;
46
+
47
+ switch (data.message) {
48
+ case "KeypadParameters":
49
+ this.alphabet = data.alphabet;
50
+ this.font = data.font;
51
+ this.controlButtons = data.controlButtons;
52
+ this.onErrorReconnectMessage = data.onErrorReconnectMessage;
53
+ this.initializeKeypad();
54
+ this.#populateKeypad();
55
+ break;
56
+ case "UpdateHeader":
57
+ document.getElementById("keypad-header").innerText = data.headerContent;
58
+ document.getElementById("keypad-header").style.display =
59
+ data.headerContent === "" ? "none" : "block";
60
+ this.headerMessage = data.headerContent;
61
+ this.#populateKeypad();
62
+ break;
63
+ case "UpdateFooter":
64
+ document.getElementById("keypad-footer").innerText = data.headerContent;
65
+ this.footerMessage = data.headerContent;
66
+ this.#populateKeypad();
67
+ break;
68
+ case "Update":
69
+ // Keypad has received data to update the keypad
70
+ if (
71
+ !data.hasOwnProperty("alphabet") &&
72
+ !data.hasOwnProperty("font") &&
73
+ !data.hasOwnProperty("controlButtons")
74
+ ) {
75
+ console.error(
76
+ 'Error in parsing data received! Must set "alphabet" or "font" properties'
77
+ );
78
+ } else {
79
+ this.alphabet = data.alphabet ?? this.alphabet;
80
+ this.font = data.font ?? this.font;
81
+ this.controlButtons = data.controlButtons ?? this.controlButtons;
82
+ }
83
+ this.#populateKeypad();
84
+ break;
85
+ case "Disable":
86
+ if (data.hasOwnProperty("keys")) {
87
+ this.disableKeys(data.keys);
88
+ } else {
89
+ this.disableKeys();
90
+ }
91
+ break;
92
+ case "Enable":
93
+ if (data.hasOwnProperty("keys")) {
94
+ this.enableKeys(data.keys);
95
+ } else {
96
+ this.enableKeys();
97
+ }
98
+ break;
99
+ case "Heartbeat":
100
+ this.lastHeartbeat = performance.now();
101
+ break;
102
+ case "InitializeKeypad":
103
+ this.initializeKeypad();
104
+ break;
105
+ default:
106
+ console.log("Unhandled message type: ", data.message);
107
+ }
108
+ };
109
+
110
+ #prepareHTML = () => {
111
+ /**
112
+ * ----------
113
+ * | Header |
114
+ * ----------
115
+ * |a b c d | <- keypad-keys
116
+ * |e f g h |
117
+ * |i j k l |
118
+ * ----------
119
+ * |space ret| <- keypad-control-keys
120
+ */
121
+ // Keypad elem is a container for a message and all keys
122
+ const keypadElem = document.createElement("div");
123
+ keypadElem.setAttribute("id", "keypad");
124
+ const keypadHeader = document.createElement("h1");
125
+ keypadHeader.setAttribute("id", "keypad-header");
126
+ keypadHeader.classList.add("noselect");
127
+ const keypadKeys = document.createElement("div");
128
+ keypadKeys.setAttribute("id", "keypad-keys");
129
+ keypadKeys.classList.add("keys");
130
+ const keypadControlKeys = document.createElement("div");
131
+ keypadControlKeys.setAttribute("id", "keypad-control-keys");
132
+ keypadControlKeys.classList.add("keys");
133
+ const keypadFooter = document.createElement("div");
134
+ keypadFooter.setAttribute("id", "keypad-footer");
135
+ keypadElem.appendChild(keypadHeader);
136
+ keypadKeys.appendChild(keypadControlKeys);
137
+ keypadElem.appendChild(keypadKeys);
138
+ keypadElem.appendChild(keypadFooter);
139
+
140
+ // Add keypad to page where specified
141
+ if (document.getElementById(this.targetElement)) {
142
+ console.log("Specified target element successfully used.");
143
+ const targetElement = document.getElementById(this.targetElement);
144
+ targetElement.innerHTML = "";
145
+ targetElement.appendChild(keypadElem);
146
+ } else {
147
+ console.log("No target element used.");
148
+ document.getElementsByTagName("main")[0].appendChild(keypadElem);
149
+ }
150
+
151
+ window.onresize = () => {
152
+ console.count("Window resized.");
153
+ applyMaxKeySize(this.alphabet?.length);
154
+ };
155
+ if (window.visualViewport)
156
+ window.visualViewport.onresize = () => {
157
+ console.count("VisualViewport resized.");
158
+ applyMaxKeySize(this.alphabet?.length);
159
+ };
160
+ };
161
+
162
+ #populateKeypad = () => {
163
+ const buttonResponseFn = (button) => {
164
+ // Send response message to experimentClient
165
+ const message = {
166
+ type: "keypad", // Add type for ConnectAPeer routing
167
+ message: "Keypress",
168
+ response: button.id,
169
+ timeSent: Date.now(),
170
+ elapsedStartToSend: Date.now() - window.startTime,
171
+ };
172
+
173
+ // Send the keypress through the connection manager
174
+ if (this.connectionManager) {
175
+ this.connectionManager.send(message);
176
+ console.log("Keypress sent: ", message);
177
+ } else {
178
+ console.log("Connection manager not available");
179
+ }
180
+
181
+ // Update keypad after a period of visible non-responsivity (ie ITI)
182
+ if (this.visualResponseFeedback) {
183
+ this.visualFeedbackThenReset(this.alphabet, this.font);
184
+ }
185
+ };
186
+
187
+ const createButton = (symbol) => {
188
+ // Create a response button for this symbol
189
+ let button = document.createElement("button");
190
+ button.id = symbol;
191
+ button.className = "response-button";
192
+ button.style.fontFamily = this.font;
193
+ button.style.visibility = "hidden";
194
+
195
+ const feedbackAudio = document.getElementById("feedbackAudio");
196
+
197
+ /* Set behavior for press */
198
+ // Sound on press...
199
+ button.addEventListener("touchstart", (e) => {
200
+ e.preventDefault();
201
+ console.log("touchstart event: ", e);
202
+ feedbackAudio.play();
203
+ });
204
+ button.addEventListener("mousedown", (e) => {
205
+ e.preventDefault();
206
+ feedbackAudio.play();
207
+ });
208
+ button.addEventListener("mouseup", (e) => {
209
+ e.preventDefault();
210
+ console.log("mouseup event: ", e);
211
+ switch (e.target.className) {
212
+ case "response-button-label noselect":
213
+ buttonResponseFn(e.target.parentElement);
214
+ break;
215
+ case "response-button":
216
+ buttonResponseFn(e.target);
217
+ break;
218
+ }
219
+ });
220
+ // ...send signal on release
221
+ button.addEventListener("touchmove", (e) => {
222
+ /* prevent delay and simulated mouse events */
223
+ e.preventDefault();
224
+ });
225
+ button.addEventListener("touchend", (e) => {
226
+ /* prevent delay and simulated mouse events */
227
+ e.preventDefault();
228
+ const elementEndedOn = document.elementFromPoint(
229
+ e.changedTouches[0].clientX,
230
+ e.changedTouches[0].clientY
231
+ );
232
+ switch (elementEndedOn?.className) {
233
+ case "response-button-label noselect":
234
+ buttonResponseFn(elementEndedOn?.parentElement);
235
+ break;
236
+ case "response-button":
237
+ buttonResponseFn(elementEndedOn);
238
+ break;
239
+ }
240
+ });
241
+
242
+ // Create a label for the button
243
+ let buttonLabel = document.createElement("span");
244
+ buttonLabel.classList.add("response-button-label", "noselect");
245
+ buttonLabel.innerText = symbol;
246
+ buttonLabel.style.fontFamily = this.font;
247
+
248
+ // Add the label to the button
249
+ button.appendChild(buttonLabel);
250
+ // Add the labeled-button to the HTML
251
+ if (
252
+ this.controlButtons
253
+ .map((x) => x.toLowerCase())
254
+ .includes(symbol.toLowerCase())
255
+ ) {
256
+ document.querySelector("#keypad-control-keys").appendChild(button);
257
+ } else {
258
+ document.querySelector("#keypad-keys").appendChild(button);
259
+ }
260
+ };
261
+
262
+ // Set-up an instruction/welcome message for the user
263
+ const header = document.getElementById("keypad-header");
264
+ header.innerText = this.headerMessage || "";
265
+ header.style.display = header.innerText === "" ? "none" : "block";
266
+ // Get the keypad element
267
+ const remoteControl = document.getElementById("keypad");
268
+
269
+ // Set-up audio element
270
+ const feedbackAudio = document.createElement("audio");
271
+ feedbackAudio.id = "feedbackAudio";
272
+ feedbackAudio.src = "onems.mp3";
273
+ header.appendChild(feedbackAudio);
274
+
275
+ // Set correct font for button labels
276
+ remoteControl.style.fontFamily = this.font;
277
+ // Remove previous buttons
278
+ this.clearKeys();
279
+ // Create new buttons
280
+ if (this.alphabet) {
281
+ this.alphabet.forEach((symbol) => createButton(symbol));
282
+ // Manually style buttons, according to Denis' algorithm
283
+ setTimeout(() => applyMaxKeySize(this.alphabet.length), 5);
284
+ }
285
+ };
286
+
287
+ visualFeedbackThenReset = (delayTime = 800) => {
288
+ // ie grey out keys just after use, to discourage rapid response
289
+ this.interResponseKeypadMessaging();
290
+ // Setup keys for the next trial
291
+ setTimeout(() => this.defaultKeypadMessaging(), delayTime);
292
+ };
293
+
294
+ defaultKeypadMessaging = (headerText = "") => {
295
+ // Set-up an instruction/welcome message for the user
296
+ const header = document.getElementById("keypad-header");
297
+ if (headerText === "") {
298
+ header.style.display = "none";
299
+ } else {
300
+ header.innerText = headerText;
301
+ header.style.display = "block";
302
+ }
303
+
304
+ // Make each button pressable
305
+ const buttons = document.getElementsByClassName("response-button");
306
+ [...buttons].forEach((element) => {
307
+ element.classList.remove("unpressable", "noselect");
308
+ });
309
+ };
310
+
311
+ interResponseKeypadMessaging = (
312
+ interResponseMessage = "Please fix your gaze on the + mark on your computer screen."
313
+ ) => {
314
+ // Change header text to reflect WAIT message
315
+ const header = document.getElementById("keypad-header");
316
+ header.innerText = interResponseMessage;
317
+
318
+ // Then make each button unpressable
319
+ const buttons = document.getElementsByClassName("response-button");
320
+ [...buttons].forEach((element) => {
321
+ element.classList.add("unpressable", "noselect");
322
+ });
323
+ };
324
+
325
+ /**
326
+ * Remove all keys from the keypad.
327
+ */
328
+ clearKeys = () => {
329
+ document.querySelector("#keypad-keys").innerHTML =
330
+ "<div id='keypad-control-keys'></div>";
331
+ };
332
+
333
+ /**
334
+ * Return the nodes corresponding to the specified keys.
335
+ * @param {string[]} whichKeys id's of keys to select. defaults to all keys.
336
+ * @returns {HTMLElement[]}
337
+ */
338
+ _getKeysElements = (whichKeys = []) => {
339
+ let keyElems = [
340
+ ...document
341
+ .querySelector("#keypad")
342
+ .getElementsByClassName("response-button"),
343
+ ];
344
+ if (whichKeys.length !== 0)
345
+ keyElems = keyElems.filter((e) => whichKeys.includes(e.id));
346
+ return keyElems;
347
+ };
348
+
349
+ /**
350
+ * Make selected keys unpressable.
351
+ */
352
+ disableKeys = (whichKeys = []) => {
353
+ const keyElems = this._getKeysElements(whichKeys);
354
+ keyElems.forEach((e) => {
355
+ e.classList.add("unpressable");
356
+ e.classList.add("noselect");
357
+ e.setAttribute("inert", "");
358
+ });
359
+ };
360
+
361
+ /**
362
+ * Make selected keys pressable.
363
+ */
364
+ enableKeys = (whichKeys = []) => {
365
+ const keyElems = this._getKeysElements(whichKeys);
366
+ keyElems.forEach((e) => {
367
+ e.classList.remove("unpressable");
368
+ e.classList.remove("noselect");
369
+ e.removeAttribute("inert");
370
+ });
371
+ };
372
+
373
+ /**
374
+ * Initialize the keypad DOM elements and set up event listeners.
375
+ * Call this method when you're ready to display the keypad.
376
+ */
377
+ initializeKeypad() {
378
+ this.#prepareHTML();
379
+ }
380
+ }
381
+
382
+ async function main() {
383
+ const pp = new PhonePeer({
384
+ targetElementId: "target",
385
+ });
386
+
387
+ if (window.phoneApp) {
388
+ window.phoneApp.registerSubmodule(pp);
389
+ } else {
390
+ console.log("PhoneApp not found");
391
+ }
392
+ }
393
+
394
+ main().catch((err) => console.error(err));
@@ -0,0 +1,80 @@
1
+ import { applyMaxKeySize } from '../maxKeySize';
2
+
3
+ describe('maxKeySize', () => {
4
+ beforeEach(() => {
5
+ // Setup DOM structure
6
+ document.body.innerHTML = `
7
+ <div id="keypad">
8
+ <div id="keypad-keys">
9
+ <div id="keypad-control-keys"></div>
10
+ </div>
11
+ </div>
12
+ `;
13
+ });
14
+
15
+ test('applies same font size to control buttons with long labels', () => {
16
+ // Add control buttons
17
+ const controlKeys = ['SKIP BLOCK', 'VERY LONG LABEL THAT WRAPS', 'RETURN'];
18
+ controlKeys.forEach((label) => {
19
+ const btn = document.createElement('button');
20
+ btn.className = 'response-button';
21
+ btn.innerHTML = `<span class="response-button-label">${label}</span>`;
22
+ document.getElementById('keypad-control-keys').appendChild(btn);
23
+ });
24
+
25
+ // Add regular keys
26
+ for (let i = 0; i < 8; i++) {
27
+ const btn = document.createElement('button');
28
+ btn.className = 'response-button';
29
+ btn.innerHTML = `<span class="response-button-label">${String.fromCharCode(65 + i)}</span>`;
30
+ document.getElementById('keypad-keys').appendChild(btn);
31
+ }
32
+
33
+ // Apply font sizing
34
+ applyMaxKeySize(8 + 3, 'sans-serif');
35
+
36
+ // Check that control buttons have same font size
37
+ const controlButtons = document.querySelectorAll('#keypad-control-keys .response-button');
38
+ const firstFontSize = parseFloat(controlButtons[0].style.fontSize);
39
+
40
+ controlButtons.forEach((btn) => {
41
+ expect(parseFloat(btn.style.fontSize)).toBe(firstFontSize);
42
+ });
43
+
44
+ // Check that font size is reasonable
45
+ expect(firstFontSize).toBeGreaterThan(8);
46
+ expect(firstFontSize).toBeLessThan(50);
47
+ });
48
+
49
+ test('handles CJK characters in control button labels', () => {
50
+ // Add control buttons
51
+ const controlKeys = ['確認', '確認', '確認']; // Japanese/Chinese
52
+ controlKeys.forEach((label) => {
53
+ const btn = document.createElement('button');
54
+ btn.className = 'response-button';
55
+ btn.innerHTML = `<span class="response-button-label">${label}</span>`;
56
+ document.getElementById('keypad-control-keys').appendChild(btn);
57
+ });
58
+
59
+ // Add regular keys
60
+ for (let i = 0; i < 8; i++) {
61
+ const btn = document.createElement('button');
62
+ btn.className = 'response-button';
63
+ btn.innerHTML = `<span class="response-button-label">${String.fromCharCode(65 + i)}</span>`;
64
+ document.getElementById('keypad-keys').appendChild(btn);
65
+ }
66
+
67
+ // Apply font sizing
68
+ applyMaxKeySize(8 + 3, 'sans-serif');
69
+
70
+ const controlButtons = document.querySelectorAll('#keypad-control-keys .response-button');
71
+ controlButtons.forEach((btn) => {
72
+ const fontSize = parseFloat(btn.style.fontSize);
73
+ expect(fontSize).toBeGreaterThan(10);
74
+ });
75
+ });
76
+
77
+ afterEach(() => {
78
+ document.body.innerHTML = '';
79
+ });
80
+ });
package/src/keypad.css CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  display: grid;
9
9
  grid-template-columns: 1fr;
10
- /* Make header text as big as the text, keys take up as much space as needed, and control keys take up bottom space */
10
+ /* Make header text as big as possible, keys take up as much space as needed, and control keys take up bottom space */
11
11
  grid-template-rows: max-content auto;
12
12
 
13
13
  align-items: center;
@@ -15,6 +15,7 @@
15
15
 
16
16
  font-family: sans-serif;
17
17
  }
18
+
18
19
  #keypad-header {
19
20
  height: 100%;
20
21
  font-size: 1.5rem;
@@ -29,6 +30,7 @@
29
30
  box-shadow: inset 2px 2px 4px #888888,
30
31
  inset -2px -2px 4px #fff;
31
32
  }
33
+
32
34
  #keypad-footer {
33
35
  position: absolute;
34
36
  font-size: smaller;
@@ -39,13 +41,9 @@
39
41
 
40
42
  /* Grid of main keys themselves */
41
43
  #keypad-keys {
42
- /* Second row, within the keypad grid (ie whole page) */
44
+ /* Second row, within keypad grid (i.e., whole page) */
43
45
  grid-row: 2;
44
46
  }
45
- /* Hide scrollbar */
46
- /* #keypad-keys::-webkit-scrollbar {
47
- display: none;
48
- } */
49
47
 
50
48
  .response-button {
51
49
  /* border: 1px solid red; */
@@ -55,6 +53,7 @@
55
53
  display: flex;
56
54
  align-items: center;
57
55
  justify-content: center;
56
+ flex-direction: column; /* NEW: Enable vertical layout for wrapping */
58
57
 
59
58
  /* Curve corners of buttons */
60
59
  border-radius: min(25px, 15%);
@@ -69,13 +68,18 @@
69
68
 
70
69
  box-shadow: 1px 1px 2px #888888,
71
70
  -1px -1px 2px #fff;
71
+
72
+ /* NEW: Enable flexible text layout */
73
+ overflow: hidden;
74
+ box-sizing: border-box;
72
75
  }
76
+
73
77
  .response-button:active {
74
78
  box-shadow: 1px 2px 2px 1px #999;
75
79
  background-color: #aaa;
76
80
  }
77
81
 
78
- /* Only do on a hover-enabled device, ie not a phone/tablet */
82
+ /* Only do on a hover-enabled device, i.e., not a phone/tablet */
79
83
  @media (hover: hover) {
80
84
  .response-button:hover {
81
85
  box-shadow: inset 1px 1px 2px #888888,
@@ -83,10 +87,22 @@
83
87
  }
84
88
  }
85
89
 
86
-
87
90
  .response-button-label {
88
91
  text-align: center;
89
- /* color: #eeeeee; */
92
+ color: black;
93
+
94
+ /* NEW: Enable text wrapping and centering */
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ width: 100%;
99
+ height: 100%;
100
+ white-space: pre-wrap;
101
+ word-wrap: break-word;
102
+ word-break: normal;
103
+ overflow: hidden;
104
+ padding: 2px;
105
+ box-sizing: border-box;
90
106
  }
91
107
 
92
108
  .noselect {
@@ -106,4 +122,13 @@
106
122
  inset -1px -1px 2px #fff;
107
123
  pointer-events: none;
108
124
  user-select: none;
109
- }
125
+ }
126
+
127
+ /* Control buttons maintain bottom alignment */
128
+ #keypad-control-keys .response-button {
129
+ border-radius: 25px;
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ flex-direction: column; /* NEW: Enable vertical layout */
134
+ }
package/src/keypad.js CHANGED
@@ -82,7 +82,7 @@ class Keypad extends KeypadPeer {
82
82
  this.#populateKeypad();
83
83
  break;
84
84
  case "Update":
85
- // Keypad has received data to update the keypad
85
+ // Keypad has received data to update keypad
86
86
  if (!data.hasOwnProperty("alphabet") && !data.hasOwnProperty("font") && !data.hasOwnProperty("controlButtons")) {
87
87
  console.error(
88
88
  'Error in parsing data received! Must set "alphabet" or "font" properties',
@@ -120,7 +120,7 @@ class Keypad extends KeypadPeer {
120
120
  };
121
121
  #join = () => {
122
122
  /**
123
- * Create the connection between the two Peers.
123
+ * Create connection between two Peers.
124
124
  *
125
125
  * Sets up callbacks that handle any events related to the
126
126
  * connection and data received on it.
@@ -129,14 +129,14 @@ class Keypad extends KeypadPeer {
129
129
  if (this.conn) {
130
130
  this.conn.close();
131
131
  }
132
- // Create connection to destination peer specified by the query param
132
+ // Create connection to destination peer specified by query param
133
133
  this.conn = this.peer.connect(this.receiverPeerId, {
134
134
  reliable: true,
135
135
  });
136
136
 
137
137
  console.log("Connection: ", this.conn);
138
138
  this.conn.on("open", this.#initiateHandshake);
139
- // Handle incoming data (messages only since this is the signal sender)
139
+ // Handle incoming data (messages only since this is signal sender)
140
140
  this.conn.on("data", this.#onConnData);
141
141
  // TODO figure out how to re-establish connection, or have more robust connection
142
142
  this.conn.on("close", () => console.log("Connection closed"));
@@ -194,11 +194,13 @@ class Keypad extends KeypadPeer {
194
194
 
195
195
  window.onresize = () => {
196
196
  console.count("Window resized.");
197
- applyMaxKeySize(this.alphabet?.length);
197
+ // NEW: Pass font family to applyMaxKeySize
198
+ applyMaxKeySize(this.alphabet?.length, this.font);
198
199
  };
199
200
  if (window.visualViewport) window.visualViewport.onresize = () => {
200
201
  console.count("VisualViewport resized.");
201
- applyMaxKeySize(this.alphabet?.length);
202
+ // NEW: Pass font family to applyMaxKeySize
203
+ applyMaxKeySize(this.alphabet?.length, this.font);
202
204
  };
203
205
  };
204
206
  #populateKeypad = () => {
@@ -284,15 +286,15 @@ class Keypad extends KeypadPeer {
284
286
  }
285
287
  });
286
288
 
287
- // Create a label for the button
289
+ // Create a label for button
288
290
  let buttonLabel = document.createElement("span");
289
291
  buttonLabel.classList.add("response-button-label", "noselect");
290
292
  buttonLabel.innerText = symbol;
291
293
  buttonLabel.style.fontFamily = this.font;
292
294
 
293
- // Add the label to the button
295
+ // Add to label to button
294
296
  button.appendChild(buttonLabel);
295
- // Add the labeled-button to the HTML
297
+ // Add to labeled-button to HTML
296
298
  if (this.controlButtons.map(x => x.toLowerCase()).includes(symbol.toLowerCase())) {
297
299
  document.querySelector("#keypad-control-keys").appendChild(button);
298
300
  } else {
@@ -300,11 +302,11 @@ class Keypad extends KeypadPeer {
300
302
  }
301
303
  };
302
304
 
303
- // Set-up an instruction/welcome message for the user
305
+ // Set-up an instruction/welcome message for user
304
306
  const header = document.getElementById("keypad-header");
305
307
  header.innerText = this.headerMessage || "";
306
308
  header.style.display = header.innerText === "" ? "none" : "block";
307
- // Get the keypad element
309
+ // Get keypad element
308
310
  const remoteControl = document.getElementById("keypad");
309
311
 
310
312
  // Set-up audio element
@@ -321,16 +323,17 @@ class Keypad extends KeypadPeer {
321
323
  // Create new buttons
322
324
  this.alphabet.forEach((symbol) => createButton(symbol));
323
325
  // Manually style buttons, according to Denis' algorithm
324
- setTimeout(() => applyMaxKeySize(this.alphabet.length), 5); // Why?
326
+ // NEW: Pass font family to applyMaxKeySize
327
+ setTimeout(() => applyMaxKeySize(this.alphabet?.length, this.font), 5); // Why?
325
328
  };
326
329
  visualFeedbackThenReset = (delayTime = 800) => {
327
330
  // ie grey out keys just after use, to discourage rapid response
328
331
  this.interResponseKeypadMessaging();
329
- // Setup keys for the next trial
332
+ // Setup keys for next trial
330
333
  setTimeout(defaultKeypadMessaging, delayTime);
331
334
  };
332
335
  defaultKeypadMessaging = (headerText = "") => {
333
- // Set-up an instruction/welcome message for the user
336
+ // Set-up an instruction/welcome message for user
334
337
  const header = document.getElementById("keypad-header");
335
338
  if (headerText === "") {
336
339
  header.style.display = "none";
@@ -359,15 +362,14 @@ class Keypad extends KeypadPeer {
359
362
  });
360
363
  };
361
364
  /**
362
- * Remove all keys from the keypad.
365
+ * Remove all keys from keypad.
363
366
  */
364
367
  clearKeys = () => {
365
368
  document.querySelector("#keypad-keys").innerHTML =
366
369
  "<div id='keypad-control-keys'></div>";
367
- // document.querySelector("#keypad-control-keys").innerHTML = "";
368
370
  };
369
371
  /**
370
- * Return the nodes corresponding to the specified keys.
372
+ * Return nodes corresponding to specified keys.
371
373
  * @param {string[]} whichKeys id's of keys to select. defaults to all keys.
372
374
  * @returns {HTMLElement[]}
373
375
  */