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
@@ -3,6 +3,8 @@ import type { InferProps, Props } from 'aeico';
3
3
  import { html } from 'aeico';
4
4
  import variables from '../styles/variables.css?inline';
5
5
  import sizeCSS from '../styles/size.css?inline';
6
+ import fieldLabelCSS from '../styles/components/field-label.css?inline';
7
+ import actionButtonCSS from '../styles/components/action-button.css?inline';
6
8
  import style from '../styles/components/text-input.css?inline';
7
9
 
8
10
  class TextInput extends AeicoField {
@@ -18,37 +20,32 @@ class TextInput extends AeicoField {
18
20
  declare placeholder?: string;
19
21
  declare type?: string;
20
22
 
21
- protected static styles = [variables, sizeCSS, style];
23
+ protected static styles = [variables, sizeCSS, fieldLabelCSS, actionButtonCSS, style];
22
24
 
23
25
  render() {
24
26
  return html(({ div, input }) => {
25
- div({ className: 'input-container' }, () => {
27
+ const id = this.getFieldId();
28
+ this.renderLabel(id);
29
+ div({ className: 'input-container field-body' }, () => {
26
30
  this.fieldElement = input({
31
+ id,
27
32
  type: this.type || 'text',
28
33
  placeholder: this.placeholder || '',
34
+ required: Boolean(this.required),
29
35
  '@input': this.boundOnChange,
30
36
  });
31
37
 
32
38
  this.renderActionButtons();
33
39
  });
40
+ this.renderHelperText();
41
+ this.renderError();
34
42
 
35
43
  if (this.fieldElement && this.value != null) {
36
44
  this.fieldElement.value = String(this.value);
37
45
  }
38
- this.updateClearButtonVisibility();
39
46
  });
40
47
  }
41
48
 
42
- /**
43
- * Update clear button visibility based on input value
44
- */
45
- private updateClearButtonVisibility() {
46
- if (this.clearBtn && this.fieldElement) {
47
- const hasValue = this.fieldElement.value.length > 0;
48
- this.clearBtn.style.display = hasValue ? '' : 'none';
49
- }
50
- }
51
-
52
49
  /**
53
50
  * Write value to the input element (DOM only)
54
51
  */
@@ -58,8 +55,6 @@ class TextInput extends AeicoField {
58
55
  if (this.fieldElement) {
59
56
  this.fieldElement.value = strValue;
60
57
  }
61
-
62
- this.updateClearButtonVisibility();
63
58
  }
64
59
  }
65
60
 
@@ -0,0 +1 @@
1
+ export { default, type TextareaProps, type TextareaResize } from './textarea';
@@ -0,0 +1,107 @@
1
+ import AeicoField from '../aeico-field';
2
+ import type { InferProps } from 'aeico';
3
+ import { html, prop } from 'aeico';
4
+ import variables from '../styles/variables.css?inline';
5
+ import sizeCSS from '../styles/size.css?inline';
6
+ import fieldLabelCSS from '../styles/components/field-label.css?inline';
7
+ import actionButtonCSS from '../styles/components/action-button.css?inline';
8
+ import style from '../styles/components/textarea.css?inline';
9
+
10
+ export type TextareaResize = 'none' | 'vertical' | 'horizontal' | 'both';
11
+
12
+ class Textarea extends AeicoField {
13
+ protected fieldElement: HTMLTextAreaElement | null = null;
14
+
15
+ static tagName = 'textarea';
16
+
17
+ @prop({ type: String })
18
+ accessor placeholder: string | undefined;
19
+
20
+ @prop({ type: Number })
21
+ accessor rows: number | undefined;
22
+
23
+ @prop({ type: Number })
24
+ accessor maxlength: number | undefined;
25
+
26
+ @prop({ type: Number })
27
+ accessor minlength: number | undefined;
28
+
29
+ @prop({ type: String })
30
+ accessor resize: TextareaResize | undefined;
31
+
32
+ @prop({ type: Boolean })
33
+ accessor autoResize: boolean = false;
34
+
35
+ protected static styles = [variables, sizeCSS, fieldLabelCSS, actionButtonCSS, style];
36
+
37
+ private readonly _boundOnInput = () => {
38
+ if (this.autoResize && this.fieldElement) {
39
+ this._syncAutoResize(this.fieldElement);
40
+ }
41
+ this.setValue(this.getValue(), { silent: false, action: 'change' });
42
+ };
43
+
44
+ render() {
45
+ return html(({ div, textarea }) => {
46
+ const id = this.getFieldId();
47
+ this.renderLabel(id);
48
+ div({ className: 'textarea-container field-body' }, () => {
49
+ this.fieldElement = textarea({
50
+ id,
51
+ placeholder: this.placeholder || '',
52
+ rows: this.rows ?? 3,
53
+ required: Boolean(this.required),
54
+ '@input': this._boundOnInput,
55
+ });
56
+ this.renderActionButtons();
57
+ });
58
+ this.renderHelperText();
59
+ this.renderError();
60
+
61
+ if (this.fieldElement) {
62
+ if (this.value != null) {
63
+ this.fieldElement.value = String(this.value);
64
+ }
65
+ if (this.maxlength != null) this.fieldElement.maxLength = this.maxlength;
66
+ if (this.minlength != null) this.fieldElement.minLength = this.minlength;
67
+ this.fieldElement.style.resize = this.autoResize ? 'none' : (this.resize ?? 'vertical');
68
+ if (this.autoResize) {
69
+ this._syncAutoResize(this.fieldElement);
70
+ }
71
+ this._updateClearButtonVisibility();
72
+ }
73
+ });
74
+ }
75
+
76
+ private _syncAutoResize(ta: HTMLTextAreaElement): void {
77
+ ta.style.height = 'auto';
78
+ ta.style.height = `${ta.scrollHeight}px`;
79
+ }
80
+
81
+ private _updateClearButtonVisibility(): void {
82
+ if (this.clearBtn && this.fieldElement) {
83
+ this.clearBtn.style.display = this.fieldElement.value.length > 0 ? '' : 'none';
84
+ }
85
+ }
86
+
87
+ protected writeValue(value: string): void {
88
+ if (this.fieldElement) {
89
+ this.fieldElement.value = String(value || '');
90
+ if (this.autoResize) {
91
+ this._syncAutoResize(this.fieldElement);
92
+ }
93
+ this._updateClearButtonVisibility();
94
+ }
95
+ }
96
+ }
97
+
98
+ Textarea.register();
99
+
100
+ declare global {
101
+ interface HTMLElementTagNameMap {
102
+ 'ae-textarea': Textarea;
103
+ }
104
+ }
105
+
106
+ export default Textarea;
107
+ export type TextareaProps = InferProps<typeof Textarea>;
@@ -0,0 +1,11 @@
1
+ export type TooltipPlacement =
2
+ | 'top'
3
+ | 'top-start'
4
+ | 'top-end'
5
+ | 'bottom'
6
+ | 'bottom-start'
7
+ | 'bottom-end'
8
+ | 'left'
9
+ | 'right';
10
+
11
+ export type TooltipTrigger = 'hover' | 'click';
@@ -0,0 +1,4 @@
1
+ export { default } from './tooltip';
2
+ export { default as Tooltip } from './tooltip';
3
+ export type { TooltipProps } from './tooltip';
4
+ export type { TooltipPlacement, TooltipTrigger } from './defines';
@@ -0,0 +1,183 @@
1
+ import type { InferProps } from 'aeico';
2
+ import styleVariables from '../styles/variables.css?inline';
3
+ import tooltipStyle from '../styles/components/tooltip.css?inline';
4
+ import AeicoComponent from '../aeico-component';
5
+ import { html, prop } from 'aeico';
6
+ import type { TooltipPlacement, TooltipTrigger } from './defines';
7
+
8
+ /**
9
+ * Tooltip Component
10
+ *
11
+ * A floating label that appears on hover or focus around its trigger content.
12
+ * Wrap any element in `<ae-tooltip>` and provide content via the `content`
13
+ * attribute (plain text) or the `tooltip` named slot (rich HTML).
14
+ *
15
+ * @example
16
+ * ```html
17
+ * <ae-tooltip content="Save file">
18
+ * <ae-button>Save</ae-button>
19
+ * </ae-tooltip>
20
+ *
21
+ * <ae-tooltip placement="bottom-start">
22
+ * <span slot="tooltip"><strong>Bold</strong> tip</span>
23
+ * <ae-icon-button name="info"></ae-icon-button>
24
+ * </ae-tooltip>
25
+ * ```
26
+ */
27
+ class Tooltip extends AeicoComponent {
28
+ protected static styles = [styleVariables, tooltipStyle];
29
+
30
+ @prop({ type: String })
31
+ accessor content: string | undefined;
32
+
33
+ @prop({ type: String })
34
+ accessor placement: TooltipPlacement = 'top';
35
+
36
+ @prop({ type: Boolean })
37
+ accessor disabled: boolean = false;
38
+
39
+ @prop({ type: String })
40
+ accessor trigger: TooltipTrigger = 'hover';
41
+
42
+ @prop({ type: Boolean })
43
+ accessor open: boolean = false;
44
+
45
+ private _outsideClickHandler: ((e: MouseEvent) => void) | null = null;
46
+
47
+ connectedCallback() {
48
+ super.connectedCallback();
49
+ this.listen('mouseenter', this._handleMouseEnter);
50
+ this.listen('mouseleave', this._handleMouseLeave);
51
+ this.listen('focusin', this._handleFocusin);
52
+ this.listen('focusout', this._handleFocusout);
53
+ this.listen('click', this._handleClick);
54
+
55
+ this._outsideClickHandler = (e: MouseEvent) => {
56
+ if (!this.open) return;
57
+ if (!e.composedPath().includes(this)) this.open = false;
58
+ };
59
+ document.addEventListener('click', this._outsideClickHandler);
60
+ }
61
+
62
+ disconnectedCallback() {
63
+ super.disconnectedCallback();
64
+ if (this._outsideClickHandler) {
65
+ document.removeEventListener('click', this._outsideClickHandler);
66
+ this._outsideClickHandler = null;
67
+ }
68
+ }
69
+
70
+ private _handleMouseEnter = () => {
71
+ if (this.trigger !== 'hover') return;
72
+ if (this.disabled) return;
73
+ this.open = true;
74
+ };
75
+
76
+ private _handleMouseLeave = () => {
77
+ if (this.trigger !== 'hover') return;
78
+ this.open = false;
79
+ };
80
+
81
+ private _handleFocusin = () => {
82
+ if (this.trigger !== 'hover') return;
83
+ if (this.disabled) return;
84
+ this.open = true;
85
+ };
86
+
87
+ private _handleFocusout = () => {
88
+ if (this.trigger !== 'hover') return;
89
+ this.open = false;
90
+ };
91
+
92
+ private _handleClick = () => {
93
+ if (this.trigger !== 'click') return;
94
+ if (this.disabled) return;
95
+ this.open = !this.open;
96
+ };
97
+
98
+ private _updatePosition() {
99
+ const panel = this.shadowRoot?.querySelector<HTMLElement>('.tooltip-panel');
100
+ if (!panel) return;
101
+
102
+ const host = this.getBoundingClientRect();
103
+ const panelRect = panel.getBoundingClientRect();
104
+ const gap = 6; // Match --ae-tooltip-gap: 6px
105
+ const pw = panelRect.width;
106
+ const ph = panelRect.height;
107
+
108
+ let top: number;
109
+ let left: number;
110
+
111
+ switch (this.placement) {
112
+ case 'top-start':
113
+ top = host.top - ph - gap;
114
+ left = host.left;
115
+ break;
116
+ case 'top-end':
117
+ top = host.top - ph - gap;
118
+ left = host.right - pw;
119
+ break;
120
+ case 'bottom':
121
+ top = host.bottom + gap;
122
+ left = host.left + host.width / 2 - pw / 2;
123
+ break;
124
+ case 'bottom-start':
125
+ top = host.bottom + gap;
126
+ left = host.left;
127
+ break;
128
+ case 'bottom-end':
129
+ top = host.bottom + gap;
130
+ left = host.right - pw;
131
+ break;
132
+ case 'left':
133
+ top = host.top + host.height / 2 - ph / 2;
134
+ left = host.left - pw - gap;
135
+ break;
136
+ case 'right':
137
+ top = host.top + host.height / 2 - ph / 2;
138
+ left = host.right + gap;
139
+ break;
140
+ default: // 'top'
141
+ top = host.top - ph - gap;
142
+ left = host.left + host.width / 2 - pw / 2;
143
+ }
144
+
145
+ panel.style.top = `${top}px`;
146
+ panel.style.left = `${left}px`;
147
+ }
148
+
149
+ protected onUpdated(changedProps: Map<string, unknown>) {
150
+ if (changedProps.has('open') && this.open) {
151
+ this._updatePosition();
152
+ }
153
+ }
154
+
155
+ protected render() {
156
+ return html(({ div, span, slot }) => {
157
+ slot();
158
+ div(
159
+ {
160
+ className: `tooltip-panel placement-${this.placement ?? 'top'}`,
161
+ role: 'tooltip',
162
+ 'aria-hidden': String(!this.open),
163
+ },
164
+ () => {
165
+ slot({ name: 'tooltip' }, () => {
166
+ span({ textContent: this.content ?? '' });
167
+ });
168
+ },
169
+ );
170
+ });
171
+ }
172
+ }
173
+
174
+ Tooltip.register();
175
+
176
+ declare global {
177
+ interface HTMLElementTagNameMap {
178
+ 'ae-tooltip': Tooltip;
179
+ }
180
+ }
181
+
182
+ export type TooltipProps = InferProps<typeof Tooltip>;
183
+ export default Tooltip;
@@ -0,0 +1,26 @@
1
+ /** Minimal interface used by tree-item to read config from its parent ae-tree. */
2
+ export interface ParentTreeLike extends Element {
3
+ checkable?: boolean;
4
+ multiple?: boolean;
5
+ showLine?: boolean;
6
+ defaultExpandAll?: boolean;
7
+ icon?: string;
8
+ }
9
+
10
+ export interface TreeSelectDetail {
11
+ key: string;
12
+ selected: boolean;
13
+ selectedKeys: string[];
14
+ }
15
+
16
+ export interface TreeExpandDetail {
17
+ key: string;
18
+ expanded: boolean;
19
+ expandedKeys: string[];
20
+ }
21
+
22
+ export interface TreeCheckDetail {
23
+ key: string;
24
+ checked: boolean;
25
+ checkedKeys: string[];
26
+ }
@@ -0,0 +1,5 @@
1
+ export { default as Tree } from './tree';
2
+ export { default as TreeItem } from './tree-item';
3
+ export type { TreeProps } from './tree';
4
+ export type { TreeItemProps } from './tree-item';
5
+ export type { TreeSelectDetail, TreeExpandDetail, TreeCheckDetail } from './defines';
@@ -0,0 +1,258 @@
1
+ import AeicoComponent from '../aeico-component';
2
+ import type { InferProps } from 'aeico';
3
+ import { html, prop } from 'aeico';
4
+ import style from '../styles/components/tree-item.css?inline';
5
+ import variables from '../styles/variables.css?inline';
6
+ import type { ParentTreeLike } from './defines';
7
+ import { SVG_NS } from '../utils';
8
+ import '../icon';
9
+
10
+ let _autoKeyCounter = 0;
11
+
12
+ /**
13
+ * Tree item — used as a direct child of `<ae-tree>` or nested inside another
14
+ * `<ae-tree-item>` to create a multi-level tree.
15
+ *
16
+ * - **Parent item**: nest `<ae-tree-item>` children inside; an expand toggle is shown.
17
+ * - **Leaf item**: no `<ae-tree-item>` children; no expand toggle is shown.
18
+ *
19
+ * @prop {string} key - Unique identifier for this item.
20
+ * @prop {string} icon - Icon name (uses `<ae-icon>`). Optional.
21
+ * @prop {boolean} disabled - Disables interaction.
22
+ * @prop {boolean} expanded - Whether children are visible.
23
+ * @prop {boolean} selected - Whether this item is visually selected.
24
+ * @prop {boolean} checked - Checkbox state (checkable mode).
25
+ * @prop {boolean} indeterminate - Checkbox partial state (checkable mode, JS-only).
26
+ *
27
+ * @slot default - Child `<ae-tree-item>` elements.
28
+ * @slot label - Custom label content (falls back to the `label` attribute text).
29
+ */
30
+ class TreeItem extends AeicoComponent {
31
+ static tagName = 'tree-item';
32
+
33
+ protected static styles = [variables, style];
34
+
35
+ @prop({ type: String })
36
+ accessor key: string | undefined;
37
+
38
+ @prop({ type: String })
39
+ accessor icon: string | undefined;
40
+
41
+ /** Stable auto-generated key used when `key` prop is not set. */
42
+ private readonly _autoKey = `ae-tree-item-${_autoKeyCounter++}`;
43
+
44
+ private get _effectiveKey(): string {
45
+ return this.key ?? this._autoKey;
46
+ }
47
+
48
+ @prop({ type: Boolean })
49
+ accessor disabled: boolean = false;
50
+
51
+ @prop({ type: Boolean })
52
+ accessor expanded: boolean = false;
53
+
54
+ @prop({ type: Boolean })
55
+ accessor selected: boolean = false;
56
+
57
+ @prop({ type: Boolean })
58
+ accessor checked: boolean = false;
59
+
60
+ @prop({ type: Boolean })
61
+ accessor indeterminate: boolean = false;
62
+
63
+ private _checkboxEl: HTMLInputElement | null = null;
64
+
65
+ connectedCallback() {
66
+ super.connectedCallback();
67
+
68
+ // Auto-assign slot so users don't need to write slot="sub" manually
69
+ if (
70
+ this.parentElement?.tagName.toLowerCase() === 'ae-tree-item' &&
71
+ !this.hasAttribute('slot')
72
+ ) {
73
+ this.setAttribute('slot', 'sub');
74
+ }
75
+
76
+ let depth = 0;
77
+ let el: Element | null = this.parentElement;
78
+ while (el) {
79
+ const tag = el.tagName.toLowerCase();
80
+ if (tag === 'ae-tree-item') {
81
+ depth++;
82
+ } else if (tag === 'ae-tree') {
83
+ break;
84
+ }
85
+ el = el.parentElement;
86
+ }
87
+ this.style.setProperty('--depth', String(depth));
88
+
89
+ if (this._parentTree?.showLine) {
90
+ this.setAttribute('showline', '');
91
+ }
92
+ }
93
+
94
+ protected onMounted(): void {
95
+ if (this._parentTree?.defaultExpandAll && this._hasChildren) {
96
+ this.expanded = true;
97
+ }
98
+ }
99
+
100
+ private get _parentTree(): ParentTreeLike | null {
101
+ return this.closest<ParentTreeLike>('ae-tree');
102
+ }
103
+
104
+ private get _isCheckable(): boolean {
105
+ return this._parentTree?.checkable ?? false;
106
+ }
107
+
108
+ private get _hasChildren(): boolean {
109
+ return !!this.querySelector(':scope > ae-tree-item[slot="sub"]');
110
+ }
111
+
112
+ private _handleExpandClick = (e: Event): void => {
113
+ e.stopPropagation();
114
+ if (this.disabled) return;
115
+ this.dispatchEvent(
116
+ new CustomEvent('_tree-item-toggle-expand', {
117
+ bubbles: true,
118
+ composed: true,
119
+ detail: { key: this._effectiveKey },
120
+ }),
121
+ );
122
+ };
123
+
124
+ private _handleLabelClick = (): void => {
125
+ if (this.disabled) return;
126
+ this.dispatchEvent(
127
+ new CustomEvent('_tree-item-select', {
128
+ bubbles: true,
129
+ composed: true,
130
+ detail: { key: this._effectiveKey },
131
+ }),
132
+ );
133
+ };
134
+
135
+ private _handleCheckChange = (e: Event): void => {
136
+ if (this.disabled) return;
137
+ const input = e.target as HTMLInputElement;
138
+ this.dispatchEvent(
139
+ new CustomEvent('_tree-item-check', {
140
+ bubbles: true,
141
+ composed: true,
142
+ detail: { key: this._effectiveKey, checked: input.checked },
143
+ }),
144
+ );
145
+ };
146
+
147
+ private _handleKeydown = (e: KeyboardEvent): void => {
148
+ if (e.key === 'Enter' || e.key === ' ') {
149
+ e.preventDefault();
150
+ if ((e.target as HTMLElement).classList.contains('tree-item-label')) {
151
+ this._handleLabelClick();
152
+ }
153
+ }
154
+ if (e.key === 'ArrowRight' && this._hasChildren && !this.expanded) {
155
+ this._handleExpandClick(e);
156
+ }
157
+ if (e.key === 'ArrowLeft' && this.expanded) {
158
+ this._handleExpandClick(e);
159
+ }
160
+ };
161
+
162
+ protected onUpdated(): void {
163
+ // indeterminate/checked cannot be set correctly via HTML attribute — must set via JS property
164
+ if (this._checkboxEl) {
165
+ this._checkboxEl.checked = this.checked;
166
+ this._checkboxEl.indeterminate = this.indeterminate;
167
+ }
168
+ }
169
+
170
+ protected render() {
171
+ const hasChildren = this._hasChildren;
172
+ const isCheckable = this._isCheckable;
173
+ const expandIcon = this.icon ?? this._parentTree?.icon;
174
+
175
+ return html(({ div, button, span, input, slot, svg, path, aeIcon }) => {
176
+ div(
177
+ {
178
+ className: 'tree-item-content',
179
+ role: 'treeitem',
180
+ 'aria-expanded': hasChildren ? String(this.expanded) : undefined,
181
+ 'aria-selected': String(this.selected),
182
+ 'aria-disabled': this.disabled ? 'true' : undefined,
183
+ },
184
+ () => {
185
+ if (hasChildren) {
186
+ button(
187
+ {
188
+ type: 'button',
189
+ className: 'expand-btn',
190
+ tabIndex: -1,
191
+ 'aria-hidden': 'true',
192
+ '@click': this._handleExpandClick,
193
+ },
194
+ () => {
195
+ if (expandIcon) {
196
+ aeIcon({ className: 'expand-icon', name: expandIcon });
197
+ } else {
198
+ svg(
199
+ {
200
+ className: 'expand-icon',
201
+ viewBox: '0 0 10 10',
202
+ 'aria-hidden': 'true',
203
+ xmlns: SVG_NS,
204
+ },
205
+ () => {
206
+ path({ d: 'M2 1l6 4-6 4V1z' });
207
+ },
208
+ );
209
+ }
210
+ },
211
+ );
212
+ } else {
213
+ span({ className: 'expand-placeholder', 'aria-hidden': 'true' });
214
+ }
215
+
216
+ if (isCheckable) {
217
+ this._checkboxEl = input({
218
+ type: 'checkbox',
219
+ className: 'tree-item-checkbox',
220
+ checked: this.checked,
221
+ disabled: this.disabled,
222
+ tabIndex: -1,
223
+ '@change': this._handleCheckChange,
224
+ });
225
+ }
226
+
227
+ button(
228
+ {
229
+ type: 'button',
230
+ className: 'tree-item-label',
231
+ disabled: this.disabled,
232
+ '@click': this._handleLabelClick,
233
+ '@keydown': this._handleKeydown,
234
+ },
235
+ () => {
236
+ slot();
237
+ },
238
+ );
239
+ },
240
+ );
241
+
242
+ div({ className: 'tree-item-children', role: 'group' }, () => {
243
+ slot({ name: 'sub' });
244
+ });
245
+ });
246
+ }
247
+ }
248
+
249
+ TreeItem.register();
250
+
251
+ declare global {
252
+ interface HTMLElementTagNameMap {
253
+ 'ae-tree-item': TreeItem;
254
+ }
255
+ }
256
+
257
+ export default TreeItem;
258
+ export type TreeItemProps = InferProps<typeof TreeItem>;