Repository: evan-liu/karabiner.ts
Pull request #64 (2023-05-02 06:40:56)

✨ Allow toIfAlone() to take ToEvent or ToEvent[]


Patch:
diff --git a/src/config/condition.ts b/src/config/condition.ts
index 404b47d..0daf766 100644
--- a/src/config/condition.ts
+++ b/src/config/condition.ts
@@ -5,6 +5,7 @@ import {
   KeyboardType,
   ToVariable,
 } from '../karabiner/karabiner-config'
+import { toArray } from '../utils/to-array'
 
 export function ifVar(
   name: string,
@@ -55,7 +56,7 @@ export function ifDevice(
 ) {
   return new ConditionBuilder({
     type: 'device_if',
-    identifiers: Array.isArray(identifier) ? identifier : [identifier],
+    identifiers: toArray(identifier),
     description,
   })
 }
@@ -66,7 +67,7 @@ export function ifDeviceExists(
 ) {
   return new ConditionBuilder({
     type: 'device_exists_if',
-    identifiers: Array.isArray(identifier) ? identifier : [identifier],
+    identifiers: toArray(identifier),
     description,
   })
 }
@@ -77,7 +78,7 @@ export function ifKeyboardType(
 ) {
   return new ConditionBuilder({
     type: 'keyboard_type_if',
-    keyboard_types: Array.isArray(keyboardType) ? keyboardType : [keyboardType],
+    keyboard_types: toArray(keyboardType),
     description,
   })
 }
@@ -88,7 +89,7 @@ export function ifInputSource(
 ) {
   return new ConditionBuilder({
     type: 'input_source_if',
-    input_sources: Array.isArray(inputSource) ? inputSource : [inputSource],
+    input_sources: toArray(inputSource),
     description,
   })
 }
diff --git a/src/config/layer.ts b/src/config/layer.ts
index e5e8096..632fcbf 100644
--- a/src/config/layer.ts
+++ b/src/config/layer.ts
@@ -9,6 +9,7 @@ import { FromKeyParam, map } from './from'
 import { toSetVar } from './to'
 import { buildCondition, ConditionBuilder, ifVar } from './condition'
 import { BasicRuleBuilder } from './rule'
+import { toArray } from '../utils/to-array'
 
 export const defaultSimlayerThreshold = 200
 export const simlayerParameters = {
@@ -42,9 +43,7 @@ export class LayerRuleBuilder extends BasicRuleBuilder {
     protected readonly offValue: ToVariable['value'] = 0,
   ) {
     super(`Layer - ${varName}`)
-    this.keys = (Array.isArray(key) ? key : [key]).map(
-      (v) => getKeyWithAlias(v) as LayerKeyCode,
-    )
+    this.keys = toArray(key).map((v) => getKeyWithAlias(v) as LayerKeyCode)
     this.condition(this.layerCondition)
   }
 
@@ -93,9 +92,7 @@ export class SimlayerRuleBuilder extends BasicRuleBuilder {
     protected readonly offValue: ToVariable['value'] = 0,
   ) {
     super(`Simlayer - ${varName}`)
-    this.keys = (Array.isArray(key) ? key : [key]).map(
-      (v) => getKeyWithAlias(v) as LayerKeyCode,
-    )
+    this.keys = toArray(key).map((v) => getKeyWithAlias(v) as LayerKeyCode)
     this.condition(this.layerCondition)
   }
 
diff --git a/src/config/manipulator.ts b/src/config/manipulator.ts
index 7f12d39..e8c8d24 100644
--- a/src/config/manipulator.ts
+++ b/src/config/manipulator.ts
@@ -38,6 +38,7 @@ import { buildCondition, ConditionBuilder } from './condition'
 import { ToConsumerKeyCode } from '../karabiner/consumer-key-code'
 import { PointingButton } from '../karabiner/pointing-button'
 import { StickyModifierKeyCode } from '../karabiner/key-code'
+import { toArray } from '../utils/to-array'
 
 export interface ManipulatorBuilder {
   build(): Manipulator[]
@@ -57,15 +58,11 @@ export class BasicManipulatorBuilder implements ManipulatorBuilder {
     modifiers?: ModifierParam,
     options?: ToEventOptions,
   ): this {
-    if (Array.isArray(keyOrEvent)) {
-      this.manipulator.to = [...(this.manipulator.to || []), ...keyOrEvent]
-    } else {
-      this.addToEvent(
-        typeof keyOrEvent === 'string' || typeof keyOrEvent === 'number'
-          ? toKey(keyOrEvent, modifiers, options)
-          : keyOrEvent,
-      )
-    }
+    this.addToEvent(
+      typeof keyOrEvent === 'object'
+        ? keyOrEvent
+        : toKey(keyOrEvent, modifiers, options),
+    )
     return this
   }
 
@@ -188,79 +185,80 @@ export class BasicManipulatorBuilder implements ManipulatorBuilder {
     return this
   }
 
-  toIfAlone(event: ToEvent): this
+  toIfAlone(event: ToEvent | ToEvent[]): this
   toIfAlone(
     key: ToKeyParam,
     modifiers?: ModifierParam,
     options?: ToEventOptions,
   ): this
   toIfAlone(
-    keyOrEvent: ToEvent | ToKeyParam,
+    keyOrEvent: ToEvent | ToEvent[] | ToKeyParam,
     modifiers?: ModifierParam,
     options?: ToEventOptions,
   ): this {
     this.pushOrCreateList(
       this.manipulator,
       'to_if_alone',
-      typeof keyOrEvent === 'string'
-        ? toKey(keyOrEvent, modifiers, options)
-        : keyOrEvent,
+      typeof keyOrEvent === 'object'
+        ? keyOrEvent
+        : toKey(keyOrEvent, modifiers, options),
     )
     return this
   }
 
-  toIfHeldDown(event: ToEvent): this
+  toIfHeldDown(event: ToEvent | ToEvent[]): this
   toIfHeldDown(
     key: ToKeyParam,
     modifiers?: ModifierParam,
     options?: ToEventOptions,
   ): this
   toIfHeldDown(
-    keyOrEvent: ToEvent | ToKeyParam,
+    keyOrEvent: ToEvent | ToEvent[] | ToKeyParam,
     modifiers?: ModifierParam,
     options?: ToEventOptions,
   ): this {
     this.pushOrCreateList(
       this.manipulator,
       'to_if_held_down',
-      typeof keyOrEvent === 'string'
-        ? toKey(keyOrEvent, modifiers, options)
-        : keyOrEvent,
+      typeof keyOrEvent === 'object'
+        ? keyOrEvent
+        : toKey(keyOrEvent, modifiers, options),
     )
     return this
   }
 
-  toAfterKeyUp(event: ToEvent): this
+  toAfterKeyUp(event: ToEvent | ToEvent[]): this
   toAfterKeyUp(
     key: ToKeyParam,
     modifiers?: ModifierParam,
     options?: ToEventOptions,
   ): this
   toAfterKeyUp(
-    keyOrEvent: ToEvent | ToKeyParam,
+    keyOrEvent: ToEvent | ToEvent[] | ToKeyParam,
     modifiers?: ModifierParam,
     options?: ToEventOptions,
   ): this {
     this.pushOrCreateList(
       this.manipulator,
       'to_after_key_up',
-      typeof keyOrEvent === 'string'
-        ? toKey(keyOrEvent, modifiers, options)
-        : keyOrEvent,
+      typeof keyOrEvent === 'object'
+        ? keyOrEvent
+        : toKey(keyOrEvent, modifiers, options),
     )
     return this
   }
 
-  toDelayedAction(to_if_invoked: ToEvent[], to_if_canceled: ToEvent[]): this {
-    if (this.manipulator.to_delayed_action) {
-      this.manipulator.to_delayed_action.to_if_invoked.push(...to_if_invoked)
-      this.manipulator.to_delayed_action.to_if_canceled.push(...to_if_canceled)
-    } else {
-      this.manipulator.to_delayed_action = {
-        to_if_invoked,
-        to_if_canceled,
-      }
+  toDelayedAction(
+    ifInvoked: ToEvent | ToEvent[],
+    ifCanceled: ToEvent | ToEvent[],
+  ): this {
+    const delayedAction = this.manipulator.to_delayed_action || {
+      to_if_invoked: [],
+      to_if_canceled: [],
     }
+    toArray(ifInvoked).forEach((v) => delayedAction.to_if_invoked.push(v))
+    toArray(ifCanceled).forEach((v) => delayedAction.to_if_canceled.push(v))
+    this.manipulator.to_delayed_action = delayedAction
     return this
   }
 
@@ -284,13 +282,17 @@ export class BasicManipulatorBuilder implements ManipulatorBuilder {
     return [{ ...this.manipulator }]
   }
 
-  protected addToEvent(event: ToEvent) {
+  protected addToEvent(event: ToEvent | ToEvent[]) {
     this.pushOrCreateList(this.manipulator, 'to', event)
   }
 
-  protected pushOrCreateList<T extends {}>(obj: T, key: keyof T, item: any) {
-    const list = (obj[key] || []) as any[]
-    list.push(item)
+  protected pushOrCreateList<TListHolder extends {}, TItem>(
+    obj: TListHolder,
+    key: keyof TListHolder,
+    item: TItem | TItem[],
+  ) {
+    const list = (obj[key] || []) as TItem[]
+    toArray(item).forEach((v) => list.push(v))
     Object.assign(obj, { [key]: list })
   }
 }
diff --git a/src/utils/to-array.ts b/src/utils/to-array.ts
new file mode 100644
index 0000000..465da07
--- /dev/null
+++ b/src/utils/to-array.ts
@@ -0,0 +1,3 @@
+export function toArray<T>(src: T | T[]): T[] {
+  return Array.isArray(src) ? src : [src]
+}


Test patch:
diff --git a/src/config/manipulator.test.ts b/src/config/manipulator.test.ts
index 4dd12f0..766e2be 100644
--- a/src/config/manipulator.test.ts
+++ b/src/config/manipulator.test.ts
@@ -197,6 +197,10 @@ describe('ManipulatorBuilder', () => {
         .toIfAlone('b', '⌘', { lazy: true })
         .build()[0].to_if_alone,
     ).toEqual([toEvent, toEvent])
+    expect(
+      new BasicManipulatorBuilder(from).toIfAlone([toEvent, toEvent]).build()[0]
+        .to_if_alone,
+    ).toEqual([toEvent, toEvent])
   })
 
   test('toIfHeldDown()', () => {
@@ -206,6 +210,11 @@ describe('ManipulatorBuilder', () => {
         .toIfHeldDown('b', '⌘', { lazy: true })
         .build()[0].to_if_held_down,
     ).toEqual([toEvent, toEvent])
+    expect(
+      new BasicManipulatorBuilder(from)
+        .toIfHeldDown([toEvent, toEvent])
+        .build()[0].to_if_held_down,
+    ).toEqual([toEvent, toEvent])
   })
 
   test('toAfterKeyUp()', () => {
@@ -215,13 +224,18 @@ describe('ManipulatorBuilder', () => {
         .toAfterKeyUp('b', '⌘', { lazy: true })
         .build()[0].to_after_key_up,
     ).toEqual([toEvent, toEvent])
+    expect(
+      new BasicManipulatorBuilder(from)
+        .toAfterKeyUp([toEvent, toEvent])
+        .build()[0].to_after_key_up,
+    ).toEqual([toEvent, toEvent])
   })
 
   test('toDelayedAction()', () => {
     expect(
       new BasicManipulatorBuilder(from)
         .toDelayedAction([{ key_code: 'a' }], [{ key_code: 'b' }])
-        .toDelayedAction([{ key_code: 'c' }], [{ key_code: 'd' }])
+        .toDelayedAction([{ key_code: 'c' }], { key_code: 'd' })
         .build()[0].to_delayed_action,
     ).toEqual({
       to_if_invoked: [{ key_code: 'a' }, { key_code: 'c' }],