@@ -86,8 +87,8 @@ const hello = 'yes'
public async firstUpdated(_changedPropertiesArg) {
await super.firstUpdated(_changedPropertiesArg);
- const editor = this.shadowRoot.querySelector('dees-editor');
-
+ const editor = this.shadowRoot.querySelector('dees-editor-bare') as DeesEditorBare;
+
// lets care about wiring the markdown stuff.
const markdownOutlet = this.shadowRoot.querySelector('dees-editormarkdownoutlet');
const smartmarkdownInstance = new domtools.plugins.smartmarkdown.SmartMarkdown();
diff --git a/ts_web/elements/00group-editor/dees-editor/index.ts b/ts_web/elements/00group-editor/dees-editor/index.ts
deleted file mode 100644
index 91af439..0000000
--- a/ts_web/elements/00group-editor/dees-editor/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './dees-editor.js';
diff --git a/ts_web/elements/00group-editor/index.ts b/ts_web/elements/00group-editor/index.ts
index 86c7202..4fd543f 100644
--- a/ts_web/elements/00group-editor/index.ts
+++ b/ts_web/elements/00group-editor/index.ts
@@ -1,4 +1,4 @@
// Editor Components
-export * from './dees-editor/index.js';
+export * from './dees-editor-bare/index.js';
export * from './dees-editor-markdown/index.js';
export * from './dees-editor-markdownoutlet/index.js';
diff --git a/ts_web/elements/00group-form/dees-form/dees-form.ts b/ts_web/elements/00group-form/dees-form/dees-form.ts
index ad842d4..faef009 100644
--- a/ts_web/elements/00group-form/dees-form/dees-form.ts
+++ b/ts_web/elements/00group-form/dees-form/dees-form.ts
@@ -9,6 +9,7 @@ import {
import * as domtools from '@design.estate/dees-domtools';
import { DeesInputCheckbox } from '../../00group-input/dees-input-checkbox/dees-input-checkbox.js';
+import { DeesInputCode } from '../../00group-input/dees-input-code/dees-input-code.js';
import { DeesInputDatepicker } from '../../00group-input/dees-input-datepicker/index.js';
import { DeesInputText } from '../../00group-input/dees-input-text/dees-input-text.js';
import { DeesInputQuantitySelector } from '../../00group-input/dees-input-quantityselector/dees-input-quantityselector.js';
@@ -26,6 +27,7 @@ import { demoFunc } from './dees-form.demo.js';
// Unified set for form input types
const FORM_INPUT_TYPES = [
DeesInputCheckbox,
+ DeesInputCode,
DeesInputDatepicker,
DeesInputDropdown,
DeesInputFileupload,
@@ -41,6 +43,7 @@ const FORM_INPUT_TYPES = [
export type TFormInputElement =
| DeesInputCheckbox
+ | DeesInputCode
| DeesInputDatepicker
| DeesInputDropdown
| DeesInputFileupload
diff --git a/ts_web/elements/00group-input/dees-input-code/dees-input-code.ts b/ts_web/elements/00group-input/dees-input-code/dees-input-code.ts
new file mode 100644
index 0000000..8565462
--- /dev/null
+++ b/ts_web/elements/00group-input/dees-input-code/dees-input-code.ts
@@ -0,0 +1,719 @@
+import { DeesInputBase } from '../dees-input-base/dees-input-base.js';
+import {
+ customElement,
+ type TemplateResult,
+ property,
+ html,
+ cssManager,
+ css,
+ state,
+} from '@design.estate/dees-element';
+import { themeDefaultStyles } from '../../00theme.js';
+import { DeesModal } from '../../dees-modal/dees-modal.js';
+import '../../dees-icon/dees-icon.js';
+import '../../dees-label/dees-label.js';
+import '../../00group-editor/dees-editor-bare/dees-editor-bare.js';
+import { DeesEditorBare } from '../../00group-editor/dees-editor-bare/dees-editor-bare.js';
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'dees-input-code': DeesInputCode;
+ }
+}
+
+// Common programming languages for the language selector
+const LANGUAGES = [
+ { key: 'typescript', label: 'TypeScript' },
+ { key: 'javascript', label: 'JavaScript' },
+ { key: 'json', label: 'JSON' },
+ { key: 'html', label: 'HTML' },
+ { key: 'css', label: 'CSS' },
+ { key: 'scss', label: 'SCSS' },
+ { key: 'markdown', label: 'Markdown' },
+ { key: 'yaml', label: 'YAML' },
+ { key: 'xml', label: 'XML' },
+ { key: 'sql', label: 'SQL' },
+ { key: 'python', label: 'Python' },
+ { key: 'java', label: 'Java' },
+ { key: 'csharp', label: 'C#' },
+ { key: 'cpp', label: 'C++' },
+ { key: 'go', label: 'Go' },
+ { key: 'rust', label: 'Rust' },
+ { key: 'shell', label: 'Shell' },
+ { key: 'plaintext', label: 'Plain Text' },
+];
+
+@customElement('dees-input-code')
+export class DeesInputCode extends DeesInputBase
{
+ public static demo = () => html`
+
+ `;
+
+ // INSTANCE
+ @property({ type: String })
+ accessor value: string = '';
+
+ @property({ type: String })
+ accessor language: string = 'typescript';
+
+ @property({ type: String })
+ accessor height: string = '200px';
+
+ @property({ type: String })
+ accessor wordWrap: 'on' | 'off' = 'off';
+
+ @property({ type: Boolean })
+ accessor showLineNumbers: boolean = true;
+
+ @state()
+ accessor isLanguageDropdownOpen: boolean = false;
+
+ @state()
+ accessor copySuccess: boolean = false;
+
+ private editorElement: DeesEditorBare | null = null;
+
+ public static styles = [
+ themeDefaultStyles,
+ ...DeesInputBase.baseStyles,
+ cssManager.defaultStyles,
+ css`
+ * {
+ box-sizing: border-box;
+ }
+
+ :host {
+ display: block;
+ }
+
+ .code-container {
+ border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
+ border-radius: 6px;
+ overflow: hidden;
+ background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
+ }
+
+ .toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 12px;
+ background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')};
+ border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
+ gap: 8px;
+ }
+
+ .toolbar-left {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .toolbar-right {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ }
+
+ .language-selector {
+ position: relative;
+ }
+
+ .language-button {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 10px;
+ font-size: 12px;
+ font-weight: 500;
+ background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 12%)')};
+ border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 20%)')};
+ border-radius: 4px;
+ cursor: pointer;
+ color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
+ transition: all 0.15s ease;
+ }
+
+ .language-button:hover {
+ background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 15%)')};
+ }
+
+ .language-dropdown {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ margin-top: 4px;
+ background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
+ border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 20%)')};
+ border-radius: 6px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ z-index: 100;
+ max-height: 250px;
+ overflow-y: auto;
+ min-width: 140px;
+ }
+
+ .language-option {
+ padding: 8px 12px;
+ font-size: 12px;
+ cursor: pointer;
+ color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
+ transition: background 0.15s ease;
+ }
+
+ .language-option:hover {
+ background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 15%)')};
+ }
+
+ .language-option.selected {
+ background: ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 20%)')};
+ }
+
+ .toolbar-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ background: transparent;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ color: ${cssManager.bdTheme('hsl(0 0% 45%)', 'hsl(0 0% 60%)')};
+ transition: all 0.15s ease;
+ }
+
+ .toolbar-button:hover {
+ background: ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 15%)')};
+ color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
+ }
+
+ .toolbar-button.active {
+ background: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
+ color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
+ }
+
+ .toolbar-button.success {
+ color: hsl(142.1 76.2% 36.3%);
+ }
+
+ .editor-wrapper {
+ position: relative;
+ }
+
+ dees-editor-bare {
+ display: block;
+ }
+
+ .toolbar-divider {
+ width: 1px;
+ height: 20px;
+ background: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
+ margin: 0 4px;
+ }
+
+ :host([disabled]) .code-container {
+ opacity: 0.5;
+ pointer-events: none;
+ }
+ `,
+ ];
+
+ public render(): TemplateResult {
+ const currentLanguage = LANGUAGES.find(l => l.key === this.language) || LANGUAGES[0];
+
+ return html`
+
+
+ `;
+ }
+
+ async firstUpdated() {
+ this.editorElement = this.shadowRoot?.querySelector('dees-editor-bare') as DeesEditorBare;
+ if (this.editorElement) {
+ // Subscribe to content changes from the editor
+ this.editorElement.contentSubject.subscribe((newContent: string) => {
+ if (this.value !== newContent) {
+ this.value = newContent;
+ this.changeSubject.next(this as any);
+ }
+ });
+ }
+ }
+
+ private toggleLanguageDropdown() {
+ this.isLanguageDropdownOpen = !this.isLanguageDropdownOpen;
+ }
+
+ private handleLanguageBlur() {
+ // Small delay to allow click events on dropdown items
+ setTimeout(() => {
+ this.isLanguageDropdownOpen = false;
+ }, 150);
+ }
+
+ private async selectLanguage(e: Event, languageKey: string) {
+ e.preventDefault();
+ this.language = languageKey;
+ this.isLanguageDropdownOpen = false;
+
+ // Update the editor language
+ if (this.editorElement) {
+ this.editorElement.language = languageKey;
+ const editor = await this.editorElement.editorDeferred.promise;
+ const model = editor.getModel();
+ if (model) {
+ (window as any).monaco.editor.setModelLanguage(model, languageKey);
+ }
+ }
+ }
+
+ private toggleWordWrap() {
+ this.wordWrap = this.wordWrap === 'on' ? 'off' : 'on';
+ this.updateEditorOption('wordWrap', this.wordWrap);
+ }
+
+ private toggleLineNumbers() {
+ this.showLineNumbers = !this.showLineNumbers;
+ this.updateEditorOption('lineNumbers', this.showLineNumbers ? 'on' : 'off');
+ }
+
+ private async updateEditorOption(option: string, value: any) {
+ if (this.editorElement) {
+ const editor = await this.editorElement.editorDeferred.promise;
+ editor.updateOptions({ [option]: value });
+ }
+ }
+
+ private async copyCode() {
+ try {
+ await navigator.clipboard.writeText(this.value);
+ this.copySuccess = true;
+ setTimeout(() => {
+ this.copySuccess = false;
+ }, 2000);
+ } catch (err) {
+ console.error('Failed to copy code:', err);
+ }
+ }
+
+ private handleContentChange(e: CustomEvent) {
+ const newContent = e.detail;
+ if (this.value !== newContent) {
+ this.value = newContent;
+ this.changeSubject.next(this as any);
+ }
+ }
+
+ public async openFullscreen() {
+ const currentValue = this.value;
+ let modalEditorElement: DeesEditorBare | null = null;
+
+ // Modal-specific state
+ let modalLanguage = this.language;
+ let modalWordWrap = this.wordWrap;
+ let modalShowLineNumbers = this.showLineNumbers;
+ let modalLanguageDropdownOpen = false;
+ let modalCopySuccess = false;
+
+ // Helper to get current language label
+ const getLanguageLabel = () => {
+ const lang = LANGUAGES.find(l => l.key === modalLanguage);
+ return lang ? lang.label : 'TypeScript';
+ };
+
+ // Helper to update toolbar UI
+ const updateToolbarUI = (modal: DeesModal) => {
+ const toolbar = modal.shadowRoot?.querySelector('.modal-toolbar');
+ if (!toolbar) return;
+
+ // Update language button text
+ const langBtn = toolbar.querySelector('.language-button span');
+ if (langBtn) langBtn.textContent = getLanguageLabel();
+
+ // Update word wrap button
+ const wrapBtn = toolbar.querySelector('.wrap-btn') as HTMLElement;
+ if (wrapBtn) {
+ wrapBtn.classList.toggle('active', modalWordWrap === 'on');
+ }
+
+ // Update line numbers button
+ const linesBtn = toolbar.querySelector('.lines-btn') as HTMLElement;
+ if (linesBtn) {
+ linesBtn.classList.toggle('active', modalShowLineNumbers);
+ }
+
+ // Update copy button
+ const copyBtn = toolbar.querySelector('.copy-btn') as HTMLElement;
+ const copyIcon = copyBtn?.querySelector('dees-icon') as any;
+ if (copyBtn && copyIcon) {
+ copyBtn.classList.toggle('success', modalCopySuccess);
+ copyIcon.icon = modalCopySuccess ? 'lucide:Check' : 'lucide:Copy';
+ }
+
+ // Update dropdown visibility
+ const dropdown = toolbar.querySelector('.language-dropdown') as HTMLElement;
+ if (dropdown) {
+ dropdown.style.display = modalLanguageDropdownOpen ? 'block' : 'none';
+ }
+ };
+
+ const modal = await DeesModal.createAndShow({
+ heading: this.label || 'Code Editor',
+ width: 'fullscreen',
+ contentPadding: 0,
+ content: html`
+
+
+
+
+
+ `,
+ menuOptions: [
+ {
+ name: 'Cancel',
+ action: async (modalRef) => {
+ await modalRef.destroy();
+ },
+ },
+ {
+ name: 'Save & Close',
+ action: async (modalRef) => {
+ // Get the editor content from the modal
+ modalEditorElement = modalRef.shadowRoot?.querySelector('dees-editor-bare') as DeesEditorBare;
+ if (modalEditorElement) {
+ const editor = await modalEditorElement.editorDeferred.promise;
+ const newValue = editor.getValue();
+ this.setValue(newValue);
+ }
+ await modalRef.destroy();
+ },
+ },
+ ],
+ });
+
+ // Wait for modal to render
+ await new Promise(resolve => setTimeout(resolve, 100));
+ modalEditorElement = modal.shadowRoot?.querySelector('dees-editor-bare') as DeesEditorBare;
+
+ // Wire up toolbar event handlers
+ const toolbar = modal.shadowRoot?.querySelector('.modal-toolbar');
+ if (toolbar) {
+ // Language button click
+ const langBtn = toolbar.querySelector('.language-button');
+ langBtn?.addEventListener('click', () => {
+ modalLanguageDropdownOpen = !modalLanguageDropdownOpen;
+ updateToolbarUI(modal);
+ });
+
+ // Language option clicks
+ const langOptions = toolbar.querySelectorAll('.language-option');
+ langOptions.forEach((option) => {
+ option.addEventListener('click', async () => {
+ const newLang = (option as HTMLElement).dataset.lang;
+ if (newLang && modalEditorElement) {
+ modalLanguage = newLang;
+ modalLanguageDropdownOpen = false;
+
+ // Update editor language
+ const editor = await modalEditorElement.editorDeferred.promise;
+ const model = editor.getModel();
+ if (model) {
+ (window as any).monaco.editor.setModelLanguage(model, newLang);
+ }
+
+ // Update selected state
+ langOptions.forEach(opt => opt.classList.remove('selected'));
+ option.classList.add('selected');
+
+ updateToolbarUI(modal);
+ }
+ });
+ });
+
+ // Word wrap button
+ const wrapBtn = toolbar.querySelector('.wrap-btn');
+ wrapBtn?.addEventListener('click', async () => {
+ modalWordWrap = modalWordWrap === 'on' ? 'off' : 'on';
+ if (modalEditorElement) {
+ const editor = await modalEditorElement.editorDeferred.promise;
+ editor.updateOptions({ wordWrap: modalWordWrap });
+ }
+ updateToolbarUI(modal);
+ });
+
+ // Line numbers button
+ const linesBtn = toolbar.querySelector('.lines-btn');
+ linesBtn?.addEventListener('click', async () => {
+ modalShowLineNumbers = !modalShowLineNumbers;
+ if (modalEditorElement) {
+ const editor = await modalEditorElement.editorDeferred.promise;
+ editor.updateOptions({ lineNumbers: modalShowLineNumbers ? 'on' : 'off' });
+ }
+ updateToolbarUI(modal);
+ });
+
+ // Copy button
+ const copyBtn = toolbar.querySelector('.copy-btn');
+ copyBtn?.addEventListener('click', async () => {
+ if (modalEditorElement) {
+ const editor = await modalEditorElement.editorDeferred.promise;
+ const content = editor.getValue();
+ try {
+ await navigator.clipboard.writeText(content);
+ modalCopySuccess = true;
+ updateToolbarUI(modal);
+ setTimeout(() => {
+ modalCopySuccess = false;
+ updateToolbarUI(modal);
+ }, 2000);
+ } catch (err) {
+ console.error('Failed to copy code:', err);
+ }
+ }
+ });
+
+ // Close dropdown when clicking outside
+ document.addEventListener('click', (e) => {
+ if (modalLanguageDropdownOpen && !langBtn?.contains(e.target as Node)) {
+ modalLanguageDropdownOpen = false;
+ updateToolbarUI(modal);
+ }
+ }, { once: true });
+ }
+ }
+
+ public getValue(): string {
+ return this.value;
+ }
+
+ public setValue(value: string): void {
+ this.value = value;
+ if (this.editorElement) {
+ this.editorElement.content = value;
+ // Also update the Monaco editor directly if it's already loaded
+ this.editorElement.editorDeferred.promise.then(editor => {
+ if (editor.getValue() !== value) {
+ editor.setValue(value);
+ }
+ });
+ }
+ this.changeSubject.next(this as any);
+ }
+}
diff --git a/ts_web/elements/00group-input/dees-input-code/index.ts b/ts_web/elements/00group-input/dees-input-code/index.ts
new file mode 100644
index 0000000..ca62f79
--- /dev/null
+++ b/ts_web/elements/00group-input/dees-input-code/index.ts
@@ -0,0 +1 @@
+export * from './dees-input-code.js';
diff --git a/ts_web/elements/00group-input/index.ts b/ts_web/elements/00group-input/index.ts
index ab2eded..a4fcbdd 100644
--- a/ts_web/elements/00group-input/index.ts
+++ b/ts_web/elements/00group-input/index.ts
@@ -1,6 +1,7 @@
// Input Components
export * from './dees-input-base/index.js';
export * from './dees-input-checkbox/index.js';
+export * from './dees-input-code/index.js';
export * from './dees-input-datepicker/index.js';
export * from './dees-input-dropdown/index.js';
export * from './dees-input-fileupload/index.js';
diff --git a/ts_web/elements/dees-modal/dees-modal.ts b/ts_web/elements/dees-modal/dees-modal.ts
index 6d88d3d..8138a3a 100644
--- a/ts_web/elements/dees-modal/dees-modal.ts
+++ b/ts_web/elements/dees-modal/dees-modal.ts
@@ -45,6 +45,7 @@ export class DeesModal extends DeesElement {
showHelpButton?: boolean;
onHelp?: () => void | Promise;
mobileFullscreen?: boolean;
+ contentPadding?: number;
}) {
const body = document.body;
const modal = new DeesModal();
@@ -58,6 +59,7 @@ export class DeesModal extends DeesElement {
if (optionsArg.showHelpButton !== undefined) modal.showHelpButton = optionsArg.showHelpButton;
if (optionsArg.onHelp) modal.onHelp = optionsArg.onHelp;
if (optionsArg.mobileFullscreen !== undefined) modal.mobileFullscreen = optionsArg.mobileFullscreen;
+ if (optionsArg.contentPadding !== undefined) modal.contentPadding = optionsArg.contentPadding;
modal.windowLayer = await DeesWindowLayer.createAndShow({
blur: true,
});
@@ -108,6 +110,9 @@ export class DeesModal extends DeesElement {
@property({ type: Boolean })
accessor mobileFullscreen: boolean = false;
+ @property({ type: Number })
+ accessor contentPadding: number = 16;
+
@state()
accessor modalZIndex: number = 1000;
@@ -272,7 +277,6 @@ export class DeesModal extends DeesElement {
}
.modal .content {
- padding: 16px;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
@@ -361,7 +365,7 @@ export class DeesModal extends DeesElement {
` : ''}