@vectojs/core 1.6.1 → 1.6.2

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.
package/README.md CHANGED
@@ -112,6 +112,11 @@ This projection is intentionally thin. Applications still own accessible names,
112
112
  focus order, contrast, and correct control semantics. See the
113
113
  [accessibility guide](https://vectojs.org/learn/accessibility/).
114
114
 
115
+ Non-control workspaces that own keyboard shortcuts can opt into focus order
116
+ explicitly: return `{ role: 'region', label: 'Canvas workspace', tabIndex: 0 }`.
117
+ The Scene refreshes the projected `tabindex` when attributes change. Keep native
118
+ text inputs and editable content in charge of their own editing shortcuts.
119
+
115
120
  Interactive projected nodes capture the active pointer on `pointerdown` and route
116
121
  `pointermove`, `pointerup`, and `pointercancel` through normal VMT capture/bubble propagation.
117
122
  Treat `pointercancel` as rollback: discard transient gesture state and do not create durable
package/dist/index.js CHANGED
@@ -297,6 +297,20 @@ var ComputeParticleEntity = (_class = class extends _chunkDFNK6OV6js.Entity {
297
297
  }, _class);
298
298
 
299
299
  // src/tree/Scene.ts
300
+ var INTERACTIVE_A11Y_ROLES = /* @__PURE__ */ new Set([
301
+ "button",
302
+ "switch",
303
+ "checkbox",
304
+ "radio",
305
+ "link",
306
+ "tab",
307
+ "menuitem",
308
+ "slider",
309
+ "combobox"
310
+ ]);
311
+ function isNativelyFocusable(element) {
312
+ return element instanceof HTMLButtonElement || element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement || element instanceof HTMLAnchorElement && element.hasAttribute("href");
313
+ }
300
314
  var REDUCED_MOTION_FPS = 30;
301
315
  function parseInlinePx(value) {
302
316
  if (!value || !value.endsWith("px")) return null;
@@ -910,20 +924,7 @@ var Scene = (_class2 = class _Scene {
910
924
  }
911
925
  node.emit("blur", {});
912
926
  });
913
- const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
914
- "button",
915
- "switch",
916
- "checkbox",
917
- "radio",
918
- "link",
919
- "tab",
920
- "menuitem",
921
- "slider",
922
- "combobox"
923
- ]);
924
- const nativelyFocusable = el instanceof HTMLButtonElement || el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement || el instanceof HTMLAnchorElement && el.hasAttribute("href");
925
- if (!nativelyFocusable && attrs.role && INTERACTIVE_ROLES.has(attrs.role)) {
926
- el.setAttribute("tabindex", "0");
927
+ if (!isNativelyFocusable(el) && attrs.role && INTERACTIVE_A11Y_ROLES.has(attrs.role)) {
927
928
  el.addEventListener("keydown", (e) => {
928
929
  if (e.key === "Enter" || e.key === " ") {
929
930
  e.preventDefault();
@@ -945,6 +946,13 @@ var Scene = (_class2 = class _Scene {
945
946
  if (attrs.label !== void 0 && el.getAttribute("aria-label") !== attrs.label) {
946
947
  el.setAttribute("aria-label", attrs.label);
947
948
  }
949
+ const implicitTabIndex = !isNativelyFocusable(el) && attrs.role && INTERACTIVE_A11Y_ROLES.has(attrs.role) ? 0 : null;
950
+ const desiredTabIndex = _nullishCoalesce(attrs.tabIndex, () => ( implicitTabIndex));
951
+ if (desiredTabIndex === null) {
952
+ if (el.hasAttribute("tabindex")) el.removeAttribute("tabindex");
953
+ } else if (el.getAttribute("tabindex") !== String(desiredTabIndex)) {
954
+ el.setAttribute("tabindex", String(desiredTabIndex));
955
+ }
948
956
  if (attrs.inputType !== void 0 && el.getAttribute("type") !== attrs.inputType) {
949
957
  el.setAttribute("type", attrs.inputType);
950
958
  }
package/dist/index.mjs CHANGED
@@ -297,6 +297,20 @@ var ComputeParticleEntity = class extends Entity {
297
297
  };
298
298
 
299
299
  // src/tree/Scene.ts
300
+ var INTERACTIVE_A11Y_ROLES = /* @__PURE__ */ new Set([
301
+ "button",
302
+ "switch",
303
+ "checkbox",
304
+ "radio",
305
+ "link",
306
+ "tab",
307
+ "menuitem",
308
+ "slider",
309
+ "combobox"
310
+ ]);
311
+ function isNativelyFocusable(element) {
312
+ return element instanceof HTMLButtonElement || element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement || element instanceof HTMLAnchorElement && element.hasAttribute("href");
313
+ }
300
314
  var REDUCED_MOTION_FPS = 30;
301
315
  function parseInlinePx(value) {
302
316
  if (!value || !value.endsWith("px")) return null;
@@ -910,20 +924,7 @@ var Scene = class _Scene {
910
924
  }
911
925
  node.emit("blur", {});
912
926
  });
913
- const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
914
- "button",
915
- "switch",
916
- "checkbox",
917
- "radio",
918
- "link",
919
- "tab",
920
- "menuitem",
921
- "slider",
922
- "combobox"
923
- ]);
924
- const nativelyFocusable = el instanceof HTMLButtonElement || el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement || el instanceof HTMLAnchorElement && el.hasAttribute("href");
925
- if (!nativelyFocusable && attrs.role && INTERACTIVE_ROLES.has(attrs.role)) {
926
- el.setAttribute("tabindex", "0");
927
+ if (!isNativelyFocusable(el) && attrs.role && INTERACTIVE_A11Y_ROLES.has(attrs.role)) {
927
928
  el.addEventListener("keydown", (e) => {
928
929
  if (e.key === "Enter" || e.key === " ") {
929
930
  e.preventDefault();
@@ -945,6 +946,13 @@ var Scene = class _Scene {
945
946
  if (attrs.label !== void 0 && el.getAttribute("aria-label") !== attrs.label) {
946
947
  el.setAttribute("aria-label", attrs.label);
947
948
  }
949
+ const implicitTabIndex = !isNativelyFocusable(el) && attrs.role && INTERACTIVE_A11Y_ROLES.has(attrs.role) ? 0 : null;
950
+ const desiredTabIndex = attrs.tabIndex ?? implicitTabIndex;
951
+ if (desiredTabIndex === null) {
952
+ if (el.hasAttribute("tabindex")) el.removeAttribute("tabindex");
953
+ } else if (el.getAttribute("tabindex") !== String(desiredTabIndex)) {
954
+ el.setAttribute("tabindex", String(desiredTabIndex));
955
+ }
948
956
  if (attrs.inputType !== void 0 && el.getAttribute("type") !== attrs.inputType) {
949
957
  el.setAttribute("type", attrs.inputType);
950
958
  }
@@ -129,6 +129,8 @@ export interface A11yAttributes {
129
129
  role?: string;
130
130
  /** Accessible name applied via `aria-label`. */
131
131
  label?: string;
132
+ /** Explicit keyboard tab order for projected non-native interaction regions. */
133
+ tabIndex?: number;
132
134
  /** Destination URL; only meaningful for `tag: 'a'`. */
133
135
  href?: string;
134
136
  /** Link target; only meaningful for `tag: 'a'`. Defaults to current window. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/core",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },