@awsui/components-react
Version:
On July 19th, 2022, we launched [Cloudscape Design System](https://cloudscape.design). Cloudscape is an evolution of AWS-UI. It consists of user interface guidelines, front-end components, design resources, and development tools for building intuitive, en
158 lines • 12.6 kB
JavaScript
import { __rest } from "tslib";
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import clsx from 'clsx';
import { useContainerQuery } from '@awsui/component-toolkit';
import { useCurrentMode } from '@awsui/component-toolkit/internal';
import { useInternalI18n } from '../i18n/context';
import { getBaseProps } from '../internal/base-component';
import { useFormFieldContext } from '../internal/context/form-field-context';
import { fireNonCancelableEvent } from '../internal/events';
import useForwardFocus from '../internal/hooks/forward-focus';
import useBaseComponent from '../internal/hooks/use-base-component';
import { useControllable } from '../internal/hooks/use-controllable';
import { useMergeRefs } from '../internal/hooks/use-merge-refs';
import { useUniqueId } from '../internal/hooks/use-unique-id';
import { useVisualRefresh } from '../internal/hooks/use-visual-mode';
import { KeyCode } from '../internal/keycode';
import { applyDisplayName } from '../internal/utils/apply-display-name';
import InternalLiveRegion from '../live-region/internal';
import ErrorScreen from './error-screen';
import { useChangeEffect } from './listeners';
import LoadingScreen from './loading-screen';
import { Pane } from './pane';
import PreferencesModal from './preferences-modal';
import { ResizableBox } from './resizable-box';
import { setupEditor } from './setup-editor';
import { StatusBar } from './status-bar';
import { useEditor, useSyncEditorLabels, useSyncEditorLanguage, useSyncEditorSize, useSyncEditorTheme, useSyncEditorValue, useSyncEditorWrapLines, } from './use-editor';
import { DEFAULT_AVAILABLE_THEMES, getDefaultTheme, getLanguageLabel } from './util';
import styles from './styles.css.js';
const CodeEditor = forwardRef((props, ref) => {
var _a, _b;
const { ace, value, language, i18nStrings, editorContentHeight, onEditorContentResize, ariaLabel, languageLabel: customLanguageLabel, preferences, loading, themes, getModalRoot, removeModalRoot } = props, rest = __rest(props, ["ace", "value", "language", "i18nStrings", "editorContentHeight", "onEditorContentResize", "ariaLabel", "languageLabel", "preferences", "loading", "themes", "getModalRoot", "removeModalRoot"]);
const { __internalRootRef } = useBaseComponent('CodeEditor', { props: { language } });
const { controlId, ariaLabelledby, ariaDescribedby } = useFormFieldContext(props);
const [editorHeight = 480, setEditorHeight] = useControllable(editorContentHeight, onEditorContentResize, 480, {
componentName: 'code-editor',
changeHandler: 'onEditorContentResize',
controlledProp: 'editorContentHeight',
});
const mode = useCurrentMode(__internalRootRef);
const isRefresh = useVisualRefresh();
const baseProps = getBaseProps(rest);
const i18n = useInternalI18n('code-editor');
const errorsTabRef = useRef(null);
const warningsTabRef = useRef(null);
const [codeEditorWidth, codeEditorMeasureRef] = useContainerQuery(rect => rect.contentBoxWidth);
const mergedRef = useMergeRefs(codeEditorMeasureRef, __internalRootRef);
const paneId = useUniqueId('code-editor-pane');
const [paneStatus, setPaneStatus] = useState('hidden');
const [annotations, setAnnotations] = useState([]);
const [highlightedAnnotation, setHighlightedAnnotation] = useState();
const [cursorPosition, setCursorPosition] = useState({ row: 0, column: 0 });
const [isTabFocused, setTabFocused] = useState(false);
const { editorRef, editor } = useEditor(ace, themes, loading);
useForwardFocus(ref, editorRef);
useEffect(() => {
if (!ace || !editor) {
return;
}
setupEditor(ace, editor, setAnnotations, setCursorPosition, setHighlightedAnnotation, setPaneStatus);
return () => {
editor === null || editor === void 0 ? void 0 : editor.destroy();
};
}, [ace, editor]);
useSyncEditorLabels(editor, { controlId, ariaLabel, ariaLabelledby, ariaDescribedby });
const { onResize } = useSyncEditorSize(editor, { width: codeEditorWidth, height: editorContentHeight });
useSyncEditorValue(editor, value);
useSyncEditorLanguage(editor, language);
useSyncEditorWrapLines(editor, preferences === null || preferences === void 0 ? void 0 : preferences.wrapLines);
const defaultTheme = getDefaultTheme(mode, themes);
useSyncEditorTheme(editor, (_a = preferences === null || preferences === void 0 ? void 0 : preferences.theme) !== null && _a !== void 0 ? _a : defaultTheme);
// Change listeners
useChangeEffect(editor, props.onChange, props.onDelayedChange);
// Hide error panel when there are no errors to show.
useEffect(() => {
if (annotations.length === 0) {
setPaneStatus('hidden');
}
if (props.onValidate) {
fireNonCancelableEvent(props.onValidate, { annotations });
}
}, [annotations, props.onValidate]);
const languageLabel = customLanguageLabel !== null && customLanguageLabel !== void 0 ? customLanguageLabel : getLanguageLabel(language);
const errorCount = annotations.filter(a => a.type === 'error').length;
const warningCount = annotations.filter(a => a.type === 'warning').length;
const currentAnnotations = useMemo(() => annotations.filter(a => a.type === paneStatus), [annotations, paneStatus]);
/*
* Callbacks
*/
const onEditorKeydown = useCallback((e) => {
if (editor && e.target === editor.container && e.keyCode === KeyCode.enter) {
e.stopPropagation();
e.preventDefault();
editor.focus();
}
}, [editor]);
const onTabFocus = useCallback(() => setTabFocused(true), []);
const onTabBlur = useCallback(() => setTabFocused(false), []);
const onErrorPaneToggle = useCallback(() => {
setPaneStatus(paneStatus !== 'error' ? 'error' : 'hidden');
}, [paneStatus]);
const onWarningPaneToggle = useCallback(() => {
setPaneStatus(paneStatus !== 'warning' ? 'warning' : 'hidden');
}, [paneStatus]);
const onPaneClose = () => {
setPaneStatus('hidden');
};
const onAnnotationClick = ({ row = 0, column = 0 }) => {
if (!editor) {
return;
}
editor.focus();
editor.gotoLine(row + 1, column, false);
setHighlightedAnnotation(undefined);
};
const onAnnotationClear = useCallback(() => {
setHighlightedAnnotation(undefined);
}, []);
const [isPreferencesModalVisible, setPreferencesModalVisible] = useState(false);
const onPreferencesOpen = () => setPreferencesModalVisible(true);
const onPreferencesConfirm = (p) => {
fireNonCancelableEvent(props.onPreferencesChange, p);
setPreferencesModalVisible(false);
};
const onPreferencesDismiss = () => setPreferencesModalVisible(false);
const isPaneVisible = paneStatus !== 'hidden';
return (React.createElement("div", Object.assign({}, baseProps, { className: clsx(styles['code-editor'], baseProps.className, { [styles['code-editor-refresh']]: isRefresh }), ref: mergedRef }),
loading && (React.createElement(LoadingScreen, null,
React.createElement(InternalLiveRegion, { tagName: "span" }, i18n('i18nStrings.loadingState', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.loadingState)))),
!ace && !loading && (React.createElement(ErrorScreen, { recoveryText: i18n('i18nStrings.errorStateRecovery', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.errorStateRecovery), onRecoveryClick: props.onRecoveryClick }, i18n('i18nStrings.errorState', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.errorState))),
ace && !loading && (React.createElement(React.Fragment, null,
React.createElement(ResizableBox, { height: Math.max(editorHeight, 20), minHeight: 20, onResize: height => {
setEditorHeight(height);
onResize();
fireNonCancelableEvent(onEditorContentResize, { height });
} },
React.createElement("div", { ref: editorRef, className: clsx(styles.editor, styles.ace, isRefresh && styles['editor-refresh']), onKeyDown: onEditorKeydown, tabIndex: 0, role: "group", "aria-label": i18n('i18nStrings.editorGroupAriaLabel', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.editorGroupAriaLabel) })),
React.createElement("div", { role: "group", "aria-label": i18n('i18nStrings.statusBarGroupAriaLabel', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.statusBarGroupAriaLabel) },
React.createElement(StatusBar, { languageLabel: languageLabel, cursorPosition: i18n('i18nStrings.cursorPosition', (_b = i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.cursorPosition) === null || _b === void 0 ? void 0 : _b.call(i18nStrings, cursorPosition.row + 1, cursorPosition.column + 1), format => format({ row: cursorPosition.row + 1, column: cursorPosition.column + 1 })), errorCount: errorCount, warningCount: warningCount, paneStatus: paneStatus, onErrorPaneToggle: onErrorPaneToggle, onWarningPaneToggle: onWarningPaneToggle, onTabFocus: onTabFocus, onTabBlur: onTabBlur, errorsTabRef: errorsTabRef, warningsTabRef: warningsTabRef, i18nStrings: i18nStrings, isTabFocused: isTabFocused, paneId: isPaneVisible ? paneId : undefined, onPreferencesOpen: onPreferencesOpen, isRefresh: isRefresh }),
React.createElement(Pane, { id: paneId, paneStatus: paneStatus, visible: isPaneVisible, annotations: currentAnnotations, highlighted: highlightedAnnotation, onAnnotationClick: onAnnotationClick, onAnnotationClear: onAnnotationClear, onClose: onPaneClose, cursorPositionLabel: i18n('i18nStrings.cursorPosition', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.cursorPosition, format => (row, column) => format({ row, column })), closeButtonAriaLabel: i18n('i18nStrings.paneCloseButtonAriaLabel', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.paneCloseButtonAriaLabel) })),
isPreferencesModalVisible && (React.createElement(PreferencesModal, { getModalRoot: getModalRoot, removeModalRoot: removeModalRoot, onConfirm: onPreferencesConfirm, onDismiss: onPreferencesDismiss, themes: themes !== null && themes !== void 0 ? themes : DEFAULT_AVAILABLE_THEMES, preferences: preferences, defaultTheme: defaultTheme, i18nStrings: {
header: i18n('i18nStrings.preferencesModalHeader', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.preferencesModalHeader),
cancel: i18n('i18nStrings.preferencesModalCancel', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.preferencesModalCancel),
confirm: i18n('i18nStrings.preferencesModalConfirm', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.preferencesModalConfirm),
wrapLines: i18n('i18nStrings.preferencesModalWrapLines', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.preferencesModalWrapLines),
theme: i18n('i18nStrings.preferencesModalTheme', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.preferencesModalTheme),
lightThemes: i18n('i18nStrings.preferencesModalLightThemes', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.preferencesModalLightThemes),
darkThemes: i18n('i18nStrings.preferencesModalDarkThemes', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.preferencesModalDarkThemes),
themeFilteringAriaLabel: i18n('i18nStrings.preferencesModalThemeFilteringAriaLabel', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.preferencesModalThemeFilteringAriaLabel),
themeFilteringPlaceholder: i18n('i18nStrings.preferencesModalThemeFilteringPlaceholder', i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.preferencesModalThemeFilteringPlaceholder),
themeFilteringClearAriaLabel: i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.preferencesModalThemeFilteringClearAriaLabel,
} }))))));
});
applyDisplayName(CodeEditor, 'CodeEditor');
export default CodeEditor;
//# sourceMappingURL=index.js.map