Skip to content

Commit

Permalink
feat: improved command selector with scroll and scroll into view
Browse files Browse the repository at this point in the history
  • Loading branch information
eugene yevhen andruszczenko committed Oct 3, 2024
1 parent c362c29 commit c7e8670
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 58 deletions.
14 changes: 12 additions & 2 deletions example/dev.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
import open from 'open';
await open('./dist/index.html');
// Dynamic import handling
const openFile = async () => {
try {
// Use dynamic import to load the ES module
const open = (await import('open')).default;
await open('./dist/index.html');
} catch (err) {
console.error('Error opening file:', err);
}
};

openFile();
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"packdemo": "cd ./example && npm run pack",
"watch": "webpack --config webpack.config.js --mode development --watch",
"watch:example": "cd ./example && npm run watch",
"watch:web": "run-p watch watch:example serve:example",
"serve:example": "live-server --port=9000 example/dist",
"start:example": "cd ./example && npm install && npm run build && npm run dev",
"dev": "npm run clean && npm install && npm run build && npm run start:example",
"lint-fix": "npx eslint \"./**\" --fix",
Expand Down
25 changes: 22 additions & 3 deletions src/components/chat-item/chat-prompt-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,16 @@ export class ChatPromptInput {
});

this.quickPickOpen = true;

