---
# System prepended metadata

title: UILayer UIEvent

---

# UILayer UIEvent

### Event Object and Layers System
- systems works with tree structure of layer data
- at each time exists no more than one active layer
- systems can creates event for active layer and fires event with 2 phases
    - calls handlers on capture phase for each layer from root to active
    - calls handlers on bubling phase for each layer from active to root
- each hadler can stop propagation (as capturing and bubbling)
- layer can be enabled or disabled and that also means
    -  if parent is disabled all his chidlren are disabled
    -  layer is enabled only if itself is enabled and all his parents itself are enabled
```plantuml
@startmindmap
<style>
mindmapDiagram {
.layer { 
    BackgroundColor lightgreen
}
.chain { 
    BackgroundColor #FFBBCC 
}
.active {
    BackgroundColor lightblue 
}
}
</style>
+ Layer <<chain>>
++ Pages
+++ Page1
++++ Layer <<layer>>
+++++ ...
+++ Page2
++++ Layer <<layer>>
+++++ SidePanel
++++++ Layer <<layer>>
+++++++ ...
+++++ Header
++++++ Layer <<layer>>
+++++++ ...
+++++ Content
++++++ Layer <<layer>>
+++++++ ...
++ Popups
+++ Layer <<chain>>
++++ Popup1
+++++ Layer <<layer>>
++++++ ...
++++ Popup2
+++++ "Layer Active" <<active>>
++++++ ...
++ Tooltips
+++ ...
@endmindmap
```




#### Public interface
```typescript=
interface LayerEvent<T, TData> = {
    readonly eventKey: T;
    readonly payload: TData;
    readonly stopped: boolean;
    stopPropagation(): void; 
};

type LayerEventHandler<T> = (ev: LayerEvent<T>) => void|false;

class Layer { 
    // there is a node of tree structure data
    private parent: Layer | null = null;
    private readonly children: Layer[] = [];

    // defines if layer is enable
    private _selfEnable = true;
    setSelfEnableState(isEnable: boolean): void;
    
    // returns true if layer and all parent is '_selfEnable === true'
    get isEnable(): boolean;
    
    /* there is stack related data
     * stack===true - means all direct children are in stack 
     * and depends on theirs 'order' and '_selfEnable' 
     * we know who is a topest enable
     */
    private order?: number;
    private stack?: boolean;

    // subscribe/unsubsribe for event 
    subscribe<T>(eventKey: T, handler: LayerEventHandler<T>, capture?: boolean): void;
    unsubscribe<T>(eventKey: T, handler: LayerEventHandler<T>, capture?: boolean): void;
    
    emit<T>(eventKey: T, payload: TData): void;
    
    activate(): void;
};

// hook to get current layer
function useEventLayerContext(): Layer;

// hook to create new layer (without putting to hierarchy of react components)
// @obsolete
function useDefineEventLayer(name?: string): Layer;

// component that provides defined layer to hierarchy of react components
// @obsolete
const ProvideEventLayer: FC<{ children: ReactNode, eventLayer: Layer }>;

// component that creates and provides layer to hierarchy of react components
const EventLayer: FC<{ children: ReactNode, name?: string }>
   
// shortcut around useEffect and 'subscribe' and 'unsubscribe' functions
const useLayerEventHandler = <T>(eventKey: T, handler: LayerEventHandler<T>, dep: any[], capture?: boolean) => void;
```
#### Examples how to use
```typescript=
const App: FC = () => {
    return <EventLayer name="GlobalStack" stack>
        <EventLayer name="Pages" order={0} initialEnabled>
            <Pages>
        </EventLayer>
        <EventLayer name="Popups" order={1}>
            <Popups />
        </EventLayer>
        <EventLayer name="Tooltips" order={2}>
            <Tooltips />
        </EventLayer>
    </EventLayer>
};
const Popups: FC = () => {
    const popupService = useBetween(usePopupService);
    // enable/disable current layer depends on popups count
    const layer = useEventLayerContext();
    layer.setSelfEnableState(popupService.stack.length > 0);   
};
const LeftPanel: FC = () => {
    // define and provide manually
    const layer = useDefineEventLayer("LeftPanel");
    
    // add/remove EventListener manually
    useEffect(() => {
        return layer.subscribe('select', () => onSelect);
    }, [onSelect]);
    
    return <ProvideEventLayer eventLayer={layer}>
        ...
    </ProvideEventLayer>;
}
const RightPanel: FC = () => {
    // use layer provided before
    const layer = useEventLayerContext();

    useEffect(() => {
        const onKeyDown = (event: KeyboardEvent) => {
            if (layer.isEnable) {
                // ....
                event.stopPropagation();
            }
        };
        window.addEventListener('keydown', onKeyDown);
        return () => {
            window.removeEventListener('keydown', onKeyDown);
        };
    }, []);
    return <div>
        ...
    </div>
}
const SomeApp: FC = () => {
    // declarative way to define layers
    return <EventLayer name="Root">
        <LeftPanel/>
        <EventLayer name="RightPanel">
            {(layer) => {
                return <RightPanel/>
            }}
        </EventLayer>
    </EventLayer>
}
const MenuItem: FC = () => {
    // create interactive element
    const layer = useEventLayerContext();
    useEffect(() => {
        return layer.subscribe('select', () => onSelect);
    }, [onSelect]);
    return <div ref={ref as any} onClick={()=>{
        layer.activate();
        layer.emit('select');
    }}>Button</div>;
};
```


