diff --git a/libs/components/package.json b/libs/components/package.json
index 6e1467048d..54444d70df 100644
--- a/libs/components/package.json
+++ b/libs/components/package.json
@@ -66,6 +66,11 @@
"import": "./circular-progress.mjs",
"require": "./circular-progress.js"
},
+ "./code-editor": {
+ "types": "./code-editor/code-editor.d.ts",
+ "import": "./code-editor.mjs",
+ "require": "./code-editor.js"
+ },
"./code-snippet": {
"types": "./code-snippet/code-snippet.d.ts",
"import": "./code-snippet.mjs",
@@ -161,6 +166,11 @@
"import": "./menu.mjs",
"require": "./menu.js"
},
+ "./notebook-cell": {
+ "types": "./notebook-cell/notebook-cell.d.ts",
+ "import": "./notebook-cell.mjs",
+ "require": "./notebook-cell.js"
+ },
"./radio": {
"types": "./radio/radio.d.ts",
"import": "./radio.mjs",
diff --git a/libs/components/project.json b/libs/components/project.json
index 9d4aa946f2..e504a83e05 100644
--- a/libs/components/project.json
+++ b/libs/components/project.json
@@ -15,6 +15,12 @@
"build": {
"executor": "nx:run-commands",
"options": {
+ "customWebpackConfig": {
+ "path": "./monaco-webpack.config.js",
+ "mergeRules": {
+ "module.rules": "prepend"
+ }
+ },
"commands": [
"mkdir -p dist/libs/components/",
"vite build --config libs/components/vite.config.js --outDir dist/libs/components",
diff --git a/libs/components/src/code-editor/code-editor.scss b/libs/components/src/code-editor/code-editor.scss
new file mode 100644
index 0000000000..fed91a8618
--- /dev/null
+++ b/libs/components/src/code-editor/code-editor.scss
@@ -0,0 +1,24 @@
+:host {
+ --cv-editor-width: 100%;
+ --cv-editor-height: 100%;
+
+ box-sizing: border-box;
+
+ .monaco-editor {
+ .lines-content.monaco-editor-background {
+ height: var(--cv-editor-height);
+ width: var(--cv-editor-width);
+ }
+ }
+
+ .monaco-editor-background {
+ background-color: var(--cv-theme-surface);
+ }
+}
+
+.editor {
+ height: var(--cv-editor-height);
+ min-height: var(--cv-editor-height);
+ max-width: calc(var(--cv-editor-width) - 2px);
+ width: var(--cv-editor-width);
+}
diff --git a/libs/components/src/code-editor/code-editor.spec.ts b/libs/components/src/code-editor/code-editor.spec.ts
new file mode 100644
index 0000000000..faf43923ae
--- /dev/null
+++ b/libs/components/src/code-editor/code-editor.spec.ts
@@ -0,0 +1,11 @@
+/**
+ * @vitest-environment jsdom
+ */
+import { it, describe, expect } from 'vitest';
+import { CovalentCodeEditor } from './code-editor';
+
+describe('Code editor', () => {
+ it('should work', () => {
+ expect(new CovalentCodeEditor()).toBeDefined();
+ });
+});
diff --git a/libs/components/src/code-editor/code-editor.stories.js b/libs/components/src/code-editor/code-editor.stories.js
new file mode 100644
index 0000000000..77d59eb699
--- /dev/null
+++ b/libs/components/src/code-editor/code-editor.stories.js
@@ -0,0 +1,41 @@
+import './code-editor';
+
+const sqlContent = `
+SELECT * FROM load_to_teradata (
+ ON (
+ SELECT 'class' AS class_col,
+ 'variable' AS variable_col,
+ 'type' AS type_col,
+ category,
+ cnt,
+ 'sum' AS sum_col,
+ 'sumSq',
+ 'totalCnt'
+ FROM aster_nb_modelSC
+ )
+ tdpid ('sdt12432.labs.teradata.com')
+ username ('sample_user')
+ password ('sample_user')
+ target_table ('td_nb_modelSC')
+);
+`;
+
+export default {
+ title: 'Components/Code Editor',
+ args: {
+ theme: 'cv-light',
+ code: sqlContent,
+ language: 'sql',
+ },
+};
+
+const Template = ({ theme, language, code }) => {
+ return `
+
+
+
+
+ `;
+};
+
+export const Basic = Template.bind();
diff --git a/libs/components/src/code-editor/code-editor.theme.ts b/libs/components/src/code-editor/code-editor.theme.ts
new file mode 100644
index 0000000000..4fc921accd
--- /dev/null
+++ b/libs/components/src/code-editor/code-editor.theme.ts
@@ -0,0 +1,94 @@
+import * as tokens from '@covalent/tokens';
+
+export const cvEditorDarkTheme = {
+ base: 'vs-dark',
+ inherit: true,
+ rules: [
+ {
+ token: '',
+ foreground: tokens.CvDarkCodeSnippetColor,
+ background: tokens.CvThemeDarkColorsSurface,
+ },
+ {
+ token: 'comment',
+ foreground: tokens.CvDarkCodeSnippetComment,
+ fontStyle: 'italic',
+ },
+ { token: 'keyword', foreground: tokens.CvDarkCodeSnippetKeyword },
+ { token: 'variable', foreground: tokens.CvDarkCodeSnippetVariable },
+ { token: 'string', foreground: tokens.CvDarkCodeSnippetString },
+ { token: 'number', foreground: tokens.CvDarkCodeSnippetVariable },
+ { token: 'type', foreground: tokens.CvDarkCodeSnippetClass },
+ { token: 'class', foreground: tokens.CvDarkCodeSnippetClass },
+ { token: 'function', foreground: tokens.CvDarkCodeSnippetTitle },
+ { token: 'operator', foreground: tokens.CvDarkCodeSnippetLiteral },
+ { token: 'constant', foreground: tokens.CvDarkCodeSnippetLiteral },
+ { token: 'builtin', foreground: tokens.CvDarkCodeSnippetClass },
+ { token: 'punctuation', foreground: tokens.CvDarkCodeSnippetColor },
+ { token: 'meta', foreground: tokens.CvDarkCodeSnippetTitle },
+ { token: 'tag', foreground: tokens.CvDarkCodeSnippetSelector },
+ { token: 'attribute.name', foreground: tokens.CvDarkCodeSnippetVariable },
+ { token: 'attribute.value', foreground: tokens.CvDarkCodeSnippetString },
+ { token: 'invalid', foreground: '#f44747' },
+ { token: 'strong', fontStyle: 'bold' },
+ { token: 'emphasis', fontStyle: 'italic' },
+ {
+ token: 'link',
+ foreground: tokens.CvDarkCodeSnippetTitle,
+ fontStyle: 'underline',
+ },
+ ],
+ colors: {
+ 'editor.background': tokens.CvThemeDarkColorsSurface,
+ 'editor.foreground': tokens.CvDarkCodeSnippetColor,
+ 'editorCursor.foreground': tokens.CvDarkTextSecondaryOnBackground,
+ },
+};
+
+export const cvEditorLightTheme = {
+ base: 'vs',
+ inherit: true,
+ rules: [
+ {
+ token: '',
+ foreground: tokens.CvLightCodeSnippetColor,
+ background: tokens.CvLightSurfaceCanvas,
+ },
+ {
+ token: 'comment',
+ foreground: tokens.CvLightCodeSnippetComment,
+ fontStyle: 'italic',
+ },
+ { token: 'keyword', foreground: tokens.CvLightCodeSnippetKeyword },
+ { token: 'doctag', foreground: tokens.CvLightCodeSnippetKeyword },
+ { token: 'formula', foreground: tokens.CvLightCodeSnippetKeyword },
+ { token: 'variable', foreground: tokens.CvLightCodeSnippetVariable },
+ { token: 'string', foreground: tokens.CvLightCodeSnippetString },
+ { token: 'number', foreground: tokens.CvLightCodeSnippetVariable },
+ { token: 'type', foreground: tokens.CvLightCodeSnippetClass },
+ { token: 'class', foreground: tokens.CvLightCodeSnippetClass },
+ { token: 'function', foreground: tokens.CvLightCodeSnippetTitle },
+ { token: 'literal', foreground: tokens.CvLightCodeSnippetLiteral },
+ { token: 'operator', foreground: tokens.CvLightCodeSnippetLiteral },
+ { token: 'constant', foreground: tokens.CvLightCodeSnippetLiteral },
+ { token: 'builtin', foreground: tokens.CvLightCodeSnippetClass },
+ { token: 'punctuation', foreground: tokens.CvLightCodeSnippetColor },
+ { token: 'meta', foreground: tokens.CvLightCodeSnippetTitle },
+ { token: 'tag', foreground: tokens.CvLightCodeSnippetSelector },
+ { token: 'attribute.name', foreground: tokens.CvLightCodeSnippetVariable },
+ { token: 'attribute.value', foreground: tokens.CvLightCodeSnippetString },
+ { token: 'invalid', foreground: '#f44747' },
+ { token: 'strong', fontStyle: 'bold' },
+ { token: 'emphasis', fontStyle: 'italic' },
+ {
+ token: 'link',
+ foreground: tokens.CvLightCodeSnippetTitle,
+ fontStyle: 'underline',
+ },
+ ],
+ colors: {
+ 'editor.background': tokens.CvThemeLightColorsSurface,
+ 'editor.foreground': tokens.CvLightCodeSnippetColor,
+ 'editorCursor.foreground': tokens.CvLightTextSecondaryOnBackground,
+ },
+};
diff --git a/libs/components/src/code-editor/code-editor.ts b/libs/components/src/code-editor/code-editor.ts
new file mode 100644
index 0000000000..343ddbd340
--- /dev/null
+++ b/libs/components/src/code-editor/code-editor.ts
@@ -0,0 +1,216 @@
+import { css, html, LitElement, PropertyValues, unsafeCSS } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+import { createRef, Ref, ref } from 'lit/directives/ref.js';
+
+import styles from './code-editor.scss?inline';
+
+// -- Monaco Editor Imports --
+import { editor } from 'monaco-editor/esm/vs/editor/editor.api.js';
+import baseStyles from 'monaco-editor/min/vs/editor/editor.main.css?inline';
+
+// Register all language contributions
+import 'monaco-editor/esm/vs/basic-languages/html/html.contribution';
+import 'monaco-editor/esm/vs/basic-languages/css/css.contribution';
+import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution';
+import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution';
+import 'monaco-editor/esm/vs/basic-languages/python/python.contribution';
+import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution';
+import 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution';
+import 'monaco-editor/esm/vs/basic-languages/r/r.contribution';
+import { cvEditorDarkTheme, cvEditorLightTheme } from './code-editor.theme';
+
+@customElement('cv-code-editor')
+export class CovalentCodeEditor extends LitElement {
+ /**
+ * The container where editor will be created
+ */
+ private container: Ref = createRef();
+ /**
+ * Editor instance
+ */
+ editor?: editor.IStandaloneCodeEditor;
+ /**
+ * Theme of the editor
+ */
+ @property({ type: String }) theme?: string;
+ /**
+ * Language of th editor instance
+ */
+ @property() language?: string;
+ /**
+ * Code entered into the editor
+ */
+ @property() code?: string;
+ /**
+ * Options that can be set for the editor
+ */
+ @property({ type: Object }) options?: editor.IEditorOptions &
+ editor.IGlobalEditorOptions;
+
+ static styles = [
+ css`
+ ${unsafeCSS(baseStyles)} ${unsafeCSS(styles)}
+ `,
+ ];
+
+ private getFile() {
+ if (this.children.length > 0) return this.children[0];
+ return null;
+ }
+
+ private getCode() {
+ if (this.code) return this.code;
+ const file = this.getFile();
+ if (!file) return;
+ return file.innerHTML.trim() || '';
+ }
+
+ private getLang() {
+ if (this.language) return this.language;
+ const file = this.getFile();
+ if (!file) return;
+ const type = file.getAttribute('type');
+ return type?.split('/').pop();
+ }
+
+ private getTheme() {
+ if (this.theme) return this.theme;
+ return 'cv-light';
+ }
+
+ private setTheme = () => {
+ editor.setTheme(this.getTheme());
+ };
+
+ adjustHeight() {
+ if (this.editor && this.container.value) {
+ const contentHeight = this.editor.getContentHeight();
+ this.container.value.style.height = `${contentHeight}px`;
+ this.editor.layout();
+ }
+ }
+
+ createEditor(container: HTMLElement) {
+ // uses covalent light colors
+ editor.defineTheme('cv-light', {
+ base: 'vs',
+ inherit: true,
+ rules: cvEditorLightTheme.rules,
+ colors: cvEditorLightTheme.colors,
+ });
+
+ // uses covalent dark colors
+ editor.defineTheme('cv-dark', {
+ base: 'vs-dark',
+ inherit: true,
+ rules: cvEditorDarkTheme.rules,
+ colors: cvEditorDarkTheme.colors,
+ });
+
+ this.editor = editor.create(container, {
+ ...this.options,
+ fontLigatures: '',
+ value: this.getCode(),
+ language: this.getLang(),
+ theme: this.getTheme(),
+ automaticLayout: true,
+ scrollBeyondLastLine: false,
+ });
+ }
+
+ override disconnectedCallback(): void {
+ this.editor?.dispose();
+ window.removeEventListener('change', this.setTheme);
+ super.disconnectedCallback();
+ }
+
+ firstUpdated() {
+ if (this.container.value) {
+ this.createEditor(this.container.value);
+
+ this.onModelChange();
+
+ // Dispatch event when editor is focused
+ this.editor?.onDidFocusEditorText(() => {
+ this.dispatchEvent(
+ new CustomEvent('editor-focus', {
+ bubbles: true,
+ composed: true,
+ })
+ );
+ });
+
+ // Dispatch event when editor is blurred
+ this.editor?.onDidBlurEditorText(() => {
+ this.dispatchEvent(
+ new CustomEvent('editor-blur', {
+ bubbles: true,
+ composed: true,
+ })
+ );
+ });
+
+ // Adjust the height of the editor when content changes, to avoid vertical scroll bar
+ this.editor?.onDidContentSizeChange(() => {
+ this.adjustHeight();
+ });
+
+ this.adjustHeight();
+
+ window
+ .matchMedia('(prefers-color-scheme: dark)')
+ .addEventListener('change', this.setTheme);
+ }
+ }
+
+ getValue() {
+ const value = this.editor?.getValue();
+ return value;
+ }
+
+ onModelChange() {
+ this.editor?.onDidChangeModelContent(() => {
+ this.code = this.getValue();
+ // Dispatch event when code in the editor is changed
+ this.dispatchEvent(
+ new CustomEvent('code-change', {
+ detail: { code: this.code },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ });
+ }
+
+ render() {
+ return html` `;
+ }
+
+ setValue(value: string) {
+ this.editor?.setValue(value);
+ }
+
+ updated(changedProperties: PropertyValues) {
+ if (changedProperties.has('code') && this.editor) {
+ const currentCode = this.editor.getValue();
+ if (this.code !== currentCode) {
+ this.editor.setValue(this.code || '');
+ }
+ }
+ if (changedProperties.has('language') && this.editor) {
+ // Update monaco editor language when language prop is changed
+ editor.setModelLanguage(
+ this.editor.getModel() as editor.ITextModel,
+ this.language || ''
+ );
+ }
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'cv-code-editor': CovalentCodeEditor;
+ }
+}
+
+export default CovalentCodeEditor;
diff --git a/libs/components/src/code-snippet/code-snippet.ts b/libs/components/src/code-snippet/code-snippet.ts
index 92a4c3ad74..eeb2bdc125 100644
--- a/libs/components/src/code-snippet/code-snippet.ts
+++ b/libs/components/src/code-snippet/code-snippet.ts
@@ -78,7 +78,7 @@ export class CovalentCodeSnippet extends LitElement {
return html` ${container}
+ >${container}
`;
}
diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts
index 9665dca50b..70b39aadf7 100644
--- a/libs/components/src/index.ts
+++ b/libs/components/src/index.ts
@@ -8,6 +8,7 @@ export * from './card/card';
export * from './chips/chip';
export * from './chips/chip-set';
export * from './circular-progress/circular-progress';
+export * from './code-editor/code-editor';
export * from './code-snippet/code-snippet';
export * from './dialog/dialog';
export * from './drawer/drawer';
@@ -27,6 +28,7 @@ export * from './list/list-item';
export * from './list/nav-list-item';
export * from './list/radio-list-item';
export * from './menu/menu';
+export * from './notebook-cell/notebook-cell';
export * from './radio/radio';
export * from './select/select';
export * from './side-sheet/side-sheet';
diff --git a/libs/components/src/notebook-cell/notebook-cell.scss b/libs/components/src/notebook-cell/notebook-cell.scss
new file mode 100644
index 0000000000..6f81e748aa
--- /dev/null
+++ b/libs/components/src/notebook-cell/notebook-cell.scss
@@ -0,0 +1,167 @@
+:host {
+ --mdc-icon-button-size: 24px;
+
+ font-family: var(--mdc-typography-body1-font-family);
+ width: 100%;
+}
+
+cv-code-snippet {
+ background-color: var(--cv-theme-surface);
+ box-sizing: border-box;
+}
+
+cv-code-snippet::part(code) {
+ font-size: var(--mdc-typography-body2-font-size);
+ line-height: var(--mdc-typography-body2-line-height);
+ padding: 0;
+ white-space: pre-wrap;
+}
+
+cv-code-editor {
+ margin-top: -1px;
+}
+
+.selectionIndicator {
+ background-color: var(--cv-theme-primary);
+ border-radius: 2px;
+ height: 100%;
+ visibility: hidden;
+}
+
+.selectionIndicatorWrapper {
+ min-width: 8px;
+
+ &:hover {
+ .selectionIndicator {
+ visibility: visible;
+ }
+ }
+}
+
+.status {
+ padding: 0 1px;
+ position: relative;
+}
+
+.loading {
+ top: 2.5px;
+}
+
+.timesExecuted {
+ align-items: center;
+ box-sizing: border-box;
+ color: var(--cv-theme-on-surface-38);
+ display: flex;
+ font-family: var(--cv-typography-subtitle2-font-family);
+ font-size: var(--cv-typography-subtitle2-font-size);
+ height: 56px;
+ line-height: var(--cv-typography-headline6-line-height);
+
+ .executionCount {
+ padding-right: 1rem;
+ overflow: hidden;
+ text-align: right;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 50px;
+ }
+
+ cv-icon {
+ color: var(--cv-theme-on-surface-variant);
+ padding: 0.5rem;
+ visibility: hidden;
+ transition: visibility 0.3s ease;
+ }
+}
+
+.timesExecutedWrapper {
+ cursor: grab;
+
+ &:active {
+ cursor: grabbing;
+ }
+
+ &:hover {
+ cv-icon {
+ visibility: visible;
+ }
+ }
+}
+
+.cell {
+ box-sizing: border-box;
+ cursor: pointer;
+ display: flex;
+ justify-content: space-between;
+ position: relative;
+ transition: background-color 0.2s ease;
+ user-select: auto;
+
+ &.focused,
+ &.selected {
+ .selectionIndicator {
+ visibility: visible;
+ }
+ }
+
+ &:hover {
+ background-color: var(--cv-theme-secondary-4);
+
+ .selectionIndicator {
+ visibility: visible;
+ background-color: var(--cv-theme-on-secondary-container-38);
+ }
+ }
+
+ &:active {
+ background-color: var(--cv-theme-secondary-12);
+
+ .selectionIndicator {
+ visibility: visible;
+ background-color: var(--cv-theme-on-secondary-container-74);
+ }
+ }
+}
+
+.cellWrapper {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+}
+
+.cellOutputWrapper {
+ display: flex;
+ flex-direction: column;
+ max-width: calc(100% - 95px);
+ flex: 1;
+}
+
+.contextMenu {
+ background: white;
+ border: 1px solid #ccc;
+ box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
+ overflow: auto;
+ position: absolute;
+ visibility: hidden;
+ z-index: 1000;
+}
+
+.contextMenu.open {
+ visibility: visible;
+}
+
+.editorContainer {
+ box-sizing: border-box;
+ background-color: var(--cv-theme-surface);
+ border: 1px solid var(--cv-theme-outline-variant);
+ border-radius: var(--cv-editor-border-radius, 4px);
+ min-height: 56px;
+ overflow: hidden;
+ padding: 1rem;
+}
+
+.errors,
+.output {
+ max-width: 100%;
+ width: 100%;
+}
diff --git a/libs/components/src/notebook-cell/notebook-cell.spec.ts b/libs/components/src/notebook-cell/notebook-cell.spec.ts
new file mode 100644
index 0000000000..975babb06f
--- /dev/null
+++ b/libs/components/src/notebook-cell/notebook-cell.spec.ts
@@ -0,0 +1,11 @@
+/**
+ * @vitest-environment jsdom
+ */
+import { it, describe, expect } from 'vitest';
+import { CovalentNotebookCell } from './notebook-cell';
+
+describe('Notebook cell', () => {
+ it('should work', () => {
+ expect(new CovalentNotebookCell()).toBeDefined();
+ });
+});
diff --git a/libs/components/src/notebook-cell/notebook-cell.stories.js b/libs/components/src/notebook-cell/notebook-cell.stories.js
new file mode 100644
index 0000000000..be9c4c66c7
--- /dev/null
+++ b/libs/components/src/notebook-cell/notebook-cell.stories.js
@@ -0,0 +1,68 @@
+import './notebook-cell';
+import '../alert/alert';
+import '../icon/icon';
+import '../typography/typography';
+import '../list/list';
+import '../list/list-item';
+
+export default {
+ title: 'Components/Notebook Cell',
+ args: {
+ code: 'Select * from DBC.UserInfo;',
+ index: 0,
+ language: 'sql',
+ loading: false,
+ selected: true,
+ hideCount: false,
+ hideEditor: false,
+ theme: 'cv-light',
+ timesExecuted: 2,
+ },
+};
+
+const Template = ({
+ code,
+ index,
+ language,
+ loading,
+ selected,
+ hideCount,
+ hideEditor,
+ theme,
+ timesExecuted,
+}) => {
+ return `
+
+
+
+
+
+
+ Create and Populate Tables
+ Tables are created and populated using SQL
+
+
+
+ Cut
+ Copy
+ Paste
+ Delete
+
+ Clear outputs
+ Restart
+
+
+
+
`;
+};
+
+export const Basic = Template.bind({});
diff --git a/libs/components/src/notebook-cell/notebook-cell.ts b/libs/components/src/notebook-cell/notebook-cell.ts
new file mode 100644
index 0000000000..3546fa88c7
--- /dev/null
+++ b/libs/components/src/notebook-cell/notebook-cell.ts
@@ -0,0 +1,296 @@
+import { css, html, LitElement, PropertyValues, unsafeCSS } from 'lit';
+import { customElement, property, state } from 'lit/decorators.js';
+import styles from './notebook-cell.scss?inline';
+import { classMap } from 'lit/directives/class-map.js';
+
+import '../code-editor/code-editor';
+import '../code-snippet/code-snippet';
+import '../icon-button/icon-button';
+import '../typography/typography';
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'cv-notebook-cell': CovalentNotebookCell;
+ }
+}
+
+enum CvCellEvents {
+ RUN_CODE = 'cell-run-code',
+}
+
+/**
+ * Notebook cell
+ *
+ * @slot - This element has a slot
+ */
+@customElement('cv-notebook-cell')
+export class CovalentNotebookCell extends LitElement {
+ /**
+ * The index of the cell in a notebook
+ */
+ @property({ type: Number })
+ index?: number;
+
+ /**
+ * Code written in the cell
+ */
+ @property({ type: String })
+ code = '';
+
+ /**
+ * Language of the code
+ */
+ @property({ type: String })
+ language = '';
+
+ /**
+ * Whether the cell is loading
+ */
+ @property({ type: Boolean, reflect: true })
+ loading = false;
+
+ /**
+ * Whether the cell is selected
+ */
+ @property({ type: Boolean, reflect: true })
+ selected = false;
+
+ /**
+ * Number of times the cell was exceuted
+ */
+ @property({ type: Number })
+ timesExecuted = 0;
+
+ /**
+ * Whether the editor is shown
+ */
+ @property({ type: Boolean, reflect: true })
+ hideEditor = false;
+
+ /**
+ * Whether the execution count is shown
+ */
+ @property({ type: Boolean, reflect: true })
+ hideCount = false;
+
+ /**
+ * Whether the execution count is shown
+ */
+ @property({ type: String })
+ editorTheme = '';
+
+ private _editorFocused = false;
+
+ @state()
+ private _isMenuOpen = false;
+
+ private _menuMaxHeight = 'auto';
+
+ private _menuPosition = { top: 0, left: 0 };
+
+ editorOptions = {
+ minimap: { enabled: false }, // Disable minimap to save space
+ wordWrap: 'on', // Enable word wrap to avoid horizontal scroll
+ fontSize: '14px',
+ glyphMargin: false,
+ folding: false,
+ lineHeight: 20,
+ lineNumbers: 'off',
+ lineDecorationsWidth: 0,
+ lineNumbersMinChars: 0,
+ renderLineHighlight: 'none',
+ overviewRulerLanes: 0,
+ hideCursorInOverviewRuler: true,
+ scrollbar: {
+ alwaysConsumeMouseWheel: false,
+ vertical: 'hidden',
+ },
+ };
+
+ static override styles = [
+ css`
+ ${unsafeCSS(styles)}
+ `,
+ ];
+
+ constructor() {
+ super();
+ this.closeContextMenu = this.closeContextMenu.bind(this);
+ this.showContextMenu = this.showContextMenu.bind(this);
+ }
+
+ closeContextMenu(): void {
+ this._isMenuOpen = false;
+ }
+
+ connectedCallback(): void {
+ super.connectedCallback();
+ document.addEventListener('click', this.closeContextMenu);
+ }
+
+ disconnectedCallback(): void {
+ super.disconnectedCallback();
+ document.removeEventListener('click', this.closeContextMenu);
+ }
+
+ handleCodeChange(e: CustomEvent) {
+ this.code = e.detail.code;
+ }
+
+ handleRun() {
+ this.dispatchEvent(
+ new CustomEvent(CvCellEvents.RUN_CODE, {
+ detail: { index: this.index, code: this.code },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }
+
+ setEditorFocus(setFocus: boolean) {
+ this._editorFocused = setFocus;
+ this.requestUpdate();
+ }
+
+ showContextMenu(e: MouseEvent) {
+ e.preventDefault();
+ const cells = document.querySelectorAll('cv-notebook-cell');
+ cells?.forEach((cell) => {
+ cell.closeContextMenu();
+ });
+
+ const menu = this.shadowRoot?.querySelector(
+ '.contextMenu'
+ ) as HTMLDivElement;
+ const menuHeight = menu?.offsetHeight;
+
+ // Determine if there is enough space below the click position
+ const viewportHeight = window.innerHeight;
+ const spaceBelow = viewportHeight - e.clientY;
+ const spaceAbove = e.clientY;
+
+ // Determine if there is enough space below, otherwise open upwards
+ if (spaceBelow >= menuHeight) {
+ // Enough space below, open normally
+ this._menuPosition = { top: e.clientY, left: e.clientX };
+ this._menuMaxHeight = `${spaceBelow}px`;
+ } else if (spaceAbove >= menuHeight) {
+ // Enough space above, open upwards fully
+ this._menuPosition = { top: e.clientY - menuHeight, left: e.clientX };
+ this._menuMaxHeight = `${menuHeight}px`;
+ } else {
+ // Not enough space either way, open upwards with adjusted height
+ this._menuPosition = { top: e.clientY - spaceAbove, left: e.clientX };
+ this._menuMaxHeight = `${spaceAbove}px`;
+ }
+ this._isMenuOpen = true;
+ }
+
+ protected updated(changedProperties: PropertyValues) {
+ const editor = this.shadowRoot?.querySelector('cv-code-editor');
+ if (changedProperties.has('code') || changedProperties.has('language')) {
+ // Update the editor instance's code and language properties
+ if (editor) {
+ editor.code = this.code;
+ editor.language = this.language;
+ } else {
+ // Update the code in code-snippet component
+ const codeSnippet = this.shadowRoot?.querySelector('cv-code-snippet');
+ if (codeSnippet && !this.selected) {
+ codeSnippet.highlight();
+ }
+ }
+ }
+ super.updated(changedProperties);
+ }
+
+ getEditorBgColor(): string {
+ const editorContainer = this.shadowRoot?.querySelector('.editorContainer');
+ if (!editorContainer) {
+ return '';
+ }
+ const styles = window.getComputedStyle(editorContainer);
+ return styles.getPropertyValue('background-color');
+ }
+
+ renderEditor() {
+ // Show editor when the cell is selected and show code snippet otherwise
+ const editor = this.selected
+ ? html` this.setEditorFocus(true)}"
+ @editor-blur="${() => this.setEditorFocus(false)}"
+ .code="${this.code}"
+ .language="${this.language}"
+ .options="${this.editorOptions}"
+ .theme="${this.editorTheme}"
+ >`
+ : html`${this.code}`;
+ return html`${this.hideEditor
+ ? ''
+ : html`${editor}
`}`;
+ }
+
+ renderOutput() {
+ const output = html`
+
+
+
+
`;
+ return html`${output}`;
+ }
+
+ renderExecutionCount() {
+ if (this.hideCount) {
+ return html` `;
+ }
+ const loadingClass = {
+ status: true,
+ loading: this.loading,
+ };
+ return html`[${this.loading ? '*' : this.timesExecuted || ' '}] :`;
+ }
+
+ protected render() {
+ const classes = {
+ cell: true,
+ selected: this.selected,
+ focused: this._editorFocused,
+ };
+ return html`
+
+
+
+
+
+
drag_indicator
+
${this.renderExecutionCount()}
+
+
+
+
+ ${this.renderEditor()} ${this.renderOutput()}
+
+
+
+ `;
+ }
+}
+
+export default CovalentNotebookCell;
diff --git a/libs/components/src/select/select.scss b/libs/components/src/select/select.scss
index 5f6f5b4e20..82af225eaa 100644
--- a/libs/components/src/select/select.scss
+++ b/libs/components/src/select/select.scss
@@ -5,10 +5,29 @@
--mdc-select-ink-color: var(--mdc-theme-text-primary-on-background);
--mdc-select-label-ink-color: var(--mdc-theme-text-secondary-on-background);
--mdc-select-outlined-idle-border-color: var(--mdc-theme-border);
- --mdc-select-outlined-hover-border-color: var(--mdc-theme-text-icon-on-background);
+ --mdc-select-outlined-hover-border-color: var(
+ --mdc-theme-text-icon-on-background
+ );
--mdc-select-dropdown-icon-color: var(--mdc-theme-text-icon-on-background);
.mdc-select:not(.mdc-select--disabled) .mdc-select__icon {
- color: var(--mdc-select-dropdown-icon-color)
+ color: var(--mdc-select-dropdown-icon-color);
+ }
+
+ // provides consumer an option to set height
+ .mdc-select--outlined .mdc-select__anchor,
+ .mdc-select--filled .mdc-select__anchor {
+ height: var(--cv-select-height, 56px);
+ }
+
+ // provides consumer an option to adjust the position of label in the outlined version
+ .mdc-select--outlined:not(.mdc-select--with-leading-icon)
+ .mdc-select__anchor
+ .mdc-floating-label--float-above,
+ .mdc-select--filled:not(.mdc-select--with-leading-icon)
+ .mdc-select__anchor
+ .mdc-floating-label--float-above {
+ transform: translateY(var(--cv-select-label-top-position, -37.25px))
+ scale(1);
}
}
diff --git a/libs/components/vite.config.js b/libs/components/vite.config.js
index e446ebd210..c839388730 100644
--- a/libs/components/vite.config.js
+++ b/libs/components/vite.config.js
@@ -20,6 +20,7 @@ module.exports = defineConfig(({ mode }) => {
'libs/components/src/chips/chip',
'libs/components/src/chips/chip-set',
'libs/components/src/circular-progress/circular-progress',
+ 'libs/components/src/code-editor/code-editor',
'libs/components/src/code-snippet/code-snippet',
'libs/components/src/dialog/dialog',
'libs/components/src/drawer/drawer',
@@ -39,6 +40,7 @@ module.exports = defineConfig(({ mode }) => {
'libs/components/src/list/nav-list-item',
'libs/components/src/list/radio-list-item',
'libs/components/src/menu/menu',
+ 'libs/components/src/notebook-cell/notebook-cell',
'libs/components/src/radio/radio',
'libs/components/src/select/select',
'libs/components/src/side-sheet/side-sheet',
@@ -63,6 +65,9 @@ module.exports = defineConfig(({ mode }) => {
'libs/components/src/typography/typography',
],
name: 'Covalent',
+ rollupOptions: {
+ external: ['monaco-editor'],
+ },
},
},
server: {