@sondor-technology/sondor-components 1.0.0 → 1.0.1-develop.3

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 (168) hide show
  1. package/README.md +8 -0
  2. package/dist/components/alert/alert.d.ts +13 -0
  3. package/dist/components/alert/alert.d.ts.map +1 -0
  4. package/dist/components/alert/alert.js +105 -0
  5. package/dist/components/badge/badge.d.ts +13 -0
  6. package/dist/components/badge/badge.d.ts.map +1 -0
  7. package/dist/components/badge/badge.js +101 -0
  8. package/dist/components/breadcrumb/breadcrumb.d.ts +19 -0
  9. package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -0
  10. package/dist/components/breadcrumb/breadcrumb.js +99 -0
  11. package/dist/components/button/button.d.ts +23 -0
  12. package/dist/components/button/button.d.ts.map +1 -0
  13. package/dist/components/button/button.js +304 -0
  14. package/dist/components/button-group/button-group.d.ts +6 -0
  15. package/dist/components/button-group/button-group.d.ts.map +1 -0
  16. package/dist/components/button-group/button-group.js +74 -0
  17. package/dist/components/card/card.d.ts +11 -0
  18. package/dist/components/card/card.d.ts.map +1 -0
  19. package/dist/components/card/card.js +54 -0
  20. package/dist/components/checkbox/checkbox.d.ts +28 -0
  21. package/dist/components/checkbox/checkbox.d.ts.map +1 -0
  22. package/dist/components/checkbox/checkbox.js +247 -0
  23. package/dist/components/code-block/code-block.d.ts +20 -0
  24. package/dist/components/code-block/code-block.d.ts.map +1 -0
  25. package/dist/components/code-block/code-block.js +260 -0
  26. package/dist/components/content-card/content-card.d.ts +13 -0
  27. package/dist/components/content-card/content-card.d.ts.map +1 -0
  28. package/dist/components/content-card/content-card.js +140 -0
  29. package/dist/components/divider/divider.d.ts +16 -0
  30. package/dist/components/divider/divider.d.ts.map +1 -0
  31. package/dist/components/divider/divider.js +82 -0
  32. package/dist/components/drawer/drawer.d.ts +22 -0
  33. package/dist/components/drawer/drawer.d.ts.map +1 -0
  34. package/dist/components/drawer/drawer.js +226 -0
  35. package/dist/components/field/field.d.ts +11 -0
  36. package/dist/components/field/field.d.ts.map +1 -0
  37. package/dist/components/field/field.js +32 -0
  38. package/dist/components/footer/footer.d.ts +6 -0
  39. package/dist/components/footer/footer.d.ts.map +1 -0
  40. package/dist/components/footer/footer.js +65 -0
  41. package/dist/components/form/form-step.d.ts +13 -0
  42. package/dist/components/form/form-step.d.ts.map +1 -0
  43. package/dist/components/form/form-step.js +41 -0
  44. package/dist/components/form/form.d.ts +25 -0
  45. package/dist/components/form/form.d.ts.map +1 -0
  46. package/dist/components/form/form.js +358 -0
  47. package/dist/components/grid/grid-row.d.ts +17 -0
  48. package/dist/components/grid/grid-row.d.ts.map +1 -0
  49. package/dist/components/grid/grid-row.js +92 -0
  50. package/dist/components/grid/grid.d.ts +13 -0
  51. package/dist/components/grid/grid.d.ts.map +1 -0
  52. package/dist/components/grid/grid.js +42 -0
  53. package/dist/components/icon/icon-paths.d.ts +132 -0
  54. package/dist/components/icon/icon-paths.d.ts.map +1 -0
  55. package/dist/components/icon/icon-paths.js +137 -0
  56. package/dist/components/icon/icon.d.ts +16 -0
  57. package/dist/components/icon/icon.d.ts.map +1 -0
  58. package/dist/components/icon/icon.js +95 -0
  59. package/dist/components/input/input.d.ts +31 -0
  60. package/dist/components/input/input.d.ts.map +1 -0
  61. package/dist/components/input/input.js +268 -0
  62. package/dist/components/link/link.d.ts +16 -0
  63. package/dist/components/link/link.d.ts.map +1 -0
  64. package/dist/components/link/link.js +116 -0
  65. package/dist/components/metadata/metadata.d.ts +18 -0
  66. package/dist/components/metadata/metadata.d.ts.map +1 -0
  67. package/dist/components/metadata/metadata.js +71 -0
  68. package/dist/components/modal/modal.d.ts +24 -0
  69. package/dist/components/modal/modal.d.ts.map +1 -0
  70. package/dist/components/modal/modal.js +163 -0
  71. package/dist/components/page-layout/page-layout.d.ts +46 -0
  72. package/dist/components/page-layout/page-layout.d.ts.map +1 -0
  73. package/dist/components/page-layout/page-layout.js +697 -0
  74. package/dist/components/radio/radio.d.ts +16 -0
  75. package/dist/components/radio/radio.d.ts.map +1 -0
  76. package/dist/components/radio/radio.js +152 -0
  77. package/dist/components/radio-group/radio-group.d.ts +27 -0
  78. package/dist/components/radio-group/radio-group.d.ts.map +1 -0
  79. package/dist/components/radio-group/radio-group.js +161 -0
  80. package/dist/components/router/router.d.ts +7 -0
  81. package/dist/components/router/router.d.ts.map +1 -0
  82. package/dist/components/router/router.js +37 -0
  83. package/dist/components/select/select.d.ts +16 -0
  84. package/dist/components/select/select.d.ts.map +1 -0
  85. package/dist/components/select/select.js +172 -0
  86. package/dist/components/skeleton/skeleton.d.ts +17 -0
  87. package/dist/components/skeleton/skeleton.d.ts.map +1 -0
  88. package/dist/components/skeleton/skeleton.js +78 -0
  89. package/dist/components/switch/switch.d.ts +25 -0
  90. package/dist/components/switch/switch.d.ts.map +1 -0
  91. package/dist/components/switch/switch.js +185 -0
  92. package/dist/components/table/data-table.d.ts +72 -0
  93. package/dist/components/table/data-table.d.ts.map +1 -0
  94. package/dist/components/table/data-table.js +551 -0
  95. package/dist/components/table/table.d.ts +22 -0
  96. package/dist/components/table/table.d.ts.map +1 -0
  97. package/dist/components/table/table.js +167 -0
  98. package/dist/components/tabs/tab-panel.d.ts +8 -0
  99. package/dist/components/tabs/tab-panel.d.ts.map +1 -0
  100. package/dist/components/tabs/tab-panel.js +46 -0
  101. package/dist/components/tabs/tab.d.ts +9 -0
  102. package/dist/components/tabs/tab.d.ts.map +1 -0
  103. package/dist/components/tabs/tab.js +91 -0
  104. package/dist/components/tabs/tabs.d.ts +14 -0
  105. package/dist/components/tabs/tabs.d.ts.map +1 -0
  106. package/dist/components/tabs/tabs.js +85 -0
  107. package/dist/components/textarea/textarea.d.ts +36 -0
  108. package/dist/components/textarea/textarea.d.ts.map +1 -0
  109. package/dist/components/textarea/textarea.js +231 -0
  110. package/dist/components/theme-toggle/theme-toggle.d.ts +15 -0
  111. package/dist/components/theme-toggle/theme-toggle.d.ts.map +1 -0
  112. package/dist/components/theme-toggle/theme-toggle.js +130 -0
  113. package/dist/components/vertical-nav/vertical-nav-group.d.ts +10 -0
  114. package/dist/components/vertical-nav/vertical-nav-group.d.ts.map +1 -0
  115. package/dist/components/vertical-nav/vertical-nav-group.js +127 -0
  116. package/dist/components/vertical-nav/vertical-nav-item.d.ts +9 -0
  117. package/dist/components/vertical-nav/vertical-nav-item.d.ts.map +1 -0
  118. package/dist/components/vertical-nav/vertical-nav-item.js +85 -0
  119. package/dist/styles/index.css +171 -0
  120. package/dist/styles/reset.d.ts +2 -0
  121. package/dist/styles/reset.d.ts.map +1 -0
  122. package/dist/styles/reset.js +50 -0
  123. package/package.json +7 -2
  124. package/src/components/alert/alert.ts +100 -100
  125. package/src/components/badge/badge.ts +96 -96
  126. package/src/components/breadcrumb/breadcrumb.ts +98 -98
  127. package/src/components/button/button.ts +281 -281
  128. package/src/components/button-group/button-group.ts +67 -67
  129. package/src/components/card/card.ts +52 -52
  130. package/src/components/checkbox/checkbox.ts +238 -238
  131. package/src/components/code-block/code-block.ts +256 -256
  132. package/src/components/content-card/content-card.ts +133 -133
  133. package/src/components/divider/divider.ts +68 -68
  134. package/src/components/drawer/drawer.ts +217 -217
  135. package/src/components/field/field.ts +30 -30
  136. package/src/components/footer/footer.ts +58 -58
  137. package/src/components/form/form-step.ts +32 -32
  138. package/src/components/form/form.ts +376 -376
  139. package/src/components/grid/grid-row.ts +75 -75
  140. package/src/components/grid/grid.ts +36 -36
  141. package/src/components/icon/icon-paths.ts +140 -124
  142. package/src/components/icon/icon.ts +82 -82
  143. package/src/components/input/input.ts +248 -248
  144. package/src/components/link/link.ts +97 -97
  145. package/src/components/metadata/metadata.ts +66 -66
  146. package/src/components/modal/modal.ts +158 -158
  147. package/src/components/page-layout/page-layout.ts +704 -704
  148. package/src/components/radio/radio.ts +142 -142
  149. package/src/components/radio-group/radio-group.ts +157 -157
  150. package/src/components/router/router.ts +46 -46
  151. package/src/components/select/select.ts +161 -161
  152. package/src/components/skeleton/skeleton.ts +67 -67
  153. package/src/components/switch/switch.ts +181 -181
  154. package/src/components/table/data-table.ts +556 -556
  155. package/src/components/table/table.ts +153 -153
  156. package/src/components/tabs/tab-panel.ts +30 -30
  157. package/src/components/tabs/tab.ts +76 -76
  158. package/src/components/tabs/tabs.ts +88 -88
  159. package/src/components/textarea/textarea.ts +215 -215
  160. package/src/components/theme-toggle/theme-toggle.ts +131 -131
  161. package/src/components/vertical-nav/vertical-nav-group.ts +109 -96
  162. package/src/components/vertical-nav/vertical-nav-item.ts +66 -49
  163. package/src/styles/globals.css +58 -58
  164. package/src/styles/index.css +80 -80
  165. package/src/styles/reset.ts +50 -50
  166. package/src/styles/theme.css +58 -58
  167. package/src/styles/typography.css +25 -25
  168. package/tsconfig.json +15 -15
