@design.estate/dees-element

中文文档
4.0.2发布于 2026/10/09MITTypeScriptES Module近 30 天下载 1万

A library for creating custom elements extending the lit element class with additional functionalities.

原始使用文档
文档目录16 节
  1. Issue Reporting and Security
  2. Install
  3. Usage
  4. 🧱 Creating a Custom Element
  5. 🧪 Demo Wrapper Utilities
  6. 🎨 Theme Management with cssManager
  7. 📦 Container-Responsive Components
  8. ⚡ Reactive Properties & State
  9. 🔄 Theme Change Callbacks
  10. 🚀 Lifecycle Helpers
  11. 🌍 Languages with localize and provideI18n
  12. 📡 Directives
  13. 📦 Full Export Reference
  14. License and Legal Information
  15. Trademarks
  16. Company Information

@design.estate/dees-element

A powerful custom element base class that extends Lit's LitElement with integrated theming, responsive CSS utilities, RxJS-powered directives, and DOM tooling — so you can build web components that look great and stay reactive out of the box.

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

Install

npm install @design.estate/dees-element
# or
pnpm install @design.estate/dees-element

This package ships as ESM and is written in TypeScript. Make sure your project targets ES2022+ with a modern module resolution strategy (e.g. NodeNext).

Usage

Everything you need is exported from the main entry point:

import {
  DeesElement,
  customElement,
  property,
  state,
  html,
  css,
  cssManager,
  directives,
} from '@design.estate/dees-element';

🧱 Creating a Custom Element

Extend DeesElement and apply the @customElement decorator:

import { DeesElement, customElement, html, css, cssManager } from '@design.estate/dees-element';

@customElement('my-button')
class MyButton extends DeesElement {
  static styles = [
    cssManager.defaultStyles,
    css`
      .btn {
        padding: 8px 16px;
        border-radius: 4px;
        background: ${cssManager.bdTheme('#0060df', '#3a8fff')};
        color: ${cssManager.bdTheme('#fff', '#fff')};
        border: none;
        cursor: pointer;
      }
    `,
  ];

  render() {
    return html`<button class="btn"><slot></slot></button>`;
  }
}

That single bdTheme() call generates a CSS variable that automatically flips between the bright and dark values when the user's theme changes — no manual toggling needed.

🧪 Demo Wrapper Utilities

Component demos can import the demotools subpath to register <dees-demowrapper>. The wrapper renders its slotted demo content and then runs runAfterRender, which is useful for demos that need to set up state, query rendered light-DOM children, or simulate user interaction after first render.

import { html } from '@design.estate/dees-element';
import { DeesDemoWrapper } from '@design.estate/dees-element/demotools';

export const demo = () => html`
  <dees-demowrapper
    .runAfterRender=${(wrapper: DeesDemoWrapper) => {
      const button = wrapper.querySelector('button');
      button?.dispatchEvent(new Event('click', { bubbles: true }));
    }}
  >
    <button>Demo action</button>
  </dees-demowrapper>
`;

🎨 Theme Management with cssManager

The singleton cssManager is the central hub for theming and responsive layout:

MethodPurpose
cssManager.defaultStylesBase styles for consistent element rendering
cssManager.bdTheme(bright, dark)Returns a CSSResult that auto-switches between bright/dark values
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:

@customElement('my-card')
class MyCard extends DeesElement {
  static styles = [
    cssManager.defaultStyles,
    css`
      :host {
        display: block;
        padding: 16px;
        background: ${cssManager.bdTheme('#ffffff', '#1e1e1e')};
        color: ${cssManager.bdTheme('#111', '#eee')};
        border-radius: 8px;
      }
    `,
    cssManager.cssForPhone(css`
      :host { padding: 8px; }
    `),
  ];

  render() {
    return html`<slot></slot>`;
  }
}

📦 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*:

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:

APIScopeGenerated 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()DecoratorSets 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:

import { DeesElement, customElement, property, state, html } from '@design.estate/dees-element';

@customElement('my-counter')
class MyCounter extends DeesElement {
  @property({ type: String })
  accessor label = 'Count';

  @state()
  accessor count = 0;

  render() {
    return html`
      <button @click=${() => this.count++}>
        ${this.label}: ${this.count}
      </button>
    `;
  }
}

Note: This library uses the TC39 standard decorators with the accessor keyword for decorated class properties.

🔄 Theme Change Callbacks

DeesElement tracks the current theme via the goBright property and exposes an optional themeChanged callback:

@customElement('theme-aware')
class ThemeAware extends DeesElement {
  protected themeChanged(goBright: boolean) {
    console.log(goBright ? 'Switched to bright' : 'Switched to dark');
  }

  render() {
    return html`<p>Current theme: ${this.goBright ? 'bright' : 'dark'}</p>`;
  }
}

goBright is reflected as the gobright attribute on the host element, so themes can be styled in plain CSS without JavaScript or per-value bdTheme() pairs:

public static styles = [
  css`
    :host {
      background: #000;
    }
    :host([gobright]) {
      background: #fff;
    }
  `,
];

The attribute updates automatically whenever the theme switches. An element takes the theme in force as it connects, so its first render is already in that theme and no colour fades in from the dark default. An element created with ignoreGlobal follows its own domtools and takes its theme once that is set up.

🚀 Lifecycle Helpers

DeesElement adds lifecycle utilities on top of LitElement:

@customElement('my-widget')
class MyWidget extends DeesElement {
  constructor() {
    super();

    // Runs for each connection, after the previous connection has finished cleanup.
    this.registerStartupFunction(async (signal) => {
      if (signal.aborted) return;
      console.log('Widget connected!');
    });

    // Runs when the element is disconnected — perfect for cleanup
    this.registerGarbageFunction(() => {
      console.log('Widget removed');
    });
  }

  render() {
    return html`<p>Hello World</p>`;
  }
}

Additionally, this.elementDomReady is a promise that resolves after firstUpdated, which is handy when you need to wait for the initial render:

await this.elementDomReady;
// The element's shadow DOM is now fully rendered

Startup hooks receive an AbortSignal that is aborted synchronously on removal. Use it to cancel work that supports cancellation. Hooks already running are allowed to settle; garbage hooks then finish before the next connection starts. Register hooks once, usually in the constructor. Resources created by a startup hook belong to that connection.

Lit directives disconnect and rxSubscriptions unsubscribe immediately on removal. Subscriptions added by a finishing startup hook are drained before cleanup completes. Every garbage hook is attempted even if another throws; the returned disconnect promise rejects with an AggregateError, and later connections can still run. Subclasses should call the superclass lifecycle method immediately and await its returned promise before starting connection-specific asynchronous work.

The resolve directive ignores stale promises, retains pending work across removal, and renders the current promise after reconnection even if it settled while detached.

🌍 Languages with localize and provideI18n

A component speaks the words of its package's catalog, a typed @push.rocks/smarti18n catalog of ICU messages. The app decides the language once, by providing an I18n instance; every component below re-renders when it changes.

1. The component package defines its catalog (see the smarti18n readme for messages, translations and checks):

import { defineCatalog } from '@push.rocks/smarti18n';

export const messages = defineCatalog({
  namespace: '@my/components',
  source: 'en',
  messages: {
    counter: 'Step {current, number} of {total, number}',
    help: 'Read the <link>guide</link>.',
  },
});
messages.addTranslation('de', () => import('./messages.de.js'));

2. A component takes its words with localize:

import { DeesElement, customElement, html, localize } from '@design.estate/dees-element';
import { messages } from './messages.js';

@customElement('my-stepper')
class MyStepper extends DeesElement {
  private t = localize(this, messages);

  render() {
    return html`
      <span>${this.t('counter', { current: 2, total: 4 })}</span>
      <p>${this.t.rich('help', { link: (chunks) => html`<a href="/guide">${chunks}</a>` })}</p>
      <small>${this.t.format.number(1234.5)}</small>
    `;
  }
}
  • Keys and parameters are typed: this.t('counter', { current: 2 }) and this.t('countr') are compile errors.
  • this.t.format writes numbers, money, dates, lists and relative times in the formats the catalog's messages are written in, so it always agrees with the numbers inside messages, and this.t.i18n is the instance the element speaks now.

3. The app provides its instance, on <html> so that elements appended to document.body (modals, menus) find it too:

import { createI18n } from '@push.rocks/smarti18n';
import { provideI18n } from '@design.estate/dees-element';

const i18n = createI18n({ locale: 'de-DE' });
provideI18n(document.documentElement, i18n, { reflect: true }); // keeps <html lang dir> in step

await i18n.setLocale('en-GB'); // every localized element re-renders in English

How it behaves:

  • Without a provider an element speaks its catalog's source language (smarti18n's sourceI18n). A provider that appears later is still found, so the order of setup does not matter.

  • A fallback of its own: localize(this, messages, { fallback }) speaks fallback instead of sourceI18n while no provider serves the element, and again when its provider is disposed. A component library keeps formats of its own that way until a host provides a language, for example numbers without grouping, in messages and t.format alike:

    import { createSourceI18n, Formats } from '@push.rocks/smarti18n';
    
    class UngroupedFormats extends Formats {
      public override intlNumberFormat(options: Intl.NumberFormatOptions): Intl.NumberFormat {
        return super.intlNumberFormat({ useGrouping: false, ...options });
      }
    }
    const neutral = createSourceI18n({ createFormats: (settings) => new UngroupedFormats(settings) });
    
    // in the component
    private t = localize(this, messages, { fallback: neutral });
    
  • A subtree can speak another language: provideI18n(previewElement, documentI18n) serves the elements below previewElement (a document preview, a demo side by side). On a DeesElement host the provider lives with the host; reflect sets lang and dir on it while it is connected. provider.i18n = other hands the subtree another instance. provider.dispose() stops a provider and restores its host's lang and dir; a provider on a plain element, such as <html>, lives until then.

  • An overlay can speak its opener's language: a provider serves the elements below its host, never the host's own localize() words. provideI18n(overlay, openerI18n, { includeHost: true }) serves the host too: its own words and the content it renders speak openerI18n apart from the page, follow a locale switch or provider.i18n = other, and return to the page's language on provider.dispose(). It works whether the host is connected already or connects later.

  • No render before the words are in. DeesElement.scheduleUpdate() waits until the catalogs an element uses are loaded for its language, so a lazily defined catalog never shows its source words first. It waits only while words are still to load: with every word loaded, an update is synchronous, so a localized child that a parent renders is current once the parent's updateComplete resolves. Words that fail to load reject updateComplete (with the render's own error as well, in an AggregateError); the next update tries again. A subclass that overrides scheduleUpdate() must call super.scheduleUpdate().

  • No leaks. An element subscribes to its provider and its instance on connect and unsubscribes on disconnect.

  • Right to left. i18n.direction is rtl for Arabic, Hebrew, Persian and the pseudo-locale ar-XB; with reflect, dir="rtl" flips the page. en-XA (createI18n({ locale: 'en-XA', pseudo: true })) shows clipped or untranslated text at a glance.

  • The context is i18nContext (the Web Components context protocol, via @lit/context), keyed by Symbol.for('dees.i18n'), so every copy of this package in a page shares it.

📡 Directives

The directives namespace includes powerful template helpers, accessible via directives.*:

resolve — Render a Promise

import { html, directives } from '@design.estate/dees-element';

render() {
  return html`${directives.resolve(this.fetchData())}`;
}

resolveExec — Resolve a lazy async function

