Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
4d676e0
feat(ui5-ai-textarea): introduce new component
ndeshev Aug 20, 2025
d18dcac
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 1, 2025
8ded4f7
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 1, 2025
4842d6a
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 1, 2025
7fe9641
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 1, 2025
65161cb
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 2, 2025
531abcf
Merge branch 'main' into ai-textarea
ndeshev Sep 2, 2025
89e1cf2
Merge branch 'main' into ai-textarea
ndeshev Sep 2, 2025
4d2c79a
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 2, 2025
84f38f6
Merge branch 'main' into ai-textarea
ndeshev Sep 18, 2025
8b25d8f
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 24, 2025
8cf7379
Merge branch 'main' into ai-textarea
ndeshev Sep 24, 2025
1b26913
Merge branch 'main' into ai-textarea
ndeshev Sep 24, 2025
b8ac3b6
Merge branch 'main' into ai-textarea
ndeshev Sep 26, 2025
6511964
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 29, 2025
78f4ced
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 29, 2025
e6856b7
Merge branch 'main' into ai-textarea
ndeshev Sep 29, 2025
7a9e48d
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 29, 2025
cea2409
Merge branch 'ai-textarea' of https://github.com/SAP/ui5-webcomponent…
ndeshev Sep 29, 2025
37672ff
Merge branch 'main' into ai-textarea
ndeshev Sep 29, 2025
1d7d19c
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 29, 2025
8811b32
Merge branch 'ai-textarea' of https://github.com/SAP/ui5-webcomponent…
ndeshev Sep 29, 2025
ece74f7
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 29, 2025
7b5274c
Merge branch 'main' into ai-textarea
ndeshev Sep 29, 2025
9fa18af
Merge branch 'main' into ai-textarea
ndeshev Sep 29, 2025
c483d45
Merge branch 'main' of https://github.com/SAP/ui5-webcomponents into …
ndeshev Sep 30, 2025
4fd733d
Merge branch 'main' into ai-textarea
ndeshev Sep 30, 2025
d66e7fe
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 30, 2025
988e535
Merge branch 'main' into ai-textarea
ndeshev Sep 30, 2025
4e59f48
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 30, 2025
e9e9432
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 30, 2025
f1e6023
feat(ui5-ai-textarea): introduce new component
ndeshev Sep 30, 2025
7bab270
feat(ai-input): introduce new component POC
StefanDimitrov04 Oct 1, 2025
8d09a5b
Merge branch 'ai-textarea' of github.com:SAP/ui5-webcomponents into a…
StefanDimitrov04 Oct 1, 2025
72b6443
feat(ui5-ai-input): resolve conflicts
StefanDimitrov04 Oct 1, 2025
83b8c1e
Merge branch 'main' into ai-textarea
ndeshev Oct 2, 2025
d435c83
feat(ui5-ai-textarea): introduce new component
ndeshev Oct 3, 2025
787d306
Merge branch 'main' into ai-textarea
ndeshev Oct 3, 2025
5387e67
feat(ui5-ai-textarea): introduce new component
ndeshev Oct 4, 2025
76637d8
Merge branch 'ai-textarea' of https://github.com/UI5/webcomponents in…
ndeshev Oct 4, 2025
eab3293
Merge branch 'main' into ai-textarea
ndeshev Oct 4, 2025
18e06dc
feat(ui5-ai-textarea): introduce new component
ndeshev Oct 5, 2025
fbae010
feat(ui5-ai-textarea): introduce new component
ndeshev Oct 5, 2025
15445a7
feat(ui5-ai-textarea): introduce new component
ndeshev Oct 5, 2025
342efdc
Merge branch 'ai-textarea' of github.com:SAP/ui5-webcomponents into a…
StefanDimitrov04 Oct 6, 2025
ad08bb2
feat(ui5-ai-input): add tests and simplify code
StefanDimitrov04 Oct 7, 2025
1db10a0
feat(ui5-ai-input): fix imports
StefanDimitrov04 Oct 7, 2025
21decf5
feat(ui5-ai-input): fix imports
StefanDimitrov04 Oct 7, 2025
214955d
Merge branch 'main' of github.com:SAP/ui5-webcomponents into ai-input
StefanDimitrov04 Oct 9, 2025
f6d9a3d
feat(ui5-ai-input): remove input versioning
StefanDimitrov04 Oct 9, 2025
3d723a9
feat(ui5-ai-input): add tests
StefanDimitrov04 Oct 10, 2025
d22240e
feat(ui5-ai-input): fix css
StefanDimitrov04 Oct 13, 2025
42af0f3
feat(ui5-ai-input): fix lint
StefanDimitrov04 Oct 13, 2025
989df7d
feat(ui5-ai-input): resolve comments
StefanDimitrov04 Oct 16, 2025
be4a38c
feat(ui5-ai-input): fix css
StefanDimitrov04 Oct 21, 2025
01978f0
feat(ui5-ai-input): fix comments and design
StefanDimitrov04 Oct 22, 2025
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
839 changes: 839 additions & 0 deletions packages/ai/cypress/specs/Input.cy.tsx

