Skip to content

Commit ba6a0dc

Browse files
committed
feat(core): add optional setItems callback to listbox and combobox controller
1 parent 4132c4c commit ba6a0dc

File tree

2 files changed

+44
-10
lines changed

2 files changed

+44
-10
lines changed

core/pfe-core/controllers/combobox-controller.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,17 @@ export interface ComboboxControllerOptions<Item extends HTMLElement> extends
140140
* By default, toggles the `hidden` attribute on the item
141141
*/
142142
setItemHidden?(item: Item, hidden: boolean): void;
143+
/**
144+
* Optional. When provided, passed to ListboxController so it does not set
145+
* aria-setsize/aria-posinset on items.
146+
*/
147+
setItems?(items: Item[]): void;
148+
/**
149+
* Optional. Returns position-in-set and set size for the focused item when
150+
* building the Safari VoiceOver live-region announcement ("N of M").
151+
* When not provided, the controller reads aria-posinset/aria-setsize from the item.
152+
*/
153+
getItemPosition?(item: Item, items: Item[]): { posInSet: number; setSize: number } | null;
143154
}
144155

145156
/**
@@ -352,6 +363,7 @@ export class ComboboxController<
352363
getATFocusedItem: () => this.items[this.#fc?.atFocusedItemIndex ?? -1] ?? null,
353364
isItemDisabled: this.options.isItemDisabled,
354365
setItemSelected: this.options.setItemSelected,
366+
setItems: this.options.setItems,
355367
});
356368
ComboboxController.instances.set(host, this);
357369
ComboboxController.hosts.add(host);
@@ -551,11 +563,21 @@ export class ComboboxController<
551563
if (this.#lb.isSelected(item)) {
552564
text += `, (${this.#translate('selected', langKey)})`;
553565
}
554-
if (item.hasAttribute('aria-setsize') && item.hasAttribute('aria-posinset')) {
566+
const position =
567+
typeof this.options.getItemPosition === 'function' ?
568+
this.options.getItemPosition(item, this.items)
569+
: null;
570+
const posInSet =
571+
position?.posInSet
572+
?? (item.hasAttribute('aria-posinset') ? item.getAttribute('aria-posinset') : null);
573+
const setSize =
574+
position?.setSize
575+
?? (item.hasAttribute('aria-setsize') ? item.getAttribute('aria-setsize') : null);
576+
if (posInSet != null && setSize != null) {
555577
if (langKey === 'ja') {
556-
text += `, (${item.getAttribute('aria-setsize')} 件中 ${item.getAttribute('aria-posinset')} 件目)`;
578+
text += `, (${setSize} 件中 ${posInSet} 件目)`;
557579
} else {
558-
text += `, (${item.getAttribute('aria-posinset')} ${this.#translate('of', langKey)} ${item.getAttribute('aria-setsize')})`;
580+
text += `, (${posInSet} ${this.#translate('of', langKey)} ${setSize})`;
559581
}
560582
}
561583
ComboboxController.#alert.lang = lang;

core/pfe-core/controllers/listbox-controller.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ export interface ListboxControllerOptions<Item extends HTMLElement> {
5959
* a combobox input.
6060
*/
6161
getControlsElements?(): HTMLElement[];
62+
/**
63+
* Optional callback when items are set. When provided, the controller does **not**
64+
* set aria-setsize/aria-posinset on each item; the caller is responsible for list
65+
* semantics (e.g. via ElementInternals).
66+
*/
67+
setItems?(items: Item[]): void;
6268
}
6369

6470
/**
@@ -192,16 +198,22 @@ export class ListboxController<Item extends HTMLElement> implements ReactiveCont
192198
}
193199

194200
/**
195-
* register's the host's Item elements as listbox controller items
196-
* sets aria-setsize and aria-posinset on items
197-
* @param items items
201+
* Registers the host's Item elements as listbox controller items.
202+
* If options provides a setItems function, that function is called with the items.
203+
* Otherwise, sets aria-setsize and aria-posinset on each item.
204+
* @param items - The Item elements to register
198205
*/
199206
set items(items: Item[]) {
200207
this.#items = items;
201-
this.#items.forEach((item, index, _items) => {
202-
item.ariaSetSize = _items.length.toString();
203-
item.ariaPosInSet = (index + 1).toString();
204-
});
208+
const { setItems } = this.#options;
209+
if (typeof setItems === 'function') {
210+
setItems(items);
211+
} else {
212+
this.#items.forEach((item, index, _items) => {
213+
item.ariaSetSize = _items.length.toString();
214+
item.ariaPosInSet = (index + 1).toString();
215+
});
216+
}
205217
}
206218

207219
/**

0 commit comments

Comments
 (0)