Compare commits

...

22 Commits

Author SHA1 Message Date
961b811b7a v3.75.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-12 17:45:33 +00:00
cd491e1517 feat(dees-tile): add configurable overscroll handling to tile content and use it in modals 2026-04-12 17:45:33 +00:00
b8a03def79 v3.74.2
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-12 17:28:34 +00:00
2b6798083d fix(modal,tile,input-text): move scroll handling from tile content to modal and update input text demo to use changeSubject subscriptions 2026-04-12 17:28:34 +00:00
3c7b5dc690 v3.74.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-12 17:01:05 +00:00
2f4afddf73 fix(dees-input-text): adjust password toggle and validation icon alignment in text input 2026-04-12 17:01:05 +00:00
212a46894e v3.74.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-12 16:58:39 +00:00
653ef109be feat(input-text): add validated success state and text editing context menu to text inputs 2026-04-12 16:58:39 +00:00
a0b17132ad fix(dees-input-text, dees-input-iban): enhance validation handling and improve demo for input states 2026-04-12 11:44:59 +00:00
486ec11ce6 fix(dees-input-text): rename validation state class from 'error' to 'invalid' for consistency 2026-04-12 11:15:18 +00:00
a24d28d4e0 v3.73.2
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-12 11:10:21 +00:00
8cc45a53e9 fix(input,label): correct validation state attribute handling in text inputs and refine label description icon styling 2026-04-12 11:10:21 +00:00
edf7a86f07 v3.73.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-12 10:47:18 +00:00
8b8a8ff943 fix(dees-label): align label content and icon consistently using inline flex layout 2026-04-12 10:47:18 +00:00
59610f463e v3.73.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-12 10:44:17 +00:00
c1672bb8ae feat(dees-label): expand dees-label demo coverage and update supporting dependencies 2026-04-12 10:44:17 +00:00
3e101840a6 v3.72.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-12 09:24:23 +00:00
e87898ab82 fix(dees-stepper): improve stepper exit animation timing for cancel confirmation flow 2026-04-12 09:24:23 +00:00
8a1be59a51 v3.72.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-11 20:04:37 +00:00
a3b2ace88d feat(dees-stepper): add configurable cancellation flow with confirmation modal 2026-04-11 20:04:37 +00:00
c34037265e v3.71.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-11 16:20:34 +00:00
8c230fe3af fix(dees-modal): move modal content scrolling into dees-tile so long content stays scrollable with pinned header and actions 2026-04-11 16:20:34 +00:00
14 changed files with 637 additions and 169 deletions

View File

@@ -0,0 +1 @@
{"sessionId":"4b0f0a7f-f187-40a3-a38b-cb9a7e877011","pid":3110692,"acquiredAt":1775914414249}

View File

@@ -1,5 +1,73 @@
# Changelog
## 2026-04-12 - 3.75.0 - feat(dees-tile)
add configurable overscroll handling to tile content and use it in modals
- introduces a reflected overscroll property on dees-tile with contain, auto, and none options
- moves tile content scrolling and scrollbar styling into dees-tile instead of modal-specific styling
- updates dees-modal to enable contained overscroll through the new dees-tile API
## 2026-04-12 - 3.74.2 - fix(modal,tile,input-text)
move scroll handling from tile content to modal and update input text demo to use changeSubject subscriptions
- bump @design.estate/dees-wcctools from ^3.8.2 to ^3.8.4
- set dees-tile content overflow to hidden and apply scroll styling through dees-modal part selectors
- simplify the interactive dees-input-text demo by subscribing directly to changeSubject for live value updates
## 2026-04-12 - 3.74.1 - fix(dees-input-text)
adjust password toggle and validation icon alignment in text input
- positions the password toggle and validation icon with fixed top offsets for improved vertical alignment
- updates the validation icon styling to use a larger themed icon without the circular background
## 2026-04-12 - 3.74.0 - feat(input-text)
add validated success state and text editing context menu to text inputs
- show a delayed checkmark confirmation for successful validation and hide inline validation text afterward
- move IBAN validation handling into the shared text input validation function
- improve the email validation demo to use a stricter regex-based check
- add cut, copy, paste, and select-all context menu actions for text inputs
## 2026-04-12 - 3.73.2 - fix(input,label)
correct validation state attribute handling in text inputs and refine label description icon styling
- Change dees-input-text validationState to reflect as a string attribute and align validation selectors with the emitted host attribute
- Wrap the dees-label description icon in a dedicated container to improve sizing, hover feedback, and alignment
## 2026-04-12 - 3.73.1 - fix(dees-label)
align label content and icon consistently using inline flex layout
- change the label container from inline-block to inline-flex with centered alignment
- remove icon-specific vertical transform in favor of layout-based alignment
## 2026-04-12 - 3.73.0 - feat(dees-label)
expand dees-label demo coverage and update supporting dependencies
- replace the minimal dees-label demo with a structured showcase for basic, required, description, combined, and empty-label states
- add themed demo styling and inline annotations to better document component behavior
- update @design.estate/dees-wcctools, lucide, and @types/node dependency versions
## 2026-04-12 - 3.72.1 - fix(dees-stepper)
improve stepper exit animation timing for cancel confirmation flow
- Animate step tiles downward with fade-out during teardown instead of only fading the container
- Delay stepper destruction briefly after dismissing the confirmation modal so both exit transitions render smoothly
- Increase teardown delay to match the updated exit animation duration
## 2026-04-11 - 3.72.0 - feat(dees-stepper)
add configurable cancellation flow with confirmation modal
- adds a cancelable option to control whether steppers can be dismissed
- shows a confirmation modal when canceling via the new Cancel button or overlay backdrop
- updates footer button rendering and separators to support the new cancel action consistently
## 2026-04-11 - 3.71.1 - fix(dees-modal)
move modal content scrolling into dees-tile so long content stays scrollable with pinned header and actions
- Update dees-tile content area to use vertical scrolling when constrained by a max-height while keeping horizontal overflow clipped.
- Remove duplicate scrolling styles from dees-modal and rely on the shared tile container behavior.
- Add modal demo cases for long article, list, and form content to verify internal scrolling.
## 2026-04-11 - 3.71.0 - feat(dees-stepper)
add footer menu actions with form-aware step validation