Large diffs are not rendered by default.

259 changes: 259 additions & 0 deletions packages/ai/src/Input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import { customElement, property, slot } from "@ui5/webcomponents-base";
import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
import BaseInput from "@ui5/webcomponents/dist/Input.js";
import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
import type Menu from "@ui5/webcomponents/dist/Menu.js";
import type Button from "./Button.js";

// styles
import AIInputCss from "./generated/themes/Input.css.js";
import InputCss from "@ui5/webcomponents/dist/generated/themes/Input.css.js";
import ResponsivePopoverCommonCss from "@ui5/webcomponents/dist/generated/themes/ResponsivePopoverCommon.css.js";
import ValueStateMessageCss from "@ui5/webcomponents/dist/generated/themes/ValueStateMessage.css.js";
import SuggestionsCss from "@ui5/webcomponents/dist/generated/themes/Suggestions.css.js";

// templates
import InputTemplate from "./InputTemplate.js";
import {
VERSIONING_NEXT_BUTTON_TEXT,
VERSIONING_PREVIOUS_BUTTON_TEXT,
INPUT_WRITING_ASSISTANT_LABEL,
WRITING_ASSISTANT_GENERATING_ANNOUNCEMENT,
} from "./generated/i18n/i18n-defaults.js";

@customElement({
tag: "ui5-ai-input",
languageAware: true,
renderer: jsxRenderer,
template: InputTemplate,
styles: [
AIInputCss,
InputCss,
ResponsivePopoverCommonCss,
ValueStateMessageCss,
SuggestionsCss,
],
})

/**
* Fired when the user clicks on the AI button.
* @public
*/
@event("button-click", {
cancelable: true,
})

/**
* Fired when the user clicks on the "Stop" button to stop ongoing AI text generation.
* @public
*/
@event("stop-generation")

/**
* Fired when the user clicks on version navigation buttons.
*
* @param {boolean} backwards - Indicates if navigation is backwards (true) or forwards (false, default)
* @public
*/
@event("version-change")

