Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Jetski Global Instructions & Context

## Rules
- **Skip Tests by Default**: Do not run unit or integration tests automatically after making code changes.
- **Run Tests on Demand**: Only run tests when explicitly prompted by the user (e.g., "run tests", "verify with tests").
- **Always allowed to run `git status`**: You can run `git status` at any time to check the state of the repository.
- **Git Commits**: Follow Angular's commit message format (`<type>(<scope>): <subject>`). Use imperative mood, lowercase subject, and no trailing period. Valid scopes are in `.ng-dev/commit-message.mts`. Scopes can be omitted for `test` or refactoring types. Use `multiple` for cross-component changes. Do not use `aria` as a standalone scope.
- **Git Pushing**: Always push branches to the `tjshiu` remote (e.g., `git push tjshiu <branch-name>`), as `origin` is not configured.

## Aliases
- **start-fresh**: Run `git stash push --include-untracked -m "work-in-progress"`, checkout `main`, pull `origin main`, checkout a new branch, and stash pop.
- **update-main**: Sync local main with remote using `git checkout main && git pull upstream main && git checkout -`.

5 changes: 4 additions & 1 deletion goldens/aria/grid/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ export class Grid {
readonly colWrap: _angular_core.InputSignal<"continuous" | "loop" | "nowrap">;
readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly element: HTMLElement;
readonly enableRangeSelection: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly enableSelection: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly focusMode: _angular_core.InputSignal<"roving" | "activedescendant">;
readonly multi: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly _pattern: GridPattern;
readonly rowWrap: _angular_core.InputSignal<"continuous" | "loop" | "nowrap">;
scrollActiveCellIntoView(options?: ScrollIntoViewOptions): void;
readonly selectionMode: _angular_core.InputSignal<"follow" | "explicit">;
readonly softDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly tabbable: _angular_core.InputSignal<boolean | undefined>;
readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>;
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Grid, "[ngGrid]", ["ngGrid"], { "enableSelection": { "alias": "enableSelection"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "rowWrap": { "alias": "rowWrap"; "required": false; "isSignal": true; }; "colWrap": { "alias": "colWrap"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; }, {}, ["_rows"], never, true, never>;
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Grid, "[ngGrid]", ["ngGrid"], { "enableSelection": { "alias": "enableSelection"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "rowWrap": { "alias": "rowWrap"; "required": false; "isSignal": true; }; "colWrap": { "alias": "colWrap"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "enableRangeSelection": { "alias": "enableRangeSelection"; "required": false; "isSignal": true; }; "tabbable": { "alias": "tabbable"; "required": false; "isSignal": true; }; }, {}, ["_rows"], never, true, never>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Grid, never>;
}
Expand Down
3 changes: 2 additions & 1 deletion goldens/aria/listbox/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ export class Listbox<V> {
scrollActiveItemIntoView(options?: ScrollIntoViewOptions): void;
readonly selectionMode: _angular_core.InputSignal<"follow" | "explicit">;
readonly softDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
tabbable: _angular_core.InputSignalWithTransform<boolean, unknown>;
protected readonly textDirection: _angular_core.Signal<_angular_cdk_bidi.Direction>;
readonly typeaheadDelay: _angular_core.InputSignal<number>;
readonly value: _angular_core.ModelSignal<V[]>;
readonly wrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Listbox<any>, "[ngListbox]", ["ngListbox"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, ["_options"], never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>;
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Listbox<any>, "[ngListbox]", ["ngListbox"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "tabbable": { "alias": "tabbable"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, ["_options"], never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Listbox<any>, never>;
}
Expand Down
64 changes: 64 additions & 0 deletions goldens/aria/private/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,70 @@ export function signal<T>(initialValue: T): WritableSignalLike<T>;
// @public (undocumented)
export type SignalLike<T> = () => T;

// @public
export interface SimpleComboboxInputs extends ExpansionItem {
alwaysExpanded: SignalLike<boolean>;
disabled: SignalLike<boolean>;
element: SignalLike<HTMLElement>;
inlineSuggestion: SignalLike<string | undefined>;
popup: SignalLike<SimpleComboboxPopupPattern | undefined>;
softDisabled: SignalLike<boolean>;
value: WritableSignalLike<string>;
}

// @public
export class SimpleComboboxPattern {
constructor(inputs: SimpleComboboxInputs);
readonly activeDescendant: _angular_core.Signal<string | undefined>;
readonly autocomplete: _angular_core.Signal<"none" | "inline" | "list" | "both">;
click: _angular_core.Signal<ClickEventManager<PointerEvent>>;
closePopupOnBlurEffect(): void;
readonly disabled: () => boolean;
readonly element: () => HTMLElement;
readonly expanded: WritableSignalLike<boolean>;
highlightEffect(): void;
readonly inlineSuggestion: () => string | undefined;
// (undocumented)
readonly inputs: SimpleComboboxInputs;
readonly isDeleting: _angular_core.WritableSignal<boolean>;
readonly isEditable: _angular_core.Signal<boolean>;
readonly isFocused: _angular_core.WritableSignal<boolean>;
readonly keyboardEventRelay: _angular_core.WritableSignal<KeyboardEvent | undefined>;
keyboardEventRelayEffect(): void;
keydown: _angular_core.Signal<KeyboardEventManager<KeyboardEvent>>;
onClick(event: PointerEvent): void;
onFocusin(): void;
onFocusout(event: FocusEvent): void;
onInput(event: Event): void;
onKeydown(event: KeyboardEvent): void;
readonly popupId: _angular_core.Signal<string | undefined>;
readonly popupType: _angular_core.Signal<"listbox" | "tree" | "grid" | "dialog" | undefined>;
readonly softDisabled: () => boolean;
readonly value: WritableSignalLike<string>;
}

// @public
export interface SimpleComboboxPopupInputs {
activeDescendant: SignalLike<string | undefined>;
controlTarget: SignalLike<HTMLElement | undefined>;
popupId: SignalLike<string | undefined>;
popupType: SignalLike<'listbox' | 'tree' | 'grid' | 'dialog'>;
}

// @public
export class SimpleComboboxPopupPattern {
constructor(inputs: SimpleComboboxPopupInputs);
readonly activeDescendant: () => string | undefined;
readonly controlTarget: () => HTMLElement | undefined;
// (undocumented)
readonly inputs: SimpleComboboxPopupInputs;
readonly isFocused: _angular_core.WritableSignal<boolean>;
onFocusin(): void;
onFocusout(event: FocusEvent): void;
readonly popupId: () => string | undefined;
readonly popupType: () => "listbox" | "tree" | "grid" | "dialog";
}

// @public
export function sortDirectives(a: HasElement, b: HasElement): 1 | -1;

Expand Down
78 changes: 78 additions & 0 deletions goldens/aria/simple-combobox/index.api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
## API Report File for "@angular/aria_simple-combobox"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import * as _angular_core from '@angular/core';
import { DeferredContentAware } from '@angular/aria/private';
import * as i1 from '@angular/aria/private';
import { OnDestroy } from '@angular/core';
import { OnInit } from '@angular/core';
import { SimpleComboboxPattern } from '@angular/aria/private';
import { SimpleComboboxPopupPattern } from '@angular/aria/private';

// @public
export class Combobox extends DeferredContentAware implements OnInit {
constructor();
readonly alwaysExpanded: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly element: HTMLElement;
readonly expanded: _angular_core.ModelSignal<boolean>;
readonly inlineSuggestion: _angular_core.InputSignal<string | undefined>;
// (undocumented)
ngOnInit(): void;
readonly _pattern: SimpleComboboxPattern;
readonly _popup: _angular_core.WritableSignal<ComboboxPopup | undefined>;
_registerPopup(popup: ComboboxPopup): void;
readonly softDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
_unregisterPopup(): void;
readonly value: _angular_core.ModelSignal<string>;
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Combobox, "[ngCombobox]", ["ngCombobox"], { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "alwaysExpanded": { "alias": "alwaysExpanded"; "required": false; "isSignal": true; }; "expanded": { "alias": "expanded"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "inlineSuggestion": { "alias": "inlineSuggestion"; "required": false; "isSignal": true; }; }, { "expanded": "expandedChange"; "value": "valueChange"; }, never, never, true, never>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Combobox, never>;
}

// @public
export class ComboboxPopup implements OnInit, OnDestroy {
readonly activeDescendant: _angular_core.Signal<string | undefined>;
readonly combobox: _angular_core.InputSignal<Combobox>;
readonly controlTarget: _angular_core.Signal<HTMLElement | undefined>;
// (undocumented)
ngOnDestroy(): void;
// (undocumented)
ngOnInit(): void;
readonly _pattern: SimpleComboboxPopupPattern;
readonly popupId: _angular_core.Signal<string | undefined>;
readonly popupType: _angular_core.InputSignal<"listbox" | "tree" | "grid" | "dialog">;
_registerWidget(widget: ComboboxWidget): void;
_unregisterWidget(): void;
readonly _widget: _angular_core.WritableSignal<ComboboxWidget | undefined>;
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<ComboboxPopup, "ng-template[ngComboboxPopup]", ["ngComboboxPopup"], { "combobox": { "alias": "combobox"; "required": true; "isSignal": true; }; "popupType": { "alias": "popupType"; "required": false; "isSignal": true; }; }, {}, never, never, true, [{ directive: typeof i1.DeferredContent; inputs: {}; outputs: {}; }]>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ComboboxPopup, never>;
}

// @public
export class ComboboxWidget implements OnInit, OnDestroy {
constructor();
readonly activeDescendant: _angular_core.WritableSignal<string | undefined>;
readonly element: HTMLElement;
// (undocumented)
ngOnDestroy(): void;
// (undocumented)
ngOnInit(): void;
onFocusin(): void;
onFocusout(event: FocusEvent): void;
readonly popupId: _angular_core.WritableSignal<string | undefined>;
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<ComboboxWidget, "[ngComboboxWidget]", ["ngComboboxWidget"], {}, {}, never, never, true, never>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ComboboxWidget, never>;
}

// (No @packageDocumentation comment for this package)

```
3 changes: 2 additions & 1 deletion goldens/aria/tree/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ export class Tree<V> {
scrollActiveItemIntoView(options?: ScrollIntoViewOptions): void;
readonly selectionMode: _angular_core.InputSignal<"follow" | "explicit">;
readonly softDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly tabbable: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>;
readonly typeaheadDelay: _angular_core.InputSignal<number>;
// (undocumented)
_unregister(child: TreeItem<V>): void;
readonly value: _angular_core.ModelSignal<V[]>;
readonly wrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Tree<any>, "[ngTree]", ["ngTree"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "nav": { "alias": "nav"; "required": false; "isSignal": true; }; "currentType": { "alias": "currentType"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>;
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Tree<any>, "[ngTree]", ["ngTree"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "tabbable": { "alias": "tabbable"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "nav": { "alias": "nav"; "required": false; "isSignal": true; }; "currentType": { "alias": "currentType"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Tree<any>, never>;
}
Expand Down
1 change: 1 addition & 0 deletions src/aria/config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ARIA_ENTRYPOINTS = [
"listbox/testing",
"menu",
"menu/testing",
"simple-combobox",
"tabs",
"tabs/testing",
"toolbar",
Expand Down
11 changes: 11 additions & 0 deletions src/aria/grid/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ export class Grid {
*/
readonly selectionMode = input<'follow' | 'explicit'>('follow');

/** Whether enable range selections (with modifier keys or dragging). */
readonly enableRangeSelection = input(false, {transform: booleanAttribute});

/** Whether the grid is tabbable. */
readonly tabbable = input<boolean | undefined>(undefined);

/** The UI pattern for the grid. */
readonly _pattern = new GridPattern({
...this,
Expand All @@ -136,6 +142,11 @@ export class Grid {
afterRenderEffect(() => this._pattern.focusEffect());
}

/** Scrolls the active cell into view. */
scrollActiveCellIntoView(options: ScrollIntoViewOptions = {block: 'nearest'}) {
this._pattern.activeCell()?.element().scrollIntoView(options);
}

/** Gets the cell pattern for a given element. */
private _getCell(element: Element | null | undefined): GridCellPattern | undefined {
let target = element;
Expand Down
8 changes: 4 additions & 4 deletions src/aria/listbox/listbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ describe('Listbox', () => {
expect(listboxElement.getAttribute('aria-multiselectable')).toBe('false');
});

it('should set aria-selected to "false" for all options by default', () => {
optionElements.forEach(optionElement => {
expect(optionElement.getAttribute('aria-selected')).toBe('false');
});
it('should set aria-selected to "true" for the first option and "false" for others by default', () => {
expect(optionElements[0].getAttribute('aria-selected')).toBe('true');
expect(optionElements[1].getAttribute('aria-selected')).toBe('false');
expect(optionElements[2].getAttribute('aria-selected')).toBe('false');
});
});

Expand Down
4 changes: 4 additions & 0 deletions src/aria/listbox/listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ export class Listbox<V> {
/** Whether the listbox is readonly. */
readonly readonly = input(false, {transform: booleanAttribute});

/** Whether the list is tabbable. */
tabbable = input(true, {transform: booleanAttribute});

/** The values of the currently selected items. */
readonly value = model<V[]>([]);

Expand All @@ -146,6 +149,7 @@ export class Listbox<V> {
items: this.items,
activeItem: signal(undefined),
textDirection: this.textDirection,
tabbable: this.tabbable,
element: () => this._elementRef.nativeElement,
combobox: () => this._popup?.combobox?._pattern,
};
Expand Down
1 change: 1 addition & 0 deletions src/aria/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ts_project(
"//src/aria/private/grid",
"//src/aria/private/listbox",
"//src/aria/private/menu",
"//src/aria/private/simple-combobox",
"//src/aria/private/tabs",
"//src/aria/private/toolbar",
"//src/aria/private/tree",
Expand Down
Loading
Loading