diff --git a/packages/ai/cypress/specs/Input.cy.tsx b/packages/ai/cypress/specs/Input.cy.tsx new file mode 100644 index 000000000000..85983fe76144 --- /dev/null +++ b/packages/ai/cypress/specs/Input.cy.tsx @@ -0,0 +1,560 @@ +import AITextArea from "../../src/TextArea.js"; +import AIInput from "../../src/AIInput.js"; +import Menu from "@ui5/webcomponents/dist/Menu.js"; +import MenuItem from "@ui5/webcomponents/dist/MenuItem.js"; + +describe("Basic", () => { + describe("Initialization", () => { + it("should render with default properties", () => { + cy.mount(); + + cy.get("[ui5-ai-input]") + .as("input") + .should("exist") + .should("have.prop", "loading", false) + .should("have.prop", "currentVersion", 0) + .should("have.prop", "totalVersions", 0); + + // cy.get("@input") + // .shadow() + // .find("[ui5-ai-writing-assistant]") + // .should("exist"); + }); + + it("should set initial value as a property", () => { + cy.mount(); + + cy.get("[ui5-ai-input]") + .should("have.prop", "value", "AI initial value"); + }); + }); + + describe("Loading States", () => { + it("should display non-loading state correctly", () => { + cy.mount(); + + cy.get("[ui5-ai-input]") + // .shadow() + // .find("[ui5-ai-writing-assistant]") + .should("have.prop", "loading", false); + }); + + it("should display loading state correctly", () => { + cy.mount( + + ); + + cy.get("[ui5-ai-input]") + // .shadow() + // .find("[ui5-ai-writing-assistant]") + .should("have.prop", "loading", true) + .should("have.prop", "value", "Generating content..."); + }); + + it("should display single result correctly", () => { + cy.mount( + + ); + + cy.get("[ui5-ai-input]") + // .shadow() + // .find("[ui5-ai-writing-assistant]") + .should("have.prop", "loading", false) + .should("have.prop", "value", "Generated text") + .should("have.prop", "currentVersion", 1) + .should("have.prop", "totalVersions", 1); + }); + + it("should display multiple results correctly", () => { + cy.mount( + + ); + + cy.get("[ui5-ai-input]") + // .shadow() + // .find("[ui5-ai-writing-assistant]") + .should("have.prop", "loading", false) + .should("have.prop", "value", "Generated text") + .should("have.prop", "currentVersion", 2) + .should("have.prop", "totalVersions", 3); + }); + }); + + describe("Version Navigation", () => { + it("should fire version-change event with backwards=true for previous version", () => { + const onVersionChange = cy.spy().as("onVersionChange"); + + cy.mount( + + ); + + cy.get("[ui5-ai-input]") + // .shadow() + // .find("[ui5-ai-writing-assistant]") + .shadow() + .find("[ui5-ai-versioning]") + .shadow() + .find('[data-ui5-versioning-button="previous"]') + .should("not.be.disabled") + .realClick(); + + cy.get("@onVersionChange") + .should("have.been.calledOnce") + .its("firstCall.args.0.detail") + .should("deep.equal", { + backwards: true + }); + }); + + it("should fire version-change event with backwards=false for next version", () => { + const onVersionChange = cy.spy().as("onVersionChange"); + + cy.mount( + + ); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("[ui5-ai-writing-assistant]") + .shadow() + .find("[ui5-ai-versioning]") + .shadow() + .find('[data-ui5-versioning-button="next"]') + .should("not.be.disabled") + .realClick(); + + cy.get("@onVersionChange") + .should("have.been.calledOnce") + .its("firstCall.args.0.detail") + .should("deep.equal", { + backwards: false + }); + }); + + it("should disable previous button when at first version", () => { + cy.mount( + + ); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("[ui5-ai-writing-assistant]") + .shadow() + .find("[ui5-ai-versioning]") + .shadow() + .find('[data-ui5-versioning-button="previous"]') + .shadow() + .find("ui5-button") + .should("have.attr", "disabled"); + }); + + it("should disable next button when at last version", () => { + cy.mount( + + ); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("[ui5-ai-writing-assistant]") + .shadow() + .find("[ui5-ai-versioning]") + .shadow() + .find('[data-ui5-versioning-button="next"]') + .shadow() + .find("ui5-button") + .should("have.attr", "disabled"); + }); + + it("should sync textarea content after version navigation", () => { + const initialValue = "Version 1 content"; + const newValue = "Version 2 content"; + + cy.mount( + + ); + + cy.get("[ui5-ai-textarea]") + .as("textarea") + .invoke("prop", "value", newValue); + + cy.get("@textarea") + .shadow() + .find("[ui5-ai-writing-assistant]") + .shadow() + .find("[ui5-ai-versioning]") + .shadow() + .find('[data-ui5-versioning-button="next"]') + .realClick(); + + cy.get("@textarea") + .shadow() + .find("textarea") + .should("have.value", newValue); + }); + }); + + describe("Menu Integration", () => { + it("should handle menu slot correctly", () => { + cy.mount( + + + + + + ); + + cy.get("[ui5-ai-textarea]") + .find("ui5-menu[slot='menu']") + .should("exist"); + }); + + it("should open menu when generate button is clicked", () => { + const onOpen = cy.spy().as("onOpen"); + + cy.mount( + + + + + + ); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("[ui5-ai-writing-assistant]") + .shadow() + .find("#ai-menu-btn") + .realClick(); + + cy.get("@onOpen").should("have.been.called"); + }); + }); + + describe("Stop Generation", () => { + it("should fire stop-generation event", () => { + const onStopGeneration = cy.spy().as("onStopGeneration"); + + cy.mount( + + ); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("[ui5-ai-writing-assistant]") + .shadow() + .find("#ai-menu-btn") + .realClick(); + + cy.get("@onStopGeneration").should("have.been.calledOnce"); + }); + }); + + describe("Keyboard Shortcuts", () => { + it("should handle Shift+F4 to focus AI button", () => { + cy.mount(); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("textarea") + .focus() + .realPress(['Shift', 'F4']); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("[ui5-ai-writing-assistant]") + .shadow() + .find("#ai-menu-btn") + .should("be.focused"); + }); + + it("should handle Ctrl+Shift+Z for previous version when multiple versions exist", () => { + const onVersionChange = cy.spy().as("onVersionChange"); + + cy.mount( + + ); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("textarea") + .focus() + .realPress(['Control', 'Shift', 'z']); + + cy.get("@onVersionChange") + .should("have.been.calledOnce") + .its("firstCall.args.0.detail") + .should("deep.equal", { + backwards: true + }); + }); + + it("should handle Ctrl+Shift+Y for next version when multiple versions exist", () => { + const onVersionChange = cy.spy().as("onVersionChange"); + + cy.mount( + + ); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("textarea") + .focus() + .realPress(['Control', 'Shift', 'y']); + + cy.get("@onVersionChange") + .should("have.been.calledOnce") + .its("firstCall.args.0.detail") + .should("deep.equal", { + backwards: false + }); + }); + }); + + describe("TextArea Integration", () => { + it("should inherit TextArea functionality", () => { + cy.mount(); + + cy.get("[ui5-ai-textarea]") + .as("textarea") + .shadow() + .find("textarea") + .should("have.value", "Test content") + .type(" additional text"); + + cy.get("@textarea") + .should("have.prop", "value") + .and("include", "Test content") + .and("include", "additional text"); + }); + + it("should support readonly mode", () => { + cy.mount(); + + cy.get("[ui5-ai-textarea]") + .should("have.attr", "readonly"); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("textarea") + .should("have.attr", "readonly"); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("textarea") + .should("have.value", "Readonly content"); + }); + + it("should support disabled mode", () => { + cy.mount(); + + cy.get("[ui5-ai-textarea]") + .should("have.attr", "disabled"); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("textarea") + .should("have.attr", "disabled"); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("textarea") + .should("have.value", "Disabled content"); + }); + }); + + describe("Event Handling", () => { + it("should handle input events", () => { + const onInput = cy.spy().as("onInput"); + + cy.mount(); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("textarea") + .type("Hello"); + + cy.get("@onInput").should("have.callCount", 5); + }); + + it("should handle change events", () => { + const onChange = cy.spy().as("onChange"); + + cy.mount(); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("textarea") + .type("test") + .blur(); + + cy.get("@onChange") + .should("have.been.called"); + + cy.get("@onChange") + .its("firstCall.args.0") + .should("have.property", "target") + .and("have.property", "value", "test"); + }); + }); + + describe("Busy State", () => { + it("should show busy indicator when loading", () => { + cy.mount(); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("ui5-busy-indicator") + .should("have.attr", "active"); + }); + + it("should hide busy indicator when not loading", () => { + cy.mount(); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("ui5-busy-indicator") + .should("not.have.attr", "active"); + }); + }); + + describe("Accessibility", () => { + it("should have proper ARIA attributes", () => { + cy.mount(); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("textarea") + .should("have.attr", "aria-label") + .and("not.be.empty"); + }); + + it("should support custom accessible name", () => { + cy.mount(); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("textarea") + .should("have.attr", "aria-label", "Custom AI TextArea"); + }); + + it("should announce AI actions to screen readers", () => { + cy.mount( + + ); + + // Verify that the loading state content is announced via aria-live region + cy.get("[ui5-ai-textarea]") + .shadow() + .find("[aria-live='polite']") + .should("contain.text", "Generating content..."); + }); + }); + + describe("Error Handling", () => { + it("should handle invalid loading state gracefully", () => { + cy.mount(); + + cy.get("[ui5-ai-textarea]") + .should("exist"); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("[ui5-ai-writing-assistant]") + .should("exist"); + }); + + it("should handle invalid version indices gracefully", () => { + cy.mount( + + ); + + cy.get("[ui5-ai-textarea]") + .should("exist"); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("[ui5-ai-writing-assistant]") + .should("exist"); + }); + + it("should handle zero total versions", () => { + cy.mount( + + ); + + cy.get("[ui5-ai-textarea]") + .should("exist"); + + cy.get("[ui5-ai-textarea]") + .shadow() + .find("[ui5-ai-writing-assistant]") + .shadow() + .find("[ui5-ai-versioning]") + .should("not.exist"); + }); + }); +}); diff --git a/packages/ai/src/AIInput.ts b/packages/ai/src/AIInput.ts new file mode 100644 index 000000000000..743935b363c7 --- /dev/null +++ b/packages/ai/src/AIInput.ts @@ -0,0 +1,247 @@ +import { customElement, property, slot } from "@ui5/webcomponents-base"; +import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; +import Input 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/AIInput.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 AIInputTemplate from "./AIInputTemplate.js"; + +enum LastClickedButton { + None = "", + Previous = "previous", + Next = "next" +} + +@customElement({ + tag: "ui5-ai-input", + languageAware: true, + renderer: jsxRenderer, + template: AIInputTemplate, + styles: [ + AIInputCss, + InputCss, + ResponsivePopoverCommonCss, + ValueStateMessageCss, + SuggestionsCss, + ], +}) + +/** + * Fired when the user presses generating button while loading. + * @public + */ +@event("stop-generation") + +/** + * Fired when the user navigates via the version change buttons. + * + * @param {boolean} backwards - Indicates if navigation is backwards (true) or forwards (false, default) + * @public + */ +@event("version-change") + +// @event("generate-icon-click") + +class AIInput extends Input { + eventDetails!: Input["eventDetails"] & { + "version-change": { + backwards: boolean; + }; + "stop-generation": null; + // "generate-icon-click": { clickTarget: HTMLElement }; + }; + + @property({ type: Number }) + currentVersion = 0; + + // @property() + // menuItems: Array<{ text: string}> = []; + + /** + * 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; + + @property({ type: Boolean }) + loading: boolean = false; + + /** + * Indicates if the menu is open. + * @default 0 + * @private + */ + @property({ type: Boolean }) + _isMenuOpen: boolean = false; + + @slot({ + type: HTMLElement, + // "default": true, + invalidateOnChildChange: true, + }) + default!: Array; + + _previousCurrentStep = 0; + _previousTotalSteps = 0; + _lastClickedButton: LastClickedButton = LastClickedButton.None; + isFocused: boolean = false; + + onBeforeRendering(): void { + super.onBeforeRendering(); + const menu = this.menu; + + menu?.addEventListener("item-click", (e: Event) => { + const customEvent = e as CustomEvent; + this.dispatchEvent(new CustomEvent("item-click", { + detail: customEvent.detail, // { item: } + bubbles: true, + composed: true, + })); + }); + } + + _onfocusin(e: FocusEvent): void { + super._onfocusin(e); + this.isFocused = true; + } + + _onfocusout(e: FocusEvent): void { + super._onfocusout(e); + this.isFocused = false; + } + + _manageFocus() { + const previousButton = this.shadowRoot?.querySelectorAll("ui5-button")[0] as Button; + const nextButton = this.shadowRoot?.querySelectorAll("ui5-button")[1] as Button; + // // const previousButton = this.shadowRoot?.querySelector("ui5-ai-input-versioning")?.shadowRoot?.querySelectorAll("ui5-button")[0] as Button; + // // const nextButton = this.shadowRoot?.querySelector("ui5-ai-input-versioning")?.shadowRoot?.querySelectorAll("ui5-button")[1] as Button; + const isPreviousDisabled = this.currentVersion <= 1; + const isNextDisabled = this.currentVersion >= this.totalVersions; + + if (isPreviousDisabled && previousButton) { + // queueMicrotask(() => nextButton.focus()); + // if (nextButton && nextButton.getDomRef()) { + setTimeout(() => { + nextButton.focus(); + // previousButton.disabled = true; + }, 0); + // } + } else if (isNextDisabled && nextButton) { + // if (previousButton && previousButton.getDomRef()) { + // if(previousButton.disabled) { + // previousButton.disabled = false; + // } + setTimeout(() => { + previousButton.focus(); + // nextButton.disabled = true; + }, 0); + // } + } + + // const previousButton = this.shadowRoot?.querySelector("[data-ui5-versioning-button=\"previous\"]") as HTMLElement; + // const nextButton = this.shadowRoot?.querySelector("[data-ui5-versioning-button=\"next\"]") as HTMLElement; + // const isPreviousDisabled = this.currentVersion <= 1; + // const isNextDisabled = this.currentVersion === this.totalVersions; + // const wasPreviousDisabled = this._previousCurrentStep <= 1; + // const wasNextDisabled = this._previousCurrentStep === this._previousTotalSteps; + + // if (isPreviousDisabled && !wasPreviousDisabled && !isNextDisabled && this._lastClickedButton === LastClickedButton.Previous) { + // // nextButton.focus(); + // setTimeout( () => { + // nextButton.focus(); + // console.log("next focus"); + + // // previousButton.disabled = true; + // }, 0) + // this._lastClickedButton = LastClickedButton.None; + // } else if (isNextDisabled && !wasNextDisabled && !isPreviousDisabled && this._lastClickedButton === LastClickedButton.Next) { + // // previousButton.focus(); + // setTimeout( () => { + // previousButton.focus(); + // console.log("previous focus"); + + // // // nextButton.disabled = true; + // }, 0); + // this._lastClickedButton = LastClickedButton.None; + // } + } + + _handleAIIconClick(e: CustomEvent) { + const target = e.target as HTMLElement & { name?: string }; + if (target?.name === "stop") { + this.fireDecoratorEvent("stop-generation"); + } else { + const menu = this.shadowRoot?.querySelector("ui5-menu") as Menu; + // this.fireDecoratorEvent("generate-icon-click", { clickTarget: target }); + menu.opener = target; + menu.open = true; + } + } + + _handleVersionChange(e: CustomEvent<{ backwards: boolean }>) { + this.fireDecoratorEvent("version-change", { + backwards: e.detail.backwards, + }); + + this._manageFocus(); + // this._previousCurrentStep = this.currentVersion; + // this._previousTotalSteps = this.totalVersions; + // this._lastClickedButton = LastClickedButton.None; + } + + _handleArrowLeftClick() { + this._lastClickedButton = LastClickedButton.Previous; + this._handleVersionChange(new CustomEvent("version-change", { detail: { backwards: true } })); + } + + _handleArrowRightClick() { + this._lastClickedButton = LastClickedButton.Next; + this._handleVersionChange(new CustomEvent("version-change", { detail: { backwards: false } })); + } + + _onkeydown(e: KeyboardEvent): void { + super._onkeydown(e); + const menu = this.shadowRoot?.querySelector("ui5-menu") as Menu; + menu.opener = this.shadowRoot?.getElementById("ai-menu-icon"); + + if (e.key === "F4" && e.shiftKey) { + e.preventDefault(); + menu.open = true; + } + const goPreviousStep = e.key === "Z" && e.shiftKey && e.ctrlKey; + const goNextStep = e.key === "Y" && e.shiftKey && e.ctrlKey; + + if (goPreviousStep) { + e.preventDefault(); + this._handleArrowLeftClick(); + } else if (goNextStep) { + e.preventDefault(); + this._handleArrowRightClick(); + } + } + get iconAccName() { + return "AI Writing Assistant (Shift + F4)"; + } + + get menu() { + return this.shadowRoot?.querySelector("ui5-menu"); + } +} + +AIInput.define(); + +export default AIInput; diff --git a/packages/ai/src/AIInputTemplate.tsx b/packages/ai/src/AIInputTemplate.tsx new file mode 100644 index 000000000000..516966b7606c --- /dev/null +++ b/packages/ai/src/AIInputTemplate.tsx @@ -0,0 +1,225 @@ +import type AIInput from "./AIInput.js"; +import Icon from "@ui5/webcomponents/dist/Icon.js"; +import BusyIndicator from "@ui5/webcomponents/dist/BusyIndicator.js"; +import MenuItem from "@ui5/webcomponents/dist/MenuItem.js"; +import Button from "@ui5/webcomponents/dist/Button.js"; +import Menu from "@ui5/webcomponents/dist/Menu.js"; +import "@ui5/webcomponents-icons/dist/navigation-left-arrow.js"; +import "@ui5/webcomponents-icons/dist/navigation-right-arrow.js"; +import MenuSeparator from "@ui5/webcomponents/dist/MenuSeparator.js"; +import InputPopoverTemplate from "@ui5/webcomponents/dist/InputPopoverTemplate.js"; +import type { JsxTemplateResult } from "@ui5/webcomponents-base"; +// import Versioning from "./Versioning.js"; +// import InputVersioning from "./InputVersioning.js"; + +type TemplateHook = () => JsxTemplateResult; + +export default function AIInputTemplate(this: AIInput, hooks?: { preContent: TemplateHook, postContent: TemplateHook, suggestionsList?: TemplateHook }) { + const suggestionsList = hooks?.suggestionsList; + const preContent = hooks?.preContent || defaultPreContent; + const postContent = hooks?.postContent || defaultPostContent; + return ( + <> +
+
+ + +
+
+ {preContent.call(this)} + + + + {this._effectiveShowClearIcon && +
+ + +
+ } + + {this.icon.length > 0 && +
+ +
+ } + +
+ {this._valueStateInputIcon} +
+ { postContent.call(this) } + {this._effectiveShowSuggestions && + <> + {this.suggestionsText} + + {this.availableSuggestionsCount} + + } + + {this.accInfo.ariaDescription && + {this.accInfo.ariaDescription} + } + + {this.accInfo.accessibleDescription && + {this.accInfo.accessibleDescription} + } + + {this.linksInAriaValueStateHiddenText.length > 0 && + {this.valueStateLinksShortcutsTextAcc} + } + + {this.hasValueState && + {this.ariaValueStateHiddenText} + } +
+
+ +
+ {/* } */} + {this.isFocused && +
+ +
+ } +
+ { this._isMenuOpen = true; }} + onBeforeClose={() => { this._isMenuOpen = false; }} + > + + {/* {this.totalVersions > 1 && this._isMenuOpen && + <> + + + + + + } */} + + {renderVersioning.call(this)} + +
+
+
+ {InputPopoverTemplate.call(this, { suggestionsList })} + + ); +} + +function renderVersioning(this: AIInput) { + if (this.totalVersions <= 1 || !this._isMenuOpen) { + return null; + } + + return ( + <> + + + + + + ); +} diff --git a/packages/ai/src/VersioningTemplate.tsx b/packages/ai/src/VersioningTemplate.tsx index 2fed840172f7..413c190c0cd3 100644 --- a/packages/ai/src/VersioningTemplate.tsx +++ b/packages/ai/src/VersioningTemplate.tsx @@ -3,6 +3,7 @@ import ToolbarLabel from "./ToolbarLabel.js"; import ToolbarButton from "@ui5/webcomponents/dist/ToolbarButton.js"; import "@ui5/webcomponents-icons/dist/navigation-left-arrow.js"; import "@ui5/webcomponents-icons/dist/navigation-right-arrow.js"; +import Button from "@ui5/webcomponents/dist/Button.js"; export default function VersioningTemplate(this: Versioning) { return ( @@ -30,3 +31,28 @@ export default function VersioningTemplate(this: Versioning) { ); } + +export function InputVersioningTemplate(this: Versioning) { + return (<> + + + ); +} diff --git a/packages/ai/src/bundle.esm.ts b/packages/ai/src/bundle.esm.ts index 158da279831e..a6f96341f3b8 100644 --- a/packages/ai/src/bundle.esm.ts +++ b/packages/ai/src/bundle.esm.ts @@ -8,5 +8,6 @@ import Button from "./Button.js"; import ButtonState from "./ButtonState.js"; import PromptInput from "./PromptInput.js"; import AITextArea from "./TextArea.js"; +import AIInput from "./AIInput.js"; export default testAssets; diff --git a/packages/ai/src/themes/AIInput.css b/packages/ai/src/themes/AIInput.css new file mode 100644 index 000000000000..1fc6b2a53b3a --- /dev/null +++ b/packages/ai/src/themes/AIInput.css @@ -0,0 +1,78 @@ +/* @import "../../../main/src/themes/InputIcon.css"; */ +/* @import "@ui5/webcomponents/src/generated/themes/Input.css.js"; */ + +[ui5-ai-input] { + --ui5_input_focus_pseudo_element_content: none; +} + +.ui5-ai-input-root { + width: 100%; + height: 100%; + border-right: none; +} + +.ui5-ai-input-icon { + cursor: pointer; + color: var(--sapButton_Selected_TextColor); + border: none; +} + +.ui5-ai-input-icon:hover { + background-color: white; +} + +.ui5-input-ai-icon { + height: var(--_ui5_input_icon_wrapper_height); + padding: 0; + box-sizing: border-box; + width: var(--_ui5_input_icon_width); + min-width: var(--_ui5_input_icon_width); + display: flex; + justify-content: center; + align-items: center; +} + +.ui5-input-ai-icon:hover { + box-shadow: var(--sapField_Hover_Shadow); +} + +/* .ui5-ai-input-icon:active { + border: 1px solid var(--sapField_Hover_BorderColor); +} */ + +.ui5-input-icon-menu-open { + /* border: 1px solid var(--sapField_Hover_Shadow); */ + box-shadow: var(--sapField_Hover_Shadow); +} + +.ui5-ai-input1-busy-indicator{ + width: 100%; + height: 100%; +} + +.ui5-ai-input-icon-hidden { + visibility: hidden; + opacity: 0; +} + +[ui5-button].ai-arrow-button { + border: none; +} + +.ai-arrow-button:hover { + background-color: none; +} + +.ui5-ai-versioning-menu-footer-hidden { + display: none; +} + +#arrow-left:hover { + background-color: white; + border: 1px solid lightblue; +} + + +[ui5-menu-item].ui5-ai-versioning-menu-footer:hover { + background-color: inherit; +} \ No newline at end of file diff --git a/packages/ai/test/pages/AIInput.html b/packages/ai/test/pages/AIInput.html new file mode 100644 index 000000000000..f04040147d3d --- /dev/null +++ b/packages/ai/test/pages/AIInput.html @@ -0,0 +1,321 @@ + + + + + + AI Input Demo + + + + + + +

AI Input Demo

+ + + + + + + + + + + diff --git a/packages/main/src/Menu.ts b/packages/main/src/Menu.ts index 45f5df7db550..55cc4b4c62a1 100644 --- a/packages/main/src/Menu.ts +++ b/packages/main/src/Menu.ts @@ -282,12 +282,13 @@ class Menu extends UI5Element { /** Returns all menu items (including those in groups */ get _allMenuItems() { const items: MenuItem[] = []; + const slottedItems = this.getSlottedNodes("items"); - this.items.forEach(item => { + slottedItems.forEach(item => { if (isInstanceOfMenuItemGroup(item)) { items.push(...item._menuItems); } else if (!isInstanceOfMenuSeparator(item)) { - items.push(item as MenuItem); + items.push(item); } }); diff --git a/packages/main/test/pages/Menu.html b/packages/main/test/pages/Menu.html index d162c4aa727e..563818a11679 100644 --- a/packages/main/test/pages/Menu.html +++ b/packages/main/test/pages/Menu.html @@ -44,6 +44,12 @@ +