aeico-components 0.1.4 → 0.1.6

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 (299) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/dist/chunks/action-button.cjs +296 -0
  4. package/dist/chunks/action-button.cjs.map +1 -0
  5. package/dist/chunks/action-button.js +297 -0
  6. package/dist/chunks/action-button.js.map +1 -0
  7. package/dist/chunks/alert.cjs +4 -4
  8. package/dist/chunks/alert.cjs.map +1 -1
  9. package/dist/chunks/alert.js +5 -5
  10. package/dist/chunks/alert.js.map +1 -1
  11. package/dist/chunks/badge.cjs +1 -1
  12. package/dist/chunks/badge.cjs.map +1 -1
  13. package/dist/chunks/badge.js +2 -2
  14. package/dist/chunks/badge.js.map +1 -1
  15. package/dist/chunks/breadcrumb-item.cjs +2 -2
  16. package/dist/chunks/breadcrumb-item.cjs.map +1 -1
  17. package/dist/chunks/breadcrumb-item.js +3 -3
  18. package/dist/chunks/breadcrumb-item.js.map +1 -1
  19. package/dist/chunks/button-group.cjs +1 -1
  20. package/dist/chunks/button-group.cjs.map +1 -1
  21. package/dist/chunks/button-group.js +2 -2
  22. package/dist/chunks/button-group.js.map +1 -1
  23. package/dist/chunks/button.cjs +12 -15
  24. package/dist/chunks/button.cjs.map +1 -1
  25. package/dist/chunks/button.js +13 -16
  26. package/dist/chunks/button.js.map +1 -1
  27. package/dist/chunks/card.cjs +1 -1
  28. package/dist/chunks/card.cjs.map +1 -1
  29. package/dist/chunks/card.js +2 -2
  30. package/dist/chunks/card.js.map +1 -1
  31. package/dist/chunks/checkbox.cjs +18 -5
  32. package/dist/chunks/checkbox.cjs.map +1 -1
  33. package/dist/chunks/checkbox.js +18 -5
  34. package/dist/chunks/checkbox.js.map +1 -1
  35. package/dist/chunks/copy-button.cjs +168 -0
  36. package/dist/chunks/copy-button.cjs.map +1 -0
  37. package/dist/chunks/copy-button.js +169 -0
  38. package/dist/chunks/copy-button.js.map +1 -0
  39. package/dist/chunks/detail.cjs +7 -4
  40. package/dist/chunks/detail.cjs.map +1 -1
  41. package/dist/chunks/detail.js +8 -5
  42. package/dist/chunks/detail.js.map +1 -1
  43. package/dist/chunks/dialog.cjs +1 -1
  44. package/dist/chunks/dialog.cjs.map +1 -1
  45. package/dist/chunks/dialog.js +2 -2
  46. package/dist/chunks/dialog.js.map +1 -1
  47. package/dist/chunks/divider.cjs +1 -1
  48. package/dist/chunks/divider.cjs.map +1 -1
  49. package/dist/chunks/divider.js +2 -2
  50. package/dist/chunks/divider.js.map +1 -1
  51. package/dist/chunks/drawer.cjs +180 -0
  52. package/dist/chunks/drawer.cjs.map +1 -0
  53. package/dist/chunks/drawer.js +181 -0
  54. package/dist/chunks/drawer.js.map +1 -0
  55. package/dist/chunks/dropdown-button.cjs +2 -2
  56. package/dist/chunks/dropdown-button.cjs.map +1 -1
  57. package/dist/chunks/dropdown-button.js +6 -6
  58. package/dist/chunks/dropdown-button.js.map +1 -1
  59. package/dist/chunks/icon.cjs +31 -1
  60. package/dist/chunks/icon.cjs.map +1 -1
  61. package/dist/chunks/icon.js +32 -2
  62. package/dist/chunks/icon.js.map +1 -1
  63. package/dist/chunks/menu.cjs +396 -0
  64. package/dist/chunks/menu.cjs.map +1 -0
  65. package/dist/chunks/menu.js +397 -0
  66. package/dist/chunks/menu.js.map +1 -0
  67. package/dist/chunks/navbar.cjs +2 -3
  68. package/dist/chunks/navbar.cjs.map +1 -1
  69. package/dist/chunks/navbar.js +3 -4
  70. package/dist/chunks/navbar.js.map +1 -1
  71. package/dist/chunks/pagination.cjs +475 -0
  72. package/dist/chunks/pagination.cjs.map +1 -0
  73. package/dist/chunks/pagination.js +476 -0
  74. package/dist/chunks/pagination.js.map +1 -0
  75. package/dist/chunks/progress-bar.cjs +101 -0
  76. package/dist/chunks/progress-bar.cjs.map +1 -0
  77. package/dist/chunks/progress-bar.js +102 -0
  78. package/dist/chunks/progress-bar.js.map +1 -0
  79. package/dist/chunks/radio.cjs +11 -7
  80. package/dist/chunks/radio.cjs.map +1 -1
  81. package/dist/chunks/radio.js +11 -7
  82. package/dist/chunks/radio.js.map +1 -1
  83. package/dist/chunks/select.cjs +97 -66
  84. package/dist/chunks/select.cjs.map +1 -1
  85. package/dist/chunks/select.js +97 -66
  86. package/dist/chunks/select.js.map +1 -1
  87. package/dist/chunks/slider.cjs +9 -46
  88. package/dist/chunks/slider.cjs.map +1 -1
  89. package/dist/chunks/slider.js +9 -46
  90. package/dist/chunks/slider.js.map +1 -1
  91. package/dist/chunks/spinner.cjs +102 -0
  92. package/dist/chunks/spinner.cjs.map +1 -0
  93. package/dist/chunks/spinner.js +103 -0
  94. package/dist/chunks/spinner.js.map +1 -0
  95. package/dist/chunks/switch.cjs +110 -16
  96. package/dist/chunks/switch.cjs.map +1 -1
  97. package/dist/chunks/switch.js +111 -17
  98. package/dist/chunks/switch.js.map +1 -1
  99. package/dist/chunks/tab-panel.cjs +6 -7
  100. package/dist/chunks/tab-panel.cjs.map +1 -1
  101. package/dist/chunks/tab-panel.js +7 -8
  102. package/dist/chunks/tab-panel.js.map +1 -1
  103. package/dist/chunks/tag.cjs +1 -1
  104. package/dist/chunks/tag.cjs.map +1 -1
  105. package/dist/chunks/tag.js +2 -2
  106. package/dist/chunks/tag.js.map +1 -1
  107. package/dist/chunks/text-input.cjs +11 -16
  108. package/dist/chunks/text-input.cjs.map +1 -1
  109. package/dist/chunks/text-input.js +11 -16
  110. package/dist/chunks/text-input.js.map +1 -1
  111. package/dist/chunks/textarea.cjs +137 -0
  112. package/dist/chunks/textarea.cjs.map +1 -0
  113. package/dist/chunks/textarea.js +138 -0
  114. package/dist/chunks/textarea.js.map +1 -0
  115. package/dist/chunks/tooltip.cjs +293 -0
  116. package/dist/chunks/tooltip.cjs.map +1 -0
  117. package/dist/chunks/tooltip.js +294 -0
  118. package/dist/chunks/tooltip.js.map +1 -0
  119. package/dist/chunks/tree.cjs +468 -0
  120. package/dist/chunks/tree.cjs.map +1 -0
  121. package/dist/chunks/tree.js +469 -0
  122. package/dist/chunks/tree.js.map +1 -0
  123. package/dist/chunks/variables.cjs +2 -2
  124. package/dist/chunks/variables.js +2 -2
  125. package/dist/copy-button.cjs +6 -0
  126. package/dist/copy-button.cjs.map +1 -0
  127. package/dist/copy-button.js +6 -0
  128. package/dist/copy-button.js.map +1 -0
  129. package/dist/drawer.cjs +6 -0
  130. package/dist/drawer.cjs.map +1 -0
  131. package/dist/drawer.js +6 -0
  132. package/dist/drawer.js.map +1 -0
  133. package/dist/dropdown.js +4 -4
  134. package/dist/index.cjs +186 -0
  135. package/dist/index.cjs.map +1 -1
  136. package/dist/index.js +201 -15
  137. package/dist/index.js.map +1 -1
  138. package/dist/menu.cjs +6 -0
  139. package/dist/menu.cjs.map +1 -0
  140. package/dist/menu.js +6 -0
  141. package/dist/menu.js.map +1 -0
  142. package/dist/pagination.cjs +6 -0
  143. package/dist/pagination.cjs.map +1 -0
  144. package/dist/pagination.js +6 -0
  145. package/dist/pagination.js.map +1 -0
  146. package/dist/progress-bar.cjs +6 -0
  147. package/dist/progress-bar.cjs.map +1 -0
  148. package/dist/progress-bar.js +6 -0
  149. package/dist/progress-bar.js.map +1 -0
  150. package/dist/select.cjs +1 -1
  151. package/dist/select.cjs.map +1 -1
  152. package/dist/select.js +2 -2
  153. package/dist/select.js.map +1 -1
  154. package/dist/spinner.cjs +6 -0
  155. package/dist/spinner.cjs.map +1 -0
  156. package/dist/spinner.js +6 -0
  157. package/dist/spinner.js.map +1 -0
  158. package/dist/textarea.cjs +5 -0
  159. package/dist/textarea.cjs.map +1 -0
  160. package/dist/textarea.js +5 -0
  161. package/dist/textarea.js.map +1 -0
  162. package/dist/tooltip.cjs +6 -0
  163. package/dist/tooltip.cjs.map +1 -0
  164. package/dist/tooltip.js +6 -0
  165. package/dist/tooltip.js.map +1 -0
  166. package/dist/tree.cjs +6 -0
  167. package/dist/tree.cjs.map +1 -0
  168. package/dist/tree.js +6 -0
  169. package/dist/tree.js.map +1 -0
  170. package/dist/types/aeico-field.d.ts +57 -5
  171. package/dist/types/alert/alert.d.ts +1 -0
  172. package/dist/types/button/button.d.ts +2 -1
  173. package/dist/types/checkbox/checkbox.d.ts +5 -5
  174. package/dist/types/copy-button/copy-button.d.ts +32 -0
  175. package/dist/types/copy-button/defines.d.ts +1 -0
  176. package/dist/types/copy-button/index.d.ts +3 -0
  177. package/dist/types/detail/defines.d.ts +1 -0
  178. package/dist/types/detail/detail.d.ts +3 -1
  179. package/dist/types/detail/index.d.ts +1 -1
  180. package/dist/types/detail-group/detail-group.d.ts +39 -0
  181. package/dist/types/detail-group/index.d.ts +2 -0
  182. package/dist/types/drawer/defines.d.ts +1 -0
  183. package/dist/types/drawer/drawer.d.ts +31 -0
  184. package/dist/types/drawer/index.d.ts +3 -0
  185. package/dist/types/icon/built-in-icons.d.ts +1 -0
  186. package/dist/types/icon/icon.d.ts +1 -0
  187. package/dist/types/icon/registry.d.ts +8 -0
  188. package/dist/types/index.d.ts +19 -0
  189. package/dist/types/menu/defines.d.ts +15 -0
  190. package/dist/types/menu/index.d.ts +5 -0
  191. package/dist/types/menu/menu-item.d.ts +63 -0
  192. package/dist/types/menu/menu.d.ts +34 -0
  193. package/dist/types/number-input/index.d.ts +2 -0
  194. package/dist/types/number-input/number-input.d.ts +35 -0
  195. package/dist/types/pagination/defines.d.ts +2 -0
  196. package/dist/types/pagination/index.d.ts +3 -0
  197. package/dist/types/pagination/pagination.d.ts +77 -0
  198. package/dist/types/progress-bar/defines.d.ts +1 -0
  199. package/dist/types/progress-bar/index.d.ts +3 -0
  200. package/dist/types/progress-bar/progress-bar.d.ts +37 -0
  201. package/dist/types/radio-group/radio-group.d.ts +1 -1
  202. package/dist/types/select/select.d.ts +3 -3
  203. package/dist/types/spinner/defines.d.ts +3 -0
  204. package/dist/types/spinner/index.d.ts +3 -0
  205. package/dist/types/spinner/spinner.d.ts +35 -0
  206. package/dist/types/switch/defines.d.ts +1 -0
  207. package/dist/types/switch/switch.d.ts +13 -9
  208. package/dist/types/text-input/text-input.d.ts +0 -4
  209. package/dist/types/textarea/index.d.ts +1 -0
  210. package/dist/types/textarea/textarea.d.ts +26 -0
  211. package/dist/types/tooltip/defines.d.ts +2 -0
  212. package/dist/types/tooltip/index.d.ts +4 -0
  213. package/dist/types/tooltip/tooltip.d.ts +48 -0
  214. package/dist/types/tree/defines.d.ts +23 -0
  215. package/dist/types/tree/index.d.ts +5 -0
  216. package/dist/types/tree/tree-item.d.ts +54 -0
  217. package/dist/types/tree/tree.d.ts +64 -0
  218. package/package.json +6 -6
  219. package/src/aeico-field.ts +154 -15
  220. package/src/alert/alert.ts +3 -2
  221. package/src/button/button.ts +11 -13
  222. package/src/checkbox/checkbox.ts +21 -6
  223. package/src/copy-button/copy-button.ts +146 -0
  224. package/src/copy-button/defines.ts +5 -0
  225. package/src/copy-button/index.ts +3 -0
  226. package/src/detail/defines.ts +1 -0
  227. package/src/detail/detail.ts +5 -1
  228. package/src/detail/index.ts +1 -1
  229. package/src/detail-group/detail-group.ts +104 -0
  230. package/src/detail-group/index.ts +2 -0
  231. package/src/drawer/defines.ts +1 -0
  232. package/src/drawer/drawer.ts +157 -0
  233. package/src/drawer/index.ts +3 -0
  234. package/src/icon/built-in-icons.ts +21 -0
  235. package/src/icon/icon.ts +1 -0
  236. package/src/icon/registry.ts +22 -0
  237. package/src/index.ts +32 -0
  238. package/src/menu/defines.ts +17 -0
  239. package/src/menu/index.ts +5 -0
  240. package/src/menu/menu-item.ts +315 -0
  241. package/src/menu/menu.ts +81 -0
  242. package/src/navbar/navbar.ts +1 -3
  243. package/src/number-input/index.ts +2 -0
  244. package/src/number-input/number-input.ts +137 -0
  245. package/src/pagination/defines.ts +2 -0
  246. package/src/pagination/index.ts +3 -0
  247. package/src/pagination/pagination.ts +310 -0
  248. package/src/progress-bar/defines.ts +8 -0
  249. package/src/progress-bar/index.ts +3 -0
  250. package/src/progress-bar/progress-bar.ts +80 -0
  251. package/src/radio-group/radio-group.ts +12 -5
  252. package/src/select/select.ts +112 -71
  253. package/src/slider/slider.ts +9 -2
  254. package/src/spinner/defines.ts +12 -0
  255. package/src/spinner/index.ts +3 -0
  256. package/src/spinner/spinner.ts +81 -0
  257. package/src/styles/components/action-button.css +37 -0
  258. package/src/styles/components/checkbox.css +4 -26
  259. package/src/styles/components/copy-button.css +119 -0
  260. package/src/styles/components/detail-group.css +10 -0
  261. package/src/styles/components/detail.css +10 -1
  262. package/src/styles/components/drawer.css +161 -0
  263. package/src/styles/components/field-label.css +120 -0
  264. package/src/styles/components/menu-item.css +168 -0
  265. package/src/styles/components/menu.css +17 -0
  266. package/src/styles/components/number-input.css +167 -0
  267. package/src/styles/components/pagination.css +205 -0
  268. package/src/styles/components/progress-bar.css +44 -0
  269. package/src/styles/components/radio-group.css +0 -23
  270. package/src/styles/components/select.css +12 -39
  271. package/src/styles/components/slider.css +0 -42
  272. package/src/styles/components/spinner.css +80 -0
  273. package/src/styles/components/switch.css +68 -19
  274. package/src/styles/components/tab-panel.css +1 -1
  275. package/src/styles/components/tabs.css +1 -0
  276. package/src/styles/components/text-input.css +7 -45
  277. package/src/styles/components/textarea.css +75 -0
  278. package/src/styles/components/tooltip.css +103 -0
  279. package/src/styles/components/tree-item.css +152 -0
  280. package/src/styles/components/tree.css +10 -0
  281. package/src/styles/layout.css +457 -25
  282. package/src/switch/defines.ts +1 -0
  283. package/src/switch/switch.ts +65 -16
  284. package/src/tabs/tab.ts +1 -1
  285. package/src/tabs/tabs.ts +1 -2
  286. package/src/text-input/text-input.ts +10 -15
  287. package/src/textarea/index.ts +1 -0
  288. package/src/textarea/textarea.ts +107 -0
  289. package/src/tooltip/defines.ts +11 -0
  290. package/src/tooltip/index.ts +4 -0
  291. package/src/tooltip/tooltip.ts +183 -0
  292. package/src/tree/defines.ts +26 -0
  293. package/src/tree/index.ts +5 -0
  294. package/src/tree/tree-item.ts +258 -0
  295. package/src/tree/tree.ts +237 -0
  296. package/dist/chunks/aeico-field.cjs +0 -179
  297. package/dist/chunks/aeico-field.cjs.map +0 -1
  298. package/dist/chunks/aeico-field.js +0 -180
  299. package/dist/chunks/aeico-field.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aeico-components",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Built-in UI components for the Aeico Web Components framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -24,6 +24,7 @@
