UNPKG

@ou-imdt/utils

Version:

Utility library for interactive media development

126 lines (116 loc) 4.42 kB
import toCamelCase from '../toCamelCase.js'; import { default as Base, defaultState } from '../class/Base.js'; /** * with payload only (runs through all available targets) * handleEvent(e) { delegateChange(e); } * * with target path including any child targets (used to restrict targets) * if (e instanceof KeyboardEvent) delegateChange(e, 'event:key', this); * * with handler name (used to alias handlers, or to extend them) * spaceKeydown(e) { delegateChange(e, 'enterKeydown'); * * with target list (used to handle custom types/override defaults) * delegateChange(payload, [target, target, target]); */ /** * delegates changes (events, actions, updates etc.) to predefined handlers */ export default class DelegateChangeModule extends Base { static get [defaultState]() { return { targets: [{ type: 'event', test(payload) { return payload instanceof Event; }, template: '{type}', // e.g. mousedown, optionSelected (option-selected) targets: [{ type: 'key', test(payload) { const { key, shiftKey, ctrlKey, altKey } = payload; return payload instanceof KeyboardEvent && !({ Shift: shiftKey, Control: ctrlKey, Alt: altKey })[key]; }, computed: { shift: ({ shiftKey }) => shiftKey, ctrl: ({ ctrlKey }) => ctrlKey, alt: ({ altKey }) => altKey, key: ({ key }) => ({ ' ': 'Space' })[key] ?? key }, template: '{shift}{ctrl}{alt}{key}{type}', // e.g. shiftCtrlSpaceKeyup targets: [{ type: 'printable', test({ key, shiftKey, ctrlKey, altKey }) { return /^.$/.test(key) && // single char ![shiftKey, ctrlKey, altKey].includes(true); }, template: 'printable{type}' // e.g. printableKeydown }] }], }, { type: 'action', test({ target }) { return target instanceof HTMLElement && target.hasAttribute('data-action') && target.getAttribute('aria-disabled') !== 'true' && target.disabled !== true; }, computed: { type({ target }) { return target.getAttribute('data-action'); } }, template: ['action', '{type}'] // e.g. 'action' and 'save' }], handlers: null }; }; /** * * @param {object|array} payload - a data object or array of objects * @param {string} target - handler name, target path (e.g. event:key) or target list */ delegateChange(...args) { const argThis = (typeof args[args.length - 1] === 'object') ? args.pop() : this; const [payload, target] = args; const result = new Map(); // console.log('delegate change:', { payload, handler }); if (typeof this.handlers[target] === 'function') { return this.handlers[target]?.call(argThis, payload); } for (const { computed, template } of this.getTargets(payload)) { (Array.isArray(template) ? template : [template]).forEach((el) => { const parts = el.split(/({.*?})/).map(key => { if (key.length === 0) return ''; if (!key.startsWith('{')) return key; key = key.replaceAll(/^{|}$/g, ''); const value = computed?.[key]?.(payload) ?? payload[key]; return (typeof value === 'string') ? value : (value === true) ? key : ''; }); const handler = toCamelCase(parts.join(' ').trim()); result.set(handler, this.handlers[handler]?.call(argThis, payload)); // console.log(`delegate change [${handler}]`, result.get(handler)); }); } return result; } getTargets(payload, target) { const list = []; if (typeof target === 'string') { list.push(target.split(':').reduce((prev, type, index, array) => { const current = prev.find(target => target.type === type); return (index === array.length - 1) ? current : current.targets; }, this.targets)); } if (Array.isArray(target)) { list.push(...target); } return (list.length ? list : this.targets).reduce((result, { test, targets, ...rest }) => { const passed = test?.(payload) ?? true; if (passed) result.push(rest); if (targets) result.push(...this.getTargets(payload, targets).flat()); return result; }, []); } }