pxt-microbit 8.1.12 → 8.1.14

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/built/sim.js CHANGED
@@ -4069,13 +4069,14 @@ var pxsim;
4069
4069
  .sim-button-nut:hover {
4070
4070
  stroke:1px solid #704A4A;
4071
4071
  }
4072
- .sim-pin:hover {
4072
+ .sim-pin[focusable=true]:hover {
4073
4073
  stroke:#D4AF37;
4074
4074
  stroke-width:2px;
4075
4075
  }
4076
4076
 
4077
- .sim-pin-touch.touched {
4077
+ .sim-pin-touch[focusable=true].touched {
4078
4078
  stroke:darkorange !important;
4079
+ stroke-width:5px;
4079
4080
  }
4080
4081
 
4081
4082
  .sim-led-back:hover {
@@ -4170,6 +4171,7 @@ var pxsim;
4170
4171
  }
4171
4172
  .sim-label, .sim-button-label {
4172
4173
  fill: #000;
4174
+ pointer-events: none;
4173
4175
  }
4174
4176
  .sim-wireframe .sim-board {
4175
4177
  stroke-width: 2px;
@@ -4177,25 +4179,25 @@ var pxsim;
4177
4179
  *:focus {
4178
4180
  outline: none;
4179
4181
  }
4180
- *:focus .sim-button-outer,
4181
- .sim-shake:focus,
4182
- .sim-thermometer:focus {
4182
+ *:focus-visible .sim-button-outer,
4183
+ .sim-shake:focus-visible,
4184
+ .sim-thermometer:focus-visible {
4183
4185
  outline: 5px solid white;
4184
4186
  stroke: black;
4185
4187
  stroke-width: 10px;
4186
4188
  paint-order: stroke;
4187
4189
  }
4188
- .sim-button-outer.sim-button-group:focus > .sim-button {
4190
+ .sim-button-outer.sim-button-group:focus-visible > .sim-button {
4189
4191
  outline: 5px solid white;
4190
4192
  stroke: black;
4191
4193
  stroke-width: 5px;
4192
4194
  paint-order: stroke;
4193
4195
  }
4194
- .sim-light-level-button:focus,
4195
- .sim-antenna-outer:focus > .sim-antenna {
4196
+ .sim-light-level-button:focus-visible,
4197
+ .sim-antenna-outer:focus-visible > .sim-antenna {
4196
4198
  outline: 5px solid white;
4197
4199
  }
4198
- .sim-pin:focus {
4200
+ .sim-pin:focus-visible {
4199
4201
  stroke: white;
4200
4202
  stroke-width: 5px !important;
4201
4203
  }
@@ -4208,6 +4210,9 @@ var pxsim;
4208
4210
  -webkit-user-select: none;
4209
4211
  -ms-user-select: none;
4210
4212
  }
4213
+ [focusable=true] {
4214
+ cursor: pointer;
4215
+ }
4211
4216
  `;
4212
4217
  const MB_HIGHCONTRAST = `
4213
4218
  path.sim-board {
@@ -4220,11 +4225,11 @@ path.sim-board {
4220
4225
  .sim-led-back {
4221
4226
  stroke: white;
4222
4227
  }
4223
- *:focus .sim-button-outer,
4224
- .sim-pin:focus,
4225
- .sim-thermometer:focus,
4226
- .sim-shake:focus,
4227
- .sim-light-level-button:focus {
4228
+ *:focus-visible .sim-button-outer,
4229
+ .sim-pin:focus-visible,
4230
+ .sim-thermometer:focus-visible,
4231
+ .sim-shake:focus-visible,
4232
+ .sim-light-level-button:focus-visible {
4228
4233
  stroke: #10C8CD !important;
4229
4234
  }
4230
4235
  `;
@@ -4250,29 +4255,33 @@ path.sim-board {
4250
4255
  "P12", "P2", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20",
4251
4256
  "GND0", "GND", "+3v3", "GND1"
4252
4257
  ];
4258
+ // title is currently unused.
4253
4259
  const pinTitles = [
4254
- "P0, ANALOG IN",
4255
- "P1, ANALOG IN",
4256
- "P2, ANALOG IN",
4257
- "P3, ANALOG IN, LED Col 1",
4258
- "P4, ANALOG IN, LED Col 2",
4259
- "P5, BUTTON A",
4260
- "P6, LED Col 9",
4261
- "P7, LED Col 8",
4262
- "P8",
4263
- "P9, LED Col 7",
4264
- "P10, ANALOG IN, LED Col 3",
4265
- "P11, BUTTON B",
4266
- "P12, RESERVED ACCESSIBILITY",
4267
- "P13, SPI - SCK",
4268
- "P14, SPI - MISO",
4269
- "P15, SPI - MOSI",
4270
- "P16, SPI - Chip Select",
4271
- "P17, +3v3",
4272
- "P18, +3v3",
4273
- "P19, I2C - SCL",
4274
- "P20, I2C - SDA",
4275
- "GND", "GND", "+3v3", "GND"
4260
+ { title: "P0, ANALOG IN", ariaLabel: pxsim.localization.lf("Pin 0") },
4261
+ { title: "P1, ANALOG IN", ariaLabel: pxsim.localization.lf("Pin 1") },
4262
+ { title: "P2, ANALOG IN", ariaLabel: pxsim.localization.lf("Pin 2") },
4263
+ { title: "P3, ANALOG IN, LED Col 1", ariaLabel: pxsim.localization.lf("Pin 3") },
4264
+ { title: "P4, ANALOG IN, LED Col 2", ariaLabel: pxsim.localization.lf("Pin 4") },
4265
+ { title: "P5, BUTTON A", ariaLabel: pxsim.localization.lf("Pin 5") },
4266
+ { title: "P6, LED Col 9", ariaLabel: pxsim.localization.lf("Pin 6") },
4267
+ { title: "P7, LED Col 8", ariaLabel: pxsim.localization.lf("Pin 7") },
4268
+ { title: "P8", ariaLabel: pxsim.localization.lf("Pin 8") },
4269
+ { title: "P9, LED Col 7", ariaLabel: pxsim.localization.lf("Pin 9") },
4270
+ { title: "P10, ANALOG IN, LED Col 3", ariaLabel: pxsim.localization.lf("Pin 10") },
4271
+ { title: "P11, BUTTON B", ariaLabel: pxsim.localization.lf("Pin 11") },
4272
+ { title: "P12, RESERVED ACCESSIBILITY", ariaLabel: pxsim.localization.lf("Pin 12") },
4273
+ { title: "P13, SPI - SCK", ariaLabel: pxsim.localization.lf("Pin 13") },
4274
+ { title: "P14, SPI - MISO", ariaLabel: pxsim.localization.lf("Pin 14") },
4275
+ { title: "P15, SPI - MOSI", ariaLabel: pxsim.localization.lf("Pin 15") },
4276
+ { title: "P16, SPI - Chip Select", ariaLabel: pxsim.localization.lf("Pin 16") },
4277
+ { title: "P17, +3v3", ariaLabel: pxsim.localization.lf("Pin 3V") },
4278
+ { title: "P18, +3v3", ariaLabel: pxsim.localization.lf("Pin 3V") },
4279
+ { title: "P19, I2C - SCL", ariaLabel: pxsim.localization.lf("Pin 19") },
4280
+ { title: "P20, I2C - SDA", ariaLabel: pxsim.localization.lf("Pin 20") },
4281
+ { title: "GND", ariaLabel: pxsim.localization.lf("Pin GND") },
4282
+ { title: "GND", ariaLabel: pxsim.localization.lf("Pin GND") },
4283
+ { title: "+3v3", ariaLabel: pxsim.localization.lf("Pin 3V") },
4284
+ { title: "GND", ariaLabel: pxsim.localization.lf("Pin GND") },
4276
4285
  ];
4277
4286
  const MB_WIDTH = 500;
4278
4287
  const MB_HEIGHT = 408;
@@ -4315,6 +4324,7 @@ path.sim-board {
4315
4324
  class MicrobitBoardSvg {
4316
4325
  constructor(props) {
4317
4326
  this.props = props;
4327
+ this.liveRegionInitialized = false;
4318
4328
  this.headInitialized = false;
4319
4329
  this.antennaInitialized = false;
4320
4330
  this.lightLevelInitialized = false;
@@ -4350,7 +4360,7 @@ path.sim-board {
4350
4360
  if (props && props.runtime) {
4351
4361
  this.board = this.props.runtime.board;
4352
4362
  this.board.updateSubscribers.push(() => this.updateState());
4353
- this.updateState();
4363
+ this.updateState(true);
4354
4364
  this.attachEvents();
4355
4365
  }
4356
4366
  }
@@ -4409,7 +4419,7 @@ path.sim-board {
4409
4419
  pxsim.svg.setGradientColors(this.soundLevelGradient, theme.ledOff, theme.ledOn);
4410
4420
  this.positionV2Elements();
4411
4421
  }
4412
- updateState() {
4422
+ updateState(initialCall = false) {
4413
4423
  const state = this.board;
4414
4424
  if (!state)
4415
4425
  return;
@@ -4430,6 +4440,12 @@ path.sim-board {
4430
4440
  pxsim.U.addClass(this.element, "grayscale");
4431
4441
  else
4432
4442
  pxsim.U.removeClass(this.element, "grayscale");
4443
+ if (!initialCall && !this.liveRegionInitialized) {
4444
+ // The iframe document's innerHTML is cleared after mkBoardView is called.
4445
+ // Ensure that the live region is created after this.
4446
+ pxsim.accessibility.setLiveContent("");
4447
+ this.liveRegionInitialized = true;
4448
+ }
4433
4449
  }
4434
4450
  updateButtonPairs() {
4435
4451
  const state = this.board;
@@ -4493,8 +4509,8 @@ path.sim-board {
4493
4509
  pxsim.svg.fill(this.shakeButton, this.props.theme.virtualButtonUp);
4494
4510
  this.board.accelerometerState.shake();
4495
4511
  });
4496
- pxsim.accessibility.setAria(this.shakeButton, "button", "Shake the board");
4497
- this.shakeText = pxsim.svg.child(this.g, "text", { x: 420, y: 122, class: "sim-text-small" });
4512
+ pxsim.accessibility.setAria(this.shakeButton, "button", pxsim.localization.lf("Shake"));
4513
+ this.shakeText = pxsim.svg.child(this.g, "text", { x: 420, y: 122, class: "sim-text-small", "aria-hidden": true });
4498
4514
  this.shakeText.textContent = "SHAKE";
4499
4515
  }
4500
4516
  }
@@ -4533,19 +4549,23 @@ path.sim-board {
4533
4549
  }
4534
4550
  }
4535
4551
  updatePin(pin, index) {
4552
+ var _a, _b;
4536
4553
  if (!pin)
4537
4554
  return;
4538
4555
  let text = this.pinTexts[index];
4539
4556
  let v = "";
4557
+ let ariaValueNow;
4540
4558
  if (pin.mode & pxsim.PinFlags.Analog) {
4541
4559
  v = Math.floor(100 - (pin.value || 0) / 1023 * 100) + "%";
4542
4560
  if (text)
4543
4561
  text.textContent = (pin.period ? "~" : "") + (pin.value || 0) + "";
4562
+ ariaValueNow = (_a = pin.value) !== null && _a !== void 0 ? _a : 0;
4544
4563
  }
4545
4564
  else if (pin.mode & pxsim.PinFlags.Digital) {
4546
4565
  v = pin.value > 0 ? "0%" : "100%";
4547
4566
  if (text)
4548
4567
  text.textContent = pin.value > 0 ? "1" : "0";
4568
+ ariaValueNow = pin.value > 0 ? 1 : 0;
4549
4569
  }
4550
4570
  else if (pin.mode & pxsim.PinFlags.Touch) {
4551
4571
  v = pin.touched ? "0%" : "100%";
@@ -4561,12 +4581,37 @@ path.sim-board {
4561
4581
  pxsim.svg.setGradientValue(this.pinGradients[index], v);
4562
4582
  if (pin.mode !== pxsim.PinFlags.Unused) {
4563
4583
  pxsim.accessibility.makeFocusable(this.pins[index]);
4564
- pxsim.accessibility.setAria(this.pins[index], "slider", this.pins[index].firstChild.textContent);
4565
- this.pins[index].setAttribute("aria-valuemin", "0");
4566
- this.pins[index].setAttribute("aria-valuemax", pin.mode & pxsim.PinFlags.Analog ? "1023" : "100");
4567
- this.pins[index].setAttribute("aria-orientation", "vertical");
4568
- this.pins[index].setAttribute("aria-valuenow", text ? text.textContent : v);
4569
- pxsim.accessibility.setLiveContent(text ? text.textContent : v);
4584
+ if (pin.mode & pxsim.PinFlags.Touch) {
4585
+ this.pins[index].setAttribute("role", "button");
4586
+ this.pins[index].ariaLabel = this.pins[index].firstChild.textContent;
4587
+ this.pins[index].removeAttribute("aria-valuemin");
4588
+ this.pins[index].removeAttribute("aria-valuemax");
4589
+ this.pins[index].removeAttribute("aria-orientation");
4590
+ this.pins[index].removeAttribute("aria-valuenow");
4591
+ this.pins[index].removeAttribute("aria-valuetext");
4592
+ this.pins[index].removeAttribute("aria-readonly");
4593
+ }
4594
+ else {
4595
+ this.pins[index].setAttribute("role", "slider");
4596
+ this.pins[index].ariaLabel = this.pins[index].firstChild.textContent;
4597
+ this.pins[index].setAttribute("aria-valuemin", "0");
4598
+ this.pins[index].setAttribute("aria-valuemax", pin.mode & pxsim.PinFlags.Analog ? "1023" : "1");
4599
+ this.pins[index].setAttribute("aria-orientation", "vertical");
4600
+ this.pins[index].setAttribute("aria-valuenow", (_b = ariaValueNow.toString()) !== null && _b !== void 0 ? _b : "");
4601
+ // Check that the text content isn't just a plain int and only set aria-valuetext if required.
4602
+ if ((text === null || text === void 0 ? void 0 : text.textContent) && (text === null || text === void 0 ? void 0 : text.textContent) !== parseInt(text === null || text === void 0 ? void 0 : text.textContent).toString()) {
4603
+ this.pins[index].setAttribute("aria-valuetext", text.textContent);
4604
+ }
4605
+ else {
4606
+ this.pins[index].removeAttribute("aria-valuetext");
4607
+ }
4608
+ if (pin.mode & pxsim.PinFlags.Input) {
4609
+ this.pins[index].removeAttribute("aria-readonly");
4610
+ }
4611
+ else {
4612
+ this.pins[index].setAttribute("aria-readonly", "true");
4613
+ }
4614
+ }
4570
4615
  }
4571
4616
  }
4572
4617
  updateTemperature() {
@@ -4578,7 +4623,7 @@ path.sim-board {
4578
4623
  if (!this.thermometerInitialized) {
4579
4624
  this.thermometerInitialized = true;
4580
4625
  this.thermometer.style.visibility = "visible";
4581
- this.thermometerText = pxsim.svg.child(this.g, "text", { class: 'sim-text', x: 58, y: 130 });
4626
+ this.thermometerText = pxsim.svg.child(this.g, "text", { class: 'sim-text', x: 58, y: 130, "aria-hidden": true });
4582
4627
  if (this.props.runtime)
4583
4628
  this.props.runtime.environmentGlobals[pxsim.localization.lf("temperature")] = state.thermometerState.temperature;
4584
4629
  this.updateTheme();
@@ -4597,26 +4642,14 @@ path.sim-board {
4597
4642
  ev => { },
4598
4643
  // keydown
4599
4644
  (ev) => {
4600
- let charCode = (typeof ev.which == "number") ? ev.which : ev.keyCode;
4601
- if (charCode === 40 || charCode === 37) { // Down/Left arrow
4602
- ev.preventDefault();
4603
- state.thermometerState.temperature--;
4604
- if (state.thermometerState.temperature < -5) {
4605
- state.thermometerState.temperature = 50;
4606
- }
4607
- this.updateTemperature();
4608
- }
4609
- else if (charCode === 38 || charCode === 39) { // Up/Right arrow
4610
- ev.preventDefault();
4611
- state.thermometerState.temperature++;
4612
- if (state.thermometerState.temperature > 50) {
4613
- state.thermometerState.temperature = -5;
4614
- }
4645
+ const value = commonKeyHandler(ev, state.thermometerState.temperature, tmin, tmax);
4646
+ if (value !== undefined) {
4647
+ state.thermometerState.temperature = value;
4615
4648
  this.updateTemperature();
4616
4649
  }
4617
4650
  });
4618
4651
  pxsim.accessibility.makeFocusable(this.thermometer);
4619
- pxsim.accessibility.setAria(this.thermometer, "slider", pxsim.localization.lf("Thermometer"));
4652
+ pxsim.accessibility.setAria(this.thermometer, "slider", pxsim.localization.lf("Temperature"));
4620
4653
  this.thermometer.setAttribute("aria-valuemin", "-5");
4621
4654
  this.thermometer.setAttribute("aria-valuemax", "50");
4622
4655
  this.thermometer.setAttribute("aria-orientation", "vertical");
@@ -4629,7 +4662,6 @@ path.sim-board {
4629
4662
  this.thermometerText.textContent = t + "°C";
4630
4663
  this.thermometer.setAttribute("aria-valuenow", t.toString());
4631
4664
  this.thermometer.setAttribute("aria-valuetext", t + "°C");
4632
- pxsim.accessibility.setLiveContent(t + "°C");
4633
4665
  }
4634
4666
  updateSoundLevel() {
4635
4667
  let state = this.board;
@@ -4641,7 +4673,7 @@ path.sim-board {
4641
4673
  this.soundLevelInitialized = true;
4642
4674
  this.soundLevel.style.visibility = "visible";
4643
4675
  const level = state.microphoneState.getLevel();
4644
- this.soundLevelText = pxsim.svg.child(this.g, "text", { class: 'sim-text', x: 370, y: 90 });
4676
+ this.soundLevelText = pxsim.svg.child(this.g, "text", { class: 'sim-text', x: 370, y: 90, "aria-hidden": true });
4645
4677
  if (this.props.runtime)
4646
4678
  this.props.runtime.environmentGlobals[pxsim.localization.lf("sound level")] = state.microphoneState.getLevel();
4647
4679
  this.updateTheme();
@@ -4660,15 +4692,9 @@ path.sim-board {
4660
4692
  ev => { },
4661
4693
  // keydown
4662
4694
  (ev) => {
4663
- let charCode = (typeof ev.which == "number") ? ev.which : ev.keyCode;
4664
- if (charCode === 40 || charCode === 37) { // Down/Left arrow
4665
- ev.preventDefault();
4666
- state.microphoneState.setLevel(state.microphoneState.getLevel() - 1);
4667
- this.updateMicrophone();
4668
- }
4669
- else if (charCode === 38 || charCode === 39) { // Up/Right arrow
4670
- ev.preventDefault();
4671
- state.microphoneState.setLevel(state.microphoneState.getLevel() + 1);
4695
+ const value = commonKeyHandler(ev, state.microphoneState.getLevel(), tmin, tmax);
4696
+ if (value !== undefined) {
4697
+ state.microphoneState.setLevel(value);
4672
4698
  this.updateMicrophone();
4673
4699
  }
4674
4700
  });
@@ -4686,7 +4712,6 @@ path.sim-board {
4686
4712
  this.soundLevelText.textContent = t + "";
4687
4713
  this.soundLevel.setAttribute("aria-valuenow", t.toString());
4688
4714
  this.soundLevel.setAttribute("aria-valuetext", t + "");
4689
- pxsim.accessibility.setLiveContent(t + "");
4690
4715
  }
4691
4716
  updateHeading() {
4692
4717
  let xc = 258;
@@ -4714,23 +4739,9 @@ path.sim-board {
4714
4739
  ev => { },
4715
4740
  // keydown
4716
4741
  (ev) => {
4717
- let charCode = (typeof ev.which == "number") ? ev.which : ev.keyCode;
4718
- if (charCode === 40 || charCode === 37) { // Down/Left arrow
4719
- ev.preventDefault();
4720
- state.compassState.heading--;
4721
- if (state.compassState.heading < 0)
4722
- state.compassState.heading += 360;
4723
- if (state.compassState.heading >= 360)
4724
- state.compassState.heading %= 360;
4725
- this.updateHeading();
4726
- }
4727
- else if (charCode === 38 || charCode === 39) { // Up/Right arrow
4728
- ev.preventDefault();
4729
- state.compassState.heading++;
4730
- if (state.compassState.heading < 0)
4731
- state.compassState.heading += 360;
4732
- if (state.compassState.heading >= 360)
4733
- state.compassState.heading %= 360;
4742
+ const value = commonKeyHandler(ev, state.compassState.heading, 0, 359);
4743
+ if (value !== undefined) {
4744
+ state.compassState.heading = value;
4734
4745
  this.updateHeading();
4735
4746
  }
4736
4747
  });
@@ -4757,6 +4768,7 @@ path.sim-board {
4757
4768
  }
4758
4769
  }
4759
4770
  flashAntenna() {
4771
+ var _a;
4760
4772
  if (!this.antennaInitialized) {
4761
4773
  this.antenna.style.visibility = "visible";
4762
4774
  this.antennaInitialized = true;
@@ -4778,27 +4790,21 @@ path.sim-board {
4778
4790
  };
4779
4791
  const keyboardEventHandler = (ev) => {
4780
4792
  var _a;
4781
- const charCode = (typeof ev.which == "number") ? ev.which : ev.keyCode;
4782
- const rs = (_a = this.board.radioState.datagram.rssi) !== null && _a !== void 0 ? _a : -75;
4783
- if (charCode === 40 || charCode === 37) { // Down/Left arrow
4784
- ev.preventDefault();
4785
- setValue(rs - 1);
4786
- }
4787
- else if (charCode === 38 || charCode === 39) { // Up/Right arrow
4788
- ev.preventDefault();
4789
- setValue(rs + 1);
4793
+ const value = commonKeyHandler(ev, (_a = this.board.radioState.datagram.rssi) !== null && _a !== void 0 ? _a : -75, valueMin, valueMax);
4794
+ if (value !== undefined) {
4795
+ setValue(value);
4790
4796
  }
4791
4797
  };
4792
4798
  pxsim.svg.buttonEvents(this.antenna.children[0], mouseEventHandler, mouseEventHandler, mouseEventHandler, () => { });
4793
4799
  pxsim.svg.buttonEvents(this.antenna.children[1], mouseEventHandler, mouseEventHandler, mouseEventHandler, () => { });
4794
4800
  this.antenna.addEventListener('keydown', keyboardEventHandler);
4795
4801
  pxsim.accessibility.makeFocusable(this.antenna);
4796
- pxsim.accessibility.setAria(this.antenna, "slider", "RSSI");
4802
+ pxsim.accessibility.setAria(this.antenna, "slider", pxsim.localization.lf("Received Signal Strength Indicator"));
4803
+ ;
4797
4804
  this.antenna.setAttribute("aria-valuemin", `${valueMin}`);
4798
4805
  this.antenna.setAttribute("aria-valuemax", `${valueMax}`);
4799
4806
  this.antenna.setAttribute("aria-orientation", "horizontal");
4800
- this.antenna.setAttribute("aria-valuenow", "");
4801
- pxsim.accessibility.setLiveContent("");
4807
+ this.antenna.setAttribute("aria-valuenow", ((_a = this.board.radioState.datagram.rssi) !== null && _a !== void 0 ? _a : -75).toString());
4802
4808
  }
4803
4809
  let now = Date.now();
4804
4810
  if (now - this.lastAntennaFlash > 200) {
@@ -4819,14 +4825,13 @@ path.sim-board {
4819
4825
  let ayb = 40;
4820
4826
  for (let i = 0; i < 4; ++i)
4821
4827
  pxsim.svg.child(this.g, "rect", { x: ANTENNA_X - 90 + i * 6, y: ayt + 28 - i * 4, width: 4, height: 2 + i * 4, fill: "#fff" });
4822
- this.rssi = pxsim.svg.child(this.g, "text", { x: ANTENNA_X - 64, y: ayb, class: "sim-text" });
4828
+ this.rssi = pxsim.svg.child(this.g, "text", { x: ANTENNA_X - 64, y: ayb, class: "sim-text", "aria-hidden": true });
4823
4829
  this.rssi.textContent = "";
4824
4830
  }
4825
4831
  const vt = v.toString();
4826
4832
  if (vt !== this.rssi.textContent) {
4827
4833
  this.rssi.textContent = v.toString();
4828
4834
  this.antenna.setAttribute("aria-valuenow", this.rssi.textContent);
4829
- pxsim.accessibility.setLiveContent(this.rssi.textContent);
4830
4835
  }
4831
4836
  }
4832
4837
  updatePins() {
@@ -4860,30 +4865,18 @@ path.sim-board {
4860
4865
  ev => { },
4861
4866
  // keydown
4862
4867
  (ev) => {
4863
- let charCode = (typeof ev.which == "number") ? ev.which : ev.keyCode;
4864
- if (charCode === 40 || charCode === 37) { // Down/Left arrow
4865
- ev.preventDefault();
4866
- this.board.lightSensorState.lightLevel--;
4867
- if (this.board.lightSensorState.lightLevel < 0) {
4868
- this.board.lightSensorState.lightLevel = 255;
4869
- }
4870
- this.applyLightLevel();
4871
- }
4872
- else if (charCode === 38 || charCode === 39) { // Up/Right arrow
4873
- ev.preventDefault();
4874
- this.board.lightSensorState.lightLevel++;
4875
- if (this.board.lightSensorState.lightLevel > 255) {
4876
- this.board.lightSensorState.lightLevel = 0;
4877
- }
4878
- this.applyLightLevel();
4868
+ const value = commonKeyHandler(ev, state.lightSensorState.lightLevel, 0, 255);
4869
+ if (value !== undefined) {
4870
+ state.lightSensorState.lightLevel = value;
4871
+ this.updateLightLevel();
4879
4872
  }
4880
4873
  });
4881
- this.lightLevelText = pxsim.svg.child(this.g, "text", { x: 85, y: LIGHT_LEVEL_BUTTON_POSITION_Y + LIGHT_LEVEL_BUTTON_RADIUS - 5, text: '', class: 'sim-text' });
4874
+ this.lightLevelText = pxsim.svg.child(this.g, "text", { x: 85, y: LIGHT_LEVEL_BUTTON_POSITION_Y + LIGHT_LEVEL_BUTTON_RADIUS - 5, text: '', class: 'sim-text', 'aria-hidden': true });
4882
4875
  if (this.props.runtime)
4883
4876
  this.props.runtime.environmentGlobals[pxsim.localization.lf("lightLevel")] = state.lightSensorState.lightLevel;
4884
4877
  this.updateTheme();
4885
4878
  pxsim.accessibility.makeFocusable(this.lightLevelButton);
4886
- pxsim.accessibility.setAria(this.lightLevelButton, "slider", "Light level");
4879
+ pxsim.accessibility.setAria(this.lightLevelButton, "slider", pxsim.localization.lf("Light level"));
4887
4880
  this.lightLevelButton.setAttribute("aria-valuemin", "0");
4888
4881
  this.lightLevelButton.setAttribute("aria-valuemax", "255");
4889
4882
  this.lightLevelButton.setAttribute("aria-orientation", "vertical");
@@ -4897,7 +4890,6 @@ path.sim-board {
4897
4890
  pxsim.svg.setGradientValue(this.lightLevelGradient, Math.min(100, Math.max(0, Math.floor(lv * 100 / 255))) + '%');
4898
4891
  this.lightLevelText.textContent = lv.toString();
4899
4892
  this.lightLevelButton.setAttribute("aria-valuenow", lv.toString());
4900
- pxsim.accessibility.setLiveContent(lv.toString());
4901
4893
  }
4902
4894
  findParentElement() {
4903
4895
  let el = this.element;
@@ -4936,21 +4928,21 @@ path.sim-board {
4936
4928
  // update text
4937
4929
  if (acc.flags & pxsim.AccelerometerFlag.X) {
4938
4930
  if (!this.accTextX) {
4939
- this.accTextX = pxsim.svg.child(this.g, "text", { x: 365, y: 260, class: "sim-text" });
4931
+ this.accTextX = pxsim.svg.child(this.g, "text", { x: 365, y: 260, class: "sim-text", "aria-hidden": true });
4940
4932
  this.accTextX.textContent = "";
4941
4933
  }
4942
4934
  this.accTextX.textContent = `ax:${x}`;
4943
4935
  }
4944
4936
  if (acc.flags & pxsim.AccelerometerFlag.Y) {
4945
4937
  if (!this.accTextY) {
4946
- this.accTextY = pxsim.svg.child(this.g, "text", { x: 365, y: 285, class: "sim-text" });
4938
+ this.accTextY = pxsim.svg.child(this.g, "text", { x: 365, y: 285, class: "sim-text", "aria-hidden": true });
4947
4939
  this.accTextY.textContent = "";
4948
4940
  }
4949
4941
  this.accTextY.textContent = `ay:${-y}`;
4950
4942
  }
4951
4943
  if (acc.flags & pxsim.AccelerometerFlag.Z) {
4952
4944
  if (!this.accTextZ) {
4953
- this.accTextZ = pxsim.svg.child(this.g, "text", { x: 365, y: 310, class: "sim-text" });
4945
+ this.accTextZ = pxsim.svg.child(this.g, "text", { x: 365, y: 310, class: "sim-text", "aria-hidden": true });
4954
4946
  this.accTextZ.textContent = "";
4955
4947
  }
4956
4948
  this.accTextZ.textContent = `az:${z}`;
@@ -4968,7 +4960,9 @@ path.sim-board {
4968
4960
  "y": "0px",
4969
4961
  "width": MB_WIDTH + "px",
4970
4962
  "height": MB_HEIGHT + "px",
4971
- "fill": "rgba(0,0,0,0)"
4963
+ "fill": "rgba(0,0,0,0)",
4964
+ // Allows screen reader users to interact with board properly.
4965
+ "role": "application"
4972
4966
  });
4973
4967
  this.style = pxsim.svg.child(this.element, "style", {});
4974
4968
  this.style.textContent = MB_STYLE + (this.props.theme.highContrast ? MB_HIGHCONTRAST : "");
@@ -5095,7 +5089,7 @@ path.sim-board {
5095
5089
  this.heads.push(pxsim.svg.path(this.headParts, "sim-theme", "M269.9,50.2L269.9,50.2l-39.5,0v0c-14.1,0.1-24.6,10.7-24.6,24.8c0,13.9,10.4,24.4,24.3,24.7v0h39.6c14.2,0,24.8-10.6,24.8-24.7C294.5,61,284,50.3,269.9,50.2 M269.7,89.2L269.7,89.2l-39.3,0c-7.7-0.1-14-6.4-14-14.2c0-7.8,6.4-14.2,14.2-14.2h39.1c7.8,0,14.2,6.4,14.2,14.2C283.9,82.9,277.5,89.2,269.7,89.2"));
5096
5090
  this.heads.push(pxsim.svg.path(this.headParts, "sim-theme", "M230.6,69.7c-2.9,0-5.3,2.4-5.3,5.3c0,2.9,2.4,5.3,5.3,5.3c2.9,0,5.3-2.4,5.3-5.3C235.9,72.1,233.5,69.7,230.6,69.7"));
5097
5091
  this.heads.push(pxsim.svg.path(this.headParts, "sim-theme", "M269.7,80.3c2.9,0,5.3-2.4,5.3-5.3c0-2.9-2.4-5.3-5.3-5.3c-2.9,0-5.3,2.4-5.3,5.3C264.4,77.9,266.8,80.3,269.7,80.3"));
5098
- this.headText = pxsim.svg.child(this.g, "text", { x: 160, y: 60, class: "sim-text" });
5092
+ this.headText = pxsim.svg.child(this.g, "text", { x: 160, y: 60, class: "sim-text", "aria-hidden": true });
5099
5093
  }
5100
5094
  buildPinElements() {
5101
5095
  // https://www.microbit.co.uk/device/pins
@@ -5119,7 +5113,7 @@ path.sim-board {
5119
5113
  this.pins = pinDrawOrder.reduce((pins, pinName) => {
5120
5114
  const simPinIndex = pinNames.indexOf(pinName);
5121
5115
  const newPin = drawList[simPinIndex]();
5122
- pxsim.svg.hydrate(newPin, { title: pinTitles[simPinIndex] });
5116
+ pxsim.svg.hydrate(newPin, { title: pinTitles[simPinIndex].ariaLabel });
5123
5117
  pins[simPinIndex] = newPin;
5124
5118
  return pins;
5125
5119
  }, new Array(pinDrawOrder.length));
@@ -5129,7 +5123,7 @@ path.sim-board {
5129
5123
  pin.setAttribute("fill", `url(#${gid})`);
5130
5124
  return lg;
5131
5125
  });
5132
- this.pinTexts = [67, 165, 275].map(x => pxsim.svg.child(this.g, "text", { class: "sim-text-pin", x: x, y: 345 }));
5126
+ this.pinTexts = [67, 165, 275].map(x => pxsim.svg.child(this.g, "text", { class: "sim-text-pin", x: x, y: 345, "aria-hidden": true }));
5133
5127
  pxsim.svg.path(this.g, "sim-label", "M35.7,376.4c0-2.8,2.1-5.1,5.5-5.1c3.3,0,5.5,2.4,5.5,5.1v4.7c0,2.8-2.2,5.1-5.5,5.1c-3.3,0-5.5-2.4-5.5-5.1V376.4zM43.3,376.4c0-1.3-0.8-2.3-2.2-2.3c-1.3,0-2.1,1.1-2.1,2.3v4.7c0,1.2,0.8,2.3,2.1,2.3c1.3,0,2.2-1.1,2.2-2.3V376.4z");
5134
5128
  pxsim.svg.path(this.g, "sim-label", "M136.2,374.1c2.8,0,3.4-0.8,3.4-2.5h2.9v14.3h-3.4v-9.5h-3V374.1z");
5135
5129
  pxsim.svg.path(this.g, "sim-label", "M248.6,378.5c1.7-1,3-1.7,3-3.1c0-1.1-0.7-1.6-1.6-1.6c-1,0-1.8,0.6-1.8,2.1h-3.3c0-2.6,1.8-4.6,5.1-4.6c2.6,0,4.9,1.3,4.9,4.3c0,2.4-2.3,3.9-3.8,4.7c-2,1.3-2.5,1.8-2.5,2.9h6.1v2.7h-10C244.8,381.2,246.4,379.9,248.6,378.5z");
@@ -5199,7 +5193,7 @@ path.sim-board {
5199
5193
  const title = pxsim.localization.lf("micro:bit v2 needed");
5200
5194
  this.v2Circle = pxsim.svg.child(this.g, "circle", { r: 21, title: title });
5201
5195
  pxsim.svg.fill(this.v2Circle, "white");
5202
- this.v2Text = pxsim.svg.child(this.g, "text", { class: "sim-text", title: title });
5196
+ this.v2Text = pxsim.svg.child(this.g, "text", { class: "sim-text", title: title, "aria-hidden": true });
5203
5197
  this.v2Text.textContent = "V2";
5204
5198
  pxsim.svg.fill(this.v2Text, "black");
5205
5199
  this.v2Text.style.fontWeight = "700";
@@ -5225,8 +5219,7 @@ path.sim-board {
5225
5219
  this.microphoneLed = pxsim.svg.path(microg, "sim-led sim-mic", "M 352.852 71 C 351.315 71 350.07 72.248 350.07 73.784 V 79.056 C 350.07 80.594 351.316 81.838 352.852 81.838 C 354.387 81.838 355.634 80.593 355.634 79.056 V 73.784 C 355.634 72.248 354.387 71 352.852 71 Z M 346.743 79.981 C 346.743 82.84 348.853 85.062 351.501 85.658 V 87.095 H 348.448 V 89.329 H 357.366 V 87.095 H 354.306 V 85.658 C 356.954 85.064 359.071 82.842 359.071 79.981 H 357.057 C 357.057 82.174 355.168 83.81 352.905 83.81 C 350.64 83.81 348.757 82.173 348.757 79.981 Z");
5226
5220
  pxsim.svg.fills([this.microphoneLed], this.props.theme.ledOff);
5227
5221
  // ring
5228
- const microhole = pxsim.svg.child(this.g, "circle", { cx: 336, cy: 86, r: 3, stroke: "gold", strokeWidth: "1px" });
5229
- pxsim.svg.title(microhole, pxsim.localization.lf("microphone (micro:bit v2 needed)"));
5222
+ pxsim.svg.child(this.g, "circle", { cx: 336, cy: 86, r: 3, stroke: "gold", strokeWidth: "1px" });
5230
5223
  this.updateMicrophone();
5231
5224
  this.updateTheme();
5232
5225
  }
@@ -5330,10 +5323,11 @@ path.sim-board {
5330
5323
  let state = this.board;
5331
5324
  let pin = state.edgeConnectorState.pins[index];
5332
5325
  let svgpin = this.pins[index];
5333
- if (pin.mode & pxsim.PinFlags.Input) {
5326
+ if (pin.mode & pxsim.PinFlags.Input && !(pin.mode & pxsim.PinFlags.Touch)) {
5334
5327
  let cursor = pxsim.svg.cursorPoint(pt, this.element, ev);
5335
- let v = (400 - cursor.y) / 40 * 1023;
5336
- pin.value = Math.max(0, Math.min(1023, Math.floor(v)));
5328
+ let maxValue = pin.mode & pxsim.PinFlags.Analog ? 1023 : 1;
5329
+ let v = (400 - cursor.y) / 40 * maxValue;
5330
+ pin.value = Math.max(0, Math.min(maxValue, Math.floor(v)));
5337
5331
  }
5338
5332
  this.updatePin(pin, index);
5339
5333
  },
@@ -5343,10 +5337,11 @@ path.sim-board {
5343
5337
  let pin = state.edgeConnectorState.pins[index];
5344
5338
  let svgpin = this.pins[index];
5345
5339
  pxsim.U.addClass(svgpin, "touched");
5346
- if (pin.mode & pxsim.PinFlags.Input) {
5340
+ if (pin.mode & pxsim.PinFlags.Input && !(pin.mode & pxsim.PinFlags.Touch)) {
5347
5341
  let cursor = pxsim.svg.cursorPoint(pt, this.element, ev);
5348
- let v = (400 - cursor.y) / 40 * 1023;
5349
- pin.value = Math.max(0, Math.min(1023, Math.floor(v)));
5342
+ let maxValue = pin.mode & pxsim.PinFlags.Analog ? 1023 : 1;
5343
+ let v = (400 - cursor.y) / 40 * maxValue;
5344
+ pin.value = Math.max(0, Math.min(maxValue, Math.floor(v)));
5350
5345
  }
5351
5346
  this.updatePin(pin, index);
5352
5347
  },
@@ -5361,23 +5356,11 @@ path.sim-board {
5361
5356
  },
5362
5357
  // keydown
5363
5358
  (ev) => {
5364
- let charCode = (typeof ev.which == "number") ? ev.which : ev.keyCode;
5365
- let state = this.board;
5366
- let pin = state.edgeConnectorState.pins[index];
5367
- if (charCode === 40 || charCode === 37) { // Down/Left arrow
5368
- ev.preventDefault();
5369
- pin.value -= 10;
5370
- if (pin.value < 0) {
5371
- pin.value = 1023;
5372
- }
5373
- this.updatePin(pin, index);
5374
- }
5375
- else if (charCode === 38 || charCode === 39) { // Up/Right arrow
5376
- ev.preventDefault();
5377
- pin.value += 10;
5378
- if (pin.value > 1023) {
5379
- pin.value = 0;
5380
- }
5359
+ const state = this.board;
5360
+ const pin = state.edgeConnectorState.pins[index];
5361
+ const value = pinKeyHandler(ev, pin.value, 0, pin.mode & pxsim.PinFlags.Analog ? 1023 : 1, pin.mode);
5362
+ if (value !== undefined) {
5363
+ pin.value = value;
5381
5364
  this.updatePin(pin, index);
5382
5365
  }
5383
5366
  });
@@ -5523,6 +5506,65 @@ path.sim-board {
5523
5506
  }
5524
5507
  }
5525
5508
  visuals.MicrobitBoardSvg = MicrobitBoardSvg;
5509
+ const isHandledKey = (key) => {
5510
+ return ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "PageUp", "PageDown", "Home", "End"].includes(key);
5511
+ };
5512
+ const getSliderStepValue = (min, max) => {
5513
+ const range = max - min;
5514
+ // Assumes slider values are always integers.
5515
+ return Math.max(1, Math.floor(range / 10));
5516
+ };
5517
+ const commonKeyHandler = (e, currentValue, min, max) => {
5518
+ const key = e.key;
5519
+ if (isHandledKey(key)) {
5520
+ e.preventDefault();
5521
+ }
5522
+ switch (key) {
5523
+ case "ArrowDown":
5524
+ case "ArrowLeft": {
5525
+ return Math.max(min, currentValue - 1);
5526
+ }
5527
+ case "ArrowUp":
5528
+ case "ArrowRight": {
5529
+ return Math.min(max, currentValue + 1);
5530
+ }
5531
+ case "Home": {
5532
+ return min;
5533
+ }
5534
+ case "End": {
5535
+ return max;
5536
+ }
5537
+ case "PageDown": {
5538
+ const step = getSliderStepValue(min, max);
5539
+ const value = currentValue - step;
5540
+ return Math.max(min, value);
5541
+ }
5542
+ case "PageUp": {
5543
+ const step = getSliderStepValue(min, max);
5544
+ const value = currentValue + step;
5545
+ return Math.min(max, value);
5546
+ }
5547
+ }
5548
+ return undefined;
5549
+ };
5550
+ const pinKeyHandler = (e, currentValue, min, max, pinMode) => {
5551
+ const key = e.key;
5552
+ if (isHandledKey(key)) {
5553
+ if (!(pinMode & pxsim.PinFlags.Input)) {
5554
+ e.preventDefault();
5555
+ pxsim.accessibility.setLiveContent(pxsim.localization.lf("This pin is read-only"));
5556
+ return undefined;
5557
+ }
5558
+ if (pinMode & pxsim.PinFlags.Touch) {
5559
+ // The pin is in touch mode and has button markup, not a slider.
5560
+ e.preventDefault();
5561
+ return undefined;
5562
+ }
5563
+ }
5564
+ // The pin value for a digital pin may be higher than 1 depending on how its value was set.
5565
+ const currentValueClamped = Math.min(max, currentValue);
5566
+ return commonKeyHandler(e, currentValueClamped, min, max);
5567
+ };
5526
5568
  })(visuals = pxsim.visuals || (pxsim.visuals = {}));
5527
5569
  })(pxsim || (pxsim = {}));
5528
5570
  var pxsim;