### Event

```typescript=
// auto (un/re)subsribe
useLayerEventHandler(
    'EVENT_POPUP_CANCEL',
    (e) => {
        close();
        e.stopPropagation();
    },
    [close]
);


// manually
const layer = useEventLayerContext();
useEffect(() => {
    const handler: EventHandler<'EVENT_POPUP_CANCEL'> = (e) => {
        close();
        e.stopPropagation();
    };
    layer.subscribe('EVENT_POPUP_CANCEL', handler);
    return () => {
        layer.unsubscribe('EVENT_POPUP_CANCEL', handler);
    };
}, [close]);
/*
- how to subsribe to root layer (aka window.addEventListener) ?
- how to separate cases to handle (or not) events in non active layer? 
*/
```
//:::spoiler obsolete
#### Events abstractions
```plantuml
@startuml
skinparam componentStyle rectangle
component NativeEvents {
    component "mouse(click.down.up)"
    component "keybord(click.down.up)"
    component "gamepad()"
}
component BusinessEvents {
    component back
    component acept
}
component ComponentInterationApi {
    component click
}

NativeEvents <-- BusinessEvents
BusinessEvents <-- ComponentInterationApi
@enduml
```



```typescript=
function useHandlingService(events: {
    [customAction:string]: EvendHandler
}, options?: {
    disabled?: boolean;
}): void {...}
const UncontrolledNavigation = ()=> {
    // finds layers in children compoenents and finds whois next and prev visually using Dom API
    const navigator = useNavigationVisualByClassName({loop: true, trigger:'focus'})
    
    useHandlingService({
        'next': (ev)=> {
            navigator.next();
        },
        'prev': (ev)=> {
            navigator.prev();
        },
    }, {
        disabled: false,
    });
    
    useNativeEvent({
        'keyboard:left': 'prev',
        'keyboard:right': 'next',
        'gamepad:button1': 'next',
        'gamepad:button2': 'next',
    })
    
    return <EventLayer customEvents={{
        'next': (ev)=> {
            navigator.next();
        },
        'prev': (ev)=> {
            navigator.prev();
        },
    }} nativeEventMap={{
        'keyboard:left': 'prev',
        'keyboard:right': 'next',
        'gamepad:button1': 'next',
        'gamepad:button2': 'next',
    }} disabled=false>
        <MenuItem>Loadout</MenuItem>
        <MenuItem>Datacubes</MenuItem>
        <MenuItem>Jobs</MenuItem>
        <MenuItem>Hideout</MenuItem>
        <MenuItem>Stats</MenuItem>
    </div>
}
        
const MenuItem = ()=> {
    useHandlingService({
        'submit': (ev)=> {
            router.navigateTo(...);
        },
    });
    
    useNativeEvent({
        'keyboard:enter': 'submit',
        'gamepad:button3': 'submit',
        'mouse:click': 'submit',
        'mouse:hover': 'focus',
    })
                         
    const {isFocused, isSubmitting} = useHandlingState(['isFocused', 'isSubmitting']);
                     
    const classes = classNames({
        'selected': isFocused,
        'active': isSubmitting,
    });
    return <navigable-item>
        <div className={classes}>{children}</div>
    </navigable-item>
}
```

