@sondor-technology/sondor-components 1.0.1-develop.13 → 1.0.1-develop.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/dist/components/alert/alert.constants.d.ts +3 -0
  2. package/dist/components/alert/alert.constants.d.ts.map +1 -0
  3. package/dist/components/alert/alert.constants.js +2 -0
  4. package/dist/components/alert/alert.constants.js.map +1 -0
  5. package/dist/components/alert/alert.d.ts +4 -3
  6. package/dist/components/alert/alert.d.ts.map +1 -1
  7. package/dist/components/alert/alert.js +21 -11
  8. package/dist/components/alert/alert.js.map +1 -0
  9. package/dist/components/badge/badge.constants.d.ts +3 -0
  10. package/dist/components/badge/badge.constants.d.ts.map +1 -0
  11. package/dist/components/badge/badge.constants.js +2 -0
  12. package/dist/components/badge/badge.constants.js.map +1 -0
  13. package/dist/components/badge/badge.d.ts +1 -1
  14. package/dist/components/badge/badge.d.ts.map +1 -1
  15. package/dist/components/badge/badge.js +3 -2
  16. package/dist/components/badge/badge.js.map +1 -0
  17. package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -1
  18. package/dist/components/breadcrumb/breadcrumb.js +2 -1
  19. package/dist/components/breadcrumb/breadcrumb.js.map +1 -0
  20. package/dist/components/button/button.constants.d.ts +7 -0
  21. package/dist/components/button/button.constants.d.ts.map +1 -0
  22. package/dist/components/button/button.constants.js +4 -0
  23. package/dist/components/button/button.constants.js.map +1 -0
  24. package/dist/components/button/button.d.ts +8 -4
  25. package/dist/components/button/button.d.ts.map +1 -1
  26. package/dist/components/button/button.js +181 -137
  27. package/dist/components/button/button.js.map +1 -0
  28. package/dist/components/button-group/button-group.d.ts +1 -0
  29. package/dist/components/button-group/button-group.d.ts.map +1 -1
  30. package/dist/components/button-group/button-group.js +17 -21
  31. package/dist/components/button-group/button-group.js.map +1 -0
  32. package/dist/components/calendar/calendar.d.ts +36 -0
  33. package/dist/components/calendar/calendar.d.ts.map +1 -0
  34. package/dist/components/calendar/calendar.js +439 -0
  35. package/dist/components/calendar/calendar.js.map +1 -0
  36. package/dist/components/checkbox/checkbox.constants.d.ts +3 -0
  37. package/dist/components/checkbox/checkbox.constants.d.ts.map +1 -0
  38. package/dist/components/checkbox/checkbox.constants.js +2 -0
  39. package/dist/components/checkbox/checkbox.constants.js.map +1 -0
  40. package/dist/components/checkbox/checkbox.d.ts +5 -1
  41. package/dist/components/checkbox/checkbox.d.ts.map +1 -1
  42. package/dist/components/checkbox/checkbox.js +21 -5
  43. package/dist/components/checkbox/checkbox.js.map +1 -0
  44. package/dist/components/code-block/code-block.d.ts +7 -7
  45. package/dist/components/code-block/code-block.d.ts.map +1 -1
  46. package/dist/components/code-block/code-block.js +130 -66
  47. package/dist/components/code-block/code-block.js.map +1 -0
  48. package/dist/components/content-card/content-card.d.ts.map +1 -1
  49. package/dist/components/content-card/content-card.js +10 -15
  50. package/dist/components/content-card/content-card.js.map +1 -0
  51. package/dist/components/cron-builder/cron-builder.d.ts +34 -0
  52. package/dist/components/cron-builder/cron-builder.d.ts.map +1 -0
  53. package/dist/components/cron-builder/cron-builder.js +456 -0
  54. package/dist/components/cron-builder/cron-builder.js.map +1 -0
  55. package/dist/components/divider/divider.constants.d.ts +5 -0
  56. package/dist/components/divider/divider.constants.d.ts.map +1 -0
  57. package/dist/components/divider/divider.constants.js +3 -0
  58. package/dist/components/divider/divider.constants.js.map +1 -0
  59. package/dist/components/divider/divider.d.ts +4 -2
  60. package/dist/components/divider/divider.d.ts.map +1 -1
  61. package/dist/components/divider/divider.js +18 -9
  62. package/dist/components/divider/divider.js.map +1 -0
  63. package/dist/components/drawer/drawer.d.ts +7 -4
  64. package/dist/components/drawer/drawer.d.ts.map +1 -1
  65. package/dist/components/drawer/drawer.js +73 -30
  66. package/dist/components/drawer/drawer.js.map +1 -0
  67. package/dist/components/field/field.d.ts +2 -2
  68. package/dist/components/field/field.d.ts.map +1 -1
  69. package/dist/components/field/field.js +8 -9
  70. package/dist/components/field/field.js.map +1 -0
  71. package/dist/components/footer/footer.d.ts +8 -1
  72. package/dist/components/footer/footer.d.ts.map +1 -1
  73. package/dist/components/footer/footer.js +42 -12
  74. package/dist/components/footer/footer.js.map +1 -0
  75. package/dist/components/form/form-step.js +1 -0
  76. package/dist/components/form/form-step.js.map +1 -0
  77. package/dist/components/form/form.d.ts +3 -0
  78. package/dist/components/form/form.d.ts.map +1 -1
  79. package/dist/components/form/form.js +109 -69
  80. package/dist/components/form/form.js.map +1 -0
  81. package/dist/components/grid/grid-row.d.ts +3 -3
  82. package/dist/components/grid/grid-row.d.ts.map +1 -1
  83. package/dist/components/grid/grid-row.js +36 -22
  84. package/dist/components/grid/grid-row.js.map +1 -0
  85. package/dist/components/grid/grid.d.ts +2 -2
  86. package/dist/components/grid/grid.d.ts.map +1 -1
  87. package/dist/components/grid/grid.js +5 -2
  88. package/dist/components/grid/grid.js.map +1 -0
  89. package/dist/components/icon/icon-paths.d.ts +161 -86
  90. package/dist/components/icon/icon-paths.d.ts.map +1 -1
  91. package/dist/components/icon/icon-paths.js +176 -106
  92. package/dist/components/icon/icon-paths.js.map +1 -0
  93. package/dist/components/icon/icon.d.ts +1 -1
  94. package/dist/components/icon/icon.d.ts.map +1 -1
  95. package/dist/components/icon/icon.js +6 -5
  96. package/dist/components/icon/icon.js.map +1 -0
  97. package/dist/components/input/input.d.ts +10 -0
  98. package/dist/components/input/input.d.ts.map +1 -1
  99. package/dist/components/input/input.js +59 -43
  100. package/dist/components/input/input.js.map +1 -0
  101. package/dist/components/link/link.d.ts +6 -6
  102. package/dist/components/link/link.d.ts.map +1 -1
  103. package/dist/components/link/link.js +42 -19
  104. package/dist/components/link/link.js.map +1 -0
  105. package/dist/components/metadata/metadata.d.ts +3 -3
  106. package/dist/components/metadata/metadata.d.ts.map +1 -1
  107. package/dist/components/metadata/metadata.js +23 -20
  108. package/dist/components/metadata/metadata.js.map +1 -0
  109. package/dist/components/modal/modal.d.ts +4 -16
  110. package/dist/components/modal/modal.d.ts.map +1 -1
  111. package/dist/components/modal/modal.js +99 -62
  112. package/dist/components/modal/modal.js.map +1 -0
  113. package/dist/components/page-layout/page-layout.d.ts +4 -3
  114. package/dist/components/page-layout/page-layout.d.ts.map +1 -1
  115. package/dist/components/page-layout/page-layout.js +95 -68
  116. package/dist/components/page-layout/page-layout.js.map +1 -0
  117. package/dist/components/pin-input/pin-input.d.ts +33 -0
  118. package/dist/components/pin-input/pin-input.d.ts.map +1 -0
  119. package/dist/components/pin-input/pin-input.js +253 -0
  120. package/dist/components/pin-input/pin-input.js.map +1 -0
  121. package/dist/components/popover/popover.d.ts +26 -0
  122. package/dist/components/popover/popover.d.ts.map +1 -0
  123. package/dist/components/popover/popover.js +196 -0
  124. package/dist/components/popover/popover.js.map +1 -0
  125. package/dist/components/radio/radio.d.ts +8 -0
  126. package/dist/components/radio/radio.d.ts.map +1 -1
  127. package/dist/components/radio/radio.js +8 -12
  128. package/dist/components/radio/radio.js.map +1 -0
  129. package/dist/components/radio-group/radio-group.d.ts +6 -1
  130. package/dist/components/radio-group/radio-group.d.ts.map +1 -1
  131. package/dist/components/radio-group/radio-group.js +44 -18
  132. package/dist/components/radio-group/radio-group.js.map +1 -0
  133. package/dist/components/router/router.d.ts +5 -7
  134. package/dist/components/router/router.d.ts.map +1 -1
  135. package/dist/components/router/router.js +54 -38
  136. package/dist/components/router/router.js.map +1 -0
  137. package/dist/components/select/select.d.ts +5 -4
  138. package/dist/components/select/select.d.ts.map +1 -1
  139. package/dist/components/select/select.js +40 -9
  140. package/dist/components/select/select.js.map +1 -0
  141. package/dist/components/skeleton/skeleton.d.ts +3 -2
  142. package/dist/components/skeleton/skeleton.d.ts.map +1 -1
  143. package/dist/components/skeleton/skeleton.js +10 -13
  144. package/dist/components/skeleton/skeleton.js.map +1 -0
  145. package/dist/components/switch/switch.d.ts +12 -0
  146. package/dist/components/switch/switch.d.ts.map +1 -1
  147. package/dist/components/switch/switch.js +28 -22
  148. package/dist/components/switch/switch.js.map +1 -0
  149. package/dist/components/table/data-table.d.ts +3 -25
  150. package/dist/components/table/data-table.d.ts.map +1 -1
  151. package/dist/components/table/data-table.js +211 -146
  152. package/dist/components/table/data-table.js.map +1 -0
  153. package/dist/components/table/table.d.ts +3 -3
  154. package/dist/components/table/table.d.ts.map +1 -1
  155. package/dist/components/table/table.js +39 -20
  156. package/dist/components/table/table.js.map +1 -0
  157. package/dist/components/tabs/tab-panel.d.ts +3 -1
  158. package/dist/components/tabs/tab-panel.d.ts.map +1 -1
  159. package/dist/components/tabs/tab-panel.js +21 -11
  160. package/dist/components/tabs/tab-panel.js.map +1 -0
  161. package/dist/components/tabs/tab.d.ts +5 -1
  162. package/dist/components/tabs/tab.d.ts.map +1 -1
  163. package/dist/components/tabs/tab.js +46 -28
  164. package/dist/components/tabs/tab.js.map +1 -0
  165. package/dist/components/tabs/tabs.d.ts +4 -0
  166. package/dist/components/tabs/tabs.d.ts.map +1 -1
  167. package/dist/components/tabs/tabs.js +76 -13
  168. package/dist/components/tabs/tabs.js.map +1 -0
  169. package/dist/components/textarea/textarea.d.ts +13 -0
  170. package/dist/components/textarea/textarea.d.ts.map +1 -1
  171. package/dist/components/textarea/textarea.js +59 -13
  172. package/dist/components/textarea/textarea.js.map +1 -0
  173. package/dist/components/theme-toggle/theme-toggle.constants.d.ts +3 -0
  174. package/dist/components/theme-toggle/theme-toggle.constants.d.ts.map +1 -0
  175. package/dist/components/theme-toggle/theme-toggle.constants.js +2 -0
  176. package/dist/components/theme-toggle/theme-toggle.constants.js.map +1 -0
  177. package/dist/components/theme-toggle/theme-toggle.d.ts +3 -2
  178. package/dist/components/theme-toggle/theme-toggle.d.ts.map +1 -1
  179. package/dist/components/theme-toggle/theme-toggle.js +46 -19
  180. package/dist/components/theme-toggle/theme-toggle.js.map +1 -0
  181. package/dist/components/time-picker/time-picker.d.ts +38 -0
  182. package/dist/components/time-picker/time-picker.d.ts.map +1 -0
  183. package/dist/components/time-picker/time-picker.js +280 -0
  184. package/dist/components/time-picker/time-picker.js.map +1 -0
  185. package/dist/components/toggle-group/toggle-button.d.ts +23 -0
  186. package/dist/components/toggle-group/toggle-button.d.ts.map +1 -0
  187. package/dist/components/toggle-group/toggle-button.js +115 -0
  188. package/dist/components/toggle-group/toggle-button.js.map +1 -0
  189. package/dist/components/toggle-group/toggle-group.d.ts +32 -0
  190. package/dist/components/toggle-group/toggle-group.d.ts.map +1 -0
  191. package/dist/components/toggle-group/toggle-group.js +150 -0
  192. package/dist/components/toggle-group/toggle-group.js.map +1 -0
  193. package/dist/components/tooltip/tooltip.d.ts +33 -0
  194. package/dist/components/tooltip/tooltip.d.ts.map +1 -0
  195. package/dist/components/tooltip/tooltip.js +293 -0
  196. package/dist/components/tooltip/tooltip.js.map +1 -0
  197. package/dist/components/vertical-nav/vertical-nav-group.d.ts +5 -2
  198. package/dist/components/vertical-nav/vertical-nav-group.d.ts.map +1 -1
  199. package/dist/components/vertical-nav/vertical-nav-group.js +50 -14
  200. package/dist/components/vertical-nav/vertical-nav-group.js.map +1 -0
  201. package/dist/components/vertical-nav/vertical-nav-item.d.ts +3 -2
  202. package/dist/components/vertical-nav/vertical-nav-item.d.ts.map +1 -1
  203. package/dist/components/vertical-nav/vertical-nav-item.js +30 -8
  204. package/dist/components/vertical-nav/vertical-nav-item.js.map +1 -0
  205. package/dist/styles/index.css +49 -17
  206. package/dist/styles/reset.js +1 -0
  207. package/dist/styles/reset.js.map +1 -0
  208. package/package.json +30 -1
  209. package/src/components/alert/alert.constants.ts +2 -0
  210. package/src/components/alert/alert.ts +24 -15
  211. package/src/components/badge/badge.constants.ts +2 -0
  212. package/src/components/badge/badge.ts +3 -4
  213. package/src/components/breadcrumb/breadcrumb.ts +2 -2
  214. package/src/components/button/button.constants.ts +8 -0
  215. package/src/components/button/button.ts +184 -144
  216. package/src/components/button-group/button-group.ts +18 -22
  217. package/src/components/calendar/calendar.ts +444 -0
  218. package/src/components/checkbox/checkbox.constants.ts +2 -0
  219. package/src/components/checkbox/checkbox.ts +33 -18
  220. package/src/components/code-block/code-block.ts +151 -84
  221. package/src/components/content-card/content-card.ts +10 -16
  222. package/src/components/cron-builder/cron-builder.ts +452 -0
  223. package/src/components/divider/divider.constants.ts +5 -0
  224. package/src/components/divider/divider.ts +22 -11
  225. package/src/components/drawer/drawer.ts +79 -34
  226. package/src/components/field/field.ts +9 -11
  227. package/src/components/footer/footer.ts +43 -12
  228. package/src/components/form/form.ts +121 -84
  229. package/src/components/grid/grid-row.ts +45 -32
  230. package/src/components/grid/grid.ts +7 -4
  231. package/src/components/icon/icon-paths.ts +175 -106
  232. package/src/components/icon/icon.ts +6 -6
  233. package/src/components/input/input.ts +59 -45
  234. package/src/components/link/link.ts +45 -21
  235. package/src/components/metadata/metadata.ts +26 -24
  236. package/src/components/modal/modal.ts +108 -74
  237. package/src/components/page-layout/page-layout.ts +97 -72
  238. package/src/components/pin-input/pin-input.ts +249 -0
  239. package/src/components/popover/popover.ts +192 -0
  240. package/src/components/radio/radio.ts +8 -13
  241. package/src/components/radio-group/radio-group.ts +45 -21
  242. package/src/components/router/router.ts +73 -50
  243. package/src/components/select/select.ts +41 -80
  244. package/src/components/skeleton/skeleton.ts +14 -14
  245. package/src/components/switch/switch.ts +31 -25
  246. package/src/components/table/data-table.ts +248 -177
  247. package/src/components/table/table.ts +52 -29
  248. package/src/components/tabs/tab-panel.ts +25 -13
  249. package/src/components/tabs/tab.ts +58 -33
  250. package/src/components/tabs/tabs.ts +77 -15
  251. package/src/components/textarea/textarea.ts +63 -15
  252. package/src/components/theme-toggle/theme-toggle.constants.ts +2 -0
  253. package/src/components/theme-toggle/theme-toggle.ts +47 -22
  254. package/src/components/time-picker/time-picker.ts +272 -0
  255. package/src/components/toggle-group/toggle-button.ts +104 -0
  256. package/src/components/toggle-group/toggle-group.ts +148 -0
  257. package/src/components/tooltip/tooltip.ts +286 -0
  258. package/src/components/vertical-nav/vertical-nav-group.ts +57 -18
  259. package/src/components/vertical-nav/vertical-nav-item.ts +35 -12
  260. package/src/styles/globals.css +27 -4
  261. package/src/styles/theme.css +40 -32
  262. package/tsconfig.json +2 -1
  263. package/dist/components/card/card.d.ts +0 -11
  264. package/dist/components/card/card.d.ts.map +0 -1
  265. package/dist/components/card/card.js +0 -54
  266. package/src/components/card/card.ts +0 -53
