diff --git a/src/cdk-experimental/ui-patterns/behaviors/event-manager/BUILD.bazel b/src/cdk-experimental/ui-patterns/behaviors/event-manager/BUILD.bazel index df8008162f4b..563b0ff1ba15 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/event-manager/BUILD.bazel +++ b/src/cdk-experimental/ui-patterns/behaviors/event-manager/BUILD.bazel @@ -8,5 +8,7 @@ ts_library( ["**/*.ts"], exclude = ["**/*.spec.ts"], ), - deps = ["@npm//@angular/core"], + deps = [ + "//src/cdk-experimental/ui-patterns/behaviors/signal-like", + ], ) diff --git a/src/cdk-experimental/ui-patterns/behaviors/event-manager/keyboard-event-manager.ts b/src/cdk-experimental/ui-patterns/behaviors/event-manager/keyboard-event-manager.ts index f3deafb8a240..77d3bb8cce6d 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/event-manager/keyboard-event-manager.ts +++ b/src/cdk-experimental/ui-patterns/behaviors/event-manager/keyboard-event-manager.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Signal} from '@angular/core'; +import {SignalLike} from '../signal-like/signal-like'; import { EventHandler, EventHandlerOptions, @@ -20,9 +20,9 @@ import { * Used to represent a keycode. * * This is used to match whether an events keycode should be handled. The ability to match using a - * string, Signal, or Regexp gives us more flexibility when authoring event handlers. + * string, SignalLike, or Regexp gives us more flexibility when authoring event handlers. */ -type KeyCode = string | Signal | RegExp; +type KeyCode = string | SignalLike | RegExp; /** * An event manager that is specialized for handling keyboard events. By default this manager stops diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-focus/BUILD.bazel b/src/cdk-experimental/ui-patterns/behaviors/list-focus/BUILD.bazel index e0820a382473..37bda0ab9d97 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-focus/BUILD.bazel +++ b/src/cdk-experimental/ui-patterns/behaviors/list-focus/BUILD.bazel @@ -10,7 +10,7 @@ ts_library( ), deps = [ "//src/cdk-experimental/ui-patterns/behaviors/list-navigation", - "@npm//@angular/core", + "//src/cdk-experimental/ui-patterns/behaviors/signal-like", ], ) @@ -20,6 +20,7 @@ ng_test_library( deps = [ ":list-focus", "//src/cdk-experimental/ui-patterns/behaviors/list-navigation", + "//src/cdk-experimental/ui-patterns/behaviors/signal-like", ], ) diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-focus/list-focus.spec.ts b/src/cdk-experimental/ui-patterns/behaviors/list-focus/list-focus.spec.ts index 40dcdccf6185..79c065e78ccd 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-focus/list-focus.spec.ts +++ b/src/cdk-experimental/ui-patterns/behaviors/list-focus/list-focus.spec.ts @@ -6,16 +6,17 @@ * found in the LICENSE file at https://angular.dev/license */ -import {computed, Signal, signal} from '@angular/core'; +import {computed, signal} from '@angular/core'; +import {SignalLike} from '../signal-like/signal-like'; import {ListNavigation, ListNavigationInputs} from '../list-navigation/list-navigation'; import {ListFocus, ListFocusInputs, ListFocusItem} from './list-focus'; describe('List Focus', () => { interface TestItem extends ListFocusItem { - tabindex: Signal<-1 | 0>; + tabindex: SignalLike<-1 | 0>; } - function getItems(length: number): Signal { + function getItems(length: number): SignalLike { return signal( Array.from({length}).map((_, i) => ({ index: signal(i), @@ -28,7 +29,7 @@ describe('List Focus', () => { } function getNavigation( - items: Signal, + items: SignalLike, args: Partial> = {}, ): ListNavigation { return new ListNavigation({ diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-focus/list-focus.ts b/src/cdk-experimental/ui-patterns/behaviors/list-focus/list-focus.ts index eba11d1316e4..9807a26f6ba2 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-focus/list-focus.ts +++ b/src/cdk-experimental/ui-patterns/behaviors/list-focus/list-focus.ts @@ -6,22 +6,22 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Signal} from '@angular/core'; +import {SignalLike} from '../signal-like/signal-like'; import {ListNavigation, ListNavigationItem} from '../list-navigation/list-navigation'; /** Represents an item in a collection, such as a listbox option, than may receive focus. */ export interface ListFocusItem extends ListNavigationItem { /** A unique identifier for the item. */ - id: Signal; + id: SignalLike; /** The html element that should receive focus. */ - element: Signal; + element: SignalLike; } /** Represents the required inputs for a collection that contains focusable items. */ export interface ListFocusInputs { /** The focus strategy used by the list. */ - focusMode: Signal<'roving' | 'activedescendant'>; + focusMode: SignalLike<'roving' | 'activedescendant'>; } /** Controls focus for a list of items. */ diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-navigation/BUILD.bazel b/src/cdk-experimental/ui-patterns/behaviors/list-navigation/BUILD.bazel index b81395f2c2ce..9bf541fbe517 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-navigation/BUILD.bazel +++ b/src/cdk-experimental/ui-patterns/behaviors/list-navigation/BUILD.bazel @@ -8,13 +8,19 @@ ts_library( ["**/*.ts"], exclude = ["**/*.spec.ts"], ), - deps = ["@npm//@angular/core"], + deps = [ + "//src/cdk-experimental/ui-patterns/behaviors/signal-like", + "@npm//@angular/core", + ], ) ng_test_library( name = "unit_test_sources", srcs = glob(["**/*.spec.ts"]), - deps = [":list-navigation"], + deps = [ + ":list-navigation", + "//src/cdk-experimental/ui-patterns/behaviors/signal-like", + ], ) ng_web_test_suite( diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-navigation/list-navigation.spec.ts b/src/cdk-experimental/ui-patterns/behaviors/list-navigation/list-navigation.spec.ts index bdbbc3b1f19a..e2234a5784ed 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-navigation/list-navigation.spec.ts +++ b/src/cdk-experimental/ui-patterns/behaviors/list-navigation/list-navigation.spec.ts @@ -6,15 +6,16 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Signal, signal, WritableSignal} from '@angular/core'; +import {signal} from '@angular/core'; +import {SignalLike, WritableSignalLike} from '../signal-like/signal-like'; import {ListNavigationItem, ListNavigation, ListNavigationInputs} from './list-navigation'; describe('List Navigation', () => { interface TestItem extends ListNavigationItem { - disabled: WritableSignal; + disabled: WritableSignalLike; } - function getItems(length: number): Signal { + function getItems(length: number): SignalLike { return signal( Array.from({length}).map((_, i) => ({ index: signal(i), @@ -24,7 +25,7 @@ describe('List Navigation', () => { } function getNavigation( - items: Signal, + items: SignalLike, args: Partial> = {}, ): ListNavigation { return new ListNavigation({ diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-navigation/list-navigation.ts b/src/cdk-experimental/ui-patterns/behaviors/list-navigation/list-navigation.ts index a7cc3897ded4..ba463d7f5997 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-navigation/list-navigation.ts +++ b/src/cdk-experimental/ui-patterns/behaviors/list-navigation/list-navigation.ts @@ -6,33 +6,34 @@ * found in the LICENSE file at https://angular.dev/license */ -import {signal, Signal, WritableSignal} from '@angular/core'; +import {signal} from '@angular/core'; +import {SignalLike, WritableSignalLike} from '../signal-like/signal-like'; /** Represents an item in a collection, such as a listbox option, than can be navigated to. */ export interface ListNavigationItem { /** Whether an item is disabled. */ - disabled: Signal; + disabled: SignalLike; } /** Represents the required inputs for a collection that has navigable items. */ export interface ListNavigationInputs { /** Whether focus should wrap when navigating. */ - wrap: Signal; + wrap: SignalLike; /** The items in the list. */ - items: Signal; + items: SignalLike; /** Whether disabled items in the list should be skipped when navigating. */ - skipDisabled: Signal; + skipDisabled: SignalLike; /** The current index that has been navigated to. */ - activeIndex: WritableSignal; + activeIndex: WritableSignalLike; /** Whether the list is vertically or horizontally oriented. */ - orientation: Signal<'vertical' | 'horizontal'>; + orientation: SignalLike<'vertical' | 'horizontal'>; /** The direction that text is read based on the users locale. */ - textDirection: Signal<'rtl' | 'ltr'>; + textDirection: SignalLike<'rtl' | 'ltr'>; } /** Controls navigation for a list of items. */ diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-selection/BUILD.bazel b/src/cdk-experimental/ui-patterns/behaviors/list-selection/BUILD.bazel index 60b7f627c039..fad8ca69513d 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-selection/BUILD.bazel +++ b/src/cdk-experimental/ui-patterns/behaviors/list-selection/BUILD.bazel @@ -10,6 +10,7 @@ ts_library( ), deps = [ "//src/cdk-experimental/ui-patterns/behaviors/list-navigation", + "//src/cdk-experimental/ui-patterns/behaviors/signal-like", "@npm//@angular/core", ], ) @@ -20,6 +21,7 @@ ng_test_library( deps = [ ":list-selection", "//src/cdk-experimental/ui-patterns/behaviors/list-navigation", + "//src/cdk-experimental/ui-patterns/behaviors/signal-like", ], ) diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-selection/list-selection.spec.ts b/src/cdk-experimental/ui-patterns/behaviors/list-selection/list-selection.spec.ts index 18a8f614e692..86a955992a47 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-selection/list-selection.spec.ts +++ b/src/cdk-experimental/ui-patterns/behaviors/list-selection/list-selection.spec.ts @@ -6,16 +6,17 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Signal, signal, WritableSignal} from '@angular/core'; +import {signal} from '@angular/core'; +import {SignalLike, WritableSignalLike} from '../signal-like/signal-like'; import {ListSelectionItem, ListSelection, ListSelectionInputs} from './list-selection'; import {ListNavigation, ListNavigationInputs} from '../list-navigation/list-navigation'; describe('List Selection', () => { interface TestItem extends ListSelectionItem { - disabled: WritableSignal; + disabled: WritableSignalLike; } - function getItems(length: number): Signal { + function getItems(length: number): SignalLike { return signal( Array.from({length}).map((_, i) => ({ index: signal(i), @@ -27,7 +28,7 @@ describe('List Selection', () => { } function getNavigation( - items: Signal, + items: SignalLike, args: Partial> = {}, ): ListNavigation { return new ListNavigation({ @@ -42,7 +43,7 @@ describe('List Selection', () => { } function getSelection( - items: Signal, + items: SignalLike, navigation: ListNavigation, args: Partial> = {}, ): ListSelection { diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-selection/list-selection.ts b/src/cdk-experimental/ui-patterns/behaviors/list-selection/list-selection.ts index ac3de73fe325..ce8280d0e453 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-selection/list-selection.ts +++ b/src/cdk-experimental/ui-patterns/behaviors/list-selection/list-selection.ts @@ -6,31 +6,32 @@ * found in the LICENSE file at https://angular.dev/license */ -import {signal, Signal, WritableSignal} from '@angular/core'; +import {signal} from '@angular/core'; +import {SignalLike, WritableSignalLike} from '../signal-like/signal-like'; import {ListNavigation, ListNavigationItem} from '../list-navigation/list-navigation'; /** Represents an item in a collection, such as a listbox option, than can be selected. */ export interface ListSelectionItem extends ListNavigationItem { /** A unique identifier for the item. */ - id: Signal; + id: SignalLike; /** Whether an item is disabled. */ - disabled: Signal; + disabled: SignalLike; } /** Represents the required inputs for a collection that contains selectable items. */ export interface ListSelectionInputs { /** The items in the list. */ - items: Signal; + items: SignalLike; /** Whether multiple items in the list can be selected at once. */ - multiselectable: Signal; + multiselectable: SignalLike; /** The ids of the current selected items. */ - selectedIds: WritableSignal; + selectedIds: WritableSignalLike; /** The selection strategy used by the list. */ - selectionMode: Signal<'follow' | 'explicit'>; + selectionMode: SignalLike<'follow' | 'explicit'>; } /** Controls selection for a list of items. */ diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/BUILD.bazel b/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/BUILD.bazel index 4120c12c0ad0..9c415122deb3 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/BUILD.bazel +++ b/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/BUILD.bazel @@ -10,6 +10,7 @@ ts_library( ), deps = [ "//src/cdk-experimental/ui-patterns/behaviors/list-navigation", + "//src/cdk-experimental/ui-patterns/behaviors/signal-like", "@npm//@angular/core", ], ) @@ -20,6 +21,7 @@ ng_test_library( deps = [ ":list-typeahead", "//src/cdk-experimental/ui-patterns/behaviors/list-navigation", + "//src/cdk-experimental/ui-patterns/behaviors/signal-like", ], ) diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/list-typeahead.spec.ts b/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/list-typeahead.spec.ts index df4c8b853720..1be9ab8d0745 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/list-typeahead.spec.ts +++ b/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/list-typeahead.spec.ts @@ -6,17 +6,18 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Signal, signal, WritableSignal} from '@angular/core'; +import {signal} from '@angular/core'; +import {SignalLike, WritableSignalLike} from '../signal-like/signal-like'; import {ListTypeaheadItem, ListTypeahead} from './list-typeahead'; import {fakeAsync, tick} from '@angular/core/testing'; import {ListNavigation} from '../list-navigation/list-navigation'; describe('List Typeahead', () => { interface TestItem extends ListTypeaheadItem { - disabled: WritableSignal; + disabled: WritableSignalLike; } - function getItems(length: number): Signal { + function getItems(length: number): SignalLike { return signal( Array.from({length}).map((_, i) => ({ index: signal(i), diff --git a/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/list-typeahead.ts b/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/list-typeahead.ts index 64154eda550d..aaa2350d6d63 100644 --- a/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/list-typeahead.ts +++ b/src/cdk-experimental/ui-patterns/behaviors/list-typeahead/list-typeahead.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ -import {signal, Signal} from '@angular/core'; +import {signal} from '@angular/core'; +import {SignalLike} from '../signal-like/signal-like'; import {ListNavigationItem, ListNavigation} from '../list-navigation/list-navigation'; /** @@ -15,7 +16,7 @@ import {ListNavigationItem, ListNavigation} from '../list-navigation/list-naviga */ export interface ListTypeaheadItem extends ListNavigationItem { /** The text used by the typeahead search. */ - searchTerm: Signal; + searchTerm: SignalLike; } /** @@ -24,7 +25,7 @@ export interface ListTypeaheadItem extends ListNavigationItem { */ export interface ListTypeaheadInputs { /** The amount of time before the typeahead search is reset. */ - typeaheadDelay: Signal; + typeaheadDelay: SignalLike; } /** Controls typeahead for a list of items. */ diff --git a/src/cdk-experimental/ui-patterns/behaviors/signal-like/BUILD.bazel b/src/cdk-experimental/ui-patterns/behaviors/signal-like/BUILD.bazel new file mode 100644 index 000000000000..47d463668358 --- /dev/null +++ b/src/cdk-experimental/ui-patterns/behaviors/signal-like/BUILD.bazel @@ -0,0 +1,9 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ts_library( + name = "signal-like", + srcs = ["signal-like.ts"], + deps = [], +) diff --git a/src/cdk-experimental/ui-patterns/behaviors/signal-like/signal-like.ts b/src/cdk-experimental/ui-patterns/behaviors/signal-like/signal-like.ts new file mode 100644 index 000000000000..5717dd3b8d6d --- /dev/null +++ b/src/cdk-experimental/ui-patterns/behaviors/signal-like/signal-like.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +export type SignalLike = () => T; + +export interface WritableSignalLike extends SignalLike { + set(value: T): void; + update(updateFn: (value: T) => T): void; +} + +/** Converts a getter setter style signal to a WritableSignalLike. */ +export function convertGetterSetterToWritableSignalLike( + getter: () => T, + setter: (v: T) => void, +): WritableSignalLike { + // tslint:disable-next-line:ban Have to use `Object.assign` to preserve the getter function. + return Object.assign(getter, { + set: setter, + update: (updateCallback: (v: T) => T) => setter(updateCallback(getter())), + }); +} diff --git a/src/cdk-experimental/ui-patterns/listbox/BUILD.bazel b/src/cdk-experimental/ui-patterns/listbox/BUILD.bazel index 00137bf59503..9d68e2822333 100644 --- a/src/cdk-experimental/ui-patterns/listbox/BUILD.bazel +++ b/src/cdk-experimental/ui-patterns/listbox/BUILD.bazel @@ -14,6 +14,7 @@ ts_library( "//src/cdk-experimental/ui-patterns/behaviors/list-navigation", "//src/cdk-experimental/ui-patterns/behaviors/list-selection", "//src/cdk-experimental/ui-patterns/behaviors/list-typeahead", + "//src/cdk-experimental/ui-patterns/behaviors/signal-like", "@npm//@angular/core", ], ) diff --git a/src/cdk-experimental/ui-patterns/listbox/listbox.ts b/src/cdk-experimental/ui-patterns/listbox/listbox.ts index 69b0f53a624d..2a4dfcfa732d 100644 --- a/src/cdk-experimental/ui-patterns/listbox/listbox.ts +++ b/src/cdk-experimental/ui-patterns/listbox/listbox.ts @@ -14,7 +14,8 @@ import {ListSelection, ListSelectionInputs} from '../behaviors/list-selection/li import {ListTypeahead, ListTypeaheadInputs} from '../behaviors/list-typeahead/list-typeahead'; import {ListNavigation, ListNavigationInputs} from '../behaviors/list-navigation/list-navigation'; import {ListFocus, ListFocusInputs} from '../behaviors/list-focus/list-focus'; -import {computed, Signal} from '@angular/core'; +import {computed} from '@angular/core'; +import {SignalLike} from '../behaviors/signal-like/signal-like'; /** The selection operations that the listbox can perform. */ interface SelectOptions { @@ -32,7 +33,7 @@ export type ListboxInputs = ListNavigationInputs & ListSelectionInputs & ListTypeaheadInputs & ListFocusInputs & { - disabled: Signal; + disabled: SignalLike; }; /** Controls the state of a listbox. */ @@ -50,10 +51,10 @@ export class ListboxPattern { focusManager: ListFocus; /** Whether the list is vertically or horizontally oriented. */ - orientation: Signal<'vertical' | 'horizontal'>; + orientation: SignalLike<'vertical' | 'horizontal'>; /** Whether the listbox is disabled. */ - disabled: Signal; + disabled: SignalLike; /** The tabindex of the listbox. */ tabindex = computed(() => this.focusManager.getListTabindex()); @@ -62,7 +63,7 @@ export class ListboxPattern { activedescendant = computed(() => this.focusManager.getActiveDescendant()); /** Whether multiple items in the list can be selected at once. */ - multiselectable: Signal; + multiselectable: SignalLike; /** The number of items in the listbox. */ setsize = computed(() => this.navigation.inputs.items().length); diff --git a/src/cdk-experimental/ui-patterns/listbox/option.ts b/src/cdk-experimental/ui-patterns/listbox/option.ts index a8bd269621be..b838dc5ae7b5 100644 --- a/src/cdk-experimental/ui-patterns/listbox/option.ts +++ b/src/cdk-experimental/ui-patterns/listbox/option.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import {computed, Signal} from '@angular/core'; +import {computed} from '@angular/core'; import {ListSelection, ListSelectionItem} from '../behaviors/list-selection/list-selection'; import {ListTypeaheadItem} from '../behaviors/list-typeahead/list-typeahead'; import {ListNavigation, ListNavigationItem} from '../behaviors/list-navigation/list-navigation'; import {ListFocus, ListFocusItem} from '../behaviors/list-focus/list-focus'; +import {SignalLike} from '../behaviors/signal-like/signal-like'; /** * Represents the properties exposed by a listbox that need to be accessed by an option. @@ -28,13 +29,13 @@ export interface OptionInputs ListSelectionItem, ListTypeaheadItem, ListFocusItem { - listbox: Signal; + listbox: SignalLike; } /** Represents an option in a listbox. */ export class OptionPattern { /** A unique identifier for the option. */ - id: Signal; + id: SignalLike; /** The position of the option in the list. */ index = computed( @@ -48,19 +49,19 @@ export class OptionPattern { selected = computed(() => this.listbox().selection.inputs.selectedIds().includes(this.id())); /** Whether the option is disabled. */ - disabled: Signal; + disabled: SignalLike; /** The text used by the typeahead search. */ - searchTerm: Signal; + searchTerm: SignalLike; /** A reference to the parent listbox. */ - listbox: Signal; + listbox: SignalLike; /** The tabindex of the option. */ tabindex = computed(() => this.listbox().focusManager.getItemTabindex(this)); /** The html element that should receive focus. */ - element: Signal; + element: SignalLike; constructor(args: OptionInputs) { this.id = args.id;