@flowgram.ai/shortcuts-plugin 1.0.2 → 1.0.3

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/dist/esm/index.js CHANGED
@@ -43,6 +43,11 @@ var ShortcutsRegistry = class {
43
43
  }
44
44
  });
45
45
  }
46
+ removeHandler(commandId) {
47
+ this.shortcutsHandlers = this.shortcutsHandlers.filter(
48
+ (handler) => handler.commandId !== commandId
49
+ );
50
+ }
46
51
  has(commandId) {
47
52
  return this.shortcutsHandlers.some((handler) => handler.commandId === commandId);
48
53
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/create-shortcuts-plugin.ts","../../src/shortcuts-contribution.ts","../../src/layers/shortcuts-layer.tsx","../../src/shortcuts-utils.ts"],"sourcesContent":["/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { bindContributionProvider, definePluginCreator } from '@flowgram.ai/core';\n\nimport { ShortcutsRegistry, ShortcutsContribution } from './shortcuts-contribution';\nimport { ShortcutsLayer } from './layers';\n\n/**\n * @param opts\n *\n * createShortcutsPlugin({\n * registerShortcuts(registry) {\n * }\n * })\n */\nexport const createShortcutsPlugin = definePluginCreator<ShortcutsContribution>({\n onBind: ({ bind }) => {\n bind(ShortcutsRegistry).toSelf().inSingletonScope();\n bindContributionProvider(bind, ShortcutsContribution);\n },\n onInit: (ctx) => {\n ctx.playground.registerLayer(ShortcutsLayer);\n },\n contributionKeys: [ShortcutsContribution],\n});\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { inject, injectable, named, optional, postConstruct } from 'inversify';\nimport { Command, CommandRegistry, ContributionProvider } from '@flowgram.ai/core';\n\nexport interface ShortcutsHandler {\n commandId: string;\n commandDetail?: Omit<Command, 'id'>;\n shortcuts: string[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n isEnabled?: (...args: any[]) => boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n execute: (...args: any[]) => void;\n}\n\nexport const ShortcutsContribution = Symbol('ShortcutsContribution');\n\nexport interface ShortcutsContribution {\n registerShortcuts: (registry: ShortcutsRegistry) => void;\n}\n\n@injectable()\nexport class ShortcutsRegistry {\n @inject(ContributionProvider)\n @named(ShortcutsContribution)\n @optional()\n protected contribs: ContributionProvider<ShortcutsContribution>;\n\n @inject(CommandRegistry) protected commandRegistry: CommandRegistry;\n\n readonly shortcutsHandlers: ShortcutsHandler[] = [];\n\n addHandlers(...handlers: ShortcutsHandler[]): void {\n // 注册 command\n handlers.forEach((handler) => {\n if (!this.commandRegistry.getCommand(handler.commandId)) {\n this.commandRegistry.registerCommand(\n { id: handler.commandId, ...(handler.commandDetail || {}) },\n { execute: handler.execute, isEnabled: handler.isEnabled }\n );\n } else {\n this.commandRegistry.registerHandler(handler.commandId, {\n execute: handler.execute,\n isEnabled: handler.isEnabled,\n });\n }\n });\n // Insert before for override pre handlers\n this.shortcutsHandlers.unshift(...handlers);\n }\n\n addHandlersIfNotFound(...handlers: ShortcutsHandler[]): void {\n handlers.forEach((handler) => {\n if (!this.has(handler.commandId)) {\n this.addHandlers(handler);\n }\n });\n }\n\n has(commandId: string): boolean {\n return this.shortcutsHandlers.some((handler) => handler.commandId === commandId);\n }\n\n @postConstruct()\n protected init(): void {\n this.contribs?.forEach((contrib) => contrib.registerShortcuts(this));\n }\n}\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { inject, injectable } from 'inversify';\nimport { Layer, SelectionService, Command } from '@flowgram.ai/core';\n\nimport { isShortcutsMatch } from '../shortcuts-utils';\nimport { ShortcutsRegistry } from '../shortcuts-contribution';\n\n@injectable()\nexport class ShortcutsLayer extends Layer<object> {\n static type = 'ShortcutsLayer';\n\n @inject(ShortcutsRegistry) shortcuts: ShortcutsRegistry;\n\n @inject(SelectionService) selection: SelectionService;\n\n onReady(): void {\n this.shortcuts.addHandlersIfNotFound(\n /**\n * 放大\n */\n {\n commandId: Command.Default.ZOOM_IN,\n shortcuts: ['meta =', 'ctrl ='],\n execute: () => {\n // TODO 这里要判断 CurrentEditor\n this.config.zoomin();\n },\n },\n /**\n * 缩小\n */\n {\n commandId: Command.Default.ZOOM_OUT,\n shortcuts: ['meta -', 'ctrl -'],\n execute: () => {\n // TODO 这里要判断 CurrentEditor\n this.config.zoomout();\n },\n },\n );\n this.toDispose.pushAll([\n // 监听画布鼠标移动事件\n this.listenPlaygroundEvent('keydown', (e: KeyboardEvent) => {\n if (!this.isFocused || e.target !== this.playgroundNode) {\n return;\n }\n this.shortcuts.shortcutsHandlers.some(shortcutsHandler => {\n if (\n isShortcutsMatch(e, shortcutsHandler.shortcuts) &&\n (!shortcutsHandler.isEnabled || shortcutsHandler.isEnabled(e))\n ) {\n shortcutsHandler.execute(e);\n e.preventDefault();\n return true;\n }\n });\n }),\n ]);\n }\n}\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nconst isAppleDevice = /(mac|iphone|ipod|ipad)/i.test(\n typeof navigator !== 'undefined' ? navigator?.platform : '',\n);\n// 键盘事件 keyCode 别名\nconst aliasKeyCodeMap: Record<string, number | number[]> = {\n '0': 48,\n '1': 49,\n '2': 50,\n '3': 51,\n '4': 52,\n '5': 53,\n '6': 54,\n '7': 55,\n '8': 56,\n '9': 57,\n backspace: 8,\n tab: 9,\n enter: 13,\n shift: 16,\n ctrl: 17,\n alt: 18,\n pausebreak: 19,\n capslock: 20,\n esc: 27,\n space: 32,\n pageup: 33,\n pagedown: 34,\n end: 35,\n home: 36,\n leftarrow: 37,\n uparrow: 38,\n rightarrow: 39,\n downarrow: 40,\n insert: 45,\n delete: 46,\n a: 65,\n b: 66,\n c: 67,\n d: 68,\n e: 69,\n f: 70,\n g: 71,\n h: 72,\n i: 73,\n j: 74,\n k: 75,\n l: 76,\n m: 77,\n n: 78,\n o: 79,\n p: 80,\n q: 81,\n r: 82,\n s: 83,\n t: 84,\n u: 85,\n v: 86,\n w: 87,\n x: 88,\n y: 89,\n z: 90,\n leftwindowkey: 91,\n rightwindowkey: 92,\n meta: isAppleDevice ? [91, 93] : [91, 92],\n selectkey: 93,\n numpad0: 96,\n numpad1: 97,\n numpad2: 98,\n numpad3: 99,\n numpad4: 100,\n numpad5: 101,\n numpad6: 102,\n numpad7: 103,\n numpad8: 104,\n numpad9: 105,\n multiply: 106,\n add: 107,\n subtract: 109,\n decimalpoint: 110,\n divide: 111,\n f1: 112,\n f2: 113,\n f3: 114,\n f4: 115,\n f5: 116,\n f6: 117,\n f7: 118,\n f8: 119,\n f9: 120,\n f10: 121,\n f11: 122,\n f12: 123,\n numlock: 144,\n scrolllock: 145,\n semicolon: 186,\n equalsign: 187,\n '=': 187,\n comma: 188,\n dash: 189,\n '-': 189,\n period: 190,\n forwardslash: 191,\n graveaccent: 192,\n openbracket: 219,\n backslash: 220,\n closebracket: 221,\n singlequote: 222,\n};\n\nconst modifierKey: any = {\n ctrl: (event: KeyboardEvent) => event.ctrlKey,\n shift: (event: KeyboardEvent) => event.shiftKey,\n alt: (event: KeyboardEvent) => event.altKey,\n meta: (event: KeyboardEvent) => {\n if (event.type === 'keyup') {\n return (aliasKeyCodeMap.meta as number[]).includes(event.keyCode);\n }\n return event.metaKey;\n },\n};\n\n// 根据 event 计算激活键数量\nfunction countKeyByEvent(event: KeyboardEvent): number {\n const countOfModifier = Object.keys(modifierKey).reduce((total, key) => {\n if (modifierKey[key](event)) {\n return total + 1;\n }\n\n return total;\n }, 0);\n\n // 16 17 18 91 92 是修饰键的 keyCode,如果 keyCode 是修饰键,那么激活数量就是修饰键的数量,如果不是,那么就需要 +1\n return [16, 17, 18, 91, 92].includes(event.keyCode) ? countOfModifier : countOfModifier + 1;\n}\n\n/**\n *\n * @param event\n * @param keyString 'ctrl.s' 'meta.s'\n * @param exactMatch\n */\nfunction isKeyStringMatch(event: KeyboardEvent, keyString: string, exactMatch = true): boolean {\n // 浏览器自动补全 input 的时候,会触发 keyDown、keyUp 事件,但此时 event.key 等为空\n if (!event.key || !keyString) {\n return false;\n }\n // 字符串依次判断是否有组合键\n const genArr = keyString.split(/\\s+/);\n let genLen = 0;\n\n for (const key of genArr) {\n // 组合键\n const genModifier = modifierKey[key];\n // keyCode 别名\n const aliasKeyCode: number | number[] = aliasKeyCodeMap[key.toLowerCase()];\n\n if ((genModifier && genModifier(event)) || (aliasKeyCode && aliasKeyCode === event.keyCode)) {\n genLen++;\n }\n }\n\n /**\n * 需要判断触发的键位和监听的键位完全一致,判断方法就是触发的键位里有且等于监听的键位\n * genLen === genArr.length 能判断出来触发的键位里有监听的键位\n * countKeyByEvent(event) === genArr.length 判断出来触发的键位数量里有且等于监听的键位数量\n * 主要用来防止按组合键其子集也会触发的情况,例如监听 ctrl+a 会触发监听 ctrl 和 a 两个键的事件。\n */\n if (exactMatch) {\n return genLen === genArr.length && countKeyByEvent(event) === genArr.length;\n }\n return genLen === genArr.length;\n}\n\n/**\n * 匹配指定的快捷键\n * @param event\n * @param shortcuts\n */\nexport function isShortcutsMatch(event: KeyboardEvent, shortcuts: string[]): boolean {\n return shortcuts.some(keyString => isKeyStringMatch(event, keyString));\n}\n"],"mappings":"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","names":["inject","injectable","Command","Command","inject","injectable"]}
1
+ {"version":3,"sources":["../../src/create-shortcuts-plugin.ts","../../src/shortcuts-contribution.ts","../../src/layers/shortcuts-layer.tsx","../../src/shortcuts-utils.ts"],"sourcesContent":["/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { bindContributionProvider, definePluginCreator } from '@flowgram.ai/core';\n\nimport { ShortcutsRegistry, ShortcutsContribution } from './shortcuts-contribution';\nimport { ShortcutsLayer } from './layers';\n\n/**\n * @param opts\n *\n * createShortcutsPlugin({\n * registerShortcuts(registry) {\n * }\n * })\n */\nexport const createShortcutsPlugin = definePluginCreator<ShortcutsContribution>({\n onBind: ({ bind }) => {\n bind(ShortcutsRegistry).toSelf().inSingletonScope();\n bindContributionProvider(bind, ShortcutsContribution);\n },\n onInit: (ctx) => {\n ctx.playground.registerLayer(ShortcutsLayer);\n },\n contributionKeys: [ShortcutsContribution],\n});\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { inject, injectable, named, optional, postConstruct } from 'inversify';\nimport { Command, CommandRegistry, ContributionProvider } from '@flowgram.ai/core';\n\nexport interface ShortcutsHandler {\n commandId: string;\n commandDetail?: Omit<Command, 'id'>;\n shortcuts: string[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n isEnabled?: (...args: any[]) => boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n execute: (...args: any[]) => void;\n}\n\nexport const ShortcutsContribution = Symbol('ShortcutsContribution');\n\nexport interface ShortcutsContribution {\n registerShortcuts: (registry: ShortcutsRegistry) => void;\n}\n\n@injectable()\nexport class ShortcutsRegistry {\n @inject(ContributionProvider)\n @named(ShortcutsContribution)\n @optional()\n protected contribs: ContributionProvider<ShortcutsContribution>;\n\n @inject(CommandRegistry) protected commandRegistry: CommandRegistry;\n\n shortcutsHandlers: ShortcutsHandler[] = [];\n\n addHandlers(...handlers: ShortcutsHandler[]): void {\n // 注册 command\n handlers.forEach((handler) => {\n if (!this.commandRegistry.getCommand(handler.commandId)) {\n this.commandRegistry.registerCommand(\n { id: handler.commandId, ...(handler.commandDetail || {}) },\n { execute: handler.execute, isEnabled: handler.isEnabled }\n );\n } else {\n this.commandRegistry.registerHandler(handler.commandId, {\n execute: handler.execute,\n isEnabled: handler.isEnabled,\n });\n }\n });\n // Insert before for override pre handlers\n this.shortcutsHandlers.unshift(...handlers);\n }\n\n addHandlersIfNotFound(...handlers: ShortcutsHandler[]): void {\n handlers.forEach((handler) => {\n if (!this.has(handler.commandId)) {\n this.addHandlers(handler);\n }\n });\n }\n\n removeHandler(commandId: string): void {\n this.shortcutsHandlers = this.shortcutsHandlers.filter(\n (handler) => handler.commandId !== commandId\n );\n }\n\n has(commandId: string): boolean {\n return this.shortcutsHandlers.some((handler) => handler.commandId === commandId);\n }\n\n @postConstruct()\n protected init(): void {\n this.contribs?.forEach((contrib) => contrib.registerShortcuts(this));\n }\n}\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { inject, injectable } from 'inversify';\nimport { Layer, SelectionService, Command } from '@flowgram.ai/core';\n\nimport { isShortcutsMatch } from '../shortcuts-utils';\nimport { ShortcutsRegistry } from '../shortcuts-contribution';\n\n@injectable()\nexport class ShortcutsLayer extends Layer<object> {\n static type = 'ShortcutsLayer';\n\n @inject(ShortcutsRegistry) shortcuts: ShortcutsRegistry;\n\n @inject(SelectionService) selection: SelectionService;\n\n onReady(): void {\n this.shortcuts.addHandlersIfNotFound(\n /**\n * 放大\n */\n {\n commandId: Command.Default.ZOOM_IN,\n shortcuts: ['meta =', 'ctrl ='],\n execute: () => {\n // TODO 这里要判断 CurrentEditor\n this.config.zoomin();\n },\n },\n /**\n * 缩小\n */\n {\n commandId: Command.Default.ZOOM_OUT,\n shortcuts: ['meta -', 'ctrl -'],\n execute: () => {\n // TODO 这里要判断 CurrentEditor\n this.config.zoomout();\n },\n },\n );\n this.toDispose.pushAll([\n // 监听画布鼠标移动事件\n this.listenPlaygroundEvent('keydown', (e: KeyboardEvent) => {\n if (!this.isFocused || e.target !== this.playgroundNode) {\n return;\n }\n this.shortcuts.shortcutsHandlers.some(shortcutsHandler => {\n if (\n isShortcutsMatch(e, shortcutsHandler.shortcuts) &&\n (!shortcutsHandler.isEnabled || shortcutsHandler.isEnabled(e))\n ) {\n shortcutsHandler.execute(e);\n e.preventDefault();\n return true;\n }\n });\n }),\n ]);\n }\n}\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nconst isAppleDevice = /(mac|iphone|ipod|ipad)/i.test(\n typeof navigator !== 'undefined' ? navigator?.platform : '',\n);\n// 键盘事件 keyCode 别名\nconst aliasKeyCodeMap: Record<string, number | number[]> = {\n '0': 48,\n '1': 49,\n '2': 50,\n '3': 51,\n '4': 52,\n '5': 53,\n '6': 54,\n '7': 55,\n '8': 56,\n '9': 57,\n backspace: 8,\n tab: 9,\n enter: 13,\n shift: 16,\n ctrl: 17,\n alt: 18,\n pausebreak: 19,\n capslock: 20,\n esc: 27,\n space: 32,\n pageup: 33,\n pagedown: 34,\n end: 35,\n home: 36,\n leftarrow: 37,\n uparrow: 38,\n rightarrow: 39,\n downarrow: 40,\n insert: 45,\n delete: 46,\n a: 65,\n b: 66,\n c: 67,\n d: 68,\n e: 69,\n f: 70,\n g: 71,\n h: 72,\n i: 73,\n j: 74,\n k: 75,\n l: 76,\n m: 77,\n n: 78,\n o: 79,\n p: 80,\n q: 81,\n r: 82,\n s: 83,\n t: 84,\n u: 85,\n v: 86,\n w: 87,\n x: 88,\n y: 89,\n z: 90,\n leftwindowkey: 91,\n rightwindowkey: 92,\n meta: isAppleDevice ? [91, 93] : [91, 92],\n selectkey: 93,\n numpad0: 96,\n numpad1: 97,\n numpad2: 98,\n numpad3: 99,\n numpad4: 100,\n numpad5: 101,\n numpad6: 102,\n numpad7: 103,\n numpad8: 104,\n numpad9: 105,\n multiply: 106,\n add: 107,\n subtract: 109,\n decimalpoint: 110,\n divide: 111,\n f1: 112,\n f2: 113,\n f3: 114,\n f4: 115,\n f5: 116,\n f6: 117,\n f7: 118,\n f8: 119,\n f9: 120,\n f10: 121,\n f11: 122,\n f12: 123,\n numlock: 144,\n scrolllock: 145,\n semicolon: 186,\n equalsign: 187,\n '=': 187,\n comma: 188,\n dash: 189,\n '-': 189,\n period: 190,\n forwardslash: 191,\n graveaccent: 192,\n openbracket: 219,\n backslash: 220,\n closebracket: 221,\n singlequote: 222,\n};\n\nconst modifierKey: any = {\n ctrl: (event: KeyboardEvent) => event.ctrlKey,\n shift: (event: KeyboardEvent) => event.shiftKey,\n alt: (event: KeyboardEvent) => event.altKey,\n meta: (event: KeyboardEvent) => {\n if (event.type === 'keyup') {\n return (aliasKeyCodeMap.meta as number[]).includes(event.keyCode);\n }\n return event.metaKey;\n },\n};\n\n// 根据 event 计算激活键数量\nfunction countKeyByEvent(event: KeyboardEvent): number {\n const countOfModifier = Object.keys(modifierKey).reduce((total, key) => {\n if (modifierKey[key](event)) {\n return total + 1;\n }\n\n return total;\n }, 0);\n\n // 16 17 18 91 92 是修饰键的 keyCode,如果 keyCode 是修饰键,那么激活数量就是修饰键的数量,如果不是,那么就需要 +1\n return [16, 17, 18, 91, 92].includes(event.keyCode) ? countOfModifier : countOfModifier + 1;\n}\n\n/**\n *\n * @param event\n * @param keyString 'ctrl.s' 'meta.s'\n * @param exactMatch\n */\nfunction isKeyStringMatch(event: KeyboardEvent, keyString: string, exactMatch = true): boolean {\n // 浏览器自动补全 input 的时候,会触发 keyDown、keyUp 事件,但此时 event.key 等为空\n if (!event.key || !keyString) {\n return false;\n }\n // 字符串依次判断是否有组合键\n const genArr = keyString.split(/\\s+/);\n let genLen = 0;\n\n for (const key of genArr) {\n // 组合键\n const genModifier = modifierKey[key];\n // keyCode 别名\n const aliasKeyCode: number | number[] = aliasKeyCodeMap[key.toLowerCase()];\n\n if ((genModifier && genModifier(event)) || (aliasKeyCode && aliasKeyCode === event.keyCode)) {\n genLen++;\n }\n }\n\n /**\n * 需要判断触发的键位和监听的键位完全一致,判断方法就是触发的键位里有且等于监听的键位\n * genLen === genArr.length 能判断出来触发的键位里有监听的键位\n * countKeyByEvent(event) === genArr.length 判断出来触发的键位数量里有且等于监听的键位数量\n * 主要用来防止按组合键其子集也会触发的情况,例如监听 ctrl+a 会触发监听 ctrl 和 a 两个键的事件。\n */\n if (exactMatch) {\n return genLen === genArr.length && countKeyByEvent(event) === genArr.length;\n }\n return genLen === genArr.length;\n}\n\n/**\n * 匹配指定的快捷键\n * @param event\n * @param shortcuts\n */\nexport function isShortcutsMatch(event: KeyboardEvent, shortcuts: string[]): boolean {\n return shortcuts.some(keyString => isKeyStringMatch(event, keyString));\n}\n"],"mappings":";;;;;;;;;;;;AAKA,SAAS,0BAA0B,2BAA2B;;;ACA9D,SAAS,QAAQ,YAAY,OAAO,UAAU,qBAAqB;AACnE,SAAkB,iBAAiB,4BAA4B;AAYxD,IAAM,wBAAwB,OAAO,uBAAuB;AAO5D,IAAM,oBAAN,MAAwB;AAAA,EAAxB;AAQL,6BAAwC,CAAC;AAAA;AAAA,EAEzC,eAAe,UAAoC;AAEjD,aAAS,QAAQ,CAAC,YAAY;AAC5B,UAAI,CAAC,KAAK,gBAAgB,WAAW,QAAQ,SAAS,GAAG;AACvD,aAAK,gBAAgB;AAAA,UACnB,EAAE,IAAI,QAAQ,WAAW,GAAI,QAAQ,iBAAiB,CAAC,EAAG;AAAA,UAC1D,EAAE,SAAS,QAAQ,SAAS,WAAW,QAAQ,UAAU;AAAA,QAC3D;AAAA,MACF,OAAO;AACL,aAAK,gBAAgB,gBAAgB,QAAQ,WAAW;AAAA,UACtD,SAAS,QAAQ;AAAA,UACjB,WAAW,QAAQ;AAAA,QACrB,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAED,SAAK,kBAAkB,QAAQ,GAAG,QAAQ;AAAA,EAC5C;AAAA,EAEA,yBAAyB,UAAoC;AAC3D,aAAS,QAAQ,CAAC,YAAY;AAC5B,UAAI,CAAC,KAAK,IAAI,QAAQ,SAAS,GAAG;AAChC,aAAK,YAAY,OAAO;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,cAAc,WAAyB;AACrC,SAAK,oBAAoB,KAAK,kBAAkB;AAAA,MAC9C,CAAC,YAAY,QAAQ,cAAc;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,IAAI,WAA4B;AAC9B,WAAO,KAAK,kBAAkB,KAAK,CAAC,YAAY,QAAQ,cAAc,SAAS;AAAA,EACjF;AAAA,EAGU,OAAa;AACrB,SAAK,UAAU,QAAQ,CAAC,YAAY,QAAQ,kBAAkB,IAAI,CAAC;AAAA,EACrE;AACF;AA/CY;AAAA,EAHT,OAAO,oBAAoB;AAAA,EAC3B,MAAM,qBAAqB;AAAA,EAC3B,SAAS;AAAA,GAHC,kBAID;AAEyB;AAAA,EAAlC,OAAO,eAAe;AAAA,GANZ,kBAMwB;AA0CzB;AAAA,EADT,cAAc;AAAA,GA/CJ,kBAgDD;AAhDC,oBAAN;AAAA,EADN,WAAW;AAAA,GACC;;;ACpBb,SAAS,UAAAA,SAAQ,cAAAC,mBAAkB;AACnC,SAAS,OAAO,kBAAkB,WAAAC,gBAAe;;;ACDjD,IAAM,gBAAgB,0BAA0B;AAAA,EAC9C,OAAO,cAAc,cAAc,WAAW,WAAW;AAC3D;AAEA,IAAM,kBAAqD;AAAA,EACzD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,WAAW;AAAA,EACX,KAAK;AAAA,EACL,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AAAA,EACN,KAAK;AAAA,EACL,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,KAAK;AAAA,EACL,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,KAAK;AAAA,EACL,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,MAAM,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;AAAA,EACxC,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,KAAK;AAAA,EACL,UAAU;AAAA,EACV,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,WAAW;AAAA,EACX,KAAK;AAAA,EACL,OAAO;AAAA,EACP,MAAM;AAAA,EACN,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,aAAa;AAAA,EACb,aAAa;AAAA,EACb,WAAW;AAAA,EACX,cAAc;AAAA,EACd,aAAa;AACf;AAEA,IAAM,cAAmB;AAAA,EACvB,MAAM,CAAC,UAAyB,MAAM;AAAA,EACtC,OAAO,CAAC,UAAyB,MAAM;AAAA,EACvC,KAAK,CAAC,UAAyB,MAAM;AAAA,EACrC,MAAM,CAAC,UAAyB;AAC9B,QAAI,MAAM,SAAS,SAAS;AAC1B,aAAQ,gBAAgB,KAAkB,SAAS,MAAM,OAAO;AAAA,IAClE;AACA,WAAO,MAAM;AAAA,EACf;AACF;AAGA,SAAS,gBAAgB,OAA8B;AACrD,QAAM,kBAAkB,OAAO,KAAK,WAAW,EAAE,OAAO,CAAC,OAAO,QAAQ;AACtE,QAAI,YAAY,GAAG,EAAE,KAAK,GAAG;AAC3B,aAAO,QAAQ;AAAA,IACjB;AAEA,WAAO;AAAA,EACT,GAAG,CAAC;AAGJ,SAAO,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE,EAAE,SAAS,MAAM,OAAO,IAAI,kBAAkB,kBAAkB;AAC5F;AAQA,SAAS,iBAAiB,OAAsB,WAAmB,aAAa,MAAe;AAE7F,MAAI,CAAC,MAAM,OAAO,CAAC,WAAW;AAC5B,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,UAAU,MAAM,KAAK;AACpC,MAAI,SAAS;AAEb,aAAW,OAAO,QAAQ;AAExB,UAAM,cAAc,YAAY,GAAG;AAEnC,UAAM,eAAkC,gBAAgB,IAAI,YAAY,CAAC;AAEzE,QAAK,eAAe,YAAY,KAAK,KAAO,gBAAgB,iBAAiB,MAAM,SAAU;AAC3F;AAAA,IACF;AAAA,EACF;AAQA,MAAI,YAAY;AACd,WAAO,WAAW,OAAO,UAAU,gBAAgB,KAAK,MAAM,OAAO;AAAA,EACvE;AACA,SAAO,WAAW,OAAO;AAC3B;AAOO,SAAS,iBAAiB,OAAsB,WAA8B;AACnF,SAAO,UAAU,KAAK,eAAa,iBAAiB,OAAO,SAAS,CAAC;AACvE;;;AD7KO,IAAM,iBAAN,cAA6B,MAAc;AAAA,EAOhD,UAAgB;AACd,SAAK,UAAU;AAAA;AAAA;AAAA;AAAA,MAIb;AAAA,QACE,WAAWC,SAAQ,QAAQ;AAAA,QAC3B,WAAW,CAAC,UAAU,QAAQ;AAAA,QAC9B,SAAS,MAAM;AAEb,eAAK,OAAO,OAAO;AAAA,QACrB;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAIA;AAAA,QACE,WAAWA,SAAQ,QAAQ;AAAA,QAC3B,WAAW,CAAC,UAAU,QAAQ;AAAA,QAC9B,SAAS,MAAM;AAEb,eAAK,OAAO,QAAQ;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AACA,SAAK,UAAU,QAAQ;AAAA;AAAA,MAErB,KAAK,sBAAsB,WAAW,CAAC,MAAqB;AAC1D,YAAI,CAAC,KAAK,aAAa,EAAE,WAAW,KAAK,gBAAgB;AACvD;AAAA,QACF;AACA,aAAK,UAAU,kBAAkB,KAAK,sBAAoB;AACxD,cACE,iBAAiB,GAAG,iBAAiB,SAAS,MAC7C,CAAC,iBAAiB,aAAa,iBAAiB,UAAU,CAAC,IAC5D;AACA,6BAAiB,QAAQ,CAAC;AAC1B,cAAE,eAAe;AACjB,mBAAO;AAAA,UACT;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAnDa,eACJ,OAAO;AAEa;AAAA,EAA1BC,QAAO,iBAAiB;AAAA,GAHd,eAGgB;AAED;AAAA,EAAzBA,QAAO,gBAAgB;AAAA,GALb,eAKe;AALf,iBAAN;AAAA,EADNC,YAAW;AAAA,GACC;;;AFMN,IAAM,wBAAwB,oBAA2C;AAAA,EAC9E,QAAQ,CAAC,EAAE,KAAK,MAAM;AACpB,SAAK,iBAAiB,EAAE,OAAO,EAAE,iBAAiB;AAClD,6BAAyB,MAAM,qBAAqB;AAAA,EACtD;AAAA,EACA,QAAQ,CAAC,QAAQ;AACf,QAAI,WAAW,cAAc,cAAc;AAAA,EAC7C;AAAA,EACA,kBAAkB,CAAC,qBAAqB;AAC1C,CAAC;","names":["inject","injectable","Command","Command","inject","injectable"]}
package/dist/index.d.mts CHANGED
@@ -20,9 +20,10 @@ interface ShortcutsContribution {
20
20
  declare class ShortcutsRegistry {
21
21
  protected contribs: ContributionProvider<ShortcutsContribution>;
22
22
  protected commandRegistry: CommandRegistry;
23
- readonly shortcutsHandlers: ShortcutsHandler[];
23
+ shortcutsHandlers: ShortcutsHandler[];
24
24
  addHandlers(...handlers: ShortcutsHandler[]): void;
25
25
  addHandlersIfNotFound(...handlers: ShortcutsHandler[]): void;
26
+ removeHandler(commandId: string): void;
26
27
  has(commandId: string): boolean;
27
28
  protected init(): void;
28
29
  }
package/dist/index.d.ts CHANGED
@@ -20,9 +20,10 @@ interface ShortcutsContribution {
20
20
  declare class ShortcutsRegistry {
21
21
  protected contribs: ContributionProvider<ShortcutsContribution>;
22
22
  protected commandRegistry: CommandRegistry;
23
- readonly shortcutsHandlers: ShortcutsHandler[];
23
+ shortcutsHandlers: ShortcutsHandler[];
24
24
  addHandlers(...handlers: ShortcutsHandler[]): void;
25
25
  addHandlersIfNotFound(...handlers: ShortcutsHandler[]): void;
26
+ removeHandler(commandId: string): void;
26
27
  has(commandId: string): boolean;
27
28
  protected init(): void;
28
29
  }
package/dist/index.js CHANGED
@@ -68,6 +68,11 @@ var ShortcutsRegistry = class {
68
68
  }
69
69
  });
70
70
  }
71
+ removeHandler(commandId) {
72
+ this.shortcutsHandlers = this.shortcutsHandlers.filter(
73
+ (handler) => handler.commandId !== commandId
74
+ );
75
+ }
71
76
  has(commandId) {
72
77
  return this.shortcutsHandlers.some((handler) => handler.commandId === commandId);
73
78
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/create-shortcuts-plugin.ts","../src/shortcuts-contribution.ts","../src/layers/shortcuts-layer.tsx","../src/shortcuts-utils.ts"],"sourcesContent":["/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './create-shortcuts-plugin';\nexport * from './shortcuts-contribution';\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { bindContributionProvider, definePluginCreator } from '@flowgram.ai/core';\n\nimport { ShortcutsRegistry, ShortcutsContribution } from './shortcuts-contribution';\nimport { ShortcutsLayer } from './layers';\n\n/**\n * @param opts\n *\n * createShortcutsPlugin({\n * registerShortcuts(registry) {\n * }\n * })\n */\nexport const createShortcutsPlugin = definePluginCreator<ShortcutsContribution>({\n onBind: ({ bind }) => {\n bind(ShortcutsRegistry).toSelf().inSingletonScope();\n bindContributionProvider(bind, ShortcutsContribution);\n },\n onInit: (ctx) => {\n ctx.playground.registerLayer(ShortcutsLayer);\n },\n contributionKeys: [ShortcutsContribution],\n});\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { inject, injectable, named, optional, postConstruct } from 'inversify';\nimport { Command, CommandRegistry, ContributionProvider } from '@flowgram.ai/core';\n\nexport interface ShortcutsHandler {\n commandId: string;\n commandDetail?: Omit<Command, 'id'>;\n shortcuts: string[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n isEnabled?: (...args: any[]) => boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n execute: (...args: any[]) => void;\n}\n\nexport const ShortcutsContribution = Symbol('ShortcutsContribution');\n\nexport interface ShortcutsContribution {\n registerShortcuts: (registry: ShortcutsRegistry) => void;\n}\n\n@injectable()\nexport class ShortcutsRegistry {\n @inject(ContributionProvider)\n @named(ShortcutsContribution)\n @optional()\n protected contribs: ContributionProvider<ShortcutsContribution>;\n\n @inject(CommandRegistry) protected commandRegistry: CommandRegistry;\n\n readonly shortcutsHandlers: ShortcutsHandler[] = [];\n\n addHandlers(...handlers: ShortcutsHandler[]): void {\n // 注册 command\n handlers.forEach((handler) => {\n if (!this.commandRegistry.getCommand(handler.commandId)) {\n this.commandRegistry.registerCommand(\n { id: handler.commandId, ...(handler.commandDetail || {}) },\n { execute: handler.execute, isEnabled: handler.isEnabled }\n );\n } else {\n this.commandRegistry.registerHandler(handler.commandId, {\n execute: handler.execute,\n isEnabled: handler.isEnabled,\n });\n }\n });\n // Insert before for override pre handlers\n this.shortcutsHandlers.unshift(...handlers);\n }\n\n addHandlersIfNotFound(...handlers: ShortcutsHandler[]): void {\n handlers.forEach((handler) => {\n if (!this.has(handler.commandId)) {\n this.addHandlers(handler);\n }\n });\n }\n\n has(commandId: string): boolean {\n return this.shortcutsHandlers.some((handler) => handler.commandId === commandId);\n }\n\n @postConstruct()\n protected init(): void {\n this.contribs?.forEach((contrib) => contrib.registerShortcuts(this));\n }\n}\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { inject, injectable } from 'inversify';\nimport { Layer, SelectionService, Command } from '@flowgram.ai/core';\n\nimport { isShortcutsMatch } from '../shortcuts-utils';\nimport { ShortcutsRegistry } from '../shortcuts-contribution';\n\n@injectable()\nexport class ShortcutsLayer extends Layer<object> {\n static type = 'ShortcutsLayer';\n\n @inject(ShortcutsRegistry) shortcuts: ShortcutsRegistry;\n\n @inject(SelectionService) selection: SelectionService;\n\n onReady(): void {\n this.shortcuts.addHandlersIfNotFound(\n /**\n * 放大\n */\n {\n commandId: Command.Default.ZOOM_IN,\n shortcuts: ['meta =', 'ctrl ='],\n execute: () => {\n // TODO 这里要判断 CurrentEditor\n this.config.zoomin();\n },\n },\n /**\n * 缩小\n */\n {\n commandId: Command.Default.ZOOM_OUT,\n shortcuts: ['meta -', 'ctrl -'],\n execute: () => {\n // TODO 这里要判断 CurrentEditor\n this.config.zoomout();\n },\n },\n );\n this.toDispose.pushAll([\n // 监听画布鼠标移动事件\n this.listenPlaygroundEvent('keydown', (e: KeyboardEvent) => {\n if (!this.isFocused || e.target !== this.playgroundNode) {\n return;\n }\n this.shortcuts.shortcutsHandlers.some(shortcutsHandler => {\n if (\n isShortcutsMatch(e, shortcutsHandler.shortcuts) &&\n (!shortcutsHandler.isEnabled || shortcutsHandler.isEnabled(e))\n ) {\n shortcutsHandler.execute(e);\n e.preventDefault();\n return true;\n }\n });\n }),\n ]);\n }\n}\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nconst isAppleDevice = /(mac|iphone|ipod|ipad)/i.test(\n typeof navigator !== 'undefined' ? navigator?.platform : '',\n);\n// 键盘事件 keyCode 别名\nconst aliasKeyCodeMap: Record<string, number | number[]> = {\n '0': 48,\n '1': 49,\n '2': 50,\n '3': 51,\n '4': 52,\n '5': 53,\n '6': 54,\n '7': 55,\n '8': 56,\n '9': 57,\n backspace: 8,\n tab: 9,\n enter: 13,\n shift: 16,\n ctrl: 17,\n alt: 18,\n pausebreak: 19,\n capslock: 20,\n esc: 27,\n space: 32,\n pageup: 33,\n pagedown: 34,\n end: 35,\n home: 36,\n leftarrow: 37,\n uparrow: 38,\n rightarrow: 39,\n downarrow: 40,\n insert: 45,\n delete: 46,\n a: 65,\n b: 66,\n c: 67,\n d: 68,\n e: 69,\n f: 70,\n g: 71,\n h: 72,\n i: 73,\n j: 74,\n k: 75,\n l: 76,\n m: 77,\n n: 78,\n o: 79,\n p: 80,\n q: 81,\n r: 82,\n s: 83,\n t: 84,\n u: 85,\n v: 86,\n w: 87,\n x: 88,\n y: 89,\n z: 90,\n leftwindowkey: 91,\n rightwindowkey: 92,\n meta: isAppleDevice ? [91, 93] : [91, 92],\n selectkey: 93,\n numpad0: 96,\n numpad1: 97,\n numpad2: 98,\n numpad3: 99,\n numpad4: 100,\n numpad5: 101,\n numpad6: 102,\n numpad7: 103,\n numpad8: 104,\n numpad9: 105,\n multiply: 106,\n add: 107,\n subtract: 109,\n decimalpoint: 110,\n divide: 111,\n f1: 112,\n f2: 113,\n f3: 114,\n f4: 115,\n f5: 116,\n f6: 117,\n f7: 118,\n f8: 119,\n f9: 120,\n f10: 121,\n f11: 122,\n f12: 123,\n numlock: 144,\n scrolllock: 145,\n semicolon: 186,\n equalsign: 187,\n '=': 187,\n comma: 188,\n dash: 189,\n '-': 189,\n period: 190,\n forwardslash: 191,\n graveaccent: 192,\n openbracket: 219,\n backslash: 220,\n closebracket: 221,\n singlequote: 222,\n};\n\nconst modifierKey: any = {\n ctrl: (event: KeyboardEvent) => event.ctrlKey,\n shift: (event: KeyboardEvent) => event.shiftKey,\n alt: (event: KeyboardEvent) => event.altKey,\n meta: (event: KeyboardEvent) => {\n if (event.type === 'keyup') {\n return (aliasKeyCodeMap.meta as number[]).includes(event.keyCode);\n }\n return event.metaKey;\n },\n};\n\n// 根据 event 计算激活键数量\nfunction countKeyByEvent(event: KeyboardEvent): number {\n const countOfModifier = Object.keys(modifierKey).reduce((total, key) => {\n if (modifierKey[key](event)) {\n return total + 1;\n }\n\n return total;\n }, 0);\n\n // 16 17 18 91 92 是修饰键的 keyCode,如果 keyCode 是修饰键,那么激活数量就是修饰键的数量,如果不是,那么就需要 +1\n return [16, 17, 18, 91, 92].includes(event.keyCode) ? countOfModifier : countOfModifier + 1;\n}\n\n/**\n *\n * @param event\n * @param keyString 'ctrl.s' 'meta.s'\n * @param exactMatch\n */\nfunction isKeyStringMatch(event: KeyboardEvent, keyString: string, exactMatch = true): boolean {\n // 浏览器自动补全 input 的时候,会触发 keyDown、keyUp 事件,但此时 event.key 等为空\n if (!event.key || !keyString) {\n return false;\n }\n // 字符串依次判断是否有组合键\n const genArr = keyString.split(/\\s+/);\n let genLen = 0;\n\n for (const key of genArr) {\n // 组合键\n const genModifier = modifierKey[key];\n // keyCode 别名\n const aliasKeyCode: number | number[] = aliasKeyCodeMap[key.toLowerCase()];\n\n if ((genModifier && genModifier(event)) || (aliasKeyCode && aliasKeyCode === event.keyCode)) {\n genLen++;\n }\n }\n\n /**\n * 需要判断触发的键位和监听的键位完全一致,判断方法就是触发的键位里有且等于监听的键位\n * genLen === genArr.length 能判断出来触发的键位里有监听的键位\n * countKeyByEvent(event) === genArr.length 判断出来触发的键位数量里有且等于监听的键位数量\n * 主要用来防止按组合键其子集也会触发的情况,例如监听 ctrl+a 会触发监听 ctrl 和 a 两个键的事件。\n */\n if (exactMatch) {\n return genLen === genArr.length && countKeyByEvent(event) === genArr.length;\n }\n return genLen === genArr.length;\n}\n\n/**\n * 匹配指定的快捷键\n * @param event\n * @param shortcuts\n */\nexport function isShortcutsMatch(event: KeyboardEvent, shortcuts: string[]): boolean {\n return shortcuts.some(keyString => isKeyStringMatch(event, keyString));\n}\n"],"mappings":"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","names":["import_core","import_inversify","import_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/create-shortcuts-plugin.ts","../src/shortcuts-contribution.ts","../src/layers/shortcuts-layer.tsx","../src/shortcuts-utils.ts"],"sourcesContent":["/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './create-shortcuts-plugin';\nexport * from './shortcuts-contribution';\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { bindContributionProvider, definePluginCreator } from '@flowgram.ai/core';\n\nimport { ShortcutsRegistry, ShortcutsContribution } from './shortcuts-contribution';\nimport { ShortcutsLayer } from './layers';\n\n/**\n * @param opts\n *\n * createShortcutsPlugin({\n * registerShortcuts(registry) {\n * }\n * })\n */\nexport const createShortcutsPlugin = definePluginCreator<ShortcutsContribution>({\n onBind: ({ bind }) => {\n bind(ShortcutsRegistry).toSelf().inSingletonScope();\n bindContributionProvider(bind, ShortcutsContribution);\n },\n onInit: (ctx) => {\n ctx.playground.registerLayer(ShortcutsLayer);\n },\n contributionKeys: [ShortcutsContribution],\n});\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { inject, injectable, named, optional, postConstruct } from 'inversify';\nimport { Command, CommandRegistry, ContributionProvider } from '@flowgram.ai/core';\n\nexport interface ShortcutsHandler {\n commandId: string;\n commandDetail?: Omit<Command, 'id'>;\n shortcuts: string[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n isEnabled?: (...args: any[]) => boolean;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n execute: (...args: any[]) => void;\n}\n\nexport const ShortcutsContribution = Symbol('ShortcutsContribution');\n\nexport interface ShortcutsContribution {\n registerShortcuts: (registry: ShortcutsRegistry) => void;\n}\n\n@injectable()\nexport class ShortcutsRegistry {\n @inject(ContributionProvider)\n @named(ShortcutsContribution)\n @optional()\n protected contribs: ContributionProvider<ShortcutsContribution>;\n\n @inject(CommandRegistry) protected commandRegistry: CommandRegistry;\n\n shortcutsHandlers: ShortcutsHandler[] = [];\n\n addHandlers(...handlers: ShortcutsHandler[]): void {\n // 注册 command\n handlers.forEach((handler) => {\n if (!this.commandRegistry.getCommand(handler.commandId)) {\n this.commandRegistry.registerCommand(\n { id: handler.commandId, ...(handler.commandDetail || {}) },\n { execute: handler.execute, isEnabled: handler.isEnabled }\n );\n } else {\n this.commandRegistry.registerHandler(handler.commandId, {\n execute: handler.execute,\n isEnabled: handler.isEnabled,\n });\n }\n });\n // Insert before for override pre handlers\n this.shortcutsHandlers.unshift(...handlers);\n }\n\n addHandlersIfNotFound(...handlers: ShortcutsHandler[]): void {\n handlers.forEach((handler) => {\n if (!this.has(handler.commandId)) {\n this.addHandlers(handler);\n }\n });\n }\n\n removeHandler(commandId: string): void {\n this.shortcutsHandlers = this.shortcutsHandlers.filter(\n (handler) => handler.commandId !== commandId\n );\n }\n\n has(commandId: string): boolean {\n return this.shortcutsHandlers.some((handler) => handler.commandId === commandId);\n }\n\n @postConstruct()\n protected init(): void {\n this.contribs?.forEach((contrib) => contrib.registerShortcuts(this));\n }\n}\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nimport { inject, injectable } from 'inversify';\nimport { Layer, SelectionService, Command } from '@flowgram.ai/core';\n\nimport { isShortcutsMatch } from '../shortcuts-utils';\nimport { ShortcutsRegistry } from '../shortcuts-contribution';\n\n@injectable()\nexport class ShortcutsLayer extends Layer<object> {\n static type = 'ShortcutsLayer';\n\n @inject(ShortcutsRegistry) shortcuts: ShortcutsRegistry;\n\n @inject(SelectionService) selection: SelectionService;\n\n onReady(): void {\n this.shortcuts.addHandlersIfNotFound(\n /**\n * 放大\n */\n {\n commandId: Command.Default.ZOOM_IN,\n shortcuts: ['meta =', 'ctrl ='],\n execute: () => {\n // TODO 这里要判断 CurrentEditor\n this.config.zoomin();\n },\n },\n /**\n * 缩小\n */\n {\n commandId: Command.Default.ZOOM_OUT,\n shortcuts: ['meta -', 'ctrl -'],\n execute: () => {\n // TODO 这里要判断 CurrentEditor\n this.config.zoomout();\n },\n },\n );\n this.toDispose.pushAll([\n // 监听画布鼠标移动事件\n this.listenPlaygroundEvent('keydown', (e: KeyboardEvent) => {\n if (!this.isFocused || e.target !== this.playgroundNode) {\n return;\n }\n this.shortcuts.shortcutsHandlers.some(shortcutsHandler => {\n if (\n isShortcutsMatch(e, shortcutsHandler.shortcuts) &&\n (!shortcutsHandler.isEnabled || shortcutsHandler.isEnabled(e))\n ) {\n shortcutsHandler.execute(e);\n e.preventDefault();\n return true;\n }\n });\n }),\n ]);\n }\n}\n","/**\n * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n */\n\nconst isAppleDevice = /(mac|iphone|ipod|ipad)/i.test(\n typeof navigator !== 'undefined' ? navigator?.platform : '',\n);\n// 键盘事件 keyCode 别名\nconst aliasKeyCodeMap: Record<string, number | number[]> = {\n '0': 48,\n '1': 49,\n '2': 50,\n '3': 51,\n '4': 52,\n '5': 53,\n '6': 54,\n '7': 55,\n '8': 56,\n '9': 57,\n backspace: 8,\n tab: 9,\n enter: 13,\n shift: 16,\n ctrl: 17,\n alt: 18,\n pausebreak: 19,\n capslock: 20,\n esc: 27,\n space: 32,\n pageup: 33,\n pagedown: 34,\n end: 35,\n home: 36,\n leftarrow: 37,\n uparrow: 38,\n rightarrow: 39,\n downarrow: 40,\n insert: 45,\n delete: 46,\n a: 65,\n b: 66,\n c: 67,\n d: 68,\n e: 69,\n f: 70,\n g: 71,\n h: 72,\n i: 73,\n j: 74,\n k: 75,\n l: 76,\n m: 77,\n n: 78,\n o: 79,\n p: 80,\n q: 81,\n r: 82,\n s: 83,\n t: 84,\n u: 85,\n v: 86,\n w: 87,\n x: 88,\n y: 89,\n z: 90,\n leftwindowkey: 91,\n rightwindowkey: 92,\n meta: isAppleDevice ? [91, 93] : [91, 92],\n selectkey: 93,\n numpad0: 96,\n numpad1: 97,\n numpad2: 98,\n numpad3: 99,\n numpad4: 100,\n numpad5: 101,\n numpad6: 102,\n numpad7: 103,\n numpad8: 104,\n numpad9: 105,\n multiply: 106,\n add: 107,\n subtract: 109,\n decimalpoint: 110,\n divide: 111,\n f1: 112,\n f2: 113,\n f3: 114,\n f4: 115,\n f5: 116,\n f6: 117,\n f7: 118,\n f8: 119,\n f9: 120,\n f10: 121,\n f11: 122,\n f12: 123,\n numlock: 144,\n scrolllock: 145,\n semicolon: 186,\n equalsign: 187,\n '=': 187,\n comma: 188,\n dash: 189,\n '-': 189,\n period: 190,\n forwardslash: 191,\n graveaccent: 192,\n openbracket: 219,\n backslash: 220,\n closebracket: 221,\n singlequote: 222,\n};\n\nconst modifierKey: any = {\n ctrl: (event: KeyboardEvent) => event.ctrlKey,\n shift: (event: KeyboardEvent) => event.shiftKey,\n alt: (event: KeyboardEvent) => event.altKey,\n meta: (event: KeyboardEvent) => {\n if (event.type === 'keyup') {\n return (aliasKeyCodeMap.meta as number[]).includes(event.keyCode);\n }\n return event.metaKey;\n },\n};\n\n// 根据 event 计算激活键数量\nfunction countKeyByEvent(event: KeyboardEvent): number {\n const countOfModifier = Object.keys(modifierKey).reduce((total, key) => {\n if (modifierKey[key](event)) {\n return total + 1;\n }\n\n return total;\n }, 0);\n\n // 16 17 18 91 92 是修饰键的 keyCode,如果 keyCode 是修饰键,那么激活数量就是修饰键的数量,如果不是,那么就需要 +1\n return [16, 17, 18, 91, 92].includes(event.keyCode) ? countOfModifier : countOfModifier + 1;\n}\n\n/**\n *\n * @param event\n * @param keyString 'ctrl.s' 'meta.s'\n * @param exactMatch\n */\nfunction isKeyStringMatch(event: KeyboardEvent, keyString: string, exactMatch = true): boolean {\n // 浏览器自动补全 input 的时候,会触发 keyDown、keyUp 事件,但此时 event.key 等为空\n if (!event.key || !keyString) {\n return false;\n }\n // 字符串依次判断是否有组合键\n const genArr = keyString.split(/\\s+/);\n let genLen = 0;\n\n for (const key of genArr) {\n // 组合键\n const genModifier = modifierKey[key];\n // keyCode 别名\n const aliasKeyCode: number | number[] = aliasKeyCodeMap[key.toLowerCase()];\n\n if ((genModifier && genModifier(event)) || (aliasKeyCode && aliasKeyCode === event.keyCode)) {\n genLen++;\n }\n }\n\n /**\n * 需要判断触发的键位和监听的键位完全一致,判断方法就是触发的键位里有且等于监听的键位\n * genLen === genArr.length 能判断出来触发的键位里有监听的键位\n * countKeyByEvent(event) === genArr.length 判断出来触发的键位数量里有且等于监听的键位数量\n * 主要用来防止按组合键其子集也会触发的情况,例如监听 ctrl+a 会触发监听 ctrl 和 a 两个键的事件。\n */\n if (exactMatch) {\n return genLen === genArr.length && countKeyByEvent(event) === genArr.length;\n }\n return genLen === genArr.length;\n}\n\n/**\n * 匹配指定的快捷键\n * @param event\n * @param shortcuts\n */\nexport function isShortcutsMatch(event: KeyboardEvent, shortcuts: string[]): boolean {\n return shortcuts.some(keyString => isKeyStringMatch(event, keyString));\n}\n"],"mappings":"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","names":["import_core","import_inversify","import_core"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowgram.ai/shortcuts-plugin",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "homepage": "https://flowgram.ai/",
5
5
  "repository": "https://github.com/bytedance/flowgram.ai",
6
6
  "license": "MIT",
@@ -18,7 +18,7 @@
18
18
  "dependencies": {
19
19
  "inversify": "^6.0.1",
20
20
  "reflect-metadata": "~0.2.2",
21
- "@flowgram.ai/core": "1.0.2"
21
+ "@flowgram.ai/core": "1.0.3"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@vitest/coverage-v8": "^3.2.4",
@@ -26,8 +26,8 @@
26
26
  "tsup": "^8.0.1",
27
27
  "typescript": "^5.8.3",
28
28
  "vitest": "^3.2.4",
29
- "@flowgram.ai/ts-config": "1.0.2",
30
- "@flowgram.ai/eslint-config": "1.0.2"
29
+ "@flowgram.ai/ts-config": "1.0.3",
30
+ "@flowgram.ai/eslint-config": "1.0.3"
31
31
  },
32
32
  "publishConfig": {
33
33
  "access": "public",