@@ -1,19 +1,20 @@
1
1
  import {LitElement, html, css} from "lit";
2
2
  import {customElement, property} from "lit/decorators.js";
3
3
  import {resetStyles} from "../../styles/reset.js";
4
-
5
- export type ThemeMode = "light" | "system" | "dark";
4
+ import {ThemeMode} from "./theme-toggle.constants.js";
6
5
 
7
6
  @customElement("st-theme-toggle")
8
7
  export class StThemeToggle extends LitElement {
9
8
  @property({type: String, reflect: true}) theme: ThemeMode = "system";
10
9
 
10
+ // 🚀 FEATURE: Automatically applies the theme to the document and saves to localStorage
11
+ @property({type: Boolean}) auto = false;
12
+
11
13
  static styles = [
12
14
  resetStyles,
13
15
  css`
14
16
  :host {
15
17
  display: inline-flex;
16
- /* Use semantic tokens for the container */
17
18
  background-color: var(--st-color-surface);
18
19
  border: 1px solid var(--st-color-border);
19
20
  border-radius: 999px;
@@ -39,12 +40,9 @@ export class StThemeToggle extends LitElement {
39
40
  color: var(--st-color-text);
40
41
  }
41
42
 
42
- /* Active State Styling */
43
43
  button[aria-pressed="true"] {
44
- /* Use the main background color so the active pill "pops" out of the surface */
45
44
  background-color: var(--st-color-bg);
46
45
  color: var(--st-color-primary);
47
- /* Subtle shadow to lift the active pill */
48
46
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
49
47
  }
50
48
 
@@ -60,10 +58,45 @@ export class StThemeToggle extends LitElement {
60
58
  `,
61
59
  ];
62
60
 
61
+ override connectedCallback() {
62
+ super.connectedCallback();
63
+
64
+ // 🚀 FIX: Semantically group the buttons for screen readers
65
+ this.setAttribute("role", "group");
66
+ this.setAttribute("aria-label", "Theme preference");
67
+
68
+ // If auto is enabled, try loading from localStorage first
69
+ if (this.auto) {
70
+ try {
71
+ const savedTheme = localStorage.getItem("st-theme") as ThemeMode | null;
72
+ if (savedTheme) {
73
+ this.theme = savedTheme;
74
+ document.documentElement.setAttribute("data-theme", savedTheme);
75
+ return;
76
+ }
77
+ } catch (e) {
78
+ // Silently fail if localStorage is blocked (e.g., incognito mode)
79
+ }
80
+ }
81
+
82
+ // Fallback to checking the document root for a pre-rendered theme
83
+ const rootTheme = document.documentElement.getAttribute("data-theme") as ThemeMode | null;
84
+ if (rootTheme) {
85
+ this.theme = rootTheme;
86
+ }
87
+ }
88
+
63
89
  private _handleThemeChange(newTheme: ThemeMode) {
64
90
  this.theme = newTheme;
65
91
 
66
- // Dispatch an event so the consuming app knows to update the document root
92
+ // Apply automatically if the feature is enabled
93
+ if (this.auto) {
94
+ document.documentElement.setAttribute("data-theme", newTheme);
95
+ try {
96
+ localStorage.setItem("st-theme", newTheme);
97
+ } catch (e) {}
98
+ }
99
+
67
100
  this.dispatchEvent(
68
101
  new CustomEvent("st-theme-change", {
69
102
  detail: {theme: newTheme},
@@ -73,24 +106,16 @@ export class StThemeToggle extends LitElement {
73
106
  );
74
107
  }
75
108
 
76
- override connectedCallback() {
77
- super.connectedCallback();
78
- // Check the html tag for the theme injected by your blocking <head> script
79
- const rootTheme = document.documentElement.getAttribute("data-theme") as ThemeMode;
80
- if (rootTheme) {
81
- this.theme = rootTheme;
82
- }
83
- }
84
-
85
109
  render() {
86
110
  return html`
87
111
  <button
88
112
  aria-pressed=${this.theme === "light"}
113
+ aria-label="Light Mode"
89
114
  title="Light Mode"
90
115
  @click=${() => this._handleThemeChange("light")}
91
116
  >
92
- <!-- Sun Icon -->
93
- <svg viewBox="0 0 24 24">
117
+ <!-- 🚀 FIX: Hide decorative SVGs from the accessibility tree -->
118
+ <svg aria-hidden="true" viewBox="0 0 24 24">
94
119
  <circle cx="12" cy="12" r="5"></circle>
95
120
  <path
96
121
  d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"
@@ -100,11 +125,11 @@ export class StThemeToggle extends LitElement {
100
125
 
101
126
  <button
102
127
  aria-pressed=${this.theme === "system"}
128
+ aria-label="System Preference"
103
129
  title="System Preference"
104
130
  @click=${() => this._handleThemeChange("system")}
105
131
  >
106
- <!-- Monitor/System Icon -->
107
- <svg viewBox="0 0 24 24">
132
+ <svg aria-hidden="true" viewBox="0 0 24 24">
108
133
  <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
109
134
  <path d="M8 21h8M12 17v4"></path>
110
135
  </svg>
@@ -112,11 +137,11 @@ export class StThemeToggle extends LitElement {
112
137
 
113
138
  <button
114
139
  aria-pressed=${this.theme === "dark"}
140
+ aria-label="Dark Mode"
115
141
  title="Dark Mode"
116
142
  @click=${() => this._handleThemeChange("dark")}
117
143
  >
118
- <!-- Moon Icon -->
119
- <svg viewBox="0 0 24 24">
144
+ <svg aria-hidden="true" viewBox="0 0 24 24">
120
145
  <path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"></path>
121
146
  </svg>
122
147
  </button>
@@ -0,0 +1,272 @@
1
+ import {LitElement, html, css, PropertyValues} from "lit";
2
+ import {customElement, property, query, state} from "lit/decorators.js";
3
+ import {classMap} from "lit/directives/class-map.js";
4
+ import {ifDefined} from "lit/directives/if-defined.js";
5
+ import {resetStyles} from "../../styles/reset.js";
6
+
7
+ @customElement("st-time-picker")
8
+ export class StTimePicker extends LitElement {
9
+ static formAssociated = true;
10
+ private _internals: ElementInternals;
11
+
12
+ @property({type: String, reflect: true}) name = "";
13
+ @property({type: String}) label = "";
14
+
15
+ // Visually hide the label for screen-reader only accessibility
16
+ @property({type: Boolean, attribute: "hide-label"}) hideLabel = false;
17
+
18
+ // Time string in HH:mm format (e.g., "14:30")
19
+ @property({type: String}) value = "";
20
+
21
+ @property({type: String}) min = "";
22
+ @property({type: String}) max = "";
23
+ @property({type: Number}) step?: number;
24
+
25
+ @property({type: Boolean, reflect: true}) disabled = false;
26
+ @property({type: Boolean, reflect: true}) required = false;
27
+ @property({type: Boolean, reflect: true}) invalid = false;
28
+
29
+ @state() private _touched = false;
30
+
31
+ @query(".native-input") private _inputEl!: HTMLInputElement;
32
+
33
+ private _labelId = `time-label-${Math.random().toString(36).substring(2, 9)}`;
34
+ private _defaultValue = "";
35
+
36
+ constructor() {
37
+ super();
38
+ this._internals = this.attachInternals();
39
+ }
40
+
41
+ static styles = [
42
+ resetStyles,
43
+ css`
44
+ :host {
45
+ display: block;
46
+ font-family: inherit;
47
+ }
48
+
49
+ label {
50
+ display: block;
51
+ font-size: var(--st-font-size-sm);
52
+ font-weight: 500;
53
+ color: var(--st-color-text-muted);
54
+ margin-bottom: var(--st-spacing-xs);
55
+ }
56
+
57
+ .sr-only {
58
+ position: absolute;
59
+ width: 1px;
60
+ height: 1px;
61
+ padding: 0;
62
+ margin: -1px;
63
+ overflow: hidden;
64
+ clip: rect(0, 0, 0, 0);
65
+ border: 0;
66
+ }
67
+
68
+ .input-wrapper {
69
+ position: relative;
70
+ display: flex;
71
+ align-items: center;
72
+ width: 100%;
73
+ background: var(--st-color-surface);
74
+ border: 1px solid var(--st-color-border);
75
+ border-radius: var(--st-radius-md);
76
+ box-sizing: border-box;
77
+ transition:
78
+ border-color 0.2s,
79
+ box-shadow 0.2s,
80
+ background-color 0.2s;
81
+ }
82
+
83
+ /* Focus within ring */
84
+ .input-wrapper:focus-within {
85
+ border-color: var(--st-color-primary);
86
+ box-shadow: 0 0 0 2px var(--st-color-primary-muted, rgba(59, 130, 246, 0.25));
87
+ }
88
+
89
+ .input-wrapper.is-invalid {
90
+ border-color: var(--st-color-danger, #dc2626);
91
+ }
92
+
93
+ .input-wrapper.is-disabled {
94
+ background: var(--st-color-surface-muted, #f1f5f9);
95
+ cursor: not-allowed;
96
+ opacity: 0.7;
97
+ }
98
+
99
+ .native-input {
100
+ width: 100%;
101
+ min-height: 42px;
102
+ padding: var(--st-spacing-xs) var(--st-spacing-md);
103
+ background: transparent;
104
+ border: none;
105
+ color: var(--st-color-text);
106
+ font-family: inherit;
107
+ font-size: var(--st-font-size-base);
108
+ outline: none;
109
+ cursor: inherit;
110
+ appearance: none;
111
+ -webkit-appearance: none;
112
+ }
113
+
114
+ /* Style the native clock icon generated by WebKit/Blink */
115
+ .native-input::-webkit-calendar-picker-indicator {
116
+ cursor: pointer;
117
+ opacity: 0.6;
118
+ transition: opacity 0.2s;
119
+ filter: var(--st-icon-filter, none); /* Allows inverting for dark mode */
120
+ }
121
+
122
+ .native-input::-webkit-calendar-picker-indicator:hover {
123
+ opacity: 1;
124
+ }
125
+
126
+ .native-input:disabled {
127
+ color: var(--st-color-text-muted);
128
+ }
129
+ `,
130
+ ];
131
+
132
+ override firstUpdated(changedProperties: PropertyValues) {
133
+ super.firstUpdated(changedProperties);
134
+ this._defaultValue = this.value;
135
+
136
+ // Sync initial validity state based on native input
137
+ this._syncValidity();
138
+ }
139
+
140
+ override updated(changedProperties: PropertyValues) {
141
+ super.updated(changedProperties);
142
+ if (
143
+ changedProperties.has("value") ||
144
+ changedProperties.has("required") ||
145
+ changedProperties.has("min") ||
146
+ changedProperties.has("max")
147
+ ) {
148
+ this._internals.setFormValue(this.value || null);
149
+ this._syncValidity();
150
+ }
151
+ }
152
+
153
+ private _syncValidity() {
154
+ if (!this._inputEl) return;
155
+
156
+ this.invalid = false;
157
+
158
+ // 🚀 FIX: Safari/WebKit poorly supports min/max on time inputs.
159
+ // We enforce these constraints manually using strict lexicographical comparison.
160
+ if (this.required && !this.value) {
161
+ this._internals.setValidity({valueMissing: true}, "Please select a time.");
162
+ if (this._touched) this.invalid = true;
163
+ return;
164
+ }
165
+
166
+ if (this.value && this.min && this.value < this.min) {
167
+ this._internals.setValidity({rangeUnderflow: true}, `Time must be ${this.min} or later.`);
168
+ if (this._touched) this.invalid = true;
169
+ return;
170
+ }
171
+
172
+ if (this.value && this.max && this.value > this.max) {
173
+ this._internals.setValidity({rangeOverflow: true}, `Time must be ${this.max} or earlier.`);
174
+ if (this._touched) this.invalid = true;
175
+ return;
176
+ }
177
+
178
+ // Fallback to native validation for things like 'step' bounds
179
+ const isValid = this._inputEl.checkValidity();
180
+
181
+ if (isValid) {
182
+ this._internals.setValidity({});
183
+ this.invalid = false;
184
+ } else {
185
+ this._internals.setValidity(
186
+ this._inputEl.validity,
187
+ this._inputEl.validationMessage,
188
+ this._inputEl,
189
+ );
190
+ if (this._touched) {
191
+ this.invalid = true;
192
+ }
193
+ }
194
+ }
195
+
196
+ public checkValidity() {
197
+ return this._internals.checkValidity();
198
+ }
199
+ public reportValidity() {
200
+ this._touched = true;
201
+ this._syncValidity();
202
+ return this._internals.reportValidity();
203
+ }
204
+
205
+ formResetCallback() {
206
+ this.value = this._defaultValue;
207
+ this._touched = false;
208
+ this.invalid = false;
209
+ this._syncValidity();
210
+ }
211
+
212
+ public override focus(options?: FocusOptions) {
213
+ this._inputEl?.focus(options);
214
+ }
215
+
216
+ private _handleInput(e: Event) {
217
+ this.value = (e.target as HTMLInputElement).value;
218
+ this.dispatchEvent(new Event("input", {bubbles: true, composed: true}));
219
+ }
220
+
221
+ private _handleChange(e: Event) {
222
+ this._touched = true;
223
+ this.value = (e.target as HTMLInputElement).value;
224
+ this._syncValidity();
225
+ this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
226
+ }
227
+
228
+ private _handleBlur() {
229
+ this._touched = true;
230
+ this._syncValidity();
231
+ }
232
+
233
+ render() {
234
+ return html`
235
+ ${this.label
236
+ ? html`<label id=${this._labelId} class=${classMap({"sr-only": this.hideLabel})}
237
+ >${this.label}</label
238
+ >`
239
+ : ""}
240
+
241
+ <div
242
+ class=${classMap({
243
+ "input-wrapper": true,
244
+ "is-disabled": this.disabled,
245
+ "is-invalid": this.invalid,
246
+ })}
247
+ >
248
+ <input
249
+ class="native-input"
250
+ type="time"
251
+ name=${ifDefined(this.name || undefined)}
252
+ .value=${this.value}
253
+ min=${ifDefined(this.min || undefined)}
254
+ max=${ifDefined(this.max || undefined)}
255
+ step=${ifDefined(this.step)}
256
+ ?required=${this.required}
257
+ ?disabled=${this.disabled}
258
+ aria-labelledby=${ifDefined(this.label ? this._labelId : undefined)}
259
+ @input=${this._handleInput}
260
+ @change=${this._handleChange}
261
+ @blur=${this._handleBlur}
262
+ />
263
+ </div>
264
+ `;
265
+ }
266
+ }
267
+
268
+ declare global {
269
+ interface HTMLElementTagNameMap {
270
+ "st-time-picker": StTimePicker;
271
+ }
272
+ }
@@ -0,0 +1,104 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+ import { resetStyles } from "../../styles/reset.js";
4
+
5
+ @customElement("st-toggle-button")
6
+ export class StToggleButton extends LitElement {
7
+ // 🚀 FIX: Forwards programmatic focus natively to the inner button
8
+ static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true };
9
+
10
+ @property({ type: String }) value = "";
11
+ @property({ type: Boolean, reflect: true }) selected = false;
12
+ @property({ type: Boolean, reflect: true }) disabled = false;
13
+
14
+ static styles = [
15
+ resetStyles,
16
+ css`
17
+ :host {
18
+ display: inline-flex;
19
+ flex: 1; /* Ensures all buttons in a group stretch evenly */
20
+ }
21
+
22
+ button {
23
+ display: inline-flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ gap: var(--st-spacing-xs);
27
+ width: 100%;
28
+ padding: var(--st-spacing-sm) var(--st-spacing-md);
29
+ background-color: var(--st-color-surface);
30
+ color: var(--st-color-text);
31
+ border: none;
32
+ font-size: var(--st-font-size-sm);
33
+ font-weight: var(--st-font-weight-medium);
34
+ cursor: pointer;
35
+ transition: all 0.2s ease;
36
+ white-space: nowrap;
37
+ }
38
+
39
+ button:hover:not(:disabled) {
40
+ background-color: var(--st-color-surface-hover, #f8fafc);
41
+ }
42
+
43
+ button:focus-visible {
44
+ outline: 2px solid var(--st-color-primary);
45
+ outline-offset: -2px;
46
+ z-index: 1; /* Brings the focus ring above siblings */
47
+ }
48
+
49
+ /* Active/Selected State */
50
+ :host([selected]) button {
51
+ background-color: var(--st-color-primary-light, #eff6ff);
52
+ color: var(--st-color-primary-dark, #1d4ed8);
53
+ font-weight: var(--st-font-weight-semibold);
54
+ }
55
+
56
+ /* Disabled State */
57
+ :host([disabled]) {
58
+ pointer-events: none;
59
+ }
60
+
61
+ :host([disabled]) button {
62
+ opacity: 0.5;
63
+ background-color: var(--st-color-surface-muted, #f1f5f9);
64
+ color: var(--st-color-text-muted);
65
+ }
66
+
67
+ ::slotted(svg) {
68
+ width: 1.125rem;
69
+ height: 1.125rem;
70
+ fill: currentColor;
71
+ }
72
+ `
73
+ ];
74
+
75
+ private _handleClick() {
76
+ if (this.disabled) return;
77
+
78
+ // Dispatch an internal event for the group orchestrator to catch
79
+ this.dispatchEvent(new CustomEvent("st-toggle-action", {
80
+ detail: { value: this.value },
81
+ bubbles: true,
82
+ composed: true
83
+ }));
84
+ }
85
+
86
+ render() {
87
+ return html`
88
+ <button
89
+ type="button"
90
+ aria-pressed=${this.selected ? "true" : "false"}
91
+ ?disabled=${this.disabled}
92
+ @click=${this._handleClick}
93
+ >
94
+ <slot></slot>
95
+ </button>
96
+ `;
97
+ }
98
+ }
99
+
100
+ declare global {
101
+ interface HTMLElementTagNameMap {
102
+ "st-toggle-button": StToggleButton;
103
+ }
104
+ }
@@ -0,0 +1,148 @@
1
+ import { LitElement, html, css, PropertyValues } from "lit";
2
+ import { customElement, property, queryAssignedElements } from "lit/decorators.js";
3
+ import { ifDefined } from "lit/directives/if-defined.js";
4
+ import { resetStyles } from "../../styles/reset.js";
5
+ import type { StToggleButton } from "./toggle-button.js";
6
+ import "./toggle-button.js";
7
+
8
+ @customElement("st-toggle-group")
9
+ export class StToggleGroup extends LitElement {
10
+ static formAssociated = true;
11
+ private _internals: ElementInternals;
12
+
13
+ @queryAssignedElements({ selector: "st-toggle-button" })
14
+ private _buttons!: StToggleButton[];
15
+
16
+ @property({ type: String, reflect: true }) name = "";
17
+
18
+ // A comma-separated list of values if multiple=true, or a single value if false
19
+ @property({ type: String }) value = "";
20
+
21
+ @property({ type: Boolean }) multiple = false;
22
+ @property({ type: Boolean, reflect: true }) disabled = false;
23
+ @property({ type: Boolean, reflect: true }) required = false;
24
+
25
+ // ARIA Support
26
+ @property({ attribute: "aria-label" }) ariaLabel = "";
27
+
28
+ private _defaultValue = "";
29
+
30
+ constructor() {
31
+ super();
32
+ this._internals = this.attachInternals();
33
+ this.addEventListener("st-toggle-action", this._handleToggle as EventListener);
34
+ }
35
+
36
+ static styles = [
37
+ resetStyles,
38
+ css`
39
+ :host {
40
+ display: inline-flex;
41
+ }
42
+
43
+ .group-container {
44
+ display: inline-flex;
45
+ background-color: var(--st-color-border);
46
+ /* 🚀 FIX: This 1px gap perfectly acts as the inner border between children */
47
+ gap: 1px;
48
+ border: 1px solid var(--st-color-border);
49
+ border-radius: var(--st-radius-md);
50
+ overflow: hidden; /* Clips the child buttons exactly to the border radius */
51
+ }
52
+
53
+ :host([disabled]) .group-container {
54
+ opacity: 0.5;
55
+ pointer-events: none;
56
+ }
57
+ `
58
+ ];
59
+
60
+ override firstUpdated(changedProperties: PropertyValues) {
61
+ super.firstUpdated(changedProperties);
62
+ this._defaultValue = this.value;
63
+ }
64
+
65
+ override updated(changedProperties: PropertyValues) {
66
+ super.updated(changedProperties);
67
+
68
+ if (changedProperties.has("value")) {
69
+ this._internals.setFormValue(this.value || null);
70
+ this._syncButtons();
71
+ this._syncValidity();
72
+ }
73
+
74
+ if (changedProperties.has("disabled")) {
75
+ this._buttons.forEach(btn => btn.disabled = this.disabled);
76
+ }
77
+ }
78
+
79
+ private _syncButtons() {
80
+ const values = this.multiple && this.value ? this.value.split(",") : [this.value];
81
+ this._buttons.forEach(btn => {
82
+ btn.selected = values.includes(btn.value);
83
+ });
84
+ }
85
+
86
+ private _handleSlotChange() {
87
+ this._syncButtons();
88
+ if (this.disabled) {
89
+ this._buttons.forEach(btn => btn.disabled = true);
90
+ }
91
+ }
92
+
93
+ private _handleToggle(e: CustomEvent<{ value: string }>) {
94
+ e.stopPropagation();
95
+ const toggledValue = e.detail.value;
96
+
97
+ if (this.multiple) {
98
+ let values = this.value ? this.value.split(",") : [];
99
+ if (values.includes(toggledValue)) {
100
+ values = values.filter(v => v !== toggledValue); // Deselect
101
+ } else {
102
+ values.push(toggledValue); // Select
103
+ }
104
+ this.value = values.join(",");
105
+ } else {
106
+ // Single select behavior (radio button style: clicking active doesn't deselect)
107
+ if (this.value !== toggledValue) {
108
+ this.value = toggledValue;
109
+ }
110
+ }
111
+
112
+ this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
113
+ }
114
+
115
+ formResetCallback() {
116
+ this.value = this._defaultValue;
117
+ }
118
+
119
+ private _syncValidity() {
120
+ if (this.required && !this.value) {
121
+ this._internals.setValidity({ valueMissing: true }, "Please select an option.");
122
+ } else {
123
+ this._internals.setValidity({});
124
+ }
125
+ }
126
+
127
+ public checkValidity() { return this._internals.checkValidity(); }
128
+ public reportValidity() { return this._internals.reportValidity(); }
129
+
130
+ render() {
131
+ return html`
132
+ <div
133
+ class="group-container"
134
+ role="group"
135
+ aria-label=${ifDefined(this.ariaLabel || undefined)}
136
+ aria-required=${this.required ? "true" : "false"}
137
+ >
138
+ <slot @slotchange=${this._handleSlotChange}></slot>
139
+ </div>
140
+ `;
141
+ }
142
+ }
143
+
144
+ declare global {
145
+ interface HTMLElementTagNameMap {
146
+ "st-toggle-group": StToggleGroup;
147
+ }
148
+ }