Repository: evan-liu/karabiner.ts
Pull request #72 (2023-05-08 08:42:22)

✨ Add mapSimultaneous.modifiers()


Patch:
diff --git a/src/config/from.ts b/src/config/from.ts
index e822605..51cad72 100644
--- a/src/config/from.ts
+++ b/src/config/from.ts
@@ -2,11 +2,7 @@ import { FromKeyCode } from '../karabiner/key-code'
 import { getKeyWithAlias, KeyAlias, NumberKeyValue } from '../utils/key-alias'
 import { FromModifierParam } from './modifier'
 import { BasicManipulatorBuilder } from './manipulator'
-import {
-  FromEvent,
-  FromKeyType,
-  SimultaneousOptions,
-} from '../karabiner/karabiner-config'
+import { FromEvent } from '../karabiner/karabiner-config'
 import { FromConsumerKeyCode } from '../karabiner/consumer-key-code'
 import { PointingButton } from '../karabiner/pointing-button'
 import {
@@ -43,25 +39,6 @@ export function map(
   })
 }
 
-/** Start a manipulator with from.simultaneous */
-export function mapSimultaneous(
-  keys: Array<FromKeyParam | FromKeyType>,
-  options?: SimultaneousOptions,
-  threshold?: number,
-) {
-  const manipulatorBuilder = new BasicManipulatorBuilder({
-    simultaneous: keys.map((v) =>
-      typeof v === 'object' ? v : { key_code: getKeyWithAlias<FromKeyCode>(v) },
-    ),
-    simultaneous_options: options,
-  })
-  return threshold
-    ? manipulatorBuilder.parameters({
-        'basic.simultaneous_threshold_milliseconds': threshold,
-      })
-    : manipulatorBuilder
-}
-
 /** Start a manipulator with from.consumer_key_code and modifiers */
 export function mapConsumerKey(
   code: FromConsumerKeyCode,
diff --git a/src/config/simultaneous.ts b/src/config/simultaneous.ts
new file mode 100644
index 0000000..7f6678c
--- /dev/null
+++ b/src/config/simultaneous.ts
@@ -0,0 +1,59 @@
+import {
+  FromEvent,
+  FromKeyType,
+  SimultaneousOptions,
+} from '../karabiner/karabiner-config'
+import { BasicManipulatorBuilder } from './manipulator'
+import { getKeyWithAlias } from '../utils/key-alias'
+import { FromKeyCode } from '../karabiner/key-code'
+import { FromKeyParam } from './from'
+import {
+  FromModifierOverloadParam,
+  FromOptionalModifierParam,
+  parseFromModifierOverload,
+} from '../utils/from-modifier-overload'
+import { FromModifierParam } from './modifier'
+
+/** Start a manipulator with from.simultaneous */
+export function mapSimultaneous(
+  keys: Array<FromKeyParam | FromKeyType>,
+  options?: SimultaneousOptions,
+  threshold?: number,
+) {
+  const manipulatorBuilder = new SimultaneousManipulatorBuilder({
+    simultaneous: keys.map((v) =>
+      typeof v === 'object' ? v : { key_code: getKeyWithAlias<FromKeyCode>(v) },
+    ),
+    simultaneous_options: options,
+  })
+  if (threshold) {
+    manipulatorBuilder.parameters({
+      'basic.simultaneous_threshold_milliseconds': threshold,
+    })
+  }
+  return manipulatorBuilder
+}
+
+export class SimultaneousManipulatorBuilder extends BasicManipulatorBuilder {
+  constructor(from: FromEvent) {
+    super(from)
+  }
+
+  /** Set the from.modifiers */
+  public modifiers(
+    mandatoryModifiers?: FromModifierOverloadParam,
+    optionalModifiers?: FromModifierParam,
+  ): this
+  /** Set the from.modifiers */
+  public modifiers(modifiers: FromOptionalModifierParam): this
+  public modifiers(
+    mandatoryModifiers?: FromModifierOverloadParam,
+    optionalModifiers?: FromModifierParam,
+  ): this {
+    this.manipulator.from.modifiers =
+      mandatoryModifiers || optionalModifiers
+        ? parseFromModifierOverload(mandatoryModifiers, optionalModifiers)
+        : undefined
+    return this
+  }
+}
diff --git a/src/index.ts b/src/index.ts
index 0d36f03..3f129e5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -21,6 +21,7 @@ export { withModifier } from './utils/with-modifier'
 
 // From
 export * from './config/from'
+export { mapSimultaneous } from './config/simultaneous'
 export type { DoubleTapParam } from './config/double-tap'
 export { mapDoubleTap } from './config/double-tap'
 export { mouseMotionToScroll } from './config/mouse-motion-to-scroll'


Test patch:
diff --git a/src/config/from.test.ts b/src/config/from.test.ts
index a3bbc6a..f5b174d 100644
--- a/src/config/from.test.ts
+++ b/src/config/from.test.ts
@@ -1,5 +1,5 @@
 import { expect, test } from 'vitest'
-import { map, mapConsumerKey, mapPointingButton, mapSimultaneous } from './from'
+import { map, mapConsumerKey, mapPointingButton } from './from'
 
 test('map()', () => {
   expect(map('/', '⌘⌥', '⌃').build()[0].from).toEqual({
@@ -30,28 +30,6 @@ test('map()', () => {
   })
 })
 
-test('mapSimultaneous()', () => {
-  const { from, parameters } = mapSimultaneous(
-    ['a', '←', 2, { any: 'pointing_button' }],
-    { key_up_order: 'strict' },
-    123,
-  ).build()[0]
-  expect(from).toEqual({
-    simultaneous: [
-      { key_code: 'a' },
-      { key_code: 'left_arrow' },
-      { key_code: '2' },
-      { any: 'pointing_button' },
-    ],
-    simultaneous_options: { key_up_order: 'strict' },
-  })
-  expect(parameters).toEqual({
-    'basic.simultaneous_threshold_milliseconds': 123,
-  })
-
-  expect(mapSimultaneous([1, 2]).build()[0].parameters).toBeUndefined()
-})
-
 test('mapConsumerKey()', () => {
   expect(mapConsumerKey('menu', '⌘⌥', '⌃').build()[0].from).toEqual({
     consumer_key_code: 'menu',
diff --git a/src/config/simultaneous.test.ts b/src/config/simultaneous.test.ts
new file mode 100644
index 0000000..89d6679
--- /dev/null
+++ b/src/config/simultaneous.test.ts
@@ -0,0 +1,32 @@
+import { expect, test } from 'vitest'
+import { mapSimultaneous } from './simultaneous'
+
+test('mapSimultaneous()', () => {
+  const { from, parameters } = mapSimultaneous(
+    ['a', '←', 2, { any: 'pointing_button' }],
+    { key_up_order: 'strict' },
+    123,
+  ).build()[0]
+  expect(from).toEqual({
+    simultaneous: [
+      { key_code: 'a' },
+      { key_code: 'left_arrow' },
+      { key_code: '2' },
+      { any: 'pointing_button' },
+    ],
+    simultaneous_options: { key_up_order: 'strict' },
+  })
+
+  expect(parameters).toEqual({
+    'basic.simultaneous_threshold_milliseconds': 123,
+  })
+
+  expect(mapSimultaneous([1, 2]).build()[0].parameters).toBeUndefined()
+
+  expect(
+    mapSimultaneous([1, 2]).modifiers(null).build()[0].from.modifiers,
+  ).toBeUndefined()
+  expect(
+    mapSimultaneous([1, 2]).modifiers('⌘').build()[0].from.modifiers,
+  ).toEqual({ mandatory: ['command'] })
+})