Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cdk-experimental/ui-patterns): listbox ui pattern #30495

Merged
merged 11 commits into from
Feb 27, 2025

Conversation

wagnermaciel
Copy link
Contributor

No description provided.

@angular-robot angular-robot bot added the detected: feature PR contains a feature commit label Feb 14, 2025
@wagnermaciel wagnermaciel marked this pull request as ready for review February 18, 2025 15:16
@wagnermaciel wagnermaciel requested review from a team as code owners February 18, 2025 15:16
@wagnermaciel wagnermaciel requested review from crisbeto, andrewseguin, jelbourn and mmalerba and removed request for a team February 18, 2025 15:16
Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First batch of comments, I'm still going

Comment on lines 35 to 38
* An event manager that is specialized for handling mouse events. By default this manager stops
* propagation and prevents default on all events it handles.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that pointer events should always preventDefault and stopPropogation the way that keyboard events do. What's your thinking here?

Copy link
Contributor

@mmalerba mmalerba Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was no thinking on this, I wasn't really sure what the default behaviors should be. This is just what I put in the prototype.

Thinking about it more right now, I would suggest:
keyboard:

  • preventDefualt (yes)
  • stopPropagation (yes? - motivation for this is things like stacks of dialog, menu, etc where escape should probably close the top one, not all of them)

mouse:

  • preventDefault (no - e.g. this is probably not desired for mousedown where preventDefault would cancel focusing of the element)
  • stopPropagation (maybe? not really sure about this one)

Anyways that's just my thought for the default defaults. People could always create a manager with different defaults new KeydownEventManager({preventDefault: false}) or register a handler with a different setting than the manager's default manager.on('x', () => {...}, {preventDefault: false})

'cdk-experimental/popover-edit',
'cdk-experimental/scrolling',
'cdk-experimental/selection',
'cdk-experimental/table-scroll-container',
'cdk-experimental/ui-patterns',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we call this patterns? The ui- seems a bit redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jelbourn Do you recall the reason for keeping the ui- prefix?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the ui-, patterns by itself makes me think of like GoF design patterns or something

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the thinking was that just "patterns" by itself was too generic

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't everything we do related to building UI though? It seems a bit redundant to have it in the name. Then again, if we don't intend to make this public it might not matter 🤷‍♂️

label = input<string>();

/** The text used by the typeahead search. */
searchTerm = computed(() => this.label() ?? this.element().textContent);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make the label required so we don't have to hit the DOM every time the computed runs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the devex is worth the performance cost in this case. If I understand correctly, this computed will only rerun if the label is set/unset or if the element changes (which I would assume is rare)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that initially it runs so the runtime can figure out the dependencies and afterwards it only runs on changes. That being said, it might be a bit misleading because it's not going to update if the textContent changes. I expect it might come up, given that we had to account for it in mat-option: https://github.com/angular/components/blob/main/src/material/core/option/option.ts#L267

My thinking was that it's easier to introduce it later than having consumers depend on it and us having to change it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, I hadn't considered the text content changing. I still lean against making the label required because I think it's too much boilerplate. Would it be ok to leave this for a follow-up PR so that we can discuss when we review the public API design doc?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option can be to only derive the label from the input and if there isn't one, we just don't do typeahead.

Copy link
Member

@devversion devversion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for dev-infra; leaving naming to others

multiselectable: Signal<boolean>;

/** The ids of the current selected items. */
selectedIds: WritableSignal<string[]>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily for this PR, but one thing we should discuss in a bit more detail is how we're managing selected IDs, since I worry that the array isn't the best data structure vs. a Set, but we don't have a pattern established yet for a Set that plays well with signals

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM aside from a last few minor comments

private readonly _generatedId = inject(_IdGenerator).getId('cdk-option-');

/** A unique identifier for the option. */
protected id = computed(() => this._generatedId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a TODO / cleanup for a next PR, but these generally shouldn't be computed for things that won't change.

I think the reason that you're doing this is because the e.g. OptionInputs expects a signal. However, you don't really want those to be a Signal<T>, but instead a () => T (which would also make the types work for Wiz) . You could optionally introduce a convenience type for this that's something like

export type Reactive<T> = () => T;

We probably want to do this before build too many more ___Input types

@wagnermaciel wagnermaciel added target: patch This PR is targeted for the next patch release action: merge The PR is ready for merge by the caretaker target: minor This PR is targeted for the next minor release and removed target: patch This PR is targeted for the next patch release labels Feb 27, 2025
@wagnermaciel wagnermaciel merged commit fc46997 into angular:main Feb 27, 2025
24 of 26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
action: merge The PR is ready for merge by the caretaker detected: feature PR contains a feature commit target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants