Compare commits

...

10 Commits

Author SHA1 Message Date
e049b041e2 v2.2.4
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-03-27 16:56:48 +00:00
caf228b32d fix(build): migrate package config to .smartconfig and align build tooling 2026-03-27 16:56:48 +00:00
f4f4ec5024 v2.2.3
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-03-12 20:27:18 +00:00
ba56173966 fix(repo): no changes to commit 2026-03-12 20:27:18 +00:00
3e6e953461 v2.2.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-03-11 19:35:16 +00:00
343d02c40d fix(decorators): patch container-responsive styles to fix stale container queries and add customElement wrapper that sets .is and warns on class/tag mismatch 2026-03-11 19:35:16 +00:00
a69c41fef7 v2.2.1
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-03-11 08:30:00 +00:00
ce78188072 fix(dees-element): rename cssForCustom to cssForConstraint, remove DeesElement static cssFor* helpers, and add optional elementClass parameter to cssManager breakpoint helpers 2026-03-11 08:30:00 +00:00
8c46bbd2e9 v2.2.0
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-03-11 08:18:22 +00:00
4de0dd933d feat(dees-element): add container-responsive APIs (containerResponsive decorator, DeesElement static cssFor* container helpers, and cssManager cssForCustom) and update docs 2026-03-11 08:18:22 +00:00
13 changed files with 1955 additions and 1532 deletions

View File

@@ -35,5 +35,15 @@
},
"@ship.zone/szci": {
"npmGlobalTools": []
},
"@git.zone/tsbundle": {
"bundles": [
{
"from": "./ts/index.ts",
"to": "./dist_bundle/bundle.js",
"outputMode": "bundle",
"bundler": "esbuild"
}
]
}
}

View File

@@ -1,5 +1,44 @@
# Changelog
## 2026-03-27 - 2.2.4 - fix(build)
migrate package config to .smartconfig and align build tooling
- replace npmextra.json with .smartconfig.json and include tsbundle bundle configuration
- update build script and package files list to use the new smartconfig-based setup
- bump build-related dependencies and add Node types to tsconfig
- add definite assignment assertions for element properties to satisfy TypeScript checks
## 2026-03-12 - 2.2.3 - fix(repo)
no changes to commit
## 2026-03-11 - 2.2.2 - fix(decorators)
patch container-responsive styles to fix stale container queries and add customElement wrapper that sets .is and warns on class/tag mismatch
- containerResponsive now derives the kebab name and replaces stale '@container <derived>' occurrences inside CSSResult cssText using unsafeCSS to correct container queries produced before decorators ran
- containerResponsive appends containerContextStyles to the component styles while preserving/processing existing static styles
- Added a customElement decorator wrapper that assigns .is on the class, logs a warning if the class name kebab-cases to a different tag name, and delegates to Lit's customElement
- ts/index.ts now re-exports customElement from the local decorator so the new behavior is used project-wide
## 2026-03-11 - 2.2.1 - fix(dees-element)
rename cssForCustom to cssForConstraint, remove DeesElement static cssFor* helpers, and add optional elementClass parameter to cssManager breakpoint helpers
- Rename: cssManager.cssForCustom -> cssManager.cssForConstraint and DeesElement.cssForCustom -> DeesElement.cssForConstraint; underlying domtools calls updated to cssForConstraint/cssForConstraintContainer.
- Breaking: Removed static helpers DeesElement.cssForDesktop, cssForNotebook, cssForTablet, cssForPhablet, cssForPhone — consumers must now use cssManager.cssFor*(css, this) or pass an element class to get component-scoped container rules.
- API change: cssManager.cssForDesktop|cssForNotebook|cssForTablet|cssForPhablet|cssForPhone now accept an optional elementClass (or pass this) and a helper getContainerNameFromClass was added to generate container names.
- Docs: readme.md updated to show new signatures, guidance to pass this for component-scoped constraints, and renaming of "custom" to "constraint".
- Dependency: bumped @design.estate/dees-domtools from ^2.4.0 to ^2.5.1.
## 2026-03-11 - 2.2.0 - feat(dees-element)
add container-responsive APIs (containerResponsive decorator, DeesElement static cssFor* container helpers, and cssManager cssForCustom) and update docs
- Exported new containerResponsive decorator (ts/decorators.containerresponsive.ts and exported from ts/index.ts)
- Added component-level static helpers on DeesElement: cssForDesktop, cssForNotebook, cssForTablet, cssForPhablet, cssForPhone, cssForCustom
- Added cssManager.cssForCustom to provide curried custom breakpoint helper
- Updated readme with Container-Responsive Components section and usage examples
- Bumped dependency @design.estate/dees-domtools from ^2.3.8 to ^2.4.0
- Non-breaking new functionality — recommend minor version bump
## 2026-01-27 - 2.1.6 - fix(docs, deps, tests)
update README with expanded usage docs and examples; bump devDependencies and dees-domtools; fix test import path

View File

@@ -1,4 +1,4 @@
Copyright (c) 2020 Lossless GmbH (hello@lossless.com)
Copyright (c) 2020 Task Venture Capital GmbH (hello@task.vc)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,6 +1,6 @@
{
"name": "@design.estate/dees-element",
"version": "2.1.6",
"version": "2.2.4",
"private": false,
"description": "A library for creating custom elements extending the lit element class with additional functionalities.",
"main": "dist_ts/index.js",
@@ -10,17 +10,17 @@
"license": "MIT",
"scripts": {
"test": "(tstest test/ --verbose)",
"build": "(tsbuild tsfolders --web --allowimplicitany && tsbundle npm)",
"build": "(tsbuild tsfolders --web --allowimplicitany && tsbundle)",
"buildDocs": "tsdoc"
},
"devDependencies": {
"@git.zone/tsbuild": "^4.1.2",
"@git.zone/tsbundle": "^2.8.3",
"@git.zone/tstest": "^3.1.8",
"@types/node": "^25.0.10"
"@git.zone/tsbuild": "^4.4.0",
"@git.zone/tsbundle": "^2.10.0",
"@git.zone/tstest": "^3.6.1",
"@types/node": "^25.5.0"
},
"dependencies": {
"@design.estate/dees-domtools": "^2.3.8",
"@design.estate/dees-domtools": "^2.5.3",
"@push.rocks/isounique": "^1.0.5",
"@push.rocks/smartrx": "^3.0.10",
"lit": "^3.3.2"
@@ -37,7 +37,7 @@
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
".smartconfig.json",
"readme.md"
],
"keywords": [

3215
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -72,11 +72,12 @@ The singleton `cssManager` is the central hub for theming and responsive layout:
|---|---|
| `cssManager.defaultStyles` | Base styles for consistent element rendering |
| `cssManager.bdTheme(bright, dark)` | Returns a `CSSResult` that auto-switches between bright/dark values |
| `cssManager.cssForDesktop(css)` | Media-query wrapper for desktop breakpoints |
| `cssManager.cssForNotebook(css)` | Media-query wrapper for notebook breakpoints |
| `cssManager.cssForTablet(css)` | Media-query wrapper for tablet breakpoints |
| `cssManager.cssForPhablet(css)` | Media-query wrapper for phablet breakpoints |
| `cssManager.cssForPhone(css)` | Media-query wrapper for phone breakpoints |
| `cssManager.cssForDesktop(css, this?)` | Breakpoint for desktop; pass `this` for component-scoped |
| `cssManager.cssForNotebook(css, this?)` | Breakpoint for notebook; pass `this` for component-scoped |
| `cssManager.cssForTablet(css, this?)` | Breakpoint for tablet; pass `this` for component-scoped |
| `cssManager.cssForPhablet(css, this?)` | Breakpoint for phablet; pass `this` for component-scoped |
| `cssManager.cssForPhone(css, this?)` | Breakpoint for phone; pass `this` for component-scoped |
| `cssManager.cssForConstraint({ maxWidth, minWidth })` | Custom viewport-level constraint (curried) |
| `cssManager.cssGridColumns(cols, gap)` | Generates CSS grid column widths |
Example — responsive + themed styles:
@@ -106,6 +107,57 @@ class MyCard extends DeesElement {
}
```
### 📦 Container-Responsive Components
For components that need to respond to their **own width** (not the viewport), use the `@containerResponsive()` decorator and pass `this` as the second argument to `cssManager.cssFor*`:
```typescript
import {
DeesElement, customElement, html, css, cssManager,
containerResponsive,
} from '@design.estate/dees-element';
@containerResponsive()
@customElement('my-stats-grid')
class MyStatsGrid extends DeesElement {
static styles = [
cssManager.defaultStyles,
css`.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }`,
// Component-level: when THIS element is narrower than tablet width
cssManager.cssForTablet(css`
.grid { grid-template-columns: repeat(2, 1fr); }
`, this),
// Viewport-level: when the browser window is phone-sized
cssManager.cssForPhone(css`
.grid { grid-template-columns: 1fr; }
`),
// Component-level with custom width constraint
this.cssForConstraint({ maxWidth: 500 })(css`
.grid { gap: 8px; }
`),
];
render() {
return html`<div class="grid"><slot></slot></div>`;
}
}
```
**How it works:**
| API | Scope | Generated CSS |
|-----|-------|---------------|
| `cssManager.cssForPhablet(css)` | Viewport | `@media` + `@container wccToolsViewport` |
| `cssManager.cssForPhablet(css, this)` | Component | `@container <tag-name>` only |
| `cssManager.cssForConstraint({maxWidth:800})(css)` | Viewport | `@media` + `@container wccToolsViewport` |
| `this.cssForConstraint({maxWidth:500})(css)` | Component | `@container <tag-name>` only |
| `@containerResponsive()` | Decorator | Sets `container-type: inline-size` + `container-name` on `:host` |
The `@containerResponsive()` decorator is required for component-scoped queries — it establishes the CSS containment context on `:host`.
### ⚡ Reactive Properties & State
Use the standard Lit decorators, re-exported for convenience:
@@ -255,6 +307,7 @@ The directives namespace also re-exports these commonly used Lit directives:
| `unsafeHTML` | Render raw HTML in templates |
| `render` | Lit render function |
| `static` / `unsafeStatic` | Static html template helpers |
| `containerResponsive` | Decorator that adds CSS containment to `:host` |
| `domtools` | DOM tooling utilities |
| `directives` | All directives (resolve, subscribe, etc.) |
| `rxjs` (type) | RxJS type re-export |

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@design.estate/dees-element',
version: '2.1.6',
version: '2.2.4',
description: 'A library for creating custom elements extending the lit element class with additional functionalities.'
}

View File

@@ -47,26 +47,60 @@ export class CssManager {
return domtools.elementBasic.staticStyles;
}
public cssForDesktop(contentArg: CSSResult) {
private getContainerNameFromClass(elementClass: { is?: string; name: string }): string {
return elementClass.is || elementClass.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
public cssForDesktop(contentArg: CSSResult, elementClass?: { is?: string; name: string }) {
if (elementClass) {
return unsafeCSS(domtools.breakpoints.cssForContainer(
contentArg, `(min-width: ${domtools.breakpoints.desktop}px)`, this.getContainerNameFromClass(elementClass),
));
}
return unsafeCSS(domtools.breakpoints.cssForDesktop(contentArg));
}
public cssForNotebook(contentArg: CSSResult) {
public cssForNotebook(contentArg: CSSResult, elementClass?: { is?: string; name: string }) {
if (elementClass) {
return unsafeCSS(domtools.breakpoints.cssForContainer(
contentArg, `(max-width: ${domtools.breakpoints.notebook}px)`, this.getContainerNameFromClass(elementClass),
));
}
return unsafeCSS(domtools.breakpoints.cssForNotebook(contentArg));
}
public cssForTablet(contentArg: CSSResult) {
public cssForTablet(contentArg: CSSResult, elementClass?: { is?: string; name: string }) {
if (elementClass) {
return unsafeCSS(domtools.breakpoints.cssForContainer(
contentArg, `(max-width: ${domtools.breakpoints.tablet}px)`, this.getContainerNameFromClass(elementClass),
));
}
return unsafeCSS(domtools.breakpoints.cssForTablet(contentArg));
}
public cssForPhablet(contentArg: CSSResult) {
public cssForPhablet(contentArg: CSSResult, elementClass?: { is?: string; name: string }) {
if (elementClass) {
return unsafeCSS(domtools.breakpoints.cssForContainer(
contentArg, `(max-width: ${domtools.breakpoints.phablet}px)`, this.getContainerNameFromClass(elementClass),
));
}
return unsafeCSS(domtools.breakpoints.cssForPhablet(contentArg));
}
public cssForPhone(contentArg: CSSResult) {
public cssForPhone(contentArg: CSSResult, elementClass?: { is?: string; name: string }) {
if (elementClass) {
return unsafeCSS(domtools.breakpoints.cssForContainer(
contentArg, `(max-width: ${domtools.breakpoints.phone}px)`, this.getContainerNameFromClass(elementClass),
));
}
return unsafeCSS(domtools.breakpoints.cssForPhone(contentArg));
}
public cssForConstraint(constraints: { maxWidth?: number; minWidth?: number }) {
return (contentArg: CSSResult) =>
unsafeCSS(domtools.breakpoints.cssForConstraint(constraints)(contentArg));
}
public bdTheme(brightValueArg: string, darkValueArg: string): CSSResult {
let returnCssVar: string;

View File

@@ -1,6 +1,18 @@
import * as plugins from './plugins.js';
import { type CSSResult } from 'lit';
export class DeesElement extends plugins.lit.LitElement {
// STATIC — component-level constraint helper (use in `static styles = [...]`)
private static getContainerName(): string {
return (this as any).is || this.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
static cssForConstraint(constraints: { maxWidth?: number; minWidth?: number }) {
return (cssArg: CSSResult) =>
plugins.domtools.breakpoints.cssForConstraintContainer(constraints, this.getContainerName())(cssArg);
}
// INSTANCE
@plugins.lit.property({ type: Boolean })
public accessor goBright: boolean = false;
@@ -9,10 +21,10 @@ export class DeesElement extends plugins.lit.LitElement {
public domtoolsPromise: Promise<plugins.domtools.DomTools>;
@plugins.lit.property()
public accessor domtools: plugins.domtools.DomTools;
public accessor domtools!: plugins.domtools.DomTools;
public rxSubscriptions: plugins.smartrx.rxjs.Subscription[] = [];
private themeSubscription: plugins.smartrx.rxjs.Subscription;
private themeSubscription!: plugins.smartrx.rxjs.Subscription;
private elementDomReadyDeferred = plugins.domtools.plugins.smartpromise.defer();
public elementDomReady = this.elementDomReadyDeferred.promise;

View File

@@ -0,0 +1,47 @@
import { unsafeCSS, type CSSResult } from 'lit';
import * as domtools from '@design.estate/dees-domtools';
const camelToKebab = (name: string) =>
name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
export function containerResponsive() {
return function (target: any) {
const tagName: string =
target.is || target.name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
// If .is differs from the regex-derived name, cssManager.cssFor*(css, this)
// in static styles (evaluated before decorators) used the wrong container name.
// Fix those stale references now.
const derivedName = target.name ? camelToKebab(target.name) : null;
if (derivedName && derivedName !== tagName) {
const fixStyle = (style: CSSResult) => {
if (style && style.cssText && style.cssText.includes(`@container ${derivedName}`)) {
return unsafeCSS(
style.cssText.replaceAll(`@container ${derivedName}`, `@container ${tagName}`)
);
}
return style;
};
const original = target.styles;
if (Array.isArray(original)) {
target.styles = original.map(fixStyle);
} else if (original) {
target.styles = fixStyle(original);
}
}
// Append containment context styles
const containerStyles = domtools.breakpoints.containerContextStyles(tagName);
const current = target.styles;
if (Array.isArray(current)) {
target.styles = [...current, containerStyles];
} else if (current) {
target.styles = [current, containerStyles];
} else {
target.styles = [containerStyles];
}
return target;
};
}

View File

@@ -0,0 +1,25 @@
import { customElement as litCustomElement } from 'lit/decorators/custom-element.js';
const camelToKebab = (name: string) =>
name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
export function customElement(tagName: string) {
return (classOrTarget: any, context?: any) => {
// Set .is so that other decorators and utilities can read the tag name
classOrTarget.is = tagName;
// Warn if class name convention doesn't match the tag
if (classOrTarget.name) {
const derived = camelToKebab(classOrTarget.name);
if (derived !== tagName) {
console.warn(
`[dees-element] Class "${classOrTarget.name}" kebab-cases to "${derived}" but tag is "${tagName}". ` +
`Container queries use .is ("${tagName}").`
);
}
}
// Delegate to Lit's original decorator
return litCustomElement(tagName)(classOrTarget, context);
};
}

View File

@@ -7,7 +7,7 @@ export { html as static, unsafeStatic } from 'lit/static-html.js';
export { unsafeHTML } from 'lit/directives/unsafe-html.js';
export { customElement } from 'lit/decorators/custom-element.js';
export { customElement } from './decorators.customelement.js';
export { property, state, query, queryAll, queryAsync } from 'lit/decorators.js';
@@ -18,6 +18,9 @@ export { domtools };
// DeesElements exports
export * from './classes.dees-element.js';
// decorator exports
export { containerResponsive } from './decorators.containerresponsive.js';
// directives exports
import * as directives from './directives/index.js';

View File

@@ -4,7 +4,8 @@
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true
"verbatimModuleSyntax": true,
"types": ["node"]
},
"exclude": [
"dist_*/**/*.d.ts"