mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-10-31 06:29:43 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			ha-icon-pi
			...
			move-defau
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 365db51526 | 
							
								
								
									
										6
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,7 @@ body: | ||||
|  | ||||
|         **Please do not report issues for custom cards.** | ||||
|  | ||||
|         [fr]: https://github.com/orgs/home-assistant/discussions | ||||
|         [fr]: https://github.com/home-assistant/frontend/discussions | ||||
|         [releases]: https://github.com/home-assistant/home-assistant/releases | ||||
|   - type: checkboxes | ||||
|     attributes: | ||||
| @@ -108,9 +108,9 @@ body: | ||||
|       render: yaml | ||||
|   - type: textarea | ||||
|     attributes: | ||||
|       label: JavaScript errors shown in your browser console/inspector | ||||
|       label: Javascript errors shown in your browser console/inspector | ||||
|       description: > | ||||
|         If you come across any JavaScript or other error logs, e.g., in your | ||||
|         If you come across any Javascript or other error logs, e.g., in your | ||||
|         browser console/inspector please provide them. | ||||
|       render: txt | ||||
|   - type: textarea | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| blank_issues_enabled: false | ||||
| contact_links: | ||||
|   - name: Request a feature for the UI / Dashboards | ||||
|     url: https://github.com/orgs/home-assistant/discussions | ||||
|     url: https://github.com/home-assistant/frontend/discussions/category_choices | ||||
|     about: Request a new feature for the Home Assistant frontend. | ||||
|   - name: Report a bug that is NOT related to the UI / Dashboards | ||||
|     url: https://github.com/home-assistant/core/issues | ||||
|   | ||||
							
								
								
									
										53
									
								
								.github/ISSUE_TEMPLATE/task.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										53
									
								
								.github/ISSUE_TEMPLATE/task.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,53 +0,0 @@ | ||||
| name: Task | ||||
| description: For staff only - Create a task | ||||
| type: Task | ||||
| body: | ||||
|   - type: markdown | ||||
|     attributes: | ||||
|       value: | | ||||
|         ## ⚠️ RESTRICTED ACCESS | ||||
|  | ||||
|         **This form is restricted to Open Home Foundation staff and authorized contributors only.** | ||||
|  | ||||
|         If you are a community member wanting to contribute, please: | ||||
|         - For bug reports: Use the [bug report form](https://github.com/home-assistant/frontend/issues/new?template=bug_report.yml) | ||||
|         - For feature requests: Submit to [Feature Requests](https://github.com/orgs/home-assistant/discussions) | ||||
|  | ||||
|         --- | ||||
|  | ||||
|         ### For authorized contributors | ||||
|  | ||||
|         Use this form to create tasks for development work, improvements, or other actionable items that need to be tracked. | ||||
|   - type: textarea | ||||
|     id: description | ||||
|     attributes: | ||||
|       label: Description | ||||
|       description: | | ||||
|         Provide a clear and detailed description of the task that needs to be accomplished. | ||||
|  | ||||
|         Be specific about what needs to be done, why it's important, and any constraints or requirements. | ||||
|       placeholder: | | ||||
|         Describe the task, including: | ||||
|         - What needs to be done | ||||
|         - Why this task is needed | ||||
|         - Expected outcome | ||||
|         - Any constraints or requirements | ||||
|     validations: | ||||
|       required: true | ||||
|   - type: textarea | ||||
|     id: additional_context | ||||
|     attributes: | ||||
|       label: Additional context | ||||
|       description: | | ||||
|         Any additional information, links, research, or context that would be helpful. | ||||
|  | ||||
|         Include links to related issues, research, prototypes, roadmap opportunities etc. | ||||
|       placeholder: | | ||||
|         - Roadmap opportunity: [link] | ||||
|         - Epic: [link] | ||||
|         - Feature request: [link] | ||||
|         - Technical design documents: [link] | ||||
|         - Prototype/mockup: [link] | ||||
|         - Dependencies: [links] | ||||
|     validations: | ||||
|       required: false | ||||
							
								
								
									
										596
									
								
								.github/copilot-instructions.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										596
									
								
								.github/copilot-instructions.md
									
									
									
									
										vendored
									
									
								
							| @@ -1,596 +0,0 @@ | ||||
| # GitHub Copilot & Claude Code Instructions | ||||
|  | ||||
| You are an assistant helping with development of the Home Assistant frontend. The frontend is built using Lit-based Web Components and TypeScript, providing a responsive and performant interface for home automation control. | ||||
|  | ||||
| ## Table of Contents | ||||
|  | ||||
| - [Quick Reference](#quick-reference) | ||||
| - [Core Architecture](#core-architecture) | ||||
| - [Development Standards](#development-standards) | ||||
| - [Component Library](#component-library) | ||||
| - [Common Patterns](#common-patterns) | ||||
| - [Text and Copy Guidelines](#text-and-copy-guidelines) | ||||
| - [Development Workflow](#development-workflow) | ||||
| - [Review Guidelines](#review-guidelines) | ||||
|  | ||||
| ## Quick Reference | ||||
|  | ||||
| ### Essential Commands | ||||
|  | ||||
| ```bash | ||||
| yarn lint          # ESLint + Prettier + TypeScript + Lit | ||||
| yarn format        # Auto-fix ESLint + Prettier | ||||
| yarn lint:types    # TypeScript compiler | ||||
| yarn test          # Vitest | ||||
| script/develop     # Development server | ||||
| ``` | ||||
|  | ||||
| ### Component Prefixes | ||||
|  | ||||
| - `ha-` - Home Assistant components | ||||
| - `hui-` - Lovelace UI components | ||||
| - `dialog-` - Dialog components | ||||
|  | ||||
| ### Import Patterns | ||||
|  | ||||
| ```typescript | ||||
| import type { HomeAssistant } from "../types"; | ||||
| import { fireEvent } from "../common/dom/fire_event"; | ||||
| import { showAlertDialog } from "../dialogs/generic/show-alert-dialog"; | ||||
| ``` | ||||
|  | ||||
| ## Core Architecture | ||||
|  | ||||
| The Home Assistant frontend is a modern web application that: | ||||
|  | ||||
| - Uses Web Components (custom elements) built with Lit framework | ||||
| - Is written entirely in TypeScript with strict type checking | ||||
| - Communicates with the backend via WebSocket API | ||||
| - Provides comprehensive theming and internationalization | ||||
|  | ||||
| ## Development Standards | ||||
|  | ||||
| ### Code Quality Requirements | ||||
|  | ||||
| **Linting and Formatting (Enforced by Tools)** | ||||
|  | ||||
| - ESLint config extends Airbnb, TypeScript strict, Lit, Web Components, Accessibility | ||||
| - Prettier with ES5 trailing commas enforced | ||||
| - No console statements (`no-console: "error"`) - use proper logging | ||||
| - Import organization: No unused imports, consistent type imports | ||||
|  | ||||
| **Naming Conventions** | ||||
|  | ||||
| - PascalCase for types and classes | ||||
| - camelCase for variables, methods | ||||
| - Private methods require leading underscore | ||||
| - Public methods forbid leading underscore | ||||
|  | ||||
| ### TypeScript Usage | ||||
|  | ||||
| - **Always use strict TypeScript**: Enable all strict flags, avoid `any` types | ||||
| - **Proper type imports**: Use `import type` for type-only imports | ||||
| - **Define interfaces**: Create proper interfaces for data structures | ||||
| - **Type component properties**: All Lit properties must be properly typed | ||||
| - **No unused variables**: Prefix with `_` if intentionally unused | ||||
| - **Consistent imports**: Use `@typescript-eslint/consistent-type-imports` | ||||
|  | ||||
| ```typescript | ||||
| // Good | ||||
| import type { HomeAssistant } from "../types"; | ||||
|  | ||||
| interface EntityConfig { | ||||
|   entity: string; | ||||
|   name?: string; | ||||
| } | ||||
|  | ||||
| @property({ type: Object }) | ||||
| hass!: HomeAssistant; | ||||
|  | ||||
| // Bad | ||||
| @property() | ||||
| hass: any; | ||||
| ``` | ||||
|  | ||||
| ### Web Components with Lit | ||||
|  | ||||
| - **Use Lit 3.x patterns**: Follow modern Lit practices | ||||
| - **Extend appropriate base classes**: Use `LitElement`, `SubscribeMixin`, or other mixins as needed | ||||
| - **Define custom element names**: Use `ha-` prefix for components | ||||
|  | ||||
| ```typescript | ||||
| @customElement("ha-my-component") | ||||
| export class HaMyComponent extends LitElement { | ||||
|   @property({ attribute: false }) | ||||
|   hass!: HomeAssistant; | ||||
|  | ||||
|   @state() | ||||
|   private _config?: MyComponentConfig; | ||||
|  | ||||
|   static get styles() { | ||||
|     return css` | ||||
|       :host { | ||||
|         display: block; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   render() { | ||||
|     return html`<div>Content</div>`; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Component Guidelines | ||||
|  | ||||
| - **Use composition**: Prefer composition over inheritance | ||||
| - **Lazy load panels**: Heavy panels should be dynamically imported | ||||
| - **Optimize renders**: Use `@state()` for internal state, `@property()` for public API | ||||
| - **Handle loading states**: Always show appropriate loading indicators | ||||
| - **Support themes**: Use CSS custom properties from theme | ||||
|  | ||||
| ### Data Management | ||||
|  | ||||
| - **Use WebSocket API**: All backend communication via home-assistant-js-websocket | ||||
| - **Cache appropriately**: Use collections and caching for frequently accessed data | ||||
| - **Handle errors gracefully**: All API calls should have error handling | ||||
| - **Update real-time**: Subscribe to state changes for live updates | ||||
|  | ||||
| ```typescript | ||||
| // Good | ||||
| try { | ||||
|   const result = await fetchEntityRegistry(this.hass.connection); | ||||
|   this._processResult(result); | ||||
| } catch (err) { | ||||
|   showAlertDialog(this, { | ||||
|     text: `Failed to load: ${err.message}`, | ||||
|   }); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Styling Guidelines | ||||
|  | ||||
| - **Use CSS custom properties**: Leverage the theme system | ||||
| - **Mobile-first responsive**: Design for mobile, enhance for desktop | ||||
| - **Follow Material Design**: Use Material Web Components where appropriate | ||||
| - **Support RTL**: Ensure all layouts work in RTL languages | ||||
|  | ||||
| ```typescript | ||||
| static get styles() { | ||||
|   return css` | ||||
|     :host { | ||||
|       --spacing: 16px; | ||||
|       padding: var(--spacing); | ||||
|       color: var(--primary-text-color); | ||||
|       background-color: var(--card-background-color); | ||||
|     } | ||||
|  | ||||
|     @media (max-width: 600px) { | ||||
|       :host { | ||||
|         --spacing: 8px; | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Performance Best Practices | ||||
|  | ||||
| - **Code split**: Split code at the panel/dialog level | ||||
| - **Lazy load**: Use dynamic imports for heavy components | ||||
| - **Optimize bundle**: Keep initial bundle size minimal | ||||
| - **Use virtual scrolling**: For long lists, implement virtual scrolling | ||||
| - **Memoize computations**: Cache expensive calculations | ||||
|  | ||||
| ### Testing Requirements | ||||
|  | ||||
| - **Write tests**: Add tests for data processing and utilities | ||||
| - **Test with Vitest**: Use the established test framework | ||||
| - **Mock appropriately**: Mock WebSocket connections and API calls | ||||
| - **Test accessibility**: Ensure components are accessible | ||||
|  | ||||
| ## Component Library | ||||
|  | ||||
| ### Dialog Components | ||||
|  | ||||
| **Available Dialog Types:** | ||||
|  | ||||
| - `ha-md-dialog` - Preferred for new code (Material Design 3) | ||||
| - `ha-dialog` - Legacy component still widely used | ||||
|  | ||||
| **Opening Dialogs (Fire Event Pattern - Recommended):** | ||||
|  | ||||
| ```typescript | ||||
| fireEvent(this, "show-dialog", { | ||||
|   dialogTag: "dialog-example", | ||||
|   dialogImport: () => import("./dialog-example"), | ||||
|   dialogParams: { title: "Example", data: someData }, | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| **Dialog Implementation Requirements:** | ||||
|  | ||||
| - Implement `HassDialog<T>` interface | ||||
| - Use `createCloseHeading()` for standard headers | ||||
| - Import `haStyleDialog` for consistent styling | ||||
| - Return `nothing` when no params (loading state) | ||||
| - Fire `dialog-closed` event when closing | ||||
| - Add `dialogInitialFocus` for accessibility | ||||
|  | ||||
| ```` | ||||
|  | ||||
| ### Form Component (ha-form) | ||||
| - Schema-driven using `HaFormSchema[]` | ||||
| - Supports entity, device, area, target, number, boolean, time, action, text, object, select, icon, media, location selectors | ||||
| - Built-in validation with error display | ||||
| - Use `dialogInitialFocus` in dialogs | ||||
| - Use `computeLabel`, `computeError`, `computeHelper` for translations | ||||
|  | ||||
| ```typescript | ||||
| <ha-form | ||||
|   .hass=${this.hass} | ||||
|   .data=${this._data} | ||||
|   .schema=${this._schema} | ||||
|   .error=${this._errors} | ||||
|   .computeLabel=${(schema) => this.hass.localize(`ui.panel.${schema.name}`)} | ||||
|   @value-changed=${this._valueChanged} | ||||
| ></ha-form> | ||||
| ```` | ||||
|  | ||||
| ### Alert Component (ha-alert) | ||||
|  | ||||
| - Types: `error`, `warning`, `info`, `success` | ||||
| - Properties: `title`, `alert-type`, `dismissable`, `icon`, `action`, `rtl` | ||||
| - Content announced by screen readers when dynamically displayed | ||||
|  | ||||
| ```html | ||||
| <ha-alert alert-type="error">Error message</ha-alert> | ||||
| <ha-alert alert-type="warning" title="Warning">Description</ha-alert> | ||||
| <ha-alert alert-type="success" dismissable>Success message</ha-alert> | ||||
| ``` | ||||
|  | ||||
| ## Common Patterns | ||||
|  | ||||
| ### Creating a Panel | ||||
|  | ||||
| ```typescript | ||||
| @customElement("ha-panel-myfeature") | ||||
| export class HaPanelMyFeature extends SubscribeMixin(LitElement) { | ||||
|   @property({ attribute: false }) | ||||
|   hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ type: Boolean, reflect: true }) | ||||
|   narrow!: boolean; | ||||
|  | ||||
|   @property() | ||||
|   route!: Route; | ||||
|  | ||||
|   hassSubscribe() { | ||||
|     return [ | ||||
|       subscribeEntityRegistry(this.hass.connection, (entities) => { | ||||
|         this._entities = entities; | ||||
|       }), | ||||
|     ]; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Creating a Dialog | ||||
|  | ||||
| ```typescript | ||||
| @customElement("dialog-my-feature") | ||||
| export class DialogMyFeature | ||||
|   extends LitElement | ||||
|   implements HassDialog<MyDialogParams> | ||||
| { | ||||
|   @property({ attribute: false }) | ||||
|   hass!: HomeAssistant; | ||||
|  | ||||
|   @state() | ||||
|   private _params?: MyDialogParams; | ||||
|  | ||||
|   public async showDialog(params: MyDialogParams): Promise<void> { | ||||
|     this._params = params; | ||||
|   } | ||||
|  | ||||
|   public closeDialog(): void { | ||||
|     this._params = undefined; | ||||
|     fireEvent(this, "dialog-closed", { dialog: this.localName }); | ||||
|   } | ||||
|  | ||||
|   protected render() { | ||||
|     if (!this._params) { | ||||
|       return nothing; | ||||
|     } | ||||
|  | ||||
|     return html` | ||||
|       <ha-dialog | ||||
|         open | ||||
|         @closed=${this.closeDialog} | ||||
|         .heading=${createCloseHeading(this.hass, this._params.title)} | ||||
|       > | ||||
|         <!-- Dialog content --> | ||||
|         <ha-button | ||||
|           appearance="plain" | ||||
|           @click=${this.closeDialog} | ||||
|           slot="secondaryAction" | ||||
|         > | ||||
|           ${this.hass.localize("ui.common.cancel")} | ||||
|         </ha-button> | ||||
|         <ha-button @click=${this._submit} slot="primaryAction"> | ||||
|           ${this.hass.localize("ui.common.save")} | ||||
|         </ha-button> | ||||
|       </ha-dialog> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   static styles = [haStyleDialog, css``]; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Dialog Design Guidelines | ||||
|  | ||||
| - Max width: 560px (Alert/confirmation: 320px fixed width) | ||||
| - Close X-icon on top left (all screen sizes) | ||||
| - Submit button grouped with cancel at bottom right | ||||
| - Keep button labels short: "Save", "Delete", "Enable" | ||||
| - Destructive actions use red warning button | ||||
| - Always use a title (best practice) | ||||
| - Strive for minimalism | ||||
|  | ||||
| #### Creating a Lovelace Card | ||||
|  | ||||
| **Purpose**: Cards allow users to tell different stories about their house (based on gallery) | ||||
|  | ||||
| ```typescript | ||||
| @customElement("hui-my-card") | ||||
| export class HuiMyCard extends LitElement implements LovelaceCard { | ||||
|   @property({ attribute: false }) | ||||
|   hass!: HomeAssistant; | ||||
|  | ||||
|   @state() | ||||
|   private _config?: MyCardConfig; | ||||
|  | ||||
|   public setConfig(config: MyCardConfig): void { | ||||
|     if (!config.entity) { | ||||
|       throw new Error("Entity required"); | ||||
|     } | ||||
|     this._config = config; | ||||
|   } | ||||
|  | ||||
|   public getCardSize(): number { | ||||
|     return 3; // Height in grid units | ||||
|   } | ||||
|  | ||||
|   // Optional: Editor for card configuration | ||||
|   public static getConfigElement(): LovelaceCardEditor { | ||||
|     return document.createElement("hui-my-card-editor"); | ||||
|   } | ||||
|  | ||||
|   // Optional: Stub config for card picker | ||||
|   public static getStubConfig(): object { | ||||
|     return { entity: "" }; | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **Card Guidelines:** | ||||
|  | ||||
| - Cards are highly customizable for different households | ||||
| - Implement `LovelaceCard` interface with `setConfig()` and `getCardSize()` | ||||
| - Use proper error handling in `setConfig()` | ||||
| - Consider all possible states (loading, error, unavailable) | ||||
| - Support different entity types and states | ||||
| - Follow responsive design principles | ||||
| - Add configuration editor when needed | ||||
|  | ||||
| ### Internationalization | ||||
|  | ||||
| - **Use localize**: Always use the localization system | ||||
| - **Add translation keys**: Add keys to src/translations/en.json | ||||
| - **Support placeholders**: Use proper placeholder syntax | ||||
|  | ||||
| ```typescript | ||||
| this.hass.localize("ui.panel.config.updates.update_available", { | ||||
|   count: 5, | ||||
| }); | ||||
| ``` | ||||
|  | ||||
| ### Accessibility | ||||
|  | ||||
| - **ARIA labels**: Add appropriate ARIA labels | ||||
| - **Keyboard navigation**: Ensure all interactions work with keyboard | ||||
| - **Screen reader support**: Test with screen readers | ||||
| - **Color contrast**: Meet WCAG AA standards | ||||
|  | ||||
| ## Development Workflow | ||||
|  | ||||
| ### Setup and Commands | ||||
|  | ||||
| 1. **Setup**: `script/setup` - Install dependencies | ||||
| 2. **Develop**: `script/develop` - Development server | ||||
| 3. **Lint**: `yarn lint` - Run all linting before committing | ||||
| 4. **Test**: `yarn test` - Add and run tests | ||||
| 5. **Build**: `script/build_frontend` - Test production build | ||||
|  | ||||
| ### Common Pitfalls to Avoid | ||||
|  | ||||
| - Don't use `querySelector` - Use refs or component properties | ||||
| - Don't manipulate DOM directly - Let Lit handle rendering | ||||
| - Don't use global styles - Scope styles to components | ||||
| - Don't block the main thread - Use web workers for heavy computation | ||||
| - Don't ignore TypeScript errors - Fix all type issues | ||||
|  | ||||
| ### Security Best Practices | ||||
|  | ||||
| - Sanitize HTML - Never use `unsafeHTML` with user content | ||||
| - Validate inputs - Always validate user inputs | ||||
| - Use HTTPS - All external resources must use HTTPS | ||||
| - CSP compliance - Ensure code works with Content Security Policy | ||||
|  | ||||
| ### Text and Copy Guidelines | ||||
|  | ||||
| #### Terminology Standards | ||||
|  | ||||
| **Delete vs Remove** (Based on gallery/src/pages/Text/remove-delete-add-create.markdown) | ||||
|  | ||||
| - **Use "Remove"** for actions that can be restored or reapplied: | ||||
|   - Removing a user's permission | ||||
|   - Removing a user from a group | ||||
|   - Removing links between items | ||||
|   - Removing a widget from dashboard | ||||
|   - Removing an item from a cart | ||||
| - **Use "Delete"** for permanent, non-recoverable actions: | ||||
|   - Deleting a field | ||||
|   - Deleting a value in a field | ||||
|   - Deleting a task | ||||
|   - Deleting a group | ||||
|   - Deleting a permission | ||||
|   - Deleting a calendar event | ||||
|  | ||||
| **Create vs Add** (Create pairs with Delete, Add pairs with Remove) | ||||
|  | ||||
| - **Use "Add"** for already-existing items: | ||||
|   - Adding a permission to a user | ||||
|   - Adding a user to a group | ||||
|   - Adding links between items | ||||
|   - Adding a widget to dashboard | ||||
|   - Adding an item to a cart | ||||
| - **Use "Create"** for something made from scratch: | ||||
|   - Creating a new field | ||||
|   - Creating a new task | ||||
|   - Creating a new group | ||||
|   - Creating a new permission | ||||
|   - Creating a new calendar event | ||||
|  | ||||
| #### Writing Style (Consistent with Home Assistant Documentation) | ||||
|  | ||||
| - **Use American English**: Standard spelling and terminology | ||||
| - **Friendly, informational tone**: Be inspiring, personal, comforting, engaging | ||||
| - **Address users directly**: Use "you" and "your" | ||||
| - **Be inclusive**: Objective, non-discriminatory language | ||||
| - **Be concise**: Use clear, direct language | ||||
| - **Be consistent**: Follow established terminology patterns | ||||
| - **Use active voice**: "Delete the automation" not "The automation should be deleted" | ||||
| - **Avoid jargon**: Use terms familiar to home automation users | ||||
|  | ||||
| #### Language Standards | ||||
|  | ||||
| - **Always use "Home Assistant"** in full, never "HA" or "HASS" | ||||
| - **Avoid abbreviations**: Spell out terms when possible | ||||
| - **Use sentence case everywhere**: Titles, headings, buttons, labels, UI elements | ||||
|   - ✅ "Create new automation" | ||||
|   - ❌ "Create New Automation" | ||||
|   - ✅ "Device settings" | ||||
|   - ❌ "Device Settings" | ||||
| - **Oxford comma**: Use in lists (item 1, item 2, and item 3) | ||||
| - **Replace Latin terms**: Use "like" instead of "e.g.", "for example" instead of "i.e." | ||||
| - **Avoid CAPS for emphasis**: Use bold or italics instead | ||||
| - **Write for all skill levels**: Both technical and non-technical users | ||||
|  | ||||
| #### Key Terminology | ||||
|  | ||||
| - **"add-on"** (hyphenated, not "addon") | ||||
| - **"integration"** (preferred over "component") | ||||
| - **Technical terms**: Use lowercase (automation, entity, device, service) | ||||
|  | ||||
| #### Translation Considerations | ||||
|  | ||||
| - **Add translation keys**: All user-facing text must be translatable | ||||
| - **Use placeholders**: Support dynamic content in translations | ||||
| - **Keep context**: Provide enough context for translators | ||||
|  | ||||
| ```typescript | ||||
| // Good | ||||
| this.hass.localize("ui.panel.config.automation.delete_confirm", { | ||||
|   name: automation.alias, | ||||
| }); | ||||
|  | ||||
| // Bad - hardcoded text | ||||
| ("Are you sure you want to delete this automation?"); | ||||
| ``` | ||||
|  | ||||
| ### Common Review Issues (From PR Analysis) | ||||
|  | ||||
| #### User Experience and Accessibility | ||||
|  | ||||
| - **Form validation**: Always provide proper field labels and validation feedback | ||||
| - **Form accessibility**: Prevent password managers from incorrectly identifying fields | ||||
| - **Loading states**: Show clear progress indicators during async operations | ||||
| - **Error handling**: Display meaningful error messages when operations fail | ||||
| - **Mobile responsiveness**: Ensure components work well on small screens | ||||
| - **Hit targets**: Make clickable areas large enough for touch interaction | ||||
| - **Visual feedback**: Provide clear indication of interactive states | ||||
|  | ||||
| #### Dialog and Modal Patterns | ||||
|  | ||||
| - **Dialog width constraints**: Respect minimum and maximum width requirements | ||||
| - **Interview progress**: Show clear progress for multi-step operations | ||||
| - **State persistence**: Handle dialog state properly during background operations | ||||
| - **Cancel behavior**: Ensure cancel/close buttons work consistently | ||||
| - **Form prefilling**: Use smart defaults but allow user override | ||||
|  | ||||
| #### Component Design Patterns | ||||
|  | ||||
| - **Terminology consistency**: Use "Join"/"Apply" instead of "Group" when appropriate | ||||
| - **Visual hierarchy**: Ensure proper font sizes and spacing ratios | ||||
| - **Grid alignment**: Components should align to the design grid system | ||||
| - **Badge placement**: Position badges and indicators consistently | ||||
| - **Color theming**: Respect theme variables and design system colors | ||||
|  | ||||
| #### Code Quality Issues | ||||
|  | ||||
| - **Null checking**: Always check if entities exist before accessing properties | ||||
| - **TypeScript safety**: Handle potentially undefined array/object access | ||||
| - **Import organization**: Remove unused imports and use proper type imports | ||||
| - **Event handling**: Properly subscribe and unsubscribe from events | ||||
| - **Memory leaks**: Clean up subscriptions and event listeners | ||||
|  | ||||
| #### Configuration and Props | ||||
|  | ||||
| - **Optional parameters**: Make configuration fields optional when sensible | ||||
| - **Smart defaults**: Provide reasonable default values | ||||
| - **Future extensibility**: Design APIs that can be extended later | ||||
| - **Validation**: Validate configuration before applying changes | ||||
|  | ||||
| ## Review Guidelines | ||||
|  | ||||
| ### Core Requirements Checklist | ||||
|  | ||||
| - [ ] TypeScript strict mode passes (`yarn lint:types`) | ||||
| - [ ] No ESLint errors or warnings (`yarn lint:eslint`) | ||||
| - [ ] Prettier formatting applied (`yarn lint:prettier`) | ||||
| - [ ] Lit analyzer passes (`yarn lint:lit`) | ||||
| - [ ] Component follows Lit best practices | ||||
| - [ ] Proper error handling implemented | ||||
| - [ ] Loading states handled | ||||
| - [ ] Mobile responsive | ||||
| - [ ] Theme variables used | ||||
| - [ ] Translations added | ||||
| - [ ] Accessible to screen readers | ||||
| - [ ] Tests added (where applicable) | ||||
| - [ ] No console statements (use proper logging) | ||||
| - [ ] Unused imports removed | ||||
| - [ ] Proper naming conventions | ||||
|  | ||||
| ### Text and Copy Checklist | ||||
|  | ||||
| - [ ] Follows terminology guidelines (Delete vs Remove, Create vs Add) | ||||
| - [ ] Localization keys added for all user-facing text | ||||
| - [ ] Uses "Home Assistant" (never "HA" or "HASS") | ||||
| - [ ] Sentence case for ALL text (titles, headings, buttons, labels) | ||||
| - [ ] American English spelling | ||||
| - [ ] Friendly, informational tone | ||||
| - [ ] Avoids abbreviations and jargon | ||||
| - [ ] Correct terminology (add-on not addon, integration not component) | ||||
|  | ||||
| ### Component-Specific Checks | ||||
|  | ||||
| - [ ] Dialogs implement HassDialog interface | ||||
| - [ ] Dialog styling uses haStyleDialog | ||||
| - [ ] Dialog accessibility includes dialogInitialFocus | ||||
| - [ ] ha-alert used correctly for messages | ||||
| - [ ] ha-form uses proper schema structure | ||||
| - [ ] Components handle all states (loading, error, unavailable) | ||||
| - [ ] Entity existence checked before property access | ||||
| - [ ] Event subscriptions properly cleaned up | ||||
							
								
								
									
										12
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -21,12 +21,12 @@ jobs: | ||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|         with: | ||||
|           ref: dev | ||||
|  | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -42,7 +42,7 @@ jobs: | ||||
|       - name: Deploy to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=cast/dist --alias dev | ||||
|           npx -y netlify-cli deploy --dir=cast/dist --alias dev | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }} | ||||
| @@ -56,12 +56,12 @@ jobs: | ||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|         with: | ||||
|           ref: master | ||||
|  | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -77,7 +77,7 @@ jobs: | ||||
|       - name: Deploy to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=cast/dist --prod | ||||
|           npx -y netlify-cli deploy --dir=cast/dist --prod | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }} | ||||
|   | ||||
							
								
								
									
										22
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -24,9 +24,9 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -37,7 +37,7 @@ jobs: | ||||
|       - name: Build resources | ||||
|         run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages | ||||
|       - name: Setup lint cache | ||||
|         uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | ||||
|         uses: actions/cache@v4.2.3 | ||||
|         with: | ||||
|           path: | | ||||
|             node_modules/.cache/prettier | ||||
| @@ -58,9 +58,9 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -76,9 +76,9 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -89,7 +89,7 @@ jobs: | ||||
|         env: | ||||
|           IS_TEST: "true" | ||||
|       - name: Upload bundle stats | ||||
|         uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | ||||
|         uses: actions/upload-artifact@v4.6.2 | ||||
|         with: | ||||
|           name: frontend-bundle-stats | ||||
|           path: build/stats/*.json | ||||
| @@ -100,9 +100,9 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -113,7 +113,7 @@ jobs: | ||||
|         env: | ||||
|           IS_TEST: "true" | ||||
|       - name: Upload bundle stats | ||||
|         uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | ||||
|         uses: actions/upload-artifact@v4.6.2 | ||||
|         with: | ||||
|           name: supervisor-bundle-stats | ||||
|           path: build/stats/*.json | ||||
|   | ||||
							
								
								
									
										8
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,7 +23,7 @@ jobs: | ||||
|  | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|         with: | ||||
|           # We must fetch at least the immediate parents so that if this is | ||||
|           # a pull request then we can checkout the head. | ||||
| @@ -36,14 +36,14 @@ jobs: | ||||
|  | ||||
|       # Initializes the CodeQL tools for scanning. | ||||
|       - name: Initialize CodeQL | ||||
|         uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 | ||||
|         uses: github/codeql-action/init@v3 | ||||
|         with: | ||||
|           languages: ${{ matrix.language }} | ||||
|  | ||||
|       # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java). | ||||
|       # If this step fails, then you should remove it and run the build manually (see below) | ||||
|       - name: Autobuild | ||||
|         uses: github/codeql-action/autobuild@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 | ||||
|         uses: github/codeql-action/autobuild@v3 | ||||
|  | ||||
|       # ℹ️ Command-line programs to run using the OS shell. | ||||
|       # 📚 https://git.io/JvXDl | ||||
| @@ -57,4 +57,4 @@ jobs: | ||||
|       #   make release | ||||
|  | ||||
|       - name: Perform CodeQL Analysis | ||||
|         uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 | ||||
|         uses: github/codeql-action/analyze@v3 | ||||
|   | ||||
							
								
								
									
										12
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -22,12 +22,12 @@ jobs: | ||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|         with: | ||||
|           ref: dev | ||||
|  | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -43,7 +43,7 @@ jobs: | ||||
|       - name: Deploy to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=demo/dist --prod | ||||
|           npx -y netlify-cli deploy --dir=demo/dist --prod | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }} | ||||
| @@ -57,12 +57,12 @@ jobs: | ||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|         with: | ||||
|           ref: master | ||||
|  | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -78,7 +78,7 @@ jobs: | ||||
|       - name: Deploy to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=demo/dist --prod | ||||
|           npx -y netlify-cli deploy --dir=demo/dist --prod | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_SITE_ID }} | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -16,10 +16,10 @@ jobs: | ||||
|       url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|  | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -35,7 +35,7 @@ jobs: | ||||
|       - name: Deploy to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=gallery/dist --prod | ||||
|           npx -y netlify-cli deploy --dir=gallery/dist --prod | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|           NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }} | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -21,10 +21,10 @@ jobs: | ||||
|     if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview') | ||||
|     steps: | ||||
|       - name: Check out files from GitHub | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|  | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -40,7 +40,7 @@ jobs: | ||||
|       - name: Deploy preview to Netlify | ||||
|         id: deploy | ||||
|         run: | | ||||
|           npx -y netlify-cli@23.7.3 deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}" \ | ||||
|           npx -y netlify-cli deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}" \ | ||||
|             --json > deploy_output.json | ||||
|         env: | ||||
|           NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/labeler.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/labeler.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -10,6 +10,6 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Apply labels | ||||
|         uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 | ||||
|         uses: actions/labeler@v5.0.0 | ||||
|         with: | ||||
|           sync-labels: true | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,7 +9,7 @@ jobs: | ||||
|   lock: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 | ||||
|       - uses: dessant/lock-threads@v5.0.1 | ||||
|         with: | ||||
|           github-token: ${{ github.token }} | ||||
|           process-only: "issues, prs" | ||||
|   | ||||
							
								
								
									
										10
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -20,15 +20,15 @@ jobs: | ||||
|       contents: write | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|  | ||||
|       - name: Set up Python ${{ env.PYTHON_VERSION }} | ||||
|         uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 | ||||
|         uses: actions/setup-python@v5 | ||||
|         with: | ||||
|           python-version: ${{ env.PYTHON_VERSION }} | ||||
|  | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -57,14 +57,14 @@ jobs: | ||||
|         run: tar -czvf translations.tar.gz translations | ||||
|  | ||||
|       - name: Upload build artifacts | ||||
|         uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | ||||
|         uses: actions/upload-artifact@v4.6.2 | ||||
|         with: | ||||
|           name: wheels | ||||
|           path: dist/home_assistant_frontend*.whl | ||||
|           if-no-files-found: error | ||||
|  | ||||
|       - name: Upload translations | ||||
|         uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 | ||||
|         uses: actions/upload-artifact@v4.6.2 | ||||
|         with: | ||||
|           name: translations | ||||
|           path: translations.tar.gz | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/relative-ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/relative-ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -17,7 +17,7 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Send bundle stats and build information to RelativeCI | ||||
|         uses: relative-ci/agent-action@8504826a02078b05756e4c07e380023cc2c4274a # v3.1.0 | ||||
|         uses: relative-ci/agent-action@v3.0.0 | ||||
|         with: | ||||
|           key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }} | ||||
|           token: ${{ github.token }} | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/release-drafter.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release-drafter.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -18,6 +18,6 @@ jobs: | ||||
|       pull-requests: read | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0 | ||||
|       - uses: release-drafter/release-drafter@v6.1.0 | ||||
|         env: | ||||
|           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|   | ||||
							
								
								
									
										23
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -23,10 +23,10 @@ jobs: | ||||
|       contents: write # Required to upload release assets | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|  | ||||
|       - name: Set up Python ${{ env.PYTHON_VERSION }} | ||||
|         uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 | ||||
|         uses: actions/setup-python@v5 | ||||
|         with: | ||||
|           python-version: ${{ env.PYTHON_VERSION }} | ||||
|  | ||||
| @@ -34,7 +34,7 @@ jobs: | ||||
|         uses: home-assistant/actions/helpers/verify-version@master | ||||
|  | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -55,7 +55,7 @@ jobs: | ||||
|           script/release | ||||
|  | ||||
|       - name: Upload release assets | ||||
|         uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 | ||||
|         uses: softprops/action-gh-release@v2.2.2 | ||||
|         with: | ||||
|           files: | | ||||
|             dist/*.whl | ||||
| @@ -73,9 +73,8 @@ jobs: | ||||
|           version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' ) | ||||
|           echo "home-assistant-frontend==$version" > ./requirements.txt | ||||
|  | ||||
|       # home-assistant/wheels doesn't support SHA pinning | ||||
|       - name: Build wheels | ||||
|         uses: home-assistant/wheels@2025.10.0 | ||||
|         uses: home-assistant/wheels@2025.03.0 | ||||
|         with: | ||||
|           abi: cp313 | ||||
|           tag: musllinux_1_2 | ||||
| @@ -91,9 +90,9 @@ jobs: | ||||
|       contents: write # Required to upload release assets | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -108,7 +107,7 @@ jobs: | ||||
|       - name: Tar folder | ||||
|         run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist . | ||||
|       - name: Upload release asset | ||||
|         uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 | ||||
|         uses: softprops/action-gh-release@v2.2.2 | ||||
|         with: | ||||
|           files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz | ||||
|  | ||||
| @@ -120,9 +119,9 @@ jobs: | ||||
|       contents: write # Required to upload release assets | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|       - name: Setup Node | ||||
|         uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | ||||
|         uses: actions/setup-node@v4.4.0 | ||||
|         with: | ||||
|           node-version-file: ".nvmrc" | ||||
|           cache: yarn | ||||
| @@ -137,6 +136,6 @@ jobs: | ||||
|       - name: Tar folder | ||||
|         run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build . | ||||
|       - name: Upload release asset | ||||
|         uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 | ||||
|         uses: softprops/action-gh-release@v2.2.2 | ||||
|         with: | ||||
|           files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz | ||||
|   | ||||
							
								
								
									
										58
									
								
								.github/workflows/restrict-task-creation.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										58
									
								
								.github/workflows/restrict-task-creation.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,58 +0,0 @@ | ||||
| name: Restrict task creation | ||||
|  | ||||
| # yamllint disable-line rule:truthy | ||||
| on: | ||||
|   issues: | ||||
|     types: [opened] | ||||
|  | ||||
| jobs: | ||||
|   check-authorization: | ||||
|     runs-on: ubuntu-latest | ||||
|     # Only run if this is a Task issue type (from the issue form) | ||||
|     if: github.event.issue.type.name == 'Task' | ||||
|     steps: | ||||
|       - name: Check if user is authorized | ||||
|         uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 | ||||
|         with: | ||||
|           script: | | ||||
|             const issueAuthor = context.payload.issue.user.login; | ||||
|  | ||||
|             // Check if user is an organization member | ||||
|             try { | ||||
|               await github.rest.orgs.checkMembershipForUser({ | ||||
|                 org: 'home-assistant', | ||||
|                 username: issueAuthor | ||||
|               }); | ||||
|               console.log(`✅ ${issueAuthor} is an organization member`); | ||||
|               return; // Authorized | ||||
|             } catch (error) { | ||||
|               console.log(`❌ ${issueAuthor} is not authorized to create Task issues`); | ||||
|             } | ||||
|  | ||||
|             // Close the issue with a comment | ||||
|             await github.rest.issues.createComment({ | ||||
|               owner: context.repo.owner, | ||||
|               repo: context.repo.repo, | ||||
|               issue_number: context.issue.number, | ||||
|               body: `Hi @${issueAuthor}, thank you for your contribution!\n\n` + | ||||
|                     `Task issues are restricted to Open Home Foundation staff and authorized contributors.\n\n` + | ||||
|                     `If you would like to:\n` + | ||||
|                     `- Report a bug: Please use the [bug report form](https://github.com/home-assistant/frontend/issues/new?template=bug_report.yml)\n` + | ||||
|                     `- Request a feature: Please submit to [Feature Requests](https://github.com/orgs/home-assistant/discussions)\n\n` + | ||||
|                     `If you believe you should have access to create Task issues, please contact the maintainers.` | ||||
|             }); | ||||
|  | ||||
|             await github.rest.issues.update({ | ||||
|               owner: context.repo.owner, | ||||
|               repo: context.repo.repo, | ||||
|               issue_number: context.issue.number, | ||||
|               state: 'closed' | ||||
|             }); | ||||
|  | ||||
|             // Add a label to indicate this was auto-closed | ||||
|             await github.rest.issues.addLabels({ | ||||
|               owner: context.repo.owner, | ||||
|               repo: context.repo.repo, | ||||
|               issue_number: context.issue.number, | ||||
|               labels: ['auto-closed'] | ||||
|             }); | ||||
							
								
								
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,7 +10,7 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: 90 days stale policy | ||||
|         uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 | ||||
|         uses: actions/stale@v9.1.0 | ||||
|         with: | ||||
|           repo-token: ${{ secrets.GITHUB_TOKEN }} | ||||
|           days-before-stale: 90 | ||||
|   | ||||
							
								
								
									
										3
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,6 @@ | ||||
| name: Translations | ||||
|  | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|   push: | ||||
|     branches: | ||||
|       - dev | ||||
| @@ -14,7 +13,7 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||||
|         uses: actions/checkout@v4.2.2 | ||||
|  | ||||
|       - name: Upload Translations | ||||
|         run: | | ||||
|   | ||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -53,7 +53,3 @@ src/cast/dev_const.ts | ||||
|  | ||||
| # test coverage | ||||
| test/coverage/ | ||||
|  | ||||
| # AI tooling | ||||
| .claude | ||||
|  | ||||
|   | ||||
							
								
								
									
										942
									
								
								.yarn/releases/yarn-4.10.3.cjs
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										942
									
								
								.yarn/releases/yarn-4.10.3.cjs
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										948
									
								
								.yarn/releases/yarn-4.9.1.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										948
									
								
								.yarn/releases/yarn-4.9.1.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -6,4 +6,4 @@ enableGlobalCache: false | ||||
|  | ||||
| nodeLinker: node-modules | ||||
|  | ||||
| yarnPath: .yarn/releases/yarn-4.10.3.cjs | ||||
| yarnPath: .yarn/releases/yarn-4.9.1.cjs | ||||
|   | ||||
| @@ -1,8 +0,0 @@ | ||||
| # People marked here will be automatically requested for a review | ||||
| # when the code that they own is touched. | ||||
| # https://github.com/blog/2392-introducing-code-owners | ||||
| # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners | ||||
|  | ||||
| # Part of the frontend that mobile developper should review | ||||
| src/external_app/ @bgoncal @TimoPtr | ||||
| test/external_app/ @bgoncal @TimoPtr | ||||
| @@ -183,6 +183,7 @@ module.exports.babelOptions = ({ | ||||
|       include: /\/node_modules\//, | ||||
|       exclude: [ | ||||
|         "element-internals-polyfill", | ||||
|         "@shoelace-style", | ||||
|         "@?lit(?:-labs|-element|-html)?", | ||||
|       ].map((p) => new RegExp(`/node_modules/${p}/`)), | ||||
|     }, | ||||
|   | ||||
| @@ -14,5 +14,5 @@ | ||||
|   "name": "Home Assistant Cast", | ||||
|   "short_name": "HA Cast", | ||||
|   "start_url": "/?homescreen=1", | ||||
|   "theme_color": "#009ac7" | ||||
|   "theme_color": "#03A9F4" | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import "@material/mwc-button/mwc-button"; | ||||
|  | ||||
| import type { ActionDetail } from "@material/mwc-list/mwc-list"; | ||||
| import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js"; | ||||
| import type { Auth, Connection } from "home-assistant-js-websocket"; | ||||
| @@ -18,7 +20,6 @@ import { atLeastVersion } from "../../../../src/common/config/version"; | ||||
| import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute"; | ||||
| import "../../../../src/components/ha-icon"; | ||||
| import "../../../../src/components/ha-list"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-list-item"; | ||||
| import "../../../../src/components/ha-svg-icon"; | ||||
| import { | ||||
| @@ -62,20 +63,12 @@ class HcCast extends LitElement { | ||||
|               <p class="question action-item"> | ||||
|                 Stay logged in? | ||||
|                 <span> | ||||
|                   <ha-button | ||||
|                     appearance="plain" | ||||
|                     size="small" | ||||
|                     @click=${this._handleSaveTokens} | ||||
|                   > | ||||
|                   <mwc-button @click=${this._handleSaveTokens}> | ||||
|                     YES | ||||
|                   </ha-button> | ||||
|                   <ha-button | ||||
|                     appearance="plain" | ||||
|                     size="small" | ||||
|                     @click=${this._handleSkipSaveTokens} | ||||
|                   > | ||||
|                   </mwc-button> | ||||
|                   <mwc-button @click=${this._handleSkipSaveTokens}> | ||||
|                     NO | ||||
|                   </ha-button> | ||||
|                   </mwc-button> | ||||
|                 </span> | ||||
|               </p> | ||||
|             ` | ||||
| @@ -85,10 +78,10 @@ class HcCast extends LitElement { | ||||
|           : !this.castManager.status | ||||
|             ? html` | ||||
|                 <p class="center-item"> | ||||
|                   <ha-button @click=${this._handleLaunch}> | ||||
|                     <ha-svg-icon slot="start" .path=${mdiCast}></ha-svg-icon> | ||||
|                   <mwc-button raised @click=${this._handleLaunch}> | ||||
|                     <ha-svg-icon .path=${mdiCast}></ha-svg-icon> | ||||
|                     Start Casting | ||||
|                   </ha-button> | ||||
|                   </mwc-button> | ||||
|                 </p> | ||||
|               ` | ||||
|             : html` | ||||
| @@ -128,22 +121,14 @@ class HcCast extends LitElement { | ||||
|         <div class="card-actions"> | ||||
|           ${this.castManager.status | ||||
|             ? html` | ||||
|                 <ha-button appearance="plain" @click=${this._handleLaunch}> | ||||
|                   <ha-svg-icon | ||||
|                     slot="start" | ||||
|                     .path=${mdiCastConnected} | ||||
|                   ></ha-svg-icon> | ||||
|                 <mwc-button @click=${this._handleLaunch}> | ||||
|                   <ha-svg-icon .path=${mdiCastConnected}></ha-svg-icon> | ||||
|                   Manage | ||||
|                 </ha-button> | ||||
|                 </mwc-button> | ||||
|               ` | ||||
|             : ""} | ||||
|           <div class="spacer"></div> | ||||
|           <ha-button | ||||
|             variant="danger" | ||||
|             appearance="plain" | ||||
|             @click=${this._handleLogout} | ||||
|             >Log out</ha-button | ||||
|           > | ||||
|           <mwc-button @click=${this._handleLogout}>Log out</mwc-button> | ||||
|         </div> | ||||
|       </hc-layout> | ||||
|     `; | ||||
| @@ -242,7 +227,7 @@ class HcCast extends LitElement { | ||||
|     } | ||||
|  | ||||
|     .question:before { | ||||
|       border-radius: var(--ha-border-radius-sm); | ||||
|       border-radius: 4px; | ||||
|       position: absolute; | ||||
|       top: 0; | ||||
|       right: 0; | ||||
| @@ -260,6 +245,13 @@ class HcCast extends LitElement { | ||||
|       color: var(--secondary-text-color); | ||||
|     } | ||||
|  | ||||
|     mwc-button ha-svg-icon { | ||||
|       margin-right: 8px; | ||||
|       margin-inline-end: 8px; | ||||
|       margin-inline-start: initial; | ||||
|       height: 18px; | ||||
|     } | ||||
|  | ||||
|     ha-list-item ha-icon, | ||||
|     ha-list-item ha-svg-icon { | ||||
|       padding: 12px; | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import "@material/mwc-button"; | ||||
| import { mdiCastConnected, mdiCast } from "@mdi/js"; | ||||
| import type { | ||||
|   Auth, | ||||
| @@ -27,7 +28,6 @@ import "../../../../src/layouts/hass-loading-screen"; | ||||
| import { registerServiceWorker } from "../../../../src/util/register-service-worker"; | ||||
| import "./hc-layout"; | ||||
| import "../../../../src/components/ha-textfield"; | ||||
| import "../../../../src/components/ha-button"; | ||||
|  | ||||
| const seeFAQ = (qid) => html` | ||||
|   See <a href="./faq.html${qid ? `#${qid}` : ""}">the FAQ</a> for more | ||||
| @@ -83,14 +83,11 @@ export class HcConnect extends LitElement { | ||||
|             Unable to connect to ${tokens!.hassUrl}. | ||||
|           </div> | ||||
|           <div class="card-actions"> | ||||
|             <ha-button appearance="plain" href="/">Retry</ha-button> | ||||
|             <a href="/"> | ||||
|               <mwc-button> Retry </mwc-button> | ||||
|             </a> | ||||
|             <div class="spacer"></div> | ||||
|             <ha-button | ||||
|               appearance="plain" | ||||
|               variant="danger" | ||||
|               @click=${this._handleLogout} | ||||
|               >Log out</ha-button | ||||
|             > | ||||
|             <mwc-button @click=${this._handleLogout}>Log out</mwc-button> | ||||
|           </div> | ||||
|         </hc-layout> | ||||
|       `; | ||||
| @@ -131,19 +128,16 @@ export class HcConnect extends LitElement { | ||||
|             ${this.error ? html` <p class="error">${this.error}</p> ` : ""} | ||||
|           </div> | ||||
|           <div class="card-actions"> | ||||
|             <ha-button appearance="plain" @click=${this._handleDemo}> | ||||
|             <mwc-button @click=${this._handleDemo}> | ||||
|               Show Demo | ||||
|               <ha-svg-icon | ||||
|                 slot="end" | ||||
|                 .path=${this.castManager.castState === "CONNECTED" | ||||
|                   ? mdiCastConnected | ||||
|                   : mdiCast} | ||||
|               ></ha-svg-icon> | ||||
|             </ha-button> | ||||
|             </mwc-button> | ||||
|             <div class="spacer"></div> | ||||
|             <ha-button appearance="plain" @click=${this._handleConnect} | ||||
|               >Authorize</ha-button | ||||
|             > | ||||
|             <mwc-button @click=${this._handleConnect}>Authorize</mwc-button> | ||||
|           </div> | ||||
|         </hc-layout> | ||||
|       `; | ||||
| @@ -315,6 +309,10 @@ export class HcConnect extends LitElement { | ||||
|       color: darkred; | ||||
|     } | ||||
|  | ||||
|     mwc-button ha-svg-icon { | ||||
|       margin-left: 8px; | ||||
|     } | ||||
|  | ||||
|     .spacer { | ||||
|       flex: 1; | ||||
|     } | ||||
|   | ||||
| @@ -95,8 +95,7 @@ class HcLayout extends LitElement { | ||||
|     } | ||||
|  | ||||
|     .hero { | ||||
|       border-radius: var(--ha-border-radius-sm) var(--ha-border-radius-sm) | ||||
|         var(--ha-border-radius-square) var(--ha-border-radius-square); | ||||
|       border-radius: 4px 4px 0 0; | ||||
|     } | ||||
|     .subtitle { | ||||
|       font-size: var(--ha-font-size-m); | ||||
|   | ||||
| @@ -75,7 +75,7 @@ export const castDemoEntities: () => Entity[] = () => | ||||
|         longitude: 4.8903147, | ||||
|         radius: 100, | ||||
|         friendly_name: "Home", | ||||
|         icon: "mdi:home", | ||||
|         icon: "hass:home", | ||||
|       }, | ||||
|     }, | ||||
|     "input_number.harmonyvolume": { | ||||
| @@ -88,7 +88,7 @@ export const castDemoEntities: () => Entity[] = () => | ||||
|         step: 1, | ||||
|         mode: "slider", | ||||
|         friendly_name: "Volume", | ||||
|         icon: "mdi:volume-high", | ||||
|         icon: "hass:volume-high", | ||||
|       }, | ||||
|     }, | ||||
|     "climate.upstairs": { | ||||
|   | ||||
| @@ -56,7 +56,7 @@ export const castDemoLovelace: () => LovelaceConfig = () => { | ||||
|                 type: "weblink", | ||||
|                 url: "/lovelace/climate", | ||||
|                 name: "Climate controls", | ||||
|                 icon: "mdi:arrow-right", | ||||
|                 icon: "hass:arrow-right", | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
| @@ -76,7 +76,7 @@ export const castDemoLovelace: () => LovelaceConfig = () => { | ||||
|                 type: "weblink", | ||||
|                 url: "/lovelace/overview", | ||||
|                 name: "Back", | ||||
|                 icon: "mdi:arrow-left", | ||||
|                 icon: "hass:arrow-left", | ||||
|               }, | ||||
|             ], | ||||
|           }, | ||||
|   | ||||
| @@ -75,5 +75,5 @@ | ||||
|   "name": "Home Assistant Demo", | ||||
|   "short_name": "HA Demo", | ||||
|   "start_url": "/?homescreen=1", | ||||
|   "theme_color": "#009ac7" | ||||
|   "theme_color": "#03A9F4" | ||||
| } | ||||
|   | ||||
| @@ -143,7 +143,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => | ||||
|       state: "on", | ||||
|       attributes: { | ||||
|         friendly_name: "Home Automation", | ||||
|         icon: "mdi:home-automation", | ||||
|         icon: "hass:home-automation", | ||||
|       }, | ||||
|     }, | ||||
|     "input_boolean.tvtime": { | ||||
|   | ||||
| @@ -4,7 +4,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({ | ||||
|   title: "Home Assistant", | ||||
|   views: [ | ||||
|     { | ||||
|       icon: "mdi:home-assistant", | ||||
|       icon: "hass:home-assistant", | ||||
|       id: "home", | ||||
|       title: "Home", | ||||
|       cards: [ | ||||
|   | ||||
| @@ -1236,7 +1236,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ | ||||
|         }, | ||||
|       ], | ||||
|       path: "security", | ||||
|       icon: "mdi:shield-home", | ||||
|       icon: "hass:shield-home", | ||||
|       name: "Security", | ||||
|       background: | ||||
|         'center / cover no-repeat url("/assets/jimpower/background-15.jpg") fixed', | ||||
|   | ||||
| @@ -89,14 +89,11 @@ export class HADemoCard extends LitElement implements LovelaceCard { | ||||
|           )} | ||||
|         </div> | ||||
|         <div class="actions small-hidden"> | ||||
|           <ha-button | ||||
|             appearance="plain" | ||||
|             size="small" | ||||
|             href="https://www.home-assistant.io" | ||||
|             target="_blank" | ||||
|           > | ||||
|             ${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")} | ||||
|           </ha-button> | ||||
|           <a href="https://www.home-assistant.io" target="_blank"> | ||||
|             <ha-button> | ||||
|               ${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")} | ||||
|             </ha-button> | ||||
|           </a> | ||||
|         </div> | ||||
|       </ha-card> | ||||
|     `; | ||||
|   | ||||
| @@ -68,7 +68,7 @@ | ||||
|       } | ||||
|       #ha-launch-screen .ha-launch-screen-spacer-top { | ||||
|         flex: 1; | ||||
|         margin-top: calc( 2 * max(var(--safe-area-inset-top, 0px), 48px) + 46px ); | ||||
|         margin-top: calc( 2 * max(var(--safe-area-inset-bottom), 48px) + 46px ); | ||||
|         padding-top: 48px; | ||||
|       } | ||||
|       #ha-launch-screen .ha-launch-screen-spacer-bottom { | ||||
| @@ -76,7 +76,7 @@ | ||||
|         padding-top: 48px; | ||||
|       } | ||||
|       .ohf-logo { | ||||
|         margin: max(var(--safe-area-inset-bottom, 0px), 48px) 0; | ||||
|         margin: max(var(--safe-area-inset-bottom), 48px) 0; | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         align-items: center; | ||||
|   | ||||
| @@ -11,7 +11,6 @@ import tseslint from "typescript-eslint"; | ||||
| import eslintConfigPrettier from "eslint-config-prettier"; | ||||
| import { configs as litConfigs } from "eslint-plugin-lit"; | ||||
| import { configs as wcConfigs } from "eslint-plugin-wc"; | ||||
| import { configs as a11yConfigs } from "eslint-plugin-lit-a11y"; | ||||
|  | ||||
| const _filename = fileURLToPath(import.meta.url); | ||||
| const _dirname = path.dirname(_filename); | ||||
| @@ -22,14 +21,13 @@ const compat = new FlatCompat({ | ||||
| }); | ||||
|  | ||||
| export default tseslint.config( | ||||
|   ...compat.extends("airbnb-base"), | ||||
|   ...compat.extends("airbnb-base", "plugin:lit-a11y/recommended"), | ||||
|   eslintConfigPrettier, | ||||
|   litConfigs["flat/all"], | ||||
|   tseslint.configs.recommended, | ||||
|   tseslint.configs.strict, | ||||
|   tseslint.configs.stylistic, | ||||
|   wcConfigs["flat/recommended"], | ||||
|   a11yConfigs.recommended, | ||||
|   { | ||||
|     plugins: { | ||||
|       "unused-imports": unusedImports, | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| import "@material/mwc-button/mwc-button"; | ||||
| import type { Button } from "@material/mwc-button"; | ||||
| import type { TemplateResult } from "lit"; | ||||
| import { html, LitElement, css, nothing } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
| import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element"; | ||||
| import { fireEvent } from "../../../src/common/dom/fire_event"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import "../../../src/components/ha-button"; | ||||
| import type { HaButton } from "../../../src/components/ha-button"; | ||||
|  | ||||
| @customElement("demo-black-white-row") | ||||
| class DemoBlackWhiteRow extends LitElement { | ||||
| @@ -25,9 +25,12 @@ class DemoBlackWhiteRow extends LitElement { | ||||
|               <slot name="light"></slot> | ||||
|             </div> | ||||
|             <div class="card-actions"> | ||||
|               <ha-button .disabled=${this.disabled} @click=${this.handleSubmit}> | ||||
|               <mwc-button | ||||
|                 .disabled=${this.disabled} | ||||
|                 @click=${this.handleSubmit} | ||||
|               > | ||||
|                 Submit | ||||
|               </ha-button> | ||||
|               </mwc-button> | ||||
|             </div> | ||||
|           </ha-card> | ||||
|         </div> | ||||
| @@ -37,9 +40,12 @@ class DemoBlackWhiteRow extends LitElement { | ||||
|               <slot name="dark"></slot> | ||||
|             </div> | ||||
|             <div class="card-actions"> | ||||
|               <ha-button .disabled=${this.disabled} @click=${this.handleSubmit}> | ||||
|               <mwc-button | ||||
|                 .disabled=${this.disabled} | ||||
|                 @click=${this.handleSubmit} | ||||
|               > | ||||
|                 Submit | ||||
|               </ha-button> | ||||
|               </mwc-button> | ||||
|             </div> | ||||
|           </ha-card> | ||||
|           ${this.value | ||||
| @@ -68,7 +74,7 @@ class DemoBlackWhiteRow extends LitElement { | ||||
|   } | ||||
|  | ||||
|   handleSubmit(ev) { | ||||
|     const content = (ev.target as HaButton).closest(".content")!; | ||||
|     const content = (ev.target as Button).closest(".content")!; | ||||
|     fireEvent(this, "submitted" as any, { | ||||
|       slot: content.classList.contains("light") ? "light" : "dark", | ||||
|     }); | ||||
|   | ||||
| @@ -1,11 +1,10 @@ | ||||
| import { LitElement, css, html, nothing } from "lit"; | ||||
| import { LitElement, css, html } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import "../../../src/dialogs/more-info/more-info-content"; | ||||
| import "../../../src/state-summary/state-card-content"; | ||||
| import "../ha-demo-options"; | ||||
| import type { HomeAssistant } from "../../../src/types"; | ||||
| import { computeShowNewMoreInfo } from "../../../src/dialogs/more-info/const"; | ||||
|  | ||||
| @customElement("demo-more-info") | ||||
| class DemoMoreInfo extends LitElement { | ||||
| @@ -22,13 +21,11 @@ class DemoMoreInfo extends LitElement { | ||||
|       <div class="root"> | ||||
|         <div id="card"> | ||||
|           <ha-card> | ||||
|             ${!computeShowNewMoreInfo(state) | ||||
|               ? html`<state-card-content | ||||
|                   .stateObj=${state} | ||||
|                   .hass=${this.hass} | ||||
|                   in-dialog | ||||
|                 ></state-card-content>` | ||||
|               : nothing} | ||||
|             <state-card-content | ||||
|               .stateObj=${state} | ||||
|               .hass=${this.hass} | ||||
|               in-dialog | ||||
|             ></state-card-content> | ||||
|  | ||||
|             <more-info-content | ||||
|               .hass=${this.hass} | ||||
|   | ||||
| @@ -1106,7 +1106,7 @@ export default { | ||||
|       friendly_name: "Philips Hue", | ||||
|       entity_picture: null, | ||||
|       description: | ||||
|         "Press the button on the bridge to register Philips Hue with Home Assistant.", | ||||
|         "Press the button on the bridge to register Philips Hue with Home Assistant.\n\n", | ||||
|       submit_caption: "I have pressed the button", | ||||
|     }, | ||||
|     last_changed: "2018-07-19T10:44:46.515160+00:00", | ||||
|   | ||||
| @@ -17,10 +17,6 @@ export const createMediaPlayerEntities = () => [ | ||||
|       new Date().getTime() - 23000 | ||||
|     ).toISOString(), | ||||
|     volume_level: 0.5, | ||||
|     source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"], | ||||
|     source: "AirPlay", | ||||
|     sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"], | ||||
|     sound_mode: "Music", | ||||
|   }), | ||||
|   getEntity("media_player", "music_playing", "playing", { | ||||
|     friendly_name: "Playing The Music", | ||||
| @@ -28,8 +24,8 @@ export const createMediaPlayerEntities = () => [ | ||||
|     media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)", | ||||
|     media_artist: "Technohead", | ||||
|     // Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media + | ||||
|     // Select Source + Stop + Clear + Play + Shuffle Set + Browse Media + Grouping | ||||
|     supported_features: 784959, | ||||
|     // Select Source + Stop + Clear + Play + Shuffle Set + Browse Media | ||||
|     supported_features: 195135, | ||||
|     entity_picture: "/images/album_cover.jpg", | ||||
|     media_duration: 300, | ||||
|     media_position: 0, | ||||
| @@ -38,9 +34,6 @@ export const createMediaPlayerEntities = () => [ | ||||
|       new Date().getTime() - 23000 | ||||
|     ).toISOString(), | ||||
|     volume_level: 0.5, | ||||
|     sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"], | ||||
|     sound_mode: "Music", | ||||
|     group_members: ["media_player.playing", "media_player.stream_playing"], | ||||
|   }), | ||||
|   getEntity("media_player", "stream_playing", "playing", { | ||||
|     friendly_name: "Playing the Stream", | ||||
| @@ -156,18 +149,15 @@ export const createMediaPlayerEntities = () => [ | ||||
|   }), | ||||
|   getEntity("media_player", "receiver_on", "on", { | ||||
|     source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"], | ||||
|     sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"], | ||||
|     volume_level: 0.63, | ||||
|     is_volume_muted: false, | ||||
|     source: "TV", | ||||
|     sound_mode: "Movie", | ||||
|     friendly_name: "Receiver (selectable sources)", | ||||
|     // Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode | ||||
|     supported_features: 84364, | ||||
|   }), | ||||
|   getEntity("media_player", "receiver_off", "off", { | ||||
|     source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"], | ||||
|     sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"], | ||||
|     friendly_name: "Receiver (selectable sources)", | ||||
|     // Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode | ||||
|     supported_features: 84364, | ||||
|   | ||||
| @@ -208,7 +208,7 @@ class HaGallery extends LitElement { | ||||
|       } | ||||
|  | ||||
|       .sidebar a[active]::before { | ||||
|         border-radius: var(--ha-border-radius-lg); | ||||
|         border-radius: 12px; | ||||
|         position: absolute; | ||||
|         top: 0; | ||||
|         right: 2px; | ||||
| @@ -241,7 +241,7 @@ class HaGallery extends LitElement { | ||||
|         text-align: center; | ||||
|         margin: 16px; | ||||
|         padding: 16px; | ||||
|         border-radius: var(--ha-border-radius-lg); | ||||
|         border-radius: 12px; | ||||
|         background-color: var(--primary-background-color); | ||||
|       } | ||||
|  | ||||
|   | ||||
| @@ -18,6 +18,7 @@ import { HaDeviceAction } from "../../../../src/panels/config/automation/action/ | ||||
| import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event"; | ||||
| import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if"; | ||||
| import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel"; | ||||
| import { HaPlayMediaAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-play_media"; | ||||
| import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat"; | ||||
| import { HaSequenceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-sequence"; | ||||
| import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service"; | ||||
| @@ -31,6 +32,7 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [ | ||||
|   { name: "Service", actions: [HaServiceAction.defaultConfig] }, | ||||
|   { name: "Condition", actions: [HaConditionAction.defaultConfig] }, | ||||
|   { name: "Delay", actions: [HaDelayAction.defaultConfig] }, | ||||
|   { name: "Play media", actions: [HaPlayMediaAction.defaultConfig] }, | ||||
|   { name: "Wait", actions: [HaWaitAction.defaultConfig] }, | ||||
|   { name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] }, | ||||
|   { name: "Repeat", actions: [HaRepeatAction.defaultConfig] }, | ||||
|   | ||||
| @@ -147,13 +147,13 @@ The `title ` option should not be used without a description. | ||||
|  | ||||
| <ha-alert alert-type="success"> | ||||
|   This is a success alert — check it out! | ||||
|   <ha-button slot="action">Undo</ha-button> | ||||
|   <mwc-button slot="action" label="Undo"></mwc-button> | ||||
| </ha-alert> | ||||
|  | ||||
| ```html | ||||
| <ha-alert alert-type="success"> | ||||
|   This is a success alert — check it out! | ||||
|   <ha-button slot="action">Undo</ha-button> | ||||
|   <mwc-button slot="action" label="Undo"></mwc-button> | ||||
| </ha-alert> | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import "@material/mwc-button/mwc-button"; | ||||
| import type { TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement } from "lit/decorators"; | ||||
| import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element"; | ||||
| import "../../../../src/components/ha-alert"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-logo-svg"; | ||||
|  | ||||
| const alerts: { | ||||
| @@ -78,13 +78,13 @@ const alerts: { | ||||
|     title: "Error with action", | ||||
|     description: "This is a test error alert with action", | ||||
|     type: "error", | ||||
|     actionSlot: html`<ha-button size="small" slot="action">restart</ha-button>`, | ||||
|     actionSlot: html`<mwc-button slot="action" label="restart"></mwc-button>`, | ||||
|   }, | ||||
|   { | ||||
|     title: "Unsaved data", | ||||
|     description: "You have unsaved data", | ||||
|     type: "warning", | ||||
|     actionSlot: html`<ha-button size="small" slot="action">save</ha-button>`, | ||||
|     actionSlot: html`<mwc-button slot="action" label="save"></mwc-button>`, | ||||
|   }, | ||||
|   { | ||||
|     title: "Slotted icon", | ||||
| @@ -108,7 +108,7 @@ const alerts: { | ||||
|     title: "Slotted action", | ||||
|     description: "Alert with slotted action", | ||||
|     type: "info", | ||||
|     actionSlot: html`<ha-button slot="action">action</ha-button>`, | ||||
|     actionSlot: html`<mwc-button slot="action" label="action"></mwc-button>`, | ||||
|   }, | ||||
|   { | ||||
|     description: "Dismissable information (RTL)", | ||||
| @@ -120,7 +120,7 @@ const alerts: { | ||||
|     title: "Error with action", | ||||
|     description: "This is a test error alert with action (RTL)", | ||||
|     type: "error", | ||||
|     actionSlot: html`<ha-button slot="action">restart</ha-button>`, | ||||
|     actionSlot: html`<mwc-button slot="action" label="restart"></mwc-button>`, | ||||
|     rtl: true, | ||||
|   }, | ||||
|   { | ||||
| @@ -211,7 +211,7 @@ export class DemoHaAlert extends LitElement { | ||||
|       max-height: 24px; | ||||
|       width: 24px; | ||||
|     } | ||||
|     ha-button { | ||||
|     mwc-button { | ||||
|       --mdc-theme-primary: var(--primary-text-color); | ||||
|     } | ||||
|   `; | ||||
|   | ||||
| @@ -117,7 +117,7 @@ export class DemoHaBadge extends LitElement { | ||||
|     } | ||||
|     .card-content { | ||||
|       display: flex; | ||||
|       gap: var(--ha-space-6); | ||||
|       gap: 24px; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -1,67 +0,0 @@ | ||||
| --- | ||||
| title: Button | ||||
| --- | ||||
|  | ||||
| <style> | ||||
|   .wrapper { | ||||
|     display: flex; | ||||
|     gap: 24px; | ||||
|   } | ||||
| </style> | ||||
|  | ||||
| # Button `<ha-button>` | ||||
|  | ||||
| ## Implementation | ||||
|  | ||||
| ### Example Usage | ||||
|  | ||||
| <div class="wrapper"> | ||||
|   <ha-button> | ||||
|     simple button | ||||
|   </ha-button> | ||||
|   <ha-button appearance="plain"> | ||||
|     plain button | ||||
|   </ha-button> | ||||
|   <ha-button appearance="filled"> | ||||
|     filled button | ||||
|   </ha-button> | ||||
|  | ||||
|   <ha-button size="small"> | ||||
|     small | ||||
|   </ha-button> | ||||
| </div> | ||||
|  | ||||
| ```html | ||||
| <ha-button> simple button </ha-button> | ||||
|  | ||||
| <ha-button size="small"> small </ha-button> | ||||
| ``` | ||||
|  | ||||
| ### API | ||||
|  | ||||
| This component is based on the webawesome button component. | ||||
| Check the [webawesome documentation](https://webawesome.com/docs/components/button/) for more details. | ||||
|  | ||||
| **Slots** | ||||
|  | ||||
| - default slot: Label of the button | ||||
|   ` - no default | ||||
| - `start`: The prefix container (usually for icons). | ||||
|   ` - no default | ||||
| - `end`: The suffix container (usually for icons). | ||||
|   ` - no default | ||||
|  | ||||
| **Properties/Attributes** | ||||
|  | ||||
| | Name       | Type                                           | Default  | Description                                                                       | | ||||
| | ---------- | ---------------------------------------------- | -------- | --------------------------------------------------------------------------------- | | ||||
| | appearance | "accent"/"filled"/"plain"                      | "accent" | Sets the button appearance.                                                       | | ||||
| | variants   | "brand"/"danger"/"neutral"/"warning"/"success" | "brand"  | Sets the button color variant. "brand" is default.                                | | ||||
| | size       | "small"/"medium"                               | "medium" | Sets the button size.                                                             | | ||||
| | loading    | Boolean                                        | false    | Shows a loading indicator instead of the buttons label and disable buttons click. | | ||||
| | disabled   | Boolean                                        | false    | Disables the button and prevents user interaction.                                | | ||||
|  | ||||
| **CSS Custom Properties** | ||||
|  | ||||
| - `--ha-button-height` - Height of the button. | ||||
| - `--ha-button-border-radius` - Border radius of the button. Defaults to `var(--ha-border-radius-pill)`. | ||||
| @@ -1,171 +0,0 @@ | ||||
| import { mdiHome } from "@mdi/js"; | ||||
| import type { TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement } from "lit/decorators"; | ||||
| import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element"; | ||||
| import { titleCase } from "../../../../src/common/string/title-case"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-svg-icon"; | ||||
| import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg"; | ||||
|  | ||||
| const appearances = ["accent", "filled", "plain"]; | ||||
| const variants = ["brand", "danger", "neutral", "warning", "success"]; | ||||
|  | ||||
| @customElement("demo-components-ha-button") | ||||
| export class DemoHaButton extends LitElement { | ||||
|   protected render(): TemplateResult { | ||||
|     return html` | ||||
|       ${["light", "dark"].map( | ||||
|         (mode) => html` | ||||
|           <div class=${mode}> | ||||
|             <ha-card header="ha-button in ${mode}"> | ||||
|               <div class="card-content"> | ||||
|                 ${variants.map( | ||||
|                   (variant) => html` | ||||
|                     <div> | ||||
|                       ${appearances.map( | ||||
|                         (appearance) => html` | ||||
|                           <ha-button | ||||
|                             .appearance=${appearance} | ||||
|                             .variant=${variant} | ||||
|                           > | ||||
|                             <ha-svg-icon | ||||
|                               .path=${mdiHomeAssistant} | ||||
|                               slot="start" | ||||
|                             ></ha-svg-icon> | ||||
|                             ${titleCase(`${variant} ${appearance}`)} | ||||
|                             <ha-svg-icon | ||||
|                               .path=${mdiHome} | ||||
|                               slot="end" | ||||
|                             ></ha-svg-icon> | ||||
|                           </ha-button> | ||||
|                         ` | ||||
|                       )} | ||||
|                     </div> | ||||
|                     <div> | ||||
|                       ${appearances.map( | ||||
|                         (appearance) => html` | ||||
|                           <ha-button | ||||
|                             .appearance=${appearance} | ||||
|                             .variant=${variant} | ||||
|                             size="small" | ||||
|                           > | ||||
|                             ${titleCase(`${variant} ${appearance}`)} | ||||
|                           </ha-button> | ||||
|                         ` | ||||
|                       )} | ||||
|                     </div> | ||||
|                     <div> | ||||
|                       ${appearances.map( | ||||
|                         (appearance) => html` | ||||
|                           <ha-button | ||||
|                             .appearance=${appearance} | ||||
|                             .variant=${variant} | ||||
|                             loading | ||||
|                           > | ||||
|                             <ha-svg-icon | ||||
|                               .path=${mdiHomeAssistant} | ||||
|                               slot="start" | ||||
|                             ></ha-svg-icon> | ||||
|                             ${titleCase(`${variant} ${appearance}`)} | ||||
|                             <ha-svg-icon | ||||
|                               .path=${mdiHome} | ||||
|                               slot="end" | ||||
|                             ></ha-svg-icon> | ||||
|                           </ha-button> | ||||
|                         ` | ||||
|                       )} | ||||
|                     </div> | ||||
|                   ` | ||||
|                 )} | ||||
|                 ${variants.map( | ||||
|                   (variant) => html` | ||||
|                     <div> | ||||
|                       ${appearances.map( | ||||
|                         (appearance) => html` | ||||
|                           <ha-button | ||||
|                             .variant=${variant} | ||||
|                             .appearance=${appearance} | ||||
|                             disabled | ||||
|                           > | ||||
|                             ${titleCase(`${appearance}`)} | ||||
|                           </ha-button> | ||||
|                         ` | ||||
|                       )} | ||||
|                     </div> | ||||
|                     <div> | ||||
|                       ${appearances.map( | ||||
|                         (appearance) => html` | ||||
|                           <ha-button | ||||
|                             .variant=${variant} | ||||
|                             .appearance=${appearance} | ||||
|                             size="small" | ||||
|                             disabled | ||||
|                           > | ||||
|                             ${titleCase(`${appearance}`)} | ||||
|                           </ha-button> | ||||
|                         ` | ||||
|                       )} | ||||
|                     </div> | ||||
|                   ` | ||||
|                 )} | ||||
|               </div> | ||||
|             </ha-card> | ||||
|           </div> | ||||
|         ` | ||||
|       )} | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   firstUpdated(changedProps) { | ||||
|     super.firstUpdated(changedProps); | ||||
|     applyThemesOnElement( | ||||
|       this.shadowRoot!.querySelector(".dark"), | ||||
|       { | ||||
|         default_theme: "default", | ||||
|         default_dark_theme: "default", | ||||
|         themes: {}, | ||||
|         darkMode: true, | ||||
|         theme: "default", | ||||
|       }, | ||||
|       undefined, | ||||
|       undefined, | ||||
|       true | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   static styles = css` | ||||
|     :host { | ||||
|       display: flex; | ||||
|       justify-content: center; | ||||
|     } | ||||
|     .dark, | ||||
|     .light { | ||||
|       display: block; | ||||
|       background-color: var(--primary-background-color); | ||||
|       padding: 0 50px; | ||||
|     } | ||||
|     .button { | ||||
|       padding: unset; | ||||
|     } | ||||
|     ha-card { | ||||
|       margin: 24px auto; | ||||
|     } | ||||
|     .card-content { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       gap: var(--ha-space-6); | ||||
|     } | ||||
|     .card-content div { | ||||
|       display: flex; | ||||
|       gap: var(--ha-space-2); | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-components-ha-button": DemoHaButton; | ||||
|   } | ||||
| } | ||||
| @@ -9,10 +9,10 @@ import { css, html, LitElement } from "lit"; | ||||
| import { customElement } from "lit/decorators"; | ||||
| import { ifDefined } from "lit/directives/if-defined"; | ||||
| import { repeat } from "lit/directives/repeat"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-button"; | ||||
| import "../../../../src/components/ha-control-button-group"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-svg-icon"; | ||||
| import "../../../../src/components/ha-control-button-group"; | ||||
|  | ||||
| interface Button { | ||||
|   label: string; | ||||
| @@ -156,17 +156,17 @@ export class DemoHaBarButton extends LitElement { | ||||
|       --control-button-icon-color: var(--primary-color); | ||||
|       --control-button-background-color: var(--primary-color); | ||||
|       --control-button-background-opacity: 0.2; | ||||
|       --control-button-border-radius: var(--ha-border-radius-xl); | ||||
|       --control-button-border-radius: 18px; | ||||
|       height: 100px; | ||||
|       width: 100px; | ||||
|     } | ||||
|     .custom-group { | ||||
|       --control-button-group-thickness: 100px; | ||||
|       --control-button-group-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-button-group-border-radius: 36px; | ||||
|       --control-button-group-spacing: 20px; | ||||
|     } | ||||
|     .custom-group ha-control-button { | ||||
|       --control-button-border-radius: var(--ha-border-radius-xl); | ||||
|       --control-button-border-radius: 18px; | ||||
|       --mdc-icon-size: 32px; | ||||
|     } | ||||
|     .vertical-buttons { | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import type { TemplateResult } from "lit"; | ||||
| import { LitElement, css, html } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import { ifDefined } from "lit/directives/if-defined"; | ||||
| import { repeat } from "lit/directives/repeat"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-number-buttons"; | ||||
| import { repeat } from "lit/directives/repeat"; | ||||
| import { ifDefined } from "lit/directives/if-defined"; | ||||
|  | ||||
| const buttons: { | ||||
|   id: string; | ||||
| @@ -94,7 +94,7 @@ export class DemoHarControlNumberButtons extends LitElement { | ||||
|       --control-number-buttons-background-color: #2196f3; | ||||
|       --control-number-buttons-background-opacity: 0.1; | ||||
|       --control-number-buttons-thickness: 100px; | ||||
|       --control-number-buttons-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-number-buttons-border-radius: 36px; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -131,7 +131,7 @@ export class DemoHaControlSelectMenu extends LitElement { | ||||
|       --control-button-icon-color: var(--primary-color); | ||||
|       --control-button-background-color: var(--primary-color); | ||||
|       --control-button-background-opacity: 0.2; | ||||
|       --control-button-border-radius: var(--ha-border-radius-xl); | ||||
|       --control-button-border-radius: 18px; | ||||
|       height: 100px; | ||||
|       width: 100px; | ||||
|     } | ||||
|   | ||||
| @@ -135,7 +135,7 @@ export class DemoHaControlSelect extends LitElement { | ||||
|                 .options=${options} | ||||
|                 class=${ifDefined(config.class)} | ||||
|                 @value-changed=${this.handleValueChanged} | ||||
|                 .label=${label} | ||||
|                 aria-labelledby=${id} | ||||
|                 ?disabled=${config.disabled} | ||||
|               > | ||||
|               </ha-control-select> | ||||
| @@ -156,7 +156,7 @@ export class DemoHaControlSelect extends LitElement { | ||||
|                   vertical | ||||
|                   class=${ifDefined(config.class)} | ||||
|                   @value-changed=${this.handleValueChanged} | ||||
|                   .label=${label} | ||||
|                   aria-labelledby=${id} | ||||
|                   ?disabled=${config.disabled} | ||||
|                 > | ||||
|                 </ha-control-select> | ||||
| @@ -187,7 +187,7 @@ export class DemoHaControlSelect extends LitElement { | ||||
|       --mdc-icon-size: 24px; | ||||
|       --control-select-color: var(--state-fan-active-color); | ||||
|       --control-select-thickness: 130px; | ||||
|       --control-select-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-select-border-radius: 36px; | ||||
|     } | ||||
|     .vertical-selects { | ||||
|       height: 300px; | ||||
|   | ||||
| @@ -3,8 +3,8 @@ import { css, html, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import { ifDefined } from "lit/directives/if-defined"; | ||||
| import { repeat } from "lit/directives/repeat"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-slider"; | ||||
| import "../../../../src/components/ha-card"; | ||||
|  | ||||
| const sliders: { | ||||
|   id: string; | ||||
| @@ -97,7 +97,7 @@ export class DemoHaBarSlider extends LitElement { | ||||
|                 class=${ifDefined(config.class)} | ||||
|                 @value-changed=${this.handleValueChanged} | ||||
|                 @slider-moved=${this.handleSliderMoved} | ||||
|                 .label=${label} | ||||
|                 aria-labelledby=${id} | ||||
|                 .unit=${config.unit} | ||||
|               > | ||||
|               </ha-control-slider> | ||||
| @@ -119,7 +119,7 @@ export class DemoHaBarSlider extends LitElement { | ||||
|                   class=${ifDefined(config.class)} | ||||
|                   @value-changed=${this.handleValueChanged} | ||||
|                   @slider-moved=${this.handleSliderMoved} | ||||
|                   .label=${label} | ||||
|                   aria-label=${label} | ||||
|                   .unit=${config.unit} | ||||
|                 > | ||||
|                 </ha-control-slider> | ||||
| @@ -151,7 +151,7 @@ export class DemoHaBarSlider extends LitElement { | ||||
|       --control-slider-background: #ffcf4c; | ||||
|       --control-slider-background-opacity: 0.2; | ||||
|       --control-slider-thickness: 130px; | ||||
|       --control-slider-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-slider-border-radius: 36px; | ||||
|     } | ||||
|     .vertical-sliders { | ||||
|       height: 300px; | ||||
|   | ||||
| @@ -9,8 +9,8 @@ import { css, html, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import { ifDefined } from "lit/directives/if-defined"; | ||||
| import { repeat } from "lit/directives/repeat"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-control-switch"; | ||||
| import "../../../../src/components/ha-card"; | ||||
|  | ||||
| const switches: { | ||||
|   id: string; | ||||
| @@ -63,7 +63,7 @@ export class DemoHaControlSwitch extends LitElement { | ||||
|                 @change=${this.handleValueChanged} | ||||
|                 .pathOn=${mdiLightbulb} | ||||
|                 .pathOff=${mdiLightbulbOff} | ||||
|                 .label=${label} | ||||
|                 aria-labelledby=${id} | ||||
|                 ?disabled=${config.disabled} | ||||
|                 ?reversed=${config.reversed} | ||||
|               > | ||||
| @@ -84,7 +84,7 @@ export class DemoHaControlSwitch extends LitElement { | ||||
|                   vertical | ||||
|                   class=${ifDefined(config.class)} | ||||
|                   @change=${this.handleValueChanged} | ||||
|                   .label=${label} | ||||
|                   aria-label=${label} | ||||
|                   .pathOn=${mdiGarageOpen} | ||||
|                   .pathOff=${mdiGarage} | ||||
|                   ?disabled=${config.disabled} | ||||
| @@ -118,7 +118,7 @@ export class DemoHaControlSwitch extends LitElement { | ||||
|       --control-switch-on-color: var(--green-color); | ||||
|       --control-switch-off-color: var(--red-color); | ||||
|       --control-switch-thickness: 130px; | ||||
|       --control-switch-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-switch-border-radius: 36px; | ||||
|       --control-switch-padding: 6px; | ||||
|       --mdc-icon-size: 24px; | ||||
|     } | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| /* eslint-disable lit/no-template-arrow */ | ||||
| import "@material/mwc-button"; | ||||
| import type { TemplateResult } from "lit"; | ||||
| import { html, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
|   | ||||
| @@ -1,37 +0,0 @@ | ||||
| --- | ||||
| title: Marquee Text | ||||
| --- | ||||
|  | ||||
| # Marquee Text `<ha-marquee-text>` | ||||
|  | ||||
| Marquee text component scrolls text horizontally if it overflows its container. It supports pausing on hover and customizable speed and pause duration. | ||||
|  | ||||
| ## Implementation | ||||
|  | ||||
| ### Example Usage | ||||
|  | ||||
| <ha-marquee-text style="width: 200px;"> | ||||
|     This is a long text that will scroll horizontally if it overflows the container. | ||||
| </ha-marquee-text> | ||||
|  | ||||
| ```html | ||||
| <ha-marquee-text style="width: 200px;"> | ||||
|   This is a long text that will scroll horizontally if it overflows the | ||||
|   container. | ||||
| </ha-marquee-text> | ||||
| ``` | ||||
|  | ||||
| ### API | ||||
|  | ||||
| **Slots** | ||||
|  | ||||
| - default slot: The text content to be displayed and scrolled. | ||||
|   - no default | ||||
|  | ||||
| **Properties/Attributes** | ||||
|  | ||||
| | Name           | Type    | Default | Description                                                                  | | ||||
| | -------------- | ------- | ------- | ---------------------------------------------------------------------------- | | ||||
| | speed          | number  | `15`    | The speed of the scrolling animation. Higher values result in faster scroll. | | ||||
| | pause-on-hover | boolean | `true`  | Whether to pause the scrolling animation when                                | | ||||
| | pause-duration | number  | `1000`  | The delay in milliseconds before the scrolling animation starts/restarts.    | | ||||
| @@ -1,25 +0,0 @@ | ||||
| import { css, LitElement } from "lit"; | ||||
| import { customElement } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-marquee-text"; | ||||
|  | ||||
| @customElement("demo-components-ha-marquee-text") | ||||
| export class DemoHaMarqueeText extends LitElement { | ||||
|   static styles = css` | ||||
|     ha-card { | ||||
|       max-width: 600px; | ||||
|       margin: 24px auto; | ||||
|     } | ||||
|     .card-content { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       align-items: flex-start; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-components-ha-marquee-text": DemoHaMarqueeText; | ||||
|   } | ||||
| } | ||||
| @@ -1,32 +0,0 @@ | ||||
| --- | ||||
| title: Progress Button | ||||
| --- | ||||
|  | ||||
| <style> | ||||
|   .wrapper { | ||||
|     display: flex; | ||||
|     gap: 24px; | ||||
|   } | ||||
| </style> | ||||
|  | ||||
| # Progress Button `<ha-progress-button>` | ||||
|  | ||||
| ### API | ||||
|  | ||||
| This component is a wrapper around `<ha-button>` that adds support for showing progress | ||||
|  | ||||
| **Slots** | ||||
|  | ||||
| - default slot: Label of the button | ||||
|   ` - no default | ||||
|  | ||||
| **Properties/Attributes** | ||||
|  | ||||
| | Name       | Type                                           | Default   | Description                                        | | ||||
| | ---------- | ---------------------------------------------- | --------- | -------------------------------------------------- | | ||||
| | label      | string                                         | "accent"  | Sets the button label.                             | | ||||
| | disabled   | Boolean                                        | false     | Disables the button if true.                       | | ||||
| | progress   | Boolean                                        | false     | Shows a progress indicator on the button.          | | ||||
| | appearance | "accent"/"filled"/"plain"                      | "accent"  | Sets the button appearance.                        | | ||||
| | variants   | "brand"/"danger"/"neutral"/"warning"/"success" | "brand"   | Sets the button color variant. "brand" is default. | | ||||
| | iconPath   | string                                         | undefined | Sets the icon path for the button.                 | | ||||
| @@ -1,139 +0,0 @@ | ||||
| import type { TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement } from "lit/decorators"; | ||||
| import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element"; | ||||
| import "../../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-svg-icon"; | ||||
| import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg"; | ||||
|  | ||||
| @customElement("demo-components-ha-progress-button") | ||||
| export class DemoHaProgressButton extends LitElement { | ||||
|   protected render(): TemplateResult { | ||||
|     return html` | ||||
|       ${["light", "dark"].map( | ||||
|         (mode) => html` | ||||
|           <div class=${mode}> | ||||
|             <ha-card header="ha-progress-button in ${mode}"> | ||||
|               <div class="card-content"> | ||||
|                 <ha-progress-button @click=${this._clickedSuccess}> | ||||
|                   Success | ||||
|                 </ha-progress-button> | ||||
|                 <ha-progress-button @click=${this._clickedFail}> | ||||
|                   Fail | ||||
|                 </ha-progress-button> | ||||
|                 <ha-progress-button size="small" @click=${this._clickedSuccess}> | ||||
|                   small | ||||
|                 </ha-progress-button> | ||||
|                 <ha-progress-button | ||||
|                   appearance="filled" | ||||
|                   @click=${this._clickedSuccess} | ||||
|                 > | ||||
|                   filled | ||||
|                 </ha-progress-button> | ||||
|                 <ha-progress-button | ||||
|                   appearance="plain" | ||||
|                   @click=${this._clickedSuccess} | ||||
|                 > | ||||
|                   plain | ||||
|                 </ha-progress-button> | ||||
|                 <ha-progress-button | ||||
|                   variant="warning" | ||||
|                   @click=${this._clickedSuccess} | ||||
|                 > | ||||
|                   warning | ||||
|                 </ha-progress-button> | ||||
|                 <ha-progress-button | ||||
|                   variant="neutral" | ||||
|                   @click=${this._clickedSuccess} | ||||
|                   label="with icon" | ||||
|                   .iconPath=${mdiHomeAssistant} | ||||
|                 > | ||||
|                   With Icon | ||||
|                 </ha-progress-button> | ||||
|                 <ha-progress-button progress @click=${this._clickedSuccess}> | ||||
|                   progress | ||||
|                 </ha-progress-button> | ||||
|                 <ha-progress-button disabled @click=${this._clickedSuccess}> | ||||
|                   disabled | ||||
|                 </ha-progress-button> | ||||
|               </div> | ||||
|             </ha-card> | ||||
|           </div> | ||||
|         ` | ||||
|       )} | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   firstUpdated(changedProps) { | ||||
|     super.firstUpdated(changedProps); | ||||
|     applyThemesOnElement( | ||||
|       this.shadowRoot!.querySelector(".dark"), | ||||
|       { | ||||
|         default_theme: "default", | ||||
|         default_dark_theme: "default", | ||||
|         themes: {}, | ||||
|         darkMode: true, | ||||
|         theme: "default", | ||||
|       }, | ||||
|       undefined, | ||||
|       undefined, | ||||
|       true | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   private async _clickedSuccess(ev: CustomEvent): Promise<void> { | ||||
|     console.log("Clicked success"); | ||||
|     const button = ev.currentTarget as any; | ||||
|     button.progress = true; | ||||
|  | ||||
|     setTimeout(() => { | ||||
|       button.actionSuccess(); | ||||
|       button.progress = false; | ||||
|     }, 1000); | ||||
|   } | ||||
|  | ||||
|   private async _clickedFail(ev: CustomEvent): Promise<void> { | ||||
|     const button = ev.currentTarget as any; | ||||
|     button.progress = true; | ||||
|  | ||||
|     setTimeout(() => { | ||||
|       button.actionError(); | ||||
|       button.progress = false; | ||||
|     }, 1000); | ||||
|   } | ||||
|  | ||||
|   static styles = css` | ||||
|     :host { | ||||
|       display: flex; | ||||
|       justify-content: center; | ||||
|     } | ||||
|     .dark, | ||||
|     .light { | ||||
|       display: block; | ||||
|       background-color: var(--primary-background-color); | ||||
|       padding: 0 50px; | ||||
|     } | ||||
|     .button { | ||||
|       padding: unset; | ||||
|     } | ||||
|     ha-card { | ||||
|       margin: 24px auto; | ||||
|     } | ||||
|     .card-content { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       gap: var(--ha-space-6); | ||||
|     } | ||||
|     .card-content div { | ||||
|       display: flex; | ||||
|       gap: var(--ha-space-2); | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-components-ha-progress-button": DemoHaProgressButton; | ||||
|   } | ||||
| } | ||||
| @@ -131,7 +131,7 @@ export class DemoHaSelectBox extends LitElement { | ||||
|       --mdc-icon-size: 24px; | ||||
|       --control-select-color: var(--state-fan-active-color); | ||||
|       --control-select-thickness: 130px; | ||||
|       --control-select-border-radius: var(--ha-border-radius-6xl); | ||||
|       --control-select-border-radius: 36px; | ||||
|     } | ||||
|  | ||||
|     p.title { | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import "@material/mwc-button"; | ||||
| import type { TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| @@ -415,34 +416,6 @@ const SCHEMAS: { | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       items: { | ||||
|         name: "Items", | ||||
|         selector: { | ||||
|           object: { | ||||
|             label_field: "name", | ||||
|             description_field: "value", | ||||
|             multiple: true, | ||||
|             fields: { | ||||
|               name: { | ||||
|                 label: "Name", | ||||
|                 selector: { text: {} }, | ||||
|                 required: true, | ||||
|               }, | ||||
|               value: { | ||||
|                 label: "Value", | ||||
|                 selector: { | ||||
|                   number: { | ||||
|                     mode: "slider", | ||||
|                     min: 0, | ||||
|                     max: 100, | ||||
|                     unit_of_measurement: "%", | ||||
|                   }, | ||||
|                 }, | ||||
|               }, | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| ]; | ||||
|   | ||||
| @@ -1,38 +0,0 @@ | ||||
| --- | ||||
| title: Slider | ||||
| subtitle: A slider component for selecting a value from a range. | ||||
| --- | ||||
|  | ||||
| <style> | ||||
|   .wrapper { | ||||
|     display: flex; | ||||
|     gap: 24px; | ||||
|   } | ||||
| </style> | ||||
|  | ||||
| # Slider `<ha-slider>` | ||||
|  | ||||
| ## Implementation | ||||
|  | ||||
| ### Example Usage | ||||
|  | ||||
| <div class="wrapper"> | ||||
|   <ha-slider size="small" with-markers min="0" max="8" value="4"></ha-slider> | ||||
|   <ha-slider size="medium"></ha-slider> | ||||
| </div> | ||||
|  | ||||
| ```html | ||||
| <ha-slider size="small" with-markers min="0" max="8" value="4"></ha-slider> | ||||
| <ha-slider size="medium"></ha-slider> | ||||
| ``` | ||||
|  | ||||
| ### API | ||||
|  | ||||
| This component is based on the webawesome slider component. | ||||
| Check the [webawesome documentation](https://webawesome.com/docs/components/slider/) for more details. | ||||
|  | ||||
| **CSS Custom Properties** | ||||
|  | ||||
| - `--ha-slider-track-size` - Height of the slider track. Defaults to `4px`. | ||||
| - `--ha-slider-thumb-color` - Color of the slider thumb. Defaults to `var(--primary-color)`. | ||||
| - `--ha-slider-indicator-color` - Color of the filled portion of the slider track. Defaults to `var(--primary-color)`. | ||||
| @@ -1,100 +0,0 @@ | ||||
| import type { TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
| import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element"; | ||||
| import "../../../../src/components/ha-bar"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-spinner"; | ||||
| import "../../../../src/components/ha-slider"; | ||||
| import type { HomeAssistant } from "../../../../src/types"; | ||||
|  | ||||
| @customElement("demo-components-ha-slider") | ||||
| export class DemoHaSlider extends LitElement { | ||||
|   @property({ attribute: false }) hass!: HomeAssistant; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html` | ||||
|       ${["light", "dark"].map( | ||||
|         (mode) => html` | ||||
|           <div class=${mode}> | ||||
|             <ha-card header="ha-slider ${mode} demo"> | ||||
|               <div class="card-content"> | ||||
|                 <span>Default (disabled)</span> | ||||
|                 <ha-slider | ||||
|                   disabled | ||||
|                   min="0" | ||||
|                   max="8" | ||||
|                   value="4" | ||||
|                   with-markers | ||||
|                 ></ha-slider> | ||||
|                 <span>Small</span> | ||||
|                 <ha-slider | ||||
|                   size="small" | ||||
|                   min="0" | ||||
|                   max="8" | ||||
|                   value="4" | ||||
|                   with-markers | ||||
|                 ></ha-slider> | ||||
|                 <span>Medium</span> | ||||
|                 <ha-slider | ||||
|                   size="medium" | ||||
|                   min="0" | ||||
|                   max="8" | ||||
|                   value="4" | ||||
|                   with-markers | ||||
|                 ></ha-slider> | ||||
|               </div> | ||||
|             </ha-card> | ||||
|           </div> | ||||
|         ` | ||||
|       )} | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   firstUpdated(changedProps) { | ||||
|     super.firstUpdated(changedProps); | ||||
|     applyThemesOnElement( | ||||
|       this.shadowRoot!.querySelector(".dark"), | ||||
|       { | ||||
|         default_theme: "default", | ||||
|         default_dark_theme: "default", | ||||
|         themes: {}, | ||||
|         darkMode: true, | ||||
|         theme: "default", | ||||
|       }, | ||||
|       undefined, | ||||
|       undefined, | ||||
|       true | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   static styles = css` | ||||
|     :host { | ||||
|       display: flex; | ||||
|       justify-content: center; | ||||
|     } | ||||
|     .dark, | ||||
|     .light { | ||||
|       display: block; | ||||
|       background-color: var(--primary-background-color); | ||||
|       padding: 0 50px; | ||||
|       margin: 16px; | ||||
|       border-radius: var(--ha-border-radius-md); | ||||
|     } | ||||
|     ha-card { | ||||
|       margin: 24px auto; | ||||
|     } | ||||
|     .card-content { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       align-items: center; | ||||
|       gap: var(--ha-space-6); | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-components-ha-slider": DemoHaSlider; | ||||
|   } | ||||
| } | ||||
| @@ -61,7 +61,7 @@ export class DemoHaSpinner extends LitElement { | ||||
|       background-color: var(--primary-background-color); | ||||
|       padding: 0 50px; | ||||
|       margin: 16px; | ||||
|       border-radius: var(--ha-border-radius-md); | ||||
|       border-radius: 8px; | ||||
|     } | ||||
|     ha-card { | ||||
|       margin: 24px auto; | ||||
| @@ -70,7 +70,7 @@ export class DemoHaSpinner extends LitElement { | ||||
|       display: flex; | ||||
|       flex-direction: column; | ||||
|       align-items: center; | ||||
|       gap: var(--ha-space-6); | ||||
|       gap: 24px; | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -6,23 +6,21 @@ A tooltip's target is its _first child element_, so you should only wrap one ele | ||||
|  | ||||
| Tooltips use `display: contents` so they won't interfere with how elements are positioned in a flex or grid layout. | ||||
|  | ||||
| <ha-button id="hover">Hover Me</ha-button> | ||||
| <ha-tooltip for="hover"> | ||||
| This is a tooltip | ||||
| <ha-tooltip content="This is a tooltip"> | ||||
|   <ha-button>Hover Me</ha-button> | ||||
| </ha-tooltip> | ||||
|  | ||||
| ``` | ||||
| <ha-button id="hover">Hover Me</ha-button> | ||||
| <ha-tooltip for="hover"> | ||||
| This is a tooltip | ||||
| <ha-tooltip content="This is a tooltip"> | ||||
|   <ha-button>Hover Me</ha-button> | ||||
| </ha-tooltip> | ||||
| ``` | ||||
|  | ||||
| ## Documentation | ||||
|  | ||||
| This element is based on webawesome `wa-tooltip` it only sets some css tokens and has a custom show/hide animation. | ||||
| This element is based on shoelace `sl-tooltip` it only sets some css tokens and has a custom show/hide animation. | ||||
|  | ||||
| <a href="https://webawesome.com/docs/components/tooltip/" target="_blank" rel="noopener noreferrer">Webawesome documentation</a> | ||||
| <a href="https://shoelace.style/components/tooltip" target="_blank" rel="noopener noreferrer">Shoelace documentation</a> | ||||
|  | ||||
| ### HA style tokens | ||||
|  | ||||
| @@ -30,7 +28,7 @@ In your theme settings use this without the prefixed `--`. | ||||
|  | ||||
| - `--ha-tooltip-border-radius` (Default: 4px) | ||||
| - `--ha-tooltip-arrow-size` (Default: 8px) | ||||
| - `--wa-tooltip-font-family` (Default: `var(--ha-font-family-body)`) | ||||
| - `--sl-tooltip-font-family` (Default: `var(--ha-font-family-body)`) | ||||
| - `--ha-tooltip-font-size` (Default: `var(--ha-font-size-s)`) | ||||
| - `--wa-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`) | ||||
| - `--wa-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`) | ||||
| - `--sl-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`) | ||||
| - `--sl-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`) | ||||
|   | ||||
| @@ -1,3 +0,0 @@ | ||||
| --- | ||||
| title: Dialog (ha-wa-dialog) | ||||
| --- | ||||
| @@ -1,523 +0,0 @@ | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement, state } from "lit/decorators"; | ||||
| import { mdiCog, mdiHelp } from "@mdi/js"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-dialog-footer"; | ||||
| import "../../../../src/components/ha-form/ha-form"; | ||||
| import "../../../../src/components/ha-icon-button"; | ||||
| import "../../../../src/components/ha-wa-dialog"; | ||||
| import type { HaFormSchema } from "../../../../src/components/ha-form/types"; | ||||
|  | ||||
| const SCHEMA: HaFormSchema[] = [ | ||||
|   { type: "string", name: "Name", default: "", autofocus: true }, | ||||
|   { type: "string", name: "Email", default: "" }, | ||||
| ]; | ||||
|  | ||||
| type DialogType = | ||||
|   | false | ||||
|   | "basic" | ||||
|   | "basic-subtitle-below" | ||||
|   | "basic-subtitle-above" | ||||
|   | "form" | ||||
|   | "actions"; | ||||
|  | ||||
| @customElement("demo-components-ha-wa-dialog") | ||||
| export class DemoHaWaDialog extends LitElement { | ||||
|   @state() private _openDialog: DialogType = false; | ||||
|  | ||||
|   protected render() { | ||||
|     return html` | ||||
|       <div class="content"> | ||||
|         <h1>Dialog <code><ha-wa-dialog></code></h1> | ||||
|  | ||||
|         <p class="subtitle">Dialog component built with WebAwesome.</p> | ||||
|  | ||||
|         <h2>Demos</h2> | ||||
|  | ||||
|         <div class="buttons"> | ||||
|           <ha-button @click=${this._handleOpenDialog("basic")} | ||||
|             >Basic dialog</ha-button | ||||
|           > | ||||
|           <ha-button @click=${this._handleOpenDialog("basic-subtitle-below")} | ||||
|             >Basic dialog with subtitle below</ha-button | ||||
|           > | ||||
|           <ha-button @click=${this._handleOpenDialog("basic-subtitle-above")} | ||||
|             >Basic dialog with subtitle above</ha-button | ||||
|           > | ||||
|           <ha-button @click=${this._handleOpenDialog("form")} | ||||
|             >Dialog with form</ha-button | ||||
|           > | ||||
|           <ha-button @click=${this._handleOpenDialog("actions")} | ||||
|             >Dialog with actions</ha-button | ||||
|           > | ||||
|         </div> | ||||
|  | ||||
|         <ha-wa-dialog | ||||
|           .open=${this._openDialog === "basic"} | ||||
|           header-title="Basic dialog" | ||||
|           @closed=${this._handleClosed} | ||||
|         > | ||||
|           <div>Dialog content</div> | ||||
|         </ha-wa-dialog> | ||||
|  | ||||
|         <ha-wa-dialog | ||||
|           .open=${this._openDialog === "basic-subtitle-below"} | ||||
|           header-title="Basic dialog with subtitle" | ||||
|           header-subtitle="This is a basic dialog with a subtitle below" | ||||
|           @closed=${this._handleClosed} | ||||
|         > | ||||
|           <div>Dialog content</div> | ||||
|         </ha-wa-dialog> | ||||
|  | ||||
|         <ha-wa-dialog | ||||
|           .open=${this._openDialog === "basic-subtitle-above"} | ||||
|           header-title="Dialog with subtitle above" | ||||
|           header-subtitle="This is a basic dialog with a subtitle above" | ||||
|           header-subtitle-position="above" | ||||
|           @closed=${this._handleClosed} | ||||
|         > | ||||
|           <div>Dialog content</div> | ||||
|         </ha-wa-dialog> | ||||
|  | ||||
|         <ha-wa-dialog | ||||
|           .open=${this._openDialog === "form"} | ||||
|           header-title="Dialog with form" | ||||
|           header-subtitle="This is a dialog with a form and a footer" | ||||
|           prevent-scrim-close | ||||
|           @closed=${this._handleClosed} | ||||
|         > | ||||
|           <ha-form autofocus .schema=${SCHEMA}></ha-form> | ||||
|           <ha-dialog-footer slot="footer"> | ||||
|             <ha-button | ||||
|               data-dialog="close" | ||||
|               slot="secondaryAction" | ||||
|               variant="plain" | ||||
|               >Cancel</ha-button | ||||
|             > | ||||
|             <ha-button data-dialog="close" slot="primaryAction" variant="accent" | ||||
|               >Submit</ha-button | ||||
|             > | ||||
|           </ha-dialog-footer> | ||||
|         </ha-wa-dialog> | ||||
|  | ||||
|         <ha-wa-dialog | ||||
|           .open=${this._openDialog === "actions"} | ||||
|           header-title="Dialog with actions" | ||||
|           header-subtitle="This is a dialog with header actions" | ||||
|           @closed=${this._handleClosed} | ||||
|         > | ||||
|           <div slot="headerActionItems"> | ||||
|             <ha-icon-button label="Settings" path=${mdiCog}></ha-icon-button> | ||||
|             <ha-icon-button label="Help" path=${mdiHelp}></ha-icon-button> | ||||
|           </div> | ||||
|  | ||||
|           <div>Dialog content</div> | ||||
|         </ha-wa-dialog> | ||||
|  | ||||
|         <h2>Design</h2> | ||||
|  | ||||
|         <h3>Width</h3> | ||||
|  | ||||
|         <p>There are multiple widths available for the dialog.</p> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>Name</th> | ||||
|               <th>Value</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>small</code></td> | ||||
|               <td><code>min(320px, var(--full-width))</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>medium</code></td> | ||||
|               <td><code>min(580px, var(--full-width))</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>large</code></td> | ||||
|               <td><code>min(720px, var(--full-width))</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>full</code></td> | ||||
|               <td><code>var(--full-width)</code></td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|  | ||||
|         <p> | ||||
|           <code>--full-width</code> is calculated based on the available width | ||||
|           of the screen. 95vw is the maximum width of the dialog on a large | ||||
|           screen, while on a small screen it is 100vw minus the safe area | ||||
|           insets. | ||||
|         </p> | ||||
|  | ||||
|         <p>Dialogs have a default width of <code>medium</code>.</p> | ||||
|  | ||||
|         <h3>Prevent scrim close</h3> | ||||
|  | ||||
|         <p> | ||||
|           You can prevent the dialog from being closed by clicking the | ||||
|           scrim/overlay. This is allowed by default. | ||||
|         </p> | ||||
|  | ||||
|         <h3>Header</h3> | ||||
|  | ||||
|         <p>The header contains a title, a subtitle and action items.</p> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>Slot</th> | ||||
|               <th>Description</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>header</code></td> | ||||
|               <td>The entire header area.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>headerTitle</code></td> | ||||
|               <td>The header title text.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>headerSubtitle</code></td> | ||||
|               <td>The header subtitle text.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>headerActionItems</code></td> | ||||
|               <td>The header action items.</td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|  | ||||
|         <h4>Header title</h4> | ||||
|  | ||||
|         <p>The header title is a text string.</p> | ||||
|  | ||||
|         <h4>Header subtitle</h4> | ||||
|  | ||||
|         <p>The header subtitle is a text string.</p> | ||||
|  | ||||
|         <h4>Header action items</h4> | ||||
|  | ||||
|         <p> | ||||
|           The header action items usually containing icon buttons and/or menu | ||||
|           buttons. | ||||
|         </p> | ||||
|  | ||||
|         <h3>Body</h3> | ||||
|  | ||||
|         <p>The body is the content of the dialog.</p> | ||||
|  | ||||
|         <h3>Footer</h3> | ||||
|  | ||||
|         <p>The footer is the footer of the dialog.</p> | ||||
|  | ||||
|         <p> | ||||
|           It is recommended to use the <code>ha-dialog-footer</code> component | ||||
|           for the footer and to style the buttons inside the footer as so: | ||||
|         </p> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>Slot</th> | ||||
|               <th>Description</th> | ||||
|               <th>Variant to use</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>secondaryAction</code></td> | ||||
|               <td>The secondary action button(s).</td> | ||||
|               <td><code>plain</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>primaryAction</code></td> | ||||
|               <td>The primary action button(s).</td> | ||||
|               <td><code>accent</code></td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|  | ||||
|         <h2>Implementation</h2> | ||||
|  | ||||
|         <h3>Example Usage</h3> | ||||
|  | ||||
|         <pre><code><ha-wa-dialog | ||||
|   open | ||||
|   header-title="Dialog title" | ||||
|   header-subtitle="Dialog subtitle" | ||||
|   prevent-scrim-close | ||||
| > | ||||
|   <div slot="headerActionItems"> | ||||
|     <ha-icon-button label="Settings" path="mdiCog"></ha-icon-button> | ||||
|     <ha-icon-button label="Help" path="mdiHelp"></ha-icon-button> | ||||
|   </div> | ||||
|   <div>Dialog content</div> | ||||
|   <ha-dialog-footer slot="footer"> | ||||
|     <ha-button data-dialog="close" slot="secondaryAction" variant="plain" | ||||
|       >Cancel</ha-button | ||||
|     > | ||||
|     <ha-button slot="primaryAction" variant="accent">Submit</ha-button> | ||||
|   </ha-dialog-footer> | ||||
| </ha-wa-dialog></code></pre> | ||||
|  | ||||
|         <h3>API</h3> | ||||
|  | ||||
|         <p> | ||||
|           This component is based on the webawesome dialog component. Check the | ||||
|           <a | ||||
|             href="https://webawesome.com/docs/components/dialog/" | ||||
|             target="_blank" | ||||
|             rel="noopener noreferrer" | ||||
|             >webawesome documentation</a | ||||
|           > | ||||
|           for more details. | ||||
|         </p> | ||||
|  | ||||
|         <h4>Attributes</h4> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>Attribute</th> | ||||
|               <th>Description</th> | ||||
|               <th>Default</th> | ||||
|               <th>Options</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>open</code></td> | ||||
|               <td>Controls the dialog open state.</td> | ||||
|               <td><code>false</code></td> | ||||
|               <td><code>false</code>, <code>true</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>width</code></td> | ||||
|               <td>Preferred dialog width preset.</td> | ||||
|               <td><code>medium</code></td> | ||||
|               <td> | ||||
|                 <code>small</code>, <code>medium</code>, <code>large</code>, | ||||
|                 <code>full</code> | ||||
|               </td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>prevent-scrim-close</code></td> | ||||
|               <td> | ||||
|                 Prevents closing the dialog by clicking the scrim/overlay. | ||||
|               </td> | ||||
|               <td><code>false</code></td> | ||||
|               <td><code>true</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>header-title</code></td> | ||||
|               <td>Header title text when no custom title slot is provided.</td> | ||||
|               <td></td> | ||||
|               <td></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>header-subtitle</code></td> | ||||
|               <td> | ||||
|                 Header subtitle text when no custom subtitle slot is provided. | ||||
|               </td> | ||||
|               <td></td> | ||||
|               <td></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>header-subtitle-position</code></td> | ||||
|               <td>Position of the subtitle relative to the title.</td> | ||||
|               <td><code>below</code></td> | ||||
|               <td><code>above</code>, <code>below</code></td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>flexcontent</code></td> | ||||
|               <td> | ||||
|                 Makes the dialog body a flex container for flexible layouts. | ||||
|               </td> | ||||
|               <td><code>false</code></td> | ||||
|               <td><code>false</code>, <code>true</code></td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|  | ||||
|         <h4>CSS Custom Properties</h4> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>CSS Property</th> | ||||
|               <th>Description</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>--dialog-content-padding</code></td> | ||||
|               <td>Padding for dialog content sections.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--ha-dialog-show-duration</code></td> | ||||
|               <td>Show animation duration.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--ha-dialog-hide-duration</code></td> | ||||
|               <td>Hide animation duration.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--ha-dialog-surface-background</code></td> | ||||
|               <td>Dialog background color.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--ha-dialog-border-radius</code></td> | ||||
|               <td>Border radius of the dialog surface.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--dialog-z-index</code></td> | ||||
|               <td>Z-index for the dialog.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--dialog-surface-position</code></td> | ||||
|               <td>CSS position of the dialog surface.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>--dialog-surface-margin-top</code></td> | ||||
|               <td>Top margin for the dialog surface.</td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|  | ||||
|         <h4>Events</h4> | ||||
|  | ||||
|         <table> | ||||
|           <thead> | ||||
|             <tr> | ||||
|               <th>Event</th> | ||||
|               <th>Description</th> | ||||
|             </tr> | ||||
|           </thead> | ||||
|           <tbody> | ||||
|             <tr> | ||||
|               <td><code>opened</code></td> | ||||
|               <td>Fired when the dialog is shown.</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|               <td><code>closed</code></td> | ||||
|               <td>Fired after the dialog is hidden.</td> | ||||
|             </tr> | ||||
|           </tbody> | ||||
|         </table> | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   private _handleOpenDialog = (dialog: DialogType) => () => { | ||||
|     this._openDialog = dialog; | ||||
|   }; | ||||
|  | ||||
|   private _handleClosed = () => { | ||||
|     this._openDialog = false; | ||||
|   }; | ||||
|  | ||||
|   static styles = [ | ||||
|     css` | ||||
|       :host { | ||||
|         display: block; | ||||
|         padding: var(--ha-space-4); | ||||
|       } | ||||
|  | ||||
|       .content { | ||||
|         max-width: 1000px; | ||||
|         margin: 0 auto; | ||||
|       } | ||||
|  | ||||
|       h1 { | ||||
|         margin-top: 0; | ||||
|         margin-bottom: var(--ha-space-2); | ||||
|       } | ||||
|  | ||||
|       h2 { | ||||
|         margin-top: var(--ha-space-6); | ||||
|         margin-bottom: var(--ha-space-3); | ||||
|       } | ||||
|  | ||||
|       h3, | ||||
|       h4 { | ||||
|         margin-top: var(--ha-space-4); | ||||
|         margin-bottom: var(--ha-space-2); | ||||
|       } | ||||
|  | ||||
|       p { | ||||
|         margin: var(--ha-space-2) 0; | ||||
|         line-height: 1.6; | ||||
|       } | ||||
|  | ||||
|       .subtitle { | ||||
|         color: var(--secondary-text-color); | ||||
|         font-size: 1.1em; | ||||
|         margin-bottom: var(--ha-space-4); | ||||
|       } | ||||
|  | ||||
|       table { | ||||
|         width: 100%; | ||||
|         border-collapse: collapse; | ||||
|         margin: var(--ha-space-3) 0; | ||||
|       } | ||||
|  | ||||
|       th, | ||||
|       td { | ||||
|         text-align: left; | ||||
|         padding: var(--ha-space-2); | ||||
|         border-bottom: 1px solid var(--divider-color); | ||||
|       } | ||||
|  | ||||
|       th { | ||||
|         font-weight: 500; | ||||
|       } | ||||
|  | ||||
|       code { | ||||
|         background-color: var(--secondary-background-color); | ||||
|         padding: 2px 6px; | ||||
|         border-radius: 4px; | ||||
|         font-family: monospace; | ||||
|         font-size: 0.9em; | ||||
|       } | ||||
|  | ||||
|       pre { | ||||
|         background-color: var(--secondary-background-color); | ||||
|         padding: var(--ha-space-3); | ||||
|         border-radius: 8px; | ||||
|         overflow-x: auto; | ||||
|         margin: var(--ha-space-3) 0; | ||||
|       } | ||||
|  | ||||
|       pre code { | ||||
|         background-color: transparent; | ||||
|         padding: 0; | ||||
|       } | ||||
|  | ||||
|       .buttons { | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         flex-wrap: wrap; | ||||
|         gap: var(--ha-space-2); | ||||
|         margin: var(--ha-space-4) 0; | ||||
|       } | ||||
|  | ||||
|       a { | ||||
|         color: var(--primary-color); | ||||
|       } | ||||
|     `, | ||||
|   ]; | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-components-ha-wa-dialog": DemoHaWaDialog; | ||||
|   } | ||||
| } | ||||
| @@ -11,7 +11,6 @@ import { provideHass } from "../../../../src/fake_data/provide_hass"; | ||||
| import "../../components/demo-cards"; | ||||
| import { mockIcons } from "../../../../demo/src/stubs/icons"; | ||||
| import { ClimateEntityFeature } from "../../../../src/data/climate"; | ||||
| import { FanEntityFeature } from "../../../../src/data/fan"; | ||||
|  | ||||
| const ENTITIES = [ | ||||
|   getEntity("switch", "tv_outlet", "on", { | ||||
| @@ -101,15 +100,6 @@ const ENTITIES = [ | ||||
|       ClimateEntityFeature.FAN_MODE + | ||||
|       ClimateEntityFeature.TARGET_TEMPERATURE_RANGE, | ||||
|   }), | ||||
|   getEntity("fan", "fan_demo", "on", { | ||||
|     friendly_name: "Ceiling fan", | ||||
|     device_class: "fan", | ||||
|     direction: "reverse", | ||||
|     supported_features: | ||||
|       FanEntityFeature.DIRECTION + | ||||
|       FanEntityFeature.SET_SPEED + | ||||
|       FanEntityFeature.OSCILLATE, | ||||
|   }), | ||||
| ]; | ||||
|  | ||||
| const CONFIGS = [ | ||||
| @@ -271,33 +261,6 @@ const CONFIGS = [ | ||||
|   - type: target-temperature | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Fan direction feature", | ||||
|     config: ` | ||||
| - type: tile | ||||
|   entity: fan.fan_demo | ||||
|   features: | ||||
|   - type: fan-direction | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Fan speed feature", | ||||
|     config: ` | ||||
| - type: tile | ||||
|   entity: fan.fan_demo | ||||
|   features: | ||||
|   - type: fan-speed | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Fan oscillate feature", | ||||
|     config: ` | ||||
| - type: tile | ||||
|   entity: fan.fan_demo | ||||
|   features: | ||||
|   - type: fan-oscillate | ||||
|     `, | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-lovelace-tile-card") | ||||
|   | ||||
| @@ -5,13 +5,13 @@ import type { | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { mockIcons } from "../../../../demo/src/stubs/icons"; | ||||
| import { computeDomain } from "../../../../src/common/entity/compute_domain"; | ||||
| import { computeStateDisplay } from "../../../../src/common/entity/compute_state_display"; | ||||
| import "../../../../src/components/data-table/ha-data-table"; | ||||
| import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table"; | ||||
| import "../../../../src/components/entity/state-badge"; | ||||
| import { provideHass } from "../../../../src/fake_data/provide_hass"; | ||||
| import { mockIcons } from "../../../../demo/src/stubs/icons"; | ||||
| import type { HomeAssistant } from "../../../../src/types"; | ||||
|  | ||||
| const SENSOR_DEVICE_CLASSES = [ | ||||
| @@ -434,7 +434,7 @@ export class DemoEntityState extends LitElement { | ||||
|       display: block; | ||||
|       height: 20px; | ||||
|       width: 20px; | ||||
|       border-radius: var(--ha-border-radius-md); | ||||
|       border-radius: 10px; | ||||
|       background-color: rgb(--color); | ||||
|     } | ||||
|   `; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import "@material/mwc-button"; | ||||
| import type { TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import type { ActionHandlerEvent } from "../../../../src/data/lovelace/action_handler"; | ||||
| import { actionHandler } from "../../../../src/panels/lovelace/common/directives/action-handler-directive"; | ||||
| @@ -13,16 +13,12 @@ export class DemoUtilLongPress extends LitElement { | ||||
|       ${[1, 2, 3].map( | ||||
|         () => html` | ||||
|           <ha-card> | ||||
|             <ha-button | ||||
|               appearance="plain" | ||||
|             <mwc-button | ||||
|               @action=${this._handleAction} | ||||
|               .actionHandler=${actionHandler({ | ||||
|                 hasHold: true, | ||||
|                 hasDoubleClick: true, | ||||
|               })} | ||||
|               .actionHandler=${actionHandler({})} | ||||
|             > | ||||
|               (long) press me! | ||||
|             </ha-button> | ||||
|             </mwc-button> | ||||
|  | ||||
|             <textarea></textarea> | ||||
|  | ||||
|   | ||||
| @@ -1,3 +0,0 @@ | ||||
| --- | ||||
| title: Fan | ||||
| --- | ||||
| @@ -1,50 +0,0 @@ | ||||
| import type { PropertyValues, TemplateResult } from "lit"; | ||||
| import { html, LitElement } from "lit"; | ||||
| import { customElement, property, query } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/dialogs/more-info/more-info-content"; | ||||
| import { getEntity } from "../../../../src/fake_data/entity"; | ||||
| import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass"; | ||||
| import { provideHass } from "../../../../src/fake_data/provide_hass"; | ||||
| import "../../components/demo-more-infos"; | ||||
| import { FanEntityFeature } from "../../../../src/data/fan"; | ||||
|  | ||||
| const ENTITIES = [ | ||||
|   getEntity("fan", "fan", "on", { | ||||
|     friendly_name: "Fan", | ||||
|     device_class: "fan", | ||||
|     supported_features: | ||||
|       FanEntityFeature.OSCILLATE + | ||||
|       FanEntityFeature.DIRECTION + | ||||
|       FanEntityFeature.SET_SPEED, | ||||
|   }), | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-more-info-fan") | ||||
| class DemoMoreInfoFan extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: MockHomeAssistant; | ||||
|  | ||||
|   @query("demo-more-infos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html` | ||||
|       <demo-more-infos | ||||
|         .hass=${this.hass} | ||||
|         .entities=${ENTITIES.map((ent) => ent.entityId)} | ||||
|       ></demo-more-infos> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "demo-more-info-fan": DemoMoreInfoFan; | ||||
|   } | ||||
| } | ||||
| @@ -11,10 +11,7 @@ import "../../../../src/components/ha-alert"; | ||||
| import "../../../../src/components/ha-button-menu"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-form/ha-form"; | ||||
| import type { | ||||
|   HaFormSchema, | ||||
|   HaFormDataContainer, | ||||
| } from "../../../../src/components/ha-form/types"; | ||||
| import type { HaFormSchema } from "../../../../src/components/ha-form/types"; | ||||
| import "../../../../src/components/ha-formfield"; | ||||
| import "../../../../src/components/ha-icon-button"; | ||||
| import "../../../../src/components/ha-list-item"; | ||||
| @@ -36,7 +33,6 @@ import { haStyle } from "../../../../src/resources/styles"; | ||||
| import type { HomeAssistant } from "../../../../src/types"; | ||||
| import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; | ||||
| import { hassioStyle } from "../../resources/hassio-style"; | ||||
| import type { ObjectSelector, Selector } from "../../../../src/data/selector"; | ||||
|  | ||||
| const SUPPORTED_UI_TYPES = [ | ||||
|   "string", | ||||
| @@ -82,125 +78,78 @@ class HassioAddonConfig extends LitElement { | ||||
|  | ||||
|   @query("ha-yaml-editor") private _editor?: HaYamlEditor; | ||||
|  | ||||
|   private _getTranslationEntry( | ||||
|     language: string, | ||||
|     entry: HaFormSchema, | ||||
|     options?: { path?: string[] } | ||||
|   ) { | ||||
|     let parent = this.addon.translations[language]?.configuration; | ||||
|     if (!parent) return undefined; | ||||
|     if (options?.path) { | ||||
|       for (const key of options.path) { | ||||
|         parent = parent[key]?.fields; | ||||
|         if (!parent) return undefined; | ||||
|       } | ||||
|     } | ||||
|     return parent[entry.name]; | ||||
|   } | ||||
|  | ||||
|   public computeLabel = ( | ||||
|     entry: HaFormSchema, | ||||
|     _data: HaFormDataContainer, | ||||
|     options?: { path?: string[] } | ||||
|   ): string => | ||||
|     this._getTranslationEntry(this.hass.language, entry, options)?.name || | ||||
|     this._getTranslationEntry("en", entry, options)?.name || | ||||
|   public computeLabel = (entry: HaFormSchema): string => | ||||
|     this.addon.translations[this.hass.language]?.configuration?.[entry.name] | ||||
|       ?.name || | ||||
|     this.addon.translations.en?.configuration?.[entry.name]?.name || | ||||
|     entry.name; | ||||
|  | ||||
|   public computeHelper = ( | ||||
|     entry: HaFormSchema, | ||||
|     options?: { path?: string[] } | ||||
|   ): string => | ||||
|     this._getTranslationEntry(this.hass.language, entry, options) | ||||
|   public computeHelper = (entry: HaFormSchema): string => | ||||
|     this.addon.translations[this.hass.language]?.configuration?.[entry.name] | ||||
|       ?.description || | ||||
|     this._getTranslationEntry("en", entry, options)?.description || | ||||
|     this.addon.translations.en?.configuration?.[entry.name]?.description || | ||||
|     ""; | ||||
|  | ||||
|   private _convertSchema = memoizeOne( | ||||
|     // Convert supervisor schema to selectors | ||||
|     (schema: readonly HaFormSchema[]): HaFormSchema[] => | ||||
|       this._convertSchemaElements(schema) | ||||
|     (schema: Record<string, any>): HaFormSchema[] => | ||||
|       schema.map((entry) => | ||||
|         entry.type === "select" | ||||
|           ? { | ||||
|               name: entry.name, | ||||
|               required: entry.required, | ||||
|               selector: { select: { options: entry.options } }, | ||||
|             } | ||||
|           : entry.type === "string" | ||||
|             ? entry.multiple | ||||
|               ? { | ||||
|                   name: entry.name, | ||||
|                   required: entry.required, | ||||
|                   selector: { | ||||
|                     select: { options: [], multiple: true, custom_value: true }, | ||||
|                   }, | ||||
|                 } | ||||
|               : { | ||||
|                   name: entry.name, | ||||
|                   required: entry.required, | ||||
|                   selector: { | ||||
|                     text: { | ||||
|                       type: entry.format | ||||
|                         ? entry.format | ||||
|                         : MASKED_FIELDS.includes(entry.name) | ||||
|                           ? "password" | ||||
|                           : "text", | ||||
|                     }, | ||||
|                   }, | ||||
|                 } | ||||
|             : entry.type === "boolean" | ||||
|               ? { | ||||
|                   name: entry.name, | ||||
|                   required: entry.required, | ||||
|                   selector: { boolean: {} }, | ||||
|                 } | ||||
|               : entry.type === "schema" | ||||
|                 ? { | ||||
|                     name: entry.name, | ||||
|                     required: entry.required, | ||||
|                     selector: { object: {} }, | ||||
|                   } | ||||
|                 : entry.type === "float" || entry.type === "integer" | ||||
|                   ? { | ||||
|                       name: entry.name, | ||||
|                       required: entry.required, | ||||
|                       selector: { | ||||
|                         number: { | ||||
|                           mode: "box", | ||||
|                           step: entry.type === "float" ? "any" : undefined, | ||||
|                         }, | ||||
|                       }, | ||||
|                     } | ||||
|                   : entry | ||||
|       ) | ||||
|   ); | ||||
|  | ||||
|   private _convertSchemaElements( | ||||
|     schema: readonly HaFormSchema[] | ||||
|   ): HaFormSchema[] { | ||||
|     return schema.map((entry) => this._convertSchemaElement(entry)); | ||||
|   } | ||||
|  | ||||
|   private _convertSchemaElement(entry: any): HaFormSchema { | ||||
|     if (entry.type === "schema" && !entry.multiple) { | ||||
|       return { | ||||
|         name: entry.name, | ||||
|         type: "expandable", | ||||
|         required: entry.required, | ||||
|         schema: this._convertSchemaElements(entry.schema), | ||||
|       }; | ||||
|     } | ||||
|     const selector = this._convertSchemaElementToSelector(entry, false); | ||||
|     if (selector) { | ||||
|       return { | ||||
|         name: entry.name, | ||||
|         required: entry.required, | ||||
|         selector, | ||||
|       }; | ||||
|     } | ||||
|     return entry; | ||||
|   } | ||||
|  | ||||
|   private _convertSchemaElementToSelector( | ||||
|     entry: any, | ||||
|     force: boolean | ||||
|   ): Selector | null { | ||||
|     if (entry.type === "select") { | ||||
|       return { select: { options: entry.options } }; | ||||
|     } | ||||
|     if (entry.type === "string") { | ||||
|       return entry.multiple | ||||
|         ? { select: { options: [], multiple: true, custom_value: true } } | ||||
|         : { | ||||
|             text: { | ||||
|               type: entry.format | ||||
|                 ? entry.format | ||||
|                 : MASKED_FIELDS.includes(entry.name) | ||||
|                   ? "password" | ||||
|                   : "text", | ||||
|             }, | ||||
|           }; | ||||
|     } | ||||
|     if (entry.type === "boolean") { | ||||
|       return { boolean: {} }; | ||||
|     } | ||||
|     if (entry.type === "schema") { | ||||
|       const fields: NonNullable<ObjectSelector["object"]>["fields"] = {}; | ||||
|       for (const child_entry of entry.schema) { | ||||
|         fields[child_entry.name] = { | ||||
|           required: child_entry.required, | ||||
|           selector: this._convertSchemaElementToSelector(child_entry, true)!, | ||||
|         }; | ||||
|       } | ||||
|       return { | ||||
|         object: { | ||||
|           multiple: entry.multiple, | ||||
|           fields, | ||||
|         }, | ||||
|       }; | ||||
|     } | ||||
|     if (entry.type === "float" || entry.type === "integer") { | ||||
|       return { | ||||
|         number: { | ||||
|           mode: "box", | ||||
|           step: entry.type === "float" ? "any" : undefined, | ||||
|         }, | ||||
|       }; | ||||
|     } | ||||
|     if (force) { | ||||
|       return { object: {} }; | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   private _filteredSchema = memoizeOne( | ||||
|   private _filteredShchema = memoizeOne( | ||||
|     (options: Record<string, unknown>, schema: HaFormSchema[]) => | ||||
|       schema.filter((entry) => entry.name in options || entry.required) | ||||
|   ); | ||||
| @@ -212,7 +161,7 @@ class HassioAddonConfig extends LitElement { | ||||
|       showForm && | ||||
|       JSON.stringify(this.addon.schema) !== | ||||
|         JSON.stringify( | ||||
|           this._filteredSchema(this.addon.options, this.addon.schema!) | ||||
|           this._filteredShchema(this.addon.options, this.addon.schema!) | ||||
|         ); | ||||
|     return html` | ||||
|       <h1>${this.addon.name}</h1> | ||||
| @@ -250,7 +199,6 @@ class HassioAddonConfig extends LitElement { | ||||
|         <div class="card-content"> | ||||
|           ${showForm | ||||
|             ? html`<ha-form | ||||
|                 .hass=${this.hass} | ||||
|                 .disabled=${this.disabled} | ||||
|                 .data=${this._options!} | ||||
|                 @value-changed=${this._configChanged} | ||||
| @@ -259,7 +207,7 @@ class HassioAddonConfig extends LitElement { | ||||
|                 .schema=${this._convertSchema( | ||||
|                   this._showOptional | ||||
|                     ? this.addon.schema! | ||||
|                     : this._filteredSchema( | ||||
|                     : this._filteredShchema( | ||||
|                         this.addon.options, | ||||
|                         this.addon.schema! | ||||
|                       ) | ||||
|   | ||||
| @@ -99,8 +99,7 @@ class HassioAddonNetwork extends LitElement { | ||||
|           : nothing} | ||||
|         <div class="card-actions"> | ||||
|           <ha-progress-button | ||||
|             variant="danger" | ||||
|             appearance="plain" | ||||
|             class="warning" | ||||
|             .disabled=${this.disabled} | ||||
|             @click=${this._resetTapped} | ||||
|           > | ||||
|   | ||||
| @@ -25,7 +25,6 @@ import type { CSSResultGroup, TemplateResult } from "lit"; | ||||
| import { LitElement, css, html, nothing } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import { classMap } from "lit/directives/class-map"; | ||||
| import { ifDefined } from "lit/directives/if-defined"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { atLeastVersion } from "../../../../src/common/config/version"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| @@ -188,13 +187,12 @@ class HassioAddonInfo extends LitElement { | ||||
|                 "addon.dashboard.protection_mode.content" | ||||
|               )} | ||||
|               <ha-button | ||||
|                 variant="danger" | ||||
|                 slot="action" | ||||
|                 @click=${this._protectionToggled} | ||||
|               > | ||||
|                 ${this.supervisor.localize( | ||||
|                 .label=${this.supervisor.localize( | ||||
|                   "addon.dashboard.protection_mode.enable" | ||||
|                 )} | ||||
|                 @click=${this._protectionToggled} | ||||
|               > | ||||
|               </ha-button> | ||||
|             </ha-alert> | ||||
|           ` | ||||
| @@ -694,16 +692,14 @@ class HassioAddonInfo extends LitElement { | ||||
|               ? this._computeIsRunning | ||||
|                 ? html` | ||||
|                     <ha-progress-button | ||||
|                       variant="danger" | ||||
|                       appearance="plain" | ||||
|                       class="warning" | ||||
|                       @click=${this._stopClicked} | ||||
|                       .disabled=${systemManaged && !this.controlEnabled} | ||||
|                     > | ||||
|                       ${this.supervisor.localize("addon.dashboard.stop")} | ||||
|                     </ha-progress-button> | ||||
|                     <ha-progress-button | ||||
|                       variant="danger" | ||||
|                       appearance="plain" | ||||
|                       class="warning" | ||||
|                       @click=${this._restartClicked} | ||||
|                     > | ||||
|                       ${this.supervisor.localize("addon.dashboard.restart")} | ||||
| @@ -713,60 +709,10 @@ class HassioAddonInfo extends LitElement { | ||||
|                     <ha-progress-button | ||||
|                       @click=${this._startClicked} | ||||
|                       .progress=${this.addon.state === "startup"} | ||||
|                       appearance="plain" | ||||
|                     > | ||||
|                       ${this.supervisor.localize("addon.dashboard.start")} | ||||
|                     </ha-progress-button> | ||||
|                   ` | ||||
|               : nothing} | ||||
|           </div> | ||||
|           <div> | ||||
|             ${this.addon.version | ||||
|               ? html` | ||||
|                   <ha-progress-button | ||||
|                     variant="danger" | ||||
|                     appearance="plain" | ||||
|                     @click=${this._uninstallClicked} | ||||
|                     .disabled=${systemManaged && !this.controlEnabled} | ||||
|                   > | ||||
|                     ${this.supervisor.localize("addon.dashboard.uninstall")} | ||||
|                   </ha-progress-button> | ||||
|                   ${this.addon.build | ||||
|                     ? html` | ||||
|                         <ha-progress-button | ||||
|                           variant="danger" | ||||
|                           appearance="plain" | ||||
|                           @click=${this._rebuildClicked} | ||||
|                         > | ||||
|                           ${this.supervisor.localize("addon.dashboard.rebuild")} | ||||
|                         </ha-progress-button> | ||||
|                       ` | ||||
|                     : nothing} | ||||
|                   ${this._computeShowWebUI || this._computeShowIngressUI | ||||
|                     ? html` | ||||
|                         <ha-button | ||||
|                           href=${ifDefined( | ||||
|                             !this._computeShowIngressUI | ||||
|                               ? this._pathWebui! | ||||
|                               : nothing | ||||
|                           )} | ||||
|                           target=${ifDefined( | ||||
|                             !this._computeShowIngressUI ? "_blank" : nothing | ||||
|                           )} | ||||
|                           rel=${ifDefined( | ||||
|                             !this._computeShowIngressUI ? "noopener" : nothing | ||||
|                           )} | ||||
|                           @click=${!this._computeShowWebUI | ||||
|                             ? this._openIngress | ||||
|                             : undefined} | ||||
|                         > | ||||
|                           ${this.supervisor.localize( | ||||
|                             "addon.dashboard.open_web_ui" | ||||
|                           )} | ||||
|                         </ha-button> | ||||
|                       ` | ||||
|                     : nothing} | ||||
|                 ` | ||||
|               : html` | ||||
|                   <ha-progress-button | ||||
|                     .disabled=${!this.addon.available} | ||||
| @@ -776,12 +722,58 @@ class HassioAddonInfo extends LitElement { | ||||
|                   </ha-progress-button> | ||||
|                 `} | ||||
|           </div> | ||||
|           <div> | ||||
|             ${this.addon.version | ||||
|               ? html` ${this._computeShowWebUI | ||||
|                     ? html` | ||||
|                         <a | ||||
|                           href=${this._pathWebui!} | ||||
|                           tabindex="-1" | ||||
|                           target="_blank" | ||||
|                           rel="noopener" | ||||
|                         > | ||||
|                           <ha-button> | ||||
|                             ${this.supervisor.localize( | ||||
|                               "addon.dashboard.open_web_ui" | ||||
|                             )} | ||||
|                           </ha-button> | ||||
|                         </a> | ||||
|                       ` | ||||
|                     : nothing} | ||||
|                   ${this._computeShowIngressUI | ||||
|                     ? html` | ||||
|                         <ha-button @click=${this._openIngress}> | ||||
|                           ${this.supervisor.localize( | ||||
|                             "addon.dashboard.open_web_ui" | ||||
|                           )} | ||||
|                         </ha-button> | ||||
|                       ` | ||||
|                     : nothing} | ||||
|                   <ha-progress-button | ||||
|                     class="warning" | ||||
|                     @click=${this._uninstallClicked} | ||||
|                     .disabled=${systemManaged && !this.controlEnabled} | ||||
|                   > | ||||
|                     ${this.supervisor.localize("addon.dashboard.uninstall")} | ||||
|                   </ha-progress-button> | ||||
|                   ${this.addon.build | ||||
|                     ? html` | ||||
|                         <ha-progress-button | ||||
|                           class="warning" | ||||
|                           @click=${this._rebuildClicked} | ||||
|                         > | ||||
|                           ${this.supervisor.localize("addon.dashboard.rebuild")} | ||||
|                         </ha-progress-button> | ||||
|                       ` | ||||
|                     : nothing}` | ||||
|               : nothing} | ||||
|           </div> | ||||
|         </div> | ||||
|       </ha-card> | ||||
|  | ||||
|       ${this.addon.long_description | ||||
|         ? html` | ||||
|             <ha-card class="long-description" outlined> | ||||
|             <ha-card outlined> | ||||
|               <div class="card-content"> | ||||
|                 <ha-markdown | ||||
|                   .content=${this.addon.long_description} | ||||
| @@ -1154,17 +1146,15 @@ class HassioAddonInfo extends LitElement { | ||||
|           ), | ||||
|           dismissText: this.supervisor.localize("common.cancel"), | ||||
|         }); | ||||
|         button.actionError(); | ||||
|         button.progress = false; | ||||
|         return; | ||||
|       } | ||||
|     } catch (err: any) { | ||||
|       button.actionError(); | ||||
|       button.progress = false; | ||||
|       showAlertDialog(this, { | ||||
|         title: "Failed to validate addon configuration", | ||||
|         text: extractApiErrorMessage(err), | ||||
|       }); | ||||
|       button.progress = false; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
| @@ -1178,15 +1168,11 @@ class HassioAddonInfo extends LitElement { | ||||
|       }; | ||||
|       fireEvent(this, "hass-api-called", eventdata); | ||||
|     } catch (err: any) { | ||||
|       button.actionError(); | ||||
|       button.progress = false; | ||||
|       showAlertDialog(this, { | ||||
|         title: this.supervisor.localize("addon.dashboard.action_error.start"), | ||||
|         text: extractApiErrorMessage(err), | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|     button.actionSuccess(); | ||||
|     button.progress = false; | ||||
|   } | ||||
|  | ||||
| @@ -1242,7 +1228,6 @@ class HassioAddonInfo extends LitElement { | ||||
|         path: "uninstall", | ||||
|       }; | ||||
|       fireEvent(this, "hass-api-called", eventdata); | ||||
|       button.actionSuccess(); | ||||
|     } catch (err: any) { | ||||
|       showAlertDialog(this, { | ||||
|         title: this.supervisor.localize( | ||||
| @@ -1250,7 +1235,6 @@ class HassioAddonInfo extends LitElement { | ||||
|         ), | ||||
|         text: extractApiErrorMessage(err), | ||||
|       }); | ||||
|       button.actionError(); | ||||
|     } | ||||
|     button.progress = false; | ||||
|   } | ||||
| @@ -1333,9 +1317,6 @@ class HassioAddonInfo extends LitElement { | ||||
|         .description a { | ||||
|           color: var(--primary-color); | ||||
|         } | ||||
|         .long-description { | ||||
|           direction: ltr; | ||||
|         } | ||||
|         ha-assist-chip { | ||||
|           --md-sys-color-primary: var(--text-primary-color); | ||||
|           --md-sys-color-on-surface: var(--text-primary-color); | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import "@material/mwc-button"; | ||||
| import type { TemplateResult } from "lit"; | ||||
| import { LitElement, css, html, nothing } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import "@material/mwc-button"; | ||||
| import type { ActionDetail } from "@material/mwc-list"; | ||||
|  | ||||
| import { mdiBackupRestore, mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js"; | ||||
| @@ -16,7 +17,6 @@ import type { | ||||
| } from "../../../src/components/data-table/ha-data-table"; | ||||
| import "../../../src/components/ha-button-menu"; | ||||
| import "../../../src/components/ha-fab"; | ||||
| import "../../../src/components/ha-button"; | ||||
| import "../../../src/components/ha-icon-button"; | ||||
| import "../../../src/components/ha-list-item"; | ||||
| import "../../../src/components/ha-svg-icon"; | ||||
| @@ -241,13 +241,12 @@ export class HassioBackups extends LitElement { | ||||
|               <div class="header-btns"> | ||||
|                 ${!this.narrow | ||||
|                   ? html` | ||||
|                       <ha-button | ||||
|                         appearance="plain" | ||||
|                         variant="danger" | ||||
|                       <mwc-button | ||||
|                         @click=${this._deleteSelected} | ||||
|                         class="warning" | ||||
|                       > | ||||
|                         ${this.supervisor.localize("backup.delete_selected")} | ||||
|                       </ha-button> | ||||
|                       </mwc-button> | ||||
|                     ` | ||||
|                   : html` | ||||
|                       <ha-icon-button | ||||
| @@ -409,7 +408,7 @@ export class HassioBackups extends LitElement { | ||||
|           margin-inline-end: -12px; | ||||
|           margin-inline-start: initial; | ||||
|         } | ||||
|         .header-btns > ha-button, | ||||
|         .header-btns > mwc-button, | ||||
|         .header-btns > ha-icon-button { | ||||
|           margin: 8px; | ||||
|         } | ||||
|   | ||||
| @@ -121,7 +121,7 @@ class HassioCardContent extends LitElement { | ||||
|       height: 12px; | ||||
|       top: 8px; | ||||
|       right: 8px; | ||||
|       border-radius: var(--ha-border-radius-circle); | ||||
|       border-radius: 50%; | ||||
|     } | ||||
|     .topbar { | ||||
|       position: absolute; | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| import "@material/mwc-button"; | ||||
| import type { CSSResultGroup } from "lit"; | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import "../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import "../../../src/components/ha-button"; | ||||
| import "../../../src/components/ha-settings-row"; | ||||
| import "../../../src/components/ha-svg-icon"; | ||||
| import type { HassioHassOSInfo } from "../../../src/data/hassio/host"; | ||||
| @@ -108,9 +109,10 @@ export class HassioUpdate extends LitElement { | ||||
|           </ha-settings-row> | ||||
|         </div> | ||||
|         <div class="card-actions"> | ||||
|           <ha-button appearance="plain" href="/hassio/update-available/${key}"> | ||||
|             ${this.supervisor.localize("common.show")} | ||||
|           </ha-button> | ||||
|           <a href="/hassio/update-available/${key}"> | ||||
|             <mwc-button .label=${this.supervisor.localize("common.show")}> | ||||
|             </mwc-button> | ||||
|           </a> | ||||
|         </div> | ||||
|       </ha-card> | ||||
|     `; | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import "@material/mwc-button/mwc-button"; | ||||
| import type { CSSResultGroup } from "lit"; | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import "../../../../src/components/ha-dialog"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-form/ha-form"; | ||||
| import type { SchemaUnion } from "../../../../src/components/ha-form/types"; | ||||
| import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; | ||||
| @@ -77,21 +77,20 @@ class HassioBackupLocationDialog extends LitElement { | ||||
|           @value-changed=${this._valueChanged} | ||||
|           dialogInitialFocus | ||||
|         ></ha-form> | ||||
|         <ha-button | ||||
|           appearance="plain" | ||||
|         <mwc-button | ||||
|           slot="secondaryAction" | ||||
|           @click=${this.closeDialog} | ||||
|           dialogInitialFocus | ||||
|         > | ||||
|           ${this._dialogParams.supervisor.localize("common.cancel")} | ||||
|         </ha-button> | ||||
|         <ha-button | ||||
|         </mwc-button> | ||||
|         <mwc-button | ||||
|           .disabled=${this._waiting || !this._data} | ||||
|           slot="primaryAction" | ||||
|           @click=${this._changeMount} | ||||
|         > | ||||
|           ${this._dialogParams.supervisor.localize("common.save")} | ||||
|         </ha-button> | ||||
|         </mwc-button> | ||||
|       </ha-dialog> | ||||
|     `; | ||||
|   } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import { atLeastVersion } from "../../../../src/common/config/version"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import { stopPropagation } from "../../../../src/common/dom/stop_propagation"; | ||||
| import { slugify } from "../../../../src/common/string/slugify"; | ||||
| import "../../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../../src/components/ha-alert"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-button-menu"; | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| import "@material/mwc-button"; | ||||
| import type { CSSResultGroup } from "lit"; | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| import { customElement, property, query, state } from "lit/decorators"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import "../../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../../src/components/ha-alert"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-spinner"; | ||||
| import { createCloseHeading } from "../../../../src/components/ha-dialog"; | ||||
| import { | ||||
| @@ -68,20 +69,16 @@ class HassioCreateBackupDialog extends LitElement { | ||||
|         ${this._error | ||||
|           ? html`<ha-alert alert-type="error">${this._error}</ha-alert>` | ||||
|           : ""} | ||||
|         <ha-button | ||||
|           appearance="plain" | ||||
|           slot="secondaryAction" | ||||
|           @click=${this.closeDialog} | ||||
|         > | ||||
|         <mwc-button slot="secondaryAction" @click=${this.closeDialog}> | ||||
|           ${this._dialogParams.supervisor.localize("common.close")} | ||||
|         </ha-button> | ||||
|         <ha-button | ||||
|         </mwc-button> | ||||
|         <mwc-button | ||||
|           .disabled=${this._creatingBackup} | ||||
|           slot="primaryAction" | ||||
|           @click=${this._createBackup} | ||||
|         > | ||||
|           ${this._dialogParams.supervisor.localize("backup.create")} | ||||
|         </ha-button> | ||||
|         </mwc-button> | ||||
|       </ha-dialog> | ||||
|     `; | ||||
|   } | ||||
|   | ||||
| @@ -4,7 +4,6 @@ import { customElement, property, state } from "lit/decorators"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import "../../../../src/components/ha-dialog"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-list-item"; | ||||
| import "../../../../src/components/ha-select"; | ||||
| import "../../../../src/components/ha-spinner"; | ||||
| @@ -21,8 +20,8 @@ import type { HomeAssistant } from "../../../../src/types"; | ||||
| import type { HassioDatatiskDialogParams } from "./show-dialog-hassio-datadisk"; | ||||
|  | ||||
| const calculateMoveTime = memoizeOne((supervisor: Supervisor): number => { | ||||
|   // Assume a speed of 30 MB/s. | ||||
|   const moveTime = (supervisor.host.disk_used * 1000) / 60 / 30; | ||||
|   const speed = supervisor.host.disk_life_time !== "" ? 30 : 10; | ||||
|   const moveTime = (supervisor.host.disk_used * 1000) / 60 / speed; | ||||
|   const rebootTime = (supervisor.host.startup_time * 4) / 60; | ||||
|   return Math.ceil((moveTime + rebootTime) / 10) * 10; | ||||
| }); | ||||
| @@ -110,18 +109,17 @@ class HassioDatadiskDialog extends LitElement { | ||||
|                       "dialog.datadisk_move.no_devices" | ||||
|                     )} | ||||
|  | ||||
|               <ha-button | ||||
|                 appearance="plain" | ||||
|                 slot="primaryAction" | ||||
|               <mwc-button | ||||
|                 slot="secondaryAction" | ||||
|                 @click=${this.closeDialog} | ||||
|                 dialogInitialFocus | ||||
|               > | ||||
|                 ${this.dialogParams.supervisor.localize( | ||||
|                   "dialog.datadisk_move.cancel" | ||||
|                 )} | ||||
|               </ha-button> | ||||
|               </mwc-button> | ||||
|  | ||||
|               <ha-button | ||||
|               <mwc-button | ||||
|                 .disabled=${!this.selectedDevice} | ||||
|                 slot="primaryAction" | ||||
|                 @click=${this._moveDatadisk} | ||||
| @@ -129,7 +127,7 @@ class HassioDatadiskDialog extends LitElement { | ||||
|                 ${this.dialogParams.supervisor.localize( | ||||
|                   "dialog.datadisk_move.move" | ||||
|                 )} | ||||
|               </ha-button>`} | ||||
|               </mwc-button>`} | ||||
|       </ha-dialog> | ||||
|     `; | ||||
|   } | ||||
|   | ||||
| @@ -164,7 +164,7 @@ class HassioHardwareDialog extends LitElement { | ||||
|         pre, | ||||
|         code { | ||||
|           background-color: var(--markdown-code-background-color, none); | ||||
|           border-radius: var(--ha-border-radius-sm); | ||||
|           border-radius: 3px; | ||||
|         } | ||||
|         pre { | ||||
|           padding: 16px; | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import "@material/mwc-button/mwc-button"; | ||||
| import { mdiClose } from "@mdi/js"; | ||||
| import type { CSSResultGroup } from "lit"; | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| @@ -5,7 +6,6 @@ import { customElement, property, state } from "lit/decorators"; | ||||
| import { cache } from "lit/directives/cache"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import "../../../../src/components/ha-alert"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-dialog"; | ||||
| import "../../../../src/components/ha-expansion-panel"; | ||||
| import "../../../../src/components/ha-formfield"; | ||||
| @@ -15,8 +15,7 @@ import "../../../../src/components/ha-list"; | ||||
| import "../../../../src/components/ha-list-item"; | ||||
| import "../../../../src/components/ha-password-field"; | ||||
| import "../../../../src/components/ha-radio"; | ||||
| import "../../../../src/components/ha-tab-group"; | ||||
| import "../../../../src/components/ha-tab-group-tab"; | ||||
| import "../../../../src/components/ha-spinner"; | ||||
| import "../../../../src/components/ha-textfield"; | ||||
| import type { HaTextField } from "../../../../src/components/ha-textfield"; | ||||
| import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; | ||||
| @@ -38,6 +37,7 @@ import type { HassDialog } from "../../../../src/dialogs/make-dialog-manager"; | ||||
| import { haStyleDialog } from "../../../../src/resources/styles"; | ||||
| import type { HomeAssistant } from "../../../../src/types"; | ||||
| import type { HassioNetworkDialogParams } from "./show-dialog-network"; | ||||
| import "../../../../src/components/sl-tab-group"; | ||||
|  | ||||
| const IP_VERSIONS = ["ipv4", "ipv6"]; | ||||
|  | ||||
| @@ -115,19 +115,19 @@ export class DialogHassioNetwork | ||||
|             ></ha-icon-button> | ||||
|           </ha-header-bar> | ||||
|           ${this._interfaces.length > 1 | ||||
|             ? html`<ha-tab-group @wa-tab-show=${this._handleTabActivated} | ||||
|             ? html`<sl-tab-group @sl-tab-show=${this._handleTabActivated} | ||||
|                 >${this._interfaces.map( | ||||
|                   (device, index) => | ||||
|                     html`<ha-tab-group-tab | ||||
|                     html`<sl-tab | ||||
|                       slot="nav" | ||||
|                       .id=${device.interface} | ||||
|                       .panel=${index.toString()} | ||||
|                       .active=${this._curTabIndex === index} | ||||
|                     > | ||||
|                       ${device.interface} | ||||
|                     </ha-tab-group-tab>` | ||||
|                     </sl-tab>` | ||||
|                 )} | ||||
|               </ha-tab-group>` | ||||
|               </sl-tab-group>` | ||||
|             : ""} | ||||
|         </div> | ||||
|         ${cache(this._renderTab())} | ||||
| @@ -154,16 +154,16 @@ export class DialogHassioNetwork | ||||
|                       )} | ||||
|                     </p>` | ||||
|                   : ""} | ||||
|                 <ha-button | ||||
|                   appearance="plain" | ||||
|                   size="small" | ||||
|                 <mwc-button | ||||
|                   class="scan" | ||||
|                   @click=${this._scanForAP} | ||||
|                   .disabled=${this._scanning} | ||||
|                   .loading=${this._scanning} | ||||
|                 > | ||||
|                   ${this.supervisor.localize("dialog.network.scan_ap")} | ||||
|                 </ha-button> | ||||
|                   ${this._scanning | ||||
|                     ? html`<ha-spinner aria-label="Scanning" size="small"> | ||||
|                       </ha-spinner>` | ||||
|                     : this.supervisor.localize("dialog.network.scan_ap")} | ||||
|                 </mwc-button> | ||||
|                 ${this._accessPoints && | ||||
|                 this._accessPoints.accesspoints && | ||||
|                 this._accessPoints.accesspoints.length !== 0 | ||||
| @@ -270,16 +270,16 @@ export class DialogHassioNetwork | ||||
|           : ""} | ||||
|       </div> | ||||
|       <div class="buttons"> | ||||
|         <ha-button @click=${this.closeDialog} appearance="plain"> | ||||
|           ${this.supervisor.localize("common.cancel")} | ||||
|         </ha-button> | ||||
|         <ha-button | ||||
|           @click=${this._updateNetwork} | ||||
|           .disabled=${!this._dirty} | ||||
|           .loading=${this._processing} | ||||
|         <mwc-button | ||||
|           .label=${this.supervisor.localize("common.cancel")} | ||||
|           @click=${this.closeDialog} | ||||
|         > | ||||
|           ${this.supervisor.localize("common.save")} | ||||
|         </ha-button> | ||||
|         </mwc-button> | ||||
|         <mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}> | ||||
|           ${this._processing | ||||
|             ? html`<ha-spinner size="small"> </ha-spinner>` | ||||
|             : this.supervisor.localize("common.save")} | ||||
|         </mwc-button> | ||||
|       </div>`; | ||||
|   } | ||||
|  | ||||
| @@ -584,7 +584,11 @@ export class DialogHassioNetwork | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         ha-button.scan { | ||||
|         mwc-button.warning { | ||||
|           --mdc-theme-primary: var(--error-color); | ||||
|         } | ||||
|  | ||||
|         mwc-button.scan { | ||||
|           margin-left: 8px; | ||||
|           margin-inline-start: 8px; | ||||
|           margin-inline-end: initial; | ||||
| @@ -605,8 +609,8 @@ export class DialogHassioNetwork | ||||
|             var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); | ||||
|           display: flex; | ||||
|           justify-content: space-between; | ||||
|           padding: 16px; | ||||
|           padding-bottom: max(var(--safe-area-inset-bottom), 16px); | ||||
|           padding: 8px; | ||||
|           padding-bottom: max(var(--safe-area-inset-bottom), 8px); | ||||
|           background-color: var(--mdc-theme-surface, #fff); | ||||
|         } | ||||
|         .warning { | ||||
| @@ -628,10 +632,10 @@ export class DialogHassioNetwork | ||||
|           --mdc-list-side-padding: 10px; | ||||
|         } | ||||
|  | ||||
|         ha-tab-group-tab { | ||||
|         sl-tab { | ||||
|           flex: 1; | ||||
|         } | ||||
|         ha-tab-group-tab::part(base) { | ||||
|         sl-tab::part(base) { | ||||
|           width: 100%; | ||||
|           justify-content: center; | ||||
|         } | ||||
|   | ||||
| @@ -1,14 +1,13 @@ | ||||
| import { mdiDelete, mdiPlus } from "@mdi/js"; | ||||
| import "@material/mwc-button/mwc-button"; | ||||
| import { mdiDelete } from "@mdi/js"; | ||||
| import type { CSSResultGroup, TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import { createCloseHeading } from "../../../../src/components/ha-dialog"; | ||||
| import "../../../../src/components/ha-form/ha-form"; | ||||
| import type { SchemaUnion } from "../../../../src/components/ha-form/types"; | ||||
| import "../../../../src/components/ha-icon-button"; | ||||
| import "../../../../src/components/ha-settings-row"; | ||||
| import "../../../../src/components/ha-svg-icon"; | ||||
| import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; | ||||
| import { | ||||
|   addHassioDockerRegistry, | ||||
| @@ -85,19 +84,16 @@ class HassioRegistriesDialog extends LitElement { | ||||
|                 dialogInitialFocus | ||||
|               ></ha-form> | ||||
|               <div class="action"> | ||||
|                 <ha-button | ||||
|                 <mwc-button | ||||
|                   ?disabled=${Boolean( | ||||
|                     !this._input.registry || | ||||
|                       !this._input.username || | ||||
|                       !this._input.password | ||||
|                   )} | ||||
|                   @click=${this._addNewRegistry} | ||||
|                   appearance="filled" | ||||
|                   size="small" | ||||
|                 > | ||||
|                   <ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon> | ||||
|                   ${this.supervisor.localize("dialog.registries.add_registry")} | ||||
|                 </ha-button> | ||||
|                 </mwc-button> | ||||
|               </div> | ||||
|             ` | ||||
|           : html`${this._registries?.length | ||||
| @@ -130,17 +126,11 @@ class HassioRegistriesDialog extends LitElement { | ||||
|                     </ha-alert> | ||||
|                   `} | ||||
|               <div class="action"> | ||||
|                 <ha-button | ||||
|                   @click=${this._addRegistry} | ||||
|                   dialogInitialFocus | ||||
|                   appearance="filled" | ||||
|                   size="small" | ||||
|                 > | ||||
|                   <ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon> | ||||
|                 <mwc-button @click=${this._addRegistry} dialogInitialFocus> | ||||
|                   ${this.supervisor.localize( | ||||
|                     "dialog.registries.add_new_registry" | ||||
|                   )} | ||||
|                 </ha-button> | ||||
|                 </mwc-button> | ||||
|               </div> `} | ||||
|       </ha-dialog> | ||||
|     `; | ||||
| @@ -228,7 +218,7 @@ class HassioRegistriesDialog extends LitElement { | ||||
|       css` | ||||
|         .registry { | ||||
|           border: 1px solid var(--divider-color); | ||||
|           border-radius: var(--ha-border-radius-sm); | ||||
|           border-radius: 4px; | ||||
|           margin-top: 4px; | ||||
|         } | ||||
|         .action { | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import { mdiDelete, mdiDeleteOff, mdiPlus } from "@mdi/js"; | ||||
| import "@material/mwc-button/mwc-button"; | ||||
| import { mdiDelete, mdiDeleteOff } from "@mdi/js"; | ||||
| import type { CSSResultGroup } from "lit"; | ||||
| import { css, html, LitElement, nothing } from "lit"; | ||||
| import { customElement, property, query, state } from "lit/decorators"; | ||||
| @@ -6,15 +7,10 @@ import memoizeOne from "memoize-one"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import { caseInsensitiveStringCompare } from "../../../../src/common/string/compare"; | ||||
| import "../../../../src/components/ha-alert"; | ||||
| import "../../../../src/components/ha-button"; | ||||
| import "../../../../src/components/ha-tooltip"; | ||||
| import "../../../../src/components/ha-spinner"; | ||||
| import { createCloseHeading } from "../../../../src/components/ha-dialog"; | ||||
| import "../../../../src/components/ha-icon-button"; | ||||
| import "../../../../src/components/ha-md-list"; | ||||
| import "../../../../src/components/ha-md-list-item"; | ||||
| import "../../../../src/components/ha-svg-icon"; | ||||
| import "../../../../src/components/ha-textfield"; | ||||
| import type { HaTextField } from "../../../../src/components/ha-textfield"; | ||||
| import "../../../../src/components/ha-tooltip"; | ||||
| import type { | ||||
|   HassioAddonInfo, | ||||
|   HassioAddonRepository, | ||||
| @@ -28,6 +24,10 @@ import { | ||||
| import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; | ||||
| import type { HomeAssistant } from "../../../../src/types"; | ||||
| import type { HassioRepositoryDialogParams } from "./show-dialog-repositories"; | ||||
| import type { HaTextField } from "../../../../src/components/ha-textfield"; | ||||
| import "../../../../src/components/ha-textfield"; | ||||
| import "../../../../src/components/ha-md-list"; | ||||
| import "../../../../src/components/ha-md-list-item"; | ||||
|  | ||||
| @customElement("dialog-hassio-repositories") | ||||
| class HassioRepositoriesDialog extends LitElement { | ||||
| @@ -119,27 +119,26 @@ class HassioRepositoriesDialog extends LitElement { | ||||
|                         <div>${repo.url}</div> | ||||
|                       </div> | ||||
|                       <ha-tooltip | ||||
|                         .for="icon-button-${repo.slug}" | ||||
|                         class="delete" | ||||
|                         slot="end" | ||||
|                       > | ||||
|                         ${this._dialogParams!.supervisor.localize( | ||||
|                         .content=${this._dialogParams!.supervisor.localize( | ||||
|                           usedRepositories.includes(repo.slug) | ||||
|                             ? "dialog.repositories.used" | ||||
|                             : "dialog.repositories.remove" | ||||
|                         )} | ||||
|                       > | ||||
|                         <div> | ||||
|                           <ha-icon-button | ||||
|                             .disabled=${usedRepositories.includes(repo.slug)} | ||||
|                             .slug=${repo.slug} | ||||
|                             .path=${usedRepositories.includes(repo.slug) | ||||
|                               ? mdiDeleteOff | ||||
|                               : mdiDelete} | ||||
|                             @click=${this._removeRepository} | ||||
|                           > | ||||
|                           </ha-icon-button> | ||||
|                         </div> | ||||
|                       </ha-tooltip> | ||||
|                       <div .id="icon-button-${repo.slug}"> | ||||
|                         <ha-icon-button | ||||
|                           .disabled=${usedRepositories.includes(repo.slug)} | ||||
|                           .slug=${repo.slug} | ||||
|                           .path=${usedRepositories.includes(repo.slug) | ||||
|                             ? mdiDeleteOff | ||||
|                             : mdiDelete} | ||||
|                           @click=${this._removeRepository} | ||||
|                         > | ||||
|                         </ha-icon-button> | ||||
|                       </div> | ||||
|                     </ha-md-list-item> | ||||
|                   ` | ||||
|                 ) | ||||
| @@ -160,22 +159,18 @@ class HassioRepositoriesDialog extends LitElement { | ||||
|               @keydown=${this._handleKeyAdd} | ||||
|               dialogInitialFocus | ||||
|             ></ha-textfield> | ||||
|             <ha-button | ||||
|               .loading=${this._processing} | ||||
|               @click=${this._addRepository} | ||||
|               appearance="filled" | ||||
|               size="small" | ||||
|             > | ||||
|               <ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon> | ||||
|               ${this._dialogParams!.supervisor.localize( | ||||
|                 "dialog.repositories.add" | ||||
|               )} | ||||
|             </ha-button> | ||||
|             <mwc-button @click=${this._addRepository}> | ||||
|               ${this._processing | ||||
|                 ? html`<ha-spinner size="small"></ha-spinner>` | ||||
|                 : this._dialogParams!.supervisor.localize( | ||||
|                     "dialog.repositories.add" | ||||
|                   )} | ||||
|             </mwc-button> | ||||
|           </div> | ||||
|         </div> | ||||
|         <ha-button slot="primaryAction" @click=${this.closeDialog}> | ||||
|         <mwc-button slot="primaryAction" @click=${this.closeDialog}> | ||||
|           ${this._dialogParams?.supervisor.localize("common.close")} | ||||
|         </ha-button> | ||||
|         </mwc-button> | ||||
|       </ha-dialog> | ||||
|     `; | ||||
|   } | ||||
| @@ -193,14 +188,19 @@ class HassioRepositoriesDialog extends LitElement { | ||||
|         } | ||||
|         .option { | ||||
|           border: 1px solid var(--divider-color); | ||||
|           border-radius: var(--ha-border-radius-sm); | ||||
|           border-radius: 4px; | ||||
|           margin-top: 4px; | ||||
|         } | ||||
|         ha-button { | ||||
|         mwc-button { | ||||
|           margin-left: 8px; | ||||
|           margin-inline-start: 8px; | ||||
|           margin-inline-end: initial; | ||||
|         } | ||||
|         ha-spinner { | ||||
|           display: block; | ||||
|           margin: 32px; | ||||
|           text-align: center; | ||||
|         } | ||||
|         div.delete ha-icon-button { | ||||
|           color: var(--error-color); | ||||
|         } | ||||
| @@ -249,8 +249,6 @@ class HassioRepositoriesDialog extends LitElement { | ||||
|       await addStoreRepository(this.hass, input.value); | ||||
|       await this._loadData(); | ||||
|  | ||||
|       fireEvent(this, "supervisor-collection-refresh", { collection: "store" }); | ||||
|  | ||||
|       input.value = ""; | ||||
|     } catch (err: any) { | ||||
|       this._error = extractApiErrorMessage(err); | ||||
| @@ -263,8 +261,6 @@ class HassioRepositoriesDialog extends LitElement { | ||||
|     try { | ||||
|       await removeStoreRepository(this.hass, slug); | ||||
|       await this._loadData(); | ||||
|  | ||||
|       fireEvent(this, "supervisor-collection-refresh", { collection: "store" }); | ||||
|     } catch (err: any) { | ||||
|       this._error = extractApiErrorMessage(err); | ||||
|     } | ||||
|   | ||||
| @@ -159,7 +159,7 @@ class HassioSystemManagedDialog extends LitElement { | ||||
|           display: flex; | ||||
|           justify-content: center; | ||||
|           align-items: center; | ||||
|           gap: var(--ha-space-4); | ||||
|           gap: 16px; | ||||
|           --mdc-icon-size: 48px; | ||||
|           margin-bottom: 32px; | ||||
|         } | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import type { PropertyValues, TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import { fireEvent } from "../../../src/common/dom/fire_event"; | ||||
| import { goBack, navigate } from "../../../src/common/navigate"; | ||||
| import { navigate } from "../../../src/common/navigate"; | ||||
| import { extractSearchParam } from "../../../src/common/url/search-params"; | ||||
| import { nextRender } from "../../../src/common/util/render-status"; | ||||
| import "../../../src/components/ha-icon-button"; | ||||
| @@ -193,7 +193,7 @@ class HassioIngressView extends LitElement { | ||||
|         title: addon.name, | ||||
|       }); | ||||
|       await nextRender(); | ||||
|       goBack(); | ||||
|       history.back(); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
| @@ -275,7 +275,7 @@ class HassioIngressView extends LitElement { | ||||
|         title: addon.name, | ||||
|       }); | ||||
|       await nextRender(); | ||||
|       goBack(); | ||||
|       history.back(); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -31,7 +31,7 @@ export const hassioStyle = css` | ||||
|   .card-group { | ||||
|     display: grid; | ||||
|     grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | ||||
|     grid-gap: var(--ha-space-2); | ||||
|     grid-gap: 8px; | ||||
|   } | ||||
|   @media screen and (min-width: 640px) { | ||||
|     .card-group { | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| import "@material/mwc-button"; | ||||
|  | ||||
| import type { CSSResultGroup, TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
| import { atLeastVersion } from "../../../src/common/config/version"; | ||||
| import "../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../src/components/ha-button"; | ||||
| import "../../../src/components/ha-button-menu"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import "../../../src/components/ha-settings-row"; | ||||
| @@ -69,12 +70,12 @@ class HassioCoreInfo extends LitElement { | ||||
|               ${!atLeastVersion(this.hass.config.version, 2021, 12) && | ||||
|               this.supervisor.core.update_available | ||||
|                 ? html` | ||||
|                     <ha-button | ||||
|                       appearance="plain" | ||||
|                       href="/hassio/update-available/core" | ||||
|                     > | ||||
|                       ${this.supervisor.localize("common.show")} | ||||
|                     </ha-button> | ||||
|                     <a href="/hassio/update-available/core"> | ||||
|                       <mwc-button | ||||
|                         .label=${this.supervisor.localize("common.show")} | ||||
|                       > | ||||
|                       </mwc-button> | ||||
|                     </a> | ||||
|                   ` | ||||
|                 : ""} | ||||
|             </ha-settings-row> | ||||
| @@ -94,7 +95,7 @@ class HassioCoreInfo extends LitElement { | ||||
|         <div class="card-actions"> | ||||
|           <ha-progress-button | ||||
|             slot="primaryAction" | ||||
|             variant="danger" | ||||
|             class="warning" | ||||
|             @click=${this._coreRestart} | ||||
|             .title=${this.supervisor.localize("common.restart_name", { | ||||
|               name: "Core", | ||||
| @@ -187,6 +188,11 @@ class HassioCoreInfo extends LitElement { | ||||
|           white-space: normal; | ||||
|           color: var(--secondary-text-color); | ||||
|         } | ||||
|  | ||||
|         .warning { | ||||
|           --mdc-theme-primary: var(--error-color); | ||||
|         } | ||||
|  | ||||
|         ha-button-menu { | ||||
|           color: var(--secondary-text-color); | ||||
|           --mdc-menu-min-width: 200px; | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import "@material/mwc-button"; | ||||
|  | ||||
| import { mdiDotsVertical } from "@mdi/js"; | ||||
| import type { CSSResultGroup, TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| @@ -6,11 +8,10 @@ import memoizeOne from "memoize-one"; | ||||
| import { atLeastVersion } from "../../../src/common/config/version"; | ||||
| import { fireEvent } from "../../../src/common/dom/fire_event"; | ||||
| import "../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../src/components/ha-button"; | ||||
| import "../../../src/components/ha-button-menu"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import "../../../src/components/ha-icon-button"; | ||||
| import "../../../src/components/ha-list-item"; | ||||
| import "../../../src/components/ha-icon-button"; | ||||
| import "../../../src/components/ha-settings-row"; | ||||
| import { | ||||
|   extractApiErrorMessage, | ||||
| @@ -76,28 +77,24 @@ class HassioHostInfo extends LitElement { | ||||
|                   <span slot="description"> | ||||
|                     ${this.supervisor.host.hostname} | ||||
|                   </span> | ||||
|                   <ha-button | ||||
|                   <mwc-button | ||||
|                     .label=${this.supervisor.localize("system.host.change")} | ||||
|                     @click=${this._changeHostnameClicked} | ||||
|                     appearance="plain" | ||||
|                     size="small" | ||||
|                   > | ||||
|                     ${this.supervisor.localize("system.host.change")} | ||||
|                   </ha-button> | ||||
|                   </mwc-button> | ||||
|                 </ha-settings-row>` | ||||
|               : ""} | ||||
|             ${this.supervisor.host.features.includes("network") | ||||
|               ? html`<ha-settings-row> | ||||
|               ? html` <ha-settings-row> | ||||
|                   <span slot="heading"> | ||||
|                     ${this.supervisor.localize("system.host.ip_address")} | ||||
|                   </span> | ||||
|                   <span slot="description"> ${primaryIpAddress} </span> | ||||
|                   <ha-button | ||||
|                   <mwc-button | ||||
|                     .label=${this.supervisor.localize("system.host.change")} | ||||
|                     @click=${this._changeNetworkClicked} | ||||
|                     appearance="plain" | ||||
|                     size="small" | ||||
|                   > | ||||
|                     ${this.supervisor.localize("system.host.change")} | ||||
|                   </ha-button> | ||||
|                   </mwc-button> | ||||
|                 </ha-settings-row>` | ||||
|               : ""} | ||||
|  | ||||
| @@ -111,13 +108,12 @@ class HassioHostInfo extends LitElement { | ||||
|               ${!atLeastVersion(this.hass.config.version, 2021, 12) && | ||||
|               this.supervisor.os.update_available | ||||
|                 ? html` | ||||
|                     <ha-button | ||||
|                       appearance="plain" | ||||
|                       size="small" | ||||
|                       href="/hassio/update-available/os" | ||||
|                     > | ||||
|                       ${this.supervisor.localize("common.show")} | ||||
|                     </ha-button> | ||||
|                     <a href="/hassio/update-available/os"> | ||||
|                       <mwc-button | ||||
|                         .label=${this.supervisor.localize("common.show")} | ||||
|                       > | ||||
|                       </mwc-button> | ||||
|                     </a> | ||||
|                   ` | ||||
|                 : ""} | ||||
|             </ha-settings-row> | ||||
| @@ -143,12 +139,16 @@ class HassioHostInfo extends LitElement { | ||||
|               : ""} | ||||
|           </div> | ||||
|           <div> | ||||
|             ${this.supervisor.host.disk_life_time !== null | ||||
|             ${this.supervisor.host.disk_life_time !== "" && | ||||
|             this.supervisor.host.disk_life_time >= 10 | ||||
|               ? html` <ha-settings-row> | ||||
|                   <span slot="heading"> | ||||
|                     ${this.supervisor.localize("system.host.lifetime_used")} | ||||
|                     ${this.supervisor.localize( | ||||
|                       "system.host.emmc_lifetime_used" | ||||
|                     )} | ||||
|                   </span> | ||||
|                   <span slot="description"> | ||||
|                     ${this.supervisor.host.disk_life_time - 10} % - | ||||
|                     ${this.supervisor.host.disk_life_time} % | ||||
|                   </span> | ||||
|                 </ha-settings-row>` | ||||
| @@ -167,7 +167,7 @@ class HassioHostInfo extends LitElement { | ||||
|         <div class="card-actions"> | ||||
|           ${this.supervisor.host.features.includes("reboot") | ||||
|             ? html` | ||||
|                 <ha-progress-button variant="danger" @click=${this._hostReboot}> | ||||
|                 <ha-progress-button class="warning" @click=${this._hostReboot}> | ||||
|                   ${this.supervisor.localize("system.host.reboot_host")} | ||||
|                 </ha-progress-button> | ||||
|               ` | ||||
| @@ -175,7 +175,7 @@ class HassioHostInfo extends LitElement { | ||||
|           ${this.supervisor.host.features.includes("shutdown") | ||||
|             ? html` | ||||
|                 <ha-progress-button | ||||
|                   variant="danger" | ||||
|                   class="warning" | ||||
|                   @click=${this._hostShutdown} | ||||
|                 > | ||||
|                   ${this.supervisor.localize("system.host.shutdown_host")} | ||||
| @@ -431,6 +431,10 @@ class HassioHostInfo extends LitElement { | ||||
|           color: var(--secondary-text-color); | ||||
|         } | ||||
|  | ||||
|         .warning { | ||||
|           --mdc-theme-primary: var(--error-color); | ||||
|         } | ||||
|  | ||||
|         ha-button-menu { | ||||
|           color: var(--secondary-text-color); | ||||
|           --mdc-menu-min-width: 200px; | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import { atLeastVersion } from "../../../src/common/config/version"; | ||||
| import { fireEvent } from "../../../src/common/dom/fire_event"; | ||||
| import "../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../src/components/ha-alert"; | ||||
| import "../../../src/components/ha-button"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import "../../../src/components/ha-settings-row"; | ||||
| import "../../../src/components/ha-switch"; | ||||
| @@ -81,13 +80,12 @@ class HassioSupervisorInfo extends LitElement { | ||||
|               ${!atLeastVersion(this.hass.config.version, 2021, 12) && | ||||
|               this.supervisor.supervisor.update_available | ||||
|                 ? html` | ||||
|                     <ha-button | ||||
|                       appearance="plain" | ||||
|                       size="small" | ||||
|                       href="/hassio/update-available/supervisor" | ||||
|                     > | ||||
|                       ${this.supervisor.localize("common.show")} | ||||
|                     </ha-button> | ||||
|                     <a href="/hassio/update-available/supervisor"> | ||||
|                       <mwc-button | ||||
|                         .label=${this.supervisor.localize("common.show")} | ||||
|                       > | ||||
|                       </mwc-button> | ||||
|                     </a> | ||||
|                   ` | ||||
|                 : ""} | ||||
|             </ha-settings-row> | ||||
| @@ -158,28 +156,24 @@ class HassioSupervisorInfo extends LitElement { | ||||
|                   ${this.supervisor.localize( | ||||
|                     "system.supervisor.unsupported_title" | ||||
|                   )} | ||||
|                   <ha-button | ||||
|                   <mwc-button | ||||
|                     slot="action" | ||||
|                     .label=${this.supervisor.localize("common.learn_more")} | ||||
|                     @click=${this._unsupportedDialog} | ||||
|                     variant="warning" | ||||
|                     size="small" | ||||
|                   > | ||||
|                     ${this.supervisor.localize("common.learn_more")} | ||||
|                   </ha-button> | ||||
|                   </mwc-button> | ||||
|                 </ha-alert>`} | ||||
|             ${!this.supervisor.supervisor.healthy | ||||
|               ? html`<ha-alert alert-type="error"> | ||||
|                   ${this.supervisor.localize( | ||||
|                     "system.supervisor.unhealthy_title" | ||||
|                   )} | ||||
|                   <ha-button | ||||
|                     variant="danger" | ||||
|                     size="small" | ||||
|                   <mwc-button | ||||
|                     slot="action" | ||||
|                     .label=${this.supervisor.localize("common.learn_more")} | ||||
|                     @click=${this._unhealthyDialog} | ||||
|                   > | ||||
|                     ${this.supervisor.localize("common.learn_more")} | ||||
|                   </ha-button> | ||||
|                   </mwc-button> | ||||
|                 </ha-alert>` | ||||
|               : ""} | ||||
|           </div> | ||||
| @@ -454,6 +448,9 @@ class HassioSupervisorInfo extends LitElement { | ||||
|           white-space: normal; | ||||
|           color: var(--secondary-text-color); | ||||
|         } | ||||
|         ha-alert mwc-button { | ||||
|           --mdc-theme-primary: var(--primary-text-color); | ||||
|         } | ||||
|         a { | ||||
|           text-decoration: none; | ||||
|         } | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import "@material/mwc-button"; | ||||
|  | ||||
| import type { CSSResultGroup, TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement, property, state } from "lit/decorators"; | ||||
|   | ||||
| @@ -208,16 +208,14 @@ class UpdateAvailableCard extends LitElement { | ||||
|               <div class="card-actions"> | ||||
|                 ${changelog | ||||
|                   ? html` | ||||
|                       <ha-button | ||||
|                         href=${changelog} | ||||
|                         target="_blank" | ||||
|                         rel="noreferrer" | ||||
|                         appearance="plain" | ||||
|                       > | ||||
|                         ${this.supervisor.localize( | ||||
|                           "update_available.open_release_notes" | ||||
|                         )} | ||||
|                       </ha-button> | ||||
|                       <a href=${changelog} target="_blank" rel="noreferrer"> | ||||
|                         <ha-button | ||||
|                           .label=${this.supervisor.localize( | ||||
|                             "update_available.open_release_notes" | ||||
|                           )} | ||||
|                         > | ||||
|                         </ha-button> | ||||
|                       </a> | ||||
|                     ` | ||||
|                   : nothing} | ||||
|                 <span></span> | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import type { TemplateResult } from "lit"; | ||||
| import { css, html, LitElement } from "lit"; | ||||
| import { customElement, property } from "lit/decorators"; | ||||
| import type { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||
| import { goBack } from "../../../src/common/navigate"; | ||||
| import "../../../src/layouts/hass-subpage"; | ||||
| import type { HomeAssistant, Route } from "../../../src/types"; | ||||
| import "./update-available-card"; | ||||
| @@ -36,7 +35,7 @@ class UpdateAvailableDashboard extends LitElement { | ||||
|   } | ||||
|  | ||||
|   private _updateComplete() { | ||||
|     goBack(); | ||||
|     history.back(); | ||||
|   } | ||||
|  | ||||
|   static styles = css` | ||||
|   | ||||
| @@ -3,26 +3,26 @@ import { mdiArrowCollapseDown, mdiDownload } from "@mdi/js"; | ||||
| // eslint-disable-next-line import/extensions | ||||
| import { IntersectionController } from "@lit-labs/observers/intersection-controller.js"; | ||||
| import { LitElement, type PropertyValues, css, html, nothing } from "lit"; | ||||
| import { customElement, property, query, state } from "lit/decorators"; | ||||
| import { classMap } from "lit/directives/class-map"; | ||||
| import { fireEvent } from "../../../src/common/dom/fire_event"; | ||||
| import { customElement, property, query, state } from "lit/decorators"; | ||||
| import type { | ||||
|   LandingPageKeys, | ||||
|   LocalizeFunc, | ||||
| } from "../../../src/common/translations/localize"; | ||||
| import { waitForSeconds } from "../../../src/common/util/wait"; | ||||
| import "../../../src/components/ha-alert"; | ||||
| import "../../../src/components/ha-ansi-to-html"; | ||||
| import type { HaAnsiToHtml } from "../../../src/components/ha-ansi-to-html"; | ||||
| import "../../../src/components/ha-button"; | ||||
| import "../../../src/components/ha-icon-button"; | ||||
| import "../../../src/components/ha-svg-icon"; | ||||
| import { fileDownload } from "../../../src/util/file_download"; | ||||
| import "../../../src/components/ha-ansi-to-html"; | ||||
| import "../../../src/components/ha-alert"; | ||||
| import type { HaAnsiToHtml } from "../../../src/components/ha-ansi-to-html"; | ||||
| import { | ||||
|   getObserverLogs, | ||||
|   downloadUrl as observerLogsDownloadUrl, | ||||
| } from "../data/observer"; | ||||
| import { fireEvent } from "../../../src/common/dom/fire_event"; | ||||
| import { fileDownload } from "../../../src/util/file_download"; | ||||
| import { getSupervisorLogs, getSupervisorLogsFollow } from "../data/supervisor"; | ||||
| import { waitForSeconds } from "../../../src/common/util/wait"; | ||||
| import { ASSUME_CORE_START_SECONDS } from "../ha-landing-page"; | ||||
|  | ||||
| const ERROR_CHECK = /^[\d\s-:]+(ERROR|CRITICAL)(.*)/gm; | ||||
| @@ -64,7 +64,7 @@ class LandingPageLogs extends LitElement { | ||||
|   protected render() { | ||||
|     return html` | ||||
|       <div class="actions"> | ||||
|         <ha-button appearance="plain" @click=${this._toggleLogDetails}> | ||||
|         <ha-button @click=${this._toggleLogDetails}> | ||||
|           ${this.localize(this._show ? "hide_details" : "show_details")} | ||||
|         </ha-button> | ||||
|         ${this._show | ||||
| @@ -81,11 +81,7 @@ class LandingPageLogs extends LitElement { | ||||
|               alert-type="error" | ||||
|               .title=${this.localize("logs.fetch_error")} | ||||
|             > | ||||
|               <ha-button | ||||
|                 size="small" | ||||
|                 variant="danger" | ||||
|                 @click=${this._startLogStream} | ||||
|               > | ||||
|               <ha-button @click=${this._startLogStream}> | ||||
|                 ${this.localize("logs.retry")} | ||||
|               </ha-button> | ||||
|             </ha-alert> | ||||
| @@ -108,13 +104,14 @@ class LandingPageLogs extends LitElement { | ||||
|               !this._scrolledToBottomController.value) || | ||||
|             false, | ||||
|         })}" | ||||
|         size="small" | ||||
|         appearance="filled" | ||||
|         @click=${this._scrollToBottom} | ||||
|       > | ||||
|         <ha-svg-icon .path=${mdiArrowCollapseDown} slot="start"></ha-svg-icon> | ||||
|         <ha-svg-icon .path=${mdiArrowCollapseDown} slot="icon"></ha-svg-icon> | ||||
|         ${this.localize("logs.scroll_down_button")} | ||||
|         <ha-svg-icon .path=${mdiArrowCollapseDown} slot="end"></ha-svg-icon> | ||||
|         <ha-svg-icon | ||||
|           .path=${mdiArrowCollapseDown} | ||||
|           slot="trailingIcon" | ||||
|         ></ha-svg-icon> | ||||
|       </ha-button> | ||||
|     `; | ||||
|   } | ||||
| @@ -302,7 +299,7 @@ class LandingPageLogs extends LitElement { | ||||
|         max-height: 300px; | ||||
|         overflow: auto; | ||||
|         border: 1px solid var(--divider-color); | ||||
|         border-radius: var(--ha-border-radius-sm); | ||||
|         border-radius: 4px; | ||||
|         padding: 4px; | ||||
|       } | ||||
|  | ||||
| @@ -311,14 +308,21 @@ class LandingPageLogs extends LitElement { | ||||
|       } | ||||
|  | ||||
|       .new-logs-indicator { | ||||
|         --mdc-theme-primary: var(--text-primary-color); | ||||
|  | ||||
|         overflow: hidden; | ||||
|         position: absolute; | ||||
|         bottom: 4px; | ||||
|         left: 4px; | ||||
|         bottom: 0; | ||||
|         left: 0; | ||||
|         right: 0; | ||||
|         height: 0; | ||||
|         background-color: var(--primary-color); | ||||
|         border-radius: 8px; | ||||
|  | ||||
|         transition: height 0.4s ease-out; | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|         align-items: center; | ||||
|       } | ||||
|  | ||||
|       .new-logs-indicator.visible { | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user