Skip to content

Commit

Permalink
Merge branch 'main' into beyackle/focusAfterLoad
Browse files Browse the repository at this point in the history
  • Loading branch information
beyackle authored Mar 29, 2021
2 parents 9b60bcc + 5417fb1 commit 73d71c0
Show file tree
Hide file tree
Showing 106 changed files with 1,892 additions and 962 deletions.
18 changes: 6 additions & 12 deletions Composer/packages/adaptive-form/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
},
"license": "MIT",
"peerDependencies": {
"@bfc/code-editor": "*",
"@bfc/extension-client": "*",
"@bfc/intellisense": "*",
"@uifabric/fluent-theme": "^7.1.4",
"@uifabric/icons": "^7.3.0",
"@uifabric/styling": "^7.7.4",
Expand All @@ -30,20 +27,17 @@
"react-dom": "16.13.1"
},
"devDependencies": {
"@bfc/code-editor": "*",
"@bfc/extension-client": "*",
"@bfc/intellisense": "*",
"@botframework-composer/test-utils": "*",
"@types/lodash": "^4.14.149",
"@types/react": "16.9.23",
"format-message": "^6.2.3",
"react": "16.13.1",
"react-dom": "16.13.1"
"@types/react": "16.9.23"
},
"dependencies": {
"@bfc/built-in-functions": "*",
"@bfc/code-editor": "*",
"@bfc/extension-client": "*",
"@bfc/intellisense": "*",
"@emotion/core": "^10.0.27",
"lodash": "^4.17.19",
"react-error-boundary": "^1.2.5",
"@bfc/built-in-functions": "*"
"react-error-boundary": "^1.2.5"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { DirectionalHint } from 'office-ui-fabric-react/lib/ContextualMenu';
import React from 'react';
import { Callout } from 'office-ui-fabric-react/lib/Callout';
import { FieldToolbar } from '@bfc/code-editor';
import { useShellApi } from '@bfc/extension-client';

const inputs = ['input', 'textarea'];

type Props = {
container: HTMLDivElement | null;
target: HTMLInputElement | HTMLTextAreaElement | null;
value?: string;
onChange: (expression: string) => void;
onClearTarget: () => void;
};

const jsFieldToolbarMenuClassName = 'js-field-toolbar-menu';

export const ExpressionFieldToolbar = (props: Props) => {
const { onClearTarget, container, target, value = '', onChange } = props;
const { projectId, shellApi } = useShellApi();

const [memoryVariables, setMemoryVariables] = React.useState<string[] | undefined>();

React.useEffect(() => {
const abortController = new AbortController();
(async () => {
try {
const variables = await shellApi.getMemoryVariables(projectId, { signal: abortController.signal });
setMemoryVariables(variables);
} catch (e) {
// error can be due to abort
}
})();
}, [projectId]);

React.useEffect(() => {
const keyDownHandler = (e: KeyboardEvent) => {
if (
e.key === 'Escape' &&
(!document.activeElement || inputs.includes(document.activeElement.tagName.toLowerCase()))
) {
onClearTarget();
}
};

const focusHandler = (e: FocusEvent) => {
if (container?.contains(e.target as Node)) {
return;
}

if (
!e
.composedPath()
.filter((n) => n instanceof Element)
.map((n) => (n as Element).className)
.some((c) => c.indexOf(jsFieldToolbarMenuClassName) !== -1)
) {
onClearTarget();
}
};

document.addEventListener('focusin', focusHandler);
document.addEventListener('keydown', keyDownHandler);

return () => {
document.removeEventListener('focusin', focusHandler);
document.removeEventListener('keydown', keyDownHandler);
};
}, [container, onClearTarget]);

const onSelectToolbarMenuItem = React.useCallback(
(text: string) => {
if (typeof target?.selectionStart === 'number') {
const start = target.selectionStart;
const end = typeof target?.selectionEnd === 'number' ? target.selectionEnd : target.selectionStart;

const updatedItem = [value.slice(0, start), text, value.slice(end)].join('');
onChange(updatedItem);

setTimeout(() => {
target.setSelectionRange(updatedItem.length, updatedItem.length);
}, 0);
}

target?.focus();
},
[target, value, onChange]
);
return target ? (
<Callout
doNotLayer
directionalHint={DirectionalHint.topLeftEdge}
gapSpace={2}
isBeakVisible={false}
target={target}
>
<FieldToolbar
key="field-toolbar"
dismissHandlerClassName={jsFieldToolbarMenuClassName}
excludedToolbarItems={['template']}
properties={memoryVariables}
onSelectToolbarMenuItem={onSelectToolbarMenuItem}
/>
</Callout>
) : null;
};

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React, { useRef, useState } from 'react';

import { getIntellisenseUrl } from '../../utils/getIntellisenseUrl';
import { ExpressionSwitchWindow } from '../expressions/ExpressionSwitchWindow';
import { ExpressionsListMenu } from '../expressions/ExpressionsListMenu';
import { ExpressionFieldToolbar } from '../expressions/ExpressionFieldToolbar';

import { JsonField } from './JsonField';
import { NumberField } from './NumberField';
Expand Down Expand Up @@ -68,23 +68,25 @@ export const IntellisenseExpressionField: React.FC<FieldProps<string>> = (props)
const scopes = ['expressions', 'user-variables'];
const intellisenseServerUrlRef = useRef(getIntellisenseUrl());

const [expressionsListContainerElements, setExpressionsListContainerElements] = useState<HTMLDivElement[]>([]);
const [containerElm, setContainerElm] = useState<HTMLDivElement | null>(null);
const [toolbarTargetElm, setToolbarTargetElm] = useState<HTMLInputElement | HTMLTextAreaElement | null>(null);

const focus = React.useCallback(
(id: string, value?: string, event?: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (event?.target) {
event.stopPropagation();
setToolbarTargetElm(event.target as HTMLInputElement | HTMLTextAreaElement);
}
},
[]
);

const completionListOverrideResolver = (value: string) => {
return value === '=' ? (
<ExpressionsListMenu
onExpressionSelected={(expression: string) => onChange(expression)}
onMenuMount={(refs) => {
setExpressionsListContainerElements(refs);
}}
/>
) : null;
};
const onClearTarget = React.useCallback(() => {
setToolbarTargetElm(null);
}, []);

return (
<Intellisense
completionListOverrideContainerElements={expressionsListContainerElements}
completionListOverrideResolver={completionListOverrideResolver}
focused={defaultFocused}
id={`intellisense-${id}`}
scopes={scopes}
Expand All @@ -102,18 +104,28 @@ export const IntellisenseExpressionField: React.FC<FieldProps<string>> = (props)
onKeyUpTextField,
onClickTextField,
}) => (
<StringField
{...props}
cursorPosition={cursorPosition}
focused={focused}
id={id}
value={textFieldValue}
onBlur={noop} // onBlur managed by Intellisense
onChange={(newValue) => onValueChanged(newValue || '')}
onClick={onClickTextField}
onKeyDown={onKeyDownTextField}
onKeyUp={onKeyUpTextField}
/>
<div ref={setContainerElm}>
<StringField
{...props}
cursorPosition={cursorPosition}
focused={focused}
id={id}
value={textFieldValue}
onBlur={noop} // onBlur managed by Intellisense
onChange={(newValue) => onValueChanged(newValue || '')}
onClick={onClickTextField}
onFocus={focus}
onKeyDown={onKeyDownTextField}
onKeyUp={onKeyUpTextField}
/>
<ExpressionFieldToolbar
container={containerElm}
target={toolbarTargetElm}
value={textFieldValue}
onChange={onChange}
onClearTarget={onClearTarget}
/>
</div>
)}
</Intellisense>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const StringField: React.FC<FieldProps<string>> = function StringField(pr
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
if (typeof onFocus === 'function') {
e.stopPropagation();
onFocus(id, value);
onFocus(id, value, e);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('<StringField />', () => {
const input = getByLabelText('a label');

fireEvent.focus(input);
expect(onFocus).toHaveBeenCalledWith('string field', 'string value');
expect(onFocus).toHaveBeenCalledWith('string field', 'string value', expect.any(Object));

fireEvent.blur(input);
expect(onBlur).toHaveBeenCalledWith('string field', 'string value');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
projectMetaDataState,
botProjectIdsState,
dialogState,
luFilesState,
luFilesSelectorFamily,
} from '../../../src/recoilModel';

const state = {
Expand Down Expand Up @@ -63,7 +63,7 @@ describe('Root Bot External Service', () => {
set(dialogState({ projectId: state.projectId, dialogId: state.dialogs[0].id }), state.dialogs[0]);
set(dialogState({ projectId: state.projectId, dialogId: state.dialogs[1].id }), state.dialogs[1]);
set(botProjectIdsState, state.botProjectIdsState);
set(luFilesState(state.projectId), state.luFiles);
set(luFilesSelectorFamily(state.projectId), state.luFiles);
set(projectMetaDataState(state.projectId), state.projectMetaDataState);
set(settingsState(state.projectId), state.settings);
set(dispatcherState, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
formDialogSchemaIdsState,
jsonSchemaFilesState,
lgFilesSelectorFamily,
luFilesState,
luFilesSelectorFamily,
schemasState,
settingsState,
} from '../../../../src/recoilModel';
Expand Down Expand Up @@ -110,7 +110,7 @@ describe('<DiagnosticList/>', () => {
set(currentProjectIdState, state.projectId);
set(botProjectIdsState, [state.projectId]);
set(dialogIdsState(state.projectId), []);
set(luFilesState(state.projectId), state.luFiles);
set(luFilesSelectorFamily(state.projectId), state.luFiles);
set(lgFilesSelectorFamily(state.projectId), state.lgFiles);
set(jsonSchemaFilesState(state.projectId), state.jsonSchemaFiles);
set(botDiagnosticsState(state.projectId), state.diagnostics);
Expand Down
Loading

0 comments on commit 73d71c0

Please sign in to comment.