@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
@@ -0,0 +1,192 @@
1
+ import {LitElement, html, css, PropertyValues} from "lit";
2
+ import {customElement, property, query, state, queryAssignedElements} from "lit/decorators.js";
3
+ import {resetStyles} from "../../styles/reset.js";
4
+
5
+ export type PopoverPosition = "top" | "bottom" | "left" | "right";
6
+
7
+ @customElement("st-popover")
8
+ export class StPopover extends LitElement {
9
+ @property({type: String}) position: PopoverPosition = "bottom";
10
+ @property({type: Number}) offset = 8;
11
+ @property({type: Boolean, reflect: true}) disabled = false;
12
+
13
+ @state() private _isOpen = false;
14
+
15
+ @query(".trigger-wrapper") private _triggerWrapper!: HTMLElement;
16
+ @query("#popover-content") private _popoverEl!: HTMLElement;
17
+
18
+ @queryAssignedElements({slot: "trigger"}) private _triggerElements!: HTMLElement[];
19
+
20
+ static styles = [
21
+ resetStyles,
22
+ css`
23
+ :host {
24
+ display: inline-block;
25
+ }
26
+
27
+ .trigger-wrapper {
28
+ display: inline-block;
29
+ cursor: pointer;
30
+ }
31
+
32
+ /* Native popover styles */
33
+ [popover] {
34
+ position: fixed;
35
+ margin: 0;
36
+ padding: 0;
37
+ border: none;
38
+ background: transparent;
39
+ overflow: visible;
40
+ /* 🚀 FIX: Erase the browser's native centering inset so our JS coordinates work */
41
+ inset: auto;
42
+ }
43
+
44
+ .popover-surface {
45
+ background: var(--st-color-surface);
46
+ border: 1px solid var(--st-color-border);
47
+ border-radius: var(--st-radius-md);
48
+ box-shadow:
49
+ 0 10px 15px -3px rgba(0, 0, 0, 0.1),
50
+ 0 4px 6px -2px rgba(0, 0, 0, 0.05);
51
+ padding: var(--st-spacing-md);
52
+ color: var(--st-color-text);
53
+
54
+ opacity: 0;
55
+ transform: scale(0.95);
56
+ transition:
57
+ opacity 0.15s ease,
58
+ transform 0.15s ease;
59
+ }
60
+
61
+ [popover]:popover-open .popover-surface {
62
+ opacity: 1;
63
+ transform: scale(1);
64
+ }
65
+
66
+ [popover]::backdrop {
67
+ background: transparent;
68
+ }
69
+ `,
70
+ ];
71
+
72
+ override updated(changedProperties: PropertyValues) {
73
+ super.updated(changedProperties);
74
+ if (changedProperties.has("_isOpen")) {
75
+ this._triggerElements?.forEach((el) => {
76
+ el.setAttribute("aria-expanded", String(this._isOpen));
77
+ });
78
+ }
79
+ }
80
+
81
+ private _handleTriggerSlotChange() {
82
+ this._triggerElements?.forEach((el) => {
83
+ el.setAttribute("aria-haspopup", "dialog");
84
+ el.setAttribute("aria-expanded", String(this._isOpen));
85
+ });
86
+ }
87
+
88
+ private _togglePopover() {
89
+ if (this.disabled) return;
90
+ this._isOpen ? this.hide() : this.show();
91
+ }
92
+
93
+ public show() {
94
+ if (this.disabled || this._isOpen) return;
95
+
96
+ // 🚀 FIX: Update state synchronously so the _reposition check passes instantly
97
+ this._isOpen = true;
98
+
99
+ this._popoverEl.showPopover();
100
+ this._reposition();
101
+
102
+ window.addEventListener("resize", this._reposition);
103
+ window.addEventListener("scroll", this._reposition, true);
104
+ }
105
+
106
+ public hide() {
107
+ if (!this._isOpen) return;
108
+
109
+ // 🚀 FIX: Update state synchronously
110
+ this._isOpen = false;
111
+
112
+ this._popoverEl.hidePopover();
113
+ }
114
+
115
+ private _handleNativeToggle(e: Event) {
116
+ const isOpening = (e as any).newState === "open";
117
+ this._isOpen = isOpening;
118
+
119
+ // This handles the cleanup when the user clicks outside or hits ESC
120
+ if (!isOpening) {
121
+ window.removeEventListener("resize", this._reposition);
122
+ window.removeEventListener("scroll", this._reposition, true);
123
+ }
124
+ }
125
+
126
+ private _reposition = () => {
127
+ if (!this._isOpen || !this._popoverEl || !this._triggerWrapper) return;
128
+
129
+ const triggerRect = this._triggerWrapper.getBoundingClientRect();
130
+ const popoverRect = this._popoverEl.getBoundingClientRect();
131
+
132
+ let top = 0;
133
+ let left = 0;
134
+
135
+ switch (this.position) {
136
+ case "bottom":
137
+ top = triggerRect.bottom + this.offset;
138
+ left = triggerRect.left + triggerRect.width / 2 - popoverRect.width / 2;
139
+ break;
140
+ case "top":
141
+ top = triggerRect.top - popoverRect.height - this.offset;
142
+ left = triggerRect.left + triggerRect.width / 2 - popoverRect.width / 2;
143
+ break;
144
+ case "right":
145
+ top = triggerRect.top + triggerRect.height / 2 - popoverRect.height / 2;
146
+ left = triggerRect.right + this.offset;
147
+ break;
148
+ case "left":
149
+ top = triggerRect.top + triggerRect.height / 2 - popoverRect.height / 2;
150
+ left = triggerRect.left - popoverRect.width - this.offset;
151
+ break;
152
+ }
153
+
154
+ const padding = 8;
155
+ if (left < padding) left = padding;
156
+ if (left + popoverRect.width > window.innerWidth - padding) {
157
+ left = window.innerWidth - popoverRect.width - padding;
158
+ }
159
+ if (top < padding) top = padding;
160
+ if (top + popoverRect.height > window.innerHeight - padding) {
161
+ top = window.innerHeight - popoverRect.height - padding;
162
+ }
163
+
164
+ this._popoverEl.style.top = `${top}px`;
165
+ this._popoverEl.style.left = `${left}px`;
166
+ };
167
+
168
+ render() {
169
+ return html`
170
+ <div
171
+ class="trigger-wrapper"
172
+ @click=${this._togglePopover}
173
+ @keydown=${(e: KeyboardEvent) =>
174
+ e.key === "Enter" || e.key === " " ? this._togglePopover() : null}
175
+ >
176
+ <slot name="trigger" @slotchange=${this._handleTriggerSlotChange}></slot>
177
+ </div>
178
+
179
+ <div id="popover-content" popover="auto" @toggle=${this._handleNativeToggle}>
180
+ <div class="popover-surface">
181
+ <slot name="content"></slot>
182
+ </div>
183
+ </div>
184
+ `;
185
+ }
186
+ }
187
+
188
+ declare global {
189
+ interface HTMLElementTagNameMap {
190
+ "st-popover": StPopover;
191
+ }
192
+ }
@@ -1,13 +1,15 @@
1
1
  import {LitElement, html, css} from "lit";