class Input extends BaseInput {
eventDetails!: BaseInput["eventDetails"] & {
"version-change": {
backwards: boolean;
};
"stop-generation": object;
"button-click": object;
};

/**
* Indicates the index of the currently displayed version.
*
* @default 0
*/
@property({ type: Number })
currentVersion = 0;

/**
* Indicates the total number of result versions available.
*
* When not set or `0`, versioning UI will be hidden.
*
* @default 0
* @public
*/
@property({ type: Number })
totalVersions = 0;

/**
* Defines whether the AI Writing Assistant is currently loading.
*
* When `true`, indicates that an AI action is in progress.
*
* @default false
*/
@property({ type: Boolean })
loading: boolean = false;

/**
* Indicates if the menu is open.
* @default 0
* @private
*/
@property({ type: Boolean })
_isMenuOpen: boolean = false;

/**
* Defines the items of the menu for the component.
* @public
*/
@slot({
type: HTMLElement,
invalidateOnChildChange: true,
})
actions!: Array<HTMLElement>;

_previousCurrentStep = 0;
_previousTotalSteps = 0;
isFocused: boolean = false;

_onfocusin(e: FocusEvent): void {
super._onfocusin(e);
this.isFocused = true;
}

_onfocusout(e: FocusEvent): void {
super._onfocusout(e);
this.isFocused = false;
}

/**
* Manages focus when navigation buttons become disabled/enabled.
* Automatically moves focus to available button when user reaches boundaries.
* @private
*/
_manageVersionButtonsFocus() {
const previousButton = this.shadowRoot?.querySelectorAll("ui5-button")[0] as Button;
const nextButton = this.shadowRoot?.querySelectorAll("ui5-button")[1] as Button;
const isPreviousDisabled = this.currentVersion <= 1;
const isNextDisabled = this.currentVersion >= this.totalVersions;

if (isPreviousDisabled && previousButton) {
setTimeout(() => {
nextButton.focus();
}, 0);
} else if (isNextDisabled && nextButton) {
setTimeout(() => {
previousButton.focus();
}, 0);
}
}

/**
* Handles the click event for the AI generate icon.
* Fires the appropriate event based on the AI icon state.
* @private
*/
_handleAIIconClick(e: Event) {
const target = e.target as HTMLElement & { name?: string };
if (target?.name === "stop") {
this.fireDecoratorEvent("stop-generation");
} else {
const opener = this.shadowRoot?.querySelector(".ui5-input-ai-icon") as HTMLElement;
this.fireDecoratorEvent("button-click");
this.menu.opener = opener;
this.menu.open = true;
this.menu.horizontalAlign = "End";
}
}

/**
* Handles the version change event from the versioning component.
*
* @param {CustomEvent} e - The version change event
*/
_handleVersionChange(e: CustomEvent<{ backwards: boolean }>) {
this.fireDecoratorEvent("version-change", {
backwards: e.detail.backwards,
});
this._manageVersionButtonsFocus();
}

/**
* Handles the click event for the "Previous Version" button.
* Updates the current version index and syncs content.
* @private
*/
_handlePreviousButtonClick(): void {
this._handleVersionChange(new CustomEvent("version-change", { detail: { backwards: true } }));
}

/**
* Handles the click event for the "Next Version" button.
* Updates the current version index and syncs content.
* @private
*/
_handleNextButtonClick(): void {
this._handleVersionChange(new CustomEvent("version-change", { detail: { backwards: false } }));
}

_onMenuIconClick(): void {
this.menu?.addEventListener("item-click", (e: Event) => {
const customEvent = e as CustomEvent;
this.dispatchEvent(new CustomEvent("item-click", {
detail: customEvent.detail,
bubbles: true,
composed: true,
}));
});
}

/**
* Handles keydown events for keyboard shortcuts.
* @private
*/
_onkeydown(e: KeyboardEvent): void {
super._onkeydown(e);
this.menu.opener = this.shadowRoot?.querySelector(".ui5-input-ai-icon") as HTMLElement;

if (e.key === "F4" && e.shiftKey) {
e.preventDefault();
this.menu.open = true;
this.menu.horizontalAlign = "End";
}
const goPreviousStep = e.key === "Z" && e.shiftKey && e.ctrlKey;
const goNextStep = e.key === "Y" && e.shiftKey && e.ctrlKey;

if (goPreviousStep) {
e.preventDefault();
this._handlePreviousButtonClick();
} else if (goNextStep) {
e.preventDefault();
this._handleNextButtonClick();
}
}

get ariaLabel() {
return this.accessibleName || !this.loading ? Input.i18nBundle.getText(INPUT_WRITING_ASSISTANT_LABEL) : Input.i18nBundle.getText(WRITING_ASSISTANT_GENERATING_ANNOUNCEMENT);
}

get stopGeneratingTooltip() {
return Input.i18nBundle.getText(WRITING_ASSISTANT_GENERATING_ANNOUNCEMENT);
}

get nextButtonAccessibleName() {
return Input.i18nBundle.getText(VERSIONING_NEXT_BUTTON_TEXT);
}

get previousButtonAccessibleName() {
return Input.i18nBundle.getText(VERSIONING_PREVIOUS_BUTTON_TEXT);
}

get menu() {
return this.shadowRoot?.querySelector("ui5-menu") as Menu;
}
}

Input.define();

export default Input;
Loading
Loading