@ou-imdt/utils
Version:
Utility library for interactive media development
126 lines (116 loc) • 4.42 kB
JavaScript
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;
}, []);
}
}