diff --git a/src/index.tsx b/src/index.tsx index 644d8b364..a9bb6dd0f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,6 +3,8 @@ * * SPDX-License-Identifier: MIT */ +// Before everything else so the shims are in place for module initialisation. +import "./polyfills"; // The CSS entry point: declares the cascade-layer order Panda's PostCSS // plugin fills, and the vendor-layer imports. First so app styles cascade // after it. diff --git a/src/polyfills.test.ts b/src/polyfills.test.ts new file mode 100644 index 000000000..f08702367 --- /dev/null +++ b/src/polyfills.test.ts @@ -0,0 +1,39 @@ +/** + * (c) 2026, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +import { describe, expect, it } from "vitest"; +import { at } from "./polyfills"; + +describe("at", () => { + const call = (target: ArrayLike, index: number) => + at.call(target, index) as T | undefined; + + it("indexes from the start", () => { + expect(call(["a", "b", "c"], 0)).toEqual("a"); + expect(call(["a", "b", "c"], 2)).toEqual("c"); + }); + + it("indexes from the end", () => { + expect(call(["a", "b", "c"], -1)).toEqual("c"); + expect(call(["a", "b", "c"], -3)).toEqual("a"); + }); + + it("returns undefined when out of range", () => { + expect(call(["a"], 1)).toBeUndefined(); + expect(call(["a"], -2)).toBeUndefined(); + expect(call([], -1)).toBeUndefined(); + }); + + it("truncates and coerces the index", () => { + expect(call(["a", "b", "c"], 1.9)).toEqual("b"); + expect(call(["a", "b", "c"], -1.9)).toEqual("c"); + expect(call(["a", "b", "c"], NaN)).toEqual("a"); + expect(call(["a", "b", "c"], undefined as unknown as number)).toEqual("a"); + }); + + it("works on strings", () => { + expect(call("abc", -1)).toEqual("c"); + }); +}); diff --git a/src/polyfills.ts b/src/polyfills.ts new file mode 100644 index 000000000..c9ab87e36 --- /dev/null +++ b/src/polyfills.ts @@ -0,0 +1,21 @@ +/** + * (c) 2026, Micro:bit Educational Foundation and contributors + * + * SPDX-License-Identifier: MIT + */ +// Remove when we drop Safari < 15.4. +// Also seen helping some v old Chrome but not reasonable to maintain support here past when we drop older Safari. +export function at(this: ArrayLike, index: number): T | undefined { + const i = Math.trunc(index) || 0; + return this[i < 0 ? this.length + i : i]; +} + +for (const proto of [Array.prototype, String.prototype]) { + if (!(proto as { at?: unknown }).at) { + Object.defineProperty(proto, "at", { + value: at, + writable: true, + configurable: true, + }); + } +}