const titleElements = document.querySelectorAll('.mynah-chat-command-selector-group-title');
if (titleElements.length > 0) {
const observer = new IntersectionObserver(
([ e ]) => e.target.classList.toggle('stuck', e.intersectionRatio < 1),
{ threshold: [ 1 ] }
);

titleElements.forEach((element) => observer.observe(element));
}
}
}
} else {
Expand Down Expand Up @@ -286,11 +296,20 @@ export class ChatPromptInput {
}

if (nextElementIndex !== -1) {
// Remove the active class from the previously selected command
commandElements[lastActiveElement]?.classList.remove('target-command');
commandElements[nextElementIndex].classList.add('target-command');
if (commandElements[nextElementIndex].getAttribute('prompt') !== null) {
this.promptTextInput.updateTextInputValue(commandElements[nextElementIndex].getAttribute('prompt') as string);

// Add the active class to the new selected command
const selectedElement = commandElements[nextElementIndex];
selectedElement.classList.add('target-command');

// Update the input value with the selected command
if (selectedElement.getAttribute('prompt') !== null) {
this.promptTextInput.updateTextInputValue(selectedElement.getAttribute('prompt') as string);
}

// Ensure the selected command is scrolled into view
selectedElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
} else {
if (this.quickPick != null) {
Expand Down
80 changes: 47 additions & 33 deletions src/components/overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,25 @@ export class Overlay {
private readonly innerContainer: ExtendedHTMLElement;
private readonly guid = generateUID();
private readonly onClose;
horizontalDirection: OverlayHorizontalDirection;
verticalDirection: OverlayVerticalDirection;
referenceElement: HTMLElement | ExtendedHTMLElement | undefined;
referencePoint: { top: number; left: number } | undefined;
stretchWidth: boolean;

constructor (props: OverlayProps) {
const horizontalDirection = props.horizontalDirection ?? OverlayHorizontalDirection.TO_RIGHT;
const verticalDirection = props.verticalDirection ?? OverlayVerticalDirection.START_TO_BOTTOM;
this.horizontalDirection = props.horizontalDirection ?? OverlayHorizontalDirection.TO_RIGHT;
this.verticalDirection = props.verticalDirection ?? OverlayVerticalDirection.START_TO_BOTTOM;
this.referenceElement = props.referenceElement;
this.referencePoint = props.referencePoint;
this.stretchWidth = props.stretchWidth ?? false;
this.onClose = props.onClose;
const dimOutside = props.dimOutside !== false;
const closeOnOutsideClick = props.closeOnOutsideClick !== false;

const calculatedTop = this.getCalculatedTop(verticalDirection, props.referenceElement, props.referencePoint);
const calculatedLeft = this.getCalculatedLeft(horizontalDirection, props.referenceElement, props.referencePoint);
const calculatedWidth = props.stretchWidth === true ? this.getCalculatedWidth(props.referenceElement) : 0;
const calculatedTop = this.getCalculatedTop();
const calculatedLeft = this.getCalculatedLeft();
const calculatedWidth = this.stretchWidth ? this.getCalculatedWidth() : 0;

this.innerContainer = DomBuilder.getInstance().build({
type: 'div',
Expand All @@ -102,7 +110,7 @@ export class Overlay {

this.container = DomBuilder.getInstance().build({
type: 'div',
classNames: [ 'mynah-overlay-container', horizontalDirection, verticalDirection, props.background !== false ? 'background' : '' ],
classNames: [ 'mynah-overlay-container', this.horizontalDirection, this.verticalDirection, props.background !== false ? 'background' : '' ],
attributes: {
style: `top: ${calculatedTop}px; left: ${calculatedLeft}px; ${calculatedWidth !== 0 ? `width: ${calculatedWidth}px;` : ''}`,
},
Expand Down Expand Up @@ -159,19 +167,35 @@ export class Overlay {
this.container.style.left = `${effectiveLeft - (lastContainerRect.left + lastContainerRect.width + OVERLAY_MARGIN - winWidth)}px`;
}

window.addEventListener('resize', this.calculatePosition);

// we need to delay the class toggle
// to avoid the skipping of the transition comes from css
// for a known js-css relation problem
setTimeout(() => {
this.render.addClass('mynah-overlay-open');

this.calculatePosition();
if (closeOnOutsideClick) {
window.addEventListener('blur', this.windowBlurHandler.bind(this));
window.addEventListener('resize', this.windowBlurHandler.bind(this));
// window.addEventListener('resize', this.windowBlurHandler.bind(this));
}
}, 10);
}

private readonly calculatePosition = (): void => {
const top = this.getCalculatedTop();
const left = this.getCalculatedLeft();
const width = this.getCalculatedWidth();

[
{ prop: 'width', value: width },
{ prop: 'top', value: top },
{ prop: 'left', value: left }
].forEach(({ prop, value }) => {
this.container.style.setProperty(prop, `${Math.round(value)}px`);
});
};

close = (): void => {
this.render.removeClass('mynah-overlay-open');
// In this timeout, we're waiting the close animation to be ended
Expand All @@ -189,19 +213,15 @@ export class Overlay {
window.removeEventListener('resize', this.windowBlurHandler.bind(this));
};

private readonly getCalculatedLeft = (
horizontalDirection: OverlayHorizontalDirection,
referenceElement?: HTMLElement | ExtendedHTMLElement,
referencePoint?: { top?: number; left: number }
): number => {
private readonly getCalculatedLeft = (): number => {
const referenceRectangle =
referenceElement !== undefined
? referenceElement.getBoundingClientRect()
: referencePoint !== undefined
? { left: referencePoint.left, width: 0 }
this.referenceElement !== undefined
? this.referenceElement.getBoundingClientRect()
: this.referencePoint !== undefined
? { left: this.referencePoint.left, width: 0 }
: { left: 0, width: 0 };

switch (horizontalDirection.toString()) {
switch (this.horizontalDirection.toString()) {
case OverlayHorizontalDirection.TO_RIGHT:
return referenceRectangle.left + referenceRectangle.width + OVERLAY_MARGIN;
case OverlayHorizontalDirection.START_TO_RIGHT:
Expand All @@ -217,27 +237,21 @@ export class Overlay {
}
};

private readonly getCalculatedWidth = (
referenceElement?: HTMLElement | ExtendedHTMLElement
): number => {
return referenceElement !== undefined
? referenceElement.getBoundingClientRect().width
private readonly getCalculatedWidth = (): number => {
return this.referenceElement !== undefined
? this.referenceElement.getBoundingClientRect().width
: 0;
};

private readonly getCalculatedTop = (
verticalDirection: OverlayVerticalDirection,
referenceElement?: HTMLElement | ExtendedHTMLElement,
referencePoint?: { top: number; left?: number }
): number => {
private readonly getCalculatedTop = (): number => {
const referenceRectangle =
referenceElement !== undefined
? referenceElement.getBoundingClientRect()
: referencePoint !== undefined
? { top: referencePoint.top, height: 0 }
this.referenceElement !== undefined
? this.referenceElement.getBoundingClientRect()
: this.referencePoint !== undefined
? { top: this.referencePoint.top, height: 0 }
: { top: 0, height: 0 };

switch (verticalDirection.toString()) {
switch (this.verticalDirection.toString()) {
case OverlayVerticalDirection.TO_BOTTOM:
return referenceRectangle.top + referenceRectangle.height + OVERLAY_MARGIN;
case OverlayVerticalDirection.START_TO_BOTTOM:
Expand Down
20 changes: 9 additions & 11 deletions src/styles/components/_overlay.scss
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
&.horizontal-direction {
&-to-left,
&-from-end-to-left {
--overlayLeftPos: -100%;
--overlayLeftPos: calc(-100%);
--overlayInnerHorizontalOrigin: right;
&:before {
transform-origin: right center;
Expand Down Expand Up @@ -142,7 +142,9 @@
transition-delay: 0ms;
}

transform: translate3d(var(--overlayLeftPos), var(--overlayTopPos), 0);
transform: translate3d(var(--overlayLeftPos), var(--overlayTopPos), 0) scale(1);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
&:not(.mynah-overlay-open) {
&,
Expand All @@ -155,17 +157,13 @@
> .mynah-overlay-container {
> .mynah-overlay-inner-container {
transform: translate3d(0, 0, 0) scale(1);
}
&:before {
transform: translate3d(0, 0, 0) scale(1);
transition-delay: 50ms;
opacity: 1;
}
> .mynah-overlay-inner-container {
transition-delay: 20ms;
}
> .mynah-overlay-inner-container {
opacity: 1;
&:before {
transform: translate3d(0, 0, 0) scale(1);
transition-delay: 50ms;
opacity: 1;
}
}
&:after {
transition-delay: 0ms;
Expand Down
40 changes: 32 additions & 8 deletions src/styles/components/chat/_chat-command-selector.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
box-sizing: border-box;
background-color: var(--mynah-card-bg);
border-radius: var(--mynah-card-radius);
width: 100%;
pointer-events: all;
flex-flow: column nowrap;
align-items: stretch;
justify-content: flex-start;
max-height: 80vh;
overflow-x: hidden;
padding: var(--mynah-sizing-4);
overflow-y: auto;
margin-top: var(--mynah-sizing-4);
padding: 0 var(--mynah-sizing-4) var(--mynah-sizing-4) var(--mynah-sizing-4);
overflow: hidden auto;
scroll-snap-type: y mandatory;
scroll-behavior: smooth;
@include list-fader-bottom();
> .mynah-chat-command-selector-group {
display: flex;
Expand All @@ -25,12 +26,33 @@
> .mynah-chat-command-selector-group-title {
margin: 0;
color: var(--mynah-color-text-strong);
padding: 0 var(--mynah-sizing-3);
margin-bottom: var(--mynah-sizing-1);
padding: var(--mynah-sizing-3) var(--mynah-sizing-3) 0 var(--mynah-sizing-3);
margin-bottom: var(--mynah-sizing-3);
font-size: var(--mynah-font-size-large);
position: relative;
border-radius: var(--mynah-input-radius);
overflow: hidden;
position: sticky;
top: -1px;
background-color: var(--mynah-card-bg);
z-index: 1;
outline: solid var(--mynah-sizing-4) var(--mynah-card-bg);

&:before {
content: '';
position: absolute;
top: calc(var(--mynah-sizing-3) * -2);
left: calc(var(--mynah-sizing-3) * -2);
background-color: var(--mynah-card-bg);
z-index: -1;
right: calc(var(--mynah-sizing-3) * -2);
bottom: calc(var(--mynah-sizing-3) * -2);
padding: var(--mynah-sizing-3);
}

&.stuck {
box-shadow:
0px calc(var(--mynah-sizing-4) + 3px) 0 var(--mynah-card-bg),
0px calc(var(--mynah-sizing-4) + 4px) 0 rgba(0, 0, 0, 0.075);
}
}

& + .mynah-chat-command-selector-group {
Expand All @@ -54,6 +76,8 @@
border-radius: var(--mynah-input-radius);
transition: var(--mynah-short-transition-rev);
gap: var(--mynah-sizing-1);
scroll-snap-align: center;
scroll-snap-stop: always;
&[disabled='true'] {
&::before {
border-color: transparent !important;
Expand Down
2 changes: 1 addition & 1 deletion ui-tests/src/styles/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ body {
* {
font-stretch: 100%;
letter-spacing: normal;
text-rendering: optimizeSpeed;
text-rendering: optimizeLegibility !important;
-webkit-font-smoothing: antialiased;
}

Expand Down

0 comments on commit c7e8670

Please sign in to comment.