```typescript=
const ControlledNavigation = ()=> {
    // finds layers in children compoenents and finds whois next and prev visually using Dom API
    const [selectedIndex, setSelectedIndex] = useState(0);
    
    useHandlingService({
        'next': (ev)=> {
            setSelectedIndex((selectedIndex+1)%selectedIndex)
        },
        'prev': (ev)=> {
            setSelectedIndex((2*selectedIndex-1)%selectedIndex)
        },
    }, {
        disabled: false,
    });
    
    useNativeEvent({
        'keyboard:left': 'prev',
        'keyboard:right': 'next',
        'gamepad:button1': 'next',
        'gamepad:button2': 'next',
    })
    
    const tabs = ['Loadout','Datacubes','Jobs','Hideout','Stats']
    
    return <div>
        {tabs.map((tab, index)=>{
            return <MenuItem 
                key={index} 
                selected={selectedIndex=index}>{tab}
            </MenuItem>
        })}
    </div>
}
        
const MenuItem = ({selected})=> {
    useHandlingService({
        'submit': (ev)=> {
            router.navigateTo(...);
        },
    });
    
    useNativeEvent({
        'keyboard:enter': 'submit',
        'gamepad:button3': 'submit',
        'mouse:click': 'submit',
        'mouse:hover': 'focus',
    })
                         
    const {isFocused, isSubmitting} = useHandlingState(['isFocused', 'isSubmitting']);
                     
    const classes = classNames({
        'selected': isFocused || selected,
        'active': isSubmitting,
    });
    return <div className={classes}>{children}</div>
}
```
```plantuml
@startuml

group useEventLayerContext
ComponentA -> LayerContext: getContext
LayerContext --> ComponentA: return Layer1
end

ComponentA -> Layer1: emit action 'A'

group useEventLayerContext
ComponentB -> LayerContext: getContext
LayerContext --> ComponentB: return Layer2
end

ComponentB -> Layer2: subsribe

@enduml
```
:::


| Index | Button .pressed Code | Button on Xbox | Button on PlayStation | 
|-------|-----------------------------|---------------------|-----------------------| 
| 0 | gamepad.buttons[0].pressed | A | X |
| 1 | gamepad.buttons[1].pressed | B | O | 
| 2 | gamepad.buttons[2].pressed | X | Square |
| 3 | gamepad.buttons[3].pressed | Y | Triangle | 
| 4 | gamepad.buttons[4].pressed | LB | L1 | 
| 5 | gamepad.buttons[5].pressed | RB | R1 |
| 6 | gamepad.buttons[6].pressed | LT | L2 | 
| 7 | gamepad.buttons[7].pressed | RT | R2 |
| 8 | gamepad.buttons[8].pressed | Show Address Bar | Share |
| 9 | gamepad.buttons[9].pressed | Show Menu | Options | 
| 10 | gamepad.buttons[10].pressed | Left Stick Pressed | Left Stick Pressed |
| 11 | gamepad.buttons[11].pressed | Right Stick Pressed | Right Stick Pressed |
| 12 | gamepad.buttons[12].pressed | Directional Up | Directional Up | 
| 13 | gamepad.buttons[13].pressed | Directional Down | Directional Down |
| 14 | gamepad.buttons[14].pressed | Directional Left | Directional Left | 
| 15 | gamepad.buttons[15].pressed | Directional Right | Directional Right | | 16 | gamepad.buttons[16].pressed | Xbox Light-Up Logo | PlayStation Logo |