Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

aria-voyager

Package Overview
Dependencies
Maintainers
0
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aria-voyager - npm Package Compare versions

Comparing version 0.0.4 to 0.1.0

44

dist/index.d.ts

@@ -8,2 +8,3 @@ interface EmitterOptions<T> {

itemActivated(item?: Item): void;
dispose?(): void;
}

@@ -32,3 +33,3 @@

setControl(control: Control): void;
teardown?(): void;
dispose?(): void;
}

@@ -45,5 +46,7 @@

}
type Orientation = 'horizontal' | 'vertical';
interface Options {
multiple: boolean;
disabled: boolean;
orientation: Orientation;
}

@@ -73,2 +76,3 @@ type Item = HTMLElement;

protected registerNavigationPatterns(patterns: NavigationPattern[]): void;
dispose(): void;
private handleEvent;

@@ -138,2 +142,33 @@ readOptions(): void;

interface SelectionBehavior {
/**
* Selection behavior:
*
* - `automatic`: active item becomes selected item
* - `manual`: user must select manually with spacebar
*
* @defaultValue `automatic`
*/
singleSelection?: 'automatic' | 'manual';
}
type TablistBehavior = SelectionBehavior;
interface TablistOptions {
updater?: UpdateStrategy;
emitter?: EmitStrategy;
behavior?: TablistBehavior;
}
declare class Tablist extends Control {
#private;
protected focusStrategy: RovingTabindexStrategy;
get selection(): HTMLElement[];
get activeItem(): HTMLElement | undefined;
get prevActiveItem(): HTMLElement | undefined;
constructor(element: HTMLElement, options?: TablistOptions);
readItems(): void;
readSelection(): void;
readOptions(): void;
ensureSelection(): void;
}
declare class IndexEmitStrategy implements EmitStrategy {

@@ -145,2 +180,3 @@ private control;

itemActivated(item: Item): void | undefined;
dispose(): void;
}

@@ -154,2 +190,3 @@

itemActivated(item: Item): void | undefined;
dispose(): void;
}

@@ -163,3 +200,3 @@

setControl(control: Control): void;
teardown(): void;
dispose(): void;
}

@@ -174,4 +211,5 @@

updateOptions(): void;
dispose(): void;
}
export { Control, DomObserverUpdateStrategy, type EmitStrategy, IndexEmitStrategy, ItemEmitStrategy, Listbox, Menu, ReactiveUpdateStrategy, type UpdateStrategy };
export { Control, DomObserverUpdateStrategy, type EmitStrategy, IndexEmitStrategy, ItemEmitStrategy, Listbox, Menu, type Orientation, ReactiveUpdateStrategy, Tablist, type TablistBehavior, type TablistOptions, type UpdateStrategy };

@@ -25,3 +25,3 @@ // src/controls/-utils.ts

}
const changedItems = changes.every((change) => change.type === "childList");
const changedItems = changes.some((change) => change.type === "childList");
const itemAttributes = ["aria-disabled"];