2
2
  import {customElement, property} from "lit/decorators.js";
3
+ import {ifDefined} from "lit/directives/if-defined.js";
3
4
  import {resetStyles} from "../../styles/reset.js";
4
5
 
5
6
  @customElement("st-radio")
6
7
  export class StRadio extends LitElement {
8
+ static shadowRootOptions = {...LitElement.shadowRootOptions, delegatesFocus: true};
9
+
7
10
  @property({type: String}) value = "";
8
11
  @property({type: Boolean, reflect: true}) checked = false;
9
12
  @property({type: Boolean, reflect: true}) disabled = false;
10
-
11
13
  @property({attribute: "aria-label"}) ariaLabel = "";
12
14
 
13
15
  static styles = [
@@ -28,7 +30,6 @@ export class StRadio extends LitElement {
28
30
  font-size: var(--st-font-size-base);
29
31
  }
30
32
 
31
- /* Hide the native input visually, but keep it accessible for screen readers */
32
33
  input {
33
34
  position: absolute;
34
35
  width: 1px;
@@ -41,7 +42,6 @@ export class StRadio extends LitElement {
41
42
  border-width: 0;
42
43
  }
43
44
 
44
- /* Custom Radio Circle */
45
45
  .control {
46
46
  display: flex;
47
47
  align-items: center;
@@ -55,45 +55,42 @@ export class StRadio extends LitElement {
55
55
  flex-shrink: 0;
56
56
  }
57
57
 
58
- /* Inner Dot (Hidden by default) */
59
58
  .control::after {
60
59
  content: "";
61
60
  width: 0.625rem;
62
61
  height: 0.625rem;
63
62
  border-radius: 50%;
64
- background-color: var(--st-color-bg); /* Matches the dot color to background initially */
63
+ background-color: var(--st-color-bg);
65
64
  transition:
66
65
  transform 0.2s ease,
67
66
  background-color 0.2s ease;
68
67
  transform: scale(0);
69
68
  }
70
69
 
71
- /* Hover State */
72
70
  label:hover .control {
73
71
  border-color: var(--st-color-primary-hover);
74
72
  }
75
73
 
76
- /* Focus State (Driven by the hidden input) */
77
74
  input:focus-visible + .control {
78
75
  box-shadow:
79
76
  0 0 0 2px var(--st-color-bg),
80
77
  0 0 0 4px var(--st-color-primary);
81
78
  }
82
79
 
83
- /* Checked State */
84
80
  :host([checked]) .control {
85
81
  border-color: var(--st-color-primary);
86
82
  background-color: var(--st-color-primary);
87
83
  }
84
+
88
85
  :host([checked]) .control::after {
89
- background-color: var(--st-white); /* White dot inside the primary circle */
86
+ background-color: #ffffff; /* Explicitly white for contrast */
90
87
  transform: scale(1);
91
88
  }
92
89
 
93
- /* Disabled State */
94
90
  :host([disabled]) {
95
91
  pointer-events: none;
96
92
  }
93
+
97
94
  :host([disabled]) label {
98
95
  opacity: 0.5;
99
96
  cursor: not-allowed;
@@ -102,11 +99,9 @@ export class StRadio extends LitElement {
102
99
  ];
103
100
 
104
101
  private _handleChange(e: Event) {
105
- // Prevent the native change event from leaking out
106
102
  e.stopPropagation();
107
103
 
108
104
  if (!this.disabled) {
109
- // Dispatch a custom event to tell the parent group this radio was selected
110
105
  this.dispatchEvent(
111
106
  new CustomEvent("st-radio-select", {
112
107
  detail: {value: this.value},
@@ -122,7 +117,7 @@ export class StRadio extends LitElement {
122
117
  <label>
123
118
  <input
124
119
  type="radio"
125
- aria-label=${this.ariaLabel || ""}
120
+ aria-label=${ifDefined(this.ariaLabel || undefined)}
126
121
  .value=${this.value}
127
122
  .checked=${this.checked}
128
123
  ?disabled=${this.disabled}
@@ -12,23 +12,23 @@ export class StRadioGroup extends LitElement {
12
12
  @property({type: String}) value = "";
13
13
  @property({type: String}) label = "";
14
14
  @property({attribute: "help-text"}) helpText = "";
15
+ @property({type: String, reflect: true}) layout: "vertical" | "horizontal" = "vertical";
16
+ @property({type: Boolean, reflect: true}) required = false;
15
17
  @property({type: Boolean, reflect: true}) disabled = false;
16
18
  @property({type: Boolean, reflect: true}) invalid = false;
17
19
 
18
- // Grabs all slotted st-radio components automatically
19
20
  @queryAssignedElements({selector: "st-radio"})
20
21
  private _radios!: StRadio[];
21
22
 
22
23
  constructor() {
23
24
  super();
24
25
  this._internals = this.attachInternals();
26
+ this._internals.role = "radiogroup";
25
27
 
26
- // 1. Trigger error state if the user clicks away and the field is invalid
27
28
  this.addEventListener("change", () => {
28
29
  this.invalid = !this._internals.validity.valid;
29
30
  });
30
31
 
31
- // 2. Trigger error state if the st-form tries to submit and fails
32
32
  this.addEventListener("invalid", () => {
33
33
  this.invalid = true;
34
34
  });
@@ -64,7 +64,6 @@ export class StRadioGroup extends LitElement {
64
64
  gap: var(--st-spacing-sm);
65
65
  }
66
66
 
67
- /* Horizontal layout modifier */
68
67
  :host([layout="horizontal"]) .options {
69
68
  flex-direction: row;
70
69
  gap: var(--st-spacing-md);
@@ -76,36 +75,40 @@ export class StRadioGroup extends LitElement {
76
75
  margin: 0;
77
76
  }
78
77
 
79
- :host([invalid]) .help-text {
80
- color: var(--st-color-error);
81
- }
78
+ :host([invalid]) .help-text,
82
79
  :host([invalid]) legend {
83
80
  color: var(--st-color-error);
84
81
  }
85
82
  `,
86
83
  ];
87
84
 
85
+ private _handleRadioSelect = (e: Event) => {
86
+ if (this.disabled) return;
87
+
88
+ const customEvent = e as CustomEvent<{value: string}>;
89
+ this.value = customEvent.detail.value;
90
+
91
+ // 🚀 THE FIX: Clear the invalid visual state as soon as the user makes a selection
92
+ if (this.invalid) {
93
+ this.invalid = false;
94
+ }
95
+
96
+ this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
97
+ };
98
+
88
99
  connectedCallback() {
89
100
  super.connectedCallback();
90
- // Listen for selections from child radios
91
- this.addEventListener("st-radio-select", this._handleRadioSelect as EventListener);
101
+ this.addEventListener("st-radio-select", this._handleRadioSelect);
92
102
  }
93
103
 
94
104
  disconnectedCallback() {
95
105
  super.disconnectedCallback();
96
- this.removeEventListener("st-radio-select", this._handleRadioSelect as EventListener);
97
- }
98
-
99
- private _handleRadioSelect(e: CustomEvent<{value: string}>) {
100
- if (this.disabled) return;
101
- this.value = e.detail.value;
102
- this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
106
+ this.removeEventListener("st-radio-select", this._handleRadioSelect);
103
107
  }
104
108
 
105
109
  override updated(changedProperties: PropertyValues<this>) {
106
110
  super.updated(changedProperties);
107
111
 
108
- // When the group's value changes, sync the form and update all child radios
109
112
  if (changedProperties.has("value")) {
110
113
  this._internals.setFormValue(this.value);
111
114
  this._syncRadios();
@@ -114,9 +117,20 @@ export class StRadioGroup extends LitElement {
114
117
  if (changedProperties.has("disabled")) {
115
118
  this._syncRadios();
116
119
  }
120
+
121
+ if (changedProperties.has("value") || changedProperties.has("required")) {
122
+ this._syncValidity();
123
+ }
124
+ }
125
+
126
+ private _syncValidity() {
127
+ if (this.required && !this.value) {
128
+ this._internals.setValidity({valueMissing: true}, "Please select an option.");
129
+ } else {
130
+ this._internals.setValidity({});
131
+ }
117
132
  }
118
133
 
119
- // Loops through children and unchecks/checks them based on the group's current value
120
134
  private _syncRadios() {
121
135
  if (!this._radios) return;
122
136
 
@@ -128,11 +142,21 @@ export class StRadioGroup extends LitElement {
128
142
  });
129
143
  }
130
144
 
131
- // Fired natively by the browser if the parent <form> is reset
132
145
  formResetCallback() {
133
- this.value = "";
134
- this._internals.setFormValue("");
146
+ const initialValue = this.getAttribute("value") || "";
147
+ this.value = initialValue;
148
+ this._internals.setFormValue(initialValue);
135
149
  this._syncRadios();
150
+ this.updateComplete.then(() => this._syncValidity());
151
+ }
152
+
153
+ // Expose standard native methods
154
+ checkValidity() {
155
+ return this._internals.checkValidity();
156
+ }
157
+
158
+ reportValidity() {
159
+ return this._internals.reportValidity();
136
160
  }
137
161
 
138
162
  render() {
@@ -1,6 +1,6 @@
1
1
  export interface RouteDefinition {
2
2
  component: string;
3
- [key: string]: unknown; // Allows consuming apps to attach custom metadata (e.g., titles, auth roles)
3
+ [key: string]: unknown;
4
4
  }
5
5
 
6
6
  export type RouteConfig = Record<string, RouteDefinition>;
@@ -13,85 +13,105 @@ export interface RouteMatch {
13
13
  }
14
14
 
15
15
  function getRouteWeight(pattern: string): number {
16
- const segments = pattern.split('/').filter(Boolean);
16
+ const segments = pattern.split("/").filter(Boolean);
17
17
  return segments.reduce((weight, seg) => {
18
- return weight + (seg.startsWith(':') ? 1 : 10);
18
+ return weight + (seg.startsWith(":") ? 1 : 10);
19
19
  }, 0);
20
20
  }
21
21
 
22
22
  export class AppRouter {
23
23
  private _routes: RouteConfig = {};
24
24
  private _initialized = false;
25
+ private _boundNotify: () => void;
26
+ private _boundClick: (e: MouseEvent) => void;
25
27
 
26
28
  constructor(routes?: RouteConfig) {
29
+ this._boundNotify = () => this._notify();
30
+ this._boundClick = (e: MouseEvent) => this._handleClick(e);
31
+
27
32
  if (routes) {
28
33
  this.setRoutes(routes);
29
34
  }
30
35
 
31
- window.addEventListener('popstate', () => this._notify());
32
-
33
- document.addEventListener('click', (e) => {
34
- if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
35
- return;
36
- }
37
-
38
- const path = e.composedPath();
39
- const link = path.find((el: any) =>
40
- el && typeof el.hasAttribute === 'function' && el.hasAttribute('href')
41
- ) as HTMLElement | undefined;
36
+ // 🚀 FIX: SSR Safety check. Prevents crashes during Node.js CI test imports.
37
+ if (typeof window !== "undefined") {
38
+ window.addEventListener("popstate", this._boundNotify);
39
+ document.addEventListener("click", this._boundClick as EventListener);
40
+ }
41
+ }
42
42
 
43
- if (!link) return;
43
+ /**
44
+ * 🚀 FIX: Removes global listeners to prevent memory leaks during testing/teardown.
45
+ */
46
+ public destroy() {
47
+ if (typeof window !== "undefined") {
48
+ window.removeEventListener("popstate", this._boundNotify);
49
+ document.removeEventListener("click", this._boundClick as EventListener);
50
+ }
51
+ }
44
52
 
45
- const href = link.getAttribute('href');
46
- const target = link.getAttribute('target');
53
+ private _handleClick(e: MouseEvent) {
54
+ if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
55
+ return;
56
+ }
47
57
 
48
- if (!href || href.startsWith('http') || href.startsWith('mailto:') || target === '_blank') {
49
- return;
50
- }
58
+ const path = e.composedPath();
59
+ const link = path.find(
60
+ (el: any) => el && typeof el.hasAttribute === "function" && el.hasAttribute("href"),
61
+ ) as HTMLElement | undefined;
62
+
63
+ if (!link) return;
64
+
65
+ const href = link.getAttribute("href");
66
+ const target = link.getAttribute("target");
67
+
68
+ // 🚀 FIX: Ignore links with the 'download' attribute so file downloads aren't hijacked
69
+ if (
70
+ !href ||
71
+ href.startsWith("http") ||
72
+ href.startsWith("mailto:") ||
73
+ target === "_blank" ||
74
+ link.hasAttribute("download")
75
+ ) {
76
+ return;
77
+ }
51
78
 
52
- if (href.startsWith('/')) {
53
- e.preventDefault();
54
- this.navigate(href);
55
- }
56
- });
79
+ if (href.startsWith("/")) {
80
+ e.preventDefault();
81
+ this.navigate(href);
82
+ }
57
83
  }
58
84
 
59
- /**
60
- * Register or update the route configuration for this application.
61
- */
62
85
  public setRoutes(routes: RouteConfig) {
63
86
  this._routes = routes;
64
87
 
65
- // Trigger initial route resolution once routes are registered
66
88
  if (!this._initialized) {
67
89
  this._initialized = true;
68
- setTimeout(() => this._notify(), 0);
90
+ if (typeof window !== "undefined") {
91
+ setTimeout(() => this._notify(), 0);
92
+ }
69
93
  } else {
70
94
  this._notify();
71
95
  }
72
96
  }
73
97
 
74
- /**
75
- * Programmatically navigate to an internal path.
76
- */
77
98
  public navigate(path: string) {
78
- window.history.pushState(null, '', path);
99
+ if (typeof window !== "undefined") {
100
+ window.history.pushState(null, "", path);
101
+ }
79
102
  this._notify();
80
103
  }
81
104
 
82
- /**
83
- * Matches a URL path against the registered routes.
84
- */
85
105
  public matchRoute(currentPath: string): RouteMatch | null {
86
106
  const sortedRoutes = Object.entries(this._routes).sort(
87
- ([patternA], [patternB]) => getRouteWeight(patternB) - getRouteWeight(patternA)
107
+ ([patternA], [patternB]) => getRouteWeight(patternB) - getRouteWeight(patternA),
88
108
  );
89
109
 
90
110
  for (const [pattern, routeDef] of sortedRoutes) {
91
111
  const paramNames: string[] = [];
92
112
  const regexPath = pattern.replace(/:([a-zA-Z0-9_]+)/g, (_, paramName) => {
93
113
  paramNames.push(paramName);
94
- return '([^/]+)';
114
+ return "([^/]+)";
95
115
  });
96
116
 
97
117
  const regex = new RegExp(`^${regexPath}$`);
@@ -107,7 +127,7 @@ export class AppRouter {
107
127
  path: currentPath,
108
128
  component: routeDef.component,
109
129
  params,
110
- definition: routeDef
130
+ definition: routeDef,
111
131
  };
112
132
  }
113
133
  }
@@ -116,19 +136,22 @@ export class AppRouter {
116
136
  }
117
137
 
118
138
  private _notify() {
139
+ if (typeof window === "undefined") return;
140
+
119
141
  const currentPath = window.location.pathname;
120
142
  const match = this.matchRoute(currentPath);
121
143
 
122
- window.dispatchEvent(new CustomEvent('route-changed', {
123
- detail: {
124
- path: currentPath,
125
- component: match ? match.component : 'not-found-page',
126
- params: match ? match.params : {},
127
- definition: match ? match.definition : null
128
- }
129
- }));
144
+ window.dispatchEvent(
145
+ new CustomEvent("route-changed", {
146
+ detail: {
147
+ path: currentPath,
148
+ component: match ? match.component : "not-found-page",
149
+ params: match ? match.params : {},
150
+ definition: match ? match.definition : null,
151
+ },
152
+ }),
153
+ );
130
154
  }
131
155
  }
132
156
 
133
- // Export a singleton instance for standard app-wide use
134
- export const router = new AppRouter();
157
+ export const router = new AppRouter();