diff --git a/docs/reference/actions.md b/docs/reference/actions.md
index 5c7e909b..778b0ad9 100644
--- a/docs/reference/actions.md
+++ b/docs/reference/actions.md
@@ -120,12 +120,13 @@ If you want to subscribe to a compound filter using a modifier key, you can writ
The list of supported modifier keys is shown below.
-| Modifier | Notes |
-| -------- | ------------------ |
-| `alt` | `option` on MacOS |
-| `ctrl` | |
-| `meta` | Command key on MacOS |
-| `shift` | |
+| Modifier | Notes |
+| -------- | ------------------------------------------------------ |
+| `alt` | `option` on MacOS |
+| `ctrl` | |
+| `meta` | `⌘ command` key on MacOS |
+| `shift` | |
+| `mod` | `⌘ command` (Meta) key on MacOS, `ctrl` key on Windows |
### Global Events
diff --git a/examples/views/slideshow.ejs b/examples/views/slideshow.ejs
index c404a9a8..23a0c8be 100644
--- a/examples/views/slideshow.ejs
+++ b/examples/views/slideshow.ejs
@@ -1,6 +1,6 @@
<%- include("layout/head") %>
-
+
@@ -8,6 +8,10 @@
🙈
🙉
🙊
+
+
+ Hint: Use keyboard shortcuts to navigate the slideshow: mod + j & mod + k
+
<%- include("layout/tail") %>
diff --git a/src/core/action.ts b/src/core/action.ts
index e009399b..dd47678c 100644
--- a/src/core/action.ts
+++ b/src/core/action.ts
@@ -4,7 +4,7 @@ import { Schema } from "./schema"
import { camelize } from "./string_helpers"
import { hasProperty } from "./utils"
-const allModifiers = ["meta", "ctrl", "alt", "shift"]
+const allModifiers = ["meta", "ctrl", "alt", "shift", "mod"]
export class Action {
readonly element: Element
@@ -98,9 +98,14 @@ export class Action {
}
private keyFilterDissatisfied(event: KeyboardEvent | MouseEvent, filters: Array
): boolean {
- const [meta, ctrl, alt, shift] = allModifiers.map((modifier) => filters.includes(modifier))
-
- return event.metaKey !== meta || event.ctrlKey !== ctrl || event.altKey !== alt || event.shiftKey !== shift
+ const [meta, ctrl, alt, shift, mod] = allModifiers.map((modifier) => filters.includes(modifier))
+ const modKey = mod && this.schema.getModKey()
+ return (
+ event.metaKey !== (meta || modKey === "Meta") ||
+ event.ctrlKey !== (ctrl || modKey === "Control") ||
+ event.altKey !== alt ||
+ event.shiftKey !== shift
+ )
}
}
diff --git a/src/core/schema.ts b/src/core/schema.ts
index bbc5b24a..fc6ca1f0 100644
--- a/src/core/schema.ts
+++ b/src/core/schema.ts
@@ -5,6 +5,7 @@ export interface Schema {
targetAttributeForScope(identifier: string): string
outletAttributeForScope(identifier: string, outlet: string): string
keyMappings: { [key: string]: string }
+ getModKey(): string
}
export const defaultSchema: Schema = {
@@ -31,6 +32,10 @@ export const defaultSchema: Schema = {
// [0-9]
...objectFromEntries("0123456789".split("").map((n) => [n, n])),
},
+ getModKey: ((_: { key?: string }) => () => {
+ if (!_.key) _.key = /Mac|iPod|iPhone|iPad/.test(window?.navigator?.platform || "") ? "Meta" : "Control"
+ return _.key // memoize the modifier key on first call to avoid platform sniffing on every call
+ })({ key: "" }),
}
function objectFromEntries(array: [string, any][]): object {
diff --git a/src/tests/modules/core/action_keyboard_filter_tests.ts b/src/tests/modules/core/action_keyboard_filter_tests.ts
index 64a9303c..904773d3 100644
--- a/src/tests/modules/core/action_keyboard_filter_tests.ts
+++ b/src/tests/modules/core/action_keyboard_filter_tests.ts
@@ -22,6 +22,7 @@ export default class ActionKeyboardFilterTests extends LogControllerTestCase {
`
@@ -177,7 +178,7 @@ export default class ActionKeyboardFilterTests extends LogControllerTestCase {
this.assertActions({ name: "log2", identifier: "a", eventType: "keydown", currentTarget: button })
}
- async "test ignore event handlers associated with modifiers other than ctrol+shift+a"() {
+ async "test ignore event handlers associated with modifiers other than ctrl+shift+a"() {
const button = this.findElement("#button9")
await this.nextFrame
await this.triggerKeyboardEvent(button, "keydown", { key: "A", ctrlKey: true, shiftKey: true })
@@ -197,4 +198,53 @@ export default class ActionKeyboardFilterTests extends LogControllerTestCase {
await this.triggerEvent(button, "jquery.a")
this.assertActions({ name: "log2", identifier: "a", eventType: "jquery.a", currentTarget: button })
}
+
+ async "test that the default schema getModKey resolved value is based on the platform"() {
+ const expectedKeyMapping = navigator.platform?.match(/Mac|iPod|iPhone|iPad/) ? "Meta" : "Control"
+ this.assert.equal(defaultSchema.getModKey(), expectedKeyMapping)
+ }
+
+ async "test ignore event handlers associated with modifiers mod+ (dynamic based on platform)"() {
+ const button = this.findElement("#button11")
+ await this.nextFrame
+ await this.triggerKeyboardEvent(button, "keydown", { key: "s", ctrlKey: true })
+ await this.triggerKeyboardEvent(button, "keydown", { key: "s", metaKey: true })
+ // We should only see one event using `mod` (which is dynamic based on platform)
+ this.assertActions({ name: "log", identifier: "a", eventType: "keydown", currentTarget: button })
+
+ customSchema.getModKey = () => "Control" // set up for next test
+ }
+
+ async "test ignore event handlers associated with modifiers mod+ (set to 'Control')"() {
+ // see .mod setting in previous test (mocking Windows)
+ this.schema = {
+ ...this.application.schema,
+ keyMappings: { ...this.application.schema.keyMappings, mod: "Control" },
+ }
+ const button = this.findElement("#button11")
+ await this.nextFrame
+ await this.triggerKeyboardEvent(button, "keydown", { key: "s", metaKey: true })
+ this.assertNoActions()
+ await this.triggerKeyboardEvent(button, "keydown", { key: "s", ctrlKey: true })
+ this.assertActions({ name: "log", identifier: "a", eventType: "keydown", currentTarget: button })
+
+ customSchema.getModKey = () => "Meta" // set up for next test
+ }
+
+ async "test ignore event handlers associated with modifiers mod+ (set to 'Meta')"() {
+ // see .mod setting in previous test (mocking Windows)
+ this.schema = {
+ ...this.application.schema,
+ keyMappings: { ...this.application.schema.keyMappings, mod: "Meta" },
+ }
+ const button = this.findElement("#button11")
+ await this.nextFrame
+ await this.triggerKeyboardEvent(button, "keydown", { key: "s", ctrlKey: true })
+ this.assertNoActions()
+ await this.triggerKeyboardEvent(button, "keydown", { key: "s", metaKey: true })
+ this.assertActions({ name: "log", identifier: "a", eventType: "keydown", currentTarget: button })
+
+ // Reset to default for any subsequent tests
+ this.schema = customSchema
+ }
}