Skip to content

Commit

Permalink
Merge pull request #224 from aws/221-pressing-tab-on-context-selector…
Browse files Browse the repository at this point in the history
…-duplicates-the-subsequent-text-if-it-exists

221 pressing tab on context selector duplicates the subsequent text if it exists
  • Loading branch information
Jurredr authored Jan 17, 2025
2 parents 1b8b9d2 + a152e93 commit cce7cb7
Show file tree
Hide file tree
Showing 13 changed files with 71 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/components/chat-item/chat-item-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface ChatItemCardProps {
chatItem: ChatItem;
inline?: boolean;
small?: boolean;
onAnimationStateChange?: (isAnimating: boolean) => void;
}
export class ChatItemCard {
readonly props: ChatItemCardProps;
Expand Down Expand Up @@ -216,6 +217,7 @@ export class ChatItemCard {
this.render?.addClass('typewriter-animating');
} else {
this.render?.removeClass('typewriter-animating');
this.props.onAnimationStateChange?.(isAnimating);
}
},
children:
Expand Down
4 changes: 2 additions & 2 deletions src/components/chat-item/chat-prompt-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ export class ChatPromptInput {
this.handleContextCommandSelection(commandToSend);
} else {
// Otherwise pass the given text by user
const command = this.promptTextInput.getTextInputValue().substring(this.quickPickTriggerIndex).match(/\S*/gi)?.[0] ?? '';
const command = this.promptTextInput.getTextInputValue().substring(this.quickPickTriggerIndex, this.promptTextInput.getCursorPos());
this.handleContextCommandSelection({ command });
}
} else {
Expand Down Expand Up @@ -467,7 +467,7 @@ export class ChatPromptInput {
[ ...this.quickPickItemGroups ].forEach((quickPickGroup: QuickActionCommandGroup) => {
const newQuickPickCommandGroup = { ...quickPickGroup };
try {
const searchTerm = this.promptTextInput.getTextInputValue().substring(this.quickPickTriggerIndex).match(/\S*/gi)?.[0];
const searchTerm = this.promptTextInput.getTextInputValue().substring(this.quickPickTriggerIndex, this.promptTextInput.getCursorPos());
const promptRegex = new RegExp(searchTerm ?? '', 'gi');
newQuickPickCommandGroup.commands = newQuickPickCommandGroup.commands.filter(command =>
command.command.match(promptRegex)
Expand Down
42 changes: 41 additions & 1 deletion src/components/chat-item/chat-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Config } from '../../helper/config';
import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom';
import { generateUID } from '../../helper/guid';
import { MynahUITabsStore } from '../../helper/tabs-store';
import { CardRenderDetails, ChatItem, ChatItemType, PromptAttachmentType, TabHeaderDetails } from '../../static';
import { CardRenderDetails, ChatItem, ChatItemType, MynahEventNames, PromptAttachmentType, TabHeaderDetails } from '../../static';
import { Button } from '../button';
import { Icon, MynahIcons } from '../icon';
import { ChatItemCard } from './chat-item-card';
Expand All @@ -18,6 +18,7 @@ import '../../styles/components/chat/_chat-wrapper.scss';
import testIds from '../../helper/test-ids';
import { TitleDescriptionWithIcon } from '../title-description-with-icon';
import { GradientBackground } from '../background';
import { MynahUIGlobalEvents } from '../../helper/events';

export const CONTAINER_GAP = 12;
export interface ChatWrapperProps {
Expand All @@ -39,6 +40,8 @@ export class ChatWrapper {
private lastStreamingChatItemCard: ChatItemCard | null;
private lastStreamingChatItemMessageId: string | null;
private allRenderedChatItems: Record<string, ChatItemCard> = {};
private scrollPos: number = 0;
private restoreScrollAfterResize: boolean = false;
render: ExtendedHTMLElement;
constructor (props: ChatWrapperProps) {
this.props = props;
Expand Down Expand Up @@ -74,6 +77,31 @@ export class ChatWrapper {
this.allRenderedChatItems = {};
}
});

MynahUITabsStore.getInstance().addListener('selectedTabChange', (selectedTabId) => {
if (this.props.tabId === selectedTabId) {
setTimeout(() => {
this.chatItemsContainer.scrollTop = this.scrollPos;
}, 10);
}
});
MynahUITabsStore.getInstance().addListener('beforeTabChange', (selectedTabId) => {
if (this.props.tabId !== selectedTabId) {
this.scrollPos = this.chatItemsContainer.scrollTop;
}
});

MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.ROOT_RESIZE, (data: {clientRect: DOMRect}) => {
if (!this.restoreScrollAfterResize && (data.clientRect.height < 10 || data.clientRect.width < 10) && this.props.tabId === MynahUITabsStore.getInstance().getSelectedTabId()) {
this.restoreScrollAfterResize = true;
} else if (this.restoreScrollAfterResize) {
this.restoreScrollAfterResize = false;
setTimeout(() => {
this.chatItemsContainer.scrollTop = this.scrollPos;
}, 10);
}
});

MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'loadingChat', (loadingChat: boolean) => {
if (loadingChat) {
this.render.addClass('loading');
Expand Down Expand Up @@ -153,6 +181,11 @@ export class ChatWrapper {
classNames: [ 'mynah-chat-items-container' ],
persistent: true,
children: [],
events: {
wheel: () => {
this.scrollPos = this.chatItemsContainer.scrollTop;
}
}
});

this.tabHeaderDetails = new TitleDescriptionWithIcon({
Expand Down Expand Up @@ -242,6 +275,11 @@ export class ChatWrapper {
this.removeEmptyCardsAndFollowups();
const currentMessageId: string = (chatItem.messageId != null && chatItem.messageId !== '') ? chatItem.messageId : `TEMP_${generateUID()}`;
const chatItemCard = new ChatItemCard({
onAnimationStateChange: (isAnimating) => {
if (!isAnimating) {
this.scrollPos = this.chatItemsContainer.scrollTop;
}
},
tabId: this.props.tabId,
chatItem: {
...chatItem,
Expand Down Expand Up @@ -271,6 +309,8 @@ export class ChatWrapper {
// Only if it is a PROMPT
this.chatItemsContainer.scrollTop = this.chatItemsContainer.scrollHeight + 500;
}

this.scrollPos = this.chatItemsContainer.scrollTop;
};

private readonly checkLastAnswerStreamChange = (updateWith: Partial<ChatItem>): void => {
Expand Down
17 changes: 17 additions & 0 deletions src/helper/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export interface ExtendedHTMLElement extends HTMLInputElement {
export class DomBuilder {
private static instance: DomBuilder | undefined;
private rootFocus: boolean;
private readonly resizeObserver: ResizeObserver;
private rootBox: DOMRect;
root: ExtendedHTMLElement;
private portals: Record<string, ExtendedHTMLElement> = {};

Expand All @@ -87,6 +89,19 @@ export class DomBuilder {
this.root.addClass('mynah-ui-root');
this.rootFocus = this.root.matches(':focus') ?? false;
this.attachRootFocusListeners();
if (ResizeObserver != null) {
this.rootBox = this.root.getBoundingClientRect();
this.resizeObserver = new ResizeObserver((entry) => {
const incomingRootBox = this.root.getBoundingClientRect();
// Known issue of ResizeObserver, triggers twice for each size change.
// Check if size was really changed then trigger
if (this.rootBox.height !== incomingRootBox.height || this.rootBox.width !== incomingRootBox.width) {
this.rootBox = incomingRootBox;
MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.ROOT_RESIZE, { clientRect: this.rootBox });
}
});
this.resizeObserver.observe(this.root);
}
}

private readonly attachRootFocusListeners = (): void => {
Expand Down Expand Up @@ -126,10 +141,12 @@ export class DomBuilder {
}

setRoot = (rootSelector?: string): void => {
this.resizeObserver.unobserve(this.root);
this.root.removeEventListener('focus', this.onRootFocus);
window.removeEventListener('blur', this.onRootBlur);
this.root = this.extendDomFunctionality((DS(rootSelector ?? 'body')[0] ?? document.body) as HTMLElement);
this.attachRootFocusListeners();
this.resizeObserver.observe(this.root);
};

addClass = function (this: ExtendedHTMLElement, className: string): ExtendedHTMLElement {
Expand Down
6 changes: 4 additions & 2 deletions src/helper/tabs-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface TabStoreSubscription {
'add': Record<string, (tabId: string, tabData?: MynahUITabStoreTab) => void>;
'remove': Record<string, (tabId: string, newSelectedTab?: MynahUITabStoreTab) => void>;
'update': Record<string, (tabId: string, tabData?: MynahUITabStoreTab) => void>;
'beforeTabChange': Record<string, (tabId: string, previousSelectedTab?: MynahUITabStoreTab) => void>;
'selectedTabChange': Record<string, (tabId: string, previousSelectedTab?: MynahUITabStoreTab) => void>;
}
export class EmptyMynahUITabsStoreModel {
Expand All @@ -33,6 +34,7 @@ export class MynahUITabsStore {
add: {},
remove: {},
update: {},
beforeTabChange: {},
selectedTabChange: {}
};

Expand Down Expand Up @@ -89,6 +91,7 @@ export class MynahUITabsStore {
};

public readonly selectTab = (tabId: string): void => {
this.informSubscribers('beforeTabChange', tabId, this.tabsStore[tabId]);
this.deselectAllTabs();
this.tabsStore[tabId].isSelected = true;
this.informSubscribers('selectedTabChange', tabId, this.tabsStore[tabId]);
Expand All @@ -101,8 +104,7 @@ export class MynahUITabsStore {
public updateTab = (tabId: string, tabData?: Partial<MynahUITabStoreTab>, skipSubscribers?: boolean): void => {
if (this.tabsStore[tabId] !== undefined) {
if (tabData?.isSelected === true && this.getSelectedTabId() !== tabId) {
this.deselectAllTabs();
this.informSubscribers('selectedTabChange', tabId);
this.selectTab(tabId);
}
this.tabsStore[tabId] = { ...this.tabsStore[tabId], ...tabData };
if (tabData?.store !== undefined) {
Expand Down
1 change: 1 addition & 0 deletions src/static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ export enum MynahEventNames {
REMOVE_ATTACHMENT = 'removeAttachment',
TAB_BAR_BUTTON_CLICK = 'tabBarButtonClick',
PROMPT_PROGRESS_ACTION_CLICK = 'promptProgressActionClick',
ROOT_RESIZE = 'rootResize',
};

export enum MynahPortalNames {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion ui-tests/__test__/flows/window-boundaries.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Page } from 'playwright/test';
import testIds from '../../../src/helper/test-ids';
import { DEFAULT_VIEWPORT, getOffsetHeight, getSelector, waitForAnimationEnd } from '../helpers';
import { DEFAULT_VIEWPORT, getOffsetHeight, getSelector, justWait, waitForAnimationEnd } from '../helpers';
import { clickToFollowup } from './click-followup';
import { closeTab } from './close-tab';
import { openNewTab } from './open-new-tab';
Expand All @@ -10,6 +10,7 @@ export const checkContentInsideWindowBoundaries = async (page: Page): Promise<vo
await openNewTab(page, false, true);

await page.mouse.move(0, 0);
const chatItemsContainer = await page.waitForSelector(getSelector(testIds.chat.chatItemsContainer));
const footerPanel = await page.waitForSelector(getSelector(testIds.prompt.footerInfo));
expect(footerPanel).toBeDefined();
expect(getOffsetHeight(await footerPanel.boundingBox())).toBeLessThanOrEqual(page.viewportSize()?.height ?? 0);
Expand Down Expand Up @@ -63,6 +64,8 @@ export const checkContentInsideWindowBoundaries = async (page: Page): Promise<vo
// Check if the footer element exceeds from bottom
expect(getOffsetHeight(await footerPanel.boundingBox())).toBeLessThanOrEqual(page.viewportSize()?.height ?? 0);

justWait(500);
await chatItemsContainer.evaluate(node => { node.scrollTop = 0; });
// Snap
expect(await page.screenshot()).toMatchImageSnapshot();
};

0 comments on commit cce7cb7

Please sign in to comment.