This commit is contained in:
Akko
2025-08-04 18:57:35 +02:00
parent 8cf6e78a79
commit 9495868c2e
5030 changed files with 518594 additions and 17609 deletions

View File

@@ -0,0 +1,18 @@
declare global
{
namespace PixiMixins
{
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface Container extends Partial<import('./accessibilityTarget').AccessibleTarget> {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ContainerOptions extends Partial<import('./accessibilityTarget').AccessibleOptions> {}
interface RendererSystems
{
accessibility: import('./AccessibilitySystem').AccessibilitySystem;
}
}
}
export {};

View File

@@ -0,0 +1,164 @@
import { ExtensionType } from '../extensions/Extensions';
import type { System } from '../rendering/renderers/shared/system/System';
import type { Renderer } from '../rendering/renderers/types';
import type { isMobileResult } from '../utils/browser/isMobile';
/** @ignore */
export interface AccessibilityOptions {
/** Setting this to true will visually show the divs. */
debug?: boolean;
}
/**
* The Accessibility system recreates the ability to tab and have content read by screen readers.
* This is very important as it can possibly help people with disabilities access PixiJS content.
*
* A Container can be made accessible just like it can be made interactive. This manager will map the
* events as if the mouse was being used, minimizing the effort required to implement.
*
* An instance of this class is automatically created by default, and can be found at `renderer.accessibility`
* @memberof accessibility
*/
export declare class AccessibilitySystem implements System<AccessibilityOptions> {
private readonly _mobileInfo;
/** @ignore */
static extension: {
readonly type: readonly [ExtensionType.WebGLSystem, ExtensionType.WebGPUSystem];
readonly name: "accessibility";
};
/** Setting this to true will visually show the divs. */
debug: boolean;
/**
* The renderer this accessibility manager works for.
* @type {WebGLRenderer|WebGPURenderer}
*/
private _renderer;
/** Internal variable, see isActive getter. */
private _isActive;
/** Internal variable, see isMobileAccessibility getter. */
private _isMobileAccessibility;
/** Button element for handling touch hooks. */
private _hookDiv;
/** This is the dom element that will sit over the PixiJS element. This is where the div overlays will go. */
private _div;
/** A simple pool for storing divs. */
private _pool;
/** This is a tick used to check if an object is no longer being rendered. */
private _renderId;
/** The array of currently active accessible items. */
private _children;
/** Count to throttle div updates on android devices. */
private _androidUpdateCount;
/** The frequency to update the div elements. */
private readonly _androidUpdateFrequency;
/**
* @param {WebGLRenderer|WebGPURenderer} renderer - A reference to the current renderer
*/
constructor(renderer: Renderer, _mobileInfo?: isMobileResult);
/**
* Value of `true` if accessibility is currently active and accessibility layers are showing.
* @member {boolean}
* @readonly
*/
get isActive(): boolean;
/**
* Value of `true` if accessibility is enabled for touch devices.
* @member {boolean}
* @readonly
*/
get isMobileAccessibility(): boolean;
get hookDiv(): HTMLElement;
/**
* Creates the touch hooks.
* @private
*/
private _createTouchHook;
/**
* Destroys the touch hooks.
* @private
*/
private _destroyTouchHook;
/**
* Activating will cause the Accessibility layer to be shown.
* This is called when a user presses the tab key.
* @private
*/
private _activate;
/**
* Deactivating will cause the Accessibility layer to be hidden.
* This is called when a user moves the mouse.
* @private
*/
private _deactivate;
/**
* This recursive function will run through the scene graph and add any new accessible objects to the DOM layer.
* @private
* @param {Container} container - The Container to check.
*/
private _updateAccessibleObjects;
/**
* Runner init called, view is available at this point.
* @ignore
*/
init(options?: AccessibilityOptions): void;
/**
* Runner postrender was called, ensure that all divs are mapped correctly to their Containers.
* Only fires while active.
* @ignore
*/
postrender(): void;
/**
* private function that will visually add the information to the
* accessibility div
* @param {HTMLElement} div -
*/
private _updateDebugHTML;
/**
* Adjust the hit area based on the bounds of a display object
* @param {Rectangle} hitArea - Bounds of the child
*/
private _capHitArea;
/**
* Adds a Container to the accessibility manager
* @private
* @param {Container} container - The child to make accessible.
*/
private _addChild;
/**
* Dispatch events with the EventSystem.
* @param e
* @param type
* @private
*/
private _dispatchEvent;
/**
* Maps the div button press to pixi's EventSystem (click)
* @private
* @param {MouseEvent} e - The click event.
*/
private _onClick;
/**
* Maps the div focus events to pixi's EventSystem (mouseover)
* @private
* @param {FocusEvent} e - The focus event.
*/
private _onFocus;
/**
* Maps the div focus events to pixi's EventSystem (mouseout)
* @private
* @param {FocusEvent} e - The focusout event.
*/
private _onFocusOut;
/**
* Is called when a key is pressed
* @private
* @param {KeyboardEvent} e - The keydown event.
*/
private _onKeyDown;
/**
* Is called when the mouse moves across the renderer element
* @private
* @param {MouseEvent} e - The mouse event.
*/
private _onMouseMove;
/** Destroys the accessibility manager */
destroy(): void;
}

View File

@@ -0,0 +1,404 @@
'use strict';
var FederatedEvent = require('../events/FederatedEvent.js');
var Extensions = require('../extensions/Extensions.js');
var isMobile = require('../utils/browser/isMobile.js');
var removeItems = require('../utils/data/removeItems.js');
"use strict";
const KEY_CODE_TAB = 9;
const DIV_TOUCH_SIZE = 100;
const DIV_TOUCH_POS_X = 0;
const DIV_TOUCH_POS_Y = 0;
const DIV_TOUCH_ZINDEX = 2;
const DIV_HOOK_SIZE = 1;
const DIV_HOOK_POS_X = -1e3;
const DIV_HOOK_POS_Y = -1e3;
const DIV_HOOK_ZINDEX = 2;
class AccessibilitySystem {
// 2fps
// eslint-disable-next-line jsdoc/require-param
/**
* @param {WebGLRenderer|WebGPURenderer} renderer - A reference to the current renderer
*/
constructor(renderer, _mobileInfo = isMobile.isMobile) {
this._mobileInfo = _mobileInfo;
/** Setting this to true will visually show the divs. */
this.debug = false;
/** Internal variable, see isActive getter. */
this._isActive = false;
/** Internal variable, see isMobileAccessibility getter. */
this._isMobileAccessibility = false;
/** A simple pool for storing divs. */
this._pool = [];
/** This is a tick used to check if an object is no longer being rendered. */
this._renderId = 0;
/** The array of currently active accessible items. */
this._children = [];
/** Count to throttle div updates on android devices. */
this._androidUpdateCount = 0;
/** The frequency to update the div elements. */
this._androidUpdateFrequency = 500;
this._hookDiv = null;
if (_mobileInfo.tablet || _mobileInfo.phone) {
this._createTouchHook();
}
const div = document.createElement("div");
div.style.width = `${DIV_TOUCH_SIZE}px`;
div.style.height = `${DIV_TOUCH_SIZE}px`;
div.style.position = "absolute";
div.style.top = `${DIV_TOUCH_POS_X}px`;
div.style.left = `${DIV_TOUCH_POS_Y}px`;
div.style.zIndex = DIV_TOUCH_ZINDEX.toString();
this._div = div;
this._renderer = renderer;
this._onKeyDown = this._onKeyDown.bind(this);
this._onMouseMove = this._onMouseMove.bind(this);
globalThis.addEventListener("keydown", this._onKeyDown, false);
}
/**
* Value of `true` if accessibility is currently active and accessibility layers are showing.
* @member {boolean}
* @readonly
*/
get isActive() {
return this._isActive;
}
/**
* Value of `true` if accessibility is enabled for touch devices.
* @member {boolean}
* @readonly
*/
get isMobileAccessibility() {
return this._isMobileAccessibility;
}
get hookDiv() {
return this._hookDiv;
}
/**
* Creates the touch hooks.
* @private
*/
_createTouchHook() {
const hookDiv = document.createElement("button");
hookDiv.style.width = `${DIV_HOOK_SIZE}px`;
hookDiv.style.height = `${DIV_HOOK_SIZE}px`;
hookDiv.style.position = "absolute";
hookDiv.style.top = `${DIV_HOOK_POS_X}px`;
hookDiv.style.left = `${DIV_HOOK_POS_Y}px`;
hookDiv.style.zIndex = DIV_HOOK_ZINDEX.toString();
hookDiv.style.backgroundColor = "#FF0000";
hookDiv.title = "select to enable accessibility for this content";
hookDiv.addEventListener("focus", () => {
this._isMobileAccessibility = true;
this._activate();
this._destroyTouchHook();
});
document.body.appendChild(hookDiv);
this._hookDiv = hookDiv;
}
/**
* Destroys the touch hooks.
* @private
*/
_destroyTouchHook() {
if (!this._hookDiv) {
return;
}
document.body.removeChild(this._hookDiv);
this._hookDiv = null;
}
/**
* Activating will cause the Accessibility layer to be shown.
* This is called when a user presses the tab key.
* @private
*/
_activate() {
if (this._isActive) {
return;
}
this._isActive = true;
globalThis.document.addEventListener("mousemove", this._onMouseMove, true);
globalThis.removeEventListener("keydown", this._onKeyDown, false);
this._renderer.runners.postrender.add(this);
this._renderer.view.canvas.parentNode?.appendChild(this._div);
}
/**
* Deactivating will cause the Accessibility layer to be hidden.
* This is called when a user moves the mouse.
* @private
*/
_deactivate() {
if (!this._isActive || this._isMobileAccessibility) {
return;
}
this._isActive = false;
globalThis.document.removeEventListener("mousemove", this._onMouseMove, true);
globalThis.addEventListener("keydown", this._onKeyDown, false);
this._renderer.runners.postrender.remove(this);
this._div.parentNode?.removeChild(this._div);
}
/**
* This recursive function will run through the scene graph and add any new accessible objects to the DOM layer.
* @private
* @param {Container} container - The Container to check.
*/
_updateAccessibleObjects(container) {
if (!container.visible || !container.accessibleChildren) {
return;
}
if (container.accessible && container.isInteractive()) {
if (!container._accessibleActive) {
this._addChild(container);
}
container._renderId = this._renderId;
}
const children = container.children;
if (children) {
for (let i = 0; i < children.length; i++) {
this._updateAccessibleObjects(children[i]);
}
}
}
/**
* Runner init called, view is available at this point.
* @ignore
*/
init(options) {
this.debug = options?.debug ?? this.debug;
this._renderer.runners.postrender.remove(this);
}
/**
* Runner postrender was called, ensure that all divs are mapped correctly to their Containers.
* Only fires while active.
* @ignore
*/
postrender() {
const now = performance.now();
if (this._mobileInfo.android.device && now < this._androidUpdateCount) {
return;
}
this._androidUpdateCount = now + this._androidUpdateFrequency;
if (!this._renderer.renderingToScreen || !this._renderer.view.canvas) {
return;
}
if (this._renderer.lastObjectRendered) {
this._updateAccessibleObjects(this._renderer.lastObjectRendered);
}
const { x, y, width, height } = this._renderer.view.canvas.getBoundingClientRect();
const { width: viewWidth, height: viewHeight, resolution } = this._renderer;
const sx = width / viewWidth * resolution;
const sy = height / viewHeight * resolution;
let div = this._div;
div.style.left = `${x}px`;
div.style.top = `${y}px`;
div.style.width = `${viewWidth}px`;
div.style.height = `${viewHeight}px`;
for (let i = 0; i < this._children.length; i++) {
const child = this._children[i];
if (child._renderId !== this._renderId) {
child._accessibleActive = false;
removeItems.removeItems(this._children, i, 1);
this._div.removeChild(child._accessibleDiv);
this._pool.push(child._accessibleDiv);
child._accessibleDiv = null;
i--;
} else {
div = child._accessibleDiv;
let hitArea = child.hitArea;
const wt = child.worldTransform;
if (child.hitArea) {
div.style.left = `${(wt.tx + hitArea.x * wt.a) * sx}px`;
div.style.top = `${(wt.ty + hitArea.y * wt.d) * sy}px`;
div.style.width = `${hitArea.width * wt.a * sx}px`;
div.style.height = `${hitArea.height * wt.d * sy}px`;
} else {
hitArea = child.getBounds().rectangle;
this._capHitArea(hitArea);
div.style.left = `${hitArea.x * sx}px`;
div.style.top = `${hitArea.y * sy}px`;
div.style.width = `${hitArea.width * sx}px`;
div.style.height = `${hitArea.height * sy}px`;
if (div.title !== child.accessibleTitle && child.accessibleTitle !== null) {
div.title = child.accessibleTitle || "";
}
if (div.getAttribute("aria-label") !== child.accessibleHint && child.accessibleHint !== null) {
div.setAttribute("aria-label", child.accessibleHint || "");
}
}
if (child.accessibleTitle !== div.title || child.tabIndex !== div.tabIndex) {
div.title = child.accessibleTitle || "";
div.tabIndex = child.tabIndex;
if (this.debug) {
this._updateDebugHTML(div);
}
}
}
}
this._renderId++;
}
/**
* private function that will visually add the information to the
* accessibility div
* @param {HTMLElement} div -
*/
_updateDebugHTML(div) {
div.innerHTML = `type: ${div.type}</br> title : ${div.title}</br> tabIndex: ${div.tabIndex}`;
}
/**
* Adjust the hit area based on the bounds of a display object
* @param {Rectangle} hitArea - Bounds of the child
*/
_capHitArea(hitArea) {
if (hitArea.x < 0) {
hitArea.width += hitArea.x;
hitArea.x = 0;
}
if (hitArea.y < 0) {
hitArea.height += hitArea.y;
hitArea.y = 0;
}
const { width: viewWidth, height: viewHeight } = this._renderer;
if (hitArea.x + hitArea.width > viewWidth) {
hitArea.width = viewWidth - hitArea.x;
}
if (hitArea.y + hitArea.height > viewHeight) {
hitArea.height = viewHeight - hitArea.y;
}
}
/**
* Adds a Container to the accessibility manager
* @private
* @param {Container} container - The child to make accessible.
*/
_addChild(container) {
let div = this._pool.pop();
if (!div) {
div = document.createElement("button");
div.style.width = `${DIV_TOUCH_SIZE}px`;
div.style.height = `${DIV_TOUCH_SIZE}px`;
div.style.backgroundColor = this.debug ? "rgba(255,255,255,0.5)" : "transparent";
div.style.position = "absolute";
div.style.zIndex = DIV_TOUCH_ZINDEX.toString();
div.style.borderStyle = "none";
if (navigator.userAgent.toLowerCase().includes("chrome")) {
div.setAttribute("aria-live", "off");
} else {
div.setAttribute("aria-live", "polite");
}
if (navigator.userAgent.match(/rv:.*Gecko\//)) {
div.setAttribute("aria-relevant", "additions");
} else {
div.setAttribute("aria-relevant", "text");
}
div.addEventListener("click", this._onClick.bind(this));
div.addEventListener("focus", this._onFocus.bind(this));
div.addEventListener("focusout", this._onFocusOut.bind(this));
}
div.style.pointerEvents = container.accessiblePointerEvents;
div.type = container.accessibleType;
if (container.accessibleTitle && container.accessibleTitle !== null) {
div.title = container.accessibleTitle;
} else if (!container.accessibleHint || container.accessibleHint === null) {
div.title = `container ${container.tabIndex}`;
}
if (container.accessibleHint && container.accessibleHint !== null) {
div.setAttribute("aria-label", container.accessibleHint);
}
if (this.debug) {
this._updateDebugHTML(div);
}
container._accessibleActive = true;
container._accessibleDiv = div;
div.container = container;
this._children.push(container);
this._div.appendChild(container._accessibleDiv);
container._accessibleDiv.tabIndex = container.tabIndex;
}
/**
* Dispatch events with the EventSystem.
* @param e
* @param type
* @private
*/
_dispatchEvent(e, type) {
const { container: target } = e.target;
const boundary = this._renderer.events.rootBoundary;
const event = Object.assign(new FederatedEvent.FederatedEvent(boundary), { target });
boundary.rootTarget = this._renderer.lastObjectRendered;
type.forEach((type2) => boundary.dispatchEvent(event, type2));
}
/**
* Maps the div button press to pixi's EventSystem (click)
* @private
* @param {MouseEvent} e - The click event.
*/
_onClick(e) {
this._dispatchEvent(e, ["click", "pointertap", "tap"]);
}
/**
* Maps the div focus events to pixi's EventSystem (mouseover)
* @private
* @param {FocusEvent} e - The focus event.
*/
_onFocus(e) {
if (!e.target.getAttribute("aria-live")) {
e.target.setAttribute("aria-live", "assertive");
}
this._dispatchEvent(e, ["mouseover"]);
}
/**
* Maps the div focus events to pixi's EventSystem (mouseout)
* @private
* @param {FocusEvent} e - The focusout event.
*/
_onFocusOut(e) {
if (!e.target.getAttribute("aria-live")) {
e.target.setAttribute("aria-live", "polite");
}
this._dispatchEvent(e, ["mouseout"]);
}
/**
* Is called when a key is pressed
* @private
* @param {KeyboardEvent} e - The keydown event.
*/
_onKeyDown(e) {
if (e.keyCode !== KEY_CODE_TAB) {
return;
}
this._activate();
}
/**
* Is called when the mouse moves across the renderer element
* @private
* @param {MouseEvent} e - The mouse event.
*/
_onMouseMove(e) {
if (e.movementX === 0 && e.movementY === 0) {
return;
}
this._deactivate();
}
/** Destroys the accessibility manager */
destroy() {
this._destroyTouchHook();
this._div = null;
globalThis.document.removeEventListener("mousemove", this._onMouseMove, true);
globalThis.removeEventListener("keydown", this._onKeyDown);
this._pool = null;
this._children = null;
this._renderer = null;
}
}
/** @ignore */
AccessibilitySystem.extension = {
type: [
Extensions.ExtensionType.WebGLSystem,
Extensions.ExtensionType.WebGPUSystem
],
name: "accessibility"
};
exports.AccessibilitySystem = AccessibilitySystem;
//# sourceMappingURL=AccessibilitySystem.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,402 @@
import { FederatedEvent } from '../events/FederatedEvent.mjs';
import { ExtensionType } from '../extensions/Extensions.mjs';
import { isMobile } from '../utils/browser/isMobile.mjs';
import { removeItems } from '../utils/data/removeItems.mjs';
"use strict";
const KEY_CODE_TAB = 9;
const DIV_TOUCH_SIZE = 100;
const DIV_TOUCH_POS_X = 0;
const DIV_TOUCH_POS_Y = 0;
const DIV_TOUCH_ZINDEX = 2;
const DIV_HOOK_SIZE = 1;
const DIV_HOOK_POS_X = -1e3;
const DIV_HOOK_POS_Y = -1e3;
const DIV_HOOK_ZINDEX = 2;
class AccessibilitySystem {
// 2fps
// eslint-disable-next-line jsdoc/require-param
/**
* @param {WebGLRenderer|WebGPURenderer} renderer - A reference to the current renderer
*/
constructor(renderer, _mobileInfo = isMobile) {
this._mobileInfo = _mobileInfo;
/** Setting this to true will visually show the divs. */
this.debug = false;
/** Internal variable, see isActive getter. */
this._isActive = false;
/** Internal variable, see isMobileAccessibility getter. */
this._isMobileAccessibility = false;
/** A simple pool for storing divs. */
this._pool = [];
/** This is a tick used to check if an object is no longer being rendered. */
this._renderId = 0;
/** The array of currently active accessible items. */
this._children = [];
/** Count to throttle div updates on android devices. */
this._androidUpdateCount = 0;
/** The frequency to update the div elements. */
this._androidUpdateFrequency = 500;
this._hookDiv = null;
if (_mobileInfo.tablet || _mobileInfo.phone) {
this._createTouchHook();
}
const div = document.createElement("div");
div.style.width = `${DIV_TOUCH_SIZE}px`;
div.style.height = `${DIV_TOUCH_SIZE}px`;
div.style.position = "absolute";
div.style.top = `${DIV_TOUCH_POS_X}px`;
div.style.left = `${DIV_TOUCH_POS_Y}px`;
div.style.zIndex = DIV_TOUCH_ZINDEX.toString();
this._div = div;
this._renderer = renderer;
this._onKeyDown = this._onKeyDown.bind(this);
this._onMouseMove = this._onMouseMove.bind(this);
globalThis.addEventListener("keydown", this._onKeyDown, false);
}
/**
* Value of `true` if accessibility is currently active and accessibility layers are showing.
* @member {boolean}
* @readonly
*/
get isActive() {
return this._isActive;
}
/**
* Value of `true` if accessibility is enabled for touch devices.
* @member {boolean}
* @readonly
*/
get isMobileAccessibility() {
return this._isMobileAccessibility;
}
get hookDiv() {
return this._hookDiv;
}
/**
* Creates the touch hooks.
* @private
*/
_createTouchHook() {
const hookDiv = document.createElement("button");
hookDiv.style.width = `${DIV_HOOK_SIZE}px`;
hookDiv.style.height = `${DIV_HOOK_SIZE}px`;
hookDiv.style.position = "absolute";
hookDiv.style.top = `${DIV_HOOK_POS_X}px`;
hookDiv.style.left = `${DIV_HOOK_POS_Y}px`;
hookDiv.style.zIndex = DIV_HOOK_ZINDEX.toString();
hookDiv.style.backgroundColor = "#FF0000";
hookDiv.title = "select to enable accessibility for this content";
hookDiv.addEventListener("focus", () => {
this._isMobileAccessibility = true;
this._activate();
this._destroyTouchHook();
});
document.body.appendChild(hookDiv);
this._hookDiv = hookDiv;
}
/**
* Destroys the touch hooks.
* @private
*/
_destroyTouchHook() {
if (!this._hookDiv) {
return;
}
document.body.removeChild(this._hookDiv);
this._hookDiv = null;
}
/**
* Activating will cause the Accessibility layer to be shown.
* This is called when a user presses the tab key.
* @private
*/
_activate() {
if (this._isActive) {
return;
}
this._isActive = true;
globalThis.document.addEventListener("mousemove", this._onMouseMove, true);
globalThis.removeEventListener("keydown", this._onKeyDown, false);
this._renderer.runners.postrender.add(this);
this._renderer.view.canvas.parentNode?.appendChild(this._div);
}
/**
* Deactivating will cause the Accessibility layer to be hidden.
* This is called when a user moves the mouse.
* @private
*/
_deactivate() {
if (!this._isActive || this._isMobileAccessibility) {
return;
}
this._isActive = false;
globalThis.document.removeEventListener("mousemove", this._onMouseMove, true);
globalThis.addEventListener("keydown", this._onKeyDown, false);
this._renderer.runners.postrender.remove(this);
this._div.parentNode?.removeChild(this._div);
}
/**
* This recursive function will run through the scene graph and add any new accessible objects to the DOM layer.
* @private
* @param {Container} container - The Container to check.
*/
_updateAccessibleObjects(container) {
if (!container.visible || !container.accessibleChildren) {
return;
}
if (container.accessible && container.isInteractive()) {
if (!container._accessibleActive) {
this._addChild(container);
}
container._renderId = this._renderId;
}
const children = container.children;
if (children) {
for (let i = 0; i < children.length; i++) {
this._updateAccessibleObjects(children[i]);
}
}
}
/**
* Runner init called, view is available at this point.
* @ignore
*/
init(options) {
this.debug = options?.debug ?? this.debug;
this._renderer.runners.postrender.remove(this);
}
/**
* Runner postrender was called, ensure that all divs are mapped correctly to their Containers.
* Only fires while active.
* @ignore
*/
postrender() {
const now = performance.now();
if (this._mobileInfo.android.device && now < this._androidUpdateCount) {
return;
}
this._androidUpdateCount = now + this._androidUpdateFrequency;
if (!this._renderer.renderingToScreen || !this._renderer.view.canvas) {
return;
}
if (this._renderer.lastObjectRendered) {
this._updateAccessibleObjects(this._renderer.lastObjectRendered);
}
const { x, y, width, height } = this._renderer.view.canvas.getBoundingClientRect();
const { width: viewWidth, height: viewHeight, resolution } = this._renderer;
const sx = width / viewWidth * resolution;
const sy = height / viewHeight * resolution;
let div = this._div;
div.style.left = `${x}px`;
div.style.top = `${y}px`;
div.style.width = `${viewWidth}px`;
div.style.height = `${viewHeight}px`;
for (let i = 0; i < this._children.length; i++) {
const child = this._children[i];
if (child._renderId !== this._renderId) {
child._accessibleActive = false;
removeItems(this._children, i, 1);
this._div.removeChild(child._accessibleDiv);
this._pool.push(child._accessibleDiv);
child._accessibleDiv = null;
i--;
} else {
div = child._accessibleDiv;
let hitArea = child.hitArea;
const wt = child.worldTransform;
if (child.hitArea) {
div.style.left = `${(wt.tx + hitArea.x * wt.a) * sx}px`;
div.style.top = `${(wt.ty + hitArea.y * wt.d) * sy}px`;
div.style.width = `${hitArea.width * wt.a * sx}px`;
div.style.height = `${hitArea.height * wt.d * sy}px`;
} else {
hitArea = child.getBounds().rectangle;
this._capHitArea(hitArea);
div.style.left = `${hitArea.x * sx}px`;
div.style.top = `${hitArea.y * sy}px`;
div.style.width = `${hitArea.width * sx}px`;
div.style.height = `${hitArea.height * sy}px`;
if (div.title !== child.accessibleTitle && child.accessibleTitle !== null) {
div.title = child.accessibleTitle || "";
}
if (div.getAttribute("aria-label") !== child.accessibleHint && child.accessibleHint !== null) {
div.setAttribute("aria-label", child.accessibleHint || "");
}
}
if (child.accessibleTitle !== div.title || child.tabIndex !== div.tabIndex) {
div.title = child.accessibleTitle || "";
div.tabIndex = child.tabIndex;
if (this.debug) {
this._updateDebugHTML(div);
}
}
}
}
this._renderId++;
}
/**
* private function that will visually add the information to the
* accessibility div
* @param {HTMLElement} div -
*/
_updateDebugHTML(div) {
div.innerHTML = `type: ${div.type}</br> title : ${div.title}</br> tabIndex: ${div.tabIndex}`;
}
/**
* Adjust the hit area based on the bounds of a display object
* @param {Rectangle} hitArea - Bounds of the child
*/
_capHitArea(hitArea) {
if (hitArea.x < 0) {
hitArea.width += hitArea.x;
hitArea.x = 0;
}
if (hitArea.y < 0) {
hitArea.height += hitArea.y;
hitArea.y = 0;
}
const { width: viewWidth, height: viewHeight } = this._renderer;
if (hitArea.x + hitArea.width > viewWidth) {
hitArea.width = viewWidth - hitArea.x;
}
if (hitArea.y + hitArea.height > viewHeight) {
hitArea.height = viewHeight - hitArea.y;
}
}
/**
* Adds a Container to the accessibility manager
* @private
* @param {Container} container - The child to make accessible.
*/
_addChild(container) {
let div = this._pool.pop();
if (!div) {
div = document.createElement("button");
div.style.width = `${DIV_TOUCH_SIZE}px`;
div.style.height = `${DIV_TOUCH_SIZE}px`;
div.style.backgroundColor = this.debug ? "rgba(255,255,255,0.5)" : "transparent";
div.style.position = "absolute";
div.style.zIndex = DIV_TOUCH_ZINDEX.toString();
div.style.borderStyle = "none";
if (navigator.userAgent.toLowerCase().includes("chrome")) {
div.setAttribute("aria-live", "off");
} else {
div.setAttribute("aria-live", "polite");
}
if (navigator.userAgent.match(/rv:.*Gecko\//)) {
div.setAttribute("aria-relevant", "additions");
} else {
div.setAttribute("aria-relevant", "text");
}
div.addEventListener("click", this._onClick.bind(this));
div.addEventListener("focus", this._onFocus.bind(this));
div.addEventListener("focusout", this._onFocusOut.bind(this));
}
div.style.pointerEvents = container.accessiblePointerEvents;
div.type = container.accessibleType;
if (container.accessibleTitle && container.accessibleTitle !== null) {
div.title = container.accessibleTitle;
} else if (!container.accessibleHint || container.accessibleHint === null) {
div.title = `container ${container.tabIndex}`;
}
if (container.accessibleHint && container.accessibleHint !== null) {
div.setAttribute("aria-label", container.accessibleHint);
}
if (this.debug) {
this._updateDebugHTML(div);
}
container._accessibleActive = true;
container._accessibleDiv = div;
div.container = container;
this._children.push(container);
this._div.appendChild(container._accessibleDiv);
container._accessibleDiv.tabIndex = container.tabIndex;
}
/**
* Dispatch events with the EventSystem.
* @param e
* @param type
* @private
*/
_dispatchEvent(e, type) {
const { container: target } = e.target;
const boundary = this._renderer.events.rootBoundary;
const event = Object.assign(new FederatedEvent(boundary), { target });
boundary.rootTarget = this._renderer.lastObjectRendered;
type.forEach((type2) => boundary.dispatchEvent(event, type2));
}
/**
* Maps the div button press to pixi's EventSystem (click)
* @private
* @param {MouseEvent} e - The click event.
*/
_onClick(e) {
this._dispatchEvent(e, ["click", "pointertap", "tap"]);
}
/**
* Maps the div focus events to pixi's EventSystem (mouseover)
* @private
* @param {FocusEvent} e - The focus event.
*/
_onFocus(e) {
if (!e.target.getAttribute("aria-live")) {
e.target.setAttribute("aria-live", "assertive");
}
this._dispatchEvent(e, ["mouseover"]);
}
/**
* Maps the div focus events to pixi's EventSystem (mouseout)
* @private
* @param {FocusEvent} e - The focusout event.
*/
_onFocusOut(e) {
if (!e.target.getAttribute("aria-live")) {
e.target.setAttribute("aria-live", "polite");
}
this._dispatchEvent(e, ["mouseout"]);
}
/**
* Is called when a key is pressed
* @private
* @param {KeyboardEvent} e - The keydown event.
*/
_onKeyDown(e) {
if (e.keyCode !== KEY_CODE_TAB) {
return;
}
this._activate();
}
/**
* Is called when the mouse moves across the renderer element
* @private
* @param {MouseEvent} e - The mouse event.
*/
_onMouseMove(e) {
if (e.movementX === 0 && e.movementY === 0) {
return;
}
this._deactivate();
}
/** Destroys the accessibility manager */
destroy() {
this._destroyTouchHook();
this._div = null;
globalThis.document.removeEventListener("mousemove", this._onMouseMove, true);
globalThis.removeEventListener("keydown", this._onKeyDown);
this._pool = null;
this._children = null;
this._renderer = null;
}
}
/** @ignore */
AccessibilitySystem.extension = {
type: [
ExtensionType.WebGLSystem,
ExtensionType.WebGPUSystem
],
name: "accessibility"
};
export { AccessibilitySystem };
//# sourceMappingURL=AccessibilitySystem.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,86 @@
import type { Container } from '../scene/container/Container';
/**
* The type of the pointer event to listen for.
* Can be any of the following:
* - `auto`
* - `none`
* - `visiblePainted`
* - `visibleFill`
* - `visibleStroke`
* - `visible`
* - `painted`
* - `fill`
* - `stroke`
* - `all`
* - `inherit`
* @memberof accessibility
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events
*/
export type PointerEvents = 'auto' | 'none' | 'visiblePainted' | 'visibleFill' | 'visibleStroke' | 'visible' | 'painted' | 'fill' | 'stroke' | 'all' | 'inherit';
/**
* When `accessible` is enabled on any display object, these properties will affect its accessibility.
* @memberof accessibility
*/
export interface AccessibleOptions {
/**
* Flag for if the object is accessible. If true AccessibilityManager will overlay a
* shadow div with attributes set
* @default false
*/
accessible: boolean;
/**
* Sets the title attribute of the shadow div
* If accessibleTitle AND accessibleHint has not been this will default to 'container [tabIndex]'
* @member {string}
*/
accessibleTitle: string | null;
/** Sets the aria-label attribute of the shadow div */
accessibleHint: string | null;
/**
* @default 0
*/
tabIndex: number;
/**
* Specify the type of div the accessible layer is. Screen readers treat the element differently
* depending on this type. Defaults to button.
* @default 'button'
*/
accessibleType: string;
/**
* Specify the pointer-events the accessible div will use
* Defaults to auto.
* @default 'auto'
* @type {accessibility.PointerEvents}
*/
accessiblePointerEvents: PointerEvents;
/**
* Setting to false will prevent any children inside this container to
* be accessible. Defaults to true.
* @default true
*/
accessibleChildren: boolean;
}
/**
* The Accessibility object is attached to the {@link Container}.
* @private
*/
export interface AccessibleTarget extends AccessibleOptions {
_accessibleActive: boolean;
_accessibleDiv: AccessibleHTMLElement | null;
_renderId: number;
}
export interface AccessibleHTMLElement extends HTMLElement {
type?: string;
container?: Container;
}
/**
* Default property values of accessible objects
* used by {@link AccessibilitySystem}.
* @private
* @example
* import { accessibleTarget } from 'pixi.js';
*
* function MyObject() {}
* Object.assign(MyObject.prototype, accessibleTarget);
*/
export declare const accessibilityTarget: AccessibleTarget;

View File

@@ -0,0 +1,75 @@
'use strict';
"use strict";
const accessibilityTarget = {
/**
* Flag for if the object is accessible. If true AccessibilityManager will overlay a
* shadow div with attributes set
* @member {boolean}
* @memberof scene.Container#
*/
accessible: false,
/**
* Sets the title attribute of the shadow div
* If accessibleTitle AND accessibleHint has not been this will default to 'container [tabIndex]'
* @member {string}
* @memberof scene.Container#
*/
accessibleTitle: null,
/**
* Sets the aria-label attribute of the shadow div
* @member {string}
* @memberof scene.Container#
*/
accessibleHint: null,
/**
* @member {number}
* @memberof scene.Container#
* @todo Needs docs.
*/
tabIndex: 0,
/**
* @member {boolean}
* @memberof scene.Container#
* @private
*/
_accessibleActive: false,
/**
* @memberof scene.Container#
* @private
*/
_accessibleDiv: null,
/**
* Specify the type of div the accessible layer is. Screen readers treat the element differently
* depending on this type. Defaults to button.
* @member {string}
* @memberof scene.Container#
* @default 'button'
*/
accessibleType: "button",
/**
* Specify the pointer-events the accessible div will use
* Defaults to auto.
* @type {PointerEvents}
* @memberof scene.Container#
* @default 'auto'
*/
accessiblePointerEvents: "auto",
/**
* Setting to false will prevent any children inside this container to
* be accessible. Defaults to true.
* @member {boolean}
* @memberof scene.Container#
* @default true
*/
accessibleChildren: true,
/**
* @member {number}
* @memberof scene.Container#
* @private
*/
_renderId: -1
};
exports.accessibilityTarget = accessibilityTarget;
//# sourceMappingURL=accessibilityTarget.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,73 @@
"use strict";
const accessibilityTarget = {
/**
* Flag for if the object is accessible. If true AccessibilityManager will overlay a
* shadow div with attributes set
* @member {boolean}
* @memberof scene.Container#
*/
accessible: false,
/**
* Sets the title attribute of the shadow div
* If accessibleTitle AND accessibleHint has not been this will default to 'container [tabIndex]'
* @member {string}
* @memberof scene.Container#
*/
accessibleTitle: null,
/**
* Sets the aria-label attribute of the shadow div
* @member {string}
* @memberof scene.Container#
*/
accessibleHint: null,
/**
* @member {number}
* @memberof scene.Container#
* @todo Needs docs.
*/
tabIndex: 0,
/**
* @member {boolean}
* @memberof scene.Container#
* @private
*/
_accessibleActive: false,
/**
* @memberof scene.Container#
* @private
*/
_accessibleDiv: null,
/**
* Specify the type of div the accessible layer is. Screen readers treat the element differently
* depending on this type. Defaults to button.
* @member {string}
* @memberof scene.Container#
* @default 'button'
*/
accessibleType: "button",
/**
* Specify the pointer-events the accessible div will use
* Defaults to auto.
* @type {PointerEvents}
* @memberof scene.Container#
* @default 'auto'
*/
accessiblePointerEvents: "auto",
/**
* Setting to false will prevent any children inside this container to
* be accessible. Defaults to true.
* @member {boolean}
* @memberof scene.Container#
* @default true
*/
accessibleChildren: true,
/**
* @member {number}
* @memberof scene.Container#
* @private
*/
_renderId: -1
};
export { accessibilityTarget };
//# sourceMappingURL=accessibilityTarget.mjs.map

File diff suppressed because one or more lines are too long

2
node_modules/pixi.js/lib/accessibility/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
export * from './AccessibilitySystem';
export * from './accessibilityTarget';

10
node_modules/pixi.js/lib/accessibility/index.js generated vendored Normal file
View File

@@ -0,0 +1,10 @@
'use strict';
var AccessibilitySystem = require('./AccessibilitySystem.js');
var accessibilityTarget = require('./accessibilityTarget.js');
"use strict";
exports.AccessibilitySystem = AccessibilitySystem.AccessibilitySystem;
exports.accessibilityTarget = accessibilityTarget.accessibilityTarget;
//# sourceMappingURL=index.js.map

1
node_modules/pixi.js/lib/accessibility/index.js.map generated vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}

5
node_modules/pixi.js/lib/accessibility/index.mjs generated vendored Normal file
View File

@@ -0,0 +1,5 @@
export { AccessibilitySystem } from './AccessibilitySystem.mjs';
export { accessibilityTarget } from './accessibilityTarget.mjs';
"use strict";
//# sourceMappingURL=index.mjs.map

1
node_modules/pixi.js/lib/accessibility/index.mjs.map generated vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}

1
node_modules/pixi.js/lib/accessibility/init.d.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export {};

11
node_modules/pixi.js/lib/accessibility/init.js generated vendored Normal file
View File

@@ -0,0 +1,11 @@
'use strict';
var Extensions = require('../extensions/Extensions.js');
var Container = require('../scene/container/Container.js');
var AccessibilitySystem = require('./AccessibilitySystem.js');
var accessibilityTarget = require('./accessibilityTarget.js');
"use strict";
Extensions.extensions.add(AccessibilitySystem.AccessibilitySystem);
Container.Container.mixin(accessibilityTarget.accessibilityTarget);
//# sourceMappingURL=init.js.map

1
node_modules/pixi.js/lib/accessibility/init.js.map generated vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"init.js","sources":["../../src/accessibility/init.ts"],"sourcesContent":["import { extensions } from '../extensions/Extensions';\nimport { Container } from '../scene/container/Container';\nimport { AccessibilitySystem } from './AccessibilitySystem';\nimport { accessibilityTarget } from './accessibilityTarget';\n\nextensions.add(AccessibilitySystem);\nContainer.mixin(accessibilityTarget);\n"],"names":["extensions","AccessibilitySystem","Container","accessibilityTarget"],"mappings":";;;;;;;;AAKAA,qBAAA,CAAW,IAAIC,uCAAmB,CAAA,CAAA;AAClCC,mBAAA,CAAU,MAAMC,uCAAmB,CAAA;;"}

9
node_modules/pixi.js/lib/accessibility/init.mjs generated vendored Normal file
View File

@@ -0,0 +1,9 @@
import { extensions } from '../extensions/Extensions.mjs';
import { Container } from '../scene/container/Container.mjs';
import { AccessibilitySystem } from './AccessibilitySystem.mjs';
import { accessibilityTarget } from './accessibilityTarget.mjs';
"use strict";
extensions.add(AccessibilitySystem);
Container.mixin(accessibilityTarget);
//# sourceMappingURL=init.mjs.map

1
node_modules/pixi.js/lib/accessibility/init.mjs.map generated vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"init.mjs","sources":["../../src/accessibility/init.ts"],"sourcesContent":["import { extensions } from '../extensions/Extensions';\nimport { Container } from '../scene/container/Container';\nimport { AccessibilitySystem } from './AccessibilitySystem';\nimport { accessibilityTarget } from './accessibilityTarget';\n\nextensions.add(AccessibilitySystem);\nContainer.mixin(accessibilityTarget);\n"],"names":[],"mappings":";;;;;;AAKA,UAAA,CAAW,IAAI,mBAAmB,CAAA,CAAA;AAClC,SAAA,CAAU,MAAM,mBAAmB,CAAA"}