@eva/plugin-a11y 2.0.1-beta.27 → 2.0.1-beta.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,6 +37,11 @@ function __awaiter(thisArg, _arguments, P, generator) {
37
37
  });
38
38
  }
39
39
 
40
+ /**
41
+ * 生成唯一的标识符
42
+ * @param len 长度
43
+ * @param radix 基
44
+ */
40
45
  function uuid(len) {
41
46
  let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
42
47
  let uuid = [];
@@ -45,6 +50,11 @@ function uuid(len) {
45
50
  uuid[i] = chars[0 | (Math.random() * radix)];
46
51
  return uuid.join('');
47
52
  }
53
+ /**
54
+ * 设置 dom 样式
55
+ * @param div 需要设置样式的dom元素
56
+ * @param style 样式属性
57
+ */
48
58
  const setStyle = (element, style) => {
49
59
  const { width, height, position, left = 0, top = 0, zIndex, pointerEvents, background } = style;
50
60
  element.style.width = `${width}px`;
@@ -68,7 +78,70 @@ const setTransform = (element, transform, ratioX, ratioY) => {
68
78
  element.style.webkitTransformOrigin = 'left top';
69
79
  };
70
80
 
81
+ /**
82
+ * 无障碍组件(A11y/Accessibility)
83
+ *
84
+ * A11y 组件为游戏对象提供无障碍支持,使屏幕阅读器能够识别和朗读游戏内容。
85
+ * 它在游戏画布上方创建透明的 DOM 元素,携带 ARIA 属性,让视障用户也能使用游戏。
86
+ *
87
+ * 主要功能:
88
+ * - 提供屏幕阅读器可识别的文本标签
89
+ * - 支持 ARIA 角色和属性
90
+ * - 自动同步游戏对象的位置和尺寸
91
+ * - 支持交互事件的无障碍访问
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * // 为游戏对象提供朗读能力
96
+ * const button = new GameObject('button');
97
+ * button.addComponent(new A11y({
98
+ * hint: '开始游戏按钮',
99
+ * role: 'button'
100
+ * }));
101
+ *
102
+ * // 带交互事件的无障碍元素
103
+ * button.addComponent(new A11y({
104
+ * hint: '点击开始游戏',
105
+ * event: eventComponent
106
+ * }));
107
+ * ```
108
+ */
71
109
  class A11y extends eva_js.Component {
110
+ /**
111
+ * 构造无障碍组件
112
+ *
113
+ * @param param - 无障碍组件配置参数
114
+ * @param param.hint - 屏幕阅读器朗读文本
115
+ * @param param.interactive - 是否可交互,默认 false
116
+ * @param param.role - ARIA 角色属性
117
+ * @param param.event - 关联的事件组件(已弃用)
118
+ * @param param.delay - DOM 延迟加载时间(毫秒)
119
+ * @param param.props - ARIA value 属性(已弃用)
120
+ * @param param.state - ARIA state 属性(已弃用)
121
+ * @param param.attr - 自定义属性(已弃用)
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * // 简单文本朗读
126
+ * new A11y({ hint: '这是一个图片' })
127
+ *
128
+ * // 可交互按钮
129
+ * new A11y({
130
+ * hint: '开始游戏',
131
+ * role: 'button',
132
+ * interactive: true
133
+ * })
134
+ *
135
+ * // 带 ARIA 属性
136
+ * new A11y({
137
+ * hint: '进度条',
138
+ * role: 'progressbar',
139
+ * 'aria-valuemin': '0',
140
+ * 'aria-valuemax': '100',
141
+ * 'aria-valuenow': '50'
142
+ * })
143
+ * ```
144
+ */
72
145
  constructor(param) {
73
146
  super();
74
147
  Object.assign(this, param);
@@ -83,6 +156,7 @@ class A11y extends eva_js.Component {
83
156
  this.a11yId = `_${uuid(6)}`;
84
157
  }
85
158
  }
159
+ /** 组件名称 */
86
160
  A11y.componentName = 'A11y';
87
161
  __decorate([
88
162
  inspectorDecorator.type('boolean')
@@ -109,16 +183,25 @@ exports.A11yActivate = void 0;
109
183
  A11yActivate[A11yActivate["DISABLE"] = 1] = "DISABLE";
110
184
  A11yActivate[A11yActivate["CHECK"] = 2] = "CHECK";
111
185
  })(exports.A11yActivate || (exports.A11yActivate = {}));
186
+ /**
187
+ * 无障碍 DOM 的指针事件
188
+ */
112
189
  var PointerEvents;
113
190
  (function (PointerEvents) {
114
191
  PointerEvents["NONE"] = "none";
115
192
  PointerEvents["AUTO"] = "auto";
116
193
  })(PointerEvents || (PointerEvents = {}));
194
+ /**
195
+ * 无障碍 DOM 层的样式
196
+ */
117
197
  var MaskBackground;
118
198
  (function (MaskBackground) {
119
199
  MaskBackground["DEBUG"] = "rgba(255,0,0,0.5)";
120
200
  MaskBackground["NONE"] = "transparent";
121
201
  })(MaskBackground || (MaskBackground = {}));
202
+ /**
203
+ * 无障碍 DOM 的类型
204
+ */
122
205
  var ElementType;
123
206
  (function (ElementType) {
124
207
  ElementType["BUTTON"] = "button";
@@ -137,11 +220,69 @@ const getEventFunc = function (event, gameObject, e) {
137
220
  });
138
221
  });
139
222
  };
223
+ /**
224
+ * 无障碍系统(A11y System)
225
+ *
226
+ * A11ySystem 管理游戏中所有无障碍组件,在游戏画布上方创建无障碍覆盖层。
227
+ * 它会自动将游戏对象的位置、尺寸同步到对应的 DOM 元素上,
228
+ * 并处理与屏幕阅读器的交互。
229
+ *
230
+ * 主要功能:
231
+ * - 创建和管理无障碍 DOM 覆盖层
232
+ * - 同步游戏对象的变换到 DOM 元素
233
+ * - 处理无障碍元素的事件绑定
234
+ * - 支持调试模式(可视化无障碍区域)
235
+ * - 自动检测或手动配置无障碍功能开关
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * // 自动检测系统读屏功能
240
+ * game.addSystem(new A11ySystem());
241
+ *
242
+ * // 开启调试模式
243
+ * game.addSystem(new A11ySystem({ debug: true }));
244
+ *
245
+ * // 强制启用无障碍功能
246
+ * game.addSystem(new A11ySystem({
247
+ * activate: A11yActivate.ENABLE,
248
+ * zIndex: 10000
249
+ * }));
250
+ * ```
251
+ */
140
252
  let A11ySystem = class A11ySystem extends eva_js.System {
253
+ /**
254
+ * 构造无障碍系统
255
+ *
256
+ * @param opt - 系统配置选项
257
+ * @param opt.debug - 是否开启调试模式,默认 false
258
+ * @param opt.activate - 无障碍功能开关模式,默认 CHECK(自动检测)
259
+ * @param opt.delay - DOM 元素延迟创建时间(毫秒),默认 100
260
+ * @param opt.zIndex - 覆盖层的 z-index,默认 10000
261
+ * @param opt.checkA11yOpen - 自定义检测无障碍功能是否开启的函数
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * // 开启调试,无障碍区域会显示红色透明背景
266
+ * new A11ySystem({ debug: true })
267
+ *
268
+ * // 禁用无障碍功能
269
+ * new A11ySystem({ activate: A11yActivate.DISABLE })
270
+ *
271
+ * // 自定义检测逻辑
272
+ * new A11ySystem({
273
+ * checkA11yOpen: async () => {
274
+ * return await isScreenReaderActive();
275
+ * }
276
+ * })
277
+ * ```
278
+ */
141
279
  constructor(opt) {
142
280
  super(opt);
281
+ /** 无障碍 DOM 元素缓存 */
143
282
  this.cache = new Map();
283
+ /** 事件处理函数缓存 */
144
284
  this.eventCache = new Map();
285
+ /** 无障碍覆盖层的 z-index 值 */
145
286
  this.zIndex = ZINDEX;
146
287
  }
147
288
  get ratioX() {
@@ -199,8 +340,10 @@ let A11ySystem = class A11ySystem extends eva_js.System {
199
340
  }
200
341
  if (!this.activate)
201
342
  return;
343
+ // 渲染出父容器
202
344
  const div = document.createElement('div');
203
345
  this.div = div;
346
+ // 如果存在父容器,则渲染这个 div,子元素则会相对这个 div 进行定位,否则直接相对于 body 进行定位
204
347
  if (this.game.canvas.parentNode) {
205
348
  this.game.canvas.parentNode.insertBefore(this.div, this.game.canvas);
206
349
  }
@@ -225,12 +368,14 @@ let A11ySystem = class A11ySystem extends eva_js.System {
225
368
  return { renderWidth, renderHeight };
226
369
  }
227
370
  getCanvasBoundingClientRect() {
371
+ // 渲染画布相对于视口的实际宽高以及位置,实际的像素
228
372
  const { width, height, left, top } = this.game.canvas.getBoundingClientRect();
229
373
  return { width, height, left, top };
230
374
  }
231
375
  initDiv() {
232
376
  const { pageXOffset, pageYOffset } = window;
233
377
  const { width, height, left, top } = this.getCanvasBoundingClientRect();
378
+ // 父容器位置
234
379
  const style = {
235
380
  width,
236
381
  height,
@@ -242,6 +387,7 @@ let A11ySystem = class A11ySystem extends eva_js.System {
242
387
  background: MaskBackground.NONE,
243
388
  };
244
389
  setStyle(this.div, style);
390
+ // 给父容器设置捕获事件,用于监听事件坐标
245
391
  this.div.addEventListener('click', e => {
246
392
  const currentTarget = e.currentTarget;
247
393
  const { left, top } = currentTarget.getBoundingClientRect();
@@ -250,6 +396,9 @@ let A11ySystem = class A11ySystem extends eva_js.System {
250
396
  this.eventPosition = { x, y };
251
397
  }, true);
252
398
  }
399
+ /**
400
+ * 监听插件更新
401
+ */
253
402
  update() {
254
403
  return __awaiter(this, void 0, void 0, function* () {
255
404
  const changes = this.componentObserver.clear();
@@ -290,6 +439,10 @@ let A11ySystem = class A11ySystem extends eva_js.System {
290
439
  element && this.div.removeChild(element);
291
440
  this.cache.delete(a11yId);
292
441
  }
442
+ /**
443
+ * 监听组件被添加至游戏对象
444
+ * @param changed 改变的组件
445
+ */
293
446
  add(changed) {
294
447
  if (!this.activate)
295
448
  return;
@@ -318,6 +471,7 @@ let A11ySystem = class A11ySystem extends eva_js.System {
318
471
  }
319
472
  }, delay || this.delay);
320
473
  }
474
+ // 监听 scene 改变
321
475
  transformChange(changed) {
322
476
  const component = changed.component;
323
477
  const { gameObject } = changed;
@@ -326,16 +480,26 @@ let A11ySystem = class A11ySystem extends eva_js.System {
326
480
  return;
327
481
  const { a11yId } = a11yComponent;
328
482
  if (!component.inScene) {
483
+ // 监听 scene 删除游戏对象
329
484
  const dom = this.div.querySelector(`#${a11yId}`);
330
485
  dom && this.div.removeChild(dom);
486
+ // this.cache.delete(a11yId)
331
487
  }
332
488
  else {
489
+ // 监听 scene add
490
+ // this.div.appendChild(this.cache)
333
491
  if (this.cache.has(a11yId)) {
334
492
  const addDom = this.cache.get(a11yId);
335
493
  addDom && this.div.appendChild(addDom);
336
494
  }
337
495
  }
338
496
  }
497
+ /**
498
+ * 为无障碍组件设置监听事件
499
+ * @param element DOM 元素
500
+ * @param event 事件组件对象
501
+ * @param gameObject 游戏对象
502
+ */
339
503
  setEvent(element, event, gameObject, id) {
340
504
  if (!event) {
341
505
  return;
@@ -367,12 +531,19 @@ let A11ySystem = class A11ySystem extends eva_js.System {
367
531
  const element = this.cache.get(a11yId);
368
532
  element && element.removeEventListener('click', func);
369
533
  }
534
+ /**
535
+ * 设置无障碍属性标签
536
+ * @param element DOM 元素
537
+ * @param hint 无障碍朗读文字
538
+ * @param interactive 是否可交互
539
+ */
370
540
  setA11yAttr(element, component) {
371
541
  const { hint, props = {}, state = {}, role, a11yId: id } = component;
372
542
  const realRole = role || 'text';
373
543
  element.setAttribute('role', realRole);
374
544
  element.setAttribute('aria-label', hint);
375
545
  element.id = id;
546
+ // 这里兼容
376
547
  const ariaProps = Object.keys(props);
377
548
  for (const key of ariaProps) {
378
549
  element.setAttribute(key, props[key]);
@@ -387,8 +558,19 @@ let A11ySystem = class A11ySystem extends eva_js.System {
387
558
  }
388
559
  }
389
560
  }
561
+ /**
562
+ * 将无障碍元素设置到对应的位置
563
+ * @param element DOM 元素
564
+ * @param transform 位置属性
565
+ */
390
566
  setPosition(element, transform) {
567
+ // 相对画布定位
568
+ // const { x: anchorX, y: anchorY } = transform.anchor
569
+ // 游戏对象的宽高
391
570
  const { width, height } = transform.size;
571
+ // position
572
+ // const { x: positionX, y: positionY } = transform.position
573
+ // 设置无障碍 DOM 的样式,龙骨动画默认 2px
392
574
  const style = {
393
575
  width: width === 0 ? 1 : width * this.ratioX,
394
576
  height: height === 0 ? 1 : height * this.ratioY,
@@ -398,6 +580,7 @@ let A11ySystem = class A11ySystem extends eva_js.System {
398
580
  background: this.debug ? MaskBackground.DEBUG : MaskBackground.NONE,
399
581
  };
400
582
  setStyle(element, style);
583
+ // 调整 DOM 的位置
401
584
  setTransform(element, transform, this.ratioX, this.ratioY);
402
585
  }
403
586
  onDestroy() {
@@ -407,6 +590,7 @@ let A11ySystem = class A11ySystem extends eva_js.System {
407
590
  this.div = null;
408
591
  }
409
592
  };
593
+ /** 系统名称 */
410
594
  A11ySystem.systemName = 'A11ySystem';
411
595
  A11ySystem = __decorate([
412
596
  eva_js.decorators.componentObserver({
@@ -5,17 +5,106 @@ import type { GameObject } from '@eva/eva.js';
5
5
  import { System } from '@eva/eva.js';
6
6
  import { Transform } from '@eva/eva.js';
7
7
 
8
+ /**
9
+ * 无障碍组件(A11y/Accessibility)
10
+ *
11
+ * A11y 组件为游戏对象提供无障碍支持,使屏幕阅读器能够识别和朗读游戏内容。
12
+ * 它在游戏画布上方创建透明的 DOM 元素,携带 ARIA 属性,让视障用户也能使用游戏。
13
+ *
14
+ * 主要功能:
15
+ * - 提供屏幕阅读器可识别的文本标签
16
+ * - 支持 ARIA 角色和属性
17
+ * - 自动同步游戏对象的位置和尺寸
18
+ * - 支持交互事件的无障碍访问
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // 为游戏对象提供朗读能力
23
+ * const button = new GameObject('button');
24
+ * button.addComponent(new A11y({
25
+ * hint: '开始游戏按钮',
26
+ * role: 'button'
27
+ * }));
28
+ *
29
+ * // 带交互事件的无障碍元素
30
+ * button.addComponent(new A11y({
31
+ * hint: '点击开始游戏',
32
+ * event: eventComponent
33
+ * }));
34
+ * ```
35
+ */
8
36
  export declare class A11y extends Component<A11yParams> {
37
+ /** 组件名称 */
9
38
  static componentName: string;
39
+ /** 是否可交互 */
10
40
  interactive: boolean;
41
+ /** 屏幕阅读器朗读的文本内容 */
11
42
  hint: string;
43
+ /**
44
+ * 事件组件对象
45
+ * @deprecated 已弃用,将根据 Event 组件自动添加
46
+ */
12
47
  event: Component;
48
+ /** DOM 元素延迟加载时间(毫秒) */
13
49
  delay: number;
50
+ /** ARIA role 属性,定义元素的角色(如 button、link 等) */
14
51
  role: string;
52
+ /**
53
+ * ARIA value 属性集合
54
+ * @deprecated 已弃用,请将属性直接写在 component 上
55
+ * @example
56
+ * aria-valuemin = "0"
57
+ */
15
58
  props: object;
59
+ /**
60
+ * ARIA state 属性集合
61
+ * @deprecated 已弃用,请将属性直接写在 component 上
62
+ * @example
63
+ * aria-hidden = "true"
64
+ */
16
65
  state: object;
66
+ /**
67
+ * 自定义 DOM 属性
68
+ * @deprecated 已弃用,请将属性直接写在 component 上
69
+ */
17
70
  attr: object;
71
+ /** 辅助 DOM 元素的唯一 ID,自动生成 */
18
72
  a11yId: string;
73
+ /**
74
+ * 构造无障碍组件
75
+ *
76
+ * @param param - 无障碍组件配置参数
77
+ * @param param.hint - 屏幕阅读器朗读文本
78
+ * @param param.interactive - 是否可交互,默认 false
79
+ * @param param.role - ARIA 角色属性
80
+ * @param param.event - 关联的事件组件(已弃用)
81
+ * @param param.delay - DOM 延迟加载时间(毫秒)
82
+ * @param param.props - ARIA value 属性(已弃用)
83
+ * @param param.state - ARIA state 属性(已弃用)
84
+ * @param param.attr - 自定义属性(已弃用)
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * // 简单文本朗读
89
+ * new A11y({ hint: '这是一个图片' })
90
+ *
91
+ * // 可交互按钮
92
+ * new A11y({
93
+ * hint: '开始游戏',
94
+ * role: 'button',
95
+ * interactive: true
96
+ * })
97
+ *
98
+ * // 带 ARIA 属性
99
+ * new A11y({
100
+ * hint: '进度条',
101
+ * role: 'progressbar',
102
+ * 'aria-valuemin': '0',
103
+ * 'aria-valuemax': '100',
104
+ * 'aria-valuenow': '50'
105
+ * })
106
+ * ```
107
+ */
19
108
  constructor(param: A11yParams);
20
109
  }
21
110
 
@@ -37,18 +126,84 @@ declare interface A11yParams {
37
126
  [propName: string]: string | object | number;
38
127
  }
39
128
 
129
+ /**
130
+ * 无障碍系统(A11y System)
131
+ *
132
+ * A11ySystem 管理游戏中所有无障碍组件,在游戏画布上方创建无障碍覆盖层。
133
+ * 它会自动将游戏对象的位置、尺寸同步到对应的 DOM 元素上,
134
+ * 并处理与屏幕阅读器的交互。
135
+ *
136
+ * 主要功能:
137
+ * - 创建和管理无障碍 DOM 覆盖层
138
+ * - 同步游戏对象的变换到 DOM 元素
139
+ * - 处理无障碍元素的事件绑定
140
+ * - 支持调试模式(可视化无障碍区域)
141
+ * - 自动检测或手动配置无障碍功能开关
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * // 自动检测系统读屏功能
146
+ * game.addSystem(new A11ySystem());
147
+ *
148
+ * // 开启调试模式
149
+ * game.addSystem(new A11ySystem({ debug: true }));
150
+ *
151
+ * // 强制启用无障碍功能
152
+ * game.addSystem(new A11ySystem({
153
+ * activate: A11yActivate.ENABLE,
154
+ * zIndex: 10000
155
+ * }));
156
+ * ```
157
+ */
40
158
  export declare class A11ySystem extends System {
159
+ /** 系统名称 */
41
160
  static systemName: string;
161
+ /** 无障碍覆盖层容器 */
42
162
  div: HTMLDivElement;
163
+ /** 是否开启调试模式(显示无障碍区域背景色) */
43
164
  debug: boolean;
165
+ /** 画布横向缩放比例 */
44
166
  _ratioX: number;
167
+ /** 画布纵向缩放比例 */
45
168
  _ratioY: number;
169
+ /** 当前事件触发的坐标位置 */
46
170
  eventPosition: EventPosition;
171
+ /** 是否启用无障碍功能 */
47
172
  activate: boolean;
173
+ /** DOM 元素延迟放置时间(毫秒) */
48
174
  delay: number;
175
+ /** 无障碍 DOM 元素缓存 */
49
176
  cache: Map<string, HTMLElement>;
177
+ /** 事件处理函数缓存 */
50
178
  eventCache: Map<string, (e: MouseEvent) => void>;
179
+ /** 无障碍覆盖层的 z-index 值 */
51
180
  zIndex: number;
181
+ /**
182
+ * 构造无障碍系统
183
+ *
184
+ * @param opt - 系统配置选项
185
+ * @param opt.debug - 是否开启调试模式,默认 false
186
+ * @param opt.activate - 无障碍功能开关模式,默认 CHECK(自动检测)
187
+ * @param opt.delay - DOM 元素延迟创建时间(毫秒),默认 100
188
+ * @param opt.zIndex - 覆盖层的 z-index,默认 10000
189
+ * @param opt.checkA11yOpen - 自定义检测无障碍功能是否开启的函数
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * // 开启调试,无障碍区域会显示红色透明背景
194
+ * new A11ySystem({ debug: true })
195
+ *
196
+ * // 禁用无障碍功能
197
+ * new A11ySystem({ activate: A11yActivate.DISABLE })
198
+ *
199
+ * // 自定义检测逻辑
200
+ * new A11ySystem({
201
+ * checkA11yOpen: async () => {
202
+ * return await isScreenReaderActive();
203
+ * }
204
+ * })
205
+ * ```
206
+ */
52
207
  constructor(opt?: SystemParam);
53
208
  get ratioX(): number;
54
209
  get ratioY(): number;
@@ -65,19 +220,46 @@ export declare class A11ySystem extends System {
65
220
  top: number;
66
221
  };
67
222
  initDiv(): void;
223
+ /**
224
+ * 监听插件更新
225
+ */
68
226
  update(): Promise<void>;
69
227
  change(changed: ComponentChanged): void;
70
228
  remove(changed: ComponentChanged): void;
229
+ /**
230
+ * 监听组件被添加至游戏对象
231
+ * @param changed 改变的组件
232
+ */
71
233
  add(changed: ComponentChanged): void;
72
234
  transformChange(changed: ComponentChanged): void;
235
+ /**
236
+ * 为无障碍组件设置监听事件
237
+ * @param element DOM 元素
238
+ * @param event 事件组件对象
239
+ * @param gameObject 游戏对象
240
+ */
73
241
  setEvent(element: HTMLElement, event: EE, gameObject: GameObject, id: any): void;
74
242
  addEvent(gameObject: GameObject): void;
75
243
  removeEvent(changed: ComponentChanged): void;
244
+ /**
245
+ * 设置无障碍属性标签
246
+ * @param element DOM 元素
247
+ * @param hint 无障碍朗读文字
248
+ * @param interactive 是否可交互
249
+ */
76
250
  setA11yAttr(element: HTMLElement, component: A11y): void;
251
+ /**
252
+ * 将无障碍元素设置到对应的位置
253
+ * @param element DOM 元素
254
+ * @param transform 位置属性
255
+ */
77
256
  setPosition(element: HTMLElement, transform: Transform): void;
78
257
  onDestroy(): void;
79
258
  }
80
259
 
260
+ /**
261
+ * 点击事件位置
262
+ */
81
263
  declare interface EventPosition {
82
264
  x: number;
83
265
  y: number;
@@ -33,6 +33,11 @@ function __awaiter(thisArg, _arguments, P, generator) {
33
33
  });
34
34
  }
35
35
 
36
+ /**
37
+ * 生成唯一的标识符
38
+ * @param len 长度
39
+ * @param radix 基
40
+ */
36
41
  function uuid(len) {
37
42
  let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
38
43
  let uuid = [];
@@ -41,6 +46,11 @@ function uuid(len) {
41
46
  uuid[i] = chars[0 | (Math.random() * radix)];
42
47
  return uuid.join('');
43
48
  }
49
+ /**
50
+ * 设置 dom 样式
51
+ * @param div 需要设置样式的dom元素
52
+ * @param style 样式属性
53
+ */
44
54
  const setStyle = (element, style) => {
45
55
  const { width, height, position, left = 0, top = 0, zIndex, pointerEvents, background } = style;
46
56
  element.style.width = `${width}px`;
@@ -64,7 +74,70 @@ const setTransform = (element, transform, ratioX, ratioY) => {
64
74
  element.style.webkitTransformOrigin = 'left top';
65
75
  };
66
76
 
77
+ /**
78
+ * 无障碍组件(A11y/Accessibility)
79
+ *
80
+ * A11y 组件为游戏对象提供无障碍支持,使屏幕阅读器能够识别和朗读游戏内容。
81
+ * 它在游戏画布上方创建透明的 DOM 元素,携带 ARIA 属性,让视障用户也能使用游戏。
82
+ *
83
+ * 主要功能:
84
+ * - 提供屏幕阅读器可识别的文本标签
85
+ * - 支持 ARIA 角色和属性
86
+ * - 自动同步游戏对象的位置和尺寸
87
+ * - 支持交互事件的无障碍访问
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * // 为游戏对象提供朗读能力
92
+ * const button = new GameObject('button');
93
+ * button.addComponent(new A11y({
94
+ * hint: '开始游戏按钮',
95
+ * role: 'button'
96
+ * }));
97
+ *
98
+ * // 带交互事件的无障碍元素
99
+ * button.addComponent(new A11y({
100
+ * hint: '点击开始游戏',
101
+ * event: eventComponent
102
+ * }));
103
+ * ```
104
+ */
67
105
  class A11y extends Component {
106
+ /**
107
+ * 构造无障碍组件
108
+ *
109
+ * @param param - 无障碍组件配置参数
110
+ * @param param.hint - 屏幕阅读器朗读文本
111
+ * @param param.interactive - 是否可交互,默认 false
112
+ * @param param.role - ARIA 角色属性
113
+ * @param param.event - 关联的事件组件(已弃用)
114
+ * @param param.delay - DOM 延迟加载时间(毫秒)
115
+ * @param param.props - ARIA value 属性(已弃用)
116
+ * @param param.state - ARIA state 属性(已弃用)
117
+ * @param param.attr - 自定义属性(已弃用)
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * // 简单文本朗读
122
+ * new A11y({ hint: '这是一个图片' })
123
+ *
124
+ * // 可交互按钮
125
+ * new A11y({
126
+ * hint: '开始游戏',
127
+ * role: 'button',
128
+ * interactive: true
129
+ * })
130
+ *
131
+ * // 带 ARIA 属性
132
+ * new A11y({
133
+ * hint: '进度条',
134
+ * role: 'progressbar',
135
+ * 'aria-valuemin': '0',
136
+ * 'aria-valuemax': '100',
137
+ * 'aria-valuenow': '50'
138
+ * })
139
+ * ```
140
+ */
68
141
  constructor(param) {
69
142
  super();
70
143
  Object.assign(this, param);
@@ -79,6 +152,7 @@ class A11y extends Component {
79
152
  this.a11yId = `_${uuid(6)}`;
80
153
  }
81
154
  }
155
+ /** 组件名称 */
82
156
  A11y.componentName = 'A11y';
83
157
  __decorate([
84
158
  type('boolean')
@@ -105,16 +179,25 @@ var A11yActivate;
105
179
  A11yActivate[A11yActivate["DISABLE"] = 1] = "DISABLE";
106
180
  A11yActivate[A11yActivate["CHECK"] = 2] = "CHECK";
107
181
  })(A11yActivate || (A11yActivate = {}));
182
+ /**
183
+ * 无障碍 DOM 的指针事件
184
+ */
108
185
  var PointerEvents;
109
186
  (function (PointerEvents) {
110
187
  PointerEvents["NONE"] = "none";
111
188
  PointerEvents["AUTO"] = "auto";
112
189
  })(PointerEvents || (PointerEvents = {}));
190
+ /**
191
+ * 无障碍 DOM 层的样式
192
+ */
113
193
  var MaskBackground;
114
194
  (function (MaskBackground) {
115
195
  MaskBackground["DEBUG"] = "rgba(255,0,0,0.5)";
116
196
  MaskBackground["NONE"] = "transparent";
117
197
  })(MaskBackground || (MaskBackground = {}));
198
+ /**
199
+ * 无障碍 DOM 的类型
200
+ */
118
201
  var ElementType;
119
202
  (function (ElementType) {
120
203
  ElementType["BUTTON"] = "button";
@@ -133,11 +216,69 @@ const getEventFunc = function (event, gameObject, e) {
133
216
  });
134
217
  });
135
218
  };
219
+ /**
220
+ * 无障碍系统(A11y System)
221
+ *
222
+ * A11ySystem 管理游戏中所有无障碍组件,在游戏画布上方创建无障碍覆盖层。
223
+ * 它会自动将游戏对象的位置、尺寸同步到对应的 DOM 元素上,
224
+ * 并处理与屏幕阅读器的交互。
225
+ *
226
+ * 主要功能:
227
+ * - 创建和管理无障碍 DOM 覆盖层
228
+ * - 同步游戏对象的变换到 DOM 元素
229
+ * - 处理无障碍元素的事件绑定
230
+ * - 支持调试模式(可视化无障碍区域)
231
+ * - 自动检测或手动配置无障碍功能开关
232
+ *
233
+ * @example
234
+ * ```typescript
235
+ * // 自动检测系统读屏功能
236
+ * game.addSystem(new A11ySystem());
237
+ *
238
+ * // 开启调试模式
239
+ * game.addSystem(new A11ySystem({ debug: true }));
240
+ *
241
+ * // 强制启用无障碍功能
242
+ * game.addSystem(new A11ySystem({
243
+ * activate: A11yActivate.ENABLE,
244
+ * zIndex: 10000
245
+ * }));
246
+ * ```
247
+ */
136
248
  let A11ySystem = class A11ySystem extends System {
249
+ /**
250
+ * 构造无障碍系统
251
+ *
252
+ * @param opt - 系统配置选项
253
+ * @param opt.debug - 是否开启调试模式,默认 false
254
+ * @param opt.activate - 无障碍功能开关模式,默认 CHECK(自动检测)
255
+ * @param opt.delay - DOM 元素延迟创建时间(毫秒),默认 100
256
+ * @param opt.zIndex - 覆盖层的 z-index,默认 10000
257
+ * @param opt.checkA11yOpen - 自定义检测无障碍功能是否开启的函数
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * // 开启调试,无障碍区域会显示红色透明背景
262
+ * new A11ySystem({ debug: true })
263
+ *
264
+ * // 禁用无障碍功能
265
+ * new A11ySystem({ activate: A11yActivate.DISABLE })
266
+ *
267
+ * // 自定义检测逻辑
268
+ * new A11ySystem({
269
+ * checkA11yOpen: async () => {
270
+ * return await isScreenReaderActive();
271
+ * }
272
+ * })
273
+ * ```
274
+ */
137
275
  constructor(opt) {
138
276
  super(opt);
277
+ /** 无障碍 DOM 元素缓存 */
139
278
  this.cache = new Map();
279
+ /** 事件处理函数缓存 */
140
280
  this.eventCache = new Map();
281
+ /** 无障碍覆盖层的 z-index 值 */
141
282
  this.zIndex = ZINDEX;
142
283
  }
143
284
  get ratioX() {
@@ -195,8 +336,10 @@ let A11ySystem = class A11ySystem extends System {
195
336
  }
196
337
  if (!this.activate)
197
338
  return;
339
+ // 渲染出父容器
198
340
  const div = document.createElement('div');
199
341
  this.div = div;
342
+ // 如果存在父容器,则渲染这个 div,子元素则会相对这个 div 进行定位,否则直接相对于 body 进行定位
200
343
  if (this.game.canvas.parentNode) {
201
344
  this.game.canvas.parentNode.insertBefore(this.div, this.game.canvas);
202
345
  }
@@ -221,12 +364,14 @@ let A11ySystem = class A11ySystem extends System {
221
364
  return { renderWidth, renderHeight };
222
365
  }
223
366
  getCanvasBoundingClientRect() {
367
+ // 渲染画布相对于视口的实际宽高以及位置,实际的像素
224
368
  const { width, height, left, top } = this.game.canvas.getBoundingClientRect();
225
369
  return { width, height, left, top };
226
370
  }
227
371
  initDiv() {
228
372
  const { pageXOffset, pageYOffset } = window;
229
373
  const { width, height, left, top } = this.getCanvasBoundingClientRect();
374
+ // 父容器位置
230
375
  const style = {
231
376
  width,
232
377
  height,
@@ -238,6 +383,7 @@ let A11ySystem = class A11ySystem extends System {
238
383
  background: MaskBackground.NONE,
239
384
  };
240
385
  setStyle(this.div, style);
386
+ // 给父容器设置捕获事件,用于监听事件坐标
241
387
  this.div.addEventListener('click', e => {
242
388
  const currentTarget = e.currentTarget;
243
389
  const { left, top } = currentTarget.getBoundingClientRect();
@@ -246,6 +392,9 @@ let A11ySystem = class A11ySystem extends System {
246
392
  this.eventPosition = { x, y };
247
393
  }, true);
248
394
  }
395
+ /**
396
+ * 监听插件更新
397
+ */
249
398
  update() {
250
399
  return __awaiter(this, void 0, void 0, function* () {
251
400
  const changes = this.componentObserver.clear();
@@ -286,6 +435,10 @@ let A11ySystem = class A11ySystem extends System {
286
435
  element && this.div.removeChild(element);
287
436
  this.cache.delete(a11yId);
288
437
  }
438
+ /**
439
+ * 监听组件被添加至游戏对象
440
+ * @param changed 改变的组件
441
+ */
289
442
  add(changed) {
290
443
  if (!this.activate)
291
444
  return;
@@ -314,6 +467,7 @@ let A11ySystem = class A11ySystem extends System {
314
467
  }
315
468
  }, delay || this.delay);
316
469
  }
470
+ // 监听 scene 改变
317
471
  transformChange(changed) {
318
472
  const component = changed.component;
319
473
  const { gameObject } = changed;
@@ -322,16 +476,26 @@ let A11ySystem = class A11ySystem extends System {
322
476
  return;
323
477
  const { a11yId } = a11yComponent;
324
478
  if (!component.inScene) {
479
+ // 监听 scene 删除游戏对象
325
480
  const dom = this.div.querySelector(`#${a11yId}`);
326
481
  dom && this.div.removeChild(dom);
482
+ // this.cache.delete(a11yId)
327
483
  }
328
484
  else {
485
+ // 监听 scene add
486
+ // this.div.appendChild(this.cache)
329
487
  if (this.cache.has(a11yId)) {
330
488
  const addDom = this.cache.get(a11yId);
331
489
  addDom && this.div.appendChild(addDom);
332
490
  }
333
491
  }
334
492
  }
493
+ /**
494
+ * 为无障碍组件设置监听事件
495
+ * @param element DOM 元素
496
+ * @param event 事件组件对象
497
+ * @param gameObject 游戏对象
498
+ */
335
499
  setEvent(element, event, gameObject, id) {
336
500
  if (!event) {
337
501
  return;
@@ -363,12 +527,19 @@ let A11ySystem = class A11ySystem extends System {
363
527
  const element = this.cache.get(a11yId);
364
528
  element && element.removeEventListener('click', func);
365
529
  }
530
+ /**
531
+ * 设置无障碍属性标签
532
+ * @param element DOM 元素
533
+ * @param hint 无障碍朗读文字
534
+ * @param interactive 是否可交互
535
+ */
366
536
  setA11yAttr(element, component) {
367
537
  const { hint, props = {}, state = {}, role, a11yId: id } = component;
368
538
  const realRole = role || 'text';
369
539
  element.setAttribute('role', realRole);
370
540
  element.setAttribute('aria-label', hint);
371
541
  element.id = id;
542
+ // 这里兼容
372
543
  const ariaProps = Object.keys(props);
373
544
  for (const key of ariaProps) {
374
545
  element.setAttribute(key, props[key]);
@@ -383,8 +554,19 @@ let A11ySystem = class A11ySystem extends System {
383
554
  }
384
555
  }
385
556
  }
557
+ /**
558
+ * 将无障碍元素设置到对应的位置
559
+ * @param element DOM 元素
560
+ * @param transform 位置属性
561
+ */
386
562
  setPosition(element, transform) {
563
+ // 相对画布定位
564
+ // const { x: anchorX, y: anchorY } = transform.anchor
565
+ // 游戏对象的宽高
387
566
  const { width, height } = transform.size;
567
+ // position
568
+ // const { x: positionX, y: positionY } = transform.position
569
+ // 设置无障碍 DOM 的样式,龙骨动画默认 2px
388
570
  const style = {
389
571
  width: width === 0 ? 1 : width * this.ratioX,
390
572
  height: height === 0 ? 1 : height * this.ratioY,
@@ -394,6 +576,7 @@ let A11ySystem = class A11ySystem extends System {
394
576
  background: this.debug ? MaskBackground.DEBUG : MaskBackground.NONE,
395
577
  };
396
578
  setStyle(element, style);
579
+ // 调整 DOM 的位置
397
580
  setTransform(element, transform, this.ratioX, this.ratioY);
398
581
  }
399
582
  onDestroy() {
@@ -403,6 +586,7 @@ let A11ySystem = class A11ySystem extends System {
403
586
  this.div = null;
404
587
  }
405
588
  };
589
+ /** 系统名称 */
406
590
  A11ySystem.systemName = 'A11ySystem';
407
591
  A11ySystem = __decorate([
408
592
  decorators.componentObserver({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eva/plugin-a11y",
3
- "version": "2.0.1-beta.27",
3
+ "version": "2.0.1-beta.29",
4
4
  "description": "@eva/plugin-a11y",
5
5
  "main": "index.js",
6
6
  "module": "dist/plugin-a11y.esm.js",
@@ -18,9 +18,9 @@
18
18
  "license": "MIT",
19
19
  "homepage": "https://eva.js.org",
20
20
  "dependencies": {
21
- "@eva/eva.js": "2.0.1-beta.27",
21
+ "@eva/eva.js": "2.0.1-beta.29",
22
22
  "@eva/inspector-decorator": "^0.0.5",
23
- "@eva/plugin-renderer": "2.0.1-beta.27",
23
+ "@eva/plugin-renderer": "2.0.1-beta.29",
24
24
  "eventemitter3": "^3.1.2"
25
25
  }
26
26
  }