@@ -35,3 +35,5 @@ const changedItemAttributes = changes.some(

const optionAttributes = [...this.control.optionAttributes, "aria-disabled"];
const changedOptions = changes.length === 1 && changes[0].type === "attributes" && optionAttributes.includes(changes[0].attributeName);
const changedOptions = changes.some(
(change) => change.target === this.control.element && change.type === "attributes" && optionAttributes.includes(change.attributeName)
);
if (changedOptions) {

@@ -62,3 +64,4 @@ this.control.readOptions();

}
teardown() {
dispose() {
this.control = void 0;
this.observer.disconnect();

@@ -106,3 +109,4 @@ }

multiple: false,
disabled: false
disabled: false,
orientation: "horizontal"
};

@@ -120,6 +124,7 @@ navigationPatterns = [];

setEmitStrategy(emitter) {
this.emitter?.dispose?.();
this.emitter = emitter;
}
setUpdateStrategy(updater) {
this.updater.teardown?.();
this.updater.dispose?.();
this.updater = updater;

@@ -134,2 +139,10 @@ }

}
dispose() {
this.updater.dispose?.();
this.emitter?.dispose?.();
const eventNames = new Set(this.navigationPatterns.map((p) => p.eventListeners ?? []).flat());
for (const eventName of eventNames) {
this.element.removeEventListener(eventName, this.handleEvent.bind(this));
}
}
handleEvent(event) {

@@ -148,2 +161,3 @@ if (this.options.disabled) {

this.options.disabled = this.element.hasAttribute("aria-disabled") && this.element.getAttribute("aria-disabled") === "true" || false;
this.options.orientation = this.element.hasAttribute("aria-orientation") ? this.element.getAttribute("aria-orientation") : "horizontal";
}

@@ -290,3 +304,3 @@ readItems() {

const { event } = bag;
const item = asItemOf(event.target, this.control);
const item = event.composedPath().find((elem) => asItemOf(elem, this.control));
return {

@@ -391,5 +405,12 @@ ...bag,

import isEqual from "lodash.isequal";
var DEFAULT_BEHAVIOR = {
singleSelection: "automatic"
};
var SelectionStrategy = class {
constructor(control) {
constructor(control, behavior) {
this.control = control;
this.behavior = {
...DEFAULT_BEHAVIOR,
...behavior ?? {}
};
this.readSelection();

@@ -403,2 +424,3 @@ }

shiftItem;
behavior;
matches(event) {

@@ -419,3 +441,3 @@ return this.control.items.length > 0 && this.eventListeners.includes(event.type);

} else if (event instanceof KeyboardEvent) {
this.handleKeyboard(bag);
this.handleKeyboard(event, bag.item);
} else if (event.type === "change") {

@@ -459,4 +481,3 @@ this.handleChange();

}
handleKeyboard(bag) {
const { event, item } = bag;
handleKeyboard(event, item) {
if (event.type === "keydown") {

@@ -466,2 +487,3 @@ if (item) {

}
this.handleKeys(event);
this.handleKeyCombinations(event);

@@ -478,5 +500,13 @@ } else if (event.type === "keyup" && !event.shiftKey) {

} else {
this.selectSingle(item);
if (this.behavior.singleSelection === "automatic" || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
this.behavior.singleSelection === "manual" && event.key === " ") {
this.selectSingle(item);
}
}
}
handleKeys(event) {
if (this.behavior.singleSelection === "manual" && event.key === " " && this.control.activeItem) {
this.selectSingle(this.control.activeItem);
}
}
/**

@@ -852,2 +882,65 @@ * Handles special keyboard control cases, such as handling the spacebar key

// src/controls/tablist.ts
var Tablist = class extends Control {
#selectionStrategy;
focusStrategy = new RovingTabindexStrategy(this);
#nextNavigation = new NextNavigation(this, "ArrowRight");
#prevNavigation = new PreviousNavigation(this, "ArrowLeft");
get selection() {
return this.#selectionStrategy.selection;
}
get activeItem() {
return this.focusStrategy.activeItem;
}
get prevActiveItem() {
return this.focusStrategy.prevActiveItem;
}
constructor(element, options) {
super(element, {
capabilities: {
singleSelection: true,
multiSelection: false
},
optionAttributes: ["aria-orientation"],
...options
});
this.#selectionStrategy = new SelectionStrategy(this, options?.behavior ?? {});
this.registerNavigationPatterns([
this.#nextNavigation,
this.#prevNavigation,
new HomeNavigation(this),
new EndNavigation(this),
new PointerNavigation(this),
this.focusStrategy,
this.#selectionStrategy
]);
element.role = "tablist";
this.readOptions();
this.readItems();
}
readItems() {
this.items = [...this.element.querySelectorAll('[role="tab"]')];
this.#selectionStrategy.select(
this.selection.filter((selection) => this.items.includes(selection))
);
this.focusStrategy.updateItems();
this.ensureSelection();
}
readSelection() {
this.#selectionStrategy.readSelection();
}
readOptions() {
super.readOptions();
this.#nextNavigation.keyOrKeys = this.options.orientation === "horizontal" ? "ArrowRight" : "ArrowDown";
this.#prevNavigation.keyOrKeys = this.options.orientation === "horizontal" ? "ArrowLeft" : "ArrowUp";
this.focusStrategy.updateItems();
}
ensureSelection() {
if (this.selection.length === 0 && this.items.length > 0) {
this.focusStrategy.activateItem(this.items[0]);
this.#selectionStrategy.select([this.items[0]]);
}
}
};
// src/emit-strategies/index-emit-strategy.ts

@@ -858,2 +951,3 @@ var IndexEmitStrategy = class {

this.options = options;
this.control = control;
this.control.setEmitStrategy(this);

@@ -869,2 +963,5 @@ }

}
dispose() {
this.control = void 0;
}
};

@@ -877,2 +974,3 @@

this.options = options;
this.control = control;
this.control.setEmitStrategy(this);

@@ -886,2 +984,5 @@ }

}
dispose() {
this.control = void 0;
}
};

@@ -906,2 +1007,5 @@

}
dispose() {
this.control = void 0;
}
};

@@ -915,3 +1019,4 @@ export {

Menu,
ReactiveUpdateStrategy
ReactiveUpdateStrategy,
Tablist
};

29

package.json
{
"name": "aria-voyager",
"version": "0.0.4",
"version": "0.1.0",
"description": "A framework agnostic / universal package that implements navigation patterns for various aria roles and features",

@@ -29,6 +29,6 @@ "author": "gossi",

"@gossi/config-prettier": "0.9.1",
"@hokulea/core": "^0.1.2",
"@hokulea/core": "^0.2.0",
"@hokulea/theme-moana": "^0.0.3",
"@swc/cli": "0.4.0",
"@swc/core": "1.7.40",
"@swc/cli": "0.5.2",
"@swc/core": "1.10.0",
"@testing-library/dom": "10.4.0",

@@ -39,13 +39,14 @@ "@types/css-modules": "1.0.5",

"@types/uuid": "10.0.0",
"@vitest/browser": "^2.1.3",
"@vitest/coverage-istanbul": "^2.1.3",
"@vitest/ui": "^2.1.3",
"concurrently": "9.0.1",
"@vitest/browser": "^2.1.8",
"@vitest/coverage-istanbul": "^2.1.8",
"@vitest/ui": "^2.1.8",
"concurrently": "9.1.0",
"eslint": "8.57.1",
"prettier": "3.3.3",
"playwright": "^1.49.0",
"prettier": "3.4.2",
"tsup": "8.3.5",
"typescript": "5.6.3",
"vite": "5.4.10",
"vitest": "^2.1.3",
"webdriverio": "^9.2.1"
"typescript": "5.7.2",
"vite": "^6.0.3",
"vitest": "^2.1.8",
"webdriverio": "^9.4.1"
},

@@ -72,5 +73,5 @@ "engines": {

"lint:types": "tsc --noEmit",
"test": "vitest run --browser.headless --browser.provider=webdriverio",
"test": "vitest run --browser.headless --browser.provider=playwright",
"test:ui": "vitest"
}
}

@@ -107,2 +107,53 @@ # aria-voyager

#### `Tablist`
Bring your own markup in at first, here is an example markup for a list:
```html
<div>
<ul role="tablist">
<li role="tab" id="tab-1" aria-controls="panel-1">Tab 1</li>
<li role="tab" id="tab-2" aria-controls="panel-2">Tab 2</li>
<li role="tab" id="tab-3" aria-controls="panel-3">Tab 3</li>
</ul>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
Contents Panel 1
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2">
Contents Panel 2
</div>
<div role="tabpanel" id="panel-3" aria-labelledby="tab-3">
Contents Panel 3
</div>
<div>
```
To make it interactive, create a new `Tablist` instance pointing it at your `tablist` element.
```ts
import { Tablist } from 'aria-voyager';
const tablistElement = document.querySelector('[role="tablist"]');
new Tablist(tablistElement);
```
That is already enough to start making your listbox interactive. It will read the options from the provided HTML.
`Tablist` accepts options as second parameter:
```ts
import type { EmitStrategy, UpdateStrategy, TablistBehavior } from 'aria-voyager';
interface TablistOptions {
updater?: UpdateStrategy;
emitter?: EmitStrategy;
behavior?: TablistBehavior;
}
```
See [updater](#updater) and [emitter](#emitter).
### Strategies

@@ -109,0 +160,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc