Skip to content

Conversation

@Alexia-Claudia-Micu
Copy link
Contributor

@Alexia-Claudia-Micu Alexia-Claudia-Micu commented Feb 6, 2026

Pull Request

🀨 Rationale

https://dev.azure.com/ni/DevCentral/_workitems/edit/3740927

Add cancel button to chat input.

πŸ‘©β€πŸ’» Implementation

Add processing, stop-button-label attributes and stop event.

πŸ§ͺ Testing

Manual testing, updated tests and added matrix tests.

βœ… Checklist

  • I have updated the project documentation to reflect my changes or determined no changes are needed.

Copy link

@OliverCosma OliverCosma left a comment

Choose a reason for hiding this comment

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

This looks good to me, I'm waiting just for the textAreaKeydownHandler to be implemented :)

@Alexia-Claudia-Micu Alexia-Claudia-Micu marked this pull request as ready for review February 10, 2026 15:03
const processingStates = [
['not', false],
['', true]
] as const;
Copy link
Contributor

Choose a reason for hiding this comment

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

In other matrix stories for boolean attributes, it looks like we've established a pattern of naming the false state '' and giving the true state a value that represents its meaning. Let's keep with that pattern here.

Suggested change
] as const;
const processingStates = [
['', false],
['processing', true]
] as const;

"
>
${valueLabel} value, ${placeholderLabel} placeholder
${valueLabel} value, ${placeholderLabel} placeholder, ${processingLabel} processing
Copy link
Contributor

Choose a reason for hiding this comment

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

If you apply my suggestion above then this becomes unnecessary

Suggested change
${valueLabel} value, ${placeholderLabel} placeholder, ${processingLabel} processing
${valueLabel} value, ${placeholderLabel} placeholder, ${processingLabel}

return;
}
const eventDetail: ChatInputStopEventDetail = {};
this.$emit('stop', eventDetail);
Copy link
Contributor

Choose a reason for hiding this comment

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

When there's no extra information to provide in the detail object, our typical pattern is to leave it unset. If you do this then you can also:

  1. revert the change to types.ts
  2. remove the export from the Angular directive
  3. remove the documentation about the type from storybook
Suggested change
this.$emit('stop', eventDetail);
this.$emit('stop');

const eventDetail: ChatInputStopEventDetail = {};
this.$emit('stop', eventDetail);
this.textArea?.blur();
this.processing = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

We didn't discuss this in the spec, but we should not not automatically clear the processing state when the Stop button is clicked. We generally avoid programmatically setting state (like attributes) that is also within the client app's control since it can cause their representation of that state to get out of sync. It is also good to stay symmetric: the client app added the processing attribute so the client app should be responsible for removing it.

Suggested change
this.processing = false;

*/
public textAreaKeydownHandler(e: KeyboardEvent): boolean {
if (e.key === keyEnter && !e.shiftKey) {
if (e.key === keyEnter && !e.shiftKey && !this.processing) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for catching that we don't want the Enter key to cause the send or stop button to be clicked. Though it's worth debating what we do want it do do:

  1. the behavior with the current implementation, which is to add a new line (behave like Shift-Enter)
  2. ignore it if you just press Enter, but add a new line if you press Shift-Enter

I think I have a personal preference for 2 because it feels more consistent. But I'm open to other preferences or to checking what desktop Nigel does and copying that.

Once you decide I think we should have a test for this case.

${x => x.sendButtonLabel}
<${iconPaperPlaneTag} slot="start"><${iconPaperPlaneTag}/>
</${buttonTag}>
${when(x => !x.processing, html<ChatInput>`
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of swapping out the entire button when the processing attribute changes, I think it'd be cleaner to use the same button for both states but swap out the contents of the attributes that change. That would be less disruptive to the DOM contents and would allow the browser to preserve things like button focus.

Part of that would be using a more generic class for the button (e.g. class="action-button"). That would have some impacts on styles and the page object, but hopefully simplify them too.

public sendButtonLabel?: string;

@attr({ attribute: 'stop-button-label' })
public stopButtonLabel?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

Heads up that we have some tech debt where we want to move away from specifying user-visible strings as attributes and instead use label providers which have the advantage of allowing Nimble to provide a default value that apps can still localize.

I don't think we have to do this in this PR but it's something we should consider doing soon before we add too many more user-visible strings.

FYI @OliverCosma

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants