mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-08 22:24:15 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8adb8119b8 | ||
|
|
f598ef95f8 | ||
|
|
b438224a89 | ||
|
|
11187ad972 | ||
|
|
2f4c3b3560 |
@@ -52,10 +52,24 @@ const CONFIG_FILES = [
|
||||
path.join(__dirname, "lit-disable-dev-mode-loader.cjs"),
|
||||
path.join(__dirname, "babel-plugins", "custom-polyfill-plugin.js"),
|
||||
path.join(__dirname, "babel-plugins", "inline-constants-plugin.cjs"),
|
||||
// Its exports define the hac source aliases below
|
||||
path.join(__dirname, "..", "packages", "hac", "package.json"),
|
||||
];
|
||||
|
||||
// Patches change a package's files but not its version, which is all the
|
||||
// node_modules snapshot checks, so their contents go into the version too.
|
||||
// Resolve @home-assistant/hac entry points to their sources, so the package
|
||||
// needs no build step during development. The entry points are read from the
|
||||
// "hac-source" export condition of the package.
|
||||
const hacSourceAliases = Object.fromEntries(
|
||||
Object.entries(require("../packages/hac/package.json").exports)
|
||||
.filter(([, target]) => target["hac-source"])
|
||||
.map(([subpath, target]) => [
|
||||
`${path.posix.join("@home-assistant/hac", subpath)}$`,
|
||||
path.resolve(paths.root_dir, "packages/hac", target["hac-source"]),
|
||||
])
|
||||
);
|
||||
|
||||
const PATCHES_DIR = path.join(paths.root_dir, "patches");
|
||||
|
||||
// Content hash of the toolchain versions and our own build files, used as the
|
||||
@@ -377,6 +391,7 @@ const createRspackConfig = ({
|
||||
resolve: {
|
||||
extensions: [".ts", ".js", ".json"],
|
||||
alias: {
|
||||
...hacSourceAliases,
|
||||
"lit/static-html$": "lit/static-html.js",
|
||||
"lit/decorators$": "lit/decorators.js",
|
||||
"lit/directive$": "lit/directive.js",
|
||||
|
||||
@@ -301,6 +301,14 @@ export default tseslint.config(
|
||||
globals: globals.node,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Published as native ESM, where package subpath imports such as
|
||||
// "lit/decorators.js" need their extension.
|
||||
files: ["packages/*/src/**/*.ts"],
|
||||
rules: {
|
||||
"import-x/extensions": ["error", "ignorePackages", { ts: "never" }],
|
||||
},
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
html,
|
||||
|
||||
@@ -80,6 +80,10 @@ export default [
|
||||
header: "Feedback and status",
|
||||
pages: ["ha-alert", "ha-spinner", "ha-tip", "ha-bar", "ha-gauge"],
|
||||
},
|
||||
{
|
||||
header: "Layout",
|
||||
pages: ["hac-card"],
|
||||
},
|
||||
{
|
||||
header: "Labels and text",
|
||||
pages: ["ha-badge", "ha-label-badge", "ha-chips", "ha-marquee-text"],
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Card
|
||||
subtitle: First component of the HAC component library
|
||||
---
|
||||
|
||||
# Card `<hac-card>` and header `<hac-header>`
|
||||
|
||||
`hac-card` groups related content. It is a plain container with three slots
|
||||
and no required class names. `hac-header` is a separate part that can be used
|
||||
inside a card or any other container.
|
||||
|
||||
Both live in the `@home-assistant/hac` package in `packages/hac`. Register them
|
||||
with `import "@home-assistant/hac/card"` and
|
||||
`import "@home-assistant/hac/header"`.
|
||||
|
||||
## `hac-card`
|
||||
|
||||
| Slot | Description |
|
||||
| -------- | ---------------------------------------------------- |
|
||||
| `header` | Header of the card, usually a `hac-header`. |
|
||||
| default | Content of the card. |
|
||||
| `footer` | Footer of the card, e.g. actions. Hidden when empty. |
|
||||
|
||||
| Property | Description |
|
||||
| ------------ | ----------------------------------------------- |
|
||||
| `appearance` | `outlined` (default) or `raised` with a shadow. |
|
||||
|
||||
## `hac-header`
|
||||
|
||||
| Slot | Description |
|
||||
| ---------- | --------------------------------- |
|
||||
| default | Title. |
|
||||
| `icon` | Icon before the title. |
|
||||
| `subtitle` | Secondary text below the title. |
|
||||
| `actions` | Actions at the end of the header. |
|
||||
|
||||
| Property | Description |
|
||||
| -------- | --------------------------------------------------------------- |
|
||||
| `level` | Heading level of the title for assistive technology, default 2. |
|
||||
@@ -0,0 +1,55 @@
|
||||
import { mdiFire } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-button";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "@home-assistant/hac/card";
|
||||
import "@home-assistant/hac/header";
|
||||
|
||||
@customElement("demo-components-hac-card")
|
||||
export class DemoHacCard extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="grid">
|
||||
<hac-card>
|
||||
<p>Content only.</p>
|
||||
</hac-card>
|
||||
|
||||
<hac-card>
|
||||
<hac-header slot="header">Title</hac-header>
|
||||
<p>Card with a header.</p>
|
||||
</hac-card>
|
||||
|
||||
<hac-card appearance="raised">
|
||||
<hac-header slot="header">
|
||||
<ha-svg-icon slot="icon" .path=${mdiFire}></ha-svg-icon>
|
||||
Gas
|
||||
<span slot="subtitle">Consumption and costs</span>
|
||||
</hac-header>
|
||||
<p>Raised card with header icon, subtitle and footer.</p>
|
||||
<ha-button slot="footer" appearance="plain">Cancel</ha-button>
|
||||
<ha-button slot="footer">Save</ha-button>
|
||||
</hac-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: var(--ha-space-4);
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
align-items: start;
|
||||
padding: var(--ha-space-4);
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-hac-card": DemoHacCard;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -13,7 +13,7 @@
|
||||
"lint:prettier": "prettier . --cache --check",
|
||||
"format:prettier": "prettier . --cache --write",
|
||||
"lint:types": "node ./node_modules/@typescript/native/bin/tsc && node ./node_modules/@typescript/native/bin/tsc -p .github/scripts",
|
||||
"lint:lit": "lit-analyzer \"{.,*}/src/**/*.ts\"",
|
||||
"lint:lit": "lit-analyzer \"{.,*,packages/*}/src/**/*.ts\"",
|
||||
"lint:element-imports": "eslint \"**/src/**/*.{js,ts,html}\" --ignore-pattern=.gitignore --rule '{\"ha/no-unused-element-import\": \"warn\"}'",
|
||||
"lint:licenses": "node --no-deprecation script/check-licenses",
|
||||
"lint": "pnpm run lint:eslint && pnpm run lint:prettier && pnpm run lint:types && pnpm run lint:lit",
|
||||
@@ -152,6 +152,7 @@
|
||||
"@bundle-stats/plugin-webpack-filter": "4.22.3",
|
||||
"@eslint/js": "10.0.1",
|
||||
"@gfx/zopfli": "1.0.15",
|
||||
"@home-assistant/hac": "workspace:*",
|
||||
"@html-eslint/eslint-plugin": "0.66.1",
|
||||
"@lokalise/node-api": "16.6.0",
|
||||
"@octokit/auth-oauth-device": "8.0.5",
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# @home-assistant/hac
|
||||
|
||||
Home Assistant Components (`hac-*`): framework-agnostic web components built
|
||||
with [Lit](https://lit.dev).
|
||||
|
||||
> Work in progress, not published yet.
|
||||
|
||||
## Usage
|
||||
|
||||
Register the components you use through their entry points. Each one only
|
||||
pulls in what it needs:
|
||||
|
||||
```ts
|
||||
import "@home-assistant/hac/card"; // registers <hac-card>
|
||||
import "@home-assistant/hac/header"; // registers <hac-header>
|
||||
```
|
||||
|
||||
```html
|
||||
<hac-card>
|
||||
<hac-header slot="header">
|
||||
Energy
|
||||
<span slot="subtitle">Configure your sources</span>
|
||||
</hac-header>
|
||||
<p>Content</p>
|
||||
<button slot="footer">Save</button>
|
||||
</hac-card>
|
||||
```
|
||||
|
||||
| Import | Registers elements | Use for |
|
||||
| -------------------------- | ------------------ | -------------------------------- |
|
||||
| `@home-assistant/hac/card` | `hac-card` | Using one component |
|
||||
| `@home-assistant/hac/all` | All components | Prototypes, no bundler |
|
||||
| `@home-assistant/hac` | None | Classes and types, e.g. `extend` |
|
||||
|
||||
Registration skips tag names that are already registered. When Home Assistant
|
||||
and a custom card both ship the library, the first registration wins and
|
||||
nothing throws.
|
||||
|
||||
`lit` is a peer dependency.
|
||||
|
||||
## Theming
|
||||
|
||||
Components read the Home Assistant theme variables when they are available
|
||||
and fall back to built-in defaults otherwise. Each component also exposes its
|
||||
own `--hac-*` custom properties, documented in its source.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
pnpm --filter @home-assistant/hac build # ESM + type declarations to dist/
|
||||
pnpm --filter @home-assistant/hac dev # rebuild on change
|
||||
```
|
||||
|
||||
Inside this repository the app and gallery use the sources directly through
|
||||
the `hac-source` export condition, so no build is needed during development.
|
||||
|
||||
The package is built with [Rslib](https://rslib.rs) in bundleless mode: every
|
||||
source file becomes its own ES module.
|
||||
|
||||
### Adding a component
|
||||
|
||||
1. Add `src/<name>/hac-<name>.ts` with the class. It must not register itself.
|
||||
2. Add `src/<name>/define.ts` that registers it with `define()`.
|
||||
3. Export the class from `src/index.ts` and the entry from `src/all.ts`.
|
||||
4. Add a `./<name>` entry to `exports` in `package.json`, including the
|
||||
`hac-source` condition.
|
||||
|
||||
Only `define.ts` files and `all.ts` have side effects; `sideEffects` in
|
||||
`package.json` relies on this to tree-shake everything else.
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@home-assistant/hac",
|
||||
"version": "0.0.0",
|
||||
"description": "Home Assistant Components: framework-agnostic web components built with Lit",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/home-assistant/frontend.git",
|
||||
"directory": "packages/hac"
|
||||
},
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
"./dist/all.js",
|
||||
"./dist/*/define.js",
|
||||
"./src/all.ts",
|
||||
"./src/*/define.ts"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"hac-source": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./all": {
|
||||
"hac-source": "./src/all.ts",
|
||||
"types": "./dist/all.d.ts",
|
||||
"import": "./dist/all.js"
|
||||
},
|
||||
"./card": {
|
||||
"hac-source": "./src/card/define.ts",
|
||||
"types": "./dist/card/define.d.ts",
|
||||
"import": "./dist/card/define.js"
|
||||
},
|
||||
"./header": {
|
||||
"hac-source": "./src/header/define.ts",
|
||||
"types": "./dist/header/define.d.ts",
|
||||
"import": "./dist/header/define.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rslib build",
|
||||
"dev": "rslib build --watch",
|
||||
"prepublishOnly": "rslib build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"lit": "^3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rslib/core": "1.0.3",
|
||||
"lit": "3.3.3",
|
||||
"typescript": "6.0.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { defineConfig } from "@rslib/core";
|
||||
|
||||
// Bundleless build: every source file becomes its own ESM file so consumers'
|
||||
// bundlers can tree-shake at component level. Custom elements register
|
||||
// themselves when their module is evaluated, so only the modules a consumer
|
||||
// actually imports end up in their bundle.
|
||||
export default defineConfig({
|
||||
source: {
|
||||
entry: { index: ["./src/**/*.ts"] },
|
||||
decorators: { version: "legacy" },
|
||||
tsconfigPath: "./tsconfig.json",
|
||||
},
|
||||
tools: {
|
||||
swc: { jsc: { transform: { decoratorMetadata: false } } },
|
||||
},
|
||||
lib: [
|
||||
{
|
||||
format: "esm",
|
||||
bundle: false,
|
||||
syntax: "es2021",
|
||||
dts: true,
|
||||
output: { target: "web", distPath: { root: "./dist" } },
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
// Registers all components.
|
||||
export * from "./card/define";
|
||||
export * from "./header/define";
|
||||
@@ -0,0 +1,6 @@
|
||||
import { define } from "../utils/define";
|
||||
import { HacCard } from "./hac-card";
|
||||
|
||||
define("hac-card", HacCard);
|
||||
|
||||
export { HacCard };
|
||||
@@ -0,0 +1,113 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
|
||||
/**
|
||||
* Container that groups related content.
|
||||
*
|
||||
* Importing this module does not register the element. Import
|
||||
* `@home-assistant/hac/card` to register `<hac-card>`.
|
||||
*
|
||||
* @slot header - Header of the card, usually a `hac-header`.
|
||||
* @slot - Content of the card.
|
||||
* @slot footer - Footer of the card, e.g. actions. Hidden when empty.
|
||||
*
|
||||
* @cssprop --hac-card-background - Background of the card.
|
||||
* @cssprop --hac-card-color - Text color of the card.
|
||||
* @cssprop --hac-card-border-color - Border color of the card.
|
||||
* @cssprop --hac-card-border-width - Border width of the card.
|
||||
* @cssprop --hac-card-border-radius - Border radius of the card.
|
||||
* @cssprop --hac-card-box-shadow - Box shadow of the card.
|
||||
* @cssprop --hac-card-content-padding - Padding of the content.
|
||||
* @cssprop --hac-card-footer-padding - Padding of the footer.
|
||||
*/
|
||||
export class HacCard extends LitElement {
|
||||
/** Visual style of the card. */
|
||||
@property({ reflect: true }) public appearance: "outlined" | "raised" =
|
||||
"outlined";
|
||||
|
||||
@state() private _hasHeader = false;
|
||||
|
||||
@state() private _hasFooter = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<slot name="header" @slotchange=${this._headerChanged}></slot>
|
||||
<div class="content ${this._hasHeader ? "below-header" : ""}">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="footer" ?hidden=${!this._hasFooter}>
|
||||
<slot name="footer" @slotchange=${this._footerChanged}></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _headerChanged(ev: Event) {
|
||||
this._hasHeader = _hasAssignedElements(ev);
|
||||
}
|
||||
|
||||
private _footerChanged(ev: Event) {
|
||||
this._hasFooter = _hasAssignedElements(ev);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
background: var(
|
||||
--hac-card-background,
|
||||
var(--card-background-color, #fff)
|
||||
);
|
||||
color: var(--hac-card-color, var(--primary-text-color, #141414));
|
||||
border: var(--hac-card-border-width, 1px) solid
|
||||
var(--hac-card-border-color, var(--divider-color, #e0e0e0));
|
||||
border-radius: var(
|
||||
--hac-card-border-radius,
|
||||
var(--ha-border-radius-lg, 12px)
|
||||
);
|
||||
box-shadow: var(--hac-card-box-shadow, none);
|
||||
}
|
||||
|
||||
:host([appearance="raised"]) {
|
||||
border-color: transparent;
|
||||
box-shadow: var(
|
||||
--hac-card-box-shadow,
|
||||
0 2px 1px -1px rgba(0, 0, 0, 0.2),
|
||||
0 1px 1px 0 rgba(0, 0, 0, 0.14),
|
||||
0 1px 3px 0 rgba(0, 0, 0, 0.12)
|
||||
);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--hac-card-content-padding, var(--ha-space-4, 16px));
|
||||
}
|
||||
|
||||
/* The header already provides the spacing above the content */
|
||||
.content.below-header {
|
||||
padding-block-start: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2, 8px);
|
||||
padding: var(--hac-card-footer-padding, var(--ha-space-2, 8px));
|
||||
border-block-start: 1px solid var(--divider-color, #e0e0e0);
|
||||
}
|
||||
|
||||
.footer[hidden] {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const _hasAssignedElements = (ev: Event) =>
|
||||
(ev.target as HTMLSlotElement).assignedElements().length > 0;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hac-card": HacCard;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { define } from "../utils/define";
|
||||
import { HacHeader } from "./hac-header";
|
||||
|
||||
define("hac-header", HacHeader);
|
||||
|
||||
export { HacHeader };
|
||||
@@ -0,0 +1,131 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
|
||||
/**
|
||||
* Header with an optional icon, a title, a subtitle and actions.
|
||||
*
|
||||
* Reusable in any container, e.g. `hac-card`. The title is exposed to
|
||||
* assistive technology as a heading of the given `level`.
|
||||
*
|
||||
* Importing this module does not register the element. Import
|
||||
* `@home-assistant/hac/header` to register `<hac-header>`.
|
||||
*
|
||||
* @slot - Title of the header.
|
||||
* @slot icon - Icon displayed before the title.
|
||||
* @slot subtitle - Secondary text displayed below the title.
|
||||
* @slot actions - Actions displayed at the end of the header.
|
||||
*
|
||||
* @cssprop --hac-header-color - Title color.
|
||||
* @cssprop --hac-header-secondary-color - Color of the icon and subtitle.
|
||||
* @cssprop --hac-header-font-family - Title font family.
|
||||
* @cssprop --hac-header-font-size - Title font size.
|
||||
* @cssprop --hac-header-padding - Padding of the header.
|
||||
*/
|
||||
export class HacHeader extends LitElement {
|
||||
/** Heading level of the title for assistive technology. */
|
||||
@property({ type: Number }) public level: 1 | 2 | 3 | 4 | 5 | 6 = 2;
|
||||
|
||||
@state() private _hasIcon = false;
|
||||
|
||||
@state() private _hasSubtitle = false;
|
||||
|
||||
@state() private _hasActions = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="icon" ?hidden=${!this._hasIcon}>
|
||||
<slot name="icon" @slotchange=${this._iconChanged}></slot>
|
||||
</div>
|
||||
<div class="text">
|
||||
<div class="title" role="heading" aria-level=${this.level}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="subtitle" ?hidden=${!this._hasSubtitle}>
|
||||
<slot name="subtitle" @slotchange=${this._subtitleChanged}></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions" ?hidden=${!this._hasActions}>
|
||||
<slot name="actions" @slotchange=${this._actionsChanged}></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _iconChanged(ev: Event) {
|
||||
this._hasIcon = _hasAssignedElements(ev);
|
||||
}
|
||||
|
||||
private _subtitleChanged(ev: Event) {
|
||||
this._hasSubtitle = _hasAssignedElements(ev);
|
||||
}
|
||||
|
||||
private _actionsChanged(ev: Event) {
|
||||
this._hasActions = _hasAssignedElements(ev);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-3, 12px);
|
||||
box-sizing: border-box;
|
||||
padding: var(
|
||||
--hac-header-padding,
|
||||
var(--ha-space-4, 16px) var(--ha-space-4, 16px) var(--ha-space-3, 12px)
|
||||
);
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
flex: none;
|
||||
color: var(
|
||||
--hac-header-secondary-color,
|
||||
var(--secondary-text-color, #727272)
|
||||
);
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--hac-header-color, var(--primary-text-color, #141414));
|
||||
font-family: var(--hac-header-font-family, inherit);
|
||||
font-size: var(--hac-header-font-size, var(--ha-font-size-2xl, 24px));
|
||||
font-weight: var(--ha-font-weight-normal, 400);
|
||||
line-height: var(--ha-line-height-condensed, 1.2);
|
||||
letter-spacing: -0.012em;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-block-start: var(--ha-space-1, 4px);
|
||||
color: var(
|
||||
--hac-header-secondary-color,
|
||||
var(--secondary-text-color, #727272)
|
||||
);
|
||||
font-size: var(--ha-font-size-m, 14px);
|
||||
line-height: var(--ha-line-height-normal, 1.6);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2, 8px);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const _hasAssignedElements = (ev: Event) =>
|
||||
(ev.target as HTMLSlotElement).assignedElements().length > 0;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hac-header": HacHeader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Classes and types only. Importing them does not register any element, so
|
||||
// unused components are tree-shaken. Register elements through their entry
|
||||
// points, e.g. `@home-assistant/hac/card`, or all at once via
|
||||
// `@home-assistant/hac/all`.
|
||||
export { HacCard } from "./card/hac-card";
|
||||
export { HacHeader } from "./header/hac-header";
|
||||
export { define } from "./utils/define";
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Registers a custom element unless the tag name is already taken.
|
||||
*
|
||||
* The custom element registry is global to the page. Home Assistant and custom
|
||||
* cards can each ship a copy of this library, so registering the same tag name
|
||||
* twice must not throw. The first registration wins.
|
||||
*/
|
||||
export const define = (
|
||||
tagName: string,
|
||||
elementClass: CustomElementConstructor
|
||||
): void => {
|
||||
if (!customElements.get(tagName)) {
|
||||
customElements.define(tagName, elementClass);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { define } from "../src/utils/define";
|
||||
|
||||
// Home Assistant and custom cards can each ship a copy of the library, so a
|
||||
// second registration of the same tag must be ignored instead of throwing.
|
||||
describe("define", () => {
|
||||
it("registers an element that is not registered yet", () => {
|
||||
class First extends HTMLElement {}
|
||||
define("hac-test-first", First);
|
||||
expect(customElements.get("hac-test-first")).toBe(First);
|
||||
});
|
||||
|
||||
it("keeps the first registration of a tag name", () => {
|
||||
class Original extends HTMLElement {}
|
||||
class Duplicate extends HTMLElement {}
|
||||
define("hac-test-duplicate", Original);
|
||||
expect(() => define("hac-test-duplicate", Duplicate)).not.toThrow();
|
||||
expect(customElements.get("hac-test-duplicate")).toBe(Original);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"lib": ["ES2021", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Generated
+186
-6
@@ -513,6 +513,9 @@ importers:
|
||||
'@gfx/zopfli':
|
||||
specifier: 1.0.15
|
||||
version: 1.0.15
|
||||
'@home-assistant/hac':
|
||||
specifier: workspace:*
|
||||
version: link:packages/hac
|
||||
'@html-eslint/eslint-plugin':
|
||||
specifier: 0.66.1
|
||||
version: 0.66.1([email protected]([email protected]))
|
||||
@@ -530,7 +533,7 @@ importers:
|
||||
version: 1.63.0
|
||||
'@rsdoctor/rspack-plugin':
|
||||
specifier: 1.6.4
|
||||
version: 1.6.4(@emnapi/[email protected])(@emnapi/[email protected])(@rspack/[email protected](@swc/[email protected]))([email protected])
|
||||
version: 1.6.4(@emnapi/[email protected])(@emnapi/[email protected])(@rsbuild/[email protected]([email protected]))(@rspack/[email protected](@swc/[email protected]))([email protected])
|
||||
'@rspack/core':
|
||||
specifier: 2.2.8
|
||||
version: 2.2.8(@swc/[email protected])
|
||||
@@ -739,6 +742,18 @@ importers:
|
||||
specifier: 7.4.1
|
||||
version: 7.4.1(patch_hash=75e6346ad8d8af554c167f77ac7d9b9652f010398efdc6b399925a4795133099)([email protected])
|
||||
|
||||
packages/hac:
|
||||
devDependencies:
|
||||
'@rslib/core':
|
||||
specifier: 1.0.3
|
||||
version: 1.0.3([email protected])([email protected])
|
||||
lit:
|
||||
specifier: 3.3.3
|
||||
version: 3.3.3
|
||||
typescript:
|
||||
specifier: 6.0.3
|
||||
version: 6.0.3
|
||||
|
||||
packages:
|
||||
|
||||
'@ampproject/[email protected]':
|
||||
@@ -759,6 +774,68 @@ packages:
|
||||
resolution: {integrity: sha512-NT4s3yZLjovPpliRpTvdsdzyPjqRqiCZj9MxnarBihbaY5MbAG7DWAJcrLlhmC7xKTNCXsTELcqB8YsqpLnUFQ==}
|
||||
engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0}
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
resolution: {integrity: sha512-rMZJ6frSpR/xSbeV4hiA9WR5hHqk3rVYX8lOqEoUCaUnjEuFXvHdGN4WawJ8pVDq/IqM1UnTtsDSYgUdVmO5DA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
resolution: {integrity: sha512-tIclZ5pM+YSgk8NNTBoTkaclbS39cU5lbVfSrFWX4LUnxXfUPG+F+ppxyFjKwJTBELsGHEO4LdX3/Lc+FDwPNA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
resolution: {integrity: sha512-Bm8F7/oRHQkDXbjH+gBi9oB5huJ0q12r/AIOMIypf5PWlAVvh206Tsw468phw4Oh6kd4Ad1Z+gflNW1MutgK2g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
resolution: {integrity: sha512-yr5xcRerV41GeRxmZ8SKbfGeyZRhQePoNNCgTAb4OrWMelWwbMI80aY7/IlF2bo1sN40BSW4hZdGIO/akTd1RQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
resolution: {integrity: sha512-lluRK3hxfHMQdFuVID6seAKVx4FH0kEwF6rCh5/I40Sf3EtNR2UCBXAqd6RBA6hqlSckktvBxCfw+h4aescDmg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
resolution: {integrity: sha512-09wJQKq86LqILqQtw0+X09SzhcPwUnMVFKyia7S3XLbLqSeQpAMF55Y+zll53JRQ1dCzFEZJyvTT/7j9vwfE3w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
resolution: {integrity: sha512-/HiVgw2cdtzXLDLoG4eBBcauekNXewfagy7qiVbjkVu/KcCH8dJ9lpj8DzeWwuBNtZxuD8jpiwP0M1pz3Bc3uw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
resolution: {integrity: sha512-/gwJrJQUw65uNtAFcG2rtSTr117Gqqy3bfjaljCPKsa65dTeRcMMnjKLlZ6V9/g8z+4Xf78kdhp5ECbir4yfSw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
resolution: {integrity: sha512-CEZAMAsaDoQoVE3MT2c/ry3/ydfyaqlTjxv8DK5PG355SrdAYPgevGC2Me6tz4dhphJin89Y20VsnrS5UrYV5Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
resolution: {integrity: sha512-bjJsUt0g3UCwbn9bjv7MnoR15rszSm79yPCXn4DWWK0NuYUUqBU/srtNMR4WnpfR7TnfBRdYFoqSNx0pGCNWJg==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@babel/[email protected]':
|
||||
resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -2898,6 +2975,16 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rsbuild/[email protected]':
|
||||
resolution: {integrity: sha512-avPW0j6RaEbtV2mfl0AzTAJEimPX4Xj0pCcZyxNCrE9vkS91HmweM5V7XQj3kI5iTYfHRp9Y6To8RRHYLEvG7A==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
core-js: '>= 3.0.0'
|
||||
peerDependenciesMeta:
|
||||
core-js:
|
||||
optional: true
|
||||
|
||||
'@rsbuild/[email protected]':
|
||||
resolution: {integrity: sha512-26xtEYN0QjZYoyt0lWnvIztBWjEZJvcfw7MN4f5B4SpNggmnF7F7aNPrgkY3EccXVFx1VGQBhnCkBV//OoS07Q==}
|
||||
peerDependencies:
|
||||
@@ -2940,6 +3027,19 @@ packages:
|
||||
'@rsdoctor/[email protected]':
|
||||
resolution: {integrity: sha512-7oliDqtaLVtmVFkfSN3AKWoMQb8kuZA9AURCgb26cUW7+ZD3qgADg9uyD+WevpA6IOeF6gqo3hezTZKlwFR2Sg==}
|
||||
|
||||
'@rslib/[email protected]':
|
||||
resolution: {integrity: sha512-a5IZiyf9sNeeE9jcTsTmz16vxcThpbOSJg0BZXzJAxrUFOZX5xG25PJua9hr0LDWGryBX2UQTruVsLQ8BCpqng==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@microsoft/api-extractor': ^7
|
||||
typescript: ^5 || ^6 || ^7
|
||||
peerDependenciesMeta:
|
||||
'@microsoft/api-extractor':
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@rspack/[email protected]':
|
||||
resolution: {integrity: sha512-d6GuY/k7kJk7SOXmruIz0m+2fIXaLv2SnBHEYsbvBzE7ZO6QXq7fSABWFWNnSnNu+brYHA5Dr/skV81bXgTTCw==}
|
||||
cpu: [arm64]
|
||||
@@ -6728,6 +6828,19 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-wVxMYvWmVPam6SeZsz8OixHm+4JPfLJAAMTD0Q2te2SxksvjuHC3wuvU12teSmloQ3nFX1YC6guNzuYqDYw+vQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
peerDependencies:
|
||||
'@microsoft/api-extractor': ^7
|
||||
'@rsbuild/core': ^2.0.0
|
||||
typescript: ^5 || ^6 || ^7
|
||||
peerDependenciesMeta:
|
||||
'@microsoft/api-extractor':
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-g2wW/ermwzGTLlzGdJBDRc4YKz2/yPBjf57w9g5CvhtpZ91Xq95OaWku0oNHYi+XsuV6v8QrCo2oJJR/pCUR8g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
@@ -7901,6 +8014,45 @@ snapshots:
|
||||
is-potential-custom-element-name: 1.0.1
|
||||
lru-cache: 11.5.3
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@ast-grep/[email protected]':
|
||||
optionalDependencies:
|
||||
'@ast-grep/napi-darwin-arm64': 0.45.3
|
||||
'@ast-grep/napi-darwin-x64': 0.45.3
|
||||
'@ast-grep/napi-linux-arm64-gnu': 0.45.3
|
||||
'@ast-grep/napi-linux-arm64-musl': 0.45.3
|
||||
'@ast-grep/napi-linux-x64-gnu': 0.45.3
|
||||
'@ast-grep/napi-linux-x64-musl': 0.45.3
|
||||
'@ast-grep/napi-win32-arm64-msvc': 0.45.3
|
||||
'@ast-grep/napi-win32-ia32-msvc': 0.45.3
|
||||
'@ast-grep/napi-win32-x64-msvc': 0.45.3
|
||||
|
||||
'@babel/[email protected]':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.29.7
|
||||
@@ -10547,19 +10699,30 @@ snapshots:
|
||||
'@rollup/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@rsbuild/[email protected]':
|
||||
'@rsbuild/[email protected]([email protected])':
|
||||
dependencies:
|
||||
'@rspack/core': 2.2.8(@swc/[email protected])
|
||||
'@swc/helpers': 0.5.23
|
||||
optionalDependencies:
|
||||
core-js: 3.50.0
|
||||
transitivePeerDependencies:
|
||||
- '@module-federation/runtime-tools'
|
||||
|
||||
'@rsbuild/[email protected](@rsbuild/[email protected]([email protected]))':
|
||||
dependencies:
|
||||
acorn: 8.18.0
|
||||
browserslist-to-es-version: 1.4.2
|
||||
htmlparser2: 10.0.0
|
||||
picocolors: 1.1.1
|
||||
source-map: 0.7.6
|
||||
optionalDependencies:
|
||||
'@rsbuild/core': 2.2.11([email protected])
|
||||
|
||||
'@rsdoctor/[email protected]': {}
|
||||
|
||||
'@rsdoctor/[email protected](@emnapi/[email protected])(@emnapi/[email protected])(@rspack/[email protected](@swc/[email protected]))([email protected])':
|
||||
'@rsdoctor/[email protected](@emnapi/[email protected])(@emnapi/[email protected])(@rsbuild/[email protected]([email protected]))(@rspack/[email protected](@swc/[email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@rsbuild/plugin-check-syntax': 1.6.1
|
||||
'@rsbuild/plugin-check-syntax': 1.6.1(@rsbuild/[email protected]([email protected]))
|
||||
'@rsdoctor/graph': 1.6.4(@rspack/[email protected](@swc/[email protected]))
|
||||
'@rsdoctor/sdk': 1.6.4(@rspack/[email protected](@swc/[email protected]))([email protected])
|
||||
'@rsdoctor/types': 1.6.4(@rspack/[email protected](@swc/[email protected]))
|
||||
@@ -10592,9 +10755,9 @@ snapshots:
|
||||
- '@rspack/core'
|
||||
- webpack
|
||||
|
||||
'@rsdoctor/[email protected](@emnapi/[email protected])(@emnapi/[email protected])(@rspack/[email protected](@swc/[email protected]))([email protected])':
|
||||
'@rsdoctor/[email protected](@emnapi/[email protected])(@emnapi/[email protected])(@rsbuild/[email protected]([email protected]))(@rspack/[email protected](@swc/[email protected]))([email protected])':
|
||||
dependencies:
|
||||
'@rsdoctor/core': 1.6.4(@emnapi/[email protected])(@emnapi/[email protected])(@rspack/[email protected](@swc/[email protected]))([email protected])
|
||||
'@rsdoctor/core': 1.6.4(@emnapi/[email protected])(@emnapi/[email protected])(@rsbuild/[email protected]([email protected]))(@rspack/[email protected](@swc/[email protected]))([email protected])
|
||||
'@rsdoctor/graph': 1.6.4(@rspack/[email protected](@swc/[email protected]))
|
||||
'@rsdoctor/sdk': 1.6.4(@rspack/[email protected](@swc/[email protected]))([email protected])
|
||||
'@rsdoctor/types': 1.6.4(@rspack/[email protected](@swc/[email protected]))
|
||||
@@ -10657,6 +10820,16 @@ snapshots:
|
||||
- '@rspack/core'
|
||||
- webpack
|
||||
|
||||
'@rslib/[email protected]([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@rsbuild/core': 2.2.11([email protected])
|
||||
rsbuild-plugin-dts: 1.0.3(@rsbuild/[email protected]([email protected]))([email protected])
|
||||
optionalDependencies:
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- '@module-federation/runtime-tools'
|
||||
- core-js
|
||||
|
||||
'@rspack/[email protected]':
|
||||
optional: true
|
||||
|
||||
@@ -14580,6 +14753,13 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
[email protected](@rsbuild/[email protected]([email protected]))([email protected]):
|
||||
dependencies:
|
||||
'@ast-grep/napi': 0.45.3
|
||||
'@rsbuild/core': 2.2.11([email protected])
|
||||
optionalDependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected](@rspack/[email protected](@swc/[email protected])):
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
packages:
|
||||
- "."
|
||||
- "packages/*"
|
||||
|
||||
savePrefix: ""
|
||||
autoInstallPeers: false
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// Modules
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
// Resolve workspace packages to their sources, see packages/hac
|
||||
"customConditions": ["hac-source"],
|
||||
"resolveJsonModule": true,
|
||||
// Babel handles transpiling and no need for declaration files
|
||||
"noEmit": true,
|
||||
|
||||
Reference in New Issue
Block a user