• Jump To … +
    ./source/asset-management/image-asset.js ./source/asset-management/noise-asset.js ./source/asset-management/raw-asset.js ./source/asset-management/reaction-diffusion-asset.js ./source/asset-management/sprite-asset.js ./source/asset-management/video-asset.js ./source/core/animation-loop.js ./source/core/display-cycle.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/snippets.js ./source/core/user-interaction.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/button.js ./source/factory/canvas.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conic-gradient.js ./source/factory/crescent.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/enhanced-label.js ./source/factory/filter.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/label.js ./source/factory/line-spiral.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/oval.js ./source/factory/particle-force.js ./source/factory/particle-spring.js ./source/factory/particle-world.js ./source/factory/particle.js ./source/factory/pattern.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/radial-gradient.js ./source/factory/rectangle.js ./source/factory/render-animation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstacked-element.js ./source/factory/wheel.js ./source/helper/array-pool.js ./source/helper/color-engine.js ./source/helper/document-root-elements.js ./source/helper/filter-engine-bluenoise-data.js ./source/helper/filter-engine.js ./source/helper/random-seed.js ./source/helper/shape-path-calculation.js ./source/helper/shared-vars.js ./source/helper/system-flags.js ./source/helper/utilities.js ./source/helper/workstore.js ./source/mixin/anchor.js ./source/mixin/asset-advanced-functionality.js ./source/mixin/asset-consumer.js ./source/mixin/asset.js ./source/mixin/base.js ./source/mixin/button.js ./source/mixin/cascade.js ./source/mixin/cell-key-functions.js ./source/mixin/delta.js ./source/mixin/display-shape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/hidden-dom-elements.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shape-basic.js ./source/mixin/shape-curve.js ./source/mixin/styles.js ./source/mixin/text.js ./source/mixin/tween.js ./source/scrawl.js ./source/untracked-factory/cell-fragment.js ./source/untracked-factory/coordinate.js ./source/untracked-factory/drag-zone.js ./source/untracked-factory/keyboard-zone.js ./source/untracked-factory/observe-update.js ./source/untracked-factory/palette.js ./source/untracked-factory/particle-history.js ./source/untracked-factory/quaternion.js ./source/untracked-factory/state.js ./source/untracked-factory/text-style.js ./source/untracked-factory/vector.js
  • §

    Keyboard zones.

    makeKeyboardZone is an attempt to make setting up keyboard listeners (for accessibility) within a Scrawl-canvas stack or canvas as simple as possible. Similar to the drag zone functionality, SC tries to limit the number of keyboard event listeners attached to a particular DOM element to the bare minimum

    Required attribute of the argument object:

    • .zone - either the String name of the Stack or Canvas artefact which will host the zone, or the Stack or Canvas artefact itself

    Additional, optional, attributes in the argument object

    • 'none', 'shiftOnly', 'altOnly', 'ctrlOnly', 'metaOnly', 'shiftAlt', 'shiftCtrl', 'shiftMeta', 'altCtrl', 'altMeta', 'ctrlMeta', 'shiftAltCtrl', 'shiftAltMeta', 'shiftCtrlMeta', 'altCtrlMeta', 'all' - a set of objects containing keyboard Key: function attributes defining the group of actions to take when the user presses the associated key or key-combination.
  • §

    Imports

    import { artefact } from "../core/library.js";
    
    import { mergeDiscard, λnull, Ωempty } from "../helper/utilities.js";
    
    import { addNativeListener, removeNativeListener } from "../core/events.js";
  • §

    Shared constants

    import { $BODY, _keys, ACCEPTED_WRAPPERS, NONE } from '../helper/shared-vars.js';
  • §

    Local constants

    const KEY_DOWN = 'keydown',
        KEY_UP = 'keyup',
        KEYBOARD_GROUPS = ['none', 'shiftOnly', 'altOnly', 'ctrlOnly', 'metaOnly', 'shiftAlt', 'shiftCtrl', 'shiftMeta', 'altCtrl', 'altMeta', 'ctrlMeta', 'shiftAltCtrl', 'shiftAltMeta', 'shiftCtrlMeta', 'altCtrlMeta', 'all'],
        T_ESCAPE = 'Escape',
        T_TAB = 'Tab';
    
    
    const keyboardZones = {};
    
    const processKeyboardZoneData = function (items = Ωempty, doAddListeners, doRemoveListeners) {
    
        let zone = items.zone;
  • §

    zone is required

    • must be either a Canvas or Stack wrapper, or a wrapper’s String name
        if (!zone) return new Error('keyboardZone constructor - no zone supplied');
    
        if (zone.substring) zone = artefact[zone];
    
        if (!zone || !ACCEPTED_WRAPPERS.includes(zone.type)) return new Error('keyboardZone constructor - zone object is not a Stack or Canvas wrapper');
    
        const target = zone.domElement;
    
        if (!target) return new Error('keyboardZone constructor - zone does not contain a target DOM element');
    
        let zoneItem = keyboardZones[zone.name];
    
        if (!zoneItem) {
    
            keyboardZones[zone.name] = {};
            zoneItem = keyboardZones[zone.name];
            doAddListeners(target);
        }
    
        if (!zoneItem.extraKeys) {
    
            zoneItem.extraKeys = {
                Shift: false,
                Control: false,
                Alt: false,
                Meta: false,
            };
        }
    
        if (!zoneItem.keyGroups) {
    
            const groups = {};
            KEYBOARD_GROUPS.forEach(g => groups[g] = {});
            zoneItem.keyGroups = groups;
        }
    
        const KG = zoneItem.keyGroups;
    
        KEYBOARD_GROUPS.forEach(g => {
    
            const keymap = items[g];
            if (keymap != null) mergeDiscard(KG[g], keymap);
        });
    
        if (!zoneItem.onKeyDown) {
    
            zoneItem.onKeyDown = (e = Ωempty) => {
    
                if (e && e.key) {
    
                    e.preventDefault();
    
                    const { extraKeys, keyGroups } = zoneItem;
                    const { key } = e;
  • §

    Tab, Esc

                    if (T_TAB === key || T_ESCAPE === key) {
    
                        target.blur();
                        return;
                    }
                    if (extraKeys[key] != null) {
    
                        extraKeys[key] = true;
                        return;
                    }
    
                    const { Shift, Control, Alt, Meta } = extraKeys;
    
                    let group = keyGroups.none;
    
                    if (Shift || Control || Alt || Meta) {
    
                        if (Shift) {
                            if (Alt) {
                                if (Control) {
                                    if (Meta) group = keyGroups.all;
                                    else group = keyGroups.shiftAltCtrl;
                                }
                                else {
                                    if (Meta) group = keyGroups.shiftAltMeta;
                                    else group = keyGroups.shiftAlt;
                                }
                            }
                            else {
                                if (Control) {
                                    if (Meta) group = keyGroups.shiftCtrlMeta;
                                    else group = keyGroups.shiftCtrl;
                                }
                                else {
                                    if (Meta) group = keyGroups.shiftMeta;
                                    else group = keyGroups.shiftOnly;
                                }
                            }
                        }
                        else {
                            if (Alt) {
                                if (Control) {
                                    if (Meta) group = keyGroups.altCtrlMeta;
                                    else group = keyGroups.altCtrl;
                                }
                                else {
                                    if (Meta) group = keyGroups.altMeta;
                                    else group = keyGroups.altOnly;
                                }
                            }
                            else {
                                if (Control) {
                                    if (Meta) group = keyGroups.ctrlMeta;
                                    else group = keyGroups.ctrlOnly;
                                }
                                else {
                                    if (Meta) group = keyGroups.altOnly;
                                    else group = keyGroups.none;
                                }
                            }
                        }
                    }
                    if (group[key]) group[key]();
                }
            };
        }
    
        if (!zoneItem.onKeyUp) {
    
            zoneItem.onKeyUp = (e = Ωempty) => {
    
                if (e && e.key) {
    
                    e.preventDefault();
    
                    const extraKeys = zoneItem.extraKeys;
                    const key = e.key;
    
                    if (extraKeys[key] != null) extraKeys[key] = false;
                }
            };
        }
  • §

    kill - A function to remove the internal key mappings associated with the target DOM element, and then remove the keyboard listeners attached to that element

        if (!zoneItem.kill) {
    
            zoneItem.kill = function () {
    
                delete keyboardZones[zone.name];
                doRemoveListeners(target);
            };
        }
  • §

    getMappedKeys - A function which returns an Array of the defined keys for the given group, the name of which should be supplied as the function’s argument

    • To update key mappings, invoke the makeKeyboardZone function again, including the new mappings as part of the argument object
        const getMappedKeys = (keyGroup = NONE) => {
    
            if (zoneItem.keyGroups[keyGroup] != null) {
    
                return _keys(zoneItem.keyGroups[keyGroup]);
            }
            return [];
        }
  • §

    The return includes both the kill and getMappedKeys functions

        return {
            kill: zoneItem.kill,
            getMappedKeys,
        }
    }
  • §

    Exported function (to modules and the SC object). Add keyboard listenning functionality to a canvas or stack wrapper.

    export const makeKeyboardZone = function (items = Ωempty) {
  • §

    The exposed actionKeyDown function will search for the target element (if user has started the drag while the mouse cursor was over a child of the Stack wrapper) and then apply the required keystroke actions while that element remains focussed

        const actionKeyDown = (e = Ωempty) => {
    
            if (e && e.target) {
    
                let myTarget = e.target,
                    name = '';
    
                while (!name) {
    
                    if (keyboardZones[myTarget.id]) name = myTarget.id;
                    if (myTarget.tagName === $BODY) break;
                    myTarget = myTarget.parentElement;
                }
    
                const actions = keyboardZones[name];
    
                if (actions) {
    
                    actions.onKeyDown(e);
                    currentKeyUp = actions.onKeyUp;
                }
                else currentKeyUp = λnull;
            }
        };
  • §

    The exposed actionKeyUp function will change to match the equivalent funtions supplied by the zone selected during the actionKeyDown stage of the operation

        let currentKeyUp = λnull;
        const actionKeyUp = (e) => {
    
            currentKeyUp(e);
        };
  • §

    Listeners are added to the DOM element when the first keyboard zone is created for that target

        const doAddListeners = (target) => {
    
            addNativeListener(KEY_DOWN, actionKeyDown, target);
            addNativeListener(KEY_UP, actionKeyUp, target);
        };
  • §

    Listeners are only removed from the DOM element when all the keyboard zones associated with that target have been killed

        const doRemoveListeners = (target) => {
    
            removeNativeListener(KEY_DOWN, actionKeyDown, target);
            removeNativeListener(KEY_UP, actionKeyUp, target);
        };
  • §

    Returns an object containing the kill and getMappedKeys functions for this keyboard mapping

        return processKeyboardZoneData(items, doAddListeners, doRemoveListeners);
    };