mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-26 02:36:37 +00:00
* Fix config card rtl issues * Remove optional field from ha-form schema type (#11538) * Add entity id autocompletion to YAML code editors (#11099) * Add selectors to ha-form (#11534) * Allow translate gas total (#11547) * Migrate combobox to mwc (#11546) * New date picker (#11555) * Link via device on device page (#11554) Co-authored-by: Zack Barett <arnett.zackary@gmail.com> * Add integration_discovery to discovery sources (#11564) * Remember filter between navigation (#11565) * Convert selectors to MWC (#11543) * Covert area picker to combo-box (#11562) * Convert entity picker to ha-combo (#11560) * Convert entity picker to ha-combo * Update ha-entity-picker.ts * Handle empty better * Clear value when no device/area/entity * Update links on info page (#11590) * Migrate (input) select entities to mwc (#11591) * Convert HaFormSchemas to use selectors (#11589) * Fix number selector (#11585) * Convert entity-attribute picker to ha-combo-box (#11587) * Convert icon picker to ha-combobox (#11586) Co-authored-by: Zack <zackbarett@hey.com> * Convert area-devices picker (#11588) * Convert device automation picker to mwc (#11592) Co-authored-by: Zack <zackbarett@hey.com> * Fix clearing device in device action (#11594) * dark mode fixes (#11595) * Only show stable add-ons in the store if not advanced mode (#11596) * Convert Automation Action Choose to HA Form (#11597) * Convert Auatomation Action Choose to HA Form * remove log * Remove Import * Replace checkboxes in list items with `check-list-item` (#11610) Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Make textarea grow on input (#11618) * Update lit-virtualizer (#11623) * Convert time inputs to Lit + mwc (#11609) * Set initial focus for device, area, and entity dialogs (#11622) * Add aria-label to table headers with no title (#11503) * Add loadCardHelpers to cast scope (#11616) * Update code editor to material 3 look (#11628) * Set button role on button card and handle enter and space (#11627) * Only load ha-selector when needed (#11630) * Fix service control for older browsers (#11629) * Migrate a bunch of paper-dropdowns (#11626) * Merged too fast for Bram :) Code improv (#11632) * Add support for opening camera media source (#11633) Co-authored-by: Zack Barett <zackbarett@hey.com> * Create error when trying to backup wile system in freeze (#11634) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Add missing type to create device automation/script heading (#11635) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Generate random webhook_id and add copy button (#11568) Co-authored-by: Bram Kragten <mail@bramkragten.nl> Co-authored-by: Zack Barett <zackbarett@hey.com> Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> * Migrate search bar to mwc (#11637) * fix data-table row handlers (#11638) * Bunch of fixes and cleanup (#11636) * State Trigger -> HA Form (#11631) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Allow uploading media (#11615) * Allow uploading media * Update path * Use current item we already have * Update src/panels/media-browser/ha-panel-media-browser.ts Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Use alert dialog and use button for add media Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Add Attribute Picker as a selector - add to state trigger (#11641) Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Time Pattern to HA Form (#11648) * MQTT Trigger to Ha-Form (#11643) Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Convert Sun to Ha Form (#11647) Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Geo Location Trigger to HA - Form (#11644) Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * HA Trigger to HA Form (#11645) Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Make HA Form set required to false for selectors (#11649) * Fix Lovelace Empty Menu when not advanced or admin (#11660) * Add support for media player assumed state (#11642) * Improve search and filters on mobile + fix close button in search field (#11662) Co-authored-by: Zack <zackbarett@hey.com> * Allow adding Zigbee/Zwave device (#11650) * Numerical State to HA-Form (#11646) Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Filter fixes (#11664) * Add WORKSPACE_DIRECTORY environment variable to devcontainer and script.core (#11477) Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> * hotfix history view on missing state (#11663) Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> * Improve robustness of hls media player (#11672) * Revert compute state display show empty string as unknown (#11677) * Set initial focus for some more dialogs (#11676) * Limit types of media that can be uploaded to local media (#11683) * Don't show toggle always on more info (#11640) * Add TTS to media browser (#11679) * Omit Device info and actions for connected controller nodes (#11673) * Script Editor to Ha Form (#11601) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Another round of paper-dropdown -> mwc-select conversion (#11674) * Another round of paper-dropdown -> mwc-select conversion * ha-pick-language-row -> Lit * Update hui-view-editor.ts * Cleanup imports * hassio * Add explicit imports * hassio fixes (#11688) * Dont exclude domain for area and device (#11689) * Try to keep the browsing stack when changing players in media panel (#11681) * Allow uploading multiple files (#11687) * Bumped version to 20220214.0 * Group helpers not in an area in a single card (#11690) * Improve `stripPrefixFromEntityName` to handle colon and space separator (#11691) * Display transmitted messages in MQTT debug info dialog (#11531) * Latest paper-dropdown -> mwc-select conversion (#11692) * This adds back mobile click accessibility (#11693) * Updated text part 2 (#11686) Co-authored-by: Zack Barett <zackbarett@hey.com> * Set initial focus for lovelace dialogs (#11667) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Migrate all lovelace elements to mwc (#11695) Co-authored-by: Zack Barett <zackbarett@hey.com> * Fix import * Clean up some imports (#11696) * Convert triple dots to single char in translations (#11697) * Fixes remote icon state color (#11698) * Convert scene action to service call (#11705) * Convert scene action to service call * fix describeAction * rename to metadata * Update script.ts * Fix mode selection in automation editor (#11707) * Remove duplicate gallery page (#11711) * Add bottom padding to config links list with safe-area-inset-bottom (#11704) * Bump hls.js to v1.1.5 (#11712) * Make zwave_js config panel inclusion state aware (#11556) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Fix mwc-select in lovelace editors (#11708) * Add signed add-on capability and adjust max rating (#11703) * Add support for removing config entry from a device * Tweak * Fix lint error * Tweak * Prettier * Add play media action (#11702) Co-authored-by: Zack Barett <zackbarett@hey.com> Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Debounce refresh the cloud status if Google events happen (#11721) * Remove custom MQTT delete device button (#11724) * Apply suggestions from code review Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Update src/panels/config/devices/ha-config-device-page.ts Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Correct typing * Prettier * Remove useless Array.isArray check * Remove custom Tasmota delete device button (#11725) * Automation Conditions to conversion to ha-form or mwc (#11727) * Set initial focus for energy dialogs (#11730) * Entity Settings Page to MWC 3 (#11694) * Show why relayer is reconnecting (#11732) * Change words for trigger condition (#11733) * Update media player more info (#11734) * Pass hass to ha-form to enable selectors (#11739) * Bumped version to 20220220.0 * Add link to the selector docs * TTS form no longer showed due to import oopsie (#11742) * Improve logo rendering for playing media in browser (#11741) * Fix media upload on iOS (#11740) * Handle inifinity media duration (#11749) * Show when media is being loaded (#11750) * Lovelace Entity Card Editor to Ha Form - Adds Theme Selector and HaFormColumn (#11731) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Set initial focus for supervisor dialogs (#11710) * Convert Automation Actions to mwc/ha-form + other automation items (#11753) * Selector: remove text value when not required and empty (#11754) * Convert date-range-picker to mwc (#11755) * Radio Browser is now added during onboarding (#11756) * Add support for the media browser My link (#11757) * Show Home Assistant when creating partial backup (#11758) * Fix zwave migration (#11751) * Allow config entries to be reloaded when they are in setup_retry state (#11759) * Area Card Editor to Ha Form (#11762) * Fix WebRTC player stream playback when disconnected/connected (#11764) * set theme to undefined when no theme (#11765) * Paper input migrations (#11766) * Only show description when set (#11772) * Thermostat Editor to HA - Form (#11763) * Thermostat - Ha Form * Update hui-thermostat-card-editor.ts Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Alarm Card Editor to HA Form (#11760) * Move to ha-form * Update hui-alarm-panel-card-editor.ts Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Change icons for cover with device_class curtain (#11752) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * no need for memoize * Include scoped custom element polyfill (#11776) * Show triggered in automation editor (#11771) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Allow changing volume media player entity (#11781) Co-authored-by: Zack Barett <zackbarett@hey.com> * Add community section (#11779) * Bumped version to 20220222.0 * Fix State Condition 'For' Data (#11782) * entities card editor to MWC (#11785) * Fix ripple corner radius for button card (#11780) * Condition Card Editor to MWC (#11783) * Show number of hidden items (#11786) * Put volume slider in the middle of the button (#11788) * Add media management dialog (#11787) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Convert alarm control panel more info (#11791) * Convert alarm control panel more info * Update more-info-alarm_control_panel.ts * Update src/dialogs/more-info/controls/more-info-alarm_control_panel.ts * Apply suggestions from code review Co-authored-by: Zack Barett <zackbarett@hey.com> * import Co-authored-by: Zack Barett <zackbarett@hey.com> * Migrate more-info configurator (#11792) * Migrate more-info configurator * Update more-info-configurator.ts * Update src/dialogs/more-info/controls/more-info-configurator.ts * Update src/dialogs/more-info/controls/more-info-configurator.ts Co-authored-by: Zack Barett <zackbarett@hey.com> * Import Co-authored-by: Zack Barett <zackbarett@hey.com> * Convert more info lock (#11794) * Add Margin to Tip (#11790) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Dont render double label on number selector (#11796) * Input conversion in dev tools (#11795) * Gauge Editor to Ha Form (#11793) * Stop spinning when opening media in dialog (#11800) * Fix Entities picker (#11802) * Migrate single textfields (#11799) * Migrate single textfields * Update ha-config-name-form.ts * Update dialog-area-registry-detail.ts * Update manual-automation-editor.ts * Update manual-automation-editor.ts * required to number selector fix script * review * change repository url and project description (#11801) * Calendar card to HA Form (#11784) * Graph Footer to MWC (#11803) * History Graph Editor to ha form (#11797) * Glance editor to ha-form (#11804) * Grid Card to HA Form (#11798) * Button editor to ha-form (#11808) * Bumped version to 20220223.0 * mwc-select -> ha-select (#11806) Co-authored-by: Yosi Levy <yosilevy@gmail.com> Co-authored-by: Paulus Schoutsen <balloob@gmail.com> Co-authored-by: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Co-authored-by: Kuba Wolanin <hi@kubawolanin.com> Co-authored-by: Franck Nijhof <git@frenck.dev> Co-authored-by: Zack Barett <arnett.zackary@gmail.com> Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Zack <zackbarett@hey.com> Co-authored-by: Joakim Sørensen <ludeeus@ludeeus.dev> Co-authored-by: Steve Repsher <steverep@users.noreply.github.com> Co-authored-by: Patrick ZAJDA <patrick@zajda.fr> Co-authored-by: Thomas Lovén <thomasloven@gmail.com> Co-authored-by: Eric Severance <esev@esev.com> Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> Co-authored-by: Shay Levy <levyshay1@gmail.com> Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com> Co-authored-by: Joakim Sørensen <hi@ludeeus.dev> Co-authored-by: lintaba <lintaba@gmail.com> Co-authored-by: Allen Porter <allen@thebends.org> Co-authored-by: kpine <keith.pine@gmail.com> Co-authored-by: Brandon Rothweiler <brandonrothweiler@gmail.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Matthias de Baat <matthias.debaat@nabucasa.com> Co-authored-by: Philip Allgaier <mail@spacegaier.de> Co-authored-by: Josh McCarty <josh@joshmccarty.com> Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com> Co-authored-by: Pascal Winters <pascal@famwinters.com> Co-authored-by: Robin Wittebol <robinwittebol@live.nl> Co-authored-by: Tomasz <t.jagusz@gmail.com>
This commit is contained in:
commit
09f8f816d1
@ -16,6 +16,9 @@
|
|||||||
"runem.lit-plugin",
|
"runem.lit-plugin",
|
||||||
"ms-python.vscode-pylance"
|
"ms-python.vscode-pylance"
|
||||||
],
|
],
|
||||||
|
"containerEnv": {
|
||||||
|
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"terminal.integrated.shell.linux": "/bin/bash",
|
"terminal.integrated.shell.linux": "/bin/bash",
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,11 +1,10 @@
|
|||||||
diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
diff --git a/polyfillLoaders/EventTarget.js b/polyfillLoaders/EventTarget.js
|
||||||
index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644
|
index 4e18ade7ba485849f17f28c94c42f0e0e01ac387..8f34f4f646c7f7becc208fb5a546c96034fc74dc 100644
|
||||||
--- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
--- a/polyfillLoaders/EventTarget.js
|
||||||
+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
+++ b/polyfillLoaders/EventTarget.js
|
||||||
@@ -1,14 +1,15 @@
|
@@ -6,16 +6,15 @@
|
||||||
-let _ET, ET;
|
let _ET;
|
||||||
+let _ET;
|
let ET;
|
||||||
+let ET;
|
|
||||||
export default async function EventTarget() {
|
export default async function EventTarget() {
|
||||||
- return ET || init();
|
- return ET || init();
|
||||||
+ return ET || init();
|
+ return ET || init();
|
||||||
@ -26,4 +25,5 @@ index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc
|
|||||||
+ _ET = (await import("event-target-shim")).default.EventTarget;
|
+ _ET = (await import("event-target-shim")).default.EventTarget;
|
||||||
+ }
|
+ }
|
||||||
+ return (ET = _ET);
|
+ return (ET = _ET);
|
||||||
}
|
}
|
||||||
|
//# sourceMappingURL=EventTarget.js.map
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
|
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
|
||||||
|
|
||||||
[](https://demo.home-assistant.io/)
|
[](https://demo.home-assistant.io/)
|
||||||
|
|
||||||
- [View demo of Home Assistant](https://demo.home-assistant.io/)
|
- [View demo of Home Assistant](https://demo.home-assistant.io/)
|
||||||
- [More information about Home Assistant](https://home-assistant.io)
|
- [More information about Home Assistant](https://home-assistant.io)
|
||||||
|
@ -10,7 +10,7 @@ module.exports.ignorePackages = ({ latestBuild }) => [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Files from NPM packages that we should replace with empty file
|
// Files from NPM packages that we should replace with empty file
|
||||||
module.exports.emptyPackages = ({ latestBuild }) =>
|
module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
||||||
[
|
[
|
||||||
// Contains all color definitions for all material color sets.
|
// Contains all color definitions for all material color sets.
|
||||||
// We don't use it
|
// We don't use it
|
||||||
@ -28,6 +28,15 @@ module.exports.emptyPackages = ({ latestBuild }) =>
|
|||||||
),
|
),
|
||||||
// This polyfill is loaded in workers to support ES5, filter it out.
|
// This polyfill is loaded in workers to support ES5, filter it out.
|
||||||
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
|
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
|
||||||
|
// Icons in supervisor conflict with icons in HA so we don't load.
|
||||||
|
isHassioBuild &&
|
||||||
|
require.resolve(
|
||||||
|
path.resolve(paths.polymer_dir, "src/components/ha-icon.ts")
|
||||||
|
),
|
||||||
|
isHassioBuild &&
|
||||||
|
require.resolve(
|
||||||
|
path.resolve(paths.polymer_dir, "src/components/ha-icon-picker.ts")
|
||||||
|
),
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||||
@ -196,6 +205,7 @@ module.exports.config = {
|
|||||||
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
||||||
isProdBuild,
|
isProdBuild,
|
||||||
latestBuild,
|
latestBuild,
|
||||||
|
isHassioBuild: true,
|
||||||
defineOverlay: {
|
defineOverlay: {
|
||||||
__SUPERVISOR__: true,
|
__SUPERVISOR__: true,
|
||||||
},
|
},
|
||||||
|
@ -30,6 +30,7 @@ const createWebpackConfig = ({
|
|||||||
isProdBuild,
|
isProdBuild,
|
||||||
latestBuild,
|
latestBuild,
|
||||||
isStatsBuild,
|
isStatsBuild,
|
||||||
|
isHassioBuild,
|
||||||
dontHash,
|
dontHash,
|
||||||
}) => {
|
}) => {
|
||||||
if (!dontHash) {
|
if (!dontHash) {
|
||||||
@ -117,7 +118,9 @@ const createWebpackConfig = ({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
|
new RegExp(
|
||||||
|
bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|")
|
||||||
|
),
|
||||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||||
),
|
),
|
||||||
!isProdBuild && new LogStartCompilePlugin(),
|
!isProdBuild && new LogStartCompilePlugin(),
|
||||||
|
@ -7,6 +7,9 @@ import "../../../../src/panels/lovelace/views/hui-view";
|
|||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
import "./hc-launch-screen";
|
import "./hc-launch-screen";
|
||||||
|
|
||||||
|
(window as any).loadCardHelpers = () =>
|
||||||
|
import("../../../../src/panels/lovelace/custom-card-helpers");
|
||||||
|
|
||||||
@customElement("hc-lovelace")
|
@customElement("hc-lovelace")
|
||||||
class HcLovelace extends LitElement {
|
class HcLovelace extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
@ -2,8 +2,3 @@ import "../../src/resources/ha-style";
|
|||||||
import "../../src/resources/roboto";
|
import "../../src/resources/roboto";
|
||||||
import "../../src/resources/safari-14-attachshadow-patch";
|
import "../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "./ha-demo";
|
import "./ha-demo";
|
||||||
|
|
||||||
/* polyfill for paper-dropdown */
|
|
||||||
setTimeout(() => {
|
|
||||||
import("web-animations-js/web-animations-next-lite.min");
|
|
||||||
}, 1000);
|
|
||||||
|
@ -20,7 +20,6 @@ module.exports = [
|
|||||||
"editor-trigger",
|
"editor-trigger",
|
||||||
"editor-condition",
|
"editor-condition",
|
||||||
"editor-action",
|
"editor-action",
|
||||||
"selectors",
|
|
||||||
"trace",
|
"trace",
|
||||||
"trace-timeline",
|
"trace-timeline",
|
||||||
],
|
],
|
||||||
|
@ -3,6 +3,7 @@ import { html, LitElement, css, TemplateResult } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
|
import "../../../src/components/ha-card";
|
||||||
|
|
||||||
@customElement("demo-black-white-row")
|
@customElement("demo-black-white-row")
|
||||||
class DemoBlackWhiteRow extends LitElement {
|
class DemoBlackWhiteRow extends LitElement {
|
||||||
|
@ -3,10 +3,20 @@ import { html, css, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import { describeAction } from "../../../../src/data/script_i18n";
|
import { describeAction } from "../../../../src/data/script_i18n";
|
||||||
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
|
|
||||||
const actions = [
|
const ENTITIES = [
|
||||||
|
getEntity("scene", "kitchen_morning", "scening", {
|
||||||
|
friendly_name: "Kitchen Morning",
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "kitchen", "playing", {
|
||||||
|
friendly_name: "Sonos Kitchen",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const ACTIONS = [
|
||||||
{ wait_template: "{{ true }}", alias: "Something with an alias" },
|
{ wait_template: "{{ true }}", alias: "Something with an alias" },
|
||||||
{ delay: "0:05" },
|
{ delay: "0:05" },
|
||||||
{ wait_template: "{{ true }}" },
|
{ wait_template: "{{ true }}" },
|
||||||
@ -19,8 +29,20 @@ const actions = [
|
|||||||
device_id: "abcdefgh",
|
device_id: "abcdefgh",
|
||||||
domain: "plex",
|
domain: "plex",
|
||||||
entity_id: "media_player.kitchen",
|
entity_id: "media_player.kitchen",
|
||||||
|
type: "turn_on",
|
||||||
},
|
},
|
||||||
{ scene: "scene.kitchen_morning" },
|
{ scene: "scene.kitchen_morning" },
|
||||||
|
{
|
||||||
|
service: "scene.turn_on",
|
||||||
|
target: { entity_id: "scene.kitchen_morning" },
|
||||||
|
metadata: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: "media_player.play_media",
|
||||||
|
target: { entity_id: "media_player.kitchen" },
|
||||||
|
data: { media_content_id: "", media_content_type: "" },
|
||||||
|
metadata: { title: "Happy Song" },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
wait_for_trigger: [
|
wait_for_trigger: [
|
||||||
{
|
{
|
||||||
@ -52,7 +74,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-card header="Actions">
|
<ha-card header="Actions">
|
||||||
${actions.map(
|
${ACTIONS.map(
|
||||||
(conf) => html`
|
(conf) => html`
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<span>${describeAction(this.hass, conf as any)}</span>
|
<span>${describeAction(this.hass, conf as any)}</span>
|
||||||
@ -68,6 +90,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
const hass = provideHass(this);
|
const hass = provideHass(this);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
|
@ -14,7 +14,7 @@ import { HaDelayAction } from "../../../../src/panels/config/automation/action/t
|
|||||||
import { HaDeviceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
|
import { HaDeviceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
|
||||||
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
|
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
|
||||||
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
|
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
|
||||||
import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-scene";
|
import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-activate_scene";
|
||||||
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
|
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
|
||||||
import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
|
import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
|
||||||
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
title: Selectors
|
|
||||||
---
|
|
@ -1,102 +0,0 @@
|
|||||||
/* eslint-disable lit/no-template-arrow */
|
|
||||||
import { LitElement, TemplateResult, html } from "lit";
|
|
||||||
import { customElement, state } from "lit/decorators";
|
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
|
||||||
import type { HomeAssistant } from "../../../../src/types";
|
|
||||||
import "../../components/demo-black-white-row";
|
|
||||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
|
||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
|
||||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
|
||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
|
||||||
import "../../../../src/panels/config/automation/trigger/ha-automation-trigger";
|
|
||||||
import { Selector } from "../../../../src/data/selector";
|
|
||||||
import "../../../../src/components/ha-selector/ha-selector";
|
|
||||||
|
|
||||||
const SCHEMAS: { name: string; selector: Selector }[] = [
|
|
||||||
{ name: "Addon", selector: { addon: {} } },
|
|
||||||
|
|
||||||
{ name: "Entity", selector: { entity: {} } },
|
|
||||||
{ name: "Device", selector: { device: {} } },
|
|
||||||
{ name: "Area", selector: { area: {} } },
|
|
||||||
{ name: "Target", selector: { target: {} } },
|
|
||||||
{
|
|
||||||
name: "Number",
|
|
||||||
selector: {
|
|
||||||
number: {
|
|
||||||
min: 0,
|
|
||||||
max: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ name: "Boolean", selector: { boolean: {} } },
|
|
||||||
{ name: "Time", selector: { time: {} } },
|
|
||||||
{ name: "Action", selector: { action: {} } },
|
|
||||||
{ name: "Text", selector: { text: { multiline: false } } },
|
|
||||||
{ name: "Text Multiline", selector: { text: { multiline: true } } },
|
|
||||||
{ name: "Object", selector: { object: {} } },
|
|
||||||
{
|
|
||||||
name: "Select",
|
|
||||||
selector: {
|
|
||||||
select: {
|
|
||||||
options: ["Everyone Home", "Some Home", "All gone"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
@customElement("demo-automation-selectors")
|
|
||||||
class DemoHaSelector extends LitElement {
|
|
||||||
@state() private hass!: HomeAssistant;
|
|
||||||
|
|
||||||
private data: any = SCHEMAS.map(() => undefined);
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
const hass = provideHass(this);
|
|
||||||
hass.updateTranslations(null, "en");
|
|
||||||
hass.updateTranslations("config", "en");
|
|
||||||
mockEntityRegistry(hass);
|
|
||||||
mockDeviceRegistry(hass);
|
|
||||||
mockAreaRegistry(hass);
|
|
||||||
mockHassioSupervisor(hass);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
const valueChanged = (ev) => {
|
|
||||||
const sampleIdx = ev.target.sampleIdx;
|
|
||||||
this.data[sampleIdx] = ev.detail.value;
|
|
||||||
this.requestUpdate();
|
|
||||||
};
|
|
||||||
return html`
|
|
||||||
${SCHEMAS.map(
|
|
||||||
(info, sampleIdx) => html`
|
|
||||||
<demo-black-white-row
|
|
||||||
.title=${info.name}
|
|
||||||
.value=${{ selector: info.selector, data: this.data[sampleIdx] }}
|
|
||||||
>
|
|
||||||
${["light", "dark"].map(
|
|
||||||
(slot) =>
|
|
||||||
html`
|
|
||||||
<ha-selector
|
|
||||||
slot=${slot}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.selector=${info.selector}
|
|
||||||
.label=${info.name}
|
|
||||||
.value=${this.data[sampleIdx]}
|
|
||||||
.sampleIdx=${sampleIdx}
|
|
||||||
@value-changed=${valueChanged}
|
|
||||||
></ha-selector>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</demo-black-white-row>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"demo-automation-selectors": DemoHaSelector;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,17 @@
|
|||||||
/* eslint-disable lit/no-template-arrow */
|
/* eslint-disable lit/no-template-arrow */
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { LitElement, TemplateResult, html } from "lit";
|
import { LitElement, TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||||
import "../../../../src/components/ha-form/ha-form";
|
import "../../../../src/components/ha-form/ha-form";
|
||||||
import "../../components/demo-black-white-row";
|
import "../../components/demo-black-white-row";
|
||||||
|
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||||
|
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||||
|
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||||
|
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||||
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
|
|
||||||
const SCHEMAS: {
|
const SCHEMAS: {
|
||||||
title: string;
|
title: string;
|
||||||
@ -14,6 +20,63 @@ const SCHEMAS: {
|
|||||||
schema: HaFormSchema[];
|
schema: HaFormSchema[];
|
||||||
data?: Record<string, any>;
|
data?: Record<string, any>;
|
||||||
}[] = [
|
}[] = [
|
||||||
|
{
|
||||||
|
title: "Selectors",
|
||||||
|
translations: {
|
||||||
|
addon: "Addon",
|
||||||
|
entity: "Entity",
|
||||||
|
device: "Device",
|
||||||
|
area: "Area",
|
||||||
|
target: "Target",
|
||||||
|
number: "Number",
|
||||||
|
boolean: "Boolean",
|
||||||
|
time: "Time",
|
||||||
|
action: "Action",
|
||||||
|
text: "Text",
|
||||||
|
text_multiline: "Text Multiline",
|
||||||
|
object: "Object",
|
||||||
|
select: "Select",
|
||||||
|
icon: "Icon",
|
||||||
|
media: "Media",
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{ name: "addon", selector: { addon: {} } },
|
||||||
|
{ name: "entity", selector: { entity: {} } },
|
||||||
|
{
|
||||||
|
name: "Attribute",
|
||||||
|
selector: { attribute: { entity_id: "" } },
|
||||||
|
},
|
||||||
|
{ name: "Device", selector: { device: {} } },
|
||||||
|
{ name: "Duration", selector: { duration: {} } },
|
||||||
|
{ name: "area", selector: { area: {} } },
|
||||||
|
{ name: "target", selector: { target: {} } },
|
||||||
|
{ name: "number", selector: { number: { min: 0, max: 10 } } },
|
||||||
|
{ name: "boolean", selector: { boolean: {} } },
|
||||||
|
{ name: "time", selector: { time: {} } },
|
||||||
|
{ name: "action", selector: { action: {} } },
|
||||||
|
{ name: "text", selector: { text: { multiline: false } } },
|
||||||
|
{ name: "text_multiline", selector: { text: { multiline: true } } },
|
||||||
|
{ name: "object", selector: { object: {} } },
|
||||||
|
{
|
||||||
|
name: "select",
|
||||||
|
selector: {
|
||||||
|
select: { options: ["Everyone Home", "Some Home", "All gone"] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "icon",
|
||||||
|
selector: {
|
||||||
|
icon: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "media",
|
||||||
|
selector: {
|
||||||
|
media: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Authentication",
|
title: "Authentication",
|
||||||
translations: {
|
translations: {
|
||||||
@ -50,13 +113,11 @@ const SCHEMAS: {
|
|||||||
{
|
{
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
name: "bool",
|
name: "bool",
|
||||||
optional: true,
|
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "integer",
|
type: "integer",
|
||||||
name: "int",
|
name: "int",
|
||||||
optional: true,
|
|
||||||
default: 10,
|
default: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -67,7 +128,6 @@ const SCHEMAS: {
|
|||||||
{
|
{
|
||||||
type: "string",
|
type: "string",
|
||||||
name: "string",
|
name: "string",
|
||||||
optional: true,
|
|
||||||
default: "Default",
|
default: "Default",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -77,7 +137,6 @@ const SCHEMAS: {
|
|||||||
["other", "other"],
|
["other", "other"],
|
||||||
],
|
],
|
||||||
name: "select",
|
name: "select",
|
||||||
optional: true,
|
|
||||||
default: "default",
|
default: "default",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -87,7 +146,6 @@ const SCHEMAS: {
|
|||||||
other: "Other",
|
other: "Other",
|
||||||
},
|
},
|
||||||
name: "multi",
|
name: "multi",
|
||||||
optional: true,
|
|
||||||
default: ["default"],
|
default: ["default"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -108,7 +166,6 @@ const SCHEMAS: {
|
|||||||
{
|
{
|
||||||
type: "integer",
|
type: "integer",
|
||||||
name: "int with default",
|
name: "int with default",
|
||||||
optional: true,
|
|
||||||
default: 10,
|
default: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -122,7 +179,6 @@ const SCHEMAS: {
|
|||||||
{
|
{
|
||||||
type: "integer",
|
type: "integer",
|
||||||
name: "int range optional",
|
name: "int range optional",
|
||||||
optional: true,
|
|
||||||
valueMin: 0,
|
valueMin: 0,
|
||||||
valueMax: 10,
|
valueMax: 10,
|
||||||
},
|
},
|
||||||
@ -148,7 +204,6 @@ const SCHEMAS: {
|
|||||||
["other", "Other"],
|
["other", "Other"],
|
||||||
],
|
],
|
||||||
name: "select optional",
|
name: "select optional",
|
||||||
optional: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "select",
|
type: "select",
|
||||||
@ -161,7 +216,6 @@ const SCHEMAS: {
|
|||||||
["option", "1000"],
|
["option", "1000"],
|
||||||
],
|
],
|
||||||
name: "select many otions",
|
name: "select many otions",
|
||||||
optional: true,
|
|
||||||
default: "default",
|
default: "default",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -190,7 +244,6 @@ const SCHEMAS: {
|
|||||||
option: "1000",
|
option: "1000",
|
||||||
},
|
},
|
||||||
name: "multi many otions",
|
name: "multi many otions",
|
||||||
optional: true,
|
|
||||||
default: ["default"],
|
default: ["default"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -239,23 +292,35 @@ const SCHEMAS: {
|
|||||||
valueMin: 1,
|
valueMin: 1,
|
||||||
valueMax: 65535,
|
valueMax: 65535,
|
||||||
name: "port",
|
name: "port",
|
||||||
optional: true,
|
|
||||||
default: 80,
|
default: 80,
|
||||||
},
|
},
|
||||||
{ type: "string", name: "path", optional: true, default: "/" },
|
{ type: "string", name: "path", default: "/" },
|
||||||
{ type: "boolean", name: "ssl", optional: true, default: false },
|
{ type: "boolean", name: "ssl", default: false },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-components-ha-form")
|
@customElement("demo-components-ha-form")
|
||||||
class DemoHaForm extends LitElement {
|
class DemoHaForm extends LitElement {
|
||||||
|
@state() private hass!: HomeAssistant;
|
||||||
|
|
||||||
private data = SCHEMAS.map(
|
private data = SCHEMAS.map(
|
||||||
({ schema, data }) => data || computeInitialHaFormData(schema)
|
({ schema, data }) => data || computeInitialHaFormData(schema)
|
||||||
);
|
);
|
||||||
|
|
||||||
private disabled = SCHEMAS.map(() => false);
|
private disabled = SCHEMAS.map(() => false);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
const hass = provideHass(this);
|
||||||
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.updateTranslations("config", "en");
|
||||||
|
mockEntityRegistry(hass);
|
||||||
|
mockDeviceRegistry(hass);
|
||||||
|
mockAreaRegistry(hass);
|
||||||
|
mockHassioSupervisor(hass);
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${SCHEMAS.map((info, idx) => {
|
${SCHEMAS.map((info, idx) => {
|
||||||
@ -278,6 +343,7 @@ class DemoHaForm extends LitElement {
|
|||||||
(slot) => html`
|
(slot) => html`
|
||||||
<ha-form
|
<ha-form
|
||||||
slot=${slot}
|
slot=${slot}
|
||||||
|
.hass=${this.hass}
|
||||||
.data=${this.data[idx]}
|
.data=${this.data[idx]}
|
||||||
.schema=${info.schema}
|
.schema=${info.schema}
|
||||||
.error=${info.error}
|
.error=${info.error}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: Target Selectors
|
title: Selectors
|
||||||
---
|
---
|
||||||
|
|
||||||
|
See the website for [list of available selectors](https://www.home-assistant.io/docs/blueprint/selectors/).
|
||||||
|
@ -12,6 +12,100 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
|||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||||
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
|
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||||
|
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||||
|
|
||||||
|
const ENTITIES = [
|
||||||
|
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||||
|
friendly_name: "Alarm",
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "livingroom", "playing", {
|
||||||
|
friendly_name: "Livingroom",
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "lounge", "idle", {
|
||||||
|
friendly_name: "Lounge",
|
||||||
|
supported_features: 444983,
|
||||||
|
}),
|
||||||
|
getEntity("light", "bedroom", "on", {
|
||||||
|
friendly_name: "Bedroom",
|
||||||
|
}),
|
||||||
|
getEntity("switch", "coffee", "off", {
|
||||||
|
friendly_name: "Coffee",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEVICES = [
|
||||||
|
{
|
||||||
|
area_id: "bedroom",
|
||||||
|
configuration_url: null,
|
||||||
|
config_entries: ["config_entry_1"],
|
||||||
|
connections: [],
|
||||||
|
disabled_by: null,
|
||||||
|
entry_type: null,
|
||||||
|
id: "device_1",
|
||||||
|
identifiers: [["demo", "volume1"] as [string, string]],
|
||||||
|
manufacturer: null,
|
||||||
|
model: null,
|
||||||
|
name_by_user: null,
|
||||||
|
name: "Dishwasher",
|
||||||
|
sw_version: null,
|
||||||
|
hw_version: null,
|
||||||
|
via_device_id: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: "backyard",
|
||||||
|
configuration_url: null,
|
||||||
|
config_entries: ["config_entry_2"],
|
||||||
|
connections: [],
|
||||||
|
disabled_by: null,
|
||||||
|
entry_type: null,
|
||||||
|
id: "device_2",
|
||||||
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
|
manufacturer: null,
|
||||||
|
model: null,
|
||||||
|
name_by_user: null,
|
||||||
|
name: "Lamp",
|
||||||
|
sw_version: null,
|
||||||
|
hw_version: null,
|
||||||
|
via_device_id: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: null,
|
||||||
|
configuration_url: null,
|
||||||
|
config_entries: ["config_entry_3"],
|
||||||
|
connections: [],
|
||||||
|
disabled_by: null,
|
||||||
|
entry_type: null,
|
||||||
|
id: "device_3",
|
||||||
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
|
manufacturer: null,
|
||||||
|
model: null,
|
||||||
|
name_by_user: "User name",
|
||||||
|
name: "Technical name",
|
||||||
|
sw_version: null,
|
||||||
|
hw_version: null,
|
||||||
|
via_device_id: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const AREAS = [
|
||||||
|
{
|
||||||
|
area_id: "backyard",
|
||||||
|
name: "Backyard",
|
||||||
|
picture: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: "bedroom",
|
||||||
|
name: "Bedroom",
|
||||||
|
picture: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: "livingroom",
|
||||||
|
name: "Livingroom",
|
||||||
|
picture: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const SCHEMAS: {
|
const SCHEMAS: {
|
||||||
name: string;
|
name: string;
|
||||||
@ -21,7 +115,12 @@ const SCHEMAS: {
|
|||||||
name: "One of each",
|
name: "One of each",
|
||||||
input: {
|
input: {
|
||||||
entity: { name: "Entity", selector: { entity: {} } },
|
entity: { name: "Entity", selector: { entity: {} } },
|
||||||
|
attribute: {
|
||||||
|
name: "Attribute",
|
||||||
|
selector: { attribute: { entity_id: "" } },
|
||||||
|
},
|
||||||
device: { name: "Device", selector: { device: {} } },
|
device: { name: "Device", selector: { device: {} } },
|
||||||
|
duration: { name: "Duration", selector: { duration: {} } },
|
||||||
addon: { name: "Addon", selector: { addon: {} } },
|
addon: { name: "Addon", selector: { addon: {} } },
|
||||||
area: { name: "Area", selector: { area: {} } },
|
area: { name: "Area", selector: { area: {} } },
|
||||||
target: { name: "Target", selector: { target: {} } },
|
target: { name: "Target", selector: { target: {} } },
|
||||||
@ -48,23 +147,34 @@ const SCHEMAS: {
|
|||||||
boolean: { name: "Boolean", selector: { boolean: {} } },
|
boolean: { name: "Boolean", selector: { boolean: {} } },
|
||||||
time: { name: "Time", selector: { time: {} } },
|
time: { name: "Time", selector: { time: {} } },
|
||||||
action: { name: "Action", selector: { action: {} } },
|
action: { name: "Action", selector: { action: {} } },
|
||||||
text: { name: "Text", selector: { text: { multiline: false } } },
|
text: {
|
||||||
|
name: "Text",
|
||||||
|
selector: { text: {} },
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
name: "Password",
|
||||||
|
selector: { text: { type: "password" } },
|
||||||
|
},
|
||||||
text_multiline: {
|
text_multiline: {
|
||||||
name: "Text multiline",
|
name: "Text multiline",
|
||||||
selector: { text: { multiline: true } },
|
selector: {
|
||||||
|
text: { multiline: true },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
object: { name: "Object", selector: { object: {} } },
|
object: { name: "Object", selector: { object: {} } },
|
||||||
select: {
|
select: {
|
||||||
name: "Select",
|
name: "Select",
|
||||||
selector: { select: { options: ["Option 1", "Option 2"] } },
|
selector: { select: { options: ["Option 1", "Option 2"] } },
|
||||||
},
|
},
|
||||||
|
icon: { name: "Icon", selector: { icon: {} } },
|
||||||
|
media: { name: "Media", selector: { media: {} } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-components-ha-selector")
|
@customElement("demo-components-ha-selector")
|
||||||
class DemoHaSelector extends LitElement {
|
class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||||
@state() private hass!: HomeAssistant;
|
@state() public hass!: HomeAssistant;
|
||||||
|
|
||||||
private data = SCHEMAS.map(() => ({}));
|
private data = SCHEMAS.map(() => ({}));
|
||||||
|
|
||||||
@ -73,12 +183,130 @@ class DemoHaSelector extends LitElement {
|
|||||||
const hass = provideHass(this);
|
const hass = provideHass(this);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
hass.updateTranslations("config", "en");
|
hass.updateTranslations("config", "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
mockEntityRegistry(hass);
|
mockEntityRegistry(hass);
|
||||||
mockDeviceRegistry(hass);
|
mockDeviceRegistry(hass, DEVICES);
|
||||||
mockAreaRegistry(hass);
|
mockAreaRegistry(hass, AREAS);
|
||||||
mockHassioSupervisor(hass);
|
mockHassioSupervisor(hass);
|
||||||
|
hass.mockWS("auth/sign_path", (params) => params);
|
||||||
|
hass.mockWS("media_player/browse_media", this._browseMedia);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public provideHass(el) {
|
||||||
|
el.hass = this.hass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.addEventListener("show-dialog", this._dialogManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this.removeEventListener("show-dialog", this._dialogManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _browseMedia = ({ media_content_id }) => {
|
||||||
|
if (media_content_id === undefined) {
|
||||||
|
return {
|
||||||
|
title: "Media",
|
||||||
|
media_class: "directory",
|
||||||
|
media_content_type: "",
|
||||||
|
media_content_id: "media-source://media_source/local/.",
|
||||||
|
can_play: false,
|
||||||
|
can_expand: true,
|
||||||
|
children_media_class: "directory",
|
||||||
|
thumbnail: null,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: "Misc",
|
||||||
|
media_class: "directory",
|
||||||
|
media_content_type: "",
|
||||||
|
media_content_id: "media-source://media_source/local/misc",
|
||||||
|
can_play: false,
|
||||||
|
can_expand: true,
|
||||||
|
children_media_class: null,
|
||||||
|
thumbnail: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Movies",
|
||||||
|
media_class: "directory",
|
||||||
|
media_content_type: "",
|
||||||
|
media_content_id: "media-source://media_source/local/movies",
|
||||||
|
can_play: true,
|
||||||
|
can_expand: true,
|
||||||
|
children_media_class: "movie",
|
||||||
|
thumbnail: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Music",
|
||||||
|
media_class: "album",
|
||||||
|
media_content_type: "",
|
||||||
|
media_content_id: "media-source://media_source/local/music",
|
||||||
|
can_play: false,
|
||||||
|
can_expand: true,
|
||||||
|
children_media_class: "music",
|
||||||
|
thumbnail: "/images/album_cover_2.jpg",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: "Subfolder",
|
||||||
|
media_class: "directory",
|
||||||
|
media_content_type: "",
|
||||||
|
media_content_id: "media-source://media_source/local/sub",
|
||||||
|
can_play: false,
|
||||||
|
can_expand: true,
|
||||||
|
children_media_class: "directory",
|
||||||
|
thumbnail: null,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: "audio.mp3",
|
||||||
|
media_class: "music",
|
||||||
|
media_content_type: "audio/mpeg",
|
||||||
|
media_content_id: "media-source://media_source/local/audio.mp3",
|
||||||
|
can_play: true,
|
||||||
|
can_expand: false,
|
||||||
|
children_media_class: null,
|
||||||
|
thumbnail: "/images/album_cover.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "image.jpg",
|
||||||
|
media_class: "image",
|
||||||
|
media_content_type: "image/jpeg",
|
||||||
|
media_content_id: "media-source://media_source/local/image.jpg",
|
||||||
|
can_play: true,
|
||||||
|
can_expand: false,
|
||||||
|
children_media_class: null,
|
||||||
|
thumbnail: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "movie.mp4",
|
||||||
|
media_class: "movie",
|
||||||
|
media_content_type: "image/jpeg",
|
||||||
|
media_content_id: "media-source://media_source/local/movie.mp4",
|
||||||
|
can_play: true,
|
||||||
|
can_expand: false,
|
||||||
|
children_media_class: null,
|
||||||
|
thumbnail: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _dialogManager = (e) => {
|
||||||
|
const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail;
|
||||||
|
showDialog(
|
||||||
|
this,
|
||||||
|
this.shadowRoot!,
|
||||||
|
dialogTag,
|
||||||
|
dialogParams,
|
||||||
|
dialogImport,
|
||||||
|
addHistory
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${SCHEMAS.map((info, idx) => {
|
${SCHEMAS.map((info, idx) => {
|
||||||
@ -117,7 +345,6 @@ class DemoHaSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
paper-input,
|
|
||||||
ha-selector {
|
ha-selector {
|
||||||
width: 60;
|
width: 60;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ const createConfigEntry = (
|
|||||||
source: "zeroconf",
|
source: "zeroconf",
|
||||||
state: "loaded",
|
state: "loaded",
|
||||||
supports_options: false,
|
supports_options: false,
|
||||||
|
supports_remove_device: false,
|
||||||
supports_unload: true,
|
supports_unload: true,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
pref_disable_new_entities: false,
|
pref_disable_new_entities: false,
|
||||||
|
@ -42,7 +42,9 @@ class HassioAddonRepositoryEl extends LitElement {
|
|||||||
const repo = this.repo;
|
const repo = this.repo;
|
||||||
let _addons = this.addons;
|
let _addons = this.addons;
|
||||||
if (!this.hass.userData?.showAdvanced) {
|
if (!this.hass.userData?.showAdvanced) {
|
||||||
_addons = _addons.filter((addon) => !addon.advanced);
|
_addons = _addons.filter(
|
||||||
|
(addon) => !addon.advanced && addon.stage === "stable"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const addons = this._getAddons(_addons, this.filter);
|
const addons = this._getAddons(_addons, this.filter);
|
||||||
|
|
||||||
|
@ -221,13 +221,14 @@ class HassioAddonStore extends LitElement {
|
|||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
.search {
|
.search {
|
||||||
padding: 0 16px;
|
position: sticky;
|
||||||
background: var(--sidebar-background-color);
|
top: 0;
|
||||||
border-bottom: 1px solid var(--divider-color);
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.search search-input {
|
search-input {
|
||||||
position: relative;
|
display: block;
|
||||||
top: 2px;
|
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
||||||
|
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||||
}
|
}
|
||||||
.advanced {
|
.advanced {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@ -11,10 +9,11 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "web-animations-js/web-animations-next-lite.min";
|
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
|
||||||
import "../../../../src/components/buttons/ha-progress-button";
|
import "../../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
|
import "../../../../src/components/ha-select";
|
||||||
import {
|
import {
|
||||||
HassioAddonDetails,
|
HassioAddonDetails,
|
||||||
HassioAddonSetOptionParams,
|
HassioAddonSetOptionParams,
|
||||||
@ -57,49 +56,44 @@ class HassioAddonAudio extends LitElement {
|
|||||||
${this._error
|
${this._error
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
|
${this._inputDevices &&
|
||||||
<paper-dropdown-menu
|
html`<ha-select
|
||||||
.label=${this.supervisor.localize(
|
.label=${this.supervisor.localize(
|
||||||
"addon.configuration.audio.input"
|
"addon.configuration.audio.input"
|
||||||
)}
|
)}
|
||||||
@iron-select=${this._setInputDevice}
|
@selected=${this._setInputDevice}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
|
.value=${this._selectedInput!}
|
||||||
>
|
>
|
||||||
<paper-listbox
|
${this._inputDevices.map(
|
||||||
slot="dropdown-content"
|
(item) => html`
|
||||||
attr-for-selected="device"
|
<mwc-list-item .value=${item.device || ""}>
|
||||||
.selected=${this._selectedInput!}
|
${item.name}
|
||||||
>
|
</mwc-list-item>
|
||||||
${this._inputDevices &&
|
`
|
||||||
this._inputDevices.map(
|
)}
|
||||||
(item) => html`
|
</ha-select>`}
|
||||||
<paper-item device=${item.device || ""}>
|
${this._outputDevices &&
|
||||||
${item.name}
|
html`<ha-select
|
||||||
</paper-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
<paper-dropdown-menu
|
|
||||||
.label=${this.supervisor.localize(
|
.label=${this.supervisor.localize(
|
||||||
"addon.configuration.audio.output"
|
"addon.configuration.audio.output"
|
||||||
)}
|
)}
|
||||||
@iron-select=${this._setOutputDevice}
|
@selected=${this._setOutputDevice}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
|
.value=${this._selectedOutput!}
|
||||||
>
|
>
|
||||||
<paper-listbox
|
${this._outputDevices.map(
|
||||||
slot="dropdown-content"
|
(item) => html`
|
||||||
attr-for-selected="device"
|
<mwc-list-item .value=${item.device || ""}
|
||||||
.selected=${this._selectedOutput!}
|
>${item.name}</mwc-list-item
|
||||||
>
|
>
|
||||||
${this._outputDevices &&
|
`
|
||||||
this._outputDevices.map(
|
)}
|
||||||
(item) => html`
|
</ha-select>`}
|
||||||
<paper-item device=${item.device || ""}
|
|
||||||
>${item.name}</paper-item
|
|
||||||
>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<ha-progress-button @click=${this._saveSettings}>
|
<ha-progress-button @click=${this._saveSettings}>
|
||||||
@ -116,8 +110,7 @@ class HassioAddonAudio extends LitElement {
|
|||||||
hassioStyle,
|
hassioStyle,
|
||||||
css`
|
css`
|
||||||
:host,
|
:host,
|
||||||
ha-card,
|
ha-card {
|
||||||
paper-dropdown-menu {
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
paper-item {
|
paper-item {
|
||||||
@ -126,24 +119,30 @@ class HassioAddonAudio extends LitElement {
|
|||||||
.card-actions {
|
.card-actions {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
ha-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
ha-select:last-child {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected update(changedProperties: PropertyValues): void {
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
super.update(changedProperties);
|
super.willUpdate(changedProperties);
|
||||||
if (changedProperties.has("addon")) {
|
if (changedProperties.has("addon")) {
|
||||||
this._addonChanged();
|
this._addonChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setInputDevice(ev): void {
|
private _setInputDevice(ev): void {
|
||||||
const device = ev.detail.item.getAttribute("device");
|
const device = ev.target.value;
|
||||||
this._selectedInput = device;
|
this._selectedInput = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setOutputDevice(ev): void {
|
private _setOutputDevice(ev): void {
|
||||||
const device = ev.detail.item.getAttribute("device");
|
const device = ev.target.value;
|
||||||
this._selectedOutput = device;
|
this._selectedOutput = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
mdiFlask,
|
mdiFlask,
|
||||||
mdiHomeAssistant,
|
mdiHomeAssistant,
|
||||||
mdiKey,
|
mdiKey,
|
||||||
|
mdiLinkLock,
|
||||||
mdiNetwork,
|
mdiNetwork,
|
||||||
mdiNumeric1,
|
mdiNumeric1,
|
||||||
mdiNumeric2,
|
mdiNumeric2,
|
||||||
@ -16,6 +17,8 @@ import {
|
|||||||
mdiNumeric4,
|
mdiNumeric4,
|
||||||
mdiNumeric5,
|
mdiNumeric5,
|
||||||
mdiNumeric6,
|
mdiNumeric6,
|
||||||
|
mdiNumeric7,
|
||||||
|
mdiNumeric8,
|
||||||
mdiPound,
|
mdiPound,
|
||||||
mdiShield,
|
mdiShield,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
@ -31,6 +34,7 @@ import "../../../../src/components/buttons/ha-progress-button";
|
|||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import "../../../../src/components/ha-chip";
|
import "../../../../src/components/ha-chip";
|
||||||
|
import "../../../../src/components/ha-chip-set";
|
||||||
import "../../../../src/components/ha-markdown";
|
import "../../../../src/components/ha-markdown";
|
||||||
import "../../../../src/components/ha-settings-row";
|
import "../../../../src/components/ha-settings-row";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
import "../../../../src/components/ha-svg-icon";
|
||||||
@ -84,6 +88,8 @@ const RATING_ICON = {
|
|||||||
4: mdiNumeric4,
|
4: mdiNumeric4,
|
||||||
5: mdiNumeric5,
|
5: mdiNumeric5,
|
||||||
6: mdiNumeric6,
|
6: mdiNumeric6,
|
||||||
|
7: mdiNumeric7,
|
||||||
|
8: mdiNumeric8,
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("hassio-addon-info")
|
@customElement("hassio-addon-info")
|
||||||
@ -209,7 +215,7 @@ class HassioAddonInfo extends LitElement {
|
|||||||
>`}
|
>`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="capabilities">
|
<ha-chip-set class="capabilities">
|
||||||
${this.addon.stage !== "stable"
|
${this.addon.stage !== "stable"
|
||||||
? html` <ha-chip
|
? html` <ha-chip
|
||||||
hasIcon
|
hasIcon
|
||||||
@ -234,9 +240,9 @@ class HassioAddonInfo extends LitElement {
|
|||||||
<ha-chip
|
<ha-chip
|
||||||
hasIcon
|
hasIcon
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
green: [5, 6].includes(Number(this.addon.rating)),
|
green: Number(this.addon.rating) >= 6,
|
||||||
yellow: [3, 4].includes(Number(this.addon.rating)),
|
yellow: [3, 4, 5].includes(Number(this.addon.rating)),
|
||||||
red: [1, 2].includes(Number(this.addon.rating)),
|
red: Number(this.addon.rating) >= 2,
|
||||||
})}
|
})}
|
||||||
@click=${this._showMoreInfo}
|
@click=${this._showMoreInfo}
|
||||||
id="rating"
|
id="rating"
|
||||||
@ -364,7 +370,17 @@ class HassioAddonInfo extends LitElement {
|
|||||||
</ha-chip>
|
</ha-chip>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
${this.addon.signed
|
||||||
|
? html`
|
||||||
|
<ha-chip hasIcon @click=${this._showMoreInfo} id="signed">
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiLinkLock}></ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
"addon.dashboard.capability.label.signed"
|
||||||
|
)}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</ha-chip-set>
|
||||||
|
|
||||||
<div class="description light-color">
|
<div class="description light-color">
|
||||||
${this.addon.description}.<br />
|
${this.addon.description}.<br />
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
import { formatDate } from "../../../src/common/datetime/format_date";
|
import { formatDate } from "../../../src/common/datetime/format_date";
|
||||||
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
||||||
@ -92,6 +92,8 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
|
|
||||||
@property() public confirmBackupPassword = "";
|
@property() public confirmBackupPassword = "";
|
||||||
|
|
||||||
|
@query("paper-input, ha-radio, ha-checkbox", true) private _focusTarget;
|
||||||
|
|
||||||
public willUpdate(changedProps) {
|
public willUpdate(changedProps) {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
@ -109,6 +111,10 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override focus() {
|
||||||
|
this._focusTarget?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
private _localize = (string: string) =>
|
private _localize = (string: string) =>
|
||||||
this.supervisor?.localize(`backup.${string}`) ||
|
this.supervisor?.localize(`backup.${string}`) ||
|
||||||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
||||||
@ -169,24 +175,23 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.backupType === "partial"
|
${this.backupType === "partial"
|
||||||
? html`<div class="partial-picker">
|
? html`<div class="partial-picker">
|
||||||
${this.backup && this.backup.homeassistant
|
<ha-formfield
|
||||||
? html`
|
.label=${html`<supervisor-formfield-label
|
||||||
<ha-formfield
|
label="Home Assistant"
|
||||||
.label=${html`<supervisor-formfield-label
|
.iconPath=${mdiHomeAssistant}
|
||||||
label="Home Assistant"
|
.version=${this.backup
|
||||||
.iconPath=${mdiHomeAssistant}
|
? this.backup.homeassistant
|
||||||
.version=${this.backup.homeassistant}
|
: this.hass.config.version}
|
||||||
>
|
>
|
||||||
</supervisor-formfield-label>`}
|
</supervisor-formfield-label>`}
|
||||||
>
|
>
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
.checked=${this.homeAssistant}
|
.checked=${this.homeAssistant}
|
||||||
@click=${this.toggleHomeAssistant}
|
@click=${this.toggleHomeAssistant}
|
||||||
>
|
>
|
||||||
</ha-checkbox>
|
</ha-checkbox>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${foldersSection?.templates.length
|
${foldersSection?.templates.length
|
||||||
? html`
|
? html`
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
|
@ -148,7 +148,6 @@ export class HassioUpdate extends LitElement {
|
|||||||
}
|
}
|
||||||
ha-settings-row {
|
ha-settings-row {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
--paper-item-body-two-line-min-height: 32px;
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -64,6 +64,7 @@ export class DialogHassioBackupUpload
|
|||||||
.path=${mdiClose}
|
.path=${mdiClose}
|
||||||
slot="actionItems"
|
slot="actionItems"
|
||||||
dialogAction="cancel"
|
dialogAction="cancel"
|
||||||
|
dialogInitialFocus
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</ha-header-bar>
|
</ha-header-bar>
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,6 +92,7 @@ class HassioBackupDialog
|
|||||||
.backup=${this._backup}
|
.backup=${this._backup}
|
||||||
.onboarding=${this._dialogParams.onboarding || false}
|
.onboarding=${this._dialogParams.onboarding || false}
|
||||||
.localize=${this._dialogParams.localize}
|
.localize=${this._dialogParams.localize}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</supervisor-backup-content>`}
|
</supervisor-backup-content>`}
|
||||||
${this._error
|
${this._error
|
||||||
|
@ -61,6 +61,7 @@ class HassioCreateBackupDialog extends LitElement {
|
|||||||
: html`<supervisor-backup-content
|
: html`<supervisor-backup-content
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.supervisor=${this._dialogParams.supervisor}
|
.supervisor=${this._dialogParams.supervisor}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</supervisor-backup-content>`}
|
</supervisor-backup-content>`}
|
||||||
${this._error
|
${this._error
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import "../../../../src/components/ha-circular-progress";
|
import "../../../../src/components/ha-circular-progress";
|
||||||
import "../../../../src/components/ha-markdown";
|
import "../../../../src/components/ha-markdown";
|
||||||
|
import "../../../../src/components/ha-select";
|
||||||
import {
|
import {
|
||||||
extractApiErrorMessage,
|
extractApiErrorMessage,
|
||||||
ignoreSupervisorError,
|
ignoreSupervisorError,
|
||||||
@ -90,18 +89,20 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
)}
|
)}
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<paper-dropdown-menu
|
<ha-select
|
||||||
.label=${this.dialogParams.supervisor.localize(
|
.label=${this.dialogParams.supervisor.localize(
|
||||||
"dialog.datadisk_move.select_device"
|
"dialog.datadisk_move.select_device"
|
||||||
)}
|
)}
|
||||||
@value-changed=${this._select_device}
|
@selected=${this._select_device}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
<paper-listbox slot="dropdown-content">
|
${this.devices.map(
|
||||||
${this.devices.map(
|
(device) =>
|
||||||
(device) => html`<paper-item>${device}</paper-item>`
|
html`<mwc-list-item .value=${device}
|
||||||
)}
|
>${device}</mwc-list-item
|
||||||
</paper-listbox>
|
>`
|
||||||
</paper-dropdown-menu>
|
)}
|
||||||
|
</ha-select>
|
||||||
`
|
`
|
||||||
: this.devices === undefined
|
: this.devices === undefined
|
||||||
? this.dialogParams.supervisor.localize(
|
? this.dialogParams.supervisor.localize(
|
||||||
@ -111,7 +112,11 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
"dialog.datadisk_move.no_devices"
|
"dialog.datadisk_move.no_devices"
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
<mwc-button
|
||||||
|
slot="secondaryAction"
|
||||||
|
@click=${this.closeDialog}
|
||||||
|
dialogInitialFocus
|
||||||
|
>
|
||||||
${this.dialogParams.supervisor.localize(
|
${this.dialogParams.supervisor.localize(
|
||||||
"dialog.datadisk_move.cancel"
|
"dialog.datadisk_move.cancel"
|
||||||
)}
|
)}
|
||||||
@ -130,8 +135,8 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _select_device(event) {
|
private _select_device(ev) {
|
||||||
this.selectedDevice = event.detail.value;
|
this.selectedDevice = ev.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _moveDatadisk() {
|
private async _moveDatadisk() {
|
||||||
@ -156,7 +161,7 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
haStyle,
|
haStyle,
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
paper-dropdown-menu {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
ha-circular-progress {
|
ha-circular-progress {
|
||||||
|
@ -80,7 +80,7 @@ class HassioHardwareDialog extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<search-input
|
<search-input
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
autofocus
|
dialogInitialFocus
|
||||||
no-label-float
|
no-label-float
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@value-changed=${this._handleSearchChange}
|
@value-changed=${this._handleSearchChange}
|
||||||
@ -178,7 +178,7 @@ class HassioHardwareDialog extends LitElement {
|
|||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
}
|
}
|
||||||
search-input {
|
search-input {
|
||||||
margin: 0 16px;
|
margin: 8px 16px 0;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.device-property {
|
.device-property {
|
||||||
|
@ -37,7 +37,10 @@ class HassioMarkdownDialog extends LitElement {
|
|||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
.heading=${createCloseHeading(this.hass, this.title)}
|
.heading=${createCloseHeading(this.hass, this.title)}
|
||||||
>
|
>
|
||||||
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
<ha-markdown
|
||||||
|
.content=${this.content || ""}
|
||||||
|
dialogInitialFocus
|
||||||
|
></ha-markdown>
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,7 @@ export class DialogHassioNetwork
|
|||||||
html`<mwc-tab
|
html`<mwc-tab
|
||||||
.id=${device.interface}
|
.id=${device.interface}
|
||||||
.label=${device.interface}
|
.label=${device.interface}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</mwc-tab>`
|
</mwc-tab>`
|
||||||
)}
|
)}
|
||||||
@ -315,6 +316,7 @@ export class DialogHassioNetwork
|
|||||||
value="auto"
|
value="auto"
|
||||||
name="${version}method"
|
name="${version}method"
|
||||||
.checked=${this._interface![version]?.method === "auto"}
|
.checked=${this._interface![version]?.method === "auto"}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</ha-radio>
|
</ha-radio>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
|
@ -19,22 +19,21 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
|||||||
import type { HomeAssistant } from "../../../../src/types";
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
import { RegistriesDialogParams } from "./show-dialog-registries";
|
import { RegistriesDialogParams } from "./show-dialog-registries";
|
||||||
|
|
||||||
const SCHEMA = [
|
const SCHEMA: HaFormSchema[] = [
|
||||||
{
|
{
|
||||||
type: "string",
|
|
||||||
name: "registry",
|
name: "registry",
|
||||||
required: true,
|
required: true,
|
||||||
|
selector: { text: {} },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "string",
|
|
||||||
name: "username",
|
name: "username",
|
||||||
required: true,
|
required: true,
|
||||||
|
selector: { text: {} },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "string",
|
|
||||||
name: "password",
|
name: "password",
|
||||||
required: true,
|
required: true,
|
||||||
format: "password",
|
selector: { text: { type: "password" } },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -81,6 +80,7 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
.schema=${SCHEMA}
|
.schema=${SCHEMA}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
.computeLabel=${this._computeLabel}
|
.computeLabel=${this._computeLabel}
|
||||||
|
dialogInitialFocus
|
||||||
></ha-form>
|
></ha-form>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@ -125,7 +125,7 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
</ha-alert>
|
</ha-alert>
|
||||||
`}
|
`}
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<mwc-button @click=${this._addRegistry}>
|
<mwc-button @click=${this._addRegistry} dialogInitialFocus>
|
||||||
${this.supervisor.localize(
|
${this.supervisor.localize(
|
||||||
"dialog.registries.add_new_registry"
|
"dialog.registries.add_new_registry"
|
||||||
)}
|
)}
|
||||||
|
@ -139,6 +139,7 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
"dialog.repositories.add"
|
"dialog.repositories.add"
|
||||||
)}
|
)}
|
||||||
@keydown=${this._handleKeyAdd}
|
@keydown=${this._handleKeyAdd}
|
||||||
|
dialogInitialFocus
|
||||||
></paper-input>
|
></paper-input>
|
||||||
<mwc-button @click=${this._addRepository}>
|
<mwc-button @click=${this._addRepository}>
|
||||||
${this._processing
|
${this._processing
|
||||||
|
@ -205,16 +205,6 @@ class HassioCoreInfo extends LitElement {
|
|||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
--mdc-menu-min-width: 200px;
|
--mdc-menu-min-width: 200px;
|
||||||
}
|
}
|
||||||
@media (min-width: 563px) {
|
|
||||||
paper-listbox {
|
|
||||||
max-height: 150px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
min-height: 35px;
|
|
||||||
}
|
|
||||||
mwc-list-item ha-svg-icon {
|
mwc-list-item ha-svg-icon {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
@ -440,16 +440,6 @@ class HassioHostInfo extends LitElement {
|
|||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
--mdc-menu-min-width: 200px;
|
--mdc-menu-min-width: 200px;
|
||||||
}
|
}
|
||||||
@media (min-width: 563px) {
|
|
||||||
paper-listbox {
|
|
||||||
max-height: 150px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
min-height: 35px;
|
|
||||||
}
|
|
||||||
mwc-list-item ha-svg-icon {
|
mwc-list-item ha-svg-icon {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../src/components/buttons/ha-progress-button";
|
import "../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../src/components/ha-alert";
|
import "../../../src/components/ha-alert";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
|
import "../../../src/components/ha-select";
|
||||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
||||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||||
@ -73,24 +71,19 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.hass.userData?.showAdvanced
|
${this.hass.userData?.showAdvanced
|
||||||
? html`
|
? html`
|
||||||
<paper-dropdown-menu
|
<ha-select
|
||||||
.label=${this.supervisor.localize("system.log.log_provider")}
|
.label=${this.supervisor.localize("system.log.log_provider")}
|
||||||
@iron-select=${this._setLogProvider}
|
@selected=${this._setLogProvider}
|
||||||
|
.value=${this._selectedLogProvider}
|
||||||
>
|
>
|
||||||
<paper-listbox
|
${logProviders.map(
|
||||||
slot="dropdown-content"
|
(provider) => html`
|
||||||
attr-for-selected="provider"
|
<mwc-list-item .value=${provider.key}>
|
||||||
.selected=${this._selectedLogProvider}
|
${provider.name}
|
||||||
>
|
</mwc-list-item>
|
||||||
${logProviders.map(
|
`
|
||||||
(provider) => html`
|
)}
|
||||||
<paper-item provider=${provider.key}>
|
</ha-select>
|
||||||
${provider.name}
|
|
||||||
</paper-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
@ -110,7 +103,7 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _setLogProvider(ev): Promise<void> {
|
private async _setLogProvider(ev): Promise<void> {
|
||||||
const provider = ev.detail.item.getAttribute("provider");
|
const provider = ev.target.value;
|
||||||
this._selectedLogProvider = provider;
|
this._selectedLogProvider = provider;
|
||||||
this._loadData();
|
this._loadData();
|
||||||
}
|
}
|
||||||
@ -153,9 +146,9 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
pre {
|
pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
paper-dropdown-menu {
|
ha-select {
|
||||||
padding: 0 2%;
|
width: 100%;
|
||||||
width: 96%;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import "../../../src/common/search/search-input";
|
|
||||||
import "../../../src/components/buttons/ha-progress-button";
|
import "../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../src/components/ha-alert";
|
import "../../../src/components/ha-alert";
|
||||||
import "../../../src/components/ha-button-menu";
|
import "../../../src/components/ha-button-menu";
|
||||||
@ -192,13 +191,7 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
</a>`
|
</a>`
|
||||||
: ""}
|
: ""}
|
||||||
<span></span>
|
<span></span>
|
||||||
<ha-progress-button
|
<ha-progress-button @click=${this._update} raised>
|
||||||
.disabled=${!this._version ||
|
|
||||||
(this._shouldCreateBackup &&
|
|
||||||
this.supervisor.info?.state !== "running")}
|
|
||||||
@click=${this._update}
|
|
||||||
raised
|
|
||||||
>
|
|
||||||
${this.supervisor.localize("common.update")}
|
${this.supervisor.localize("common.update")}
|
||||||
</ha-progress-button>
|
</ha-progress-button>
|
||||||
</div>
|
</div>
|
||||||
@ -360,8 +353,14 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _update() {
|
private async _update() {
|
||||||
|
if (this._shouldCreateBackup && this.supervisor.info.state === "freeze") {
|
||||||
|
this._error = this.supervisor.localize("backup.backup_already_running");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
this._updating = true;
|
this._updating = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this._updateType === "addon") {
|
if (this._updateType === "addon") {
|
||||||
await updateHassioAddon(
|
await updateHassioAddon(
|
||||||
|
39
package.json
39
package.json
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"description": "A frontend for Home Assistant using the Polymer framework",
|
"description": "A frontend for Home Assistant",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/home-assistant/home-assistant-polymer"
|
"url": "https://github.com/home-assistant/frontend"
|
||||||
},
|
},
|
||||||
"name": "home-assistant-frontend",
|
"name": "home-assistant-frontend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@ -22,17 +22,18 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^5.0.2",
|
"@braintree/sanitize-url": "^5.0.2",
|
||||||
"@codemirror/commands": "^0.19.5",
|
"@codemirror/autocomplete": "^0.19.12",
|
||||||
"@codemirror/gutter": "^0.19.4",
|
"@codemirror/commands": "^0.19.8",
|
||||||
"@codemirror/highlight": "^0.19.6",
|
"@codemirror/gutter": "^0.19.9",
|
||||||
"@codemirror/history": "^0.19.0",
|
"@codemirror/highlight": "^0.19.7",
|
||||||
|
"@codemirror/history": "^0.19.2",
|
||||||
"@codemirror/legacy-modes": "^0.19.0",
|
"@codemirror/legacy-modes": "^0.19.0",
|
||||||
"@codemirror/rectangular-selection": "^0.19.1",
|
"@codemirror/rectangular-selection": "^0.19.1",
|
||||||
"@codemirror/search": "^0.19.2",
|
"@codemirror/search": "^0.19.6",
|
||||||
"@codemirror/state": "^0.19.4",
|
"@codemirror/state": "^0.19.6",
|
||||||
"@codemirror/stream-parser": "^0.19.2",
|
"@codemirror/stream-parser": "^0.19.5",
|
||||||
"@codemirror/text": "^0.19.5",
|
"@codemirror/text": "^0.19.6",
|
||||||
"@codemirror/view": "^0.19.15",
|
"@codemirror/view": "^0.19.40",
|
||||||
"@formatjs/intl-datetimeformat": "^4.2.5",
|
"@formatjs/intl-datetimeformat": "^4.2.5",
|
||||||
"@formatjs/intl-getcanonicallocales": "^1.8.0",
|
"@formatjs/intl-getcanonicallocales": "^1.8.0",
|
||||||
"@formatjs/intl-locale": "^2.4.40",
|
"@formatjs/intl-locale": "^2.4.40",
|
||||||
@ -45,7 +46,8 @@
|
|||||||
"@fullcalendar/daygrid": "5.9.0",
|
"@fullcalendar/daygrid": "5.9.0",
|
||||||
"@fullcalendar/interaction": "5.9.0",
|
"@fullcalendar/interaction": "5.9.0",
|
||||||
"@fullcalendar/list": "5.9.0",
|
"@fullcalendar/list": "5.9.0",
|
||||||
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
|
"@lit-labs/motion": "^1.0.2",
|
||||||
|
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch",
|
||||||
"@material/chips": "14.0.0-canary.261f2db59.0",
|
"@material/chips": "14.0.0-canary.261f2db59.0",
|
||||||
"@material/data-table": "14.0.0-canary.261f2db59.0",
|
"@material/data-table": "14.0.0-canary.261f2db59.0",
|
||||||
"@material/mwc-button": "0.25.3",
|
"@material/mwc-button": "0.25.3",
|
||||||
@ -57,7 +59,7 @@
|
|||||||
"@material/mwc-formfield": "0.25.3",
|
"@material/mwc-formfield": "0.25.3",
|
||||||
"@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch",
|
"@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch",
|
||||||
"@material/mwc-linear-progress": "0.25.3",
|
"@material/mwc-linear-progress": "0.25.3",
|
||||||
"@material/mwc-list": "0.25.3",
|
"@material/mwc-list": "^0.25.3",
|
||||||
"@material/mwc-menu": "0.25.3",
|
"@material/mwc-menu": "0.25.3",
|
||||||
"@material/mwc-radio": "0.25.3",
|
"@material/mwc-radio": "0.25.3",
|
||||||
"@material/mwc-ripple": "0.25.3",
|
"@material/mwc-ripple": "0.25.3",
|
||||||
@ -66,6 +68,7 @@
|
|||||||
"@material/mwc-switch": "0.25.3",
|
"@material/mwc-switch": "0.25.3",
|
||||||
"@material/mwc-tab": "0.25.3",
|
"@material/mwc-tab": "0.25.3",
|
||||||
"@material/mwc-tab-bar": "0.25.3",
|
"@material/mwc-tab-bar": "0.25.3",
|
||||||
|
"@material/mwc-textarea": "^0.25.3",
|
||||||
"@material/mwc-textfield": "0.25.3",
|
"@material/mwc-textfield": "0.25.3",
|
||||||
"@material/mwc-top-app-bar-fixed": "^0.25.3",
|
"@material/mwc-top-app-bar-fixed": "^0.25.3",
|
||||||
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
|
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
|
||||||
@ -87,13 +90,15 @@
|
|||||||
"@polymer/paper-tooltip": "^3.0.1",
|
"@polymer/paper-tooltip": "^3.0.1",
|
||||||
"@polymer/polymer": "3.4.1",
|
"@polymer/polymer": "3.4.1",
|
||||||
"@thomasloven/round-slider": "0.5.4",
|
"@thomasloven/round-slider": "0.5.4",
|
||||||
"@vaadin/vaadin-combo-box": "^21.0.2",
|
"@vaadin/combo-box": "^22.0.4",
|
||||||
"@vaadin/vaadin-date-picker": "^21.0.2",
|
"@vaadin/vaadin-themable-mixin": "^22.0.4",
|
||||||
"@vibrant/color": "^3.2.1-alpha.1",
|
"@vibrant/color": "^3.2.1-alpha.1",
|
||||||
"@vibrant/core": "^3.2.1-alpha.1",
|
"@vibrant/core": "^3.2.1-alpha.1",
|
||||||
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
|
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
|
||||||
"@vue/web-component-wrapper": "^1.2.0",
|
"@vue/web-component-wrapper": "^1.2.0",
|
||||||
|
"@webcomponents/scoped-custom-element-registry": "^0.0.5",
|
||||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||||
|
"app-datepicker": "^5.0.1",
|
||||||
"chart.js": "^3.3.2",
|
"chart.js": "^3.3.2",
|
||||||
"comlink": "^4.3.1",
|
"comlink": "^4.3.1",
|
||||||
"core-js": "^3.15.2",
|
"core-js": "^3.15.2",
|
||||||
@ -103,7 +108,7 @@
|
|||||||
"deep-freeze": "^0.0.1",
|
"deep-freeze": "^0.0.1",
|
||||||
"fuse.js": "^6.0.0",
|
"fuse.js": "^6.0.0",
|
||||||
"google-timezones-json": "^1.0.2",
|
"google-timezones-json": "^1.0.2",
|
||||||
"hls.js": "^1.0.11",
|
"hls.js": "^1.1.5",
|
||||||
"home-assistant-js-websocket": "^6.0.1",
|
"home-assistant-js-websocket": "^6.0.1",
|
||||||
"idb-keyval": "^5.1.3",
|
"idb-keyval": "^5.1.3",
|
||||||
"intl-messageformat": "^9.9.1",
|
"intl-messageformat": "^9.9.1",
|
||||||
@ -111,7 +116,7 @@
|
|||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
"leaflet-draw": "^1.0.4",
|
"leaflet-draw": "^1.0.4",
|
||||||
"lit": "^2.1.2",
|
"lit": "^2.1.2",
|
||||||
"lit-vaadin-helpers": "^0.2.1",
|
"lit-vaadin-helpers": "^0.3.0",
|
||||||
"marked": "^3.0.2",
|
"marked": "^3.0.2",
|
||||||
"memoize-one": "^5.2.1",
|
"memoize-one": "^5.2.1",
|
||||||
"node-vibrant": "3.2.1-alpha.1",
|
"node-vibrant": "3.2.1-alpha.1",
|
||||||
|
18
script/core
18
script/core
@ -4,6 +4,8 @@
|
|||||||
# Stop on errors
|
# Stop on errors
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
WD="${WORKSPACE_DIRECTORY:=/workspaces/frontend}"
|
||||||
|
|
||||||
if [ -z "${DEVCONTAINER}" ]; then
|
if [ -z "${DEVCONTAINER}" ]; then
|
||||||
echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
|
echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
|
||||||
exit 1
|
exit 1
|
||||||
@ -16,9 +18,9 @@ if [ -z $(which hass) ]; then
|
|||||||
git+git://github.com/home-assistant/home-assistant.git@dev
|
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "/workspaces/frontend/config" ]; then
|
if [ ! -d "${WD}/config" ]; then
|
||||||
echo "Creating default configuration."
|
echo "Creating default configuration."
|
||||||
mkdir -p "/workspaces/frontend/config";
|
mkdir -p "${WD}/config";
|
||||||
hass --script ensure_config -c config
|
hass --script ensure_config -c config
|
||||||
echo "demo:
|
echo "demo:
|
||||||
|
|
||||||
@ -26,24 +28,24 @@ logger:
|
|||||||
default: info
|
default: info
|
||||||
logs:
|
logs:
|
||||||
homeassistant.components.frontend: debug
|
homeassistant.components.frontend: debug
|
||||||
" >> /workspaces/frontend/config/configuration.yaml
|
" >> "${WD}/config/configuration.yaml"
|
||||||
|
|
||||||
if [ ! -z "${HASSIO}" ]; then
|
if [ ! -z "${HASSIO}" ]; then
|
||||||
echo "
|
echo "
|
||||||
# frontend:
|
# frontend:
|
||||||
# development_repo: /workspaces/frontend
|
# development_repo: ${WD}
|
||||||
|
|
||||||
hassio:
|
hassio:
|
||||||
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||||
else
|
else
|
||||||
echo "
|
echo "
|
||||||
frontend:
|
frontend:
|
||||||
development_repo: /workspaces/frontend
|
development_repo: ${WD}
|
||||||
|
|
||||||
# hassio:
|
# hassio:
|
||||||
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
# development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
hass -c /workspaces/frontend/config
|
hass -c "${WD}/config"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = home-assistant-frontend
|
name = home-assistant-frontend
|
||||||
version = 20220203.1
|
version = 20220223.0
|
||||||
author = The Home Assistant Authors
|
author = The Home Assistant Authors
|
||||||
author_email = hello@home-assistant.io
|
author_email = hello@home-assistant.io
|
||||||
license = Apache-2.0
|
license = Apache-2.0
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { HaDurationData } from "../../components/ha-duration-input";
|
import type { HaDurationData } from "../../components/ha-duration-input";
|
||||||
import { ForDict } from "../../data/automation";
|
import type { ForDict } from "../../data/automation";
|
||||||
|
|
||||||
export const createDurationData = (
|
export const createDurationData = (
|
||||||
duration: string | number | ForDict | undefined
|
duration: string | number | ForDict | undefined
|
||||||
): HaDurationData => {
|
): HaDurationData | undefined => {
|
||||||
if (duration === undefined) {
|
if (duration === undefined) {
|
||||||
return {};
|
return undefined;
|
||||||
}
|
}
|
||||||
if (typeof duration !== "object") {
|
if (typeof duration !== "object") {
|
||||||
if (typeof duration === "string" || isNaN(duration)) {
|
if (typeof duration === "string" || isNaN(duration)) {
|
||||||
@ -19,6 +19,9 @@ export const createDurationData = (
|
|||||||
}
|
}
|
||||||
return { seconds: duration };
|
return { seconds: duration };
|
||||||
}
|
}
|
||||||
|
if (!("days" in duration)) {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
const { days, minutes, seconds, milliseconds } = duration;
|
const { days, minutes, seconds, milliseconds } = duration;
|
||||||
let hours = duration.hours || 0;
|
let hours = duration.hours || 0;
|
||||||
hours = (hours || 0) + (days || 0) * 24;
|
hours = (hours || 0) + (days || 0) * 24;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
export const canToggleDomain = (hass: HomeAssistant, domain: string) => {
|
export const canToggleDomain = (hass: HomeAssistant, domain: string) => {
|
||||||
const services = hass.services[domain];
|
const services = hass.services[domain];
|
||||||
|
@ -1,14 +1,30 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { canToggleDomain } from "./can_toggle_domain";
|
import { canToggleDomain } from "./can_toggle_domain";
|
||||||
import { computeStateDomain } from "./compute_state_domain";
|
import { computeStateDomain } from "./compute_state_domain";
|
||||||
import { supportsFeature } from "./supports-feature";
|
import { supportsFeature } from "./supports-feature";
|
||||||
|
|
||||||
export const canToggleState = (hass: HomeAssistant, stateObj: HassEntity) => {
|
export const canToggleState = (hass: HomeAssistant, stateObj: HassEntity) => {
|
||||||
const domain = computeStateDomain(stateObj);
|
const domain = computeStateDomain(stateObj);
|
||||||
|
|
||||||
if (domain === "group") {
|
if (domain === "group") {
|
||||||
return stateObj.state === "on" || stateObj.state === "off";
|
if (
|
||||||
|
stateObj.attributes?.entity_id?.some((entity) => {
|
||||||
|
const entityStateObj = hass.states[entity];
|
||||||
|
if (!entityStateObj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityDomain = computeStateDomain(entityStateObj);
|
||||||
|
return canToggleDomain(hass, entityDomain);
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return stateObj.state === "on" || stateObj.state === "off";
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domain === "climate") {
|
if (domain === "climate") {
|
||||||
return supportsFeature(stateObj, 4096);
|
return supportsFeature(stateObj, 4096);
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,7 @@ export const computeOpenIcon = (stateObj: HassEntity): string => {
|
|||||||
case "awning":
|
case "awning":
|
||||||
case "door":
|
case "door":
|
||||||
case "gate":
|
case "gate":
|
||||||
|
case "curtain":
|
||||||
return mdiArrowExpandHorizontal;
|
return mdiArrowExpandHorizontal;
|
||||||
default:
|
default:
|
||||||
return mdiArrowUp;
|
return mdiArrowUp;
|
||||||
@ -131,6 +132,7 @@ export const computeCloseIcon = (stateObj: HassEntity): string => {
|
|||||||
case "awning":
|
case "awning":
|
||||||
case "door":
|
case "door":
|
||||||
case "gate":
|
case "gate":
|
||||||
|
case "curtain":
|
||||||
return mdiArrowCollapseHorizontal;
|
return mdiArrowCollapseHorizontal;
|
||||||
default:
|
default:
|
||||||
return mdiArrowDown;
|
return mdiArrowDown;
|
||||||
|
@ -1,24 +1,32 @@
|
|||||||
|
const SUFFIXES = [" ", ": "];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strips a device name from an entity name.
|
* Strips a device name from an entity name.
|
||||||
* @param entityName the entity name
|
* @param entityName the entity name
|
||||||
* @param lowerCasedPrefixWithSpaceSuffix the prefix to strip, lower cased with a space suffix
|
* @param lowerCasedPrefix the prefix to strip, lower cased
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const stripPrefixFromEntityName = (
|
export const stripPrefixFromEntityName = (
|
||||||
entityName: string,
|
entityName: string,
|
||||||
lowerCasedPrefixWithSpaceSuffix: string
|
lowerCasedPrefix: string
|
||||||
) => {
|
) => {
|
||||||
if (!entityName.toLowerCase().startsWith(lowerCasedPrefixWithSpaceSuffix)) {
|
const lowerCasedEntityName = entityName.toLowerCase();
|
||||||
return undefined;
|
|
||||||
|
for (const suffix of SUFFIXES) {
|
||||||
|
const lowerCasedPrefixWithSuffix = `${lowerCasedPrefix}${suffix}`;
|
||||||
|
|
||||||
|
if (lowerCasedEntityName.startsWith(lowerCasedPrefixWithSuffix)) {
|
||||||
|
const newName = entityName.substring(lowerCasedPrefixWithSuffix.length);
|
||||||
|
|
||||||
|
// If first word already has an upper case letter (e.g. from brand name)
|
||||||
|
// leave as-is, otherwise capitalize the first word.
|
||||||
|
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
|
||||||
|
? newName
|
||||||
|
: newName[0].toUpperCase() + newName.slice(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const newName = entityName.substring(lowerCasedPrefixWithSpaceSuffix.length);
|
return undefined;
|
||||||
|
|
||||||
// If first word already has an upper case letter (e.g. from brand name)
|
|
||||||
// leave as-is, otherwise capitalize the first word.
|
|
||||||
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
|
|
||||||
? newName
|
|
||||||
: newName[0].toUpperCase() + newName.slice(1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
|
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
import { mdiClose, mdiMagnify } from "@mdi/js";
|
import { mdiClose, mdiMagnify } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import "../../components/ha-icon-button";
|
import "../../components/ha-icon-button";
|
||||||
import "../../components/ha-svg-icon";
|
import "../../components/ha-svg-icon";
|
||||||
|
import "../../components/ha-textfield";
|
||||||
|
import type { HaTextField } from "../../components/ha-textfield";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { fireEvent } from "../dom/fire_event";
|
import { fireEvent } from "../dom/fire_event";
|
||||||
|
|
||||||
@ -21,11 +14,8 @@ class SearchInput extends LitElement {
|
|||||||
|
|
||||||
@property() public filter?: string;
|
@property() public filter?: string;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "no-label-float" })
|
@property({ type: Boolean })
|
||||||
public noLabelFloat? = false;
|
public suffix = false;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "no-underline" })
|
|
||||||
public noUnderline = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
public autofocus = false;
|
public autofocus = false;
|
||||||
@ -34,49 +24,44 @@ class SearchInput extends LitElement {
|
|||||||
public label?: string;
|
public label?: string;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
this.shadowRoot!.querySelector("paper-input")!.focus();
|
this._input?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@query("paper-input", true) private _input!: PaperInputElement;
|
@query("ha-textfield", true) private _input!: HaTextField;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.autofocus=${this.autofocus}
|
.autofocus=${this.autofocus}
|
||||||
.label=${this.label || "Search"}
|
.label=${this.label || "Search"}
|
||||||
.value=${this.filter}
|
.value=${this.filter || ""}
|
||||||
@value-changed=${this._filterInputChanged}
|
.icon=${true}
|
||||||
.noLabelFloat=${this.noLabelFloat}
|
.iconTrailing=${this.filter || this.suffix}
|
||||||
|
@input=${this._filterInputChanged}
|
||||||
>
|
>
|
||||||
<slot name="prefix" slot="prefix">
|
<slot name="prefix" slot="leadingIcon">
|
||||||
<ha-svg-icon class="prefix" .path=${mdiMagnify}></ha-svg-icon>
|
<ha-svg-icon
|
||||||
|
tabindex="-1"
|
||||||
|
class="prefix"
|
||||||
|
.path=${mdiMagnify}
|
||||||
|
></ha-svg-icon>
|
||||||
</slot>
|
</slot>
|
||||||
${this.filter &&
|
<div class="trailing" slot="trailingIcon">
|
||||||
html`
|
${this.filter &&
|
||||||
<ha-icon-button
|
html`
|
||||||
slot="suffix"
|
<ha-icon-button
|
||||||
@click=${this._clearSearch}
|
@click=${this._clearSearch}
|
||||||
.label=${this.hass.localize("ui.common.clear")}
|
.label=${this.hass.localize("ui.common.clear")}
|
||||||
.path=${mdiClose}
|
.path=${mdiClose}
|
||||||
></ha-icon-button>
|
class="clear-button"
|
||||||
`}
|
></ha-icon-button>
|
||||||
</paper-input>
|
`}
|
||||||
|
<slot name="suffix"></slot>
|
||||||
|
</div>
|
||||||
|
</ha-textfield>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
|
||||||
if (
|
|
||||||
changedProps.has("noUnderline") &&
|
|
||||||
(this.noUnderline || changedProps.get("noUnderline") !== undefined)
|
|
||||||
) {
|
|
||||||
(
|
|
||||||
this._input.inputElement!.parentElement!.shadowRoot!.querySelector(
|
|
||||||
"div.unfocused-line"
|
|
||||||
) as HTMLElement
|
|
||||||
).style.display = this.noUnderline ? "none" : "block";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _filterChanged(value: string) {
|
private async _filterChanged(value: string) {
|
||||||
fireEvent(this, "value-changed", { value: String(value) });
|
fireEvent(this, "value-changed", { value: String(value) });
|
||||||
}
|
}
|
||||||
@ -91,15 +76,25 @@ class SearchInput extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
|
:host {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
ha-svg-icon,
|
ha-svg-icon,
|
||||||
ha-icon-button {
|
ha-icon-button {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
ha-icon-button {
|
ha-svg-icon {
|
||||||
--mdc-icon-button-size: 24px;
|
outline: none;
|
||||||
}
|
}
|
||||||
ha-svg-icon.prefix {
|
.clear-button {
|
||||||
margin: 8px;
|
--mdc-icon-size: 20px;
|
||||||
|
}
|
||||||
|
ha-textfield {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
.trailing {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ export const iconColorCSS = css`
|
|||||||
ha-state-icon[data-domain="media_player"][data-state="on"],
|
ha-state-icon[data-domain="media_player"][data-state="on"],
|
||||||
ha-state-icon[data-domain="media_player"][data-state="paused"],
|
ha-state-icon[data-domain="media_player"][data-state="paused"],
|
||||||
ha-state-icon[data-domain="media_player"][data-state="playing"],
|
ha-state-icon[data-domain="media_player"][data-state="playing"],
|
||||||
|
ha-state-icon[data-domain="remote"][data-state="on"],
|
||||||
ha-state-icon[data-domain="script"][data-state="on"],
|
ha-state-icon[data-domain="script"][data-state="on"],
|
||||||
ha-state-icon[data-domain="sun"][data-state="above_horizon"],
|
ha-state-icon[data-domain="sun"][data-state="above_horizon"],
|
||||||
ha-state-icon[data-domain="switch"][data-state="on"],
|
ha-state-icon[data-domain="switch"][data-state="on"],
|
||||||
|
@ -11,7 +11,7 @@ export const debounce = <T extends any[]>(
|
|||||||
immediate = false
|
immediate = false
|
||||||
) => {
|
) => {
|
||||||
let timeout: number | undefined;
|
let timeout: number | undefined;
|
||||||
return (...args: T): void => {
|
const debouncedFunc = (...args: T): void => {
|
||||||
const later = () => {
|
const later = () => {
|
||||||
timeout = undefined;
|
timeout = undefined;
|
||||||
if (!immediate) {
|
if (!immediate) {
|
||||||
@ -25,4 +25,8 @@ export const debounce = <T extends any[]>(
|
|||||||
func(...args);
|
func(...args);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
debouncedFunc.cancel = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
return debouncedFunc;
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Layout1d, scroll } from "@lit-labs/virtualizer";
|
|
||||||
import { mdiArrowDown, mdiArrowUp } from "@mdi/js";
|
import { mdiArrowDown, mdiArrowUp } from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import {
|
import {
|
||||||
@ -31,6 +30,7 @@ import type { HaCheckbox } from "../ha-checkbox";
|
|||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import { filterData, sortData } from "./sort-filter";
|
import { filterData, sortData } from "./sort-filter";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "@lit-labs/virtualizer";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@ -70,6 +70,7 @@ export interface DataTableSortColumnData {
|
|||||||
|
|
||||||
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
||||||
title: TemplateResult | string;
|
title: TemplateResult | string;
|
||||||
|
label?: TemplateResult | string;
|
||||||
type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
|
type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
|
||||||
template?: (data: any, row: T) => TemplateResult | string;
|
template?: (data: any, row: T) => TemplateResult | string;
|
||||||
width?: string;
|
width?: string;
|
||||||
@ -294,6 +295,7 @@ export class HaDataTable extends LitElement {
|
|||||||
};
|
};
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
|
aria-label=${column.label}
|
||||||
class="mdc-data-table__header-cell ${classMap(classes)}"
|
class="mdc-data-table__header-cell ${classMap(classes)}"
|
||||||
style=${column.width
|
style=${column.width
|
||||||
? styleMap({
|
? styleMap({
|
||||||
@ -337,111 +339,99 @@ export class HaDataTable extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<div
|
<lit-virtualizer
|
||||||
|
scroller
|
||||||
class="mdc-data-table__content scroller ha-scrollbar"
|
class="mdc-data-table__content scroller ha-scrollbar"
|
||||||
@scroll=${this._saveScrollPos}
|
@scroll=${this._saveScrollPos}
|
||||||
>
|
.items=${this._items}
|
||||||
${scroll({
|
.renderItem=${this._renderRow}
|
||||||
items: this._items,
|
></lit-virtualizer>
|
||||||
layout: Layout1d,
|
|
||||||
renderItem: (row: DataTableRowData, index) => {
|
|
||||||
// not sure how this happens...
|
|
||||||
if (!row) {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
if (row.append) {
|
|
||||||
return html`
|
|
||||||
<div class="mdc-data-table__row">${row.content}</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
if (row.empty) {
|
|
||||||
return html` <div class="mdc-data-table__row"></div> `;
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<div
|
|
||||||
aria-rowindex=${index! + 2}
|
|
||||||
role="row"
|
|
||||||
.rowId=${row[this.id]}
|
|
||||||
@click=${this._handleRowClick}
|
|
||||||
class="mdc-data-table__row ${classMap({
|
|
||||||
"mdc-data-table__row--selected":
|
|
||||||
this._checkedRows.includes(String(row[this.id])),
|
|
||||||
clickable: this.clickable,
|
|
||||||
})}"
|
|
||||||
aria-selected=${ifDefined(
|
|
||||||
this._checkedRows.includes(String(row[this.id]))
|
|
||||||
? true
|
|
||||||
: undefined
|
|
||||||
)}
|
|
||||||
.selectable=${row.selectable !== false}
|
|
||||||
>
|
|
||||||
${this.selectable
|
|
||||||
? html`
|
|
||||||
<div
|
|
||||||
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
|
|
||||||
role="cell"
|
|
||||||
>
|
|
||||||
<ha-checkbox
|
|
||||||
class="mdc-data-table__row-checkbox"
|
|
||||||
@change=${this._handleRowCheckboxClick}
|
|
||||||
.rowId=${row[this.id]}
|
|
||||||
.disabled=${row.selectable === false}
|
|
||||||
.checked=${this._checkedRows.includes(
|
|
||||||
String(row[this.id])
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ha-checkbox>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${Object.entries(this.columns).map(
|
|
||||||
([key, column]) => {
|
|
||||||
if (column.hidden) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<div
|
|
||||||
role="cell"
|
|
||||||
class="mdc-data-table__cell ${classMap({
|
|
||||||
"mdc-data-table__cell--numeric":
|
|
||||||
column.type === "numeric",
|
|
||||||
"mdc-data-table__cell--icon":
|
|
||||||
column.type === "icon",
|
|
||||||
"mdc-data-table__cell--icon-button":
|
|
||||||
column.type === "icon-button",
|
|
||||||
"mdc-data-table__cell--overflow-menu":
|
|
||||||
column.type === "overflow-menu",
|
|
||||||
grows: Boolean(column.grows),
|
|
||||||
forceLTR: Boolean(column.forceLTR),
|
|
||||||
})}"
|
|
||||||
style=${column.width
|
|
||||||
? styleMap({
|
|
||||||
[column.grows ? "minWidth" : "width"]:
|
|
||||||
column.width,
|
|
||||||
maxWidth: column.maxWidth
|
|
||||||
? column.maxWidth
|
|
||||||
: "",
|
|
||||||
})
|
|
||||||
: ""}
|
|
||||||
>
|
|
||||||
${column.template
|
|
||||||
? column.template(row[key], row)
|
|
||||||
: row[key]}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderRow = (
|
||||||
|
row: DataTableRowData,
|
||||||
|
index: number
|
||||||
|
): TemplateResult => {
|
||||||
|
// not sure how this happens...
|
||||||
|
if (!row) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
if (row.append) {
|
||||||
|
return html` <div class="mdc-data-table__row">${row.content}</div> `;
|
||||||
|
}
|
||||||
|
if (row.empty) {
|
||||||
|
return html` <div class="mdc-data-table__row"></div> `;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
aria-rowindex=${index + 2}
|
||||||
|
role="row"
|
||||||
|
.rowId=${row[this.id]}
|
||||||
|
@click=${this._handleRowClick}
|
||||||
|
class="mdc-data-table__row ${classMap({
|
||||||
|
"mdc-data-table__row--selected": this._checkedRows.includes(
|
||||||
|
String(row[this.id])
|
||||||
|
),
|
||||||
|
clickable: this.clickable,
|
||||||
|
})}"
|
||||||
|
aria-selected=${ifDefined(
|
||||||
|
this._checkedRows.includes(String(row[this.id])) ? true : undefined
|
||||||
|
)}
|
||||||
|
.selectable=${row.selectable !== false}
|
||||||
|
>
|
||||||
|
${this.selectable
|
||||||
|
? html`
|
||||||
|
<div
|
||||||
|
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
|
||||||
|
role="cell"
|
||||||
|
>
|
||||||
|
<ha-checkbox
|
||||||
|
class="mdc-data-table__row-checkbox"
|
||||||
|
@change=${this._handleRowCheckboxClick}
|
||||||
|
.rowId=${row[this.id]}
|
||||||
|
.disabled=${row.selectable === false}
|
||||||
|
.checked=${this._checkedRows.includes(String(row[this.id]))}
|
||||||
|
>
|
||||||
|
</ha-checkbox>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${Object.entries(this.columns).map(([key, column]) => {
|
||||||
|
if (column.hidden) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
role="cell"
|
||||||
|
class="mdc-data-table__cell ${classMap({
|
||||||
|
"mdc-data-table__cell--numeric": column.type === "numeric",
|
||||||
|
"mdc-data-table__cell--icon": column.type === "icon",
|
||||||
|
"mdc-data-table__cell--icon-button":
|
||||||
|
column.type === "icon-button",
|
||||||
|
"mdc-data-table__cell--overflow-menu":
|
||||||
|
column.type === "overflow-menu",
|
||||||
|
grows: Boolean(column.grows),
|
||||||
|
forceLTR: Boolean(column.forceLTR),
|
||||||
|
})}"
|
||||||
|
style=${column.width
|
||||||
|
? styleMap({
|
||||||
|
[column.grows ? "minWidth" : "width"]: column.width,
|
||||||
|
maxWidth: column.maxWidth ? column.maxWidth : "",
|
||||||
|
})
|
||||||
|
: ""}
|
||||||
|
>
|
||||||
|
${column.template ? column.template(row[key], row) : row[key]}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
private async _sortFilterData() {
|
private async _sortFilterData() {
|
||||||
const startTime = new Date().getTime();
|
const startTime = new Date().getTime();
|
||||||
this.curRequest++;
|
this.curRequest++;
|
||||||
@ -536,7 +526,7 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleRowCheckboxClick(ev: Event) {
|
private _handleRowCheckboxClick = (ev: Event) => {
|
||||||
const checkbox = ev.currentTarget as HaCheckbox;
|
const checkbox = ev.currentTarget as HaCheckbox;
|
||||||
const rowId = (checkbox as any).rowId;
|
const rowId = (checkbox as any).rowId;
|
||||||
|
|
||||||
@ -549,16 +539,16 @@ export class HaDataTable extends LitElement {
|
|||||||
this._checkedRows = this._checkedRows.filter((row) => row !== rowId);
|
this._checkedRows = this._checkedRows.filter((row) => row !== rowId);
|
||||||
}
|
}
|
||||||
this._checkedRowsChanged();
|
this._checkedRowsChanged();
|
||||||
}
|
};
|
||||||
|
|
||||||
private _handleRowClick(ev: Event) {
|
private _handleRowClick = (ev: Event) => {
|
||||||
const target = ev.target as HTMLElement;
|
const target = ev.target as HTMLElement;
|
||||||
if (["HA-CHECKBOX", "MWC-BUTTON"].includes(target.tagName)) {
|
if (["HA-CHECKBOX", "MWC-BUTTON"].includes(target.tagName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rowId = (ev.currentTarget as any).rowId;
|
const rowId = (ev.currentTarget as any).rowId;
|
||||||
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
|
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
private _checkedRowsChanged() {
|
private _checkedRowsChanged() {
|
||||||
// force scroller to update, change it's items
|
// force scroller to update, change it's items
|
||||||
@ -571,6 +561,9 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleSearchChange(ev: CustomEvent): void {
|
private _handleSearchChange(ev: CustomEvent): void {
|
||||||
|
if (this.filter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._debounceSearch(ev.detail.value);
|
this._debounceSearch(ev.detail.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -935,11 +928,10 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
.table-header {
|
.table-header {
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
padding: 0 16px;
|
|
||||||
}
|
}
|
||||||
search-input {
|
search-input {
|
||||||
position: relative;
|
display: block;
|
||||||
top: 2px;
|
flex: 1;
|
||||||
}
|
}
|
||||||
slot[name="header"] {
|
slot[name="header"] {
|
||||||
display: block;
|
display: block;
|
||||||
@ -952,6 +944,7 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
.scroller {
|
.scroller {
|
||||||
height: calc(100% - 57px);
|
height: calc(100% - 57px);
|
||||||
|
overflow: overlay !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__table.auto-height .scroller {
|
.mdc-data-table__table.auto-height .scroller {
|
||||||
@ -967,6 +960,9 @@ export class HaDataTable extends LitElement {
|
|||||||
.clickable {
|
.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
lit-virtualizer {
|
||||||
|
contain: size layout !important;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,7 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
@ -50,36 +37,12 @@ interface AreaDevices {
|
|||||||
devices: string[];
|
devices: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
const rowRenderer: ComboBoxLitRenderer<AreaDevices> = (
|
||||||
const rowRenderer: ComboBoxLitRenderer<AreaDevices> = (item) => html`<style>
|
item
|
||||||
paper-item {
|
) => html`<mwc-list-item twoline>
|
||||||
padding: 0;
|
<span>${item.name}</span>
|
||||||
margin: -10px;
|
<span slot="secondary">${item.devices.length} devices</span>
|
||||||
margin-left: 0;
|
</mwc-list-item>`;
|
||||||
}
|
|
||||||
#content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
margin-right: -2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-item {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-item>
|
|
||||||
<paper-item-body two-line="">
|
|
||||||
<div class="name">${item.name}</div>
|
|
||||||
<div secondary>${item.devices.length} devices</div>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-area-devices-picker")
|
@customElement("ha-area-devices-picker")
|
||||||
export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||||
@ -117,9 +80,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
@property({ type: Array, attribute: "include-device-classes" })
|
@property({ type: Array, attribute: "include-device-classes" })
|
||||||
public includeDeviceClasses?: string[];
|
public includeDeviceClasses?: string[];
|
||||||
|
|
||||||
@property({ type: Boolean })
|
|
||||||
private _opened?: boolean;
|
|
||||||
|
|
||||||
@state() private _areaPicker = true;
|
@state() private _areaPicker = true;
|
||||||
|
|
||||||
@state() private _devices?: DeviceRegistryEntry[];
|
@state() private _devices?: DeviceRegistryEntry[];
|
||||||
@ -302,71 +262,30 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<vaadin-combo-box-light
|
<ha-combo-box
|
||||||
|
.hass=${this.hass}
|
||||||
item-value-path="id"
|
item-value-path="id"
|
||||||
item-id-path="id"
|
item-id-path="id"
|
||||||
item-label-path="name"
|
item-label-path="name"
|
||||||
.items=${areas}
|
.items=${areas}
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
${comboBoxRenderer(rowRenderer)}
|
.renderer=${rowRenderer}
|
||||||
@opened-changed=${this._openedChanged}
|
.label=${this.label === undefined && this.hass
|
||||||
|
? this.hass.localize("ui.components.device-picker.device")
|
||||||
|
: `${this.label} in area`}
|
||||||
@value-changed=${this._areaPicked}
|
@value-changed=${this._areaPicked}
|
||||||
>
|
>
|
||||||
<paper-input
|
</ha-combo-box>
|
||||||
.label=${this.label === undefined && this.hass
|
<mwc-button @click=${this._switchPicker}>
|
||||||
? this.hass.localize("ui.components.device-picker.device")
|
Choose individual devices
|
||||||
: `${this.label} in area`}
|
</mwc-button>
|
||||||
class="input"
|
|
||||||
autocapitalize="none"
|
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
>
|
|
||||||
<div class="suffix" slot="suffix">
|
|
||||||
${this.value
|
|
||||||
? html`<ha-icon-button
|
|
||||||
class="clear-button"
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.device-picker.clear"
|
|
||||||
)}
|
|
||||||
.path=${mdiClose}
|
|
||||||
@click=${this._clearValue}
|
|
||||||
no-ripple
|
|
||||||
></ha-icon-button> `
|
|
||||||
: ""}
|
|
||||||
${areas.length > 0
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.device-picker.show_devices"
|
|
||||||
)}
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
class="toggle-button"
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
</paper-input>
|
|
||||||
</vaadin-combo-box-light>
|
|
||||||
<mwc-button @click=${this._switchPicker}
|
|
||||||
>Choose individual devices</mwc-button
|
|
||||||
>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _clearValue(ev: Event) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._setValue([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
return this.value || [];
|
return this.value || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
|
||||||
this._opened = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _switchPicker() {
|
private async _switchPicker() {
|
||||||
this._areaPicker = !this._areaPicker;
|
this._areaPicker = !this._areaPicker;
|
||||||
}
|
}
|
||||||
@ -398,22 +317,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
.suffix {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 0px 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
@ -10,7 +7,7 @@ import {
|
|||||||
deviceAutomationsEqual,
|
deviceAutomationsEqual,
|
||||||
} from "../../data/device_automation";
|
} from "../../data/device_automation";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-paper-dropdown-menu";
|
import "../ha-select";
|
||||||
|
|
||||||
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
||||||
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
|
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
|
||||||
@ -67,14 +64,12 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
this._createNoAutomation = createNoAutomation;
|
this._createNoAutomation = createNoAutomation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _key() {
|
private get _value() {
|
||||||
if (
|
if (!this.value) {
|
||||||
!this.value ||
|
return "";
|
||||||
deviceAutomationsEqual(
|
}
|
||||||
this._createNoAutomation(this.deviceId),
|
|
||||||
this.value
|
if (!this._automations.length) {
|
||||||
)
|
|
||||||
) {
|
|
||||||
return NO_AUTOMATION_KEY;
|
return NO_AUTOMATION_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,42 +88,32 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
if (this._renderEmpty) {
|
if (this._renderEmpty) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
const value = this._value;
|
||||||
return html`
|
return html`
|
||||||
<ha-paper-dropdown-menu
|
<ha-select
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${this.value
|
.value=${value}
|
||||||
? this._localizeDeviceAutomation(this.hass, this.value)
|
@selected=${this._automationChanged}
|
||||||
: ""}
|
.disabled=${this._automations.length === 0}
|
||||||
?disabled=${this._automations.length === 0}
|
|
||||||
>
|
>
|
||||||
<paper-listbox
|
${value === NO_AUTOMATION_KEY
|
||||||
slot="dropdown-content"
|
? html`<mwc-list-item .value=${NO_AUTOMATION_KEY}>
|
||||||
.selected=${this._key}
|
${this.NO_AUTOMATION_TEXT}
|
||||||
attr-for-selected="key"
|
</mwc-list-item>`
|
||||||
@iron-select=${this._automationChanged}
|
: ""}
|
||||||
>
|
${value === UNKNOWN_AUTOMATION_KEY
|
||||||
<paper-item
|
? html`<mwc-list-item .value=${UNKNOWN_AUTOMATION_KEY}>
|
||||||
key=${NO_AUTOMATION_KEY}
|
${this.UNKNOWN_AUTOMATION_TEXT}
|
||||||
.automation=${this._createNoAutomation(this.deviceId)}
|
</mwc-list-item>`
|
||||||
hidden
|
: ""}
|
||||||
>
|
${this._automations.map(
|
||||||
${this.NO_AUTOMATION_TEXT}
|
(automation, idx) => html`
|
||||||
</paper-item>
|
<mwc-list-item .value=${`${automation.device_id}_${idx}`}>
|
||||||
<paper-item key=${UNKNOWN_AUTOMATION_KEY} hidden>
|
${this._localizeDeviceAutomation(this.hass, automation)}
|
||||||
${this.UNKNOWN_AUTOMATION_TEXT}
|
</mwc-list-item>
|
||||||
</paper-item>
|
`
|
||||||
${this._automations.map(
|
)}
|
||||||
(automation, idx) => html`
|
</ha-select>
|
||||||
<paper-item
|
|
||||||
key=${`${this.deviceId}_${idx}`}
|
|
||||||
.automation=${automation}
|
|
||||||
>
|
|
||||||
${this._localizeDeviceAutomation(this.hass, automation)}
|
|
||||||
</paper-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</paper-listbox>
|
|
||||||
</ha-paper-dropdown-menu>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,14 +123,6 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
if (changedProps.has("deviceId")) {
|
if (changedProps.has("deviceId")) {
|
||||||
this._updateDeviceInfo();
|
this._updateDeviceInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The value has changed, force the listbox to update
|
|
||||||
if (changedProps.has("value") || changedProps.has("_renderEmpty")) {
|
|
||||||
const listbox = this.shadowRoot!.querySelector("paper-listbox")!;
|
|
||||||
if (listbox) {
|
|
||||||
listbox._selectSelected(this._key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updateDeviceInfo() {
|
private async _updateDeviceInfo() {
|
||||||
@ -168,9 +145,16 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _automationChanged(ev) {
|
private _automationChanged(ev) {
|
||||||
if (ev.detail.item.automation) {
|
const value = ev.target.value;
|
||||||
this._setValue(ev.detail.item.automation);
|
if (!value || [UNKNOWN_AUTOMATION_KEY, NO_AUTOMATION_KEY].includes(value)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const [deviceId, idx] = value.split("_");
|
||||||
|
const automation = this._automations[idx];
|
||||||
|
if (automation.device_id !== deviceId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._setValue(automation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setValue(automation: T) {
|
private _setValue(automation: T) {
|
||||||
@ -183,14 +167,9 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-paper-dropdown-menu {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
margin-top: 4px;
|
||||||
paper-listbox {
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,9 @@
|
|||||||
import "@polymer/paper-item/paper-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
import { mdiCheck } from "@mdi/js";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { stringCompare } from "../../common/string/compare";
|
import { stringCompare } from "../../common/string/compare";
|
||||||
@ -46,36 +37,12 @@ export type HaDevicePickerDeviceFilterFunc = (
|
|||||||
device: DeviceRegistryEntry
|
device: DeviceRegistryEntry
|
||||||
) => boolean;
|
) => boolean;
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<mwc-list-item
|
||||||
const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<style>
|
.twoline=${!!item.area}
|
||||||
paper-item {
|
>
|
||||||
padding: 0;
|
<span>${item.name}</span>
|
||||||
margin: -10px;
|
<span slot="secondary">${item.area}</span>
|
||||||
margin-left: 0;
|
</mwc-list-item>`;
|
||||||
}
|
|
||||||
#content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
margin-right: -2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-item {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-item>
|
|
||||||
<paper-item-body two-line>
|
|
||||||
${item.name}
|
|
||||||
<span secondary>${item.area}</span>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-device-picker")
|
@customElement("ha-device-picker")
|
||||||
export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||||
@ -138,7 +105,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
if (!devices.length) {
|
if (!devices.length) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: "",
|
id: "no_devices",
|
||||||
area: "",
|
area: "",
|
||||||
name: this.hass.localize("ui.components.device-picker.no_devices"),
|
name: this.hass.localize("ui.components.device-picker.no_devices"),
|
||||||
},
|
},
|
||||||
@ -234,7 +201,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
if (!outputDevices.length) {
|
if (!outputDevices.length) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: "",
|
id: "no_devices",
|
||||||
area: "",
|
area: "",
|
||||||
name: this.hass.localize("ui.components.device-picker.no_match"),
|
name: this.hass.localize("ui.components.device-picker.no_match"),
|
||||||
},
|
},
|
||||||
@ -303,7 +270,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
.renderer=${rowRenderer}
|
.renderer=${rowRenderer}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
item-value-path="id"
|
item-value-path="id"
|
||||||
item-id-path="id"
|
|
||||||
item-label-path="name"
|
item-label-path="name"
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._deviceChanged}
|
@value-changed=${this._deviceChanged}
|
||||||
@ -317,7 +283,11 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _deviceChanged(ev: PolymerChangedEvent<string>) {
|
private _deviceChanged(ev: PolymerChangedEvent<string>) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const newValue = ev.detail.value;
|
let newValue = ev.detail.value;
|
||||||
|
|
||||||
|
if (newValue === "no_devices") {
|
||||||
|
newValue = "";
|
||||||
|
}
|
||||||
|
|
||||||
if (newValue !== this._value) {
|
if (newValue !== this._value) {
|
||||||
this._setValue(newValue);
|
this._setValue(newValue);
|
||||||
@ -335,19 +305,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
paper-input > ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||||
@ -114,7 +114,7 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
const newValue = event.detail.value;
|
const newValue = event.detail.value;
|
||||||
if (
|
if (
|
||||||
newValue === curValue ||
|
newValue === curValue ||
|
||||||
(newValue !== "" && !isValidEntityId(newValue))
|
(newValue !== undefined && !isValidEntityId(newValue))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -145,6 +145,12 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
|
|
||||||
this._updateEntities([...currentEntities, toAdd]);
|
this._updateEntities([...currentEntities, toAdd]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static override styles = css`
|
||||||
|
div {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,54 +1,14 @@
|
|||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
|
||||||
import { formatAttributeName } from "../../data/entity_attributes";
|
import { formatAttributeName } from "../../data/entity_attributes";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-icon-button";
|
import "../ha-combo-box";
|
||||||
import "../ha-svg-icon";
|
import type { HaComboBox } from "../ha-combo-box";
|
||||||
import "./state-badge";
|
|
||||||
|
|
||||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
|
||||||
const rowRenderer: ComboBoxLitRenderer<string> = (item) => html`<style>
|
|
||||||
paper-item {
|
|
||||||
padding: 0;
|
|
||||||
margin: -10px;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
#content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
margin-right: -2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-item {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-item>${formatAttributeName(item)}</paper-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-entity-attribute-picker")
|
@customElement("ha-entity-attribute-picker")
|
||||||
class HaEntityAttributePicker extends LitElement {
|
class HaEntityAttributePicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -68,7 +28,7 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) private _opened = false;
|
@property({ type: Boolean }) private _opened = false;
|
||||||
|
|
||||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues) {
|
protected shouldUpdate(changedProps: PropertyValues) {
|
||||||
return !(!changedProps.has("_opened") && this._opened);
|
return !(!changedProps.has("_opened") && this._opened);
|
||||||
@ -78,7 +38,10 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
if (changedProps.has("_opened") && this._opened) {
|
if (changedProps.has("_opened") && this._opened) {
|
||||||
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
|
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
|
||||||
(this._comboBox as any).items = state
|
(this._comboBox as any).items = state
|
||||||
? Object.keys(state.attributes)
|
? Object.keys(state.attributes).map((key) => ({
|
||||||
|
value: key,
|
||||||
|
label: formatAttributeName(key),
|
||||||
|
}))
|
||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,100 +52,31 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<vaadin-combo-box-light
|
<ha-combo-box
|
||||||
.value=${this._value}
|
.hass=${this.hass}
|
||||||
|
.value=${this.value || ""}
|
||||||
|
.autofocus=${this.autofocus}
|
||||||
|
.label=${this.label ??
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.components.entity.entity-attribute-picker.attribute"
|
||||||
|
)}
|
||||||
|
.disabled=${this.disabled || !this.entityId}
|
||||||
.allowCustomValue=${this.allowCustomValue}
|
.allowCustomValue=${this.allowCustomValue}
|
||||||
attr-for-value="bind-value"
|
item-value-path="value"
|
||||||
${comboBoxRenderer(rowRenderer)}
|
item-label-path="label"
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
>
|
>
|
||||||
<paper-input
|
</ha-combo-box>
|
||||||
.autofocus=${this.autofocus}
|
|
||||||
.label=${this.label ??
|
|
||||||
this.hass.localize(
|
|
||||||
"ui.components.entity.entity-attribute-picker.attribute"
|
|
||||||
)}
|
|
||||||
.value=${this._value ? formatAttributeName(this._value) : ""}
|
|
||||||
.disabled=${this.disabled || !this.entityId}
|
|
||||||
class="input"
|
|
||||||
autocapitalize="none"
|
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
>
|
|
||||||
<div class="suffix" slot="suffix">
|
|
||||||
${this.value
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.entity.entity-picker.clear"
|
|
||||||
)}
|
|
||||||
.path=${mdiClose}
|
|
||||||
class="clear-button"
|
|
||||||
tabindex="-1"
|
|
||||||
@click=${this._clearValue}
|
|
||||||
no-ripple
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.entity.entity-attribute-picker.show_attributes"
|
|
||||||
)}
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
class="toggle-button"
|
|
||||||
tabindex="-1"
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
</paper-input>
|
|
||||||
</vaadin-combo-box-light>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _clearValue(ev: Event) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._setValue("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _value() {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||||
this._opened = ev.detail.value;
|
this._opened = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||||
const newValue = ev.detail.value;
|
this.value = ev.detail.value;
|
||||||
if (newValue !== this._value) {
|
|
||||||
this._setValue(newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setValue(value: string) {
|
|
||||||
this.value = value;
|
|
||||||
setTimeout(() => {
|
|
||||||
fireEvent(this, "value-changed", { value });
|
|
||||||
fireEvent(this, "change");
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
.suffix {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 0px 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +1,16 @@
|
|||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
CSSResultGroup,
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
|
||||||
import { customElement, property, query } from "lit/decorators";
|
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-combo-box";
|
||||||
|
import type { HaComboBox } from "../ha-combo-box";
|
||||||
import "../ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import "./state-badge";
|
import "./state-badge";
|
||||||
@ -27,35 +18,15 @@ import "./state-badge";
|
|||||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
// eslint-disable-next-line lit/prefer-static-styles
|
||||||
const rowRenderer: ComboBoxLitRenderer<HassEntity> = (item) => html`<style>
|
const rowRenderer: ComboBoxLitRenderer<HassEntity & { friendly_name: string }> =
|
||||||
paper-icon-item {
|
(item) =>
|
||||||
padding: 0;
|
html`<mwc-list-item graphic="avatar" .twoline=${!!item.entity_id}>
|
||||||
margin: -8px;
|
${item.state
|
||||||
}
|
? html`<state-badge slot="graphic" .stateObj=${item}></state-badge>`
|
||||||
#content {
|
: ""}
|
||||||
display: flex;
|
<span>${item.friendly_name}</span>
|
||||||
align-items: center;
|
<span slot="secondary">${item.entity_id}</span>
|
||||||
}
|
</mwc-list-item>`;
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-icon-item {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-icon-item>
|
|
||||||
<state-badge slot="item-icon" .stateObj=${item}></state-badge>
|
|
||||||
<paper-item-body two-line="">
|
|
||||||
${computeStateName(item)}
|
|
||||||
<span secondary>${item.entity_id}</span>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-icon-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-entity-picker")
|
@customElement("ha-entity-picker")
|
||||||
export class HaEntityPicker extends LitElement {
|
export class HaEntityPicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -107,19 +78,19 @@ export class HaEntityPicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public hideClearIcon = false;
|
@property({ type: Boolean }) public hideClearIcon = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) private _opened = false;
|
@state() private _opened = false;
|
||||||
|
|
||||||
@query("vaadin-combo-box-light", true) private comboBox!: HTMLElement;
|
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
|
this.comboBox?.open();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
this.comboBox?.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +115,27 @@ export class HaEntityPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
let entityIds = Object.keys(hass.states);
|
let entityIds = Object.keys(hass.states);
|
||||||
|
|
||||||
|
if (!entityIds.length) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
entity_id: "",
|
||||||
|
state: "",
|
||||||
|
last_changed: "",
|
||||||
|
last_updated: "",
|
||||||
|
context: { id: "", user_id: null },
|
||||||
|
friendly_name: this.hass!.localize(
|
||||||
|
"ui.components.entity.entity-picker.no_entities"
|
||||||
|
),
|
||||||
|
attributes: {
|
||||||
|
friendly_name: this.hass!.localize(
|
||||||
|
"ui.components.entity.entity-picker.no_entities"
|
||||||
|
),
|
||||||
|
icon: "mdi:magnify",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
if (includeDomains) {
|
if (includeDomains) {
|
||||||
entityIds = entityIds.filter((eid) =>
|
entityIds = entityIds.filter((eid) =>
|
||||||
includeDomains.includes(computeDomain(eid))
|
includeDomains.includes(computeDomain(eid))
|
||||||
@ -156,7 +148,10 @@ export class HaEntityPicker extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
states = entityIds.sort().map((key) => hass!.states[key]);
|
states = entityIds.sort().map((key) => ({
|
||||||
|
...hass!.states[key],
|
||||||
|
friendly_name: computeStateName(hass!.states[key]) || key,
|
||||||
|
}));
|
||||||
|
|
||||||
if (includeDeviceClasses) {
|
if (includeDeviceClasses) {
|
||||||
states = states.filter(
|
states = states.filter(
|
||||||
@ -196,6 +191,9 @@ export class HaEntityPicker extends LitElement {
|
|||||||
last_changed: "",
|
last_changed: "",
|
||||||
last_updated: "",
|
last_updated: "",
|
||||||
context: { id: "", user_id: null },
|
context: { id: "", user_id: null },
|
||||||
|
friendly_name: this.hass!.localize(
|
||||||
|
"ui.components.entity.entity-picker.no_match"
|
||||||
|
),
|
||||||
attributes: {
|
attributes: {
|
||||||
friendly_name: this.hass!.localize(
|
friendly_name: this.hass!.localize(
|
||||||
"ui.components.entity.entity-picker.no_match"
|
"ui.components.entity.entity-picker.no_match"
|
||||||
@ -241,64 +239,25 @@ export class HaEntityPicker extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<vaadin-combo-box-light
|
<ha-combo-box
|
||||||
item-value-path="entity_id"
|
item-value-path="entity_id"
|
||||||
item-label-path="entity_id"
|
item-label-path="friendly_name"
|
||||||
|
.hass=${this.hass}
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
|
.label=${this.label === undefined
|
||||||
|
? this.hass.localize("ui.components.entity.entity-picker.entity")
|
||||||
|
: this.label}
|
||||||
.allowCustomValue=${this.allowCustomEntity}
|
.allowCustomValue=${this.allowCustomEntity}
|
||||||
.filteredItems=${this._states}
|
.filteredItems=${this._states}
|
||||||
${comboBoxRenderer(rowRenderer)}
|
.renderer=${rowRenderer}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@filter-changed=${this._filterChanged}
|
@filter-changed=${this._filterChanged}
|
||||||
>
|
>
|
||||||
<paper-input
|
</ha-combo-box>
|
||||||
.autofocus=${this.autofocus}
|
|
||||||
.label=${this.label === undefined
|
|
||||||
? this.hass.localize("ui.components.entity.entity-picker.entity")
|
|
||||||
: this.label}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
class="input"
|
|
||||||
autocapitalize="none"
|
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
>
|
|
||||||
<div class="suffix" slot="suffix">
|
|
||||||
${this.value && !this.hideClearIcon
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.entity.entity-picker.clear"
|
|
||||||
)}
|
|
||||||
.path=${mdiClose}
|
|
||||||
class="clear-button"
|
|
||||||
tabindex="-1"
|
|
||||||
@click=${this._clearValue}
|
|
||||||
no-ripple
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.entity.entity-picker.show_entities"
|
|
||||||
)}
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
class="toggle-button"
|
|
||||||
tabindex="-1"
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
</paper-input>
|
|
||||||
</vaadin-combo-box-light>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _clearValue(ev: Event) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._setValue("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
return this.value || "";
|
return this.value || "";
|
||||||
}
|
}
|
||||||
@ -308,6 +267,7 @@ export class HaEntityPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||||
|
ev.stopPropagation();
|
||||||
const newValue = ev.detail.value;
|
const newValue = ev.detail.value;
|
||||||
if (newValue !== this._value) {
|
if (newValue !== this._value) {
|
||||||
this._setValue(newValue);
|
this._setValue(newValue);
|
||||||
@ -317,9 +277,9 @@ export class HaEntityPicker extends LitElement {
|
|||||||
private _filterChanged(ev: CustomEvent): void {
|
private _filterChanged(ev: CustomEvent): void {
|
||||||
const filterString = ev.detail.value.toLowerCase();
|
const filterString = ev.detail.value.toLowerCase();
|
||||||
(this.comboBox as any).filteredItems = this._states.filter(
|
(this.comboBox as any).filteredItems = this._states.filter(
|
||||||
(state) =>
|
(entityState) =>
|
||||||
state.entity_id.toLowerCase().includes(filterString) ||
|
entityState.entity_id.toLowerCase().includes(filterString) ||
|
||||||
computeStateName(state).toLowerCase().includes(filterString)
|
computeStateName(entityState).toLowerCase().includes(filterString)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,22 +290,6 @@ export class HaEntityPicker extends LitElement {
|
|||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
.suffix {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 0px 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,17 +1,5 @@
|
|||||||
import { mdiCheck } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@ -76,54 +64,24 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
state?: HassEntity;
|
state?: HassEntity;
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
}> = (item) => html`<mwc-list-item graphic="avatar" twoline>
|
||||||
}> = (item) => html`<style>
|
${item.state
|
||||||
paper-icon-item {
|
? html`<state-badge slot="graphic" .stateObj=${item.state}></state-badge>`
|
||||||
padding: 0;
|
: ""}
|
||||||
margin: -8px;
|
<span>${item.name}</span>
|
||||||
}
|
<span slot="secondary"
|
||||||
#content {
|
>${item.id === "" || item.id === "__missing"
|
||||||
display: flex;
|
? html`<a
|
||||||
align-items: center;
|
target="_blank"
|
||||||
}
|
rel="noopener noreferrer"
|
||||||
ha-svg-icon {
|
href=${documentationUrl(this.hass, "/more-info/statistics/")}
|
||||||
padding-left: 2px;
|
>${this.hass.localize(
|
||||||
color: var(--secondary-text-color);
|
"ui.components.statistic-picker.learn_more"
|
||||||
}
|
)}</a
|
||||||
:host(:not([selected])) ha-svg-icon {
|
>`
|
||||||
display: none;
|
: item.id}</span
|
||||||
}
|
>
|
||||||
:host([selected]) paper-icon-item {
|
</mwc-list-item>`;
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-icon-item>
|
|
||||||
${item.state
|
|
||||||
? html`<state-badge
|
|
||||||
slot="item-icon"
|
|
||||||
.stateObj=${item.state}
|
|
||||||
></state-badge>`
|
|
||||||
: ""}
|
|
||||||
<paper-item-body two-line="">
|
|
||||||
${item.name}
|
|
||||||
<span secondary
|
|
||||||
>${item.id === "" || item.id === "__missing"
|
|
||||||
? html`<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
href=${documentationUrl(this.hass, "/more-info/statistics/")}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.components.statistic-picker.learn_more"
|
|
||||||
)}</a
|
|
||||||
>`
|
|
||||||
: item.id}</span
|
|
||||||
>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-icon-item>`;
|
|
||||||
|
|
||||||
private _getStatistics = memoizeOne(
|
private _getStatistics = memoizeOne(
|
||||||
(
|
(
|
||||||
@ -293,19 +251,6 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
paper-input > ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import type { PolymerChangedEvent } from "../../polymer-types";
|
import type { PolymerChangedEvent } from "../../polymer-types";
|
||||||
@ -103,6 +103,20 @@ class HaStatisticsPicker extends LitElement {
|
|||||||
|
|
||||||
this._updateStatistics([...currentEntities, toAdd]);
|
this._updateStatistics([...currentEntities, toAdd]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
width: 200px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
ha-statistic-picker {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { mdiCheck } from "@mdi/js";
|
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
@ -12,39 +11,12 @@ import { PolymerChangedEvent } from "../polymer-types";
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { HaComboBox } from "./ha-combo-box";
|
import { HaComboBox } from "./ha-combo-box";
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
|
||||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (item) => html`<style>
|
item
|
||||||
paper-item {
|
) => html`<mwc-list-item twoline>
|
||||||
padding: 0;
|
<span>${item.name}</span>
|
||||||
margin: -10px;
|
<span slot="secondary">${item.slug}</span>
|
||||||
margin-left: 0px;
|
</mwc-list-item>`;
|
||||||
}
|
|
||||||
#content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-item {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
margin-right: -2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-icon-item {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-item>
|
|
||||||
<paper-item-body two-line>
|
|
||||||
${item.name}
|
|
||||||
<span secondary>${item.slug}</span>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-addon-picker")
|
@customElement("ha-addon-picker")
|
||||||
class HaAddonPicker extends LitElement {
|
class HaAddonPicker extends LitElement {
|
||||||
|
@ -1,19 +1,6 @@
|
|||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@ -41,38 +28,18 @@ import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
|||||||
import { PolymerChangedEvent } from "../polymer-types";
|
import { PolymerChangedEvent } from "../polymer-types";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||||
|
import type { HaComboBox } from "./ha-combo-box";
|
||||||
|
import "./ha-combo-box";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (
|
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (
|
||||||
item
|
item
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
) => html`<mwc-list-item
|
||||||
) => html`<style>
|
class=${classMap({ "add-new": item.area_id === "add_new" })}
|
||||||
paper-item {
|
>
|
||||||
padding: 0;
|
${item.name}
|
||||||
margin: -10px;
|
</mwc-list-item>`;
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
#content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
margin-right: -2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-item {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-item class=${classMap({ "add-new": item.area_id === "add_new" })}>
|
|
||||||
<paper-item-body two-line>${item.name}</paper-item-body>
|
|
||||||
</paper-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-area-picker")
|
@customElement("ha-area-picker")
|
||||||
export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||||
@ -125,7 +92,9 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _opened?: boolean;
|
@state() private _opened?: boolean;
|
||||||
|
|
||||||
@query("vaadin-combo-box-light", true) public comboBox!: HTMLElement;
|
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
||||||
|
|
||||||
|
private _filter?: string;
|
||||||
|
|
||||||
private _init = false;
|
private _init = false;
|
||||||
|
|
||||||
@ -145,13 +114,13 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
|
this.comboBox?.open();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
this.comboBox?.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +139,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
if (!areas.length) {
|
if (!areas.length) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
area_id: "",
|
area_id: "no_areas",
|
||||||
name: this.hass.localize("ui.components.area-picker.no_areas"),
|
name: this.hass.localize("ui.components.area-picker.no_areas"),
|
||||||
picture: null,
|
picture: null,
|
||||||
},
|
},
|
||||||
@ -294,7 +263,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
if (!outputAreas.length) {
|
if (!outputAreas.length) {
|
||||||
outputAreas = [
|
outputAreas = [
|
||||||
{
|
{
|
||||||
area_id: "",
|
area_id: "no_areas",
|
||||||
name: this.hass.localize("ui.components.area-picker.no_match"),
|
name: this.hass.localize("ui.components.area-picker.no_match"),
|
||||||
picture: null,
|
picture: null,
|
||||||
},
|
},
|
||||||
@ -339,52 +308,25 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<vaadin-combo-box-light
|
<ha-combo-box
|
||||||
|
.hass=${this.hass}
|
||||||
item-value-path="area_id"
|
item-value-path="area_id"
|
||||||
item-id-path="area_id"
|
item-id-path="area_id"
|
||||||
item-label-path="name"
|
item-label-path="name"
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
${comboBoxRenderer(rowRenderer)}
|
.label=${this.label === undefined && this.hass
|
||||||
|
? this.hass.localize("ui.components.area-picker.area")
|
||||||
|
: this.label}
|
||||||
|
.placeholder=${this.placeholder
|
||||||
|
? this._area(this.placeholder)?.name
|
||||||
|
: undefined}
|
||||||
|
.renderer=${rowRenderer}
|
||||||
|
@filter-changed=${this._filterChanged}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._areaChanged}
|
@value-changed=${this._areaChanged}
|
||||||
>
|
>
|
||||||
<paper-input
|
</ha-combo-box>
|
||||||
.label=${this.label === undefined && this.hass
|
|
||||||
? this.hass.localize("ui.components.area-picker.area")
|
|
||||||
: this.label}
|
|
||||||
.placeholder=${this.placeholder
|
|
||||||
? this._area(this.placeholder)?.name
|
|
||||||
: undefined}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
class="input"
|
|
||||||
autocapitalize="none"
|
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
>
|
|
||||||
${this.value
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.area-picker.clear"
|
|
||||||
)}
|
|
||||||
.path=${mdiClose}
|
|
||||||
slot="suffix"
|
|
||||||
class="clear-button"
|
|
||||||
@click=${this._clearValue}
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize("ui.components.area-picker.toggle")}
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
slot="suffix"
|
|
||||||
class="toggle-button"
|
|
||||||
></ha-icon-button>
|
|
||||||
</paper-input>
|
|
||||||
</vaadin-combo-box-light>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,9 +334,29 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
this._areas?.find((area) => area.area_id === areaId)
|
this._areas?.find((area) => area.area_id === areaId)
|
||||||
);
|
);
|
||||||
|
|
||||||
private _clearValue(ev: Event) {
|
private _filterChanged(ev: CustomEvent): void {
|
||||||
ev.stopPropagation();
|
this._filter = ev.detail.value;
|
||||||
this._setValue("");
|
if (!this._filter) {
|
||||||
|
this.comboBox.filteredItems = this.comboBox.items;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
if (!this.noAdd && this.comboBox._comboBox.filteredItems?.length === 0) {
|
||||||
|
this.comboBox.filteredItems = [
|
||||||
|
{
|
||||||
|
area_id: "add_new_suggestion",
|
||||||
|
name: this.hass.localize(
|
||||||
|
"ui.components.area-picker.add_new_sugestion",
|
||||||
|
{ name: this._filter }
|
||||||
|
),
|
||||||
|
picture: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
this.comboBox.filteredItems = this.comboBox.items?.filter((item) =>
|
||||||
|
item.name.toLowerCase().includes(this._filter!.toLowerCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
@ -406,9 +368,14 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _areaChanged(ev: PolymerChangedEvent<string>) {
|
private _areaChanged(ev: PolymerChangedEvent<string>) {
|
||||||
const newValue = ev.detail.value;
|
ev.stopPropagation();
|
||||||
|
let newValue = ev.detail.value;
|
||||||
|
|
||||||
if (newValue !== "add_new") {
|
if (newValue === "no_areas") {
|
||||||
|
newValue = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!["add_new_suggestion", "add_new"].includes(newValue)) {
|
||||||
if (newValue !== this._value) {
|
if (newValue !== this._value) {
|
||||||
this._setValue(newValue);
|
this._setValue(newValue);
|
||||||
}
|
}
|
||||||
@ -425,6 +392,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
inputLabel: this.hass.localize(
|
inputLabel: this.hass.localize(
|
||||||
"ui.components.area-picker.add_dialog.name"
|
"ui.components.area-picker.add_dialog.name"
|
||||||
),
|
),
|
||||||
|
defaultValue:
|
||||||
|
newValue === "add_new_suggestion" ? this._filter : undefined,
|
||||||
confirm: async (name) => {
|
confirm: async (name) => {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
@ -445,6 +414,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
this.entityFilter,
|
this.entityFilter,
|
||||||
this.noAdd
|
this.noAdd
|
||||||
);
|
);
|
||||||
|
await this.updateComplete;
|
||||||
|
await this.comboBox.updateComplete;
|
||||||
this._setValue(area.area_id);
|
this._setValue(area.area_id);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
@ -465,19 +436,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
paper-input > ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
313
src/components/ha-base-time-input.ts
Normal file
313
src/components/ha-base-time-input.ts
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
import { LitElement, html, TemplateResult, css } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import "./ha-select";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import "./ha-textfield";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
|
|
||||||
|
export interface TimeChangedEvent {
|
||||||
|
hours: number;
|
||||||
|
minutes: number;
|
||||||
|
seconds: number;
|
||||||
|
milliseconds: number;
|
||||||
|
amPm?: "AM" | "PM";
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-base-time-input")
|
||||||
|
export class HaBaseTimeInput extends LitElement {
|
||||||
|
/**
|
||||||
|
* Label for the input
|
||||||
|
*/
|
||||||
|
@property() label?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* auto validate time inputs
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) autoValidate = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* determines if inputs are required
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) public required?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 12 or 24 hr format
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) format: 12 | 24 = 12;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* disables the inputs
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) disabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hour
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) hours = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* minute
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) minutes = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* second
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) seconds = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* milli second
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) milliseconds = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the hour input
|
||||||
|
*/
|
||||||
|
@property() hourLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the min input
|
||||||
|
*/
|
||||||
|
@property() minLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the sec input
|
||||||
|
*/
|
||||||
|
@property() secLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the milli sec input
|
||||||
|
*/
|
||||||
|
@property() millisecLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show the sec field
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) enableSecond = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show the milli sec field
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) enableMillisecond = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* limit hours input
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) noHoursLimit = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AM or PM
|
||||||
|
*/
|
||||||
|
@property() amPm: "AM" | "PM" = "AM";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatted time string
|
||||||
|
*/
|
||||||
|
@property() value?: string;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${this.label ? html`<label>${this.label}</label>` : ""}
|
||||||
|
<div class="time-input-wrap">
|
||||||
|
<ha-textfield
|
||||||
|
id="hour"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this.hours}
|
||||||
|
.label=${this.hourLabel}
|
||||||
|
name="hours"
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
no-spinner
|
||||||
|
.required=${this.required}
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
.max=${this._hourMax}
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
suffix=":"
|
||||||
|
class="hasSuffix"
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
<ha-textfield
|
||||||
|
id="min"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this._formatValue(this.minutes)}
|
||||||
|
.label=${this.minLabel}
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
name="minutes"
|
||||||
|
no-spinner
|
||||||
|
.required=${this.required}
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
max="59"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.suffix=${this.enableSecond ? ":" : ""}
|
||||||
|
class=${this.enableSecond ? "has-suffix" : ""}
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
${this.enableSecond
|
||||||
|
? html`<ha-textfield
|
||||||
|
id="sec"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this._formatValue(this.seconds)}
|
||||||
|
.label=${this.secLabel}
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
name="seconds"
|
||||||
|
no-spinner
|
||||||
|
.required=${this.required}
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
max="59"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.suffix=${this.enableMillisecond ? ":" : ""}
|
||||||
|
class=${this.enableMillisecond ? "has-suffix" : ""}
|
||||||
|
>
|
||||||
|
</ha-textfield>`
|
||||||
|
: ""}
|
||||||
|
${this.enableMillisecond
|
||||||
|
? html`<ha-textfield
|
||||||
|
id="millisec"
|
||||||
|
type="number"
|
||||||
|
.value=${this._formatValue(this.milliseconds, 3)}
|
||||||
|
.label=${this.millisecLabel}
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
name="milliseconds"
|
||||||
|
no-spinner
|
||||||
|
.required=${this.required}
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="3"
|
||||||
|
max="999"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
>
|
||||||
|
</ha-textfield>`
|
||||||
|
: ""}
|
||||||
|
${this.format === 24
|
||||||
|
? ""
|
||||||
|
: html`<ha-select
|
||||||
|
.required=${this.required}
|
||||||
|
.value=${this.amPm}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
name="amPm"
|
||||||
|
naturalMenuWidth
|
||||||
|
fixedMenuPosition
|
||||||
|
@selected=${this._valueChanged}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
>
|
||||||
|
<mwc-list-item value="AM">AM</mwc-list-item>
|
||||||
|
<mwc-list-item value="PM">PM</mwc-list-item>
|
||||||
|
</ha-select>`}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev) {
|
||||||
|
this[ev.target.name] =
|
||||||
|
ev.target.name === "amPm" ? ev.target.value : Number(ev.target.value);
|
||||||
|
const value: TimeChangedEvent = {
|
||||||
|
hours: this.hours,
|
||||||
|
minutes: this.minutes,
|
||||||
|
seconds: this.seconds,
|
||||||
|
milliseconds: this.milliseconds,
|
||||||
|
};
|
||||||
|
if (this.format === 12) {
|
||||||
|
value.amPm = this.amPm;
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onFocus(ev) {
|
||||||
|
ev.target.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format time fragments
|
||||||
|
*/
|
||||||
|
private _formatValue(value: number, padding = 2) {
|
||||||
|
return value.toString().padStart(padding, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 24 hour format has a max hr of 23
|
||||||
|
*/
|
||||||
|
private get _hourMax() {
|
||||||
|
if (this.noHoursLimit) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.format === 12) {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
return 23;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.time-input-wrap {
|
||||||
|
display: flex;
|
||||||
|
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
ha-textfield {
|
||||||
|
width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
--mdc-shape-small: 0;
|
||||||
|
--text-field-appearance: none;
|
||||||
|
--text-field-padding: 0 4px;
|
||||||
|
--text-field-suffix-padding-left: 2px;
|
||||||
|
--text-field-suffix-padding-right: 0;
|
||||||
|
--text-field-text-align: center;
|
||||||
|
}
|
||||||
|
ha-textfield.hasSuffix {
|
||||||
|
--text-field-padding: 0 0 0 4px;
|
||||||
|
}
|
||||||
|
ha-textfield:first-child {
|
||||||
|
--text-field-border-top-left-radius: var(--mdc-shape-medium);
|
||||||
|
}
|
||||||
|
ha-textfield:last-child {
|
||||||
|
--text-field-border-top-right-radius: var(--mdc-shape-medium);
|
||||||
|
}
|
||||||
|
ha-select {
|
||||||
|
--mdc-shape-small: 0;
|
||||||
|
width: 85px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-family: var(
|
||||||
|
--mdc-typography-body2-font-family,
|
||||||
|
var(--mdc-typography-font-family, Roboto, sans-serif)
|
||||||
|
);
|
||||||
|
font-size: var(--mdc-typography-body2-font-size, 0.875rem);
|
||||||
|
line-height: var(--mdc-typography-body2-line-height, 1.25rem);
|
||||||
|
font-weight: var(--mdc-typography-body2-font-weight, 400);
|
||||||
|
letter-spacing: var(
|
||||||
|
--mdc-typography-body2-letter-spacing,
|
||||||
|
0.0178571429em
|
||||||
|
);
|
||||||
|
text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
|
||||||
|
text-transform: var(--mdc-typography-body2-text-transform, inherit);
|
||||||
|
color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-base-time-input": HaBaseTimeInput;
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "./ha-select";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import { stringCompare } from "../common/string/compare";
|
import { stringCompare } from "../common/string/compare";
|
||||||
import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint";
|
import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
@ -24,7 +24,11 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
this.shadowRoot!.querySelector("paper-dropdown-menu-light")!.open();
|
const select = this.shadowRoot?.querySelector("ha-select");
|
||||||
|
if (select) {
|
||||||
|
// @ts-expect-error
|
||||||
|
select.menuOpen = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _processedBlueprints = memoizeOne((blueprints?: Blueprints) => {
|
private _processedBlueprints = memoizeOne((blueprints?: Blueprints) => {
|
||||||
@ -45,32 +49,29 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<paper-dropdown-menu-light
|
<ha-select
|
||||||
.label=${this.label ||
|
.label=${this.label ||
|
||||||
this.hass.localize("ui.components.blueprint-picker.label")}
|
this.hass.localize("ui.components.blueprint-picker.label")}
|
||||||
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
|
.value=${this.value}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
horizontal-align="left"
|
@selected=${this._blueprintChanged}
|
||||||
|
@closed=${stopPropagation}
|
||||||
>
|
>
|
||||||
<paper-listbox
|
<mwc-list-item value="">
|
||||||
slot="dropdown-content"
|
${this.hass.localize(
|
||||||
.selected=${this.value}
|
"ui.components.blueprint-picker.select_blueprint"
|
||||||
attr-for-selected="data-blueprint-path"
|
|
||||||
@iron-select=${this._blueprintChanged}
|
|
||||||
>
|
|
||||||
<paper-item data-blueprint-path="">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.components.blueprint-picker.select_blueprint"
|
|
||||||
)}
|
|
||||||
</paper-item>
|
|
||||||
${this._processedBlueprints(this.blueprints).map(
|
|
||||||
(blueprint) => html`
|
|
||||||
<paper-item data-blueprint-path=${blueprint.path}>
|
|
||||||
${blueprint.name}
|
|
||||||
</paper-item>
|
|
||||||
`
|
|
||||||
)}
|
)}
|
||||||
</paper-listbox>
|
</mwc-list-item>
|
||||||
</paper-dropdown-menu-light>
|
${this._processedBlueprints(this.blueprints).map(
|
||||||
|
(blueprint) => html`
|
||||||
|
<mwc-list-item .value=${blueprint.path}>
|
||||||
|
${blueprint.name}
|
||||||
|
</mwc-list-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ha-select>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,10 +85,10 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _blueprintChanged(ev) {
|
private _blueprintChanged(ev) {
|
||||||
const newValue = ev.detail.item.dataset.blueprintPath;
|
const newValue = ev.target.value;
|
||||||
|
|
||||||
if (newValue !== this.value) {
|
if (newValue !== this.value) {
|
||||||
this.value = ev.detail.value;
|
this.value = newValue;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fireEvent(this, "value-changed", { value: newValue });
|
fireEvent(this, "value-changed", { value: newValue });
|
||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
@ -100,15 +101,11 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
paper-dropdown-menu-light {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "@material/mwc-menu";
|
import "@material/mwc-menu";
|
||||||
import type { Corner, Menu } from "@material/mwc-menu";
|
import type { Corner, Menu, MenuCorner } from "@material/mwc-menu";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
|
||||||
@ -7,6 +7,12 @@ import { customElement, property, query } from "lit/decorators";
|
|||||||
export class HaButtonMenu extends LitElement {
|
export class HaButtonMenu extends LitElement {
|
||||||
@property() public corner: Corner = "TOP_START";
|
@property() public corner: Corner = "TOP_START";
|
||||||
|
|
||||||
|
@property() public menuCorner: MenuCorner = "START";
|
||||||
|
|
||||||
|
@property({ type: Number }) public x?: number;
|
||||||
|
|
||||||
|
@property({ type: Number }) public y?: number;
|
||||||
|
|
||||||
@property({ type: Boolean }) public multi = false;
|
@property({ type: Boolean }) public multi = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public activatable = false;
|
@property({ type: Boolean }) public activatable = false;
|
||||||
@ -32,9 +38,12 @@ export class HaButtonMenu extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
<mwc-menu
|
<mwc-menu
|
||||||
.corner=${this.corner}
|
.corner=${this.corner}
|
||||||
|
.menuCorner=${this.menuCorner}
|
||||||
.fixed=${this.fixed}
|
.fixed=${this.fixed}
|
||||||
.multi=${this.multi}
|
.multi=${this.multi}
|
||||||
.activatable=${this.activatable}
|
.activatable=${this.activatable}
|
||||||
|
.y=${this.y}
|
||||||
|
.x=${this.x}
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</mwc-menu>
|
</mwc-menu>
|
||||||
|
@ -4,6 +4,7 @@ import { mdiFilterVariant } from "@mdi/js";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
import { computeStateName } from "../common/entity/compute_state_name";
|
||||||
import { computeDeviceName } from "../data/device_registry";
|
import { computeDeviceName } from "../data/device_registry";
|
||||||
import { findRelated, RelatedResult } from "../data/search";
|
import { findRelated, RelatedResult } from "../data/search";
|
||||||
@ -65,6 +66,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
.fullwidth=${this.narrow}
|
.fullwidth=${this.narrow}
|
||||||
.corner=${this.corner}
|
.corner=${this.corner}
|
||||||
@closed=${this._onClosed}
|
@closed=${this._onClosed}
|
||||||
|
@input=${stopPropagation}
|
||||||
>
|
>
|
||||||
<ha-area-picker
|
<ha-area-picker
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@ -74,6 +76,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
.value=${this.value?.area}
|
.value=${this.value?.area}
|
||||||
no-add
|
no-add
|
||||||
@value-changed=${this._areaPicked}
|
@value-changed=${this._areaPicked}
|
||||||
|
@click=${this._preventDefault}
|
||||||
></ha-area-picker>
|
></ha-area-picker>
|
||||||
<ha-device-picker
|
<ha-device-picker
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@ -82,6 +85,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value?.device}
|
.value=${this.value?.device}
|
||||||
@value-changed=${this._devicePicked}
|
@value-changed=${this._devicePicked}
|
||||||
|
@click=${this._preventDefault}
|
||||||
></ha-device-picker>
|
></ha-device-picker>
|
||||||
<ha-entity-picker
|
<ha-entity-picker
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@ -91,6 +95,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
.value=${this.value?.entity}
|
.value=${this.value?.entity}
|
||||||
.excludeDomains=${this.excludeDomains}
|
.excludeDomains=${this.excludeDomains}
|
||||||
@value-changed=${this._entityPicked}
|
@value-changed=${this._entityPicked}
|
||||||
|
@click=${this._preventDefault}
|
||||||
></ha-entity-picker>
|
></ha-entity-picker>
|
||||||
</mwc-menu-surface>
|
</mwc-menu-surface>
|
||||||
`;
|
`;
|
||||||
@ -103,11 +108,17 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
this._open = true;
|
this._open = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onClosed(): void {
|
private _onClosed(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
this._open = false;
|
this._open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _preventDefault(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
private async _entityPicked(ev: CustomEvent) {
|
private async _entityPicked(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
const entityId = ev.detail.value;
|
const entityId = ev.detail.value;
|
||||||
if (!entityId) {
|
if (!entityId) {
|
||||||
fireEvent(this, "related-changed", { value: undefined });
|
fireEvent(this, "related-changed", { value: undefined });
|
||||||
@ -127,6 +138,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _devicePicked(ev: CustomEvent) {
|
private async _devicePicked(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
const deviceId = ev.detail.value;
|
const deviceId = ev.detail.value;
|
||||||
if (!deviceId) {
|
if (!deviceId) {
|
||||||
fireEvent(this, "related-changed", { value: undefined });
|
fireEvent(this, "related-changed", { value: undefined });
|
||||||
@ -150,6 +162,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _areaPicked(ev: CustomEvent) {
|
private async _areaPicked(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
const areaId = ev.detail.value;
|
const areaId = ev.detail.value;
|
||||||
if (!areaId) {
|
if (!areaId) {
|
||||||
fireEvent(this, "related-changed", { value: undefined });
|
fireEvent(this, "related-changed", { value: undefined });
|
||||||
@ -173,9 +186,7 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
--mdc-menu-min-width: 250px;
|
||||||
:host([narrow]) {
|
|
||||||
position: static;
|
|
||||||
}
|
}
|
||||||
ha-area-picker,
|
ha-area-picker,
|
||||||
ha-device-picker,
|
ha-device-picker,
|
||||||
@ -185,8 +196,15 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
ha-area-picker {
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
ha-entity-picker {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
:host([narrow]) ha-area-picker,
|
:host([narrow]) ha-area-picker,
|
||||||
:host([narrow]) ha-device-picker {
|
:host([narrow]) ha-device-picker,
|
||||||
|
:host([narrow]) ha-entity-picker {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
24
src/components/ha-check-list-item.ts
Normal file
24
src/components/ha-check-list-item.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { css } from "lit";
|
||||||
|
import { CheckListItemBase } from "@material/mwc-list/mwc-check-list-item-base";
|
||||||
|
import { styles as controlStyles } from "@material/mwc-list/mwc-control-list-item.css";
|
||||||
|
import { styles } from "@material/mwc-list/mwc-list-item.css";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
|
@customElement("ha-check-list-item")
|
||||||
|
export class HaCheckListItem extends CheckListItemBase {
|
||||||
|
static override styles = [
|
||||||
|
styles,
|
||||||
|
controlStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
--mdc-theme-secondary: var(--primary-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-check-list-item": HaCheckListItem;
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,18 @@
|
|||||||
import { Checkbox } from "@material/mwc-checkbox";
|
import { CheckboxBase } from "@material/mwc-checkbox/mwc-checkbox-base";
|
||||||
|
import { styles } from "@material/mwc-checkbox/mwc-checkbox.css";
|
||||||
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
@customElement("ha-checkbox")
|
@customElement("ha-checkbox")
|
||||||
export class HaCheckbox extends Checkbox {
|
export class HaCheckbox extends CheckboxBase {
|
||||||
public firstUpdated() {
|
static override styles = [
|
||||||
super.firstUpdated();
|
styles,
|
||||||
this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
|
css`
|
||||||
}
|
:host {
|
||||||
|
--mdc-theme-secondary: var(--primary-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
|
import type {
|
||||||
|
Completion,
|
||||||
|
CompletionContext,
|
||||||
|
CompletionResult,
|
||||||
|
} from "@codemirror/autocomplete";
|
||||||
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
|
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
|
||||||
|
import { HassEntities } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit";
|
import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { loadCodeMirror } from "../resources/codemirror.ondemand";
|
import { loadCodeMirror } from "../resources/codemirror.ondemand";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@ -24,10 +32,15 @@ export class HaCodeEditor extends ReactiveElement {
|
|||||||
|
|
||||||
@property() public mode = "yaml";
|
@property() public mode = "yaml";
|
||||||
|
|
||||||
|
public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public autofocus = false;
|
@property({ type: Boolean }) public autofocus = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public readOnly = false;
|
@property({ type: Boolean }) public readOnly = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "autocomplete-entities" })
|
||||||
|
public autocompleteEntities = false;
|
||||||
|
|
||||||
@property() public error = false;
|
@property() public error = false;
|
||||||
|
|
||||||
@state() private _value = "";
|
@state() private _value = "";
|
||||||
@ -110,43 +123,92 @@ export class HaCodeEditor extends ReactiveElement {
|
|||||||
|
|
||||||
private async _load(): Promise<void> {
|
private async _load(): Promise<void> {
|
||||||
this._loadedCodeMirror = await loadCodeMirror();
|
this._loadedCodeMirror = await loadCodeMirror();
|
||||||
|
const extensions = [
|
||||||
|
this._loadedCodeMirror.lineNumbers(),
|
||||||
|
this._loadedCodeMirror.EditorState.allowMultipleSelections.of(true),
|
||||||
|
this._loadedCodeMirror.history(),
|
||||||
|
this._loadedCodeMirror.highlightSelectionMatches(),
|
||||||
|
this._loadedCodeMirror.highlightActiveLine(),
|
||||||
|
this._loadedCodeMirror.drawSelection(),
|
||||||
|
this._loadedCodeMirror.rectangularSelection(),
|
||||||
|
this._loadedCodeMirror.keymap.of([
|
||||||
|
...this._loadedCodeMirror.defaultKeymap,
|
||||||
|
...this._loadedCodeMirror.searchKeymap,
|
||||||
|
...this._loadedCodeMirror.historyKeymap,
|
||||||
|
...this._loadedCodeMirror.tabKeyBindings,
|
||||||
|
saveKeyBinding,
|
||||||
|
] as KeyBinding[]),
|
||||||
|
this._loadedCodeMirror.langCompartment.of(this._mode),
|
||||||
|
this._loadedCodeMirror.theme,
|
||||||
|
this._loadedCodeMirror.Prec.fallback(
|
||||||
|
this._loadedCodeMirror.highlightStyle
|
||||||
|
),
|
||||||
|
this._loadedCodeMirror.readonlyCompartment.of(
|
||||||
|
this._loadedCodeMirror.EditorView.editable.of(!this.readOnly)
|
||||||
|
),
|
||||||
|
this._loadedCodeMirror.EditorView.updateListener.of((update) =>
|
||||||
|
this._onUpdate(update)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!this.readOnly && this.autocompleteEntities && this.hass) {
|
||||||
|
extensions.push(
|
||||||
|
this._loadedCodeMirror.autocompletion({
|
||||||
|
override: [this._entityCompletions.bind(this)],
|
||||||
|
maxRenderedOptions: 10,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.codemirror = new this._loadedCodeMirror.EditorView({
|
this.codemirror = new this._loadedCodeMirror.EditorView({
|
||||||
state: this._loadedCodeMirror.EditorState.create({
|
state: this._loadedCodeMirror.EditorState.create({
|
||||||
doc: this._value,
|
doc: this._value,
|
||||||
extensions: [
|
extensions,
|
||||||
this._loadedCodeMirror.lineNumbers(),
|
|
||||||
this._loadedCodeMirror.EditorState.allowMultipleSelections.of(true),
|
|
||||||
this._loadedCodeMirror.history(),
|
|
||||||
this._loadedCodeMirror.highlightSelectionMatches(),
|
|
||||||
this._loadedCodeMirror.highlightActiveLine(),
|
|
||||||
this._loadedCodeMirror.drawSelection(),
|
|
||||||
this._loadedCodeMirror.rectangularSelection(),
|
|
||||||
this._loadedCodeMirror.keymap.of([
|
|
||||||
...this._loadedCodeMirror.defaultKeymap,
|
|
||||||
...this._loadedCodeMirror.searchKeymap,
|
|
||||||
...this._loadedCodeMirror.historyKeymap,
|
|
||||||
...this._loadedCodeMirror.tabKeyBindings,
|
|
||||||
saveKeyBinding,
|
|
||||||
] as KeyBinding[]),
|
|
||||||
this._loadedCodeMirror.langCompartment.of(this._mode),
|
|
||||||
this._loadedCodeMirror.theme,
|
|
||||||
this._loadedCodeMirror.Prec.fallback(
|
|
||||||
this._loadedCodeMirror.highlightStyle
|
|
||||||
),
|
|
||||||
this._loadedCodeMirror.readonlyCompartment.of(
|
|
||||||
this._loadedCodeMirror.EditorView.editable.of(!this.readOnly)
|
|
||||||
),
|
|
||||||
this._loadedCodeMirror.EditorView.updateListener.of((update) =>
|
|
||||||
this._onUpdate(update)
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
root: this.shadowRoot!,
|
root: this.shadowRoot!,
|
||||||
parent: this.shadowRoot!,
|
parent: this.shadowRoot!,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getStates = memoizeOne((states: HassEntities): Completion[] => {
|
||||||
|
if (!states) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const options = Object.keys(states).map((key) => ({
|
||||||
|
type: "variable",
|
||||||
|
label: key,
|
||||||
|
detail: states[key].attributes.friendly_name,
|
||||||
|
info: `State: ${states[key].state}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
|
||||||
|
private _entityCompletions(
|
||||||
|
context: CompletionContext
|
||||||
|
): CompletionResult | null | Promise<CompletionResult | null> {
|
||||||
|
const entityWord = context.matchBefore(/[a-z_]{3,}\./);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!entityWord ||
|
||||||
|
(entityWord.from === entityWord.to && !context.explicit)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const states = this._getStates(this.hass!.states);
|
||||||
|
|
||||||
|
if (!states || !states.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
from: Number(entityWord.from),
|
||||||
|
options: states,
|
||||||
|
span: /^\w*.\w*$/,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private _blockKeyboardShortcuts() {
|
private _blockKeyboardShortcuts() {
|
||||||
this.addEventListener("keydown", (ev) => ev.stopPropagation());
|
this.addEventListener("keydown", (ev) => ev.stopPropagation());
|
||||||
}
|
}
|
||||||
@ -163,10 +225,9 @@ export class HaCodeEditor extends ReactiveElement {
|
|||||||
fireEvent(this, "value-changed", { value: this._value });
|
fireEvent(this, "value-changed", { value: this._value });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only Lit 2.0 will use this
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host(.error-state) div.cm-wrap .cm-gutters {
|
:host(.error-state) .cm-gutters {
|
||||||
border-color: var(--error-state-color, red);
|
border-color: var(--error-state-color, red);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1,37 +1,78 @@
|
|||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@vaadin/combo-box/theme/material/vaadin-combo-box-light";
|
||||||
import "@polymer/paper-item/paper-item";
|
import type { ComboBoxLight } from "@vaadin/combo-box/vaadin-combo-box-light";
|
||||||
import "@polymer/paper-item/paper-item-body";
|
import { registerStyles } from "@vaadin/vaadin-themable-mixin/register-styles";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { PolymerChangedEvent } from "../polymer-types";
|
import { PolymerChangedEvent } from "../polymer-types";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
|
import "./ha-textfield";
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
registerStyles(
|
||||||
const defaultRowRenderer: ComboBoxLitRenderer<string> = (item) => html`<style>
|
"vaadin-combo-box-item",
|
||||||
paper-item {
|
css`
|
||||||
margin: -5px -10px;
|
:host {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
</style>
|
:host([focused]:not([disabled])) {
|
||||||
<paper-item>${item}</paper-item>`;
|
background-color: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.12);
|
||||||
|
}
|
||||||
|
:host([selected]:not([disabled])) {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--mdc-theme-primary);
|
||||||
|
--mdc-ripple-color: var(--mdc-theme-primary);
|
||||||
|
--mdc-theme-text-primary-on-background: var(--mdc-theme-primary);
|
||||||
|
}
|
||||||
|
:host([selected]:not([disabled])):before {
|
||||||
|
background-color: var(--mdc-theme-primary);
|
||||||
|
opacity: 0.12;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
:host([selected][focused]:not([disabled])):before {
|
||||||
|
opacity: 0.24;
|
||||||
|
}
|
||||||
|
:host(:hover:not([disabled])) {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
[part="content"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
[part="checkmark"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
@customElement("ha-combo-box")
|
@customElement("ha-combo-box")
|
||||||
export class HaComboBox extends LitElement {
|
export class HaComboBox extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public value?: string;
|
@property() public value?: string;
|
||||||
|
|
||||||
@property() public items?: [];
|
@property() public placeholder?: string;
|
||||||
|
|
||||||
@property() public filteredItems?: [];
|
@property() public validationMessage?: string;
|
||||||
|
|
||||||
|
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public invalid?: boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public icon?: boolean;
|
||||||
|
|
||||||
|
@property() public items?: any[];
|
||||||
|
|
||||||
|
@property() public filteredItems?: any[];
|
||||||
|
|
||||||
@property({ attribute: "allow-custom-value", type: Boolean })
|
@property({ attribute: "allow-custom-value", type: Boolean })
|
||||||
public allowCustomValue?: boolean;
|
public allowCustomValue?: boolean;
|
||||||
@ -46,24 +87,25 @@ export class HaComboBox extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled?: boolean;
|
@property({ type: Boolean }) public disabled?: boolean;
|
||||||
|
|
||||||
@state() private _opened?: boolean;
|
@property({ type: Boolean, reflect: true, attribute: "opened" })
|
||||||
|
private _opened?: boolean;
|
||||||
|
|
||||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
@query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight;
|
||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
(this._comboBox as any)?.open();
|
this._comboBox?.open();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
this._comboBox?.inputElement?.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public get selectedItem() {
|
public get selectedItem() {
|
||||||
return (this._comboBox as any).selectedItem;
|
return this._comboBox.selectedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@ -72,55 +114,78 @@ export class HaComboBox extends LitElement {
|
|||||||
.itemValuePath=${this.itemValuePath}
|
.itemValuePath=${this.itemValuePath}
|
||||||
.itemIdPath=${this.itemIdPath}
|
.itemIdPath=${this.itemIdPath}
|
||||||
.itemLabelPath=${this.itemLabelPath}
|
.itemLabelPath=${this.itemLabelPath}
|
||||||
.value=${this.value}
|
.value=${this.value || ""}
|
||||||
.items=${this.items}
|
.items=${this.items}
|
||||||
.filteredItems=${this.filteredItems}
|
.filteredItems=${this.filteredItems}
|
||||||
.allowCustomValue=${this.allowCustomValue}
|
.allowCustomValue=${this.allowCustomValue}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
${comboBoxRenderer(this.renderer || defaultRowRenderer)}
|
${comboBoxRenderer(this.renderer || this._defaultRowRenderer)}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@filter-changed=${this._filterChanged}
|
@filter-changed=${this._filterChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
|
attr-for-value="value"
|
||||||
>
|
>
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
|
.placeholder=${this.placeholder}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
.validationMessage=${this.validationMessage}
|
||||||
|
.errorMessage=${this.errorMessage}
|
||||||
class="input"
|
class="input"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
|
.suffix=${html`<div style="width: 28px;"></div>`}
|
||||||
|
.icon=${this.icon}
|
||||||
|
.invalid=${this.invalid}
|
||||||
>
|
>
|
||||||
${this.value
|
<slot name="icon" slot="leadingIcon"></slot>
|
||||||
? html`
|
</ha-textfield>
|
||||||
<ha-icon-button
|
${this.value
|
||||||
.label=${this.hass.localize("ui.components.combo-box.clear")}
|
? html`<ha-svg-icon
|
||||||
.path=${mdiClose}
|
aria-label=${this.hass?.localize("ui.components.combo-box.clear")}
|
||||||
slot="suffix"
|
class="clear-button"
|
||||||
class="clear-button"
|
.path=${mdiClose}
|
||||||
@click=${this._clearValue}
|
@click=${this._clearValue}
|
||||||
></ha-icon-button>
|
></ha-svg-icon>`
|
||||||
`
|
: ""}
|
||||||
: ""}
|
<ha-svg-icon
|
||||||
|
aria-label=${this.hass?.localize("ui.components.combo-box.show")}
|
||||||
<ha-icon-button
|
class="toggle-button"
|
||||||
.label=${this.hass.localize("ui.components.combo-box.show")}
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
@click=${this._toggleOpen}
|
||||||
slot="suffix"
|
></ha-svg-icon>
|
||||||
class="toggle-button"
|
|
||||||
></ha-icon-button>
|
|
||||||
</paper-input>
|
|
||||||
</vaadin-combo-box-light>
|
</vaadin-combo-box-light>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _defaultRowRenderer: ComboBoxLitRenderer<
|
||||||
|
string | Record<string, any>
|
||||||
|
> = (item) =>
|
||||||
|
html`<mwc-list-item>
|
||||||
|
${this.itemLabelPath ? item[this.itemLabelPath] : item}
|
||||||
|
</mwc-list-item>`;
|
||||||
|
|
||||||
private _clearValue(ev: Event) {
|
private _clearValue(ev: Event) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
fireEvent(this, "value-changed", { value: undefined });
|
fireEvent(this, "value-changed", { value: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _toggleOpen(ev: Event) {
|
||||||
|
if (this._opened) {
|
||||||
|
this._comboBox?.close();
|
||||||
|
ev.stopPropagation();
|
||||||
|
} else {
|
||||||
|
this._comboBox?.inputElement.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||||
this._opened = ev.detail.value;
|
// delay this so we can handle click event before setting _opened
|
||||||
|
setTimeout(() => {
|
||||||
|
this._opened = ev.detail.value;
|
||||||
|
}, 0);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
fireEvent(this, ev.type, ev.detail);
|
fireEvent(this, ev.type, ev.detail);
|
||||||
}
|
}
|
||||||
@ -141,11 +206,38 @@ export class HaComboBox extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
paper-input > ha-icon-button {
|
:host {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
vaadin-combo-box-light {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
ha-textfield {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
ha-textfield > ha-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
ha-svg-icon {
|
||||||
|
color: var(--input-dropdown-icon-color);
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.toggle-button {
|
||||||
|
right: 12px;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
:host([opened]) .toggle-button {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
.clear-button {
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
top: -7px;
|
||||||
|
right: 36px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,140 +1,78 @@
|
|||||||
import { mdiCalendar } from "@mdi/js";
|
import { mdiCalendar } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker-light";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
import { formatDateNumeric } from "../common/datetime/format_date";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
import "./ha-textfield";
|
||||||
|
|
||||||
const i18n = {
|
const loadDatePickerDialog = () => import("./ha-dialog-date-picker");
|
||||||
monthNames: [
|
|
||||||
"January",
|
|
||||||
"February",
|
|
||||||
"March",
|
|
||||||
"April",
|
|
||||||
"May",
|
|
||||||
"June",
|
|
||||||
"July",
|
|
||||||
"August",
|
|
||||||
"September",
|
|
||||||
"October",
|
|
||||||
"November",
|
|
||||||
"December",
|
|
||||||
],
|
|
||||||
weekdays: [
|
|
||||||
"Sunday",
|
|
||||||
"Monday",
|
|
||||||
"Tuesday",
|
|
||||||
"Wednesday",
|
|
||||||
"Thursday",
|
|
||||||
"Friday",
|
|
||||||
"Saturday",
|
|
||||||
],
|
|
||||||
weekdaysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
|
||||||
firstDayOfWeek: 0,
|
|
||||||
week: "Week",
|
|
||||||
calendar: "Calendar",
|
|
||||||
clear: "Clear",
|
|
||||||
today: "Today",
|
|
||||||
cancel: "Cancel",
|
|
||||||
formatTitle: (monthName, fullYear) => monthName + " " + fullYear,
|
|
||||||
formatDate: (d: { day: number; month: number; year: number }) =>
|
|
||||||
[
|
|
||||||
("0000" + String(d.year)).slice(-4),
|
|
||||||
("0" + String(d.month + 1)).slice(-2),
|
|
||||||
("0" + String(d.day)).slice(-2),
|
|
||||||
].join("-"),
|
|
||||||
parseDate: (text: string) => {
|
|
||||||
const parts = text.split("-");
|
|
||||||
const today = new Date();
|
|
||||||
let date;
|
|
||||||
let month = today.getMonth();
|
|
||||||
let year = today.getFullYear();
|
|
||||||
if (parts.length === 3) {
|
|
||||||
year = parseInt(parts[0]);
|
|
||||||
if (parts[0].length < 3 && year >= 0) {
|
|
||||||
year += year < 50 ? 2000 : 1900;
|
|
||||||
}
|
|
||||||
month = parseInt(parts[1]) - 1;
|
|
||||||
date = parseInt(parts[2]);
|
|
||||||
} else if (parts.length === 2) {
|
|
||||||
month = parseInt(parts[0]) - 1;
|
|
||||||
date = parseInt(parts[1]);
|
|
||||||
} else if (parts.length === 1) {
|
|
||||||
date = parseInt(parts[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (date !== undefined) {
|
export interface datePickerDialogParams {
|
||||||
return { day: date, month, year };
|
value?: string;
|
||||||
}
|
min?: string;
|
||||||
return undefined;
|
max?: string;
|
||||||
},
|
locale?: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const showDatePickerDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: datePickerDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "ha-dialog-date-picker",
|
||||||
|
dialogImport: loadDatePickerDialog,
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
@customElement("ha-date-input")
|
@customElement("ha-date-input")
|
||||||
export class HaDateInput extends LitElement {
|
export class HaDateInput extends LitElement {
|
||||||
|
@property({ attribute: false }) public locale!: HomeAssistant["locale"];
|
||||||
|
|
||||||
@property() public value?: string;
|
@property() public value?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@query("vaadin-date-picker-light", true) private _datePicker;
|
|
||||||
|
|
||||||
private _inited = false;
|
|
||||||
|
|
||||||
updated(changedProps: PropertyValues) {
|
|
||||||
if (changedProps.has("value")) {
|
|
||||||
this._datePicker.value = this.value;
|
|
||||||
this._inited = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<vaadin-date-picker-light
|
return html`<ha-textfield
|
||||||
|
.label=${this.label}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._valueChanged}
|
iconTrailing="calendar"
|
||||||
attr-for-value="value"
|
@click=${this._openDialog}
|
||||||
.i18n=${i18n}
|
.value=${this.value
|
||||||
|
? formatDateNumeric(new Date(this.value), this.locale)
|
||||||
|
: ""}
|
||||||
>
|
>
|
||||||
<paper-input
|
<ha-svg-icon slot="trailingIcon" .path=${mdiCalendar}></ha-svg-icon>
|
||||||
.label=${this.label}
|
</ha-textfield>`;
|
||||||
.disabled=${this.disabled}
|
|
||||||
no-label-float
|
|
||||||
>
|
|
||||||
<ha-svg-icon slot="suffix" .path=${mdiCalendar}></ha-svg-icon>
|
|
||||||
</paper-input>
|
|
||||||
</vaadin-date-picker-light>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
private _openDialog() {
|
||||||
if (
|
if (this.disabled) {
|
||||||
!this.value ||
|
return;
|
||||||
(this._inited && !this._compareStringDates(ev.detail.value, this.value))
|
}
|
||||||
) {
|
showDatePickerDialog(this, {
|
||||||
this.value = ev.detail.value;
|
min: "1970-01-01",
|
||||||
|
value: this.value,
|
||||||
|
onChange: (value) => this._valueChanged(value),
|
||||||
|
locale: this.locale.language,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(value: string) {
|
||||||
|
if (this.value !== value) {
|
||||||
|
this.value = value;
|
||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
fireEvent(this, "value-changed", { value: ev.detail.value });
|
fireEvent(this, "value-changed", { value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _compareStringDates(a: string, b: string): boolean {
|
|
||||||
const aParts = a.split("-");
|
|
||||||
const bParts = b.split("-");
|
|
||||||
let i = 0;
|
|
||||||
for (const aPart of aParts) {
|
|
||||||
if (Number(aPart) !== Number(bParts[i])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
paper-input {
|
|
||||||
width: 110px;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
ha-svg-icon {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import "@material/mwc-list/mwc-list";
|
|||||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiCalendar } from "@mdi/js";
|
import { mdiCalendar } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@ -19,6 +18,7 @@ import { computeRTLDirection } from "../common/util/compute_rtl";
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./date-range-picker";
|
import "./date-range-picker";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
import "./ha-textfield";
|
||||||
|
|
||||||
export interface DateRangePickerRanges {
|
export interface DateRangePickerRanges {
|
||||||
[key: string]: [Date, Date];
|
[key: string]: [Date, Date];
|
||||||
@ -61,7 +61,7 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
>
|
>
|
||||||
<div slot="input" class="date-range-inputs">
|
<div slot="input" class="date-range-inputs">
|
||||||
<ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.value=${formatDateTime(this.startDate, this.hass.locale)}
|
.value=${formatDateTime(this.startDate, this.hass.locale)}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.date-range-picker.start_date"
|
"ui.components.date-range-picker.start_date"
|
||||||
@ -69,16 +69,16 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@click=${this._handleInputClick}
|
@click=${this._handleInputClick}
|
||||||
readonly
|
readonly
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.value=${formatDateTime(this.endDate, this.hass.locale)}
|
.value=${formatDateTime(this.endDate, this.hass.locale)}
|
||||||
label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.date-range-picker.end_date"
|
"ui.components.date-range-picker.end_date"
|
||||||
)}
|
)}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@click=${this._handleInputClick}
|
@click=${this._handleInputClick}
|
||||||
readonly
|
readonly
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
</div>
|
</div>
|
||||||
${this.ranges
|
${this.ranges
|
||||||
? html`<div
|
? html`<div
|
||||||
@ -158,13 +158,13 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
border-top: 1px solid var(--divider-color);
|
border-top: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-input {
|
ha-textfield {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-input:last-child {
|
ha-textfield:last-child {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 500px) {
|
@media only screen and (max-width: 500px) {
|
||||||
paper-input {
|
ha-textfield {
|
||||||
min-width: inherit;
|
min-width: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
106
src/components/ha-dialog-date-picker.ts
Normal file
106
src/components/ha-dialog-date-picker.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import "app-datepicker";
|
||||||
|
import { css, html, LitElement } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { haStyleDialog } from "../resources/styles";
|
||||||
|
import { datePickerDialogParams } from "./ha-date-input";
|
||||||
|
import "./ha-dialog";
|
||||||
|
|
||||||
|
@customElement("ha-dialog-date-picker")
|
||||||
|
export class HaDialogDatePicker extends LitElement {
|
||||||
|
@property() public value?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@state() private _params?: datePickerDialogParams;
|
||||||
|
|
||||||
|
@state() private _value?: string;
|
||||||
|
|
||||||
|
public showDialog(params: datePickerDialogParams): void {
|
||||||
|
this._params = params;
|
||||||
|
this._value = params.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog() {
|
||||||
|
this._params = undefined;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this._params) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
return html`<ha-dialog open @closed=${this.closeDialog}>
|
||||||
|
<app-datepicker
|
||||||
|
.value=${this._value}
|
||||||
|
.min=${this._params.min}
|
||||||
|
.max=${this._params.max}
|
||||||
|
.locale=${this._params.locale}
|
||||||
|
@datepicker-value-updated=${this._valueChanged}
|
||||||
|
></app-datepicker>
|
||||||
|
<mwc-button slot="secondaryAction" @click=${this._setToday}
|
||||||
|
>today</mwc-button
|
||||||
|
>
|
||||||
|
<mwc-button slot="primaryAction" dialogaction="cancel" class="cancel-btn">
|
||||||
|
cancel
|
||||||
|
</mwc-button>
|
||||||
|
<mwc-button slot="primaryAction" @click=${this._setValue}>ok</mwc-button>
|
||||||
|
</ha-dialog>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent) {
|
||||||
|
this._value = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setToday() {
|
||||||
|
this._value = new Date().toISOString().split("T")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setValue() {
|
||||||
|
this._params?.onChange(this._value!);
|
||||||
|
this.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-dialog {
|
||||||
|
--dialog-content-padding: 0;
|
||||||
|
--justify-action-buttons: space-between;
|
||||||
|
}
|
||||||
|
app-datepicker {
|
||||||
|
--app-datepicker-accent-color: var(--primary-color);
|
||||||
|
--app-datepicker-bg-color: transparent;
|
||||||
|
--app-datepicker-color: var(--primary-text-color);
|
||||||
|
--app-datepicker-disabled-day-color: var(--disabled-text-color);
|
||||||
|
--app-datepicker-focused-day-color: var(--text-primary-color);
|
||||||
|
--app-datepicker-focused-year-bg-color: var(--primary-color);
|
||||||
|
--app-datepicker-selector-color: var(--secondary-text-color);
|
||||||
|
--app-datepicker-separator-color: var(--divider-color);
|
||||||
|
--app-datepicker-weekday-color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
app-datepicker::part(calendar-day):focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
@media all and (min-width: 450px) {
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-min-width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
|
app-datepicker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-dialog-date-picker": HaDialogDatePicker;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { Dialog } from "@material/mwc-dialog";
|
import { DialogBase } from "@material/mwc-dialog/mwc-dialog-base";
|
||||||
|
import { styles } from "@material/mwc-dialog/mwc-dialog.css";
|
||||||
import { mdiClose } from "@mdi/js";
|
import { mdiClose } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, TemplateResult } from "lit";
|
import { css, html, TemplateResult } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
@ -21,8 +22,7 @@ export const createCloseHeading = (
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
@customElement("ha-dialog")
|
@customElement("ha-dialog")
|
||||||
// @ts-expect-error
|
export class HaDialog extends DialogBase {
|
||||||
export class HaDialog extends Dialog {
|
|
||||||
public scrollToPos(x: number, y: number) {
|
public scrollToPos(x: number, y: number) {
|
||||||
this.contentElement?.scrollTo(x, y);
|
this.contentElement?.scrollTo(x, y);
|
||||||
}
|
}
|
||||||
@ -31,77 +31,75 @@ export class HaDialog extends Dialog {
|
|||||||
return html`<slot name="heading"> ${super.renderHeading()} </slot>`;
|
return html`<slot name="heading"> ${super.renderHeading()} </slot>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static get styles(): CSSResultGroup {
|
static override styles = [
|
||||||
return [
|
styles,
|
||||||
Dialog.styles,
|
css`
|
||||||
css`
|
.mdc-dialog {
|
||||||
.mdc-dialog {
|
--mdc-dialog-scroll-divider-color: var(--divider-color);
|
||||||
--mdc-dialog-scroll-divider-color: var(--divider-color);
|
z-index: var(--dialog-z-index, 7);
|
||||||
z-index: var(--dialog-z-index, 7);
|
-webkit-backdrop-filter: var(--dialog-backdrop-filter, none);
|
||||||
-webkit-backdrop-filter: var(--dialog-backdrop-filter, none);
|
backdrop-filter: var(--dialog-backdrop-filter, none);
|
||||||
backdrop-filter: var(--dialog-backdrop-filter, none);
|
}
|
||||||
}
|
.mdc-dialog__actions {
|
||||||
.mdc-dialog__actions {
|
justify-content: var(--justify-action-buttons, flex-end);
|
||||||
justify-content: var(--justify-action-buttons, flex-end);
|
padding-bottom: max(env(safe-area-inset-bottom), 8px);
|
||||||
padding-bottom: max(env(safe-area-inset-bottom), 8px);
|
}
|
||||||
}
|
.mdc-dialog__actions span:nth-child(1) {
|
||||||
.mdc-dialog__actions span:nth-child(1) {
|
flex: var(--secondary-action-button-flex, unset);
|
||||||
flex: var(--secondary-action-button-flex, unset);
|
}
|
||||||
}
|
.mdc-dialog__actions span:nth-child(2) {
|
||||||
.mdc-dialog__actions span:nth-child(2) {
|
flex: var(--primary-action-button-flex, unset);
|
||||||
flex: var(--primary-action-button-flex, unset);
|
}
|
||||||
}
|
.mdc-dialog__container {
|
||||||
.mdc-dialog__container {
|
align-items: var(--vertial-align-dialog, center);
|
||||||
align-items: var(--vertial-align-dialog, center);
|
}
|
||||||
}
|
.mdc-dialog__title::before {
|
||||||
.mdc-dialog__title::before {
|
display: block;
|
||||||
display: block;
|
height: 20px;
|
||||||
height: 20px;
|
}
|
||||||
}
|
.mdc-dialog .mdc-dialog__content {
|
||||||
.mdc-dialog .mdc-dialog__content {
|
position: var(--dialog-content-position, relative);
|
||||||
position: var(--dialog-content-position, relative);
|
padding: var(--dialog-content-padding, 20px 24px);
|
||||||
padding: var(--dialog-content-padding, 20px 24px);
|
}
|
||||||
}
|
:host([hideactions]) .mdc-dialog .mdc-dialog__content {
|
||||||
:host([hideactions]) .mdc-dialog .mdc-dialog__content {
|
padding-bottom: max(
|
||||||
padding-bottom: max(
|
var(--dialog-content-padding, 20px),
|
||||||
var(--dialog-content-padding, 20px),
|
env(safe-area-inset-bottom)
|
||||||
env(safe-area-inset-bottom)
|
);
|
||||||
);
|
}
|
||||||
}
|
.mdc-dialog .mdc-dialog__surface {
|
||||||
.mdc-dialog .mdc-dialog__surface {
|
position: var(--dialog-surface-position, relative);
|
||||||
position: var(--dialog-surface-position, relative);
|
top: var(--dialog-surface-top);
|
||||||
top: var(--dialog-surface-top);
|
min-height: var(--mdc-dialog-min-height, auto);
|
||||||
min-height: var(--mdc-dialog-min-height, auto);
|
border-radius: var(
|
||||||
border-radius: var(
|
--ha-dialog-border-radius,
|
||||||
--ha-dialog-border-radius,
|
var(--ha-card-border-radius, 4px)
|
||||||
var(--ha-card-border-radius, 4px)
|
);
|
||||||
);
|
}
|
||||||
}
|
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
|
||||||
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
|
display: flex;
|
||||||
display: flex;
|
flex-direction: column;
|
||||||
flex-direction: column;
|
}
|
||||||
}
|
.header_button {
|
||||||
.header_button {
|
position: absolute;
|
||||||
position: absolute;
|
right: 16px;
|
||||||
right: 16px;
|
top: 10px;
|
||||||
top: 10px;
|
text-decoration: none;
|
||||||
text-decoration: none;
|
color: inherit;
|
||||||
color: inherit;
|
}
|
||||||
}
|
.header_title {
|
||||||
.header_title {
|
margin-right: 40px;
|
||||||
margin-right: 40px;
|
}
|
||||||
}
|
[dir="rtl"].header_button {
|
||||||
[dir="rtl"].header_button {
|
right: auto;
|
||||||
right: auto;
|
left: 16px;
|
||||||
left: 16px;
|
}
|
||||||
}
|
[dir="rtl"].header_title {
|
||||||
[dir="rtl"].header_title {
|
margin-left: 40px;
|
||||||
margin-left: 40px;
|
margin-right: 0px;
|
||||||
margin-right: 0px;
|
}
|
||||||
}
|
`,
|
||||||
`,
|
];
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import "./paper-time-input";
|
import "./ha-base-time-input";
|
||||||
|
import type { TimeChangedEvent } from "./ha-base-time-input";
|
||||||
|
|
||||||
export interface HaDurationData {
|
export interface HaDurationData {
|
||||||
hours?: number;
|
hours?: number;
|
||||||
@ -32,110 +33,69 @@ class HaDurationInput extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<paper-time-input
|
<ha-base-time-input
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.autoValidate=${this.required}
|
.autoValidate=${this.required}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
error-message="Required"
|
errorMessage="Required"
|
||||||
enable-second
|
enableSecond
|
||||||
.enableMillisecond=${this.enableMillisecond}
|
.enableMillisecond=${this.enableMillisecond}
|
||||||
format="24"
|
format="24"
|
||||||
.hour=${this._parseDuration(this._hours)}
|
.hours=${this._hours}
|
||||||
.min=${this._parseDuration(this._minutes)}
|
.minutes=${this._minutes}
|
||||||
.sec=${this._parseDuration(this._seconds)}
|
.seconds=${this._seconds}
|
||||||
.millisec=${this._parseDurationMillisec(this._milliseconds)}
|
.milliseconds=${this._milliseconds}
|
||||||
@hour-changed=${this._hourChanged}
|
@value-changed=${this._durationChanged}
|
||||||
@min-changed=${this._minChanged}
|
noHoursLimit
|
||||||
@sec-changed=${this._secChanged}
|
hourLabel="hh"
|
||||||
@millisec-changed=${this._millisecChanged}
|
minLabel="mm"
|
||||||
float-input-labels
|
secLabel="ss"
|
||||||
no-hours-limit
|
millisecLabel="ms"
|
||||||
always-float-input-labels
|
></ha-base-time-input>
|
||||||
hour-label="hh"
|
|
||||||
min-label="mm"
|
|
||||||
sec-label="ss"
|
|
||||||
millisec-label="ms"
|
|
||||||
></paper-time-input>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _hours() {
|
private get _hours() {
|
||||||
return this.data && this.data.hours ? Number(this.data.hours) : 0;
|
return this.data?.hours ? Number(this.data.hours) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _minutes() {
|
private get _minutes() {
|
||||||
return this.data && this.data.minutes ? Number(this.data.minutes) : 0;
|
return this.data?.minutes ? Number(this.data.minutes) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _seconds() {
|
private get _seconds() {
|
||||||
return this.data && this.data.seconds ? Number(this.data.seconds) : 0;
|
return this.data?.seconds ? Number(this.data.seconds) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _milliseconds() {
|
private get _milliseconds() {
|
||||||
return this.data && this.data.milliseconds
|
return this.data?.milliseconds ? Number(this.data.milliseconds) : 0;
|
||||||
? Number(this.data.milliseconds)
|
|
||||||
: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _parseDuration(value) {
|
private _durationChanged(ev: CustomEvent<{ value: TimeChangedEvent }>) {
|
||||||
return value.toString().padStart(2, "0");
|
ev.stopPropagation();
|
||||||
}
|
const value = { ...ev.detail.value };
|
||||||
|
|
||||||
private _parseDurationMillisec(value) {
|
if (!this.enableMillisecond && !value.milliseconds) {
|
||||||
return value.toString().padStart(3, "0");
|
// @ts-ignore
|
||||||
}
|
delete value.milliseconds;
|
||||||
|
} else if (value.milliseconds > 999) {
|
||||||
private _hourChanged(ev) {
|
value.seconds += Math.floor(value.milliseconds / 1000);
|
||||||
this._durationChanged(ev, "hours");
|
value.milliseconds %= 1000;
|
||||||
}
|
|
||||||
|
|
||||||
private _minChanged(ev) {
|
|
||||||
this._durationChanged(ev, "minutes");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _secChanged(ev) {
|
|
||||||
this._durationChanged(ev, "seconds");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _millisecChanged(ev) {
|
|
||||||
this._durationChanged(ev, "milliseconds");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _durationChanged(ev, unit) {
|
|
||||||
let value = Number(ev.detail.value);
|
|
||||||
|
|
||||||
if (value === this[`_${unit}`]) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let hours = this._hours;
|
if (value.seconds > 59) {
|
||||||
let minutes = this._minutes;
|
value.minutes += Math.floor(value.seconds / 60);
|
||||||
|
value.seconds %= 60;
|
||||||
if (unit === "seconds" && value > 59) {
|
|
||||||
minutes += Math.floor(value / 60);
|
|
||||||
value %= 60;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unit === "minutes" && value > 59) {
|
if (value.minutes > 59) {
|
||||||
hours += Math.floor(value / 60);
|
value.hours += Math.floor(value.minutes / 60);
|
||||||
value %= 60;
|
value.minutes %= 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newValue: HaDurationData = {
|
|
||||||
hours,
|
|
||||||
minutes,
|
|
||||||
seconds: this._seconds,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.enableMillisecond || this._milliseconds) {
|
|
||||||
newValue.milliseconds = this._milliseconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
newValue[unit] = value;
|
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: newValue,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import "@material/mwc-textfield";
|
|
||||||
import type { TextField } from "@material/mwc-textfield";
|
|
||||||
import { css, html, LitElement, TemplateResult, PropertyValues } from "lit";
|
import { css, html, LitElement, TemplateResult, PropertyValues } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import type { HaTextField } from "../ha-textfield";
|
||||||
|
import "../ha-textfield";
|
||||||
import { HaFormElement, HaFormFloatData, HaFormFloatSchema } from "./types";
|
import { HaFormElement, HaFormFloatData, HaFormFloatSchema } from "./types";
|
||||||
|
|
||||||
@customElement("ha-form-float")
|
@customElement("ha-form-float")
|
||||||
export class HaFormFloat extends LitElement implements HaFormElement {
|
export class HaFormFloat extends LitElement implements HaFormElement {
|
||||||
@property() public schema!: HaFormFloatSchema;
|
@property({ attribute: false }) public schema!: HaFormFloatSchema;
|
||||||
|
|
||||||
@property() public data!: HaFormFloatData;
|
@property({ attribute: false }) public data!: HaFormFloatData;
|
||||||
|
|
||||||
@property() public label!: string;
|
@property() public label!: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@query("mwc-textfield") private _input?: HTMLElement;
|
@query("ha-textfield") private _input?: HaTextField;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
if (this._input) {
|
if (this._input) {
|
||||||
@ -25,7 +25,7 @@ export class HaFormFloat extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<mwc-textfield
|
<ha-textfield
|
||||||
inputMode="decimal"
|
inputMode="decimal"
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${this.data !== undefined ? this.data : ""}
|
.value=${this.data !== undefined ? this.data : ""}
|
||||||
@ -35,7 +35,7 @@ export class HaFormFloat extends LitElement implements HaFormElement {
|
|||||||
.suffix=${this.schema.description?.suffix}
|
.suffix=${this.schema.description?.suffix}
|
||||||
.validationMessage=${this.schema.required ? "Required" : undefined}
|
.validationMessage=${this.schema.required ? "Required" : undefined}
|
||||||
@input=${this._valueChanged}
|
@input=${this._valueChanged}
|
||||||
></mwc-textfield>
|
></ha-textfield>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ export class HaFormFloat extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: Event) {
|
private _valueChanged(ev: Event) {
|
||||||
const source = ev.target as TextField;
|
const source = ev.target as HaTextField;
|
||||||
const rawValue = source.value.replace(",", ".");
|
const rawValue = source.value.replace(",", ".");
|
||||||
|
|
||||||
let value: number | undefined;
|
let value: number | undefined;
|
||||||
@ -81,7 +81,7 @@ export class HaFormFloat extends LitElement implements HaFormElement {
|
|||||||
:host([own-margin]) {
|
:host([own-margin]) {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
mwc-textfield {
|
ha-textfield {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
95
src/components/ha-form/ha-form-grid.ts
Normal file
95
src/components/ha-form/ha-form-grid.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import "./ha-form";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import type {
|
||||||
|
HaFormGridSchema,
|
||||||
|
HaFormDataContainer,
|
||||||
|
HaFormElement,
|
||||||
|
HaFormSchema,
|
||||||
|
} from "./types";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
|
@customElement("ha-form-grid")
|
||||||
|
export class HaFormGrid extends LitElement implements HaFormElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public data!: HaFormDataContainer;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public schema!: HaFormGridSchema;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property() public computeLabel?: (
|
||||||
|
schema: HaFormSchema,
|
||||||
|
data?: HaFormDataContainer
|
||||||
|
) => string;
|
||||||
|
|
||||||
|
@property() public computeHelper?: (schema: HaFormSchema) => string;
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this.setAttribute("own-margin", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues): void {
|
||||||
|
super.updated(changedProps);
|
||||||
|
if (changedProps.has("schema")) {
|
||||||
|
if (this.schema.column_min_width) {
|
||||||
|
this.style.setProperty(
|
||||||
|
"--form-grid-min-width",
|
||||||
|
this.schema.column_min_width
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.style.setProperty("--form-grid-min-width", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${this.schema.schema.map(
|
||||||
|
(item) =>
|
||||||
|
html`
|
||||||
|
<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this.data}
|
||||||
|
.schema=${[item]}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.computeLabel=${this.computeLabel}
|
||||||
|
.computeHelper=${this.computeHelper}
|
||||||
|
></ha-form>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: repeat(
|
||||||
|
var(--form-grid-column-count, auto-fit),
|
||||||
|
minmax(var(--form-grid-min-width, 200px), 1fr)
|
||||||
|
);
|
||||||
|
grid-gap: 8px;
|
||||||
|
}
|
||||||
|
:host > ha-form {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-form-grid": HaFormGrid;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,3 @@
|
|||||||
import "@material/mwc-textfield";
|
|
||||||
import type { TextField } from "@material/mwc-textfield";
|
|
||||||
import type { Slider } from "@material/mwc-slider";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@ -14,18 +11,21 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { HaCheckbox } from "../ha-checkbox";
|
import { HaCheckbox } from "../ha-checkbox";
|
||||||
import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types";
|
import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types";
|
||||||
import "../ha-slider";
|
import "../ha-slider";
|
||||||
|
import { HaTextField } from "../ha-textfield";
|
||||||
|
|
||||||
@customElement("ha-form-integer")
|
@customElement("ha-form-integer")
|
||||||
export class HaFormInteger extends LitElement implements HaFormElement {
|
export class HaFormInteger extends LitElement implements HaFormElement {
|
||||||
@property() public schema!: HaFormIntegerSchema;
|
@property({ attribute: false }) public schema!: HaFormIntegerSchema;
|
||||||
|
|
||||||
@property() public data?: HaFormIntegerData;
|
@property({ attribute: false }) public data?: HaFormIntegerData;
|
||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@query("paper-input ha-slider") private _input?: HTMLElement;
|
@query("ha-textfield ha-slider") private _input?:
|
||||||
|
| HaTextField
|
||||||
|
| HTMLInputElement;
|
||||||
|
|
||||||
private _lastValue?: HaFormIntegerData;
|
private _lastValue?: HaFormIntegerData;
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
<div>
|
<div>
|
||||||
${this.label}
|
${this.label}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
${this.schema.optional
|
${!this.schema.required
|
||||||
? html`
|
? html`
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
@change=${this._handleCheckboxChange}
|
@change=${this._handleCheckboxChange}
|
||||||
@ -61,7 +61,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
.min=${this.schema.valueMin}
|
.min=${this.schema.valueMin}
|
||||||
.max=${this.schema.valueMax}
|
.max=${this.schema.valueMax}
|
||||||
.disabled=${this.disabled ||
|
.disabled=${this.disabled ||
|
||||||
(this.data === undefined && this.schema.optional)}
|
(this.data === undefined && !this.schema.required)}
|
||||||
@change=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
></ha-slider>
|
></ha-slider>
|
||||||
</div>
|
</div>
|
||||||
@ -70,7 +70,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<mwc-textfield
|
<ha-textfield
|
||||||
type="number"
|
type="number"
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
@ -81,7 +81,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
.suffix=${this.schema.description?.suffix}
|
.suffix=${this.schema.description?.suffix}
|
||||||
.validationMessage=${this.schema.required ? "Required" : undefined}
|
.validationMessage=${this.schema.required ? "Required" : undefined}
|
||||||
@input=${this._valueChanged}
|
@input=${this._valueChanged}
|
||||||
></mwc-textfield>
|
></ha-textfield>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
return this.data;
|
return this.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.schema.optional) {
|
if (!this.schema.required) {
|
||||||
return this.schema.valueMin || 0;
|
return this.schema.valueMin || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: Event) {
|
private _valueChanged(ev: Event) {
|
||||||
const source = ev.target as TextField | Slider;
|
const source = ev.target as HaTextField | HTMLInputElement;
|
||||||
const rawValue = source.value;
|
const rawValue = source.value;
|
||||||
|
|
||||||
let value: number | undefined;
|
let value: number | undefined;
|
||||||
@ -172,7 +172,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
ha-slider {
|
ha-slider {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
mwc-textfield {
|
ha-textfield {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1,25 +1,27 @@
|
|||||||
import { mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
import { mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
import "@material/mwc-textfield";
|
|
||||||
import "@material/mwc-formfield";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
TemplateResult,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-button-menu";
|
import "../ha-button-menu";
|
||||||
|
import "../ha-check-list-item";
|
||||||
|
import type { HaCheckListItem } from "../ha-check-list-item";
|
||||||
|
import "../ha-checkbox";
|
||||||
|
import type { HaCheckbox } from "../ha-checkbox";
|
||||||
|
import "../ha-formfield";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
|
import "../ha-textfield";
|
||||||
import {
|
import {
|
||||||
HaFormElement,
|
HaFormElement,
|
||||||
HaFormMultiSelectData,
|
HaFormMultiSelectData,
|
||||||
HaFormMultiSelectSchema,
|
HaFormMultiSelectSchema,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import "../ha-checkbox";
|
|
||||||
import type { HaCheckbox } from "../ha-checkbox";
|
|
||||||
|
|
||||||
function optionValue(item: string | string[]): string {
|
function optionValue(item: string | string[]): string {
|
||||||
return Array.isArray(item) ? item[0] : item;
|
return Array.isArray(item) ? item[0] : item;
|
||||||
@ -57,23 +59,23 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
|||||||
: Object.entries(this.schema.options);
|
: Object.entries(this.schema.options);
|
||||||
const data = this.data || [];
|
const data = this.data || [];
|
||||||
|
|
||||||
const renderedOptions = options.map((item: string | [string, string]) => {
|
|
||||||
const value = optionValue(item);
|
|
||||||
return html`
|
|
||||||
<mwc-formfield .label=${optionLabel(item)}>
|
|
||||||
<ha-checkbox
|
|
||||||
.checked=${data.includes(value)}
|
|
||||||
.value=${value}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
></ha-checkbox>
|
|
||||||
</mwc-formfield>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// We will just render all checkboxes.
|
// We will just render all checkboxes.
|
||||||
if (options.length < SHOW_ALL_ENTRIES_LIMIT) {
|
if (options.length < SHOW_ALL_ENTRIES_LIMIT) {
|
||||||
return html`<div>${this.label}${renderedOptions}</div> `;
|
return html`<div>
|
||||||
|
${this.label}${options.map((item: string | [string, string]) => {
|
||||||
|
const value = optionValue(item);
|
||||||
|
return html`
|
||||||
|
<ha-formfield .label=${optionLabel(item)}>
|
||||||
|
<ha-checkbox
|
||||||
|
.checked=${data.includes(value)}
|
||||||
|
.value=${value}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@change=${this._valueChanged}
|
||||||
|
></ha-checkbox>
|
||||||
|
</ha-formfield>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</div> `;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@ -83,8 +85,10 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
|||||||
corner="BOTTOM_START"
|
corner="BOTTOM_START"
|
||||||
@opened=${this._handleOpen}
|
@opened=${this._handleOpen}
|
||||||
@closed=${this._handleClose}
|
@closed=${this._handleClose}
|
||||||
|
multi
|
||||||
|
activatable
|
||||||
>
|
>
|
||||||
<mwc-textfield
|
<ha-textfield
|
||||||
slot="trigger"
|
slot="trigger"
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${data
|
.value=${data
|
||||||
@ -92,12 +96,25 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
|||||||
.join(", ")}
|
.join(", ")}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
></mwc-textfield>
|
></ha-textfield>
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
slot="trigger"
|
slot="trigger"
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
${renderedOptions}
|
${options.map((item: string | [string, string]) => {
|
||||||
|
const value = optionValue(item);
|
||||||
|
const selected = data.includes(value);
|
||||||
|
return html`<ha-check-list-item
|
||||||
|
left
|
||||||
|
.selected=${selected}
|
||||||
|
.activated=${selected}
|
||||||
|
@request-selected=${this._selectedChanged}
|
||||||
|
.value=${value}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
>
|
||||||
|
${optionLabel(item)}
|
||||||
|
</ha-check-list-item>`;
|
||||||
|
})}
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -105,7 +122,7 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
|||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
const { formElement, mdcRoot } =
|
const { formElement, mdcRoot } =
|
||||||
this.shadowRoot?.querySelector("mwc-textfield") || ({} as any);
|
this.shadowRoot?.querySelector("ha-textfield") || ({} as any);
|
||||||
if (formElement) {
|
if (formElement) {
|
||||||
formElement.style.textOverflow = "ellipsis";
|
formElement.style.textOverflow = "ellipsis";
|
||||||
}
|
}
|
||||||
@ -125,9 +142,23 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _selectedChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (ev.detail.source === "property") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._handleValueChanged(
|
||||||
|
(ev.target as HaCheckListItem).value,
|
||||||
|
ev.detail.selected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
const { value, checked } = ev.target as HaCheckbox;
|
const { value, checked } = ev.target as HaCheckbox;
|
||||||
|
this._handleValueChanged(value, checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleValueChanged(value, checked: boolean): void {
|
||||||
let newValue: string[];
|
let newValue: string[];
|
||||||
|
|
||||||
if (checked) {
|
if (checked) {
|
||||||
@ -171,11 +202,11 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
|||||||
display: block;
|
display: block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
mwc-formfield {
|
ha-formfield {
|
||||||
display: block;
|
display: block;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
}
|
}
|
||||||
mwc-textfield {
|
ha-textfield {
|
||||||
display: block;
|
display: block;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import "@material/mwc-select";
|
|
||||||
import type { Select } from "@material/mwc-select";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-radio";
|
|
||||||
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./types";
|
|
||||||
|
|
||||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||||
|
import "../ha-radio";
|
||||||
import type { HaRadio } from "../ha-radio";
|
import type { HaRadio } from "../ha-radio";
|
||||||
|
import "../ha-select";
|
||||||
|
import type { HaSelect } from "../ha-select";
|
||||||
|
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./types";
|
||||||
|
|
||||||
@customElement("ha-form-select")
|
@customElement("ha-form-select")
|
||||||
export class HaFormSelect extends LitElement implements HaFormElement {
|
export class HaFormSelect extends LitElement implements HaFormElement {
|
||||||
@ -20,7 +19,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@query("mwc-select", true) private _input?: HTMLElement;
|
@query("ha-select", true) private _input?: HTMLElement;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
if (this._input) {
|
if (this._input) {
|
||||||
@ -29,7 +28,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.schema.optional && this.schema.options!.length < 6) {
|
if (this.schema.required && this.schema.options!.length < 6) {
|
||||||
return html`
|
return html`
|
||||||
<div>
|
<div>
|
||||||
${this.label}
|
${this.label}
|
||||||
@ -50,7 +49,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<mwc-select
|
<ha-select
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
@ -59,7 +58,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
@selected=${this._valueChanged}
|
@selected=${this._valueChanged}
|
||||||
>
|
>
|
||||||
${this.schema.optional
|
${!this.schema.required
|
||||||
? html`<mwc-list-item value=""></mwc-list-item>`
|
? html`<mwc-list-item value=""></mwc-list-item>`
|
||||||
: ""}
|
: ""}
|
||||||
${this.schema.options!.map(
|
${this.schema.options!.map(
|
||||||
@ -67,13 +66,13 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
<mwc-list-item .value=${value}>${label}</mwc-list-item>
|
<mwc-list-item .value=${value}>${label}</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
private _valueChanged(ev: CustomEvent) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
let value: string | undefined = (ev.target as Select | HaRadio).value;
|
let value: string | undefined = (ev.target as HaSelect | HaRadio).value;
|
||||||
|
|
||||||
if (value === this.data) {
|
if (value === this.data) {
|
||||||
return;
|
return;
|
||||||
@ -90,7 +89,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
mwc-select,
|
ha-select,
|
||||||
mwc-formfield {
|
mwc-formfield {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { mdiEye, mdiEyeOff } from "@mdi/js";
|
import { mdiEye, mdiEyeOff } from "@mdi/js";
|
||||||
import "@material/mwc-textfield";
|
|
||||||
import type { TextField } from "@material/mwc-textfield";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
TemplateResult,
|
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-icon-button";
|
import "../ha-icon-button";
|
||||||
|
import "../ha-textfield";
|
||||||
|
import type { HaTextField } from "../ha-textfield";
|
||||||
import type {
|
import type {
|
||||||
HaFormElement,
|
HaFormElement,
|
||||||
HaFormStringData,
|
HaFormStringData,
|
||||||
@ -32,7 +32,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@state() private _unmaskedPassword = false;
|
@state() private _unmaskedPassword = false;
|
||||||
|
|
||||||
@query("mwc-textfield") private _input?: HTMLElement;
|
@query("ha-textfield") private _input?: HaTextField;
|
||||||
|
|
||||||
public focus(): void {
|
public focus(): void {
|
||||||
if (this._input) {
|
if (this._input) {
|
||||||
@ -45,7 +45,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
this.schema.name.includes(field)
|
this.schema.name.includes(field)
|
||||||
);
|
);
|
||||||
return html`
|
return html`
|
||||||
<mwc-textfield
|
<ha-textfield
|
||||||
.type=${!isPassword
|
.type=${!isPassword
|
||||||
? this._stringType
|
? this._stringType
|
||||||
: this._unmaskedPassword
|
: this._unmaskedPassword
|
||||||
@ -62,7 +62,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
: this.schema.description?.suffix}
|
: this.schema.description?.suffix}
|
||||||
.validationMessage=${this.schema.required ? "Required" : undefined}
|
.validationMessage=${this.schema.required ? "Required" : undefined}
|
||||||
@input=${this._valueChanged}
|
@input=${this._valueChanged}
|
||||||
></mwc-textfield>
|
></ha-textfield>
|
||||||
${isPassword
|
${isPassword
|
||||||
? html`<ha-icon-button
|
? html`<ha-icon-button
|
||||||
toggles
|
toggles
|
||||||
@ -85,11 +85,11 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: Event): void {
|
private _valueChanged(ev: Event): void {
|
||||||
let value: string | undefined = (ev.target as TextField).value;
|
let value: string | undefined = (ev.target as HaTextField).value;
|
||||||
if (this.data === value) {
|
if (this.data === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (value === "" && this.schema.optional) {
|
if (value === "" && !this.schema.required) {
|
||||||
value = undefined;
|
value = undefined;
|
||||||
}
|
}
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
@ -118,7 +118,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
:host([own-margin]) {
|
:host([own-margin]) {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
mwc-textfield {
|
ha-textfield {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
ha-icon-button {
|
ha-icon-button {
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-alert";
|
import "../ha-alert";
|
||||||
import "./ha-form-boolean";
|
import "./ha-form-boolean";
|
||||||
import "./ha-form-constant";
|
import "./ha-form-constant";
|
||||||
|
import "./ha-form-grid";
|
||||||
import "./ha-form-float";
|
import "./ha-form-float";
|
||||||
import "./ha-form-integer";
|
import "./ha-form-integer";
|
||||||
import "./ha-form-multi_select";
|
import "./ha-form-multi_select";
|
||||||
@ -12,14 +20,20 @@ import "./ha-form-positive_time_period_dict";
|
|||||||
import "./ha-form-select";
|
import "./ha-form-select";
|
||||||
import "./ha-form-string";
|
import "./ha-form-string";
|
||||||
import { HaFormElement, HaFormDataContainer, HaFormSchema } from "./types";
|
import { HaFormElement, HaFormDataContainer, HaFormSchema } from "./types";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
const getValue = (obj, item) => (obj ? obj[item.name] : null);
|
const getValue = (obj, item) =>
|
||||||
|
obj ? (!item.name ? obj : obj[item.name]) : null;
|
||||||
|
|
||||||
|
let selectorImported = false;
|
||||||
|
|
||||||
@customElement("ha-form")
|
@customElement("ha-form")
|
||||||
export class HaForm extends LitElement implements HaFormElement {
|
export class HaForm extends LitElement implements HaFormElement {
|
||||||
@property() public data!: HaFormDataContainer;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public schema!: HaFormSchema[];
|
@property({ attribute: false }) public data!: HaFormDataContainer;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public schema!: HaFormSchema[];
|
||||||
|
|
||||||
@property() public error?: Record<string, string>;
|
@property() public error?: Record<string, string>;
|
||||||
|
|
||||||
@ -27,7 +41,12 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public computeError?: (schema: HaFormSchema, error) => string;
|
@property() public computeError?: (schema: HaFormSchema, error) => string;
|
||||||
|
|
||||||
@property() public computeLabel?: (schema: HaFormSchema) => string;
|
@property() public computeLabel?: (
|
||||||
|
schema: HaFormSchema,
|
||||||
|
data?: HaFormDataContainer
|
||||||
|
) => string;
|
||||||
|
|
||||||
|
@property() public computeHelper?: (schema: HaFormSchema) => string;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
const root = this.shadowRoot?.querySelector(".root");
|
const root = this.shadowRoot?.querySelector(".root");
|
||||||
@ -42,7 +61,19 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
willUpdate(changedProperties: PropertyValues) {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
if (
|
||||||
|
!selectorImported &&
|
||||||
|
changedProperties.has("schema") &&
|
||||||
|
this.schema?.some((item) => "selector" in item)
|
||||||
|
) {
|
||||||
|
selectorImported = true;
|
||||||
|
import("../ha-selector/ha-selector");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="root">
|
<div class="root">
|
||||||
${this.error && this.error.base
|
${this.error && this.error.base
|
||||||
@ -54,6 +85,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.schema.map((item) => {
|
${this.schema.map((item) => {
|
||||||
const error = getValue(this.error, item);
|
const error = getValue(this.error, item);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${error
|
${error
|
||||||
? html`
|
? html`
|
||||||
@ -62,12 +94,26 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
</ha-alert>
|
</ha-alert>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${dynamicElement(`ha-form-${item.type}`, {
|
${"selector" in item
|
||||||
schema: item,
|
? html`<ha-selector
|
||||||
data: getValue(this.data, item),
|
.schema=${item}
|
||||||
label: this._computeLabel(item),
|
.hass=${this.hass}
|
||||||
disabled: this.disabled,
|
.selector=${item.selector}
|
||||||
})}
|
.value=${getValue(this.data, item)}
|
||||||
|
.label=${this._computeLabel(item, this.data)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.helper=${this._computeHelper(item)}
|
||||||
|
.required=${item.required || false}
|
||||||
|
></ha-selector>`
|
||||||
|
: dynamicElement(`ha-form-${item.type}`, {
|
||||||
|
schema: item,
|
||||||
|
data: getValue(this.data, item),
|
||||||
|
label: this._computeLabel(item, this.data),
|
||||||
|
disabled: this.disabled,
|
||||||
|
hass: this.hass,
|
||||||
|
computeLabel: this.computeLabel,
|
||||||
|
computeHelper: this.computeHelper,
|
||||||
|
})}
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@ -80,21 +126,30 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
root.addEventListener("value-changed", (ev) => {
|
root.addEventListener("value-changed", (ev) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
||||||
|
|
||||||
|
const newValue = !schema.name
|
||||||
|
? ev.detail.value
|
||||||
|
: { [schema.name]: ev.detail.value };
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: { ...this.data, [schema.name]: ev.detail.value },
|
value: { ...this.data, ...newValue },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeLabel(schema: HaFormSchema) {
|
private _computeLabel(schema: HaFormSchema, data: HaFormDataContainer) {
|
||||||
return this.computeLabel
|
return this.computeLabel
|
||||||
? this.computeLabel(schema)
|
? this.computeLabel(schema, data)
|
||||||
: schema
|
: schema
|
||||||
? schema.name
|
? schema.name
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _computeHelper(schema: HaFormSchema) {
|
||||||
|
return this.computeHelper ? this.computeHelper(schema) : "";
|
||||||
|
}
|
||||||
|
|
||||||
private _computeError(error, schema: HaFormSchema | HaFormSchema[]) {
|
private _computeError(error, schema: HaFormSchema | HaFormSchema[]) {
|
||||||
return this.computeError ? this.computeError(error, schema) : error;
|
return this.computeError ? this.computeError(error, schema) : error;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { LitElement } from "lit";
|
import type { LitElement } from "lit";
|
||||||
|
import { Selector } from "../../data/selector";
|
||||||
import type { HaDurationData } from "../ha-duration-input";
|
import type { HaDurationData } from "../ha-duration-input";
|
||||||
|
|
||||||
export type HaFormSchema =
|
export type HaFormSchema =
|
||||||
@ -9,14 +10,32 @@ export type HaFormSchema =
|
|||||||
| HaFormBooleanSchema
|
| HaFormBooleanSchema
|
||||||
| HaFormSelectSchema
|
| HaFormSelectSchema
|
||||||
| HaFormMultiSelectSchema
|
| HaFormMultiSelectSchema
|
||||||
| HaFormTimeSchema;
|
| HaFormTimeSchema
|
||||||
|
| HaFormSelector
|
||||||
|
| HaFormGridSchema;
|
||||||
|
|
||||||
export interface HaFormBaseSchema {
|
export interface HaFormBaseSchema {
|
||||||
name: string;
|
name: string;
|
||||||
|
// This value is applied if no data is submitted for this field
|
||||||
default?: HaFormData;
|
default?: HaFormData;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
optional?: boolean;
|
description?: {
|
||||||
description?: { suffix?: string; suggested_value?: HaFormData };
|
suffix?: string;
|
||||||
|
// This value will be set initially when form is loaded
|
||||||
|
suggested_value?: HaFormData;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HaFormGridSchema extends HaFormBaseSchema {
|
||||||
|
type: "grid";
|
||||||
|
name: "";
|
||||||
|
column_min_width?: string;
|
||||||
|
schema: HaFormSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HaFormSelector extends HaFormBaseSchema {
|
||||||
|
type?: never;
|
||||||
|
selector: Selector;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HaFormConstantSchema extends HaFormBaseSchema {
|
export interface HaFormConstantSchema extends HaFormBaseSchema {
|
||||||
@ -38,7 +57,7 @@ export interface HaFormSelectSchema extends HaFormBaseSchema {
|
|||||||
|
|
||||||
export interface HaFormMultiSelectSchema extends HaFormBaseSchema {
|
export interface HaFormMultiSelectSchema extends HaFormBaseSchema {
|
||||||
type: "multi_select";
|
type: "multi_select";
|
||||||
options: Record<string, string> | string[];
|
options: Record<string, string> | string[] | Array<[string, string]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HaFormFloatSchema extends HaFormBaseSchema {
|
export interface HaFormFloatSchema extends HaFormBaseSchema {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { Formfield } from "@material/mwc-formfield";
|
import { FormfieldBase } from "@material/mwc-formfield/mwc-formfield-base";
|
||||||
import { css, CSSResultGroup } from "lit";
|
import { styles } from "@material/mwc-formfield/mwc-formfield.css";
|
||||||
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
@customElement("ha-formfield")
|
@customElement("ha-formfield")
|
||||||
// @ts-expect-error
|
export class HaFormfield extends FormfieldBase {
|
||||||
export class HaFormfield extends Formfield {
|
|
||||||
protected _labelClick() {
|
protected _labelClick() {
|
||||||
const input = this.input;
|
const input = this.input;
|
||||||
if (input) {
|
if (input) {
|
||||||
@ -23,20 +23,18 @@ export class HaFormfield extends Formfield {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static get styles(): CSSResultGroup {
|
static override styles = [
|
||||||
return [
|
styles,
|
||||||
Formfield.styles,
|
css`
|
||||||
css`
|
:host(:not([alignEnd])) ::slotted(ha-switch) {
|
||||||
:host(:not([alignEnd])) ::slotted(ha-switch) {
|
margin-right: 10px;
|
||||||
margin-right: 10px;
|
}
|
||||||
}
|
:host([dir="rtl"]:not([alignEnd])) ::slotted(ha-switch) {
|
||||||
:host([dir="rtl"]:not([alignEnd])) ::slotted(ha-switch) {
|
margin-left: 10px;
|
||||||
margin-left: 10px;
|
margin-right: auto;
|
||||||
margin-right: auto;
|
}
|
||||||
}
|
`,
|
||||||
`,
|
];
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -43,6 +43,8 @@ class HaHLSPlayer extends LitElement {
|
|||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
@state() private _errorIsFatal = false;
|
||||||
|
|
||||||
private _hlsPolyfillInstance?: HlsLite;
|
private _hlsPolyfillInstance?: HlsLite;
|
||||||
|
|
||||||
private _exoPlayer = false;
|
private _exoPlayer = false;
|
||||||
@ -53,6 +55,7 @@ class HaHLSPlayer extends LitElement {
|
|||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
HaHLSPlayer.streamCount += 1;
|
HaHLSPlayer.streamCount += 1;
|
||||||
if (this.hasUpdated) {
|
if (this.hasUpdated) {
|
||||||
|
this._resetError();
|
||||||
this._startHls();
|
this._startHls();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,16 +67,23 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (this._error) {
|
|
||||||
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
|
||||||
}
|
|
||||||
return html`
|
return html`
|
||||||
<video
|
${this._error
|
||||||
?autoplay=${this.autoPlay}
|
? html`<ha-alert
|
||||||
.muted=${this.muted}
|
alert-type="error"
|
||||||
?playsinline=${this.playsInline}
|
class=${this._errorIsFatal ? "fatal" : "retry"}
|
||||||
?controls=${this.controls}
|
>
|
||||||
></video>
|
${this._error}
|
||||||
|
</ha-alert>`
|
||||||
|
: ""}
|
||||||
|
${!this._errorIsFatal
|
||||||
|
? html`<video
|
||||||
|
?autoplay=${this.autoPlay}
|
||||||
|
.muted=${this.muted}
|
||||||
|
?playsinline=${this.playsInline}
|
||||||
|
?controls=${this.controls}
|
||||||
|
></video>`
|
||||||
|
: ""}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,12 +97,11 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._cleanUp();
|
this._cleanUp();
|
||||||
|
this._resetError();
|
||||||
this._startHls();
|
this._startHls();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _startHls(): Promise<void> {
|
private async _startHls(): Promise<void> {
|
||||||
this._error = undefined;
|
|
||||||
|
|
||||||
const masterPlaylistPromise = fetch(this.url);
|
const masterPlaylistPromise = fetch(this.url);
|
||||||
|
|
||||||
const Hls: typeof HlsType = (await import("hls.js/dist/hls.light.min"))
|
const Hls: typeof HlsType = (await import("hls.js/dist/hls.light.min"))
|
||||||
@ -110,8 +119,8 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hlsSupported) {
|
if (!hlsSupported) {
|
||||||
this._error = this.hass.localize(
|
this._setFatalError(
|
||||||
"ui.components.media-browser.video_not_supported"
|
this.hass.localize("ui.components.media-browser.video_not_supported")
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -219,9 +228,16 @@ class HaHLSPlayer extends LitElement {
|
|||||||
this._hlsPolyfillInstance = hls;
|
this._hlsPolyfillInstance = hls;
|
||||||
hls.attachMedia(videoEl);
|
hls.attachMedia(videoEl);
|
||||||
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
||||||
|
this._resetError();
|
||||||
hls.loadSource(url);
|
hls.loadSource(url);
|
||||||
});
|
});
|
||||||
hls.on(Hls.Events.ERROR, (_, data: any) => {
|
hls.on(Hls.Events.FRAG_LOADED, (_event, _data: any) => {
|
||||||
|
this._resetError();
|
||||||
|
});
|
||||||
|
hls.on(Hls.Events.ERROR, (_event, data: any) => {
|
||||||
|
// Some errors are recovered automatically by the hls player itself, and the others handled
|
||||||
|
// in this function require special actions to recover. Errors retried in this function
|
||||||
|
// are done with backoff to not cause unecessary failures.
|
||||||
if (!data.fatal) {
|
if (!data.fatal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -241,22 +257,22 @@ class HaHLSPlayer extends LitElement {
|
|||||||
error += " (" + data.response.code + ")";
|
error += " (" + data.response.code + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._error = error;
|
this._setRetryableError(error);
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
case Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
|
case Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
|
||||||
this._error = "Timeout while starting stream";
|
this._setRetryableError("Timeout while starting stream");
|
||||||
return;
|
break;
|
||||||
default:
|
default:
|
||||||
this._error = "Unknown stream network error (" + data.details + ")";
|
this._setRetryableError("Stream network error");
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
this._error = "Error with media stream contents (" + data.details + ")";
|
hls.startLoad();
|
||||||
} else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
|
} else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
|
||||||
this._error = "Error with media stream contents (" + data.details + ")";
|
this._setRetryableError("Error with media stream contents");
|
||||||
|
hls.recoverMediaError();
|
||||||
} else {
|
} else {
|
||||||
this._error =
|
this._setFatalError("Error playing stream");
|
||||||
"Unknown error with stream (" + data.type + ", " + data.details + ")";
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -284,6 +300,21 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _resetError() {
|
||||||
|
this._error = undefined;
|
||||||
|
this._errorIsFatal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setFatalError(errorMessage: string) {
|
||||||
|
this._error = errorMessage;
|
||||||
|
this._errorIsFatal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setRetryableError(errorMessage: string) {
|
||||||
|
this._error = errorMessage;
|
||||||
|
this._errorIsFatal = false;
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host,
|
:host,
|
||||||
@ -296,10 +327,14 @@ class HaHLSPlayer extends LitElement {
|
|||||||
max-height: var(--video-max-height, calc(100vh - 97px));
|
max-height: var(--video-max-height, calc(100vh - 97px));
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-alert {
|
.fatal {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 100px 16px;
|
padding: 100px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.retry {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import { mdiCheck, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { customIcons } from "../data/custom_icons";
|
import { customIcons } from "../data/custom_icons";
|
||||||
import { PolymerChangedEvent } from "../polymer-types";
|
import { PolymerChangedEvent } from "../polymer-types";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import "./ha-combo-box";
|
||||||
|
import type { HaComboBox } from "./ha-combo-box";
|
||||||
import "./ha-icon";
|
import "./ha-icon";
|
||||||
import "./ha-icon-button";
|
|
||||||
|
|
||||||
type IconItem = {
|
type IconItem = {
|
||||||
icon: string;
|
icon: string;
|
||||||
@ -19,35 +16,17 @@ type IconItem = {
|
|||||||
let iconItems: IconItem[] = [];
|
let iconItems: IconItem[] = [];
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
// eslint-disable-next-line lit/prefer-static-styles
|
||||||
const rowRenderer: ComboBoxLitRenderer<IconItem> = (item) => html`<style>
|
const rowRenderer: ComboBoxLitRenderer<IconItem> = (item) => html`<mwc-list-item
|
||||||
paper-icon-item {
|
graphic="avatar"
|
||||||
padding: 0;
|
>
|
||||||
margin: -8px;
|
<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>
|
||||||
}
|
${item.icon}
|
||||||
#content {
|
</mwc-list-item>`;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-icon-item {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-icon-item>
|
|
||||||
<ha-icon .icon=${item.icon} slot="item-icon"></ha-icon>
|
|
||||||
<paper-item-body>${item.icon}</paper-item-body>
|
|
||||||
</paper-icon-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-icon-picker")
|
@customElement("ha-icon-picker")
|
||||||
export class HaIconPicker extends LitElement {
|
export class HaIconPicker extends LitElement {
|
||||||
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() public value?: string;
|
@property() public value?: string;
|
||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
@ -64,51 +43,40 @@ export class HaIconPicker extends LitElement {
|
|||||||
|
|
||||||
@state() private _opened = false;
|
@state() private _opened = false;
|
||||||
|
|
||||||
@query("vaadin-combo-box-light", true) private comboBox!: HTMLElement;
|
@query("ha-combo-box", true) private comboBox!: HaComboBox;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<vaadin-combo-box-light
|
<ha-combo-box
|
||||||
|
.hass=${this.hass}
|
||||||
item-value-path="icon"
|
item-value-path="icon"
|
||||||
item-label-path="icon"
|
item-label-path="icon"
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
allow-custom-value
|
allow-custom-value
|
||||||
.filteredItems=${iconItems}
|
.filteredItems=${iconItems}
|
||||||
${comboBoxRenderer(rowRenderer)}
|
.label=${this.label}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.placeholder=${this.placeholder}
|
||||||
|
.errorMessage=${this.errorMessage}
|
||||||
|
.invalid=${this.invalid}
|
||||||
|
.renderer=${rowRenderer}
|
||||||
|
icon
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@filter-changed=${this._filterChanged}
|
@filter-changed=${this._filterChanged}
|
||||||
>
|
>
|
||||||
<paper-input
|
${this._value || this.placeholder
|
||||||
.label=${this.label}
|
? html`
|
||||||
.placeholder=${this.placeholder}
|
<ha-icon .icon=${this._value || this.placeholder} slot="icon">
|
||||||
.disabled=${this.disabled}
|
</ha-icon>
|
||||||
class="input"
|
`
|
||||||
autocapitalize="none"
|
: this.fallbackPath
|
||||||
autocomplete="off"
|
? html`<ha-svg-icon
|
||||||
autocorrect="off"
|
.path=${this.fallbackPath}
|
||||||
spellcheck="false"
|
slot="icon"
|
||||||
.errorMessage=${this.errorMessage}
|
></ha-svg-icon>`
|
||||||
.invalid=${this.invalid}
|
: ""}
|
||||||
>
|
</ha-combo-box>
|
||||||
${this._value || this.placeholder
|
|
||||||
? html`
|
|
||||||
<ha-icon .icon=${this._value || this.placeholder} slot="prefix">
|
|
||||||
</ha-icon>
|
|
||||||
`
|
|
||||||
: this.fallbackPath
|
|
||||||
? html`<ha-svg-icon
|
|
||||||
.path=${this.fallbackPath}
|
|
||||||
slot="prefix"
|
|
||||||
></ha-svg-icon>`
|
|
||||||
: ""}
|
|
||||||
<ha-icon-button
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
slot="suffix"
|
|
||||||
class="toggle-button"
|
|
||||||
></ha-icon-button>
|
|
||||||
</paper-input>
|
|
||||||
</vaadin-combo-box-light>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +118,7 @@ export class HaIconPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||||
|
ev.stopPropagation();
|
||||||
this._setValue(ev.detail.value);
|
this._setValue(ev.detail.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +127,7 @@ export class HaIconPicker extends LitElement {
|
|||||||
fireEvent(
|
fireEvent(
|
||||||
this,
|
this,
|
||||||
"value-changed",
|
"value-changed",
|
||||||
{ value },
|
{ value: this._value },
|
||||||
{
|
{
|
||||||
bubbles: false,
|
bubbles: false,
|
||||||
composed: false,
|
composed: false,
|
||||||
@ -205,17 +174,13 @@ export class HaIconPicker extends LitElement {
|
|||||||
return css`
|
return css`
|
||||||
ha-icon,
|
ha-icon,
|
||||||
ha-svg-icon {
|
ha-svg-icon {
|
||||||
|
color: var(--primary-text-color);
|
||||||
position: relative;
|
position: relative;
|
||||||
bottom: 2px;
|
bottom: 2px;
|
||||||
}
|
}
|
||||||
*[slot="prefix"] {
|
*[slot="prefix"] {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
paper-input > ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|
||||||
import { PolymerElement } from "@polymer/polymer";
|
|
||||||
import { Constructor } from "../types";
|
|
||||||
|
|
||||||
const paperDropdownClass = customElements.get(
|
|
||||||
"paper-dropdown-menu"
|
|
||||||
) as Constructor<PolymerElement>;
|
|
||||||
|
|
||||||
// patches paper drop down to properly support RTL - https://github.com/PolymerElements/paper-dropdown-menu/issues/183
|
|
||||||
export class HaPaperDropdownClass extends paperDropdownClass {
|
|
||||||
public ready() {
|
|
||||||
super.ready();
|
|
||||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
|
||||||
setTimeout(() => {
|
|
||||||
if (window.getComputedStyle(this).direction === "rtl") {
|
|
||||||
this.style.textAlign = "right";
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-paper-dropdown-menu": HaPaperDropdownClass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-paper-dropdown-menu", HaPaperDropdownClass);
|
|
@ -1,5 +1,4 @@
|
|||||||
import { mdiImagePlus } from "@mdi/js";
|
import { mdiImagePlus } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input-container";
|
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-select/mwc-select";
|
|
||||||
import "@material/mwc-textfield/mwc-textfield";
|
|
||||||
import type { TextField } from "@material/mwc-textfield/mwc-textfield";
|
|
||||||
import { mdiCamera } from "@mdi/js";
|
import { mdiCamera } from "@mdi/js";
|
||||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
@ -11,7 +9,8 @@ import { stopPropagation } from "../common/dom/stop_propagation";
|
|||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import "./ha-alert";
|
import "./ha-alert";
|
||||||
import "./ha-button-menu";
|
import "./ha-button-menu";
|
||||||
import "@material/mwc-button/mwc-button";
|
import "./ha-textfield";
|
||||||
|
import type { HaTextField } from "./ha-textfield";
|
||||||
|
|
||||||
@customElement("ha-qr-scanner")
|
@customElement("ha-qr-scanner")
|
||||||
class HaQrScanner extends LitElement {
|
class HaQrScanner extends LitElement {
|
||||||
@ -29,7 +28,7 @@ class HaQrScanner extends LitElement {
|
|||||||
|
|
||||||
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
|
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
|
||||||
|
|
||||||
@query("mwc-textfield") private _manualInput?: TextField;
|
@query("ha-textfield") private _manualInput?: HaTextField;
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
public disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
@ -102,11 +101,11 @@ class HaQrScanner extends LitElement {
|
|||||||
</ha-alert>
|
</ha-alert>
|
||||||
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
|
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<mwc-textfield
|
<ha-textfield
|
||||||
.label=${this.localize("ui.components.qr-scanner.enter_qr_code")}
|
.label=${this.localize("ui.components.qr-scanner.enter_qr_code")}
|
||||||
@keyup=${this._manualKeyup}
|
@keyup=${this._manualKeyup}
|
||||||
@paste=${this._manualPaste}
|
@paste=${this._manualPaste}
|
||||||
></mwc-textfield>
|
></ha-textfield>
|
||||||
<mwc-button @click=${this._manualSubmit}
|
<mwc-button @click=${this._manualSubmit}
|
||||||
>${this.localize("ui.common.submit")}</mwc-button
|
>${this.localize("ui.common.submit")}</mwc-button
|
||||||
>
|
>
|
||||||
@ -161,7 +160,7 @@ class HaQrScanner extends LitElement {
|
|||||||
|
|
||||||
private _manualKeyup(ev: KeyboardEvent) {
|
private _manualKeyup(ev: KeyboardEvent) {
|
||||||
if (ev.key === "Enter") {
|
if (ev.key === "Enter") {
|
||||||
this._qrCodeScanned((ev.target as TextField).value);
|
this._qrCodeScanned((ev.target as HaTextField).value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,7 +198,7 @@ class HaQrScanner extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
mwc-textfield {
|
ha-textfield {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
import { Radio } from "@material/mwc-radio";
|
import { RadioBase } from "@material/mwc-radio/mwc-radio-base";
|
||||||
|
import { styles } from "@material/mwc-radio/mwc-radio.css";
|
||||||
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
@customElement("ha-radio")
|
@customElement("ha-radio")
|
||||||
export class HaRadio extends Radio {
|
export class HaRadio extends RadioBase {
|
||||||
public firstUpdated() {
|
static override styles = [
|
||||||
super.firstUpdated();
|
styles,
|
||||||
this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
|
css`
|
||||||
}
|
:host {
|
||||||
|
--mdc-theme-secondary: var(--primary-color);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
47
src/components/ha-select.ts
Normal file
47
src/components/ha-select.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { SelectBase } from "@material/mwc-select/mwc-select-base";
|
||||||
|
import { styles } from "@material/mwc-select/mwc-select.css";
|
||||||
|
import { html, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { debounce } from "../common/util/debounce";
|
||||||
|
import { nextRender } from "../common/util/render-status";
|
||||||
|
|
||||||
|
@customElement("ha-select")
|
||||||
|
export class HaSelect extends SelectBase {
|
||||||
|
// @ts-ignore
|
||||||
|
@property({ type: Boolean }) public icon?: boolean;
|
||||||
|
|
||||||
|
protected override renderLeadingIcon() {
|
||||||
|
if (!this.icon) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`<span class="mdc-select__icon"
|
||||||
|
><slot name="icon"></slot
|
||||||
|
></span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static override styles = [styles];
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
window.addEventListener("translations-updated", this._translationsUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
window.removeEventListener(
|
||||||
|
"translations-updated",
|
||||||
|
this._translationsUpdated
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _translationsUpdated = debounce(async () => {
|
||||||
|
await nextRender();
|
||||||
|
this.layoutOptions();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-select": HaSelect;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { AddonSelector } from "../../data/selector";
|
import { AddonSelector } from "../../data/selector";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
@ -22,6 +22,12 @@ export class HaAddonSelector extends LitElement {
|
|||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
></ha-addon-picker>`;
|
></ha-addon-picker>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
ha-addon-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
38
src/components/ha-selector/ha-selector-attribute.ts
Normal file
38
src/components/ha-selector/ha-selector-attribute.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import "../entity/ha-entity-attribute-picker";
|
||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { AttributeSelector } from "../../data/selector";
|
||||||
|
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
|
@customElement("ha-selector-attribute")
|
||||||
|
export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public selector!: AttributeSelector;
|
||||||
|
|
||||||
|
@property() public value?: any;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-entity-attribute-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.entityId=${this.selector.attribute.entity_id}
|
||||||
|
.value=${this.value}
|
||||||
|
.label=${this.label}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
allow-custom-value
|
||||||
|
></ha-entity-attribute-picker>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-attribute": HaSelectorAttribute;
|
||||||
|
}
|
||||||
|
}
|
@ -35,9 +35,12 @@ export class HaBooleanSelector extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
|
:host {
|
||||||
|
height: 56px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
ha-formfield {
|
ha-formfield {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 16px 0;
|
|
||||||
--mdc-typography-body2-font-size: 1em;
|
--mdc-typography-body2-font-size: 1em;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
37
src/components/ha-selector/ha-selector-duration.ts
Normal file
37
src/components/ha-selector/ha-selector-duration.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import "../ha-duration-input";
|
||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { DurationSelector } from "../../data/selector";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
|
@customElement("ha-selector-duration")
|
||||||
|
export class HaTimeDuration extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public selector!: DurationSelector;
|
||||||
|
|
||||||
|
@property() public value?: string;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = true;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-duration-input
|
||||||
|
.label=${this.label}
|
||||||
|
.data=${this.value}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
></ha-duration-input>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-duration": HaTimeDuration;
|
||||||
|
}
|
||||||
|
}
|
@ -50,7 +50,12 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _filterEntities = (entity: HassEntity): boolean => {
|
private _filterEntities = (entity: HassEntity): boolean => {
|
||||||
if (this.selector.entity?.domain) {
|
if (this.selector.entity?.domain) {
|
||||||
if (computeStateDomain(entity) !== this.selector.entity.domain) {
|
const filterDomain = this.selector.entity.domain;
|
||||||
|
const entityDomain = computeStateDomain(entity);
|
||||||
|
if (
|
||||||
|
(Array.isArray(filterDomain) && !filterDomain.includes(entityDomain)) ||
|
||||||
|
entityDomain !== filterDomain
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
src/components/ha-selector/ha-selector-icon.ts
Normal file
41
src/components/ha-selector/ha-selector-icon.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import "../ha-icon-picker";
|
||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { IconSelector } from "../../data/selector";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
|
||||||
|
@customElement("ha-selector-icon")
|
||||||
|
export class HaIconSelector extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public selector!: IconSelector;
|
||||||
|
|
||||||
|
@property() public value?: string;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-icon-picker
|
||||||
|
.label=${this.label}
|
||||||
|
.value=${this.value}
|
||||||
|
.fallbackPath=${this.selector.icon.fallbackPath}
|
||||||
|
.placeholder=${this.selector.icon.placeholder}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-icon-picker>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent) {
|
||||||
|
fireEvent(this, "value-changed", { value: ev.detail.value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-icon": HaIconSelector;
|
||||||
|
}
|
||||||
|
}
|
264
src/components/ha-selector/ha-selector-media.ts
Normal file
264
src/components/ha-selector/ha-selector-media.ts
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
import { mdiPlayBox, mdiPlus } from "@mdi/js";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||||
|
import { getSignedPath } from "../../data/auth";
|
||||||
|
import {
|
||||||
|
MediaClassBrowserSettings,
|
||||||
|
MediaPickedEvent,
|
||||||
|
SUPPORT_BROWSE_MEDIA,
|
||||||
|
} from "../../data/media-player";
|
||||||
|
import type { MediaSelector, MediaSelectorValue } from "../../data/selector";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-alert";
|
||||||
|
import "../ha-form/ha-form";
|
||||||
|
import type { HaFormSchema } from "../ha-form/types";
|
||||||
|
import { showMediaBrowserDialog } from "../media-player/show-media-browser-dialog";
|
||||||
|
|
||||||
|
const MANUAL_SCHEMA = [
|
||||||
|
{ name: "media_content_id", required: false, selector: { text: {} } },
|
||||||
|
{ name: "media_content_type", required: false, selector: { text: {} } },
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("ha-selector-media")
|
||||||
|
export class HaMediaSelector extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public selector!: MediaSelector;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public value?: MediaSelectorValue;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
|
@state() private _thumbnailUrl?: string | null;
|
||||||
|
|
||||||
|
willUpdate(changedProps: PropertyValues<this>) {
|
||||||
|
if (changedProps.has("value")) {
|
||||||
|
const thumbnail = this.value?.metadata?.thumbnail;
|
||||||
|
const oldThumbnail = (changedProps.get("value") as this["value"])
|
||||||
|
?.metadata?.thumbnail;
|
||||||
|
if (thumbnail === oldThumbnail) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (thumbnail && thumbnail.startsWith("/")) {
|
||||||
|
this._thumbnailUrl = undefined;
|
||||||
|
// Thumbnails served by local API require authentication
|
||||||
|
getSignedPath(this.hass, thumbnail).then((signedPath) => {
|
||||||
|
this._thumbnailUrl = signedPath.path;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._thumbnailUrl = thumbnail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const stateObj = this.value?.entity_id
|
||||||
|
? this.hass.states[this.value.entity_id]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const supportsBrowse =
|
||||||
|
!this.value?.entity_id ||
|
||||||
|
(stateObj && supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA));
|
||||||
|
|
||||||
|
return html`<ha-entity-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.value?.entity_id}
|
||||||
|
.label=${this.label ||
|
||||||
|
this.hass.localize("ui.components.selectors.media.pick_media_player")}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
include-domains='["media_player"]'
|
||||||
|
allow-custom-entity
|
||||||
|
@value-changed=${this._entityChanged}
|
||||||
|
></ha-entity-picker>
|
||||||
|
${!supportsBrowse
|
||||||
|
? html`<ha-alert>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.selectors.media.browse_not_supported"
|
||||||
|
)}
|
||||||
|
</ha-alert>
|
||||||
|
<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this.value}
|
||||||
|
.schema=${MANUAL_SCHEMA}
|
||||||
|
.computeLabel=${this._computeLabelCallback}
|
||||||
|
></ha-form>`
|
||||||
|
: html`<ha-card
|
||||||
|
outlined
|
||||||
|
@click=${this._pickMedia}
|
||||||
|
class=${this.disabled || !this.value?.entity_id ? "disabled" : ""}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="thumbnail ${classMap({
|
||||||
|
portrait:
|
||||||
|
!!this.value?.metadata?.media_class &&
|
||||||
|
MediaClassBrowserSettings[
|
||||||
|
this.value.metadata.children_media_class ||
|
||||||
|
this.value.metadata.media_class
|
||||||
|
].thumbnail_ratio === "portrait",
|
||||||
|
})}"
|
||||||
|
>
|
||||||
|
${this.value?.metadata?.thumbnail
|
||||||
|
? html`
|
||||||
|
<div
|
||||||
|
class="${classMap({
|
||||||
|
"centered-image":
|
||||||
|
!!this.value.metadata.media_class &&
|
||||||
|
["app", "directory"].includes(
|
||||||
|
this.value.metadata.media_class
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
image"
|
||||||
|
style=${this._thumbnailUrl
|
||||||
|
? `background-image: url(${this._thumbnailUrl});`
|
||||||
|
: ""}
|
||||||
|
></div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<div class="icon-holder image">
|
||||||
|
<ha-svg-icon
|
||||||
|
class="folder"
|
||||||
|
.path=${!this.value?.media_content_id
|
||||||
|
? mdiPlus
|
||||||
|
: this.value?.metadata?.media_class
|
||||||
|
? MediaClassBrowserSettings[
|
||||||
|
this.value.metadata.media_class === "directory"
|
||||||
|
? this.value.metadata.children_media_class ||
|
||||||
|
this.value.metadata.media_class
|
||||||
|
: this.value.metadata.media_class
|
||||||
|
].icon
|
||||||
|
: mdiPlayBox}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
<div class="title">
|
||||||
|
${!this.value?.media_content_id
|
||||||
|
? this.hass.localize("ui.components.selectors.media.pick_media")
|
||||||
|
: this.value.metadata?.title || this.value.media_content_id}
|
||||||
|
</div>
|
||||||
|
</ha-card>`}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeLabelCallback = (schema: HaFormSchema): string =>
|
||||||
|
this.hass.localize(`ui.components.selectors.media.${schema.name}`);
|
||||||
|
|
||||||
|
private _entityChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
entity_id: ev.detail.value,
|
||||||
|
media_content_id: "",
|
||||||
|
media_content_type: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _pickMedia() {
|
||||||
|
showMediaBrowserDialog(this, {
|
||||||
|
action: "pick",
|
||||||
|
entityId: this.value!.entity_id!,
|
||||||
|
navigateIds: this.value!.metadata?.navigateIds,
|
||||||
|
mediaPickedCallback: (pickedMedia: MediaPickedEvent) => {
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: {
|
||||||
|
...this.value,
|
||||||
|
media_content_id: pickedMedia.item.media_content_id,
|
||||||
|
media_content_type: pickedMedia.item.media_content_type,
|
||||||
|
metadata: {
|
||||||
|
title: pickedMedia.item.title,
|
||||||
|
thumbnail: pickedMedia.item.thumbnail,
|
||||||
|
media_class: pickedMedia.item.media_class,
|
||||||
|
children_media_class: pickedMedia.item.children_media_class,
|
||||||
|
navigateIds: pickedMedia.navigateIds?.map((id) => ({
|
||||||
|
media_content_type: id.media_content_type,
|
||||||
|
media_content_id: id.media_content_id,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
ha-entity-picker {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
mwc-button {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
ha-alert {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
position: relative;
|
||||||
|
width: 200px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
ha-card.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
color: var(--disabled-text-color);
|
||||||
|
}
|
||||||
|
ha-card .thumbnail {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: padding-bottom 0.1s ease-out;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
}
|
||||||
|
ha-card .thumbnail.portrait {
|
||||||
|
padding-bottom: 150%;
|
||||||
|
}
|
||||||
|
ha-card .image {
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
}
|
||||||
|
.folder {
|
||||||
|
--mdc-icon-size: calc(var(--media-browse-item-size, 175px) * 0.4);
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
.centered-image {
|
||||||
|
margin: 0 8px;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
.icon-holder {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-media": HaMediaSelector;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
@ -6,6 +5,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { NumberSelector } from "../../data/selector";
|
import { NumberSelector } from "../../data/selector";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-slider";
|
import "../ha-slider";
|
||||||
|
import "../ha-textfield";
|
||||||
|
|
||||||
@customElement("ha-selector-number")
|
@customElement("ha-selector-number")
|
||||||
export class HaNumberSelector extends LitElement {
|
export class HaNumberSelector extends LitElement {
|
||||||
@ -19,56 +19,58 @@ export class HaNumberSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = true;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`${this.label}
|
return html`${this.selector.number.mode !== "box"
|
||||||
${this.selector.number.mode !== "box"
|
? html`${this.label}<ha-slider
|
||||||
? html`<ha-slider
|
.min=${this.selector.number.min}
|
||||||
.min=${this.selector.number.min}
|
.max=${this.selector.number.max}
|
||||||
.max=${this.selector.number.max}
|
.value=${this._value}
|
||||||
.value=${this._value}
|
.step=${this.selector.number.step ?? 1}
|
||||||
.step=${this.selector.number.step ?? 1}
|
.disabled=${this.disabled}
|
||||||
.disabled=${this.disabled}
|
.required=${this.required}
|
||||||
pin
|
pin
|
||||||
ignore-bar-touch
|
ignore-bar-touch
|
||||||
@change=${this._handleSliderChange}
|
@change=${this._handleSliderChange}
|
||||||
>
|
>
|
||||||
</ha-slider>`
|
</ha-slider>`
|
||||||
: ""}
|
: ""}
|
||||||
<paper-input
|
<ha-textfield
|
||||||
|
inputMode="numeric"
|
||||||
pattern="[0-9]+([\\.][0-9]+)?"
|
pattern="[0-9]+([\\.][0-9]+)?"
|
||||||
.label=${this.selector.number.mode !== "box" ? undefined : this.label}
|
.label=${this.selector.number.mode !== "box" ? undefined : this.label}
|
||||||
.placeholder=${this.placeholder}
|
.placeholder=${this.placeholder}
|
||||||
.noLabelFloat=${this.selector.number.mode !== "box"}
|
|
||||||
class=${classMap({ single: this.selector.number.mode === "box" })}
|
class=${classMap({ single: this.selector.number.mode === "box" })}
|
||||||
.min=${this.selector.number.min}
|
.min=${this.selector.number.min}
|
||||||
.max=${this.selector.number.max}
|
.max=${this.selector.number.max}
|
||||||
.value=${this.value}
|
.value=${this.value || ""}
|
||||||
.step=${this.selector.number.step ?? 1}
|
.step=${this.selector.number.step ?? 1}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
.suffix=${this.selector.number.unit_of_measurement}
|
||||||
type="number"
|
type="number"
|
||||||
auto-validate
|
autoValidate
|
||||||
@value-changed=${this._handleInputChange}
|
?no-spinner=${this.selector.number.mode !== "box"}
|
||||||
|
@input=${this._handleInputChange}
|
||||||
>
|
>
|
||||||
${this.selector.number.unit_of_measurement
|
</ha-textfield>`;
|
||||||
? html`<div slot="suffix">
|
|
||||||
${this.selector.number.unit_of_measurement}
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
</paper-input>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
return this.value || 0;
|
return this.value ?? (this.selector.number.min || 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleInputChange(ev) {
|
private _handleInputChange(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const value =
|
const value =
|
||||||
ev.detail.value === "" || isNaN(ev.detail.value)
|
ev.target.value === "" || isNaN(ev.target.value)
|
||||||
? undefined
|
? this.required
|
||||||
: Number(ev.detail.value);
|
? this.selector.number.min || 0
|
||||||
|
: undefined
|
||||||
|
: Number(ev.target.value);
|
||||||
if (this.value === value) {
|
if (this.value === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -94,7 +96,11 @@ export class HaNumberSelector extends LitElement {
|
|||||||
ha-slider {
|
ha-slider {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
ha-textfield {
|
||||||
|
--ha-textfield-input-width: 40px;
|
||||||
|
}
|
||||||
.single {
|
.single {
|
||||||
|
--ha-textfield-input-width: unset;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -18,6 +18,7 @@ export class HaObjectSelector extends LitElement {
|
|||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`<ha-yaml-editor
|
return html`<ha-yaml-editor
|
||||||
|
.hass=${this.hass}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.placeholder=${this.placeholder}
|
.placeholder=${this.placeholder}
|
||||||
.defaultValue=${this.value}
|
.defaultValue=${this.value}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { SelectSelector } from "../../data/selector";
|
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||||
|
import { SelectOption, SelectSelector } from "../../data/selector";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-paper-dropdown-menu";
|
import "../ha-select";
|
||||||
|
|
||||||
@customElement("ha-selector-select")
|
@customElement("ha-selector-select")
|
||||||
export class HaSelectSelector extends LitElement {
|
export class HaSelectSelector extends LitElement {
|
||||||
@ -15,49 +17,44 @@ export class HaSelectSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`<ha-paper-dropdown-menu
|
return html`<ha-select
|
||||||
.disabled=${this.disabled}
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
|
.value=${this.value}
|
||||||
|
.helper=${this.helper}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
@selected=${this._valueChanged}
|
||||||
>
|
>
|
||||||
<paper-listbox
|
${this.selector.select.options.map((item: string | SelectOption) => {
|
||||||
slot="dropdown-content"
|
const value = typeof item === "object" ? item.value : item;
|
||||||
attr-for-selected="item-value"
|
const label = typeof item === "object" ? item.label : item;
|
||||||
.selected=${this.value}
|
|
||||||
@selected-item-changed=${this._valueChanged}
|
return html`<mwc-list-item .value=${value}>${label}</mwc-list-item>`;
|
||||||
>
|
})}
|
||||||
${this.selector.select.options.map(
|
</ha-select>`;
|
||||||
(item: string) => html`
|
|
||||||
<paper-item .itemValue=${item}> ${item} </paper-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</paper-listbox>
|
|
||||||
</ha-paper-dropdown-menu>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev) {
|
private _valueChanged(ev) {
|
||||||
if (this.disabled || !ev.detail.value) {
|
ev.stopPropagation();
|
||||||
|
if (this.disabled || !ev.target.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: ev.detail.value.itemValue,
|
value: ev.target.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-paper-dropdown-menu {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 200px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
paper-listbox {
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
import "@material/mwc-list/mwc-list";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import "@material/mwc-tab-bar/mwc-tab-bar";
|
|
||||||
import "@material/mwc-tab/mwc-tab";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
import {
|
||||||
HassEntity,
|
HassEntity,
|
||||||
HassServiceTarget,
|
HassServiceTarget,
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import { mdiEye, mdiEyeOff } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-textarea";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { html, LitElement } from "lit";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { StringSelector } from "../../data/selector";
|
import { StringSelector } from "../../data/selector";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-icon-button";
|
||||||
|
import "../ha-textarea";
|
||||||
|
import "../ha-textfield";
|
||||||
|
|
||||||
@customElement("ha-selector-text")
|
@customElement("ha-selector-text")
|
||||||
export class HaTextSelector extends LitElement {
|
export class HaTextSelector extends LitElement {
|
||||||
@ -20,36 +22,84 @@ export class HaTextSelector extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = true;
|
||||||
|
|
||||||
|
@state() private _unmaskedPassword = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (this.selector.text?.multiline) {
|
if (this.selector.text?.multiline) {
|
||||||
return html`<paper-textarea
|
return html`<ha-textarea
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.placeholder=${this.placeholder}
|
.placeholder=${this.placeholder}
|
||||||
.value=${this.value}
|
.value=${this.value || ""}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@value-changed=${this._handleChange}
|
@input=${this._handleChange}
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
></paper-textarea>`;
|
.required=${this.required}
|
||||||
|
autogrow
|
||||||
|
></ha-textarea>`;
|
||||||
}
|
}
|
||||||
return html`<paper-input
|
return html`<ha-textfield
|
||||||
required
|
.value=${this.value || ""}
|
||||||
.value=${this.value}
|
.placeholder=${this.placeholder || ""}
|
||||||
.placeholder=${this.placeholder}
|
.disabled=${this.disabled}
|
||||||
.disabled=${this.disabled}
|
.type=${this._unmaskedPassword ? "text" : this.selector.text?.type}
|
||||||
@value-changed=${this._handleChange}
|
@input=${this._handleChange}
|
||||||
.label=${this.label}
|
.label=${this.label || ""}
|
||||||
></paper-input>`;
|
.suffix=${this.selector.text?.type === "password"
|
||||||
|
? // reserve some space for the icon.
|
||||||
|
html`<div style="width: 24px"></div>`
|
||||||
|
: this.selector.text?.suffix}
|
||||||
|
.required=${this.required}
|
||||||
|
></ha-textfield>
|
||||||
|
${this.selector.text?.type === "password"
|
||||||
|
? html`<ha-icon-button
|
||||||
|
toggles
|
||||||
|
.label=${`${this._unmaskedPassword ? "Hide" : "Show"} password`}
|
||||||
|
@click=${this._toggleUnmaskedPassword}
|
||||||
|
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
||||||
|
></ha-icon-button>`
|
||||||
|
: ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toggleUnmaskedPassword(): void {
|
||||||
|
this._unmaskedPassword = !this._unmaskedPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleChange(ev) {
|
private _handleChange(ev) {
|
||||||
const value = ev.target.value;
|
let value = ev.target.value;
|
||||||
if (this.value === value) {
|
if (this.value === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (value === "" && !this.required) {
|
||||||
|
value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
fireEvent(this, "value-changed", { value });
|
fireEvent(this, "value-changed", { value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
ha-textarea,
|
||||||
|
ha-textfield {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
ha-icon-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
--mdc-icon-button-size: 24px;
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user