Compare commits

..
Author SHA1 Message Date
dependabot[bot] 109cc7fab0 Bump vite from 8.3.0 to 8.3.4
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.3.0 to 8.3.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.3.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.3.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-20 22:18:55 +00:00
GeorgeSG 44cb92bba4 Merge pull request #68 from AcMetelka/master
Feature to accept Time HA entity.
2026-10-05 09:01:19 +03:00
Jindrich.Stepan 326c195dfd Use generic alarm entity and consistent time domain labels 2026-10-05 00:52:32 +02:00
Jindrich.Stepan 81ca4cfb53 Feature: accepts time entity, not only input_datetime. 2026-10-05 00:31:27 +02:00
6 changed files with 202 additions and 113 deletions
+27 -17
View File
@@ -11,7 +11,7 @@
This is a Time Picker Card for [Home Assistant](https://www.home-assistant.io/)'s [Lovelace UI](https://www.home-assistant.io/lovelace).
Requires an [Input Datetime](https://www.home-assistant.io/integrations/input_datetime/) that has time (`has_time: true`).
Supports a [Time](https://www.home-assistant.io/integrations/time/) entity or an [Input Datetime](https://www.home-assistant.io/integrations/input_datetime/) entity that has time (`has_time: true`).
## Installation
@@ -66,6 +66,16 @@ hide:
name: true
```
### `time` entity
```yaml
type: custom:time-picker-card
entity: time.alarm_time
minute_step: 5
```
The card reads the time entity's `HH:MM:SS` state and updates it using `time.set_value`.
### Custom config - hidden card name, 12 hour mode with a "single" hour mode picker
![Default theme with single hour mode](https://raw.githubusercontent.com/GeorgeSG/lovelace-time-picker-card/master/examples/single_hour_mode.png)
@@ -107,22 +117,22 @@ layout:
## Options
| Name | Type | Requirement | Description | Default |
| ----------------- | ------------- | ------------ | --------------------------------------------------------------------------------------------------------- | ------------- |
| type | string | **Required** | `custom:time-picker-card` | |
| entity | string | **Required** | [Input Datetime](https://www.home-assistant.io/integrations/input_datetime/) entity with `has_time: true` | |
| name | string / list | **Optional** | Card name. Accepts a [structured name](#structured-names) on Home Assistant 2026.4 and later. | Entity's name |
| link_values | boolean | **Optional** | If enabled, will change hour when minutes overflow. E.g. will go from 11:55 to 12:00, instead of 11:00 | `false` |
| hour_mode | `12` or `24` | **Optional** | Hour format. If `12`, card will show AM/PM picker | `24` |
| hour_step | number | **Optional** | Hour change when clicking arrows | `1` |
| minute_step | number | **Optional** | Minute change when clicking arrows | `5` |
| second_step | number | **Optional** | Second change when clicking arrows (only with `hide.seconds: false`) | `5` |
| delay | number | **Optional** | Delay in ms before updating entity | `0` |
| layout | object | **Optional** | Card Layout configuration | `none` |
| hide | object | **Optional** | Hide object | `none` |
| tap_action | action | **Optional** | Home assistant action to perform on tap | `more-info` |
| double_tap_action | action | **Optional** | Home assistant action to perform on tap | `more-info` |
| hold_action | action | **Optional** | Home assistant action to perform on tap | `more-info` |
| Name | Type | Requirement | Description | Default |
| ----------------- | ------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| type | string | **Required** | `custom:time-picker-card` | |
| entity | string | **Required** | [Time](https://www.home-assistant.io/integrations/time/) entity or [Input Datetime](https://www.home-assistant.io/integrations/input_datetime/) entity with `has_time: true` | |
| name | string / list | **Optional** | Card name. Accepts a [structured name](#structured-names) on Home Assistant 2026.4 and later. | Entity's name |
| link_values | boolean | **Optional** | If enabled, will change hour when minutes overflow. E.g. will go from 11:55 to 12:00, instead of 11:00 | `false` |
| hour_mode | `12` or `24` | **Optional** | Hour format. If `12`, card will show AM/PM picker | `24` |
| hour_step | number | **Optional** | Hour change when clicking arrows | `1` |
| minute_step | number | **Optional** | Minute change when clicking arrows | `5` |
| second_step | number | **Optional** | Second change when clicking arrows (only with `hide.seconds: false`) | `5` |
| delay | number | **Optional** | Delay in ms before updating entity | `0` |
| layout | object | **Optional** | Card Layout configuration | `none` |
| hide | object | **Optional** | Hide object | `none` |
| tap_action | action | **Optional** | Home assistant action to perform on tap | `more-info` |
| double_tap_action | action | **Optional** | Home assistant action to perform on tap | `more-info` |
| hold_action | action | **Optional** | Home assistant action to perform on tap | `more-info` |
### Layout Object
+83 -83
View File
@@ -407,9 +407,9 @@
}
},
"node_modules/@oxc-project/types": {
"version": "0.149.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.149.0.tgz",
"integrity": "sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==",
"version": "0.154.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz",
"integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -418,9 +418,9 @@
}
},
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.8.tgz",
"integrity": "sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz",
"integrity": "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==",
"cpu": [
"arm"
],
@@ -436,9 +436,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.8.tgz",
"integrity": "sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.14.tgz",
"integrity": "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==",
"cpu": [
"arm64"
],
@@ -454,9 +454,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.8.tgz",
"integrity": "sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.14.tgz",
"integrity": "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==",
"cpu": [
"arm64"
],
@@ -472,9 +472,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.8.tgz",
"integrity": "sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.14.tgz",
"integrity": "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==",
"cpu": [
"x64"
],
@@ -490,9 +490,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.8.tgz",
"integrity": "sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.14.tgz",
"integrity": "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==",
"cpu": [
"x64"
],
@@ -508,9 +508,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.8.tgz",
"integrity": "sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.14.tgz",
"integrity": "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==",
"cpu": [
"arm"
],
@@ -526,9 +526,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.8.tgz",
"integrity": "sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.14.tgz",
"integrity": "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==",
"cpu": [
"arm64"
],
@@ -547,9 +547,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.8.tgz",
"integrity": "sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.14.tgz",
"integrity": "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==",
"cpu": [
"arm64"
],
@@ -568,9 +568,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.8.tgz",
"integrity": "sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.14.tgz",
"integrity": "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==",
"cpu": [
"ppc64"
],
@@ -589,9 +589,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.8.tgz",
"integrity": "sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.14.tgz",
"integrity": "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==",
"cpu": [
"s390x"
],
@@ -610,9 +610,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.8.tgz",
"integrity": "sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.14.tgz",
"integrity": "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==",
"cpu": [
"x64"
],
@@ -631,9 +631,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.8.tgz",
"integrity": "sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.14.tgz",
"integrity": "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==",
"cpu": [
"x64"
],
@@ -652,9 +652,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.8.tgz",
"integrity": "sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.14.tgz",
"integrity": "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==",
"cpu": [
"arm64"
],
@@ -670,9 +670,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.8.tgz",
"integrity": "sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.14.tgz",
"integrity": "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==",
"cpu": [
"arm64"
],
@@ -688,9 +688,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.8.tgz",
"integrity": "sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz",
"integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==",
"cpu": [
"x64"
],
@@ -2606,9 +2606,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.19",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
"integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
"version": "3.3.20",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
"integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
"dev": true,
"funding": [
{
@@ -2793,9 +2793,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.28",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
"integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"version": "8.5.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz",
"integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==",
"dev": true,
"funding": [
{
@@ -2814,9 +2814,9 @@
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.18",
"nanoid": "^3.3.20",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
"source-map-js": "^1.2.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -2896,14 +2896,14 @@
}
},
"node_modules/rolldown": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.8.tgz",
"integrity": "sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz",
"integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@oxc-project/types": "=0.149.0",
"@oxc-project/types": "=0.154.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
@@ -2913,21 +2913,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm-eabi": "1.2.8",
"@rolldown/binding-android-arm64": "1.2.8",
"@rolldown/binding-darwin-arm64": "1.2.8",
"@rolldown/binding-darwin-x64": "1.2.8",
"@rolldown/binding-freebsd-x64": "1.2.8",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.8",
"@rolldown/binding-linux-arm64-gnu": "1.2.8",
"@rolldown/binding-linux-arm64-musl": "1.2.8",
"@rolldown/binding-linux-ppc64-gnu": "1.2.8",
"@rolldown/binding-linux-s390x-gnu": "1.2.8",
"@rolldown/binding-linux-x64-gnu": "1.2.8",
"@rolldown/binding-linux-x64-musl": "1.2.8",
"@rolldown/binding-openharmony-arm64": "1.2.8",
"@rolldown/binding-win32-arm64-msvc": "1.2.8",
"@rolldown/binding-win32-x64-msvc": "1.2.8"
"@rolldown/binding-android-arm-eabi": "1.2.14",
"@rolldown/binding-android-arm64": "1.2.14",
"@rolldown/binding-darwin-arm64": "1.2.14",
"@rolldown/binding-darwin-x64": "1.2.14",
"@rolldown/binding-freebsd-x64": "1.2.14",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.14",
"@rolldown/binding-linux-arm64-gnu": "1.2.14",
"@rolldown/binding-linux-arm64-musl": "1.2.14",
"@rolldown/binding-linux-ppc64-gnu": "1.2.14",
"@rolldown/binding-linux-s390x-gnu": "1.2.14",
"@rolldown/binding-linux-x64-gnu": "1.2.14",
"@rolldown/binding-linux-x64-musl": "1.2.14",
"@rolldown/binding-openharmony-arm64": "1.2.14",
"@rolldown/binding-win32-arm64-msvc": "1.2.14",
"@rolldown/binding-win32-x64-msvc": "1.2.14"
}
},
"node_modules/rollup": {
@@ -3060,9 +3060,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true,
@@ -3441,17 +3441,17 @@
}
},
"node_modules/vite": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz",
"integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==",
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz",
"integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"lightningcss": "^1.33.0",
"picomatch": "^4.0.7",
"postcss": "^8.5.28",
"rolldown": "~1.2.6",
"postcss": "^8.5.29",
"rolldown": "~1.2.12",
"tinyglobby": "^0.2.17"
},
"bin": {
+1
View File
@@ -5,6 +5,7 @@ export const CARD_VERSION = pkg.version;
export const CARD_SIZE = 3;
export const ENTITY_DOMAIN = 'input_datetime';
export const TIME_ENTITY_DOMAIN = 'time';
// Config defaults
export const DEFAULT_HOUR_STEP = 1;
+2 -2
View File
@@ -5,7 +5,7 @@ import { customElement, property } from 'lit/decorators.js';
import { TimePickerCardConfig } from './types';
const NAME_TO_LABEL_MAP: Record<string, string> = {
entity: 'input_datetime entity id',
entity: 'time or input_datetime entity id',
name: 'Name',
hour_step: 'Hour step',
minute_step: 'Minute step',
@@ -28,7 +28,7 @@ const nameSchema = (hass: HomeAssistant): FormSchemaEntry =>
: { name: 'name', selector: { text: {} } };
const schema = (hass: HomeAssistant): FormSchemaEntry[] => [
{ name: 'entity', selector: { entity: { domain: 'input_datetime' } } },
{ name: 'entity', selector: { entity: { domain: ['input_datetime', 'time'] } } },
nameSchema(hass),
{
type: 'grid',
+41 -11
View File
@@ -24,6 +24,7 @@ import {
DEFAULT_LAYOUT_NAME,
DEFAULT_MINUTE_STEP,
ENTITY_DOMAIN,
TIME_ENTITY_DOMAIN,
} from './const';
import './editor';
import { Hour } from './models/hour';
@@ -43,7 +44,7 @@ window.customCards = window.customCards || [];
window.customCards.push({
type: 'time-picker-card',
name: 'Time Picker Card',
description: 'A Time Picker card for setting the time value of Input Datetime entities.',
description: 'A Time Picker card for input_datetime and time entities.',
});
@customElement('time-picker-card')
@@ -170,18 +171,33 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
return Partial.error('Entity not found', this.config);
}
if (computeDomain(this.entity.entity_id) !== ENTITY_DOMAIN) {
return Partial.error(`You must set an ${ENTITY_DOMAIN} entity`, this.config);
const domain = computeDomain(this.entity.entity_id);
if (domain !== ENTITY_DOMAIN && domain !== TIME_ENTITY_DOMAIN) {
return Partial.error(
`You must set an ${ENTITY_DOMAIN} or ${TIME_ENTITY_DOMAIN} entity`,
this.config,
);
}
if (!this.entity.attributes.has_time) {
if (domain === ENTITY_DOMAIN && !this.entity.attributes.has_time) {
return Partial.error(
`You must set an ${ENTITY_DOMAIN} entity that sets has_time: true`,
this.config,
);
}
const { hour, minute, second } = this.entity!.attributes;
let hour: number;
let minute: number;
let second: number;
if (domain === TIME_ENTITY_DOMAIN) {
const match = /^(\d{2}):(\d{2}):(\d{2})$/.exec(this.entity.state);
if (!match || Number(match[1]) > 23 || Number(match[2]) > 59 || Number(match[3]) > 59) {
return Partial.error('The time entity has no valid HH:MM:SS value', this.config);
}
[hour, minute, second] = match.slice(1).map(Number);
} else {
({ hour, minute, second } = this.entity.attributes);
}
const hourInstance = new Hour(hour, this.config.hour_step, this.config.hour_mode);
const minuteInstance = new Minute(minute, this.config.minute_step);
const secondInstance = new Second(second, this.config.second_step);
@@ -286,13 +302,25 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
private callHassService(): Promise<void> {
if (!this.hass) {
throw new Error('Unable to update datetime');
throw new Error('Unable to update time');
}
return this.hass.callService(ENTITY_DOMAIN, 'set_datetime', {
entity_id: this.entity!.entity_id,
time: this.time.value,
});
const domain = computeDomain(this.entity!.entity_id);
const time =
domain === TIME_ENTITY_DOMAIN
? [this.time.hour.value, this.time.minute.value, this.time.second.value]
.map((value) => String(value).padStart(2, '0'))
.join(':')
: this.time.value;
return this.hass.callService(
domain,
domain === TIME_ENTITY_DOMAIN ? 'set_value' : 'set_datetime',
{
entity_id: this.entity!.entity_id,
time,
},
);
}
static get styles(): CSSResult {
@@ -390,7 +418,9 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
_: HomeAssistant,
entities: Array<string>,
): Omit<TimePickerCardConfig, 'type'> {
const datetimeEntity = entities.find((entityId) => computeDomain(entityId) === ENTITY_DOMAIN);
const datetimeEntity = entities.find((entityId) =>
[ENTITY_DOMAIN, TIME_ENTITY_DOMAIN].includes(computeDomain(entityId)),
);
return {
entity: datetimeEntity || '',
+48
View File
@@ -17,6 +17,7 @@ class ErrorCardStub extends HTMLElement {
customElements.define('hui-error-card', ErrorCardStub);
const ENTITY_ID = 'input_datetime.alarm';
const TIME_ENTITY_ID = 'time.alarm_time';
/** YAML is untyped at runtime, so setConfig has to cope with shapes the type forbids. */
const invalidConfig = (config: Record<string, unknown>): TimePickerCardConfig =>
@@ -31,6 +32,13 @@ const alarm = (hour: number, minute = 30, second = 0): HassEntity => ({
context: { id: '', user_id: null, parent_id: null },
});
const timeEntity = (state = '07:30:00'): HassEntity => ({
...alarm(7),
entity_id: TIME_ENTITY_ID,
state,
attributes: { friendly_name: 'Alarm time' },
});
const createHass = (entity: HassEntity = alarm(7)): HomeAssistant =>
({
states: { [entity.entity_id]: entity },
@@ -116,6 +124,28 @@ describe('time-picker-card', () => {
expect(inputValues(card)).toEqual(['09', '45']);
});
it('reads a time entity from its state and follows state updates', async () => {
card = await renderCard({ entity: TIME_ENTITY_ID }, createHass(timeEntity()));
expect(inputValues(card)).toEqual(['07', '30']);
card.hass = createHass(timeEntity('09:45:06'));
await settle(card);
expect(inputValues(card)).toEqual(['09', '45']);
});
it('rejects an unavailable time entity instead of rendering invalid numbers', async () => {
card = await renderCard({ entity: TIME_ENTITY_ID }, createHass(timeEntity('unavailable')));
const error = card.shadowRoot!.querySelector('hui-error-card') as ErrorCardStub;
expect(error.config?.error).toContain('HH:MM:SS');
});
it('rejects entities from other domains', async () => {
const entity = { ...alarm(7), entity_id: 'sensor.alarm' };
card = await renderCard({ entity: entity.entity_id }, createHass(entity));
const error = card.shadowRoot!.querySelector('hui-error-card') as ErrorCardStub;
expect(error.config?.error).toContain('input_datetime or time');
});
it('renders an error card when the entity is missing', async () => {
card = await renderCard({ entity: 'input_datetime.missing' });
const error = card.shadowRoot!.querySelector('hui-error-card') as ErrorCardStub;
@@ -143,6 +173,18 @@ describe('time-picker-card', () => {
});
});
it('calls time.set_value with a zero-padded time for time entities', async () => {
const hass = createHass(timeEntity());
card = await renderCard({ entity: TIME_ENTITY_ID }, hass);
const hourUnit = card.shadowRoot!.querySelector('time-unit')!;
hourUnit.shadowRoot!.querySelector<HTMLElement>('.time-picker-icon')!.click();
expect(hass.callService).toHaveBeenCalledWith('time', 'set_value', {
entity_id: TIME_ENTITY_ID,
time: '08:30:00',
});
});
it('debounces the service call when a delay is configured', async () => {
vi.useFakeTimers();
try {
@@ -168,4 +210,10 @@ describe('time-picker-card', () => {
}
});
});
it('offers time entities in the generated configuration', () => {
expect(
TimePickerCard.getStubConfig(createHass(), ['sensor.unknown', TIME_ENTITY_ID]).entity,
).toBe(TIME_ENTITY_ID);
});
});