render() {
  return html`${directives.resolveExec(() => this.loadContent())}`;
}

subscribe — Render an RxJS Observable

import { html, directives } from '@design.estate/dees-element';

render() {
  return html`<span>${directives.subscribe(this.count$)}</span>`;
}

subscribeWithTemplate — Observable + template transform

render() {
  return html`
    ${directives.subscribeWithTemplate(
      this.items$,
      (items) => html`<ul>${items.map(i => html`<li>${i}</li>`)}</ul>`
    )}
  `;
}

Re-exported Lit directives

The directives namespace also re-exports these commonly used Lit directives:

  • until — render a placeholder while a promise resolves
  • asyncAppend — append values from an async iterable
  • keyed — force re-creation of a template when a key changes
  • repeat — efficiently render lists with identity tracking

📦 Full Export Reference

ExportDescription
DeesElementBase class for custom elements
cssManagerSingleton CssManager instance
customElementClass decorator to register elements
propertyReactive property decorator
stateInternal state decorator
query, queryAll, queryAsyncShadow DOM query decorators
htmlLit html template tag
cssLit css template tag
unsafeCSSCreate CSSResult from a string
unsafeHTMLRender raw HTML in templates
renderLit render function
static / unsafeStaticStatic html template helpers
containerResponsiveDecorator that adds CSS containment to :host
domtoolsDOM tooling utilities
directivesAll directives (resolve, subscribe, etc.)
localize, ILocalizer (type), ILocalizeOptions (type)A catalog's typed words for an element, in the provided language or its fallback
provideI18n, I18nProvider, IProvideI18nOptions (type)Provide an I18n to a page or a subtree
i18nContextThe context an I18n is provided under
rxjs (type)RxJS type re-export
DeesDemoWrapper from @design.estate/dees-element/demotoolsDemo helper custom element for post-render callbacks

In dees-element 4, the public domtools namespace (and DeesElement.domtools) exposes dees-domtools 5, TypedRequest 9 and dees-comms 2. Consumers upgrading from dees-element 3 must:

  • replace domtools.plugins.smartdelay with domtools.plugins.smartpromise, and on the DomTools instance replace DeesElement.domtools.convenience.smartdelay with DeesElement.domtools.convenience.smartpromise (delayFor, delayForRandom and Timeout keep their signatures);
  • import the DeesElement constructor options type as domtools.IDomToolsConstructorOptions (the misspelt IDomToolsContructorOptions is gone);
  • connect their own typedrequest routers and targets with TypedRequest 9, whose routeAndAddResponse() resolves T | null and whose IPostMethod resolves ITypedRequest | null.

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at [email protected].

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

版本与发布记录

@design.estate/dees-element 共发布 43 个版本,首次发布于 2023/08/07;过去 12 个月发布了 20 个版本。

版本发布日期距今
4.0.2latest2026/10/091 个月前
4.0.12026/10/061 个月前
4.0.02026/10/031 个月前
3.4.12026/10/021 个月前
3.4.02026/09/291 个月前
3.3.02026/09/261 个月前

关于 @design.estate/dees-element 的常见问题

@design.estate/dees-element 是什么?

A library for creating custom elements extending the lit element class with additional functionalities.

如何安装 @design.estate/dees-element?

在项目目录运行 npm install @design.estate/dees-element,或使用 pnpm add / yarn add / bun add。

@design.estate/dees-element 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@design.estate/dees-element 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@design.estate/dees-element 可以商用吗?

@design.estate/dees-element 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@design.estate/dees-element 的下载量有多少?

根据 npm 官方下载统计,@design.estate/dees-element 在 2026/10/12 – 2026/11/10 这 30 天内被下载 10,251 次,日均约 342 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@design.estate/dees-element 还在维护吗?

最新版本 4.0.2 发布于 2026/10/09(1 个月前);至今共发布 43 个版本,其中过去 12 个月发布了 20 个;首次发布于 2023/08/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。