@@ -1,83 +1,83 @@
1
- import { LitElement, html, css } from 'lit';
2
- import { customElement, property } from 'lit/decorators.js';
3
- import { iconPaths, IconName } from './icon-paths.js';
4
- import { resetStyles } from '../../styles/reset.js';
5
-
6
- @customElement('st-icon')
7
- export class StIcon extends LitElement {
8
- @property({ type: String }) name: IconName | string = '';
9
- @property({ type: String }) variant: 'outline' | 'solid' = 'outline';
10
-
11
- // Semantic Colors
12
- @property({ type: String, reflect: true }) color: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' = 'default';
13
-
14
- // Standardized T-Shirt Sizes
15
- @property({ type: String, reflect: true }) size: 'sm' | 'md' | 'lg' | 'xl' = 'md';
16
-
17
- static styles = [
18
- resetStyles,
19
- css`
20
- :host {
21
- display: inline-flex;
22
- align-items: center;
23
- justify-content: center;
24
- color: inherit;
25
-
26
- /* Defaults to md (1.5rem) if no size attribute is matched */
27
- width: var(--icon-size, 1.5rem);
28
- height: var(--icon-size, 1.5rem);
29
- }
30
-
31
- /* ==========================================================
32
- Size Variants
33
- ========================================================== */
34
- :host([size="sm"]) { --icon-size: 1.25rem; } /* 20px - Matches small buttons/inputs */
35
- :host([size="md"]) { --icon-size: 1.5rem; } /* 24px - Default standard size */
36
- :host([size="lg"]) { --icon-size: 2rem; } /* 32px - Large callouts */
37
- :host([size="xl"]) { --icon-size: 2.5rem; } /* 40px - Hero sections/Cards */
38
-
39
- /* ==========================================================
40
- Color Variants
41
- ========================================================== */
42
- :host([color="primary"]) { color: var(--st-color-primary); }
43
- :host([color="secondary"]) { color: var(--st-color-secondary); }
44
- :host([color="success"]) { color: var(--st-color-success); }
45
- :host([color="warning"]) { color: var(--st-color-warning); }
46
- :host([color="error"]) { color: var(--st-color-error); }
47
- :host([color="info"]) { color: var(--st-color-info); }
48
-
49
- svg {
50
- width: 100%;
51
- height: 100%;
52
- display: block;
53
- }
54
- `
55
- ];
56
-
57
- render() {
58
- const iconData = (iconPaths as Record<string, any>)[this.name];
59
- if (!iconData) return html``;
60
-
61
- const pathData = iconData[this.variant] || iconData.outline;
62
-
63
- return html`
64
- <svg
65
- viewBox="0 0 24 24"
66
- xmlns="http://www.w3.org/2000/svg"
67
- fill="${this.variant === 'solid' ? 'currentColor' : 'none'}"
68
- stroke="${this.variant === 'outline' ? 'currentColor' : 'none'}"
69
- stroke-width="${this.variant === 'outline' ? '2' : '0'}"
70
- stroke-linecap="round"
71
- stroke-linejoin="round"
72
- >
73
- ${pathData}
74
- </svg>
75
- `;
76
- }
77
- }
78
-
79
- declare global {
80
- interface HTMLElementTagNameMap {
81
- 'st-icon': StIcon;
82
- }
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { iconPaths, IconName } from './icon-paths.js';
4
+ import { resetStyles } from '../../styles/reset.js';
5
+
6
+ @customElement('st-icon')
7
+ export class StIcon extends LitElement {
8
+ @property({ type: String }) name: IconName | string = '';
9
+ @property({ type: String }) variant: 'outline' | 'solid' = 'outline';
10
+
11
+ // Semantic Colors
12
+ @property({ type: String, reflect: true }) color: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' = 'default';
13
+
14
+ // Standardized T-Shirt Sizes
15
+ @property({ type: String, reflect: true }) size: 'sm' | 'md' | 'lg' | 'xl' = 'md';
16
+
17
+ static styles = [
18
+ resetStyles,
19
+ css`
20
+ :host {
21
+ display: inline-flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ color: inherit;
25
+
26
+ /* Defaults to md (1.5rem) if no size attribute is matched */
27
+ width: var(--icon-size, 1.5rem);
28
+ height: var(--icon-size, 1.5rem);
29
+ }
30
+
31
+ /* ==========================================================
32
+ Size Variants
33
+ ========================================================== */
34
+ :host([size="sm"]) { --icon-size: 1.25rem; } /* 20px - Matches small buttons/inputs */
35
+ :host([size="md"]) { --icon-size: 1.5rem; } /* 24px - Default standard size */
36
+ :host([size="lg"]) { --icon-size: 2rem; } /* 32px - Large callouts */
37
+ :host([size="xl"]) { --icon-size: 2.5rem; } /* 40px - Hero sections/Cards */
38
+
39
+ /* ==========================================================
40
+ Color Variants
41
+ ========================================================== */
42
+ :host([color="primary"]) { color: var(--st-color-primary); }
43
+ :host([color="secondary"]) { color: var(--st-color-secondary); }
44
+ :host([color="success"]) { color: var(--st-color-success); }
45
+ :host([color="warning"]) { color: var(--st-color-warning); }
46
+ :host([color="error"]) { color: var(--st-color-error); }
47
+ :host([color="info"]) { color: var(--st-color-info); }
48
+
49
+ svg {
50
+ width: 100%;
51
+ height: 100%;
52
+ display: block;
53
+ }
54
+ `
55
+ ];
56
+
57
+ render() {
58
+ const iconData = (iconPaths as Record<string, any>)[this.name];
59
+ if (!iconData) return html``;
60
+
61
+ const pathData = iconData[this.variant] || iconData.outline;
62
+
63
+ return html`
64
+ <svg
65
+ viewBox="0 0 24 24"
66
+ xmlns="http://www.w3.org/2000/svg"
67
+ fill="${this.variant === 'solid' ? 'currentColor' : 'none'}"
68
+ stroke="${this.variant === 'outline' ? 'currentColor' : 'none'}"
69
+ stroke-width="${this.variant === 'outline' ? '2' : '0'}"
70
+ stroke-linecap="round"
71
+ stroke-linejoin="round"
72
+ >
73
+ ${pathData}
74
+ </svg>
75
+ `;
76
+ }
77
+ }
78
+
79
+ declare global {
80
+ interface HTMLElementTagNameMap {
81
+ 'st-icon': StIcon;
82
+ }
83
83
  }
@@ -1,248 +1,248 @@
1
- import {LitElement, html, css, PropertyValues} from "lit";
2
- import {customElement, property, query} from "lit/decorators.js";
3
- import {ifDefined} from "lit/directives/if-defined.js"; // Helper for optional attributes
4
- import {resetStyles} from "../../styles/reset.js";
5
-
6
- export type InputType = "text" | "password" | "email" | "number" | "search" | "url";
7
-
8
- @customElement("st-input")
9
- export class StInput extends LitElement {
10
- static formAssociated = true;
11
- private _internals: ElementInternals;
12
-
13
- @query("input") private _inputEl!: HTMLInputElement;
14
- @property({type: String, reflect: true}) name = "";
15
- @property({type: String}) label = "";
16
- @property({type: String}) value = "";
17
- @property({type: String}) placeholder = "";
18
- @property({type: String}) type: InputType = "text";
19
- @property({attribute: "help-text"}) helpText = "";
20
-
21
- // Validation Properties
22
- @property({type: Boolean, reflect: true}) required = false;
23
- @property({type: Number}) minlength?: number;
24
- @property({type: Number}) maxlength?: number;
25
- @property({type: String}) pattern?: string;
26
-
27
- @property({type: Boolean, reflect: true}) disabled = false;
28
- @property({type: Boolean, reflect: true}) invalid = false;
29
- @property({type: Boolean, reflect: true}) readonly = false;
30
-
31
- constructor() {
32
- super();
33
- this._internals = this.attachInternals();
34
-
35
- // 1. Trigger error state if the user clicks away and the field is invalid
36
- this.addEventListener("focusout", () => {
37
- this.invalid = !this._inputEl.validity.valid;
38
- });
39
-
40
- // 2. Trigger error state if the st-form tries to submit and fails
41
- this.addEventListener("invalid", () => {
42
- this.invalid = true;
43
- });
44
- }
45
-
46
- static styles = [
47
- resetStyles,
48
- css`
49
- :host {
50
- display: block;
51
- }
52
-
53
- .form-control {
54
- display: flex;
55
- flex-direction: column;
56
- gap: var(--st-spacing-xs);
57
- }
58
-
59
- label {
60
- font-size: var(--st-font-size-sm);
61
- font-weight: var(--st-font-weight-medium);
62
- color: var(--st-color-text);
63
- margin-bottom: 0.5em;
64
- }
65
-
66
- /* 1. The Wrapper acts as the visual input field */
67
- .input-wrapper {
68
- display: flex;
69
- align-items: center;
70
- gap: var(--st-spacing-sm);
71
- width: 100%;
72
-
73
- background-color: var(--st-color-bg);
74
- border: 1px solid var(--st-color-border);
75
- border-radius: 4px;
76
-
77
- /* Horizontal padding on the wrapper, vertical on the input */
78
- padding: 0 var(--st-spacing-md);
79
- transition:
80
- border-color 0.2s ease,
81
- box-shadow 0.2s ease;
82
- }
83
-
84
- /* 2. Apply focus ring to the wrapper when the internal input is focused */
85
- .input-wrapper:focus-within {
86
- border-color: var(--st-color-primary);
87
- box-shadow: 0 0 0 1px var(--st-color-primary);
88
- }
89
-
90
- /* 3. The actual input becomes transparent and fills the wrapper */
91
- input {
92
- flex: 1;
93
- width: 100%;
94
- border: none;
95
- background: transparent;
96
- outline: none;
97
-
98
- /* Vertical padding stretches the wrapper */
99
- padding: var(--st-spacing-sm) 0;
100
- font-size: var(--st-font-size-base);
101
- color: var(--st-color-text);
102
- }
103
-
104
- input::placeholder {
105
- color: var(--st-color-text-muted);
106
- }
107
-
108
- /* Slotted Icons */
109
- ::slotted(svg) {
110
- width: 1.25em;
111
- height: 1.25em;
112
- /* Default icons to the muted text color */
113
- color: var(--st-color-text-muted);
114
- fill: currentColor;
115
- flex-shrink: 0;
116
- }
117
-
118
- /* Interactive Slotted Elements (like clear buttons) */
119
- ::slotted(st-button) {
120
- /* Pull the button back slightly so it aligns nicely with the input padding */
121
- margin-right: calc(var(--st-spacing-sm) * -1);
122
- }
123
-
124
- /* Invalid State */
125
- :host([invalid]) .input-wrapper {
126
- border-color: var(--st-color-error);
127
- }
128
- :host([invalid]) .input-wrapper:focus-within {
129
- box-shadow: 0 0 0 1px var(--st-color-error);
130
- }
131
- :host([invalid]) .help-text {
132
- color: var(--st-color-error);
133
- }
134
-
135
- /* Disabled & Readonly States */
136
- :host([disabled]) .input-wrapper {
137
- opacity: 0.5;
138
- cursor: not-allowed;
139
- background-color: var(--st-color-surface);
140
- }
141
- :host([disabled]) input {
142
- cursor: not-allowed;
143
- }
144
- :host([readonly]) .input-wrapper {
145
- background-color: var(--st-color-surface);
146
- }
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
- `,
161
- ];
162
-
163
- override updated(changedProperties: PropertyValues<this>) {
164
- super.updated(changedProperties);
165
-
166
- // Sync the value to the form engine
167
- if (changedProperties.has("value")) {
168
- this._internals.setFormValue(this.value);
169
- }
170
-
171
- // Sync validity whenever properties change (like value or required status)
172
- this._syncValidity();
173
- }
174
-
175
- private _syncValidity() {
176
- if (!this._inputEl) return;
177
-
178
- // setValidity arguments: (ValidityState object, Custom Error Message, Anchor Element for Tooltip)
179
- this._internals.setValidity(
180
- this._inputEl.validity,
181
- this._inputEl.validationMessage,
182
- this._inputEl,
183
- );
184
- }
185
-
186
- formResetCallback() {
187
- this.value = "";
188
- this._internals.setFormValue("");
189
- // Need to wait for the render cycle to finish before checking validity on reset
190
- this.updateComplete.then(() => this._syncValidity());
191
- }
192
-
193
- // Expose standard native methods so external scripts can call them on the component
194
- checkValidity() {
195
- return this._internals.checkValidity();
196
- }
197
-
198
- reportValidity() {
199
- return this._internals.reportValidity();
200
- }
201
-
202
- private _handleInput(e: Event) {
203
- const target = e.target as HTMLTextAreaElement;
204
- this.value = target.value;
205
-
206
- if (this.invalid && target.validity.valid) {
207
- this.invalid = false;
208
- }
209
-
210
- this.dispatchEvent(new Event("input", {bubbles: true, composed: true}));
211
- }
212
-
213
- private _handleChange(e: Event) {
214
- this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
215
- }
216
-
217
- render() {
218
- return html`
219
- <div class="form-control">
220
- ${this.label ? html`<label for="input">${this.label}</label>` : ""}
221
-
222
- <div class="input-wrapper">
223
- <slot name="prefix"></slot>
224
-
225
- <input
226
- id="input"
227
- type=${this.type}
228
- .name=${this.name}
229
- .value=${this.value}
230
- placeholder=${this.placeholder}
231
- ?disabled=${this.disabled}
232
- ?readonly=${this.readonly}
233
- ?required=${this.required}
234
- minlength=${ifDefined(this.minlength)}
235
- maxlength=${ifDefined(this.maxlength)}
236
- pattern=${ifDefined(this.pattern)}
237
- @input=${this._handleInput}
238
- @change=${this._handleChange}
239
- />
240
-
241
- <slot name="suffix"></slot>
242
- </div>
243
-
244
- ${this.helpText ? html`<div class="help-text">${this.helpText}</div>` : ""}
245
- </div>
246
- `;
247
- }
248
- }
1
+ import {LitElement, html, css, PropertyValues} from "lit";
2
+ import {customElement, property, query} from "lit/decorators.js";
3
+ import {ifDefined} from "lit/directives/if-defined.js"; // Helper for optional attributes
4
+ import {resetStyles} from "../../styles/reset.js";
5
+
6
+ export type InputType = "text" | "password" | "email" | "number" | "search" | "url";
7
+
8
+ @customElement("st-input")
9
+ export class StInput extends LitElement {
10
+ static formAssociated = true;
11
+ private _internals: ElementInternals;
12
+
13
+ @query("input") private _inputEl!: HTMLInputElement;
14
+ @property({type: String, reflect: true}) name = "";
15
+ @property({type: String}) label = "";
16
+ @property({type: String}) value = "";
17
+ @property({type: String}) placeholder = "";
18
+ @property({type: String}) type: InputType = "text";
19
+ @property({attribute: "help-text"}) helpText = "";
20
+
21
+ // Validation Properties
22
+ @property({type: Boolean, reflect: true}) required = false;
23
+ @property({type: Number}) minlength?: number;
24
+ @property({type: Number}) maxlength?: number;
25
+ @property({type: String}) pattern?: string;
26
+
27
+ @property({type: Boolean, reflect: true}) disabled = false;
28
+ @property({type: Boolean, reflect: true}) invalid = false;
29
+ @property({type: Boolean, reflect: true}) readonly = false;
30
+
31
+ constructor() {
32
+ super();
33
+ this._internals = this.attachInternals();
34
+
35
+ // 1. Trigger error state if the user clicks away and the field is invalid
36
+ this.addEventListener("focusout", () => {
37
+ this.invalid = !this._inputEl.validity.valid;
38
+ });
39
+
40
+ // 2. Trigger error state if the st-form tries to submit and fails
41
+ this.addEventListener("invalid", () => {
42
+ this.invalid = true;
43
+ });
44
+ }
45
+
46
+ static styles = [
47
+ resetStyles,
48
+ css`
49
+ :host {
50
+ display: block;
51
+ }
52
+
53
+ .form-control {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: var(--st-spacing-xs);
57
+ }
58
+
59
+ label {
60
+ font-size: var(--st-font-size-sm);
61
+ font-weight: var(--st-font-weight-medium);
62
+ color: var(--st-color-text);
63
+ margin-bottom: 0.5em;
64
+ }
65
+
66
+ /* 1. The Wrapper acts as the visual input field */
67
+ .input-wrapper {
68
+ display: flex;
69
+ align-items: center;
70
+ gap: var(--st-spacing-sm);
71
+ width: 100%;
72
+
73
+ background-color: var(--st-color-bg);
74
+ border: 1px solid var(--st-color-border);
75
+ border-radius: 4px;
76
+
77
+ /* Horizontal padding on the wrapper, vertical on the input */
78
+ padding: 0 var(--st-spacing-md);
79
+ transition:
80
+ border-color 0.2s ease,
81
+ box-shadow 0.2s ease;
82
+ }
83
+
84
+ /* 2. Apply focus ring to the wrapper when the internal input is focused */
85
+ .input-wrapper:focus-within {
86
+ border-color: var(--st-color-primary);
87
+ box-shadow: 0 0 0 1px var(--st-color-primary);
88
+ }
89
+
90
+ /* 3. The actual input becomes transparent and fills the wrapper */
91
+ input {
92
+ flex: 1;
93
+ width: 100%;
94
+ border: none;
95
+ background: transparent;
96
+ outline: none;
97
+
98
+ /* Vertical padding stretches the wrapper */
99
+ padding: var(--st-spacing-sm) 0;
100
+ font-size: var(--st-font-size-base);
101
+ color: var(--st-color-text);
102
+ }
103
+
104
+ input::placeholder {
105
+ color: var(--st-color-text-muted);
106
+ }
107
+
108
+ /* Slotted Icons */
109
+ ::slotted(svg) {
110
+ width: 1.25em;
111
+ height: 1.25em;
112
+ /* Default icons to the muted text color */
113
+ color: var(--st-color-text-muted);
114
+ fill: currentColor;
115
+ flex-shrink: 0;
116
+ }
117
+
118
+ /* Interactive Slotted Elements (like clear buttons) */
119
+ ::slotted(st-button) {
120
+ /* Pull the button back slightly so it aligns nicely with the input padding */
121
+ margin-right: calc(var(--st-spacing-sm) * -1);
122
+ }
123
+
124
+ /* Invalid State */
125
+ :host([invalid]) .input-wrapper {
126
+ border-color: var(--st-color-error);
127
+ }
128
+ :host([invalid]) .input-wrapper:focus-within {
129
+ box-shadow: 0 0 0 1px var(--st-color-error);
130
+ }
131
+ :host([invalid]) .help-text {
132
+ color: var(--st-color-error);
133
+ }
134
+
135
+ /* Disabled & Readonly States */
136
+ :host([disabled]) .input-wrapper {
137
+ opacity: 0.5;
138
+ cursor: not-allowed;
139
+ background-color: var(--st-color-surface);
140
+ }
141
+ :host([disabled]) input {
142
+ cursor: not-allowed;
143
+ }
144
+ :host([readonly]) .input-wrapper {
145
+ background-color: var(--st-color-surface);
146
+ }
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
+ `,
161
+ ];
162
+
163
+ override updated(changedProperties: PropertyValues<this>) {
164
+ super.updated(changedProperties);
165
+
166
+ // Sync the value to the form engine
167
+ if (changedProperties.has("value")) {
168
+ this._internals.setFormValue(this.value);
169
+ }
170
+
171
+ // Sync validity whenever properties change (like value or required status)
172
+ this._syncValidity();
173
+ }
174
+
175
+ private _syncValidity() {
176
+ if (!this._inputEl) return;
177
+
178
+ // setValidity arguments: (ValidityState object, Custom Error Message, Anchor Element for Tooltip)
179
+ this._internals.setValidity(
180
+ this._inputEl.validity,
181
+ this._inputEl.validationMessage,
182
+ this._inputEl,
183
+ );
184
+ }
185
+
186
+ formResetCallback() {
187
+ this.value = "";
188
+ this._internals.setFormValue("");
189
+ // Need to wait for the render cycle to finish before checking validity on reset
190
+ this.updateComplete.then(() => this._syncValidity());
191
+ }
192
+
193
+ // Expose standard native methods so external scripts can call them on the component
194
+ checkValidity() {
195
+ return this._internals.checkValidity();
196
+ }
197
+
198
+ reportValidity() {
199
+ return this._internals.reportValidity();
200
+ }
201
+
202
+ private _handleInput(e: Event) {
203
+ const target = e.target as HTMLTextAreaElement;
204
+ this.value = target.value;
205
+
206
+ if (this.invalid && target.validity.valid) {
207
+ this.invalid = false;
208
+ }
209
+
210
+ this.dispatchEvent(new Event("input", {bubbles: true, composed: true}));
211
+ }
212
+
213
+ private _handleChange(e: Event) {
214
+ this.dispatchEvent(new Event("change", {bubbles: true, composed: true}));
215
+ }
216
+
217
+ render() {
218
+ return html`
219
+ <div class="form-control">
220
+ ${this.label ? html`<label for="input">${this.label}</label>` : ""}
221
+
222
+ <div class="input-wrapper">
223
+ <slot name="prefix"></slot>
224
+
225
+ <input
226
+ id="input"
227
+ type=${this.type}
228
+ .name=${this.name}
229
+ .value=${this.value}
230
+ placeholder=${this.placeholder}
231
+ ?disabled=${this.disabled}
232
+ ?readonly=${this.readonly}
233
+ ?required=${this.required}
234
+ minlength=${ifDefined(this.minlength)}
235
+ maxlength=${ifDefined(this.maxlength)}
236
+ pattern=${ifDefined(this.pattern)}
237
+ @input=${this._handleInput}
238
+ @change=${this._handleChange}
239
+ />
240
+
241
+ <slot name="suffix"></slot>
242
+ </div>
243
+
244
+ ${this.helpText ? html`<div class="help-text">${this.helpText}</div>` : ""}
245
+ </div>
246
+ `;
247
+ }
248
+ }