24
24
  "src"
25
25
  ],
26
26
  "sideEffects": [
27
+ "./src/icon/built-in-icons.ts",
27
28
  "./dist/*.js",
28
29
  "./dist/*.cjs",
29
30
  "./dist/chunks/*.js",
@@ -49,14 +50,13 @@
49
50
  "components",
50
51
  "aeico"
51
52
  ],
52
- "author": "",
53
- "license": "ISC",
53
+ "author": "Eowl",
54
+ "license": "MIT",
54
55
  "peerDependencies": {
55
- "aeico": "^0.1.3",
56
- "aeico-localize": "^0.1.1"
56
+ "aeico": "^0.1.5"
57
57
  },
58
58
  "devDependencies": {
59
- "aeico": "^0.1.3",
59
+ "aeico": "^0.1.5",
60
60
  "aeico-localize": "^0.1.1",
61
61
  "@eslint/js": "^10.0.1",
62
62
  "@esm-bundle/chai": "^4.3.4-fix.0",
@@ -1,10 +1,10 @@
1
1
  import type { InferProps, Props, Watchers } from 'aeico';
2
2
  import { tags } from 'aeico';
3
3
  import AeicoComponent from './aeico-component';
4
- import { t } from 'aeico-localize';
5
4
 
6
5
  export type FieldAction = 'clear' | 'reset' | 'change';
7
6
  export type FieldElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
7
+ export type ActionButtonStyle = 'standalone' | 'integrated';
8
8
 
9
9
  /**
10
10
  * Base class for form field components
@@ -25,10 +25,18 @@ class AeicoField<TValue = string> extends AeicoComponent {
25
25
  defaultValue: { type: String },
26
26
  resettable: { type: Boolean },
27
27
  resetText: { type: String },
28
+ resetTitle: { type: String },
28
29
  clearable: { type: Boolean },
29
30
  clearText: { type: String },
31
+ clearTitle: { type: String },
32
+ actionButtonStyle: { type: String },
30
33
  size: { type: String },
31
34
  disabled: { type: Boolean },
35
+ label: { type: String },
36
+ labelPlacement: { type: String },
37
+ required: { type: Boolean },
38
+ helperText: { type: String },
39
+ error: { type: String },
32
40
  };
33
41
 
34
42
  /**
@@ -36,8 +44,17 @@ class AeicoField<TValue = string> extends AeicoComponent {
36
44
  */
37
45
  static watchers: Watchers = {
38
46
  disabled: 'onDisabledChanged',
47
+ error: 'onErrorChanged',
39
48
  };
40
49
 
50
+ private static _fieldIdCounter = 0;
51
+ private readonly _fieldId: string;
52
+
53
+ constructor() {
54
+ super();
55
+ this._fieldId = `ae-field-${++AeicoField._fieldIdCounter}`;
56
+ }
57
+
41
58
  /**
42
59
  * The underlying form control element (input, select, etc.)
43
60
  * Subclasses should set this to their specific element
@@ -58,10 +75,18 @@ class AeicoField<TValue = string> extends AeicoComponent {
58
75
  declare defaultValue?: TValue | string;
59
76
  declare resettable?: boolean;
60
77
  declare resetText?: string;
78
+ declare resetTitle?: string;
61
79
  declare clearable?: boolean;
62
80
  declare clearText?: string;
81
+ declare clearTitle?: string;
82
+ declare actionButtonStyle?: ActionButtonStyle;
63
83
  declare size?: string;
64
84
  declare disabled?: boolean;
85
+ declare label?: string;
86
+ declare labelPlacement?: 'top' | 'left';
87
+ declare required?: boolean;
88
+ declare helperText?: string;
89
+ declare error?: string;
65
90
 
66
91
  /**
67
92
  * Lifecycle: Component connected to DOM
@@ -82,18 +107,30 @@ class AeicoField<TValue = string> extends AeicoComponent {
82
107
  * Must be called from within a build() callback.
83
108
  */
84
109
  protected renderActionButtons(force: boolean = false) {
85
- this.renderClearButton(force);
86
- this.renderResetButton(force);
110
+ const style = this.actionButtonStyle || 'integrated';
111
+ const hasActions = force || this.clearable || this.resettable;
112
+
113
+ if (hasActions && style === 'integrated') {
114
+ tags.div({ className: 'action-controls' }, () => {
115
+ this.renderClearButton(force);
116
+ this.renderResetButton(force);
117
+ });
118
+ } else {
119
+ this.renderClearButton(force);
120
+ this.renderResetButton(force);
121
+ }
87
122
  }
88
123
 
89
124
  protected renderResetButton(force: boolean = false) {
90
125
  const { button } = tags;
126
+ const style = this.actionButtonStyle || 'integrated';
91
127
 
92
128
  if (force || this.resettable) {
129
+ const className = style === 'integrated' ? 'reset-btn action-btn' : 'reset-btn';
93
130
  this.resetBtn = button({
94
- className: 'reset-btn',
131
+ className,
95
132
  textContent: this.resetText || '↺',
96
- title: t('buttons.reset', '↺'),
133
+ title: this.resetTitle || '↺',
97
134
  '@click': this.boundOnReset,
98
135
  });
99
136
  }
@@ -101,17 +138,63 @@ class AeicoField<TValue = string> extends AeicoComponent {
101
138
 
102
139
  protected renderClearButton(force: boolean = false) {
103
140
  const { button } = tags;
141
+ const style = this.actionButtonStyle || 'integrated';
104
142
 
105
143
  if (force || this.clearable) {
144
+ const className = style === 'integrated' ? 'clear-btn action-btn' : 'clear-btn';
106
145
  this.clearBtn = button({
107
- className: 'clear-btn',
146
+ className,
108
147
  textContent: this.clearText || '✕',
109
- title: t('buttons.clear', '✕'),
148
+ title: this.clearTitle || '✕',
110
149
  '@click': this.boundOnClear,
111
150
  });
112
151
  }
113
152
  }
114
153
 
154
+ /**
155
+ * Returns a stable unique ID for this field instance,
156
+ * used to associate <label htmlFor> with the underlying input.
157
+ */
158
+ protected getFieldId(): string {
159
+ return this._fieldId;
160
+ }
161
+
162
+ /**
163
+ * Renders a <label> element when the `label` prop is set.
164
+ * Call this as the first statement inside the render() html() callback.
165
+ * @param fieldId - The id to set on the underlying form control element (pass to input via id prop)
166
+ */
167
+ protected renderLabel(fieldId: string): void {
168
+ if (!this.label) return;
169
+ const { span } = tags;
170
+ tags.label({ id: `${fieldId}-label`, className: 'field-label', for: fieldId }, () => {
171
+ span({ textContent: this.label! });
172
+ if (this.required) {
173
+ span({ className: 'field-required', 'aria-hidden': 'true', textContent: ' *' });
174
+ }
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Renders helper text below the field. Hidden when `error` is set.
180
+ * Call this after the field-body div in render().
181
+ */
182
+ protected renderHelperText(): void {
183
+ if (!this.helperText || this.error) return;
184
+ const { span } = tags;
185
+ span({ className: 'field-helper', textContent: this.helperText });
186
+ }
187
+
188
+ /**
189
+ * Renders an error message below the field when `error` is set.
190
+ * Call this after renderHelperText() in render().
191
+ */
192
+ protected renderError(): void {
193
+ if (!this.error) return;
194
+ const { span } = tags;
195
+ span({ className: 'field-error', textContent: this.error });
196
+ }
197
+
115
198
  /**
116
199
  * Watcher for disabled property
117
200
  */
@@ -121,6 +204,58 @@ class AeicoField<TValue = string> extends AeicoComponent {
121
204
  }
122
205
  }
123
206
 
207
+ /**
208
+ * Watcher for error property — syncs aria-invalid on the field element
209
+ */
210
+ protected onErrorChanged(newValue: string | undefined): void {
211
+ if (this.fieldElement) {
212
+ if (newValue) {
213
+ this.fieldElement.setAttribute('aria-invalid', 'true');
214
+ } else {
215
+ this.fieldElement.removeAttribute('aria-invalid');
216
+ }
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Lifecycle: called after every render update.
222
+ * Keeps aria-invalid on fieldElement in sync regardless of watcher timing.
223
+ */
224
+ protected onUpdated(_changedProps: Map<string, unknown>): void {
225
+ if (!this.fieldElement) return;
226
+ if (this.error) {
227
+ this.fieldElement.setAttribute('aria-invalid', 'true');
228
+ } else {
229
+ this.fieldElement.removeAttribute('aria-invalid');
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Returns true if the field passes constraint validation.
235
+ * Delegates to the underlying fieldElement when available;
236
+ * falls back to a manual required-check otherwise.
237
+ */
238
+ public checkValidity(): boolean {
239
+ if (this.fieldElement) {
240
+ return this.fieldElement.checkValidity();
241
+ }
242
+ if (this.required) {
243
+ const v = this.value;
244
+ return v !== undefined && v !== '' && v !== null;
245
+ }
246
+ return true;
247
+ }
248
+
249
+ /**
250
+ * Reports validity, showing the browser's built-in validation UI when possible.
251
+ */
252
+ public reportValidity(): boolean {
253
+ if (this.fieldElement) {
254
+ return this.fieldElement.reportValidity();
255
+ }
256
+ return this.checkValidity();
257
+ }
258
+
124
259
  /**
125
260
  * Render the field component
126
261
  * Override in subclass to provide custom rendering
@@ -136,8 +271,8 @@ class AeicoField<TValue = string> extends AeicoComponent {
136
271
  *
137
272
  * @returns Current field value
138
273
  */
139
- protected getValue(): any {
140
- return this.fieldElement?.value || '';
274
+ protected getValue(): TValue {
275
+ return (this.fieldElement?.value || '') as TValue;
141
276
  }
142
277
 
143
278
  /**
@@ -146,7 +281,7 @@ class AeicoField<TValue = string> extends AeicoComponent {
146
281
  *
147
282
  * @param _value New value to write to the element
148
283
  */
149
- protected writeValue(_value: any): void {
284
+ protected writeValue(_value: TValue): void {
150
285
  // Base implementation - subclasses override
151
286
  }
152
287
 
@@ -159,7 +294,11 @@ class AeicoField<TValue = string> extends AeicoComponent {
159
294
  * @param action Action type
160
295
  * @returns Event payload object
161
296
  */
162
- protected getEventPayload(value: any, oldValue: any, action: FieldAction): Record<string, any> {
297
+ protected getEventPayload(
298
+ value: TValue,
299
+ oldValue: TValue,
300
+ action: FieldAction,
301
+ ): Record<string, unknown> {
163
302
  return { value, oldValue, action };
164
303
  }
165
304
 
@@ -171,7 +310,7 @@ class AeicoField<TValue = string> extends AeicoComponent {
171
310
  * @param options.silent If true, won't emit change event (default: true)
172
311
  * @param options.action Action type for the event (default: 'change')
173
312
  */
174
- protected setValue(value: any, options?: { silent?: boolean; action?: FieldAction }): void {
313
+ protected setValue(value: TValue, options?: { silent?: boolean; action?: FieldAction }): void {
175
314
  const oldValue = this.getValue();
176
315
 
177
316
  // Update property value
@@ -193,9 +332,9 @@ class AeicoField<TValue = string> extends AeicoComponent {
193
332
  * @param value Value to reset to, defaults to defaultValue prop
194
333
  * @param options.silent If false, will emit reset event (default: true)
195
334
  */
196
- public reset(value?: any, options?: { silent?: boolean }): void {
335
+ public reset(value?: TValue, options?: { silent?: boolean }): void {
197
336
  const resetValue = value !== undefined ? value : this.defaultValue;
198
- this.setValue(resetValue, { ...options, action: 'reset' });
337
+ this.setValue(resetValue as TValue, { ...options, action: 'reset' });
199
338
  }
200
339
 
201
340
  /**
@@ -204,7 +343,7 @@ class AeicoField<TValue = string> extends AeicoComponent {
204
343
  * @param options.silent If false, will emit clear event (default: true)
205
344
  */
206
345
  public clear(options?: { silent?: boolean }): void {
207
- this.setValue('', { ...options, action: 'clear' });
346
+ this.setValue('' as TValue, { ...options, action: 'clear' });
208
347
  }
209
348
 
210
349
  /**
@@ -4,7 +4,6 @@ import colorCSS from '../styles/color.css?inline';
4
4
  import alertStyle from '../styles/components/alert.css?inline';
5
5
  import AeicoComponent from '../aeico-component';
6
6
  import { html } from 'aeico';
7
- import { t } from 'aeico-localize';
8
7
  import type { AlertColor, AlertSize, AlertVariant } from './defines';
9
8
 
10
9
  /**
@@ -39,6 +38,7 @@ class Alert extends AeicoComponent {
39
38
  size: { type: String },
40
39
  dismissible: { type: Boolean },
41
40
  invisible: { type: Boolean },
41
+ closeText: { type: String },
42
42
  };
43
43
 
44
44
  protected static useStyles = ['alert'];
@@ -49,6 +49,7 @@ class Alert extends AeicoComponent {
49
49
  declare size?: AlertSize;
50
50
  declare dismissible?: boolean;
51
51
  declare invisible?: boolean;
52
+ declare closeText?: string;
52
53
 
53
54
  protected render() {
54
55
  return html(({ div, slot, button, span }) => {
@@ -67,7 +68,7 @@ class Alert extends AeicoComponent {
67
68
  {
68
69
  className: 'alert-close',
69
70
  '@click': () => this._handleClose(),
70
- title: t('alert.close', 'Close alert'),
71
+ title: this.closeText || 'Close alert',
71
72
  },
72
73
  () => {
73
74
  span({ 'aria-hidden': 'true', textContent: '\u00d7' });
@@ -55,18 +55,16 @@ class Button extends AeicoComponent {
55
55
  @prop({ type: Boolean })
56
56
  block?: boolean;
57
57
 
58
- private buttonElement: HTMLButtonElement | null = null;
58
+ private _buttonElement: HTMLButtonElement | null = null;
59
+ private _slotElement: HTMLSlotElement | null = null;
59
60
  private _autoAriaLabel = false;
60
61
 
61
62
  protected onMounted() {
62
- const slot = this.shadowRoot?.querySelector('slot:not([name])');
63
- if (slot) this.listen(slot, 'slotchange', this._handleSlotChange);
64
63
  this._handleSlotChange();
65
64
  }
66
65
 
67
66
  private _handleSlotChange = () => {
68
- const slot = this.shadowRoot?.querySelector('slot:not([name])') as HTMLSlotElement | null;
69
- const nodes = slot?.assignedNodes() ?? [];
67
+ const nodes = this._slotElement?.assignedNodes() ?? [];
70
68
  // Icon-only: exactly one element (ae-icon) and no meaningful text nodes
71
69
  const elements = nodes.filter((n): n is Element => n.nodeType === Node.ELEMENT_NODE);
72
70
  const hasText = nodes.some(
@@ -92,7 +90,7 @@ class Button extends AeicoComponent {
92
90
 
93
91
  protected render() {
94
92
  return html(({ button, slot }) => {
95
- this.buttonElement = button(
93
+ this._buttonElement = button(
96
94
  {
97
95
  type: this.type || 'button',
98
96
  disabled: this.disabled,
@@ -101,7 +99,7 @@ class Button extends AeicoComponent {
101
99
  'aria-disabled': this.disabled,
102
100
  },
103
101
  () => {
104
- slot();
102
+ this._slotElement = slot({ '@slotchange': this._handleSlotChange });
105
103
  },
106
104
  );
107
105
  });
@@ -111,8 +109,8 @@ class Button extends AeicoComponent {
111
109
  * Programmatically click the button
112
110
  */
113
111
  click() {
114
- if (!this.disabled && this.buttonElement) {
115
- this.buttonElement.click();
112
+ if (!this.disabled && this._buttonElement) {
113
+ this._buttonElement.click();
116
114
  }
117
115
  }
118
116
 
@@ -120,8 +118,8 @@ class Button extends AeicoComponent {
120
118
  * Focus the button
121
119
  */
122
120
  focus() {
123
- if (this.buttonElement) {
124
- this.buttonElement.focus();
121
+ if (this._buttonElement) {
122
+ this._buttonElement.focus();
125
123
  }
126
124
  }
127
125
 
@@ -129,8 +127,8 @@ class Button extends AeicoComponent {
129
127
  * Blur the button
130
128
  */
131
129
  blur() {
132
- if (this.buttonElement) {
133
- this.buttonElement.blur();
130
+ if (this._buttonElement) {
131
+ this._buttonElement.blur();
134
132
  }
135
133
  }
136
134
  }
@@ -1,13 +1,15 @@
1
- import AeicoField from '../aeico-field';
1
+ import AeicoField, { type FieldAction } from '../aeico-field';
2
2
  import type { InferProps, Props } from 'aeico';
3
3
  import { html } from 'aeico';
4
4
  import styleVariables from '../styles/variables.css?inline';
5
5
  import sizeCSS from '../styles/size.css?inline';
6
6
  import colorCSS from '../styles/color.css?inline';
7
+ import fieldLabelCSS from '../styles/components/field-label.css?inline';
8
+ import actionButtonCSS from '../styles/components/action-button.css?inline';
7
9
  import styles from '../styles/components/checkbox.css?inline';
8
10
  import { CheckboxVariant } from './defines';
9
11
 
10
- class Checkbox extends AeicoField {
12
+ class Checkbox extends AeicoField<boolean> {
11
13
  protected fieldElement: HTMLInputElement | null = null;
12
14
 
13
15
  static tagName = 'checkbox';
@@ -22,7 +24,14 @@ class Checkbox extends AeicoField {
22
24
  declare defaultChecked?: boolean;
23
25
  declare variant?: CheckboxVariant;
24
26
 
25
- protected static styles = [styleVariables, sizeCSS, colorCSS, styles];
27
+ protected static styles = [
28
+ styleVariables,
29
+ sizeCSS,
30
+ colorCSS,
31
+ fieldLabelCSS,
32
+ actionButtonCSS,
33
+ styles,
34
+ ];
26
35
 
27
36
  protected getValue(): boolean {
28
37
  return this.fieldElement?.checked ?? false;
@@ -34,11 +43,11 @@ class Checkbox extends AeicoField {
34
43
  }
35
44
  }
36
45
 
37
- protected getEventPayload(checked: boolean, oldChecked: boolean, action: any) {
46
+ protected getEventPayload(checked: boolean, oldChecked: boolean, action: FieldAction) {
38
47
  return { checked, oldChecked, action };
39
48
  }
40
49
 
41
- protected setValue(checked: boolean, options?: { silent?: boolean; action?: any }): void {
50
+ protected setValue(checked: boolean, options?: { silent?: boolean; action?: FieldAction }): void {
42
51
  const oldChecked = this.getValue();
43
52
  this.checked = checked;
44
53
  this.writeValue(checked);
@@ -62,18 +71,24 @@ class Checkbox extends AeicoField {
62
71
 
63
72
  render() {
64
73
  return html(({ div, input }) => {
65
- div({ className: 'checkbox-container', variant: this.variant }, () => {
74
+ const id = this.getFieldId();
75
+ this.renderLabel(id);
76
+ div({ className: 'checkbox-container field-body', variant: this.variant }, () => {
66
77
  div({ className: 'checkbox-wrapper' }, () => {
67
78
  this.fieldElement = input({
79
+ id,
68
80
  type: 'checkbox',
69
81
  className: 'field-input',
70
82
  checked: Boolean(this.checked),
71
83
  disabled: Boolean(this.disabled),
84
+ required: Boolean(this.required),
72
85
  '@change': this.boundOnChange,
73
86
  });
74
87
  });
75
88
  this.renderActionButtons();
76
89
  });
90
+ this.renderHelperText();
91
+ this.renderError();
77
92
  });
78
93
  }
79
94
  }
@@ -0,0 +1,146 @@
1
+ import type { InferProps } from 'aeico';
2
+ import styleVariables from '../styles/variables.css?inline';
3
+ import sizeCSS from '../styles/size.css?inline';
4
+ import colorCSS from '../styles/color.css?inline';
5
+ import copyButtonStyle from '../styles/components/copy-button.css?inline';
6
+ import AeicoComponent from '../aeico-component';
7
+ import { html } from 'aeico';
8
+ import { prop } from 'aeico';
9
+ // Ensure ae-icon and ae-tooltip are registered
10
+ import '../icon/icon';
11
+ import '../tooltip/tooltip';
12
+ import type Tooltip from '../tooltip/tooltip';
13
+ import type { TooltipPlacement } from '../tooltip/defines';
14
+ import type { CopyButtonColor, CopyButtonSize, CopyButtonVariant } from './defines';
15
+
16
+ class CopyButton extends AeicoComponent {
17
+ protected static styles = [styleVariables, sizeCSS, colorCSS, copyButtonStyle];
18
+
19
+ @prop({ type: String })
20
+ accessor text: string | undefined;
21
+
22
+ @prop({ type: String })
23
+ accessor color: CopyButtonColor | undefined;
24
+
25
+ @prop({ type: String })
26
+ accessor variant: CopyButtonVariant | undefined;
27
+
28
+ @prop({ type: String })
29
+ accessor size: CopyButtonSize | undefined;
30
+
31
+ @prop({ type: Boolean })
32
+ accessor disabled: boolean | undefined;
33
+
34
+ @prop({ type: Number })
35
+ accessor duration: number | undefined;
36
+
37
+ @prop({ type: String })
38
+ accessor tooltip: string = 'Copy';
39
+
40
+ @prop({ type: String })
41
+ accessor tooltipCopied: string = 'Copied!';
42
+
43
+ @prop({ type: String })
44
+ accessor tooltipPlacement: TooltipPlacement = 'top';
45
+
46
+ private _slotElement: HTMLSlotElement | null = null;
47
+ private _tooltipEl: Tooltip | null = null;
48
+ private _resetTimer: ReturnType<typeof setTimeout> | null = null;
49
+
50
+ private _getTextToCopy(): string {
51
+ if (this.text != null) return this.text;
52
+ const nodes = this._slotElement?.assignedNodes({ flatten: true }) ?? [];
53
+ return nodes
54
+ .filter((n) => n.nodeType === Node.TEXT_NODE)
55
+ .map((n) => n.textContent ?? '')
56
+ .join('')
57
+ .trim();
58
+ }
59
+
60
+ private _handleClick = () => {
61
+ if (this.disabled) return;
62
+
63
+ const textToCopy = this._getTextToCopy();
64
+
65
+ void navigator.clipboard.writeText(textToCopy).then(() => {
66
+ this.setAttribute('copied', '');
67
+
68
+ if (this._tooltipEl) {
69
+ this._tooltipEl.content = this.tooltipCopied;
70
+ this._tooltipEl.open = true;
71
+ }
72
+
73
+ if (this._resetTimer !== null) {
74
+ clearTimeout(this._resetTimer);
75
+ }
76
+ const duration = this.duration ?? 2000;
77
+ this._resetTimer = setTimeout(() => {
78
+ this.removeAttribute('copied');
79
+ if (this._tooltipEl) {
80
+ this._tooltipEl.content = this.tooltip;
81
+ this._tooltipEl.open = false;
82
+ }
83
+ this._resetTimer = null;
84
+ }, duration);
85
+
86
+ this.dispatchEvent(
87
+ new CustomEvent('copy', {
88
+ bubbles: true,
89
+ composed: true,
90
+ detail: { text: textToCopy },
91
+ }),
92
+ );
93
+ });
94
+ };
95
+
96
+ protected onUnmounted() {
97
+ if (this._resetTimer !== null) {
98
+ clearTimeout(this._resetTimer);
99
+ this._resetTimer = null;
100
+ }
101
+ }
102
+
103
+ protected render() {
104
+ return html(({ aeTooltip, button, span, slot, aeIcon }) => {
105
+ this._tooltipEl = aeTooltip(
106
+ {
107
+ content: this.tooltip,
108
+ placement: this.tooltipPlacement,
109
+ disabled: this.disabled,
110
+ },
111
+ () => {
112
+ button(
113
+ {
114
+ type: 'button',
115
+ disabled: this.disabled,
116
+ part: 'button',
117
+ 'aria-label': this.tooltip,
118
+ 'aria-disabled': this.disabled,
119
+ '@click': this._handleClick,
120
+ },
121
+ () => {
122
+ span({ className: 'icon-copy' }, () => {
123
+ aeIcon({ name: 'copy' });
124
+ });
125
+ span({ className: 'icon-check' }, () => {
126
+ aeIcon({ name: 'check' });
127
+ });
128
+ this._slotElement = slot();
129
+ },
130
+ );
131
+ },
132
+ );
133
+ });
134
+ }
135
+ }
136
+
137
+ CopyButton.register();
138
+
139
+ declare global {
140
+ interface HTMLElementTagNameMap {
141
+ 'ae-copy-button': CopyButton;
142
+ }
143
+ }
144
+
145
+ export default CopyButton;
146
+ export type CopyButtonProps = InferProps<typeof CopyButton>;
@@ -0,0 +1,5 @@
1
+ export type {
2
+ ButtonColor as CopyButtonColor,
3
+ ButtonVariant as CopyButtonVariant,
4
+ ButtonSize as CopyButtonSize,
5
+ } from '../button/defines';
@@ -0,0 +1,3 @@
1
+ export { default, default as CopyButton } from './copy-button';
2
+ export type { CopyButtonProps } from './copy-button';
3
+ export type { CopyButtonColor, CopyButtonSize, CopyButtonVariant } from './defines';
@@ -1,4 +1,5 @@
1
1
  export type DetailVariant = 'subtle' | 'faint' | 'filled' | 'outlined';
2
+ export type DetailIconPlacement = 'start' | 'end';
2
3
  export type DetailColor =
3
4
  | 'default'
4
5
  | 'primary'
@@ -5,7 +5,7 @@ import detailStyle from '../styles/components/detail.css?inline';
5
5
  import AeicoComponent from '../aeico-component';
6
6
  import { html } from 'aeico';
7
7
  import { prop } from 'aeico';
8
- import type { DetailColor, DetailVariant } from './defines';
8
+ import type { DetailColor, DetailVariant, DetailIconPlacement } from './defines';
9
9
 
10
10
  /**
11
11
  * Detail component that can be used to show/hide additional content.
@@ -35,6 +35,9 @@ class Detail extends AeicoComponent {
35
35
  @prop({ type: Boolean })
36
36
  accessor disabled: boolean = false;
37
37
 
38
+ @prop({ type: String })
39
+ accessor iconPlacement: DetailIconPlacement = 'end';
40
+
38
41
  private _open: boolean = false;
39
42
 
40
43
  /** Opens the detail panel. */
@@ -120,3 +123,4 @@ declare global {
120
123
 
121
124
  export default Detail;
122
125
  export type DetailProps = InferProps<typeof Detail>;
126
+ export type { DetailIconPlacement };