View File

@@ -1,6 +1,6 @@
{
"name": "@design.estate/dees-catalog",
"version": "3.71.0",
"version": "3.75.0",
"private": false,
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
"main": "dist_ts_web/index.js",
@@ -18,7 +18,7 @@
"dependencies": {
"@design.estate/dees-domtools": "^2.5.4",
"@design.estate/dees-element": "^2.2.4",
"@design.estate/dees-wcctools": "^3.8.0",
"@design.estate/dees-wcctools": "^3.8.4",
"@fortawesome/fontawesome-svg-core": "^7.2.0",
"@fortawesome/free-brands-svg-icons": "^7.2.0",
"@fortawesome/free-regular-svg-icons": "^7.2.0",
@@ -35,10 +35,10 @@
"@tiptap/starter-kit": "^2.23.0",
"@tsclass/tsclass": "^9.5.0",
"echarts": "^5.6.0",
"lightweight-charts": "^5.1.0",
"highlight.js": "11.11.1",
"ibantools": "^4.5.1",
"lucide": "^0.577.0",
"lightweight-charts": "^5.1.0",
"lucide": "^1.8.0",
"monaco-editor": "0.55.1",
"pdfjs-dist": "^4.10.38",
"xterm": "^5.3.0",
@@ -50,7 +50,7 @@
"@git.zone/tstest": "^3.6.3",
"@git.zone/tswatch": "^3.3.2",
"@push.rocks/projectinfo": "^5.1.0",
"@types/node": "^25.5.0"
"@types/node": "^25.6.0"
},
"files": [
"ts/**/*",

65
pnpm-lock.yaml generated
View File

@@ -15,8 +15,8 @@ importers:
specifier: ^2.2.4
version: 2.2.4
'@design.estate/dees-wcctools':
specifier: ^3.8.0
version: 3.8.0
specifier: ^3.8.4
version: 3.8.4
'@fortawesome/fontawesome-svg-core':
specifier: ^7.2.0
version: 7.2.0
@@ -75,8 +75,8 @@ importers:
specifier: ^5.1.0
version: 5.1.0
lucide:
specifier: ^0.577.0
version: 0.577.0
specifier: ^1.8.0
version: 1.8.0
monaco-editor:
specifier: 0.55.1
version: 0.55.1
@@ -106,8 +106,8 @@ importers:
specifier: ^5.1.0
version: 5.1.0
'@types/node':
specifier: ^25.5.0
version: 25.5.0
specifier: ^25.6.0
version: 25.6.0
packages:
@@ -323,8 +323,8 @@ packages:
'@design.estate/dees-element@2.2.4':
resolution: {integrity: sha512-O9cA6flBMMd+pBwMQrZXwAWel9yVxgokolb+Em6gvkXxPJ0P/B5UDn4Vc2d4ts3ta55PTBm+l2dPeDVGx/bl7Q==}
'@design.estate/dees-wcctools@3.8.0':
resolution: {integrity: sha512-CC14iVKUrguzD9jIrdPBd9fZ4egVJEZMxl5y8iy0l7WLumeoYvGsoXj5INVkRPLRVLqziIdi4Je1hXqHt2NU+g==}
'@design.estate/dees-wcctools@3.8.4':
resolution: {integrity: sha512-KpFK/azK+a/Xpq33pXKcho+tdFKVHhKZM5ArvHqo9QMwTczgp5DZZgowTDUuqAofjZwnuVfCPHK/Pw9e64N46A==}
'@emnapi/core@1.8.1':
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
@@ -2121,11 +2121,11 @@ packages:
'@types/node@16.9.1':
resolution: {integrity: sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==}
'@types/node@22.19.15':
resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
'@types/node@22.19.17':
resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==}
'@types/node@25.5.0':
resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
'@types/node@25.6.0':
resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==}
'@types/randomatic@3.1.5':
resolution: {integrity: sha512-VCwCTw6qh1pRRw+5rNTAwqPmf6A+hdrkdM7dBpZVmhl7g+em3ONXlYK/bWPVKqVGMWgP0d1bog8Vc/X6zRwRRQ==}
@@ -3054,6 +3054,9 @@ packages:
lucide@0.577.0:
resolution: {integrity: sha512-PpC/m5eOItp/WU/GlQPFBXDOhq6HibL73KzYP37OX3LM7VmzWQF8voEj8QRWUFvy9FIKfeDQkWYoyS1D/MdWFA==}
lucide@1.8.0:
resolution: {integrity: sha512-JjV/QnadgFLj1Pyu9IKl0lknrolFEzo04B64QcYLLeRzZl/iEHpdbSrRRKbyXcv45SZNv+WGjIUCT33e7xHO6Q==}
make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
@@ -4002,8 +4005,8 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
undici-types@7.18.2:
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
undici-types@7.19.2:
resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
unified@11.0.5:
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
@@ -4708,7 +4711,7 @@ snapshots:
dependencies:
'@design.estate/dees-domtools': 2.5.4
'@design.estate/dees-element': 2.2.4
'@design.estate/dees-wcctools': 3.8.0
'@design.estate/dees-wcctools': 3.8.4
'@fortawesome/fontawesome-svg-core': 7.2.0
'@fortawesome/free-brands-svg-icons': 7.2.0
'@fortawesome/free-regular-svg-icons': 7.2.0
@@ -4784,7 +4787,7 @@ snapshots:
- supports-color
- vue
'@design.estate/dees-wcctools@3.8.0':
'@design.estate/dees-wcctools@3.8.4':
dependencies:
'@design.estate/dees-domtools': 2.5.4
'@design.estate/dees-element': 2.2.4
@@ -5257,7 +5260,7 @@ snapshots:
'@inquirer/figures': 1.0.15
'@inquirer/type': 2.0.0
'@types/mute-stream': 0.0.4
'@types/node': 22.19.15
'@types/node': 22.19.17
'@types/wrap-ansi': 3.0.0
ansi-escapes: 4.3.2
cli-width: 4.1.0
@@ -7235,7 +7238,7 @@ snapshots:
'@types/clean-css@4.2.11':
dependencies:
'@types/node': 25.5.0
'@types/node': 25.6.0
source-map: 0.6.1
'@types/debug@4.1.12':
@@ -7245,7 +7248,7 @@ snapshots:
'@types/fs-extra@11.0.4':
dependencies:
'@types/jsonfile': 6.1.4
'@types/node': 25.5.0
'@types/node': 25.6.0
'@types/hast@3.0.4':
dependencies:
@@ -7265,7 +7268,7 @@ snapshots:
'@types/jsonfile@6.1.4':
dependencies:
'@types/node': 25.5.0
'@types/node': 25.6.0
'@types/linkify-it@5.0.0': {}
@@ -7288,21 +7291,21 @@ snapshots:
'@types/mute-stream@0.0.4':
dependencies:
'@types/node': 25.5.0
'@types/node': 25.6.0
'@types/node-forge@1.3.14':
dependencies:
'@types/node': 25.5.0
'@types/node': 25.6.0
'@types/node@16.9.1': {}
'@types/node@22.19.15':
'@types/node@22.19.17':
dependencies:
undici-types: 6.21.0
'@types/node@25.5.0':
'@types/node@25.6.0':
dependencies:
undici-types: 7.18.2
undici-types: 7.19.2
'@types/randomatic@3.1.5': {}
@@ -7314,11 +7317,11 @@ snapshots:
'@types/tar-stream@3.1.4':
dependencies:
'@types/node': 25.5.0
'@types/node': 25.6.0
'@types/through2@2.0.41':
dependencies:
'@types/node': 25.5.0
'@types/node': 25.6.0
'@types/trusted-types@2.0.7': {}
@@ -7344,11 +7347,11 @@ snapshots:
'@types/ws@8.18.1':
dependencies:
'@types/node': 25.5.0
'@types/node': 25.6.0
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 25.5.0
'@types/node': 25.6.0
optional: true
'@ungap/structured-clone@1.3.0': {}
@@ -8300,6 +8303,8 @@ snapshots:
lucide@0.577.0: {}
lucide@1.8.0: {}
make-dir@3.1.0:
dependencies:
semver: 6.3.1
@@ -9570,7 +9575,7 @@ snapshots:
undici-types@6.21.0: {}
undici-types@7.18.2: {}
undici-types@7.19.2: {}
unified@11.0.5:
dependencies:

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@design.estate/dees-catalog',
version: '3.71.0',
version: '3.75.0',
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
}

View File

@@ -50,6 +50,16 @@ export class DeesInputIban extends DeesInputBase<DeesInputIban> {
.disabled=${this.disabled}
.required=${this.required}
.placeholder=${'DE89 3704 0044 0532 0130 00'}
.validationFunction=${(value: string) => {
const normalized = value.replace(/ /g, '');
if (normalized.length === 0) {
return { valid: true, message: '' };
}
const isValid = ibantools.isValidIBAN(normalized);
return isValid
? { valid: true, message: 'IBAN is valid' }
: { valid: false, message: 'Please enter a valid IBAN' };
}}
@input=${(eventArg: InputEvent) => {
this.validateIban(eventArg);
}}
@@ -81,10 +91,6 @@ export class DeesInputIban extends DeesInputBase<DeesInputIban> {
}
}
this.enteredIbanIsValid = ibantools.isValidIBAN(this.enteredString.replace(/ /g, ''));
const deesInputText = this.shadowRoot!.querySelector('dees-input-text') as any;
if (deesInputText) {
deesInputText.validationText = `IBAN is valid: ${this.enteredIbanIsValid}`;
}
}
public getValue(): string {

View File

@@ -210,39 +210,7 @@ export const demoFunc = () => html`
</dees-panel>
</dees-demowrapper>
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
// Demonstrate validation states
const requiredInput = elementArg.querySelector('dees-input-text[required]') as DeesInputText;
const disabledInput = elementArg.querySelector('dees-input-text[disabled]') as DeesInputText;
const errorInput = elementArg.querySelector('dees-input-text[validationState="invalid"]') as DeesInputText;
if (requiredInput) {
// Show validation on blur for empty required field
requiredInput.addEventListener('blur', () => {
if (!requiredInput.getValue()) {
console.log('Required field is empty!');
}
});
}
if (disabledInput) {
console.log('Disabled input cannot be edited');
}
if (errorInput) {
console.log('Error input shows validation message:', errorInput.validationText);
// Simulate fixing the error
errorInput.addEventListener('changeSubject', () => {
const value = errorInput.getValue();
if (value.includes('@') && value.includes('.')) {
errorInput.validationState = 'valid';
errorInput.validationText = '';
console.log('Email validation passed!');
}
});
}
}}>
<dees-demowrapper>
<dees-panel .title=${'Validation & States'} .subtitle=${'Different validation states and input configurations'}>
<div class="input-group">
<dees-input-text
@@ -258,10 +226,15 @@ export const demoFunc = () => html`
></dees-input-text>
<dees-input-text
.label=${'Field with Error'}
.label=${'Email with Validation'}
.value=${'invalid@'}
.validationText=${'Please enter a valid email address'}
.validationState=${'invalid'}
.validationFunction=${(value: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (emailRegex.test(value)) {
return { valid: true, message: 'Email address is valid' };
}
return { valid: false, message: 'Please enter a valid email address' };
}}
></dees-input-text>
</div>
</dees-panel>
@@ -311,40 +284,21 @@ export const demoFunc = () => html`
</dees-demowrapper>
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
// Set up interactive example
const dynamicInput = elementArg.querySelector('dees-input-text');
const dynamicInput = elementArg.querySelector('dees-input-text') as DeesInputText;
const output = elementArg.querySelector('#text-input-output');
if (dynamicInput && output) {
// Update output on every change
dynamicInput.addEventListener('changeSubject', ((event: CustomEvent) => {
const value = (event.detail as DeesInputText).getValue();
output.textContent = `Current value: "${value}"`;
}) as EventListener);
// Also track focus/blur events
dynamicInput.addEventListener('focus', () => {
console.log('Input focused');
});
dynamicInput.addEventListener('blur', () => {
console.log('Input blurred');
});
// Track keypress events
let keypressCount = 0;
dynamicInput.addEventListener('keydown', () => {
keypressCount++;
console.log(`Keypress count: ${keypressCount}`);
dynamicInput.changeSubject.subscribe(() => {
output.textContent = `Current value: "${dynamicInput.getValue()}"`;
});
}
}}>
<dees-panel .title=${'Interactive Example'} .subtitle=${'Try typing in the inputs to see real-time value changes'}>
<dees-input-text
.label=${'Dynamic Input'}
<dees-panel .title=${'Interactive Example'} .subtitle=${'Try typing in the input to see real-time value changes'}>
<dees-input-text
.label=${'Dynamic Input'}
.placeholder=${'Type something here...'}
></dees-input-text>
<div class="interactive-section">
<div id="text-input-output" class="output-text">Current value: ""</div>
</div>

View File

@@ -7,11 +7,13 @@ import {
customElement,
type TemplateResult,
property,
state,
html,
cssManager,
css,
} from '@design.estate/dees-element';
import { themeDefaultStyles } from '../../00theme.js';
import '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
declare global {
interface HTMLElementTagNameMap {
@@ -44,7 +46,7 @@ export class DeesInputText extends DeesInputBase {
accessor showPasswordBool = false;
@property({
type: Boolean,
type: String,
reflect: true,
})
accessor validationState!: 'valid' | 'warn' | 'invalid';
@@ -54,8 +56,8 @@ export class DeesInputText extends DeesInputBase {
})
accessor validationText: string = '';
@property({})
accessor validationFunction!: (value: string) => boolean;
@property({ attribute: false })
accessor validationFunction!: (value: string) => { valid: boolean; message?: string };
@property({
type: Boolean,
@@ -63,6 +65,11 @@ export class DeesInputText extends DeesInputBase {
})
accessor vintegrated: boolean = false;
@property({ attribute: false })
accessor validConfirmed: boolean = false;
private validTimeout: ReturnType<typeof setTimeout> | undefined;
public static styles = [
themeDefaultStyles,
...DeesInputBase.baseStyles,
@@ -127,7 +134,7 @@ export class DeesInputText extends DeesInputBase {
.showPassword {
position: absolute;
right: 1px;
top: 50%;
top: 20px;
transform: translateY(-50%);
display: flex;
align-items: center;
@@ -145,6 +152,29 @@ export class DeesInputText extends DeesInputBase {
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
}
/* Valid checkmark icon */
.validIcon {
position: absolute;
right: 10px;
top: 20px;
transform: translateY(-50%);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s ease;
pointer-events: none;
color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
}
.validIcon.show {
opacity: 1;
}
.validIcon dees-icon {
font-size: 18px;
}
/* Validation styles */
.validationContainer {
margin-top: 4px;
@@ -156,7 +186,7 @@ export class DeesInputText extends DeesInputBase {
overflow: hidden;
}
.validationContainer.error {
.validationContainer.invalid {
background: ${cssManager.bdTheme('hsl(0 84.2% 60.2% / 0.1)', 'hsl(0 72.2% 50.6% / 0.1)')};
color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
}
@@ -172,31 +202,31 @@ export class DeesInputText extends DeesInputBase {
}
/* Error state for input */
:host([validation-state="invalid"]) input {
:host([validationstate="invalid"]) input {
border-color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
}
:host([validation-state="invalid"]) input:focus {
:host([validationstate="invalid"]) input:focus {
border-color: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 72.2% 50.6%)')};
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(0 84.2% 60.2% / 0.05)', 'hsl(0 72.2% 50.6% / 0.05)')};
}
/* Warning state for input */
:host([validation-state="warn"]) input {
:host([validationstate="warn"]) input {
border-color: ${cssManager.bdTheme('hsl(25 95% 53%)', 'hsl(25 95% 63%)')};
}
:host([validation-state="warn"]) input:focus {
:host([validationstate="warn"]) input:focus {
border-color: ${cssManager.bdTheme('hsl(25 95% 53%)', 'hsl(25 95% 63%)')};
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(25 95% 53% / 0.05)', 'hsl(25 95% 63% / 0.05)')};
}
/* Valid state for input */
:host([validation-state="valid"]) input {
:host([validationstate="valid"]) input {
border-color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
}
:host([validation-state="valid"]) input:focus {
:host([validationstate="valid"]) input:focus {
border-color: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.05)', 'hsl(142.1 70.6% 45.3% / 0.05)')};
}
@@ -239,9 +269,9 @@ export class DeesInputText extends DeesInputBase {
input {
font-family: ${this.isPasswordBool ? cssMonoFontFamily : 'inherit'};
letter-spacing: ${this.isPasswordBool ? '0.5px' : 'normal'};
padding-right: ${this.isPasswordBool ? '48px' : '12px'};
padding-right: ${this.isPasswordBool ? '48px' : this.validConfirmed ? '40px' : '12px'};
}
${this.validationText
${this.validationText && !this.validConfirmed
? css`
.validationContainer {
height: auto;
@@ -275,9 +305,12 @@ export class DeesInputText extends DeesInputBase {
</div>
`
: html``}
<div class="validIcon ${this.validConfirmed ? 'show' : ''}">
<dees-icon .icon=${'lucide:Check'}></dees-icon>
</div>
${this.validationText
? html`
<div class="validationContainer ${this.validationState || 'error'}">
<div class="validationContainer ${this.validationState || 'invalid'}">
${this.validationText}
</div>
`
@@ -288,15 +321,97 @@ export class DeesInputText extends DeesInputBase {
}
firstUpdated() {
// Input event handling is already done in updateValue method
if (this.validationFunction && this.value) {
const result = this.validationFunction(this.value);
this.validationState = result.valid ? 'valid' : 'invalid';
this.validationText = result.message || '';
if (result.valid) {
this.validConfirmed = false;
this.validTimeout = setTimeout(() => {
this.validConfirmed = true;
this.validationState = undefined as any;
}, 500);
}
}
}
public async updateValue(eventArg: Event) {
const target: any = eventArg.target;
this.value = target.value;
if (this.validationFunction) {
const result = this.validationFunction(this.value);
this.validationState = result.valid ? 'valid' : 'invalid';
this.validationText = result.message || '';
if (result.valid) {
this.validConfirmed = false;
clearTimeout(this.validTimeout);
this.validTimeout = setTimeout(() => {
this.validConfirmed = true;
this.validationState = undefined as any;
}, 500);
} else {
clearTimeout(this.validTimeout);
this.validConfirmed = false;
}
}
this.changeSubject.next(this);
}
public getContextMenuItems() {
const input = this.shadowRoot!.querySelector('input')!;
const hasSelection = input.selectionStart !== input.selectionEnd;
return [
{
name: 'Cut',
iconName: 'lucide:Scissors',
shortcut: 'Cmd+X',
disabled: !hasSelection,
action: async () => {
const selected = this.value.substring(input.selectionStart!, input.selectionEnd!);
await navigator.clipboard.writeText(selected);
const start = input.selectionStart!;
this.value = this.value.substring(0, start) + this.value.substring(input.selectionEnd!);
input.value = this.value;
input.setSelectionRange(start, start);
this.changeSubject.next(this);
},
},
{
name: 'Copy',
iconName: 'lucide:Copy',
shortcut: 'Cmd+C',
disabled: !hasSelection,
action: async () => {
const selected = this.value.substring(input.selectionStart!, input.selectionEnd!);
await navigator.clipboard.writeText(selected);
},
},
{
name: 'Paste',
iconName: 'lucide:ClipboardPaste',
shortcut: 'Cmd+V',
action: async () => {
const text = await navigator.clipboard.readText();
const start = input.selectionStart!;
const end = input.selectionEnd!;
this.value = this.value.substring(0, start) + text + this.value.substring(end);
input.value = this.value;
input.setSelectionRange(start + text.length, start + text.length);
this.changeSubject.next(this);
},
},
{ divider: true },
{
name: 'Select All',
iconName: 'lucide:TextCursorInput',
shortcut: 'Cmd+A',
action: async () => {
input.select();
},
},
];
}
public getValue(): string {
return this.value;
}

View File

@@ -1,7 +1,128 @@
import { html, cssManager } from '@design.estate/dees-element';
import { html, css, cssManager } from '@design.estate/dees-element';
export const demoFunc = () => {
return html`
<dees-label .label=${'a label'}></dees-label>
`;
}
export const demoFunc = () => html`
<style>
${css`
.demo-container {
display: flex;
flex-direction: column;
gap: 24px;
padding: 24px;
max-width: 800px;
margin: 0 auto;
}
.demo-section {
background: ${cssManager.bdTheme('#f8f9fa', '#1a1a1a')};
border-radius: 8px;
padding: 24px;
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')};
}
.demo-section h3 {
margin-top: 0;
margin-bottom: 16px;
color: ${cssManager.bdTheme('#333', '#fff')};
}
.demo-section p {
color: ${cssManager.bdTheme('#666', '#999')};
margin-bottom: 16px;
font-size: 13px;
}
.label-grid {
display: flex;
flex-direction: column;
gap: 24px;
}
.label-row {
display: flex;
align-items: baseline;
gap: 16px;
}
.label-row .annotation {
font-size: 12px;
font-family: monospace;
color: ${cssManager.bdTheme('#888', '#666')};
flex-shrink: 0;
min-width: 200px;
}
`}
</style>
<div class="demo-container">
<div class="demo-section">
<h3>Basic Label</h3>
<p>A simple text label with no additional indicators.</p>
<div class="label-grid">
<div class="label-row">
<span class="annotation">label="Username"</span>
<dees-label .label=${'Username'}></dees-label>
</div>
<div class="label-row">
<span class="annotation">label="Email Address"</span>
<dees-label .label=${'Email Address'}></dees-label>
</div>
</div>
</div>
<div class="demo-section">
<h3>Required Indicator</h3>
<p>When <code>required</code> is set, a red asterisk appears after the label text.</p>
<div class="label-grid">
<div class="label-row">
<span class="annotation">required=${'{true}'}</span>
<dees-label .label=${'Full Name'} .required=${true}></dees-label>
</div>
<div class="label-row">
<span class="annotation">required=${'{false}'} (default)</span>
<dees-label .label=${'Nickname'} .required=${false}></dees-label>
</div>
</div>
</div>
<div class="demo-section">
<h3>Description (Info Icon)</h3>
<p>When <code>description</code> is set, an info icon appears next to the label. Hover over it to see the tooltip.</p>
<div class="label-grid">
<div class="label-row">
<span class="annotation">description="..."</span>
<dees-label .label=${'API Key'} .description=${'Your API key can be found in the developer settings dashboard.'}></dees-label>
</div>
<div class="label-row">
<span class="annotation">short description</span>
<dees-label .label=${'Region'} .description=${'Select your nearest datacenter.'}></dees-label>
</div>
</div>
</div>
<div class="demo-section">
<h3>Required + Description</h3>
<p>Both indicators can be combined. The asterisk appears first, then the info icon.</p>
<div class="label-grid">
<div class="label-row">
<span class="annotation">required + description</span>
<dees-label .label=${'Password'} .required=${true} .description=${'Must be at least 8 characters with one uppercase letter and one number.'}></dees-label>
</div>
<div class="label-row">
<span class="annotation">required + description</span>
<dees-label .label=${'Email Address'} .required=${true} .description=${'We will send a verification link to this address.'}></dees-label>
</div>
</div>
</div>
<div class="demo-section">
<h3>Empty Label</h3>
<p>When <code>label</code> is empty or not set, nothing is rendered. The element below has no label text:</p>
<div class="label-grid">
<div class="label-row">
<span class="annotation">label="" (empty)</span>
<dees-label .label=${''}></dees-label>
</div>
</div>
</div>
</div>
`;

View File

@@ -50,7 +50,8 @@ export class DeesLabel extends DeesElement {
}
.label {
display: inline-block;
display: inline-flex;
align-items: center;
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
font-size: 14px;
font-weight: 500;
@@ -66,13 +67,26 @@ export class DeesLabel extends DeesElement {
margin-left: 2px;
}
dees-icon {
display: inline-block;
font-size: 12px;
transform: translateY(1px);
margin-left: 4px;
.description-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin: -6px 0 -6px 2px;
border-radius: 4px;
cursor: default;
transition: background 0.15s ease, color 0.15s ease;
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
cursor: help;
}
.description-icon:hover {
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.06)', 'hsl(0 0% 100% / 0.08)')};
color: ${cssManager.bdTheme('hsl(0 0% 25%)', 'hsl(0 0% 80%)')};
}
.description-icon dees-icon {
font-size: 14px;
}
`,
];
@@ -86,7 +100,9 @@ export class DeesLabel extends DeesElement {
${this.required ? html`<span class="required">*</span>` : ''}
${this.description
? html`
<dees-icon .icon=${'lucide:info'}></dees-icon>
<div class="description-icon">
<dees-icon .icon=${'lucide:info'}></dees-icon>
</div>
<dees-speechbubble .text=${this.description}></dees-speechbubble>
`
: html``}

View File

@@ -18,6 +18,7 @@ import { themeDefaultStyles } from '../../00theme.js';
import { cssGeistFontFamily } from '../../00fonts.js';
import { zIndexRegistry } from '../../00zindex.js';
import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js';
import { DeesModal } from '../../00group-overlay/dees-modal/dees-modal.js';
import type { DeesForm } from '../../00group-form/dees-form/dees-form.js';
import '../dees-tile/dees-tile.js';
@@ -45,15 +46,16 @@ export class DeesStepper extends DeesElement {
public static async createAndShow(optionsArg: {
steps: IStep[];
cancelable?: boolean;
}): Promise<DeesStepper> {
const body = document.body;
const stepper = new DeesStepper();
stepper.steps = optionsArg.steps;
stepper.overlay = true;
if (optionsArg.cancelable !== undefined) {
stepper.cancelable = optionsArg.cancelable;
}
stepper.windowLayer = await DeesWindowLayer.createAndShow({ blur: true });
stepper.windowLayer.addEventListener('click', async () => {
await stepper.destroy();
});
body.append(stepper.windowLayer);
body.append(stepper);
@@ -81,6 +83,18 @@ export class DeesStepper extends DeesElement {
})
accessor overlay: boolean = false;
/**
* When true (default), the stepper renders a Cancel button in every step's
* footer, and clicking the backdrop (overlay mode) triggers the same cancel
* confirmation flow. Set to false for forced flows where the user must
* complete the stepper — no Cancel button, no backdrop dismissal.
*/
@property({
type: Boolean,
reflect: true,
})
accessor cancelable: boolean = true;
@property({ type: Number, attribute: false })
accessor stepperZIndex: number = 1000;
@@ -140,9 +154,14 @@ export class DeesStepper extends DeesElement {
height: 100vh;
}
.stepperContainer.predestroy {
opacity: 0;
transition: opacity 0.2s ease-in;
/* Exit animation — reverse of the entry. Tiles slide DOWN + fade out,
mirroring the .entrance slide-up. The transition override is needed
because dees-tile.step has its own 0.7s transition for step selection
which would otherwise make the exit sluggish. */
.stepperContainer.predestroy dees-tile.step {
transform: translateY(16px);
filter: opacity(0);
transition: transform 0.25s, filter 0.2s;
}
dees-tile.step {
@@ -280,15 +299,17 @@ export class DeesStepper extends DeesElement {
transition: all 0.15s ease;
background: transparent;
border: none;
border-left: 1px solid var(--dees-color-border-subtle);
color: var(--dees-color-text-muted);
white-space: nowrap;
display: flex;
align-items: center;
}
.bottomButtons .bottomButton:first-child {
border-left: none;
/* Border-left separator on every button EXCEPT the first one.
Uses general sibling so the stepHint (if rendered on the left) does
not shift which button counts as "first" and create a phantom border. */
.bottomButtons .bottomButton ~ .bottomButton {
border-left: 1px solid var(--dees-color-border-subtle);
}
.bottomButtons .bottomButton:hover {
@@ -347,6 +368,7 @@ export class DeesStepper extends DeesElement {
<div
class="stepperContainer ${this.overlay ? 'overlay' : ''}"
style=${this.overlay ? `z-index: ${this.stepperZIndex};` : ''}
@click=${this.handleOutsideClick}
>
${this.steps.map((stepArg, stepIndex) => {
const isSelected = stepArg === this.selectedStep;
@@ -370,23 +392,27 @@ export class DeesStepper extends DeesElement {
<div class="title">${stepArg.title}</div>
<div class="content">${stepArg.content}</div>
</div>
${stepArg.menuOptions && stepArg.menuOptions.length > 0
? html`<div slot="footer" class="bottomButtons">
${isSelected && this.activeForm !== null && !this.activeFormValid
? html`<div class="stepHint">Complete form to continue</div>`
: ''}
${stepArg.menuOptions.map((actionArg, actionIndex) => {
const isPrimary = actionIndex === stepArg.menuOptions!.length - 1;
const isDisabled = isPrimary && this.activeForm !== null && !this.activeFormValid;
return html`
<div
class="bottomButton ${isPrimary ? 'primary' : ''} ${isDisabled ? 'disabled' : ''}"
@click=${() => this.handleMenuOptionClick(actionArg, isPrimary)}
>${actionArg.name}</div>
`;
})}
</div>`
: ''}
<div slot="footer" class="bottomButtons">
${isSelected && this.activeForm !== null && !this.activeFormValid
? html`<div class="stepHint">Complete form to continue</div>`
: ''}
${this.cancelable
? html`<div
class="bottomButton"
@click=${() => this.handleCancelRequest()}
>Cancel</div>`
: ''}
${stepArg.menuOptions?.map((actionArg, actionIndex) => {
const isPrimary = actionIndex === stepArg.menuOptions!.length - 1;
const isDisabled = isPrimary && this.activeForm !== null && !this.activeFormValid;
return html`
<div
class="bottomButton ${isPrimary ? 'primary' : ''} ${isDisabled ? 'disabled' : ''}"
@click=${() => this.handleMenuOptionClick(actionArg, isPrimary)}
>${actionArg.name}</div>
`;
}) ?? ''}
</div>
</dees-tile>`;
})}
</div>
@@ -556,11 +582,83 @@ export class DeesStepper extends DeesElement {
await optionArg.action(this);
}
/**
* Currently-open confirmation modal (if any). Prevents double-stacking when
* the user clicks the backdrop or the Cancel button while a confirm modal
* is already visible. The reference may become stale (point to a destroyed
* modal) if the user dismisses the confirm modal by clicking its own
* backdrop — so handleCancelRequest() uses isConnected to detect that.
*/
private cancelConfirmModal?: DeesModal;
/**
* Click handler on .stepperContainer. Mirrors dees-modal.handleOutsideClick:
* when the user clicks the empty backdrop area (target === stepperContainer,
* not any descendant tile), trigger the cancel confirmation flow. Clicks
* that originate inside a step tile have a different event.target and are
* ignored here.
*/
private handleOutsideClick(eventArg: MouseEvent) {
if (!this.overlay) return;
if (!this.cancelable) return;
eventArg.stopPropagation();
const stepperContainer = this.shadowRoot!.querySelector('.stepperContainer');
if (eventArg.target === stepperContainer) {
this.handleCancelRequest();
}
}
/**
* Shown by both the backdrop click and the Cancel button in the footer.
* Presents a dees-modal asking the user to confirm cancellation. If they
* confirm, the stepper and window layer are destroyed; otherwise the
* confirm modal is dismissed and the stepper stays open.
*
* The isConnected check on the cached reference handles the case where the
* user dismissed the previous confirm modal by clicking ITS OWN backdrop —
* dees-modal.handleOutsideClick calls destroy() directly, bypassing our
* action callbacks, so our cached reference would be stale without this
* fallback check.
*/
public async handleCancelRequest() {
if (!this.cancelable) return;
if (this.cancelConfirmModal && this.cancelConfirmModal.isConnected) return;
this.cancelConfirmModal = undefined;
this.cancelConfirmModal = await DeesModal.createAndShow({
heading: 'Cancel setup?',
width: 'small',
content: html`
<p style="margin: 0;">
Are you sure you want to cancel? Any progress on the current step will be lost.
</p>
`,
menuOptions: [
{
name: 'Continue setup',
action: async (modal) => {
this.cancelConfirmModal = undefined;
await modal!.destroy();
},
},
{
name: 'Yes, cancel',
action: async (modal) => {
this.cancelConfirmModal = undefined;
modal!.destroy(); // fire-and-forget — starts the confirm modal fade
const domtools = await this.domtoolsPromise;
await domtools.convenience.smartdelay.delayFor(100);
await this.destroy();
},
},
],
});
}
public async destroy() {
const domtools = await this.domtoolsPromise;
const container = this.shadowRoot!.querySelector('.stepperContainer');
container?.classList.add('predestroy');
await domtools.convenience.smartdelay.delayFor(200);
await domtools.convenience.smartdelay.delayFor(250);
if (this.parentElement) {
this.parentElement.removeChild(this);
}

View File

@@ -45,6 +45,9 @@ export class DeesTile extends DeesElement {
@property({ type: String })
accessor heading: string = '';
@property({ type: String, reflect: true })
accessor overscroll: 'contain' | 'auto' | 'none' = 'auto';
@state()
accessor hasFooter: boolean = false;
@@ -87,14 +90,27 @@ export class DeesTile extends DeesElement {
color: var(--dees-color-text-secondary);
}
/* --- Content: the rounded inset --- */
/* --- Content: the rounded inset ---
Uses overflow-y: auto so that when a consumer (e.g. dees-modal) caps
the tile with max-height, long content scrolls inside the tile
instead of being clipped. For consumers without max-height
(e.g. dees-stepper), the tile grows with content and the scroll
never activates. Horizontal overflow stays clipped to preserve the
rounded corners. */
.tile-content {
flex: 1;
position: relative;
border-radius: 8px;
border-top: 1px solid var(--dees-color-border-subtle);
border-bottom: 1px solid var(--dees-color-border-subtle);
overflow: hidden;
overflow-x: hidden;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
}
:host([overscroll="contain"]) .tile-content {
overscroll-behavior: contain;
}
.tile-content.no-footer {

View File

@@ -352,5 +352,80 @@ export const demoFunc = () => html`
});
}}>Test Responsive</dees-button>
</div>
<div class="demo-section">
<h3>Scrollable Content</h3>
<p>When content exceeds the modal's max-height (<code>calc(100vh - 80px)</code>), the tile caps at that height and the content area scrolls inside. The heading and bottom buttons stay pinned.</p>
<div class="button-grid">
<dees-button @click=${() => {
DeesModal.createAndShow({
heading: 'Long Article',
width: 'medium',
content: html`
<h4 style="margin-top: 0;">Lorem ipsum dolor sit amet</h4>
${Array.from({ length: 40 }, (_, i) => html`
<p>
<strong>§ ${i + 1}.</strong>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
`)}
`,
menuOptions: [{
name: 'Cancel',
action: async (modal) => modal!.destroy()
}, {
name: 'Accept',
action: async (modal) => modal!.destroy()
}],
});
}}>Long Article</dees-button>
<dees-button @click=${() => {
DeesModal.createAndShow({
heading: 'Long List',
width: 'small',
content: html`
<p>Selected items:</p>
<ul style="padding-left: 20px; margin: 0;">
${Array.from({ length: 80 }, (_, i) => html`
<li style="padding: 4px 0;">Item ${i + 1} — option label</li>
`)}
</ul>
`,
menuOptions: [{
name: 'Done',
action: async (modal) => modal!.destroy()
}],
});
}}>Long List</dees-button>
<dees-button @click=${() => {
DeesModal.createAndShow({
heading: 'Tall Form',
width: 'medium',
content: html`
<dees-form>
${Array.from({ length: 25 }, (_, i) => html`
<dees-input-text .label=${`Field ${i + 1}`}></dees-input-text>
`)}
</dees-form>
`,
menuOptions: [{
name: 'Cancel',
action: async (modal) => modal!.destroy()
}, {
name: 'Submit',
action: async (modal) => modal!.destroy()
}],
});
}}>Tall Form</dees-button>
</div>
</div>
</div>
`

View File

@@ -271,13 +271,6 @@ export class DeesModal extends DeesElement {
font-size: 14px;
}
.content {
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior: contain;
scrollbar-width: thin;
scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
}
.bottomButtons {
display: flex;
flex-direction: row;
@@ -350,7 +343,7 @@ export class DeesModal extends DeesElement {
${minWidthStyle ? `dees-tile { min-width: ${minWidthStyle}; }` : ''}
</style>
<div class="modalContainer" @click=${this.handleOutsideClick} style="z-index: ${this.modalZIndex}">
<dees-tile class="${widthClass} ${mobileFullscreenClass}">
<dees-tile class="${widthClass} ${mobileFullscreenClass}" .overscroll=${'contain'}>
<div slot="header" class="heading">
<div class="heading-text">${this.heading}</div>
<div class="header-buttons">