@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,6 +1,6 @@
1
1
  import {LitElement, html, css, PropertyValues} from "lit";
2
2
  import {customElement, property, query} from "lit/decorators.js";
3
- import {ifDefined} from "lit/directives/if-defined.js"; // Helper for optional attributes
3
+ import {ifDefined} from "lit/directives/if-defined.js";
4
4
  import {resetStyles} from "../../styles/reset.js";
5
5
 
6
6
  export type InputType = "text" | "password" | "email" | "number" | "search" | "url";
@@ -8,6 +8,7 @@ export type InputType = "text" | "password" | "email" | "number" | "search" | "u
8
8
  @customElement("st-input")
9
9
  export class StInput extends LitElement {
10
10
  static formAssociated = true;
11
+ static shadowRootOptions = {...LitElement.shadowRootOptions, delegatesFocus: true};
11
12
  private _internals: ElementInternals;
12
13
 
13
14
  @query("input") private _inputEl!: HTMLInputElement;
@@ -17,8 +18,9 @@ export class StInput extends LitElement {
17
18
  @property({type: String}) placeholder = "";
18
19
  @property({type: String}) type: InputType = "text";
19
20
  @property({attribute: "help-text"}) helpText = "";
21
+ @property({type: String}) error = "";
22
+ @property({ type: String }) autocomplete?: string;
20
23
 
21
- // Validation Properties
22
24
  @property({type: Boolean, reflect: true}) required = false;
23
25
  @property({type: Number}) minlength?: number;
24
26
  @property({type: Number}) maxlength?: number;
@@ -32,12 +34,10 @@ export class StInput extends LitElement {
32
34
  super();
33
35
  this._internals = this.attachInternals();
34
36
 
35
- // 1. Trigger error state if the user clicks away and the field is invalid
36
37
  this.addEventListener("focusout", () => {
37
- this.invalid = !this._inputEl.validity.valid;
38
+ this.invalid = !!this.error || !this._inputEl.validity.valid;
38
39
  });
39
40
 
40
- // 2. Trigger error state if the st-form tries to submit and fails
41
41
  this.addEventListener("invalid", () => {
42
42
  this.invalid = true;
43
43
  });
@@ -61,41 +61,34 @@ export class StInput extends LitElement {
61
61
  font-weight: var(--st-font-weight-medium);
62
62
  color: var(--st-color-text);
63
63
  margin-bottom: 0.5em;
64
+ cursor: pointer;
64
65
  }
65
66
 
66
- /* 1. The Wrapper acts as the visual input field */
67
67
  .input-wrapper {
68
68
  display: flex;
69
69
  align-items: center;
70
70
  gap: var(--st-spacing-sm);
71
71
  width: 100%;
72
-
73
72
  background-color: var(--st-color-bg);
74
73
  border: 1px solid var(--st-color-border);
75
74
  border-radius: 4px;
76
-
77
- /* Horizontal padding on the wrapper, vertical on the input */
78
75
  padding: 0 var(--st-spacing-md);
79
76
  transition:
80
77
  border-color 0.2s ease,
81
78
  box-shadow 0.2s ease;
82
79
  }
83
80
 
84
- /* 2. Apply focus ring to the wrapper when the internal input is focused */
85
81
  .input-wrapper:focus-within {
86
82
  border-color: var(--st-color-primary);
87
83
  box-shadow: 0 0 0 1px var(--st-color-primary);
88
84
  }
89
85
 
90
- /* 3. The actual input becomes transparent and fills the wrapper */
91
86
  input {
92
87
  flex: 1;
93
88
  width: 100%;
94
89
  border: none;
95
90
  background: transparent;
96
91
  outline: none;
97
-
98
- /* Vertical padding stretches the wrapper */
99
92
  padding: var(--st-spacing-sm) 0;
100
93
  font-size: var(--st-font-size-base);
101
94
  color: var(--st-color-text);
@@ -105,92 +98,98 @@ export class StInput extends LitElement {
105
98
  color: var(--st-color-text-muted);
106
99
  }
107
100
 
108
- /* Slotted Icons */
109
101
  ::slotted(svg) {
110
102
  width: 1.25em;
111
103
  height: 1.25em;
112
- /* Default icons to the muted text color */
113
104
  color: var(--st-color-text-muted);
114
105
  fill: currentColor;
115
106
  flex-shrink: 0;
116
107
  }
117
108
 
118
- /* Interactive Slotted Elements (like clear buttons) */
119
109
  ::slotted(st-button) {
120
- /* Pull the button back slightly so it aligns nicely with the input padding */
121
110
  margin-right: calc(var(--st-spacing-sm) * -1);
122
111
  }
123
112
 
124
- /* Invalid State */
113
+ .help-text {
114
+ font-size: var(--st-font-size-xs);
115
+ color: var(--st-color-text-muted);
116
+ margin: 0;
117
+ }
118
+
119
+ /* 🚀 NEW: Explicit error text styling */
120
+ .error-text {
121
+ font-size: var(--st-font-size-xs);
122
+ color: var(--st-color-error, #dc2626);
123
+ margin: 0;
124
+ font-weight: 500;
125
+ }
126
+
125
127
  :host([invalid]) .input-wrapper {
126
- border-color: var(--st-color-error);
128
+ border-color: var(--st-color-error, #dc2626);
127
129
  }
130
+
128
131
  :host([invalid]) .input-wrapper:focus-within {
129
- box-shadow: 0 0 0 1px var(--st-color-error);
132
+ box-shadow: 0 0 0 1px var(--st-color-error, #dc2626);
130
133
  }
134
+
131
135
  :host([invalid]) .help-text {
132
- color: var(--st-color-error);
136
+ color: var(--st-color-error, #dc2626);
133
137
  }
134
138
 
135
- /* Disabled & Readonly States */
136
139
  :host([disabled]) .input-wrapper {
137
140
  opacity: 0.5;
138
141
  cursor: not-allowed;
139
142
  background-color: var(--st-color-surface);
140
143
  }
144
+
141
145
  :host([disabled]) input {
142
146
  cursor: not-allowed;
143
147
  }
148
+
144
149
  :host([readonly]) .input-wrapper {
145
150
  background-color: var(--st-color-surface);
146
151
  }
147
-
148
- .help-text {
149
- font-size: var(--st-font-size-xs);
150
- color: var(--st-color-text-muted);
151
- margin: 0;
152
- }
153
-
154
- :host([invalid]) .input-wrapper {
155
- border-color: var(--st-color-error);
156
- }
157
- :host([invalid]) .help-text {
158
- color: var(--st-color-error);
159
- }
160
152
  `,
161
153
  ];
162
154
 
163
155
  override updated(changedProperties: PropertyValues<this>) {
164
156
  super.updated(changedProperties);
165
157
 
166
- // Sync the value to the form engine
167
158
  if (changedProperties.has("value")) {
168
159
  this._internals.setFormValue(this.value);
169
160
  }
170
-
171
- // Sync validity whenever properties change (like value or required status)
172
- this._syncValidity();
161
+
162
+ if (changedProperties.has("value") || changedProperties.has("error") || changedProperties.has("required")) {
163
+ this._syncValidity();
164
+ }
173
165
  }
174
166
 
175
167
  private _syncValidity() {
176
168
  if (!this._inputEl) return;
169
+
170
+ if (this.error) {
171
+ this._internals.setValidity({ customError: true }, this.error, this._inputEl);
172
+ this.invalid = true;
173
+ return;
174
+ }
177
175
 
178
- // setValidity arguments: (ValidityState object, Custom Error Message, Anchor Element for Tooltip)
179
176
  this._internals.setValidity(
180
177
  this._inputEl.validity,
181
178
  this._inputEl.validationMessage,
182
179
  this._inputEl,
183
180
  );
181
+
182
+ if (this.invalid && this._inputEl.validity.valid) {
183
+ this.invalid = false;
184
+ }
184
185
  }
185
186
 
186
187
  formResetCallback() {
187
188
  this.value = "";
188
189
  this._internals.setFormValue("");
189
- // Need to wait for the render cycle to finish before checking validity on reset
190
190
  this.updateComplete.then(() => this._syncValidity());
191
191
  }
192
192
 
193
- // Expose standard native methods so external scripts can call them on the component
194
193
  checkValidity() {
195
194
  return this._internals.checkValidity();
196
195
  }
@@ -200,10 +199,10 @@ export class StInput extends LitElement {
200
199
  }
201
200
 
202
201
  private _handleInput(e: Event) {
203
- const target = e.target as HTMLTextAreaElement;
202
+ const target = e.target as HTMLInputElement;
204
203
  this.value = target.value;
205
204
 
206
- if (this.invalid && target.validity.valid) {
205
+ if (this.invalid && target.validity.valid && !this.error) {
207
206
  this.invalid = false;
208
207
  }
209
208
 
@@ -215,6 +214,12 @@ export class StInput extends LitElement {
215
214
  }
216
215
 
217
216
  render() {
217
+ const descriptionId = this.error ? "error-text" : (this.helpText ? "help-text" : undefined);
218
+
219
+ const isSensitive = this.type === "password" || this.type === "email";
220
+ const safeSpellcheck = isSensitive ? "false" : undefined;
221
+ const safeAutocapitalize = isSensitive ? "none" : undefined;
222
+
218
223
  return html`
219
224
  <div class="form-control">
220
225
  ${this.label ? html`<label for="input">${this.label}</label>` : ""}
@@ -234,6 +239,11 @@ export class StInput extends LitElement {
234
239
  minlength=${ifDefined(this.minlength)}
235
240
  maxlength=${ifDefined(this.maxlength)}
236
241
  pattern=${ifDefined(this.pattern)}
242
+ autocomplete=${ifDefined(this.autocomplete as any)}
243
+ spellcheck=${ifDefined(safeSpellcheck)}
244
+ autocapitalize=${ifDefined(safeAutocapitalize)}
245
+ aria-invalid=${this.invalid ? "true" : "false"}
246
+ aria-describedby=${ifDefined(descriptionId)}
237
247
  @input=${this._handleInput}
238
248
  @change=${this._handleChange}
239
249
  />
@@ -241,7 +251,11 @@ export class StInput extends LitElement {
241
251
  <slot name="suffix"></slot>
242
252
  </div>
243
253
 
244
- ${this.helpText ? html`<div class="help-text">${this.helpText}</div>` : ""}
254
+ ${this.error
255
+ ? html`<div id="error-text" class="error-text">${this.error}</div>`
256
+ : this.helpText
257
+ ? html`<div id="help-text" class="help-text">${this.helpText}</div>`
258
+ : ""}
245
259
  </div>
246
260
  `;
247
261
  }
@@ -1,21 +1,23 @@
1
- import { LitElement, html, css } from 'lit';
2
- import { customElement, property } from 'lit/decorators.js';
3
- import { resetStyles } from '../../styles/reset.js';
1
+ import {LitElement, html, css} from "lit";
2
+ import {customElement, property} from "lit/decorators.js";
3
+ import {ifDefined} from "lit/directives/if-defined.js";
4
+ import {resetStyles} from "../../styles/reset.js";
4
5
 
5
- @customElement('st-link')
6
+ @customElement("st-link")
6
7
  export class StLink extends LitElement {
7
- @property({ type: String }) href = '#';
8
- @property({ type: String }) target: '_blank' | '_self' = '_self';
9
- @property({ type: String, reflect: true }) size: 'sm' | 'md' | 'lg' = 'md';
10
- @property({ type: String, reflect: true }) variant: 'inline' | 'standalone' = 'inline';
11
- @property({ type: String, reflect: true }) color: 'default' | 'primary' | 'muted' = 'primary';
8
+ @property({type: String}) href = "#";
9
+ @property({type: String}) target: "_blank" | "_self" = "_self";
10
+ @property({type: String, reflect: true}) size: "sm" | "md" | "lg" = "md";
11
+ @property({type: String, reflect: true}) variant: "inline" | "standalone" = "inline";
12
+ @property({type: String, reflect: true}) color: "default" | "primary" | "muted" = "primary";
12
13
 
13
14
  static styles = [
14
15
  resetStyles,
15
16
  css`
16
17
  :host {
17
18
  display: inline-flex;
18
- align-items: center;
19
+ align-items: baseline; /* Ensures it sits perfectly inside a line of text */
20
+ vertical-align: baseline;
19
21
  }
20
22
 
21
23
  a {
@@ -24,7 +26,17 @@ export class StLink extends LitElement {
24
26
  gap: 0.25em;
25
27
  text-decoration: none;
26
28
  font-weight: 500;
27
- transition: color 0.2s ease, opacity 0.2s ease;
29
+ transition:
30
+ color 0.2s ease,
31
+ opacity 0.2s ease,
32
+ text-decoration-thickness 0.2s ease;
33
+ }
34
+
35
+ /* Accessibility: Clear focus indicators for keyboard navigation */
36
+ a:focus-visible {
37
+ outline: 2px solid var(--st-color-primary);
38
+ outline-offset: 2px;
39
+ border-radius: 2px;
28
40
  }
29
41
 
30
42
  /* ==========================================================
@@ -54,24 +66,28 @@ export class StLink extends LitElement {
54
66
  /* ==========================================================
55
67
  Sizes
56
68
  ========================================================== */
57
- :host([size="sm"]) a { font-size: 0.875rem; }
58
- :host([size="md"]) a { font-size: 1rem; }
59
- :host([size="lg"]) a { font-size: 1.125rem; }
69
+ :host([size="sm"]) a {
70
+ font-size: 0.875rem;
71
+ }
72
+ :host([size="md"]) a {
73
+ font-size: 1rem;
74
+ }
75
+ :host([size="lg"]) a {
76
+ font-size: 1.125rem;
77
+ }
60
78
 
61
79
  /* ==========================================================
62
80
  Variants
63
81
  ========================================================== */
64
- /* inline links live inside paragraphs and underline on hover */
65
82
  :host([variant="inline"]) a {
66
83
  text-decoration: underline;
67
84
  text-underline-offset: 4px;
68
- font-weight: inherit; /* Inherits from surrounding paragraph text */
85
+ font-weight: inherit;
69
86
  }
70
87
  :host([variant="inline"]) a:hover {
71
88
  text-decoration-thickness: 2px;
72
89
  }
73
90
 
74
- /* standalone links (e.g., footers, sidebars) have no underline by default */
75
91
  :host([variant="standalone"]) a {
76
92
  text-decoration: none;
77
93
  }
@@ -79,12 +95,20 @@ export class StLink extends LitElement {
79
95
  text-decoration: underline;
80
96
  text-underline-offset: 4px;
81
97
  }
82
- `
98
+ `,
83
99
  ];
84
100
 
85
101
  render() {
102
+ // SECURITY: Prevent XSS via javascript: URI injection
103
+ const sanitizedHref = this.href?.trim().toLowerCase().startsWith("javascript:")
104
+ ? "#blocked"
105
+ : this.href;
106
+
107
+ // SECURITY: Prevent Reverse Tabnabbing for external links
108
+ const secureRel = this.target === "_blank" ? "noopener noreferrer" : undefined;
109
+
86
110
  return html`
87
- <a href="${this.href}" target="${this.target}">
111
+ <a href="${sanitizedHref}" target="${this.target}" rel="${ifDefined(secureRel)}">
88
112
  <slot></slot>
89
113
  </a>
90
114
  `;
@@ -93,6 +117,6 @@ export class StLink extends LitElement {
93
117
 
94
118
  declare global {
95
119
  interface HTMLElementTagNameMap {
96
- 'st-link': StLink;
120
+ "st-link": StLink;
97
121
  }
98
- }
122
+ }
@@ -1,58 +1,60 @@
1
- import { LitElement, nothing } from 'lit';
2
- import { customElement, property } from 'lit/decorators.js';
1
+ import {LitElement, nothing, PropertyValues} from "lit";
2
+ import {customElement, property} from "lit/decorators.js";
3
3
 
4
- @customElement('st-metadata')
4
+ @customElement("st-metadata")
5
5
  export class StMetadata extends LitElement {
6
- @property({ type: String }) pageTitle = '';
7
- @property({ type: String }) description = '';
8
- @property({ type: String }) image = '';
6
+ @property({type: String}) pageTitle = "";
7
+ @property({type: String}) description = "";
8
+ @property({type: String}) image = "";
9
9
 
10
- protected createRenderRoot() { return this; }
10
+ protected createRenderRoot() {
11
+ return this;
12
+ }
11
13
 
12
- updated() {
14
+ // 🚀 Added strict PropertyValues typing and super call
15
+ override updated(changedProperties: PropertyValues<this>) {
16
+ super.updated(changedProperties);
13
17
  this._updateMetadata();
14
18
  }
15
19
 
16
20
  private _updateMetadata() {
17
- // Read site name from environment variable (e.g., VITE_SITE_NAME)
18
- const siteName = (import.meta as any).env?.VITE_SITE_NAME || '';
21
+ const siteName = (import.meta as any).env?.VITE_SITE_NAME || "";
19
22
 
20
23
  if (this.pageTitle) {
21
- // If site name is configured, append it; otherwise use the title standalone
22
24
  document.title = siteName ? `${this.pageTitle} - ${siteName}` : this.pageTitle;
23
- this._setMeta('property', 'og:title', this.pageTitle);
25
+ this._setMeta("property", "og:title", this.pageTitle);
24
26
  }
25
27
 
26
28
  if (this.description) {
27
- this._setMeta('name', 'description', this.description);
28
- this._setMeta('property', 'og:description', this.description);
29
+ this._setMeta("name", "description", this.description);
30
+ this._setMeta("property", "og:description", this.description);
29
31
  }
30
32
 
31
33
  if (this.image) {
32
- this._setMeta('property', 'og:image', this.image);
34
+ this._setMeta("property", "og:image", this.image);
33
35
  }
34
-
36
+
35
37
  this._setCanonical(window.location.href);
36
38
  }
37
39
 
38
- private _setMeta(attrName: 'name' | 'property', attrValue: string, content: string) {
40
+ private _setMeta(attrName: "name" | "property", attrValue: string, content: string) {
39
41
  let meta = document.head.querySelector(`meta[${attrName}="${attrValue}"]`);
40
42
  if (!meta) {
41
- meta = document.createElement('meta');
43
+ meta = document.createElement("meta");
42
44
  meta.setAttribute(attrName, attrValue);
43
45
  document.head.appendChild(meta);
44
46
  }
45
- meta.setAttribute('content', content);
47
+ meta.setAttribute("content", content);
46
48
  }
47
49
 
48
50
  private _setCanonical(url: string) {
49
51
  let link = document.head.querySelector('link[rel="canonical"]');
50
52
  if (!link) {
51
- link = document.createElement('link');
52
- link.setAttribute('rel', 'canonical');
53
+ link = document.createElement("link");
54
+ link.setAttribute("rel", "canonical");
53
55
  document.head.appendChild(link);
54
56
  }
55
- link.setAttribute('href', url);
57
+ link.setAttribute("href", url);
56
58
  }
57
59
 
58
60
  render() {
@@ -62,6 +64,6 @@ export class StMetadata extends LitElement {
62
64
 
63
65
  declare global {
64
66
  interface HTMLElementTagNameMap {
65
- 'st-metadata': StMetadata;
67
+ "st-metadata": StMetadata;
66
68
  }
67
- }
69
+ }