Compare commits

...

16 Commits

Author SHA1 Message Date
408362f3be v3.67.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-07 21:04:52 +00:00
b3f5ab3d31 feat(dees-table): improve inline cell editors with integrated input styling and auto-open dropdowns 2026-04-07 21:04:52 +00:00
8d954b17ad v3.66.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-07 15:56:55 +00:00
ac9cc8cfed feat(dees-table): add virtualized row rendering for large tables and optimize table rendering performance 2026-04-07 15:56:55 +00:00
a1e808345e v3.65.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-07 15:32:10 +00:00
efea2d62d9 feat(dees-table): add schema-based in-cell editing with keyboard navigation and cell edit events 2026-04-07 15:32:10 +00:00
2f95979cc6 v3.64.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-07 14:34:19 +00:00
b3f098b41e feat(dees-table): add file-manager style row selection and JSON copy support 2026-04-07 14:34:19 +00:00
a0d5462ff1 v3.63.0
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-07 13:55:43 +00:00
f1c204f790 feat(dees-table): add floating header support with fixed-height table mode 2026-04-07 13:55:43 +00:00
e806c9bce6 v3.62.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-07 12:19:59 +00:00
fa56c7cce8 feat(dees-table): add multi-column sorting with header menu controls and priority indicators 2026-04-07 12:19:59 +00:00
3601651e10 v3.61.2
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-04-06 10:41:20 +00:00
b5055b0696 fix(dees-input-list,dees-icon): preserve input focus after list updates and make icons ignore pointer events 2026-04-06 10:41:20 +00:00
a5f7a7ecee v3.61.1
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-05 11:08:40 +00:00
dede3216fb fix(dees-input-list): align list input with dees-tile styling and improve item affordances 2026-04-05 11:08:40 +00:00
12 changed files with 2037 additions and 336 deletions

View File

@@ -1,5 +1,62 @@
# Changelog
## 2026-04-07 - 3.67.0 - feat(dees-table)
improve inline cell editors with integrated input styling and auto-open dropdowns
- add a visually integrated mode to dees-input-text and dees-input-dropdown for table cell editing
- auto-open dropdown editors when a table cell enters edit mode
- refine table editing cell outline and dropdown value matching for inline editors
## 2026-04-07 - 3.66.0 - feat(dees-table)
add virtualized row rendering for large tables and optimize table rendering performance
- add a virtualized mode with configurable overscan to render only visible rows while preserving scroll height
- improve table render performance with memoized column and view-data computation plus deferred floating header rendering
- update the dees-table demo to showcase virtualized scrolling in the fixed-height example
## 2026-04-07 - 3.65.0 - feat(dees-table)
add schema-based in-cell editing with keyboard navigation and cell edit events
- replace editableFields with per-column editor configuration for text, number, checkbox, dropdown, date, and tags inputs
- add focused/editing cell state with arrow key navigation plus Enter, Tab, Shift+Tab, F2, and Escape editing controls
- dispatch cellEdit and cellEditError events with typed payloads and support column-level format, parse, validate, and editorOptions hooks
- update table styles and demos to reflect editable cell behavior and rename sticky header usage to fixedHeight
## 2026-04-07 - 3.64.0 - feat(dees-table)
add file-manager style row selection and JSON copy support
- adds optional selection checkbox rendering via the show-selection-checkbox property
- supports plain, ctrl/cmd, and shift-click row selection with range selection behavior
- adds Ctrl/Cmd+C and context menu actions to copy selected rows as formatted JSON
- updates row selection styling to prevent native text selection during range selection
## 2026-04-07 - 3.63.0 - feat(dees-table)
add floating header support with fixed-height table mode
- replace the sticky-header option with a fixed-height mode for internal scrolling
- add a JS-managed floating header so column headers remain visible when tables scroll inside ancestor containers
- sync floating header column widths and filter rows with the rendered table
## 2026-04-07 - 3.62.0 - feat(dees-table)
add multi-column sorting with header menu controls and priority indicators
- replace single-column sort state with ordered sort descriptors for cascading client-side sorting
- add Shift+click header sorting, context menu actions, and confirmation before replacing an active sort cascade
- show multi-sort direction and priority badges in table headers and add a demo showcasing the new behavior
## 2026-04-06 - 3.61.2 - fix(dees-input-list,dees-icon)
preserve input focus after list updates and make icons ignore pointer events
- Delays refocusing the add input in dees-input-list until after Lit re-renders complete when adding or removing entries.
- Adds pointer-events: none to dees-icon so icons do not block click interactions on surrounding controls.
## 2026-04-05 - 3.61.1 - fix(dees-input-list)
align list input with dees-tile styling and improve item affordances
- wrap the list in dees-tile with a dynamic item count heading and move the add-item controls into the tile footer
- replace custom container styling with shared tile and theme tokens for hover, focus, row, and disabled states
- show a bullet icon for non-sortable or disabled items when no candidate state icon is present
## 2026-04-05 - 3.61.0 - feat(dees-input-list)
allow freeform entries alongside candidate suggestions in dees-input-list

View File

@@ -1,6 +1,6 @@
{
"name": "@design.estate/dees-catalog",
"version": "3.61.0",
"version": "3.67.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",

View File

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

View File

@@ -1,4 +1,4 @@
import type { Column, TDisplayFunction } from './types.js';
import type { Column, ISortDescriptor, TDisplayFunction } from './types.js';
export function computeColumnsFromDisplayFunction<T>(
displayFunction: TDisplayFunction<T>,
@@ -36,11 +36,31 @@ export function getCellValue<T>(row: T, col: Column<T>, displayFunction?: TDispl
return col.value ? col.value(row) : (row as any)[col.key as any];
}
/**
* Compares two cell values in ascending order. Returns -1, 0, or 1.
* Null/undefined values sort before defined values. Numbers compare numerically;
* everything else compares as case-insensitive strings.
*/
export function compareCellValues(va: any, vb: any): number {
if (va == null && vb == null) return 0;
if (va == null) return -1;
if (vb == null) return 1;
if (typeof va === 'number' && typeof vb === 'number') {
if (va < vb) return -1;
if (va > vb) return 1;
return 0;
}
const sa = String(va).toLowerCase();
const sb = String(vb).toLowerCase();
if (sa < sb) return -1;
if (sa > sb) return 1;
return 0;
}
export function getViewData<T>(
data: T[],
effectiveColumns: Column<T>[],
sortKey?: string,
sortDir?: 'asc' | 'desc' | null,
sortBy: ISortDescriptor[],
filterText?: string,
columnFilters?: Record<string, string>,
filterMode: 'table' | 'data' = 'table',
@@ -94,21 +114,17 @@ export function getViewData<T>(
return true;
});
}
if (!sortKey || !sortDir) return arr;
const col = effectiveColumns.find((c) => String(c.key) === sortKey);
if (!col) return arr;
const dir = sortDir === 'asc' ? 1 : -1;
if (!sortBy || sortBy.length === 0) return arr;
// Pre-resolve descriptors -> columns once for performance.
const resolved = sortBy
.map((desc) => ({ desc, col: effectiveColumns.find((c) => String(c.key) === desc.key) }))
.filter((entry): entry is { desc: ISortDescriptor; col: Column<T> } => !!entry.col);
if (resolved.length === 0) return arr;
arr.sort((a, b) => {
const va = getCellValue(a, col);
const vb = getCellValue(b, col);
if (va == null && vb == null) return 0;
if (va == null) return -1 * dir;
if (vb == null) return 1 * dir;
if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * dir;
const sa = String(va).toLowerCase();
const sb = String(vb).toLowerCase();
if (sa < sb) return -1 * dir;
if (sa > sb) return 1 * dir;
for (const { desc, col } of resolved) {
const cmp = compareCellValues(getCellValue(a, col), getCellValue(b, col));
if (cmp !== 0) return desc.dir === 'asc' ? cmp : -cmp;
}
return 0;
});
return arr;

View File

@@ -55,36 +55,66 @@ export const demoFunc = () => html`
<div class="demo-container">
<div class="demo-section">
<h2 class="demo-title">Basic Table with Actions</h2>
<p class="demo-description">A standard table with row actions, editable fields, and context menu support. Double-click on descriptions to edit. Grid lines are enabled by default.</p>
<p class="demo-description">A standard table with row actions, editable cells, and context menu support. Double-click any cell to edit. Tab moves to the next editable cell, Enter to the row below, Esc cancels.</p>
<dees-table
heading1="Current Account Statement"
heading2="Bunq - Payment Account 2 - April 2021"
.editableFields="${['description']}"
.columns=${[
{ key: 'date', header: 'Date', sortable: true, editable: true, editor: 'date' },
{ key: 'amount', header: 'Amount', editable: true, editor: 'text' },
{
key: 'category',
header: 'Category',
editable: true,
editor: 'dropdown',
editorOptions: {
options: [
{ option: 'Office Supplies', key: 'office' },
{ option: 'Hardware', key: 'hardware' },
{ option: 'Software', key: 'software' },
{ option: 'Travel', key: 'travel' },
],
},
},
{ key: 'description', header: 'Description', editable: true },
{ key: 'reconciled', header: 'OK', editable: true, editor: 'checkbox' },
]}
@cellEdit=${(e: CustomEvent) => console.log('cellEdit', e.detail)}
.data=${[
{
date: '2021-04-01',
amount: '2464.65 €',
description: 'Printing Paper (Office Supplies) - STAPLES BREMEN',
category: 'office',
description: 'Printing Paper - STAPLES BREMEN',
reconciled: true,
},
{
date: '2021-04-02',
amount: '165.65 €',
description: 'Logitech Mouse (Hardware) - logi.com OnlineShop',
category: 'hardware',
description: 'Logitech Mouse - logi.com OnlineShop',
reconciled: false,
},
{
date: '2021-04-03',
amount: '2999,00 €',
description: 'Macbook Pro 16inch (Hardware) - Apple.de OnlineShop',
category: 'hardware',
description: 'Macbook Pro 16inch - Apple.de OnlineShop',
reconciled: false,
},
{
date: '2021-04-01',
amount: '2464.65 €',
category: 'office',
description: 'Office-Supplies - STAPLES BREMEN',
reconciled: true,
},
{
date: '2021-04-01',
amount: '2464.65 €',
category: 'office',
description: 'Office-Supplies - STAPLES BREMEN',
reconciled: true,
},
]}
dataName="transactions"
@@ -510,13 +540,13 @@ export const demoFunc = () => html`
<h2 class="demo-title">Column Filters + Sticky Header (New)</h2>
<p class="demo-description">Per-column quick filters and sticky header with internal scroll. Try filtering the Name column. Uses --table-max-height var.</p>
<style>
dees-table[sticky-header] { --table-max-height: 220px; }
dees-table[fixed-height] { --table-max-height: 220px; }
</style>
<dees-table
heading1="Employees"
heading2="Quick filter per column + sticky header"
.showColumnFilters=${true}
.stickyHeader=${true}
.fixedHeight=${true}
.columns=${[
{ key: 'name', header: 'Name', sortable: true },
{ key: 'email', header: 'Email', sortable: true },
@@ -580,6 +610,44 @@ export const demoFunc = () => html`
></dees-table>
</div>
<div class="demo-section">
<h2 class="demo-title">Multi-Column Sort</h2>
<p class="demo-description">
Click any column header for a single-column sort. Hold Shift while clicking to add the
column to a multi-sort cascade (or cycle its direction). Right-click any sortable header
to open a menu where you can pin a column to a specific priority slot, remove it, or
clear the cascade.
</p>
<dees-table
heading1="People Directory"
heading2="Pre-seeded with department ▲ 1, name ▲ 2"
.sortBy=${[
{ key: 'department', dir: 'asc' },
{ key: 'name', dir: 'asc' },
]}
.columns=${[
{ key: 'department', header: 'Department', sortable: true },
{ key: 'name', header: 'Name', sortable: true },
{ key: 'role', header: 'Role', sortable: true },
{ key: 'createdAt', header: 'Created', sortable: true },
{ key: 'location', header: 'Location', sortable: true },
{ key: 'status', header: 'Status', sortable: true },
]}
.data=${[
{ department: 'R&D', name: 'Alice Johnson', role: 'Engineer', createdAt: '2023-01-12', location: 'Berlin', status: 'Active' },
{ department: 'R&D', name: 'Diana Martinez', role: 'Engineer', createdAt: '2020-06-30', location: 'Madrid', status: 'Active' },
{ department: 'R&D', name: 'Mark Lee', role: 'Engineer', createdAt: '2024-03-04', location: 'Berlin', status: 'Active' },
{ department: 'Design', name: 'Bob Smith', role: 'Designer', createdAt: '2022-11-05', location: 'Paris', status: 'Active' },
{ department: 'Design', name: 'Sara Kim', role: 'Designer', createdAt: '2021-08-19', location: 'Paris', status: 'On Leave' },
{ department: 'Ops', name: 'Charlie Davis', role: 'Manager', createdAt: '2021-04-21', location: 'London', status: 'On Leave' },
{ department: 'Ops', name: 'Helena Voss', role: 'SRE', createdAt: '2023-07-22', location: 'London', status: 'Active' },
{ department: 'QA', name: 'Fiona Clark', role: 'QA', createdAt: '2022-03-14', location: 'Vienna', status: 'Active' },
{ department: 'QA', name: 'Tomás Rivera', role: 'QA', createdAt: '2024-01-09', location: 'Madrid', status: 'Active' },
{ department: 'CS', name: 'Ethan Brown', role: 'Support', createdAt: '2019-09-18', location: 'Rome', status: 'Inactive' },
]}
></dees-table>
</div>
<div class="demo-section">
<h2 class="demo-title">Wide Properties + Many Actions</h2>
<p class="demo-description">A table with many columns and rich actions to stress test layout and sticky Actions.</p>
@@ -631,8 +699,9 @@ export const demoFunc = () => html`
</style>
<dees-table
id="scrollSmallHeight"
.stickyHeader=${true}
heading1="People Directory (Scrollable)"
.fixedHeight=${true}
.virtualized=${true}
heading1="People Directory (Scrollable, Virtualized)"
heading2="Forced scrolling with many items"
.columns=${[
{ key: 'id', header: 'ID', sortable: true },

File diff suppressed because it is too large Load Diff

View File

@@ -114,29 +114,48 @@ export const tableStyles: CSSResult[] = [
border-bottom-width: 0px;
}
/* Default mode (Mode B, page sticky): horizontal scroll lives on
.tableScroll (so wide tables don't get clipped by an ancestor
overflow:hidden such as dees-tile). Vertical sticky is handled by
a JS-managed floating header (.floatingHeader, position:fixed),
which is unaffected by ancestor overflow. */
.tableScroll {
/* enable horizontal scroll only when content exceeds width */
position: relative;
overflow-x: auto;
/* prevent vertical scroll inside the table container */
overflow-y: hidden;
/* avoid reserving extra space for classic scrollbars where possible */
scrollbar-gutter: stable both-edges;
overflow-y: visible;
scrollbar-gutter: stable;
}
/* Hide horizontal scrollbar entirely when not using sticky header */
:host(:not([sticky-header])) .tableScroll {
-ms-overflow-style: none; /* IE/Edge */
scrollbar-width: none; /* Firefox (hides both axes) */
}
:host(:not([sticky-header])) .tableScroll::-webkit-scrollbar {
display: none; /* Chrome/Safari */
}
/* In sticky-header mode, hide only the horizontal scrollbar in WebKit/Blink */
:host([sticky-header]) .tableScroll::-webkit-scrollbar:horizontal {
height: 0px;
}
:host([sticky-header]) .tableScroll {
/* Mode A, internal scroll: opt-in via fixed-height attribute.
The table scrolls inside its own box and the header sticks via plain CSS sticky. */
:host([fixed-height]) .tableScroll {
max-height: var(--table-max-height, 360px);
overflow: auto;
scrollbar-gutter: stable both-edges;
}
:host([fixed-height]) .tableScroll::-webkit-scrollbar:horizontal {
height: 0px;
}
/* Floating header overlay (Mode B). Position is managed by JS so it
escapes any ancestor overflow:hidden (position:fixed is not clipped
by overflow ancestors). */
.floatingHeader {
position: fixed;
top: 0;
left: 0;
z-index: 100;
visibility: hidden;
overflow: hidden;
pointer-events: none;
}
.floatingHeader.active {
visibility: visible;
}
.floatingHeader table {
margin: 0;
}
.floatingHeader th {
pointer-events: auto;
}
table {
@@ -159,15 +178,25 @@ export const tableStyles: CSSResult[] = [
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 9%)')};
border-bottom: 1px solid var(--dees-color-border-strong);
}
:host([sticky-header]) thead th {
/* th needs its own background so sticky cells paint over scrolled rows
(browsers don't paint the <thead> box behind a sticky <th>). */
th {
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 9%)')};
}
/* Mode A — internal scroll sticky */
:host([fixed-height]) thead th {
position: sticky;
top: 0;
z-index: 2;
}
:host([fixed-height]) thead tr.filtersRow th {
top: 36px; /* matches th { height: 36px } below */
}
tbody tr {
transition: background-color 0.15s ease;
position: relative;
user-select: none;
}
/* Default horizontal lines (bottom border only) */
@@ -276,6 +305,32 @@ export const tableStyles: CSSResult[] = [
color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
letter-spacing: -0.01em;
}
th[role='columnheader']:hover {
color: var(--dees-color-text-primary);
}
th .sortArrow {
display: inline-block;
margin-left: 6px;
font-size: 10px;
line-height: 1;
opacity: 0.7;
vertical-align: middle;
}
th .sortBadge {
display: inline-block;
margin-left: 3px;
padding: 1px 5px;
font-size: 10px;
font-weight: 600;
line-height: 1;
color: ${cssManager.bdTheme('hsl(222.2 47.4% 30%)', 'hsl(217.2 91.2% 75%)')};
background: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.12)', 'hsl(217.2 91.2% 59.8% / 0.18)')};
border-radius: 999px;
vertical-align: middle;
}
:host([show-vertical-lines]) th {
border-right: 1px solid var(--dees-color-border-default);
@@ -317,32 +372,37 @@ export const tableStyles: CSSResult[] = [
min-height: 24px;
line-height: 24px;
}
td input {
position: absolute;
top: 4px;
bottom: 4px;
left: 20px;
right: 20px;
width: calc(100% - 40px);
height: calc(100% - 8px);
padding: 0 12px;
outline: none;
border: 1px solid var(--dees-color-border-default);
border-radius: 6px;
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
color: var(--dees-color-text-primary);
font-family: inherit;
font-size: inherit;
font-weight: inherit;
transition: all 0.15s ease;
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
/* Editable cell affordances */
td.editable {
cursor: text;
}
td input:focus {
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.2)', 'hsl(217.2 91.2% 59.8% / 0.2)')};
td.focused {
outline: 2px solid ${cssManager.bdTheme(
'hsl(222.2 47.4% 51.2% / 0.6)',
'hsl(217.2 91.2% 59.8% / 0.6)'
)};
outline-offset: -2px;
}
td.editingCell {
padding: 0;
outline: 2px solid ${cssManager.bdTheme(
'hsl(222.2 47.4% 51.2% / 0.6)',
'hsl(217.2 91.2% 59.8% / 0.6)'
)};
outline-offset: -2px;
}
td.editingCell .innerCellContainer {
padding: 0;
line-height: normal;
}
td.editingCell dees-input-text,
td.editingCell dees-input-checkbox,
td.editingCell dees-input-dropdown,
td.editingCell dees-input-datepicker,
td.editingCell dees-input-tags {
display: block;
width: 100%;
}
/* filter row */

View File

@@ -15,6 +15,34 @@ export interface ITableAction<T = any> {
actionFunc: (actionDataArg: ITableActionDataArg<T>) => Promise<any>;
}
/**
* Available cell editor types. Each maps to a dees-input-* component.
* Use `editor` on `Column<T>` to opt a column into in-cell editing.
*/
export type TCellEditorType =
| 'text'
| 'number'
| 'checkbox'
| 'dropdown'
| 'date'
| 'tags';
/** Detail payload for the `cellEdit` CustomEvent dispatched on commit. */
export interface ICellEditDetail<T = any> {
row: T;
key: string;
oldValue: any;
newValue: any;
}
/** Detail payload for the `cellEditError` CustomEvent dispatched on validation failure. */
export interface ICellEditErrorDetail<T = any> {
row: T;
key: string;
value: any;
message: string;
}
export interface Column<T = any> {
key: keyof T | string;
header?: string | TemplateResult;
@@ -24,6 +52,27 @@ export interface Column<T = any> {
/** whether this column participates in per-column quick filtering (default: true) */
filterable?: boolean;
hidden?: boolean;
/** Marks the column as editable. Shorthand for `editor: 'text'` if no editor is specified. */
editable?: boolean;
/** Editor type — picks the dees-input-* component used for in-cell editing. */
editor?: TCellEditorType;
/** Editor-specific options forwarded to the editor (e.g. `{ options: [...] }` for dropdowns). */
editorOptions?: Record<string, any>;
/** Convert raw row value -> editor value. Defaults to identity. */
format?: (raw: any, row: T) => any;
/** Convert editor value -> raw row value. Defaults to identity. */
parse?: (editorValue: any, row: T) => any;
/** Validate the parsed value before commit. Return string for error, true/void for ok. */
validate?: (value: any, row: T) => true | string | void;
}
/**
* One entry in a multi-column sort cascade. Order in the array reflects priority:
* index 0 is the primary sort key, index 1 the secondary tiebreaker, and so on.
*/
export interface ISortDescriptor {
key: string;
dir: 'asc' | 'desc';
}
export type TDisplayFunction<T = any> = (itemArg: T) => Record<string, any>;

View File

@@ -46,6 +46,12 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
})
accessor enableSearch: boolean = true;
@property({
type: Boolean,
reflect: true,
})
accessor vintegrated: boolean = false;
@state()
accessor isOpened = false;
@@ -126,6 +132,36 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
.selectedBox.open::after {
transform: translateY(-50%) rotate(180deg);
}
/* Visually integrated mode: shed chrome to blend into a host component
(e.g. a dees-table cell in edit mode). */
:host([vintegrated]) dees-label {
display: none;
}
:host([vintegrated]) .maincontainer {
height: 40px;
}
:host([vintegrated]) .selectedBox {
height: 40px;
line-height: 40px;
padding: 0 32px 0 16px;
font-size: 13px;
border: none;
border-radius: 0;
background: transparent;
box-shadow: none;
transition: none;
}
:host([vintegrated]) .selectedBox:hover:not(.disabled),
:host([vintegrated]) .selectedBox:focus-visible {
border: none;
box-shadow: none;
background: transparent;
}
:host([vintegrated]) .selectedBox::after {
right: 12px;
opacity: 0.6;
}
`,
];

View File

@@ -9,6 +9,7 @@ import {
} from '@design.estate/dees-element';
import { DeesInputBase } from '../dees-input-base/dees-input-base.js';
import '../../00group-utility/dees-icon/dees-icon.js';
import '../../00group-layout/dees-tile/dees-tile.js';
import { demoFunc } from './dees-input-list.demo.js';
import { themeDefaultStyles } from '../../00theme.js';
@@ -119,26 +120,19 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
width: 100%;
}
.list-container {
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
border-radius: 6px;
overflow: hidden;
transition: all 0.15s ease;
dees-tile:hover::part(outer) {
border-color: var(--dees-color-border-strong);
}
.list-container:hover:not(.disabled) {
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
}
.list-container:focus-within {
dees-tile:focus-within::part(outer) {
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
box-shadow: 0 0 0 3px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.1)', 'hsl(217.2 91.2% 59.8% / 0.1)')};
}
.list-container.disabled {
:host([disabled]) dees-tile {
opacity: 0.6;
cursor: not-allowed;
pointer-events: none;
}
.list-items {
@@ -151,8 +145,8 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
align-items: center;
gap: 6px;
padding: 6px 10px;
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
border-bottom: 1px solid var(--dees-color-border-subtle);
background: transparent;
transition: transform 0.2s ease, background 0.15s ease, box-shadow 0.15s ease;
position: relative;
overflow: hidden;
@@ -163,7 +157,7 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
}
.list-items:not(.is-dragging) .list-item:hover:not(.disabled) {
background: ${cssManager.bdTheme('hsl(0 0% 97.5%)', 'hsl(0 0% 6.9%)')};
background: var(--dees-color-row-hover);
}
/* Dragging item - follows cursor */
@@ -201,6 +195,14 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
flex-shrink: 0;
}
.item-bullet {
width: 14px;
height: 14px;
color: var(--dees-color-text-muted);
flex-shrink: 0;
opacity: 0.5;
}
.drag-handle {
display: flex;
align-items: center;
@@ -303,8 +305,6 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
display: flex;
gap: 6px;
padding: 6px 10px;
background: ${cssManager.bdTheme('hsl(0 0% 97.5%)', 'hsl(0 0% 6.9%)')};
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
}
.add-input {
@@ -442,7 +442,7 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
<div class="input-wrapper">
${this.label ? html`<dees-label .label=${this.label} .required=${this.required}></dees-label>` : ''}
<div class="list-container ${this.disabled ? 'disabled' : ''}">
<dees-tile .heading="${this.value.length} item${this.value.length !== 1 ? 's' : ''}">
<div class="list-items">
${this.value.length > 0 ? this.value.map((item, index) => html`
<div
@@ -458,13 +458,15 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
<dees-icon .icon=${'lucide:gripVertical'}></dees-icon>
</div>
` : ''}
${this.candidates.length > 0 ? html`
${this.candidates.some(c => c.viewKey === item) ? html`
<dees-icon class="candidate-check" .icon=${'lucide:check'}></dees-icon>
` : html`
<dees-icon class="candidate-unknown" .icon=${'lucide:helpCircle'}></dees-icon>
`}
` : !this.sortable || this.disabled ? html`
<dees-icon class="item-bullet" .icon=${'lucide:dot'}></dees-icon>
` : ''}
<div class="item-content">
@@ -512,7 +514,7 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
</div>
${!this.disabled && (!this.maxItems || this.value.length < this.maxItems) ? html`
<div class="add-item-container">
<div slot="footer" class="add-item-container">
<div class="autocomplete-wrapper">
${this.ghostText ? html`
<span class="ghost-text">
@@ -538,7 +540,7 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
</button>
</div>
` : ''}
</div>
</dees-tile>
${this.validationText ? html`
<div class="validation-message">${this.validationText}</div>
@@ -663,11 +665,13 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
this.currentCandidateIndex = -1;
this.matchingCandidates = [];
this.validationText = '';
const input = this.shadowRoot?.querySelector('.add-input') as HTMLInputElement;
if (input) { input.value = ''; input.focus(); }
this.emitChange();
// Re-focus input after Lit re-renders
this.updateComplete.then(() => {
const input = this.shadowRoot?.querySelector('.add-input') as HTMLInputElement;
if (input) { input.value = ''; input.focus(); }
});
}
/**
@@ -706,15 +710,13 @@ export class DeesInputList extends DeesInputBase<DeesInputList> {
this.value = [...this.value, trimmedValue];
this.inputValue = '';
this.validationText = '';
// Clear the input
const input = this.shadowRoot?.querySelector('.add-input') as HTMLInputElement;
if (input) {
input.value = '';
input.focus();
}
this.emitChange();
// Re-focus input after Lit re-renders
this.updateComplete.then(() => {
const input = this.shadowRoot?.querySelector('.add-input') as HTMLInputElement;
if (input) { input.value = ''; input.focus(); }
});
}
private startEdit(index: number) {

View File

@@ -57,6 +57,12 @@ export class DeesInputText extends DeesInputBase {
@property({})
accessor validationFunction!: (value: string) => boolean;
@property({
type: Boolean,
reflect: true,
})
accessor vintegrated: boolean = false;
public static styles = [
themeDefaultStyles,
...DeesInputBase.baseStyles,
@@ -194,6 +200,36 @@ export class DeesInputText extends DeesInputBase {
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)')};
}
/* Visually integrated mode: shed chrome to blend into a host component
(e.g. a dees-table cell in edit mode). */
:host([vintegrated]) dees-label,
:host([vintegrated]) .validationContainer {
display: none;
}
:host([vintegrated]) .maincontainer {
height: 40px;
}
:host([vintegrated]) input {
height: 40px;
line-height: 24px;
padding: 0 16px;
font-size: 13px;
border: none;
border-radius: 0;
background: transparent;
box-shadow: none;
transition: none;
}
:host([vintegrated]) input:hover:not(:disabled):not(:focus),
:host([vintegrated]) input:focus {
border: none;
box-shadow: none;
background: transparent;
}
:host([vintegrated]) .showPassword {
display: none;
}
`,
];

View File

@@ -338,6 +338,7 @@ export class DeesIcon extends DeesElement {
justify-content: center;
line-height: 1;
vertical-align: middle;
pointer-events: none;
}
/* Improve rendering performance */