@keenthemes/ktui 1.2.5 → 1.2.7

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 (196) hide show
  1. package/README.md +14 -5
  2. package/dist/ktui.js +1538 -786
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +85 -5
  6. package/lib/cjs/components/datatable/datatable-checkbox.d.ts +37 -1
  7. package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
  8. package/lib/cjs/components/datatable/datatable-checkbox.js +143 -156
  9. package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
  10. package/lib/cjs/components/datatable/datatable-column-utils.d.ts +30 -0
  11. package/lib/cjs/components/datatable/datatable-column-utils.d.ts.map +1 -0
  12. package/lib/cjs/components/datatable/datatable-column-utils.js +42 -0
  13. package/lib/cjs/components/datatable/datatable-column-utils.js.map +1 -0
  14. package/lib/cjs/components/datatable/datatable-contracts.d.ts +2 -4
  15. package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -1
  16. package/lib/cjs/components/datatable/datatable-defaults.d.ts +20 -0
  17. package/lib/cjs/components/datatable/datatable-defaults.d.ts.map +1 -0
  18. package/lib/cjs/components/datatable/datatable-defaults.js +193 -0
  19. package/lib/cjs/components/datatable/datatable-defaults.js.map +1 -0
  20. package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts +7 -0
  21. package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts.map +1 -0
  22. package/lib/cjs/components/datatable/datatable-layout-plugin.js +338 -0
  23. package/lib/cjs/components/datatable/datatable-layout-plugin.js.map +1 -0
  24. package/lib/cjs/components/datatable/datatable-local-provider.d.ts +2 -2
  25. package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -1
  26. package/lib/cjs/components/datatable/datatable-local-provider.js +85 -27
  27. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -1
  28. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  29. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +13 -13
  30. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
  31. package/lib/cjs/components/datatable/datatable-registry.d.ts +18 -0
  32. package/lib/cjs/components/datatable/datatable-registry.d.ts.map +1 -0
  33. package/lib/cjs/components/datatable/datatable-registry.js +66 -0
  34. package/lib/cjs/components/datatable/datatable-registry.js.map +1 -0
  35. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  36. package/lib/cjs/components/datatable/datatable-remote-provider.js +1 -2
  37. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -1
  38. package/lib/cjs/components/datatable/datatable-search-handler.d.ts +10 -0
  39. package/lib/cjs/components/datatable/datatable-search-handler.d.ts.map +1 -0
  40. package/lib/cjs/components/datatable/datatable-search-handler.js +65 -0
  41. package/lib/cjs/components/datatable/datatable-search-handler.js.map +1 -0
  42. package/lib/cjs/components/datatable/datatable-sort.d.ts +31 -4
  43. package/lib/cjs/components/datatable/datatable-sort.d.ts.map +1 -1
  44. package/lib/cjs/components/datatable/datatable-sort.js +86 -58
  45. package/lib/cjs/components/datatable/datatable-sort.js.map +1 -1
  46. package/lib/cjs/components/datatable/datatable-spinner.d.ts +30 -0
  47. package/lib/cjs/components/datatable/datatable-spinner.d.ts.map +1 -0
  48. package/lib/cjs/components/datatable/datatable-spinner.js +54 -0
  49. package/lib/cjs/components/datatable/datatable-spinner.js.map +1 -0
  50. package/lib/cjs/components/datatable/datatable-state-persistence.d.ts +19 -0
  51. package/lib/cjs/components/datatable/datatable-state-persistence.d.ts.map +1 -0
  52. package/lib/cjs/components/datatable/datatable-state-persistence.js +59 -0
  53. package/lib/cjs/components/datatable/datatable-state-persistence.js.map +1 -0
  54. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +2 -0
  55. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  56. package/lib/cjs/components/datatable/datatable-table-renderer.js +75 -16
  57. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -1
  58. package/lib/cjs/components/datatable/datatable-utils.d.ts +10 -0
  59. package/lib/cjs/components/datatable/datatable-utils.d.ts.map +1 -0
  60. package/lib/cjs/components/datatable/datatable-utils.js +15 -0
  61. package/lib/cjs/components/datatable/datatable-utils.js.map +1 -0
  62. package/lib/cjs/components/datatable/datatable.d.ts +35 -34
  63. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  64. package/lib/cjs/components/datatable/datatable.js +233 -497
  65. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  66. package/lib/cjs/components/datatable/index.d.ts +1 -1
  67. package/lib/cjs/components/datatable/index.d.ts.map +1 -1
  68. package/lib/cjs/components/datatable/types.d.ts +127 -11
  69. package/lib/cjs/components/datatable/types.d.ts.map +1 -1
  70. package/lib/cjs/index.d.ts +1 -1
  71. package/lib/cjs/index.d.ts.map +1 -1
  72. package/lib/cjs/index.js +6 -0
  73. package/lib/cjs/index.js.map +1 -1
  74. package/lib/esm/components/datatable/datatable-checkbox.d.ts +37 -1
  75. package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
  76. package/lib/esm/components/datatable/datatable-checkbox.js +142 -155
  77. package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
  78. package/lib/esm/components/datatable/datatable-column-utils.d.ts +30 -0
  79. package/lib/esm/components/datatable/datatable-column-utils.d.ts.map +1 -0
  80. package/lib/esm/components/datatable/datatable-column-utils.js +38 -0
  81. package/lib/esm/components/datatable/datatable-column-utils.js.map +1 -0
  82. package/lib/esm/components/datatable/datatable-contracts.d.ts +2 -4
  83. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -1
  84. package/lib/esm/components/datatable/datatable-defaults.d.ts +20 -0
  85. package/lib/esm/components/datatable/datatable-defaults.d.ts.map +1 -0
  86. package/lib/esm/components/datatable/datatable-defaults.js +190 -0
  87. package/lib/esm/components/datatable/datatable-defaults.js.map +1 -0
  88. package/lib/esm/components/datatable/datatable-layout-plugin.d.ts +7 -0
  89. package/lib/esm/components/datatable/datatable-layout-plugin.d.ts.map +1 -0
  90. package/lib/esm/components/datatable/datatable-layout-plugin.js +334 -0
  91. package/lib/esm/components/datatable/datatable-layout-plugin.js.map +1 -0
  92. package/lib/esm/components/datatable/datatable-local-provider.d.ts +2 -2
  93. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -1
  94. package/lib/esm/components/datatable/datatable-local-provider.js +85 -27
  95. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -1
  96. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  97. package/lib/esm/components/datatable/datatable-pagination-renderer.js +13 -13
  98. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
  99. package/lib/esm/components/datatable/datatable-registry.d.ts +18 -0
  100. package/lib/esm/components/datatable/datatable-registry.d.ts.map +1 -0
  101. package/lib/esm/components/datatable/datatable-registry.js +63 -0
  102. package/lib/esm/components/datatable/datatable-registry.js.map +1 -0
  103. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  104. package/lib/esm/components/datatable/datatable-remote-provider.js +1 -2
  105. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -1
  106. package/lib/esm/components/datatable/datatable-search-handler.d.ts +10 -0
  107. package/lib/esm/components/datatable/datatable-search-handler.d.ts.map +1 -0
  108. package/lib/esm/components/datatable/datatable-search-handler.js +62 -0
  109. package/lib/esm/components/datatable/datatable-search-handler.js.map +1 -0
  110. package/lib/esm/components/datatable/datatable-sort.d.ts +31 -4
  111. package/lib/esm/components/datatable/datatable-sort.d.ts.map +1 -1
  112. package/lib/esm/components/datatable/datatable-sort.js +85 -57
  113. package/lib/esm/components/datatable/datatable-sort.js.map +1 -1
  114. package/lib/esm/components/datatable/datatable-spinner.d.ts +30 -0
  115. package/lib/esm/components/datatable/datatable-spinner.d.ts.map +1 -0
  116. package/lib/esm/components/datatable/datatable-spinner.js +51 -0
  117. package/lib/esm/components/datatable/datatable-spinner.js.map +1 -0
  118. package/lib/esm/components/datatable/datatable-state-persistence.d.ts +19 -0
  119. package/lib/esm/components/datatable/datatable-state-persistence.d.ts.map +1 -0
  120. package/lib/esm/components/datatable/datatable-state-persistence.js +55 -0
  121. package/lib/esm/components/datatable/datatable-state-persistence.js.map +1 -0
  122. package/lib/esm/components/datatable/datatable-table-renderer.d.ts +2 -0
  123. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  124. package/lib/esm/components/datatable/datatable-table-renderer.js +75 -16
  125. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -1
  126. package/lib/esm/components/datatable/datatable-utils.d.ts +10 -0
  127. package/lib/esm/components/datatable/datatable-utils.d.ts.map +1 -0
  128. package/lib/esm/components/datatable/datatable-utils.js +12 -0
  129. package/lib/esm/components/datatable/datatable-utils.js.map +1 -0
  130. package/lib/esm/components/datatable/datatable.d.ts +35 -34
  131. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  132. package/lib/esm/components/datatable/datatable.js +235 -499
  133. package/lib/esm/components/datatable/datatable.js.map +1 -1
  134. package/lib/esm/components/datatable/index.d.ts +1 -1
  135. package/lib/esm/components/datatable/index.d.ts.map +1 -1
  136. package/lib/esm/components/datatable/types.d.ts +127 -11
  137. package/lib/esm/components/datatable/types.d.ts.map +1 -1
  138. package/lib/esm/index.d.ts +1 -1
  139. package/lib/esm/index.d.ts.map +1 -1
  140. package/lib/esm/index.js +6 -0
  141. package/lib/esm/index.js.map +1 -1
  142. package/package.json +5 -1
  143. package/skills/ktui/SKILL.md +711 -0
  144. package/skills/ktui-datatable/SKILL.md +302 -0
  145. package/skills/ktui-install/SKILL.md +150 -0
  146. package/skills/ktui-select/SKILL.md +271 -0
  147. package/src/components/__tests__/component.test.ts +347 -0
  148. package/src/components/collapse/collapse.css +2 -2
  149. package/src/components/datatable/__tests__/architecture-boundaries.test.ts +56 -8
  150. package/src/components/datatable/__tests__/currency-sort.test.ts +25 -28
  151. package/src/components/datatable/__tests__/datatable-checkbox.test.ts +527 -0
  152. package/src/components/datatable/__tests__/datatable-column-utils.test.ts +117 -0
  153. package/src/components/datatable/__tests__/datatable-defaults.test.ts +57 -0
  154. package/src/components/datatable/__tests__/datatable-finalize-extended.test.ts +361 -0
  155. package/src/components/datatable/__tests__/datatable-fixed-layout.test.ts +427 -0
  156. package/src/components/datatable/__tests__/datatable-improvements.test.ts +484 -0
  157. package/src/components/datatable/__tests__/datatable-pagination-extended.test.ts +508 -0
  158. package/src/components/datatable/__tests__/datatable-public-api.test.ts +269 -0
  159. package/src/components/datatable/__tests__/datatable-registry.test.ts +172 -0
  160. package/src/components/datatable/__tests__/datatable-remote-provider.test.ts +468 -0
  161. package/src/components/datatable/__tests__/datatable-search-handler.test.ts +124 -0
  162. package/src/components/datatable/__tests__/datatable-sort-extended.test.ts +417 -0
  163. package/src/components/datatable/__tests__/datatable-spinner.test.ts +95 -0
  164. package/src/components/datatable/__tests__/datatable-table-renderer-extended.test.ts +425 -0
  165. package/src/components/datatable/__tests__/datatable-types.test.ts +117 -0
  166. package/src/components/datatable/__tests__/datatable-utils.test.ts +52 -0
  167. package/src/components/datatable/__tests__/locked-layout.test.ts +257 -0
  168. package/src/components/datatable/__tests__/multi-row-headers.test.ts +7 -7
  169. package/src/components/datatable/__tests__/pagination-reset.test.ts +147 -6
  170. package/src/components/datatable/__tests__/race-conditions.test.ts +11 -11
  171. package/src/components/datatable/__tests__/setup.ts +12 -4
  172. package/src/components/datatable/datatable-checkbox.ts +139 -143
  173. package/src/components/datatable/datatable-column-utils.ts +63 -0
  174. package/src/components/datatable/datatable-contracts.ts +2 -3
  175. package/src/components/datatable/datatable-defaults.ts +204 -0
  176. package/src/components/datatable/datatable-layout-plugin.ts +459 -0
  177. package/src/components/datatable/datatable-local-provider.ts +106 -35
  178. package/src/components/datatable/datatable-pagination-renderer.ts +13 -15
  179. package/src/components/datatable/datatable-registry.ts +89 -0
  180. package/src/components/datatable/datatable-remote-provider.ts +1 -3
  181. package/src/components/datatable/datatable-search-handler.ts +97 -0
  182. package/src/components/datatable/datatable-sort.ts +111 -66
  183. package/src/components/datatable/datatable-spinner.ts +103 -0
  184. package/src/components/datatable/datatable-state-persistence.ts +67 -0
  185. package/src/components/datatable/datatable-table-renderer.ts +81 -18
  186. package/src/components/datatable/datatable-utils.ts +12 -0
  187. package/src/components/datatable/datatable.css +98 -0
  188. package/src/components/datatable/datatable.ts +288 -583
  189. package/src/components/datatable/index.ts +8 -0
  190. package/src/components/datatable/types.ts +157 -23
  191. package/src/helpers/__tests__/dom.test.ts +776 -0
  192. package/src/helpers/__tests__/utils.test.ts +332 -0
  193. package/src/index.ts +15 -0
  194. package/skills/ktui-components/SKILL.md +0 -41
  195. package/skills/ktui-theming/SKILL.md +0 -50
  196. package/src/components/datatable/datatable-event-adapter.ts +0 -21
@@ -0,0 +1,776 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import KTDom from '../dom';
3
+
4
+ describe('KTDom', () => {
5
+ beforeEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ describe('isRTL', () => {
10
+ it('returns true when dir attribute is rtl', () => {
11
+ document.documentElement.setAttribute('dir', 'rtl');
12
+ expect(KTDom.isRTL()).toBe(true);
13
+ document.documentElement.removeAttribute('dir');
14
+ });
15
+
16
+ it('returns false when dir attribute is not rtl', () => {
17
+ document.documentElement.setAttribute('dir', 'ltr');
18
+ expect(KTDom.isRTL()).toBe(false);
19
+ document.documentElement.removeAttribute('dir');
20
+ });
21
+
22
+ it('returns false when no dir attribute', () => {
23
+ document.documentElement.removeAttribute('dir');
24
+ expect(KTDom.isRTL()).toBe(false);
25
+ });
26
+ });
27
+
28
+ describe('isElement', () => {
29
+ it('returns true for HTMLElement', () => {
30
+ expect(KTDom.isElement(document.createElement('div'))).toBe(true);
31
+ });
32
+
33
+ it('returns false for null', () => {
34
+ expect(KTDom.isElement(null)).toBe(false);
35
+ });
36
+
37
+ it('returns false for string', () => {
38
+ expect(KTDom.isElement('div')).toBe(false);
39
+ });
40
+
41
+ it('returns false for plain object', () => {
42
+ expect(KTDom.isElement({})).toBe(false);
43
+ });
44
+ });
45
+
46
+ describe('getElement', () => {
47
+ it('returns element by string selector', () => {
48
+ const el = document.createElement('div');
49
+ el.id = 'test';
50
+ document.body.appendChild(el);
51
+ expect(KTDom.getElement('#test')).toBe(el);
52
+ });
53
+
54
+ it('returns element when passed an HTMLElement', () => {
55
+ const el = document.createElement('div');
56
+ expect(KTDom.getElement(el)).toBe(el);
57
+ });
58
+
59
+ it('returns null for empty string', () => {
60
+ expect(KTDom.getElement('')).toBeNull();
61
+ });
62
+
63
+ it('returns null for null', () => {
64
+ expect(KTDom.getElement(null)).toBeNull();
65
+ });
66
+
67
+ it('returns null when selector matches nothing', () => {
68
+ expect(KTDom.getElement('#nonexistent')).toBeNull();
69
+ });
70
+ });
71
+
72
+ describe('remove', () => {
73
+ it('removes element from parent', () => {
74
+ const parent = document.createElement('div');
75
+ const child = document.createElement('span');
76
+ parent.appendChild(child);
77
+ document.body.appendChild(parent);
78
+
79
+ KTDom.remove(child);
80
+ expect(parent.children.length).toBe(0);
81
+ });
82
+
83
+ it('does nothing when element has no parent', () => {
84
+ const el = document.createElement('div');
85
+ expect(() => KTDom.remove(el)).not.toThrow();
86
+ });
87
+ });
88
+
89
+ describe('hasClass', () => {
90
+ it('returns true when element has class', () => {
91
+ const el = document.createElement('div');
92
+ el.className = 'foo bar';
93
+ expect(KTDom.hasClass(el, 'foo')).toBe(true);
94
+ });
95
+
96
+ it('returns false when element lacks class', () => {
97
+ const el = document.createElement('div');
98
+ el.className = 'foo';
99
+ expect(KTDom.hasClass(el, 'bar')).toBe(false);
100
+ });
101
+
102
+ it('returns true when all space-separated classes present', () => {
103
+ const el = document.createElement('div');
104
+ el.className = 'foo bar baz';
105
+ expect(KTDom.hasClass(el, 'foo bar')).toBe(true);
106
+ });
107
+
108
+ it('returns false when any space-separated class is missing', () => {
109
+ const el = document.createElement('div');
110
+ el.className = 'foo baz';
111
+ expect(KTDom.hasClass(el, 'foo bar')).toBe(false);
112
+ });
113
+ });
114
+
115
+ describe('addClass', () => {
116
+ it('adds multiple space-separated classes', () => {
117
+ const el = document.createElement('div');
118
+ KTDom.addClass(el, 'c1 c2');
119
+ expect(el.classList.contains('c1')).toBe(true);
120
+ expect(el.classList.contains('c2')).toBe(true);
121
+ });
122
+
123
+ it('does not duplicate existing class', () => {
124
+ const el = document.createElement('div');
125
+ el.className = 'c1';
126
+ KTDom.addClass(el, 'c1 c2');
127
+ expect(el.className).toBe('c1 c2');
128
+ });
129
+
130
+ it('ignores empty class names from split', () => {
131
+ const el = document.createElement('div');
132
+ KTDom.addClass(el, 'c1 c2');
133
+ expect(el.classList.contains('c1')).toBe(true);
134
+ expect(el.classList.contains('c2')).toBe(true);
135
+ });
136
+ });
137
+
138
+ describe('removeClass', () => {
139
+ it('removes multiple space-separated classes', () => {
140
+ const el = document.createElement('div');
141
+ el.className = 'c1 c2 c3';
142
+ KTDom.removeClass(el, 'c1 c2');
143
+ expect(el.classList.contains('c1')).toBe(false);
144
+ expect(el.classList.contains('c2')).toBe(false);
145
+ expect(el.classList.contains('c3')).toBe(true);
146
+ });
147
+
148
+ it('does nothing when class is not present', () => {
149
+ const el = document.createElement('div');
150
+ el.className = 'c1';
151
+ KTDom.removeClass(el, 'c2');
152
+ expect(el.className).toBe('c1');
153
+ });
154
+ });
155
+
156
+ describe('getCssProp', () => {
157
+ it('returns computed style property value', () => {
158
+ const el = document.createElement('div');
159
+ document.body.appendChild(el);
160
+ vi.spyOn(window, 'getComputedStyle').mockReturnValue({
161
+ getPropertyValue: (prop: string) =>
162
+ prop === 'color' ? 'red' : '',
163
+ } as unknown as CSSStyleDeclaration);
164
+
165
+ expect(KTDom.getCssProp(el, 'color')).toBe('red');
166
+ vi.restoreAllMocks();
167
+ });
168
+
169
+ it('returns empty string for null element', () => {
170
+ expect(KTDom.getCssProp(null as unknown as HTMLElement, 'color')).toBe('');
171
+ });
172
+ });
173
+
174
+ describe('setCssProp', () => {
175
+ it('sets property via getComputedStyle().setProperty', () => {
176
+ const el = document.createElement('div');
177
+ const mockSetProperty = vi.fn();
178
+ vi.spyOn(window, 'getComputedStyle').mockReturnValue({
179
+ setProperty: mockSetProperty,
180
+ } as unknown as CSSStyleDeclaration);
181
+
182
+ KTDom.setCssProp(el, 'color', 'blue');
183
+ expect(mockSetProperty).toHaveBeenCalledWith('color', 'blue');
184
+ vi.restoreAllMocks();
185
+ });
186
+
187
+ it('does nothing for null element', () => {
188
+ expect(() =>
189
+ KTDom.setCssProp(null as unknown as HTMLElement, 'color', 'blue'),
190
+ ).not.toThrow();
191
+ });
192
+ });
193
+
194
+ describe('offset', () => {
195
+ it('returns bounding rect offsets', () => {
196
+ const el = document.createElement('div');
197
+ vi.spyOn(el, 'getBoundingClientRect').mockReturnValue({
198
+ top: 10,
199
+ left: 20,
200
+ right: 100,
201
+ bottom: 50,
202
+ width: 80,
203
+ height: 40,
204
+ } as DOMRect);
205
+ Object.defineProperty(window, 'innerWidth', { value: 1024, configurable: true });
206
+ Object.defineProperty(window, 'innerHeight', { value: 768, configurable: true });
207
+
208
+ const result = KTDom.offset(el);
209
+ expect(result).toEqual({
210
+ top: 10,
211
+ left: 20,
212
+ right: 924,
213
+ bottom: 758,
214
+ });
215
+ });
216
+
217
+ it('returns zeros for null element', () => {
218
+ const result = KTDom.offset(null as unknown as HTMLElement);
219
+ expect(result).toEqual({ top: 0, left: 0, right: 0, bottom: 0 });
220
+ });
221
+ });
222
+
223
+ describe('getIndex', () => {
224
+ it('returns index of element among siblings', () => {
225
+ const parent = document.createElement('div');
226
+ const c0 = document.createElement('span');
227
+ const c1 = document.createElement('span');
228
+ const c2 = document.createElement('span');
229
+ parent.append(c0, c1, c2);
230
+ expect(KTDom.getIndex(c1)).toBe(1);
231
+ });
232
+
233
+ it('returns 0 for first child', () => {
234
+ const parent = document.createElement('div');
235
+ const child = document.createElement('span');
236
+ parent.appendChild(child);
237
+ expect(KTDom.getIndex(child)).toBe(0);
238
+ });
239
+ });
240
+
241
+ describe('parents', () => {
242
+ it('returns all ancestor elements when no selector', () => {
243
+ const grandparent = document.createElement('div');
244
+ const parent = document.createElement('div');
245
+ const child = document.createElement('span');
246
+ parent.appendChild(child);
247
+ grandparent.appendChild(parent);
248
+ document.body.appendChild(grandparent);
249
+
250
+ const result = KTDom.parents(child, '');
251
+ expect(result).toContain(parent);
252
+ expect(result).toContain(grandparent);
253
+ expect(result).toContain(document.body);
254
+ });
255
+
256
+ it('returns only ancestors matching selector', () => {
257
+ const grandparent = document.createElement('div');
258
+ grandparent.className = 'target';
259
+ const parent = document.createElement('div');
260
+ const child = document.createElement('span');
261
+ parent.appendChild(child);
262
+ grandparent.appendChild(parent);
263
+ document.body.appendChild(grandparent);
264
+
265
+ const result = KTDom.parents(child, '.target');
266
+ expect(result).toHaveLength(1);
267
+ expect(result[0]).toBe(grandparent);
268
+ });
269
+
270
+ it('returns empty array for element with no parent', () => {
271
+ const el = document.createElement('div');
272
+ expect(KTDom.parents(el, '')).toEqual([]);
273
+ });
274
+ });
275
+
276
+ describe('siblings', () => {
277
+ it('returns all sibling elements', () => {
278
+ const parent = document.createElement('div');
279
+ const c0 = document.createElement('span');
280
+ const c1 = document.createElement('span');
281
+ const c2 = document.createElement('span');
282
+ parent.append(c0, c1, c2);
283
+
284
+ const result = KTDom.siblings(c1);
285
+ expect(result).toContain(c0);
286
+ expect(result).toContain(c2);
287
+ expect(result).not.toContain(c1);
288
+ });
289
+
290
+ it('returns empty array when element has no parent', () => {
291
+ const el = document.createElement('div');
292
+ expect(KTDom.siblings(el)).toEqual([]);
293
+ });
294
+
295
+ it('returns empty array for only child', () => {
296
+ const parent = document.createElement('div');
297
+ const child = document.createElement('span');
298
+ parent.appendChild(child);
299
+ expect(KTDom.siblings(child)).toEqual([]);
300
+ });
301
+ });
302
+
303
+ describe('children', () => {
304
+ it('returns children matching selector', () => {
305
+ const parent = document.createElement('div');
306
+ const c1 = document.createElement('span');
307
+ c1.className = 'match';
308
+ const c2 = document.createElement('span');
309
+ const c3 = document.createElement('span');
310
+ c3.className = 'match';
311
+ parent.append(c1, c2, c3);
312
+
313
+ const result = KTDom.children(parent, '.match');
314
+ expect(result).toHaveLength(2);
315
+ expect(result[0]).toBe(c1);
316
+ expect(result[1]).toBe(c3);
317
+ });
318
+
319
+ it('returns empty array for element with no children', () => {
320
+ const el = document.createElement('div');
321
+ expect(KTDom.children(el, '.any')).toEqual([]);
322
+ });
323
+
324
+ it('returns empty array for null element', () => {
325
+ expect(
326
+ KTDom.children(null as unknown as HTMLElement, '.any'),
327
+ ).toEqual([]);
328
+ });
329
+ });
330
+
331
+ describe('child', () => {
332
+ it('returns first child matching selector', () => {
333
+ const parent = document.createElement('div');
334
+ const c1 = document.createElement('span');
335
+ c1.className = 'match';
336
+ const c2 = document.createElement('span');
337
+ c2.className = 'match';
338
+ parent.append(c1, c2);
339
+
340
+ expect(KTDom.child(parent, '.match')).toBe(c1);
341
+ });
342
+
343
+ it('returns undefined when no children match', () => {
344
+ const parent = document.createElement('div');
345
+ expect(KTDom.child(parent, '.none')).toBeUndefined();
346
+ });
347
+ });
348
+
349
+ describe('isVisible', () => {
350
+ it('returns true for visible element', () => {
351
+ const el = document.createElement('div');
352
+ document.body.appendChild(el);
353
+ vi.spyOn(el, 'getClientRects').mockReturnValue({
354
+ length: 1,
355
+ } as unknown as DOMRectList);
356
+ vi.spyOn(window, 'getComputedStyle').mockReturnValue({
357
+ getPropertyValue: (prop: string) =>
358
+ prop === 'visibility' ? 'visible' : '',
359
+ } as unknown as CSSStyleDeclaration);
360
+
361
+ expect(KTDom.isVisible(el)).toBe(true);
362
+ vi.restoreAllMocks();
363
+ });
364
+
365
+ it('returns false for element with no client rects', () => {
366
+ const el = document.createElement('div');
367
+ vi.spyOn(el, 'getClientRects').mockReturnValue({
368
+ length: 0,
369
+ } as unknown as DOMRectList);
370
+ expect(KTDom.isVisible(el)).toBe(false);
371
+ });
372
+
373
+ it('returns false for non-element', () => {
374
+ expect(KTDom.isVisible('text' as unknown as HTMLElement)).toBe(false);
375
+ });
376
+
377
+ it('returns false when visibility is hidden', () => {
378
+ const el = document.createElement('div');
379
+ document.body.appendChild(el);
380
+ vi.spyOn(el, 'getClientRects').mockReturnValue({
381
+ length: 1,
382
+ } as unknown as DOMRectList);
383
+ vi.spyOn(window, 'getComputedStyle').mockReturnValue({
384
+ getPropertyValue: (prop: string) =>
385
+ prop === 'visibility' ? 'hidden' : '',
386
+ } as unknown as CSSStyleDeclaration);
387
+
388
+ expect(KTDom.isVisible(el)).toBe(false);
389
+ vi.restoreAllMocks();
390
+ });
391
+ });
392
+
393
+ describe('isDisabled', () => {
394
+ it('returns true for disabled input', () => {
395
+ const el = document.createElement('input');
396
+ el.disabled = true;
397
+ expect(KTDom.isDisabled(el)).toBe(true);
398
+ });
399
+
400
+ it('returns false for enabled input', () => {
401
+ const el = document.createElement('input');
402
+ el.disabled = false;
403
+ expect(KTDom.isDisabled(el)).toBe(false);
404
+ });
405
+
406
+ it('returns true for element with disabled class', () => {
407
+ const el = document.createElement('button');
408
+ el.className = 'disabled';
409
+ expect(KTDom.isDisabled(el)).toBe(true);
410
+ });
411
+
412
+ it('returns true for null element', () => {
413
+ expect(KTDom.isDisabled(null as unknown as HTMLInputElement)).toBe(true);
414
+ });
415
+
416
+ it('returns true when disabled attribute is "true"', () => {
417
+ const el = document.createElement('button');
418
+ el.setAttribute('disabled', 'true');
419
+ expect(KTDom.isDisabled(el)).toBe(true);
420
+ });
421
+
422
+ it('returns true when disabled attribute is present even if "false"', () => {
423
+ const el = document.createElement('button');
424
+ el.setAttribute('disabled', 'false');
425
+ // The DOM disabled property is a boolean: presence of the attribute makes it true
426
+ expect(KTDom.isDisabled(el)).toBe(true);
427
+ });
428
+ });
429
+
430
+ describe('transitionEnd', () => {
431
+ it('calls callback after transition duration', () => {
432
+ vi.useFakeTimers();
433
+ const el = document.createElement('div');
434
+ vi.spyOn(window, 'getComputedStyle').mockReturnValue({
435
+ transitionDuration: '0.3s',
436
+ } as unknown as CSSStyleDeclaration);
437
+
438
+ const cb = vi.fn();
439
+ KTDom.transitionEnd(el, cb);
440
+ expect(cb).not.toHaveBeenCalled();
441
+ vi.advanceTimersByTime(300);
442
+ expect(cb).toHaveBeenCalledTimes(1);
443
+ vi.useRealTimers();
444
+ vi.restoreAllMocks();
445
+ });
446
+ });
447
+
448
+ describe('animationEnd', () => {
449
+ it('calls callback after animation duration', () => {
450
+ vi.useFakeTimers();
451
+ const el = document.createElement('div');
452
+ vi.spyOn(window, 'getComputedStyle').mockReturnValue({
453
+ animationDuration: '0.5s',
454
+ } as unknown as CSSStyleDeclaration);
455
+
456
+ const cb = vi.fn();
457
+ KTDom.animationEnd(el, cb);
458
+ expect(cb).not.toHaveBeenCalled();
459
+ vi.advanceTimersByTime(500);
460
+ expect(cb).toHaveBeenCalledTimes(1);
461
+ vi.useRealTimers();
462
+ vi.restoreAllMocks();
463
+ });
464
+ });
465
+
466
+ describe('getCSSTransitionDuration', () => {
467
+ it('returns duration in milliseconds', () => {
468
+ const el = document.createElement('div');
469
+ vi.spyOn(window, 'getComputedStyle').mockReturnValue({
470
+ transitionDuration: '0.3s',
471
+ } as unknown as CSSStyleDeclaration);
472
+ expect(KTDom.getCSSTransitionDuration(el)).toBe(300);
473
+ vi.restoreAllMocks();
474
+ });
475
+ });
476
+
477
+ describe('getCSSAnimationDuration', () => {
478
+ it('returns duration in milliseconds', () => {
479
+ const el = document.createElement('div');
480
+ vi.spyOn(window, 'getComputedStyle').mockReturnValue({
481
+ animationDuration: '1s',
482
+ } as unknown as CSSStyleDeclaration);
483
+ expect(KTDom.getCSSAnimationDuration(el)).toBe(1000);
484
+ vi.restoreAllMocks();
485
+ });
486
+ });
487
+
488
+ describe('reflow', () => {
489
+ it('triggers reflow by accessing offsetHeight', () => {
490
+ const el = document.createElement('div');
491
+ const spy = vi.spyOn(el, 'offsetHeight', 'get').mockReturnValue(0);
492
+ KTDom.reflow(el);
493
+ expect(spy).toHaveBeenCalled();
494
+ spy.mockRestore();
495
+ });
496
+ });
497
+
498
+ describe('insertAfter', () => {
499
+ it('inserts new element after reference node', () => {
500
+ const parent = document.createElement('div');
501
+ const ref = document.createElement('span');
502
+ const newEl = document.createElement('p');
503
+ parent.appendChild(ref);
504
+ document.body.appendChild(parent);
505
+
506
+ KTDom.insertAfter(newEl, ref);
507
+ expect(parent.children[1]).toBe(newEl);
508
+ });
509
+
510
+ it('does nothing when reference node has no parent', () => {
511
+ const ref = document.createElement('div');
512
+ const newEl = document.createElement('div');
513
+ expect(() => KTDom.insertAfter(newEl, ref)).not.toThrow();
514
+ });
515
+ });
516
+
517
+ describe('getHighestZindex', () => {
518
+ it('returns 1 for element with no positioned ancestors', () => {
519
+ const el = document.createElement('div');
520
+ document.body.appendChild(el);
521
+ expect(KTDom.getHighestZindex(el)).toBe(1);
522
+ });
523
+
524
+ it('returns z-index of positioned ancestor', () => {
525
+ const parent = document.createElement('div');
526
+ parent.style.position = 'absolute';
527
+ parent.style.zIndex = '50';
528
+ const child = document.createElement('div');
529
+ parent.appendChild(child);
530
+ document.body.appendChild(parent);
531
+ expect(KTDom.getHighestZindex(child)).toBe(50);
532
+ });
533
+
534
+ it('ignores z-index of 0', () => {
535
+ const parent = document.createElement('div');
536
+ parent.style.position = 'relative';
537
+ parent.style.zIndex = '0';
538
+ const child = document.createElement('div');
539
+ parent.appendChild(child);
540
+ document.body.appendChild(parent);
541
+ expect(KTDom.getHighestZindex(child)).toBe(1);
542
+ });
543
+
544
+ it('returns 1 for null element', () => {
545
+ expect(KTDom.getHighestZindex(null as unknown as HTMLElement)).toBe(1);
546
+ });
547
+ });
548
+
549
+ describe('isParentOrElementHidden', () => {
550
+ it('returns true when element has display none', () => {
551
+ const el = document.createElement('div');
552
+ el.style.display = 'none';
553
+ document.body.appendChild(el);
554
+ expect(KTDom.isParentOrElementHidden(el)).toBe(true);
555
+ });
556
+
557
+ it('returns false when element and parents are visible', () => {
558
+ const el = document.createElement('div');
559
+ document.body.appendChild(el);
560
+ expect(KTDom.isParentOrElementHidden(el)).toBe(false);
561
+ });
562
+
563
+ it('returns false for null element', () => {
564
+ expect(KTDom.isParentOrElementHidden(null)).toBe(false);
565
+ });
566
+
567
+ it('returns true when parent has display none', () => {
568
+ const parent = document.createElement('div');
569
+ parent.style.display = 'none';
570
+ const child = document.createElement('div');
571
+ parent.appendChild(child);
572
+ document.body.appendChild(parent);
573
+ expect(KTDom.isParentOrElementHidden(child)).toBe(true);
574
+ });
575
+ });
576
+
577
+ describe('getViewPort', () => {
578
+ it('returns width and height', () => {
579
+ Object.defineProperty(window, 'innerWidth', { value: 1024, configurable: true });
580
+ Object.defineProperty(window, 'innerHeight', { value: 768, configurable: true });
581
+ const result = KTDom.getViewPort();
582
+ expect(result).toEqual({ width: 1024, height: 768 });
583
+ });
584
+ });
585
+
586
+ describe('getScrollTop', () => {
587
+ it('returns scrollTop value', () => {
588
+ const result = KTDom.getScrollTop();
589
+ expect(typeof result).toBe('number');
590
+ });
591
+ });
592
+
593
+ describe('isInViewport', () => {
594
+ it('returns true for element fully in viewport', () => {
595
+ const el = document.createElement('div');
596
+ vi.spyOn(el, 'getBoundingClientRect').mockReturnValue({
597
+ top: 0,
598
+ left: 0,
599
+ bottom: 100,
600
+ right: 100,
601
+ } as DOMRect);
602
+ Object.defineProperty(window, 'innerHeight', { value: 768, configurable: true });
603
+ Object.defineProperty(window, 'innerWidth', { value: 1024, configurable: true });
604
+
605
+ expect(KTDom.isInViewport(el)).toBe(true);
606
+ });
607
+
608
+ it('returns false for element below viewport', () => {
609
+ const el = document.createElement('div');
610
+ vi.spyOn(el, 'getBoundingClientRect').mockReturnValue({
611
+ top: 800,
612
+ left: 0,
613
+ bottom: 900,
614
+ right: 100,
615
+ } as DOMRect);
616
+ Object.defineProperty(window, 'innerHeight', { value: 768, configurable: true });
617
+ Object.defineProperty(window, 'innerWidth', { value: 1024, configurable: true });
618
+
619
+ expect(KTDom.isInViewport(el)).toBe(false);
620
+ });
621
+ });
622
+
623
+ describe('isPartiallyInViewport', () => {
624
+ it('returns true for partially visible element', () => {
625
+ const el = document.createElement('div');
626
+ vi.spyOn(el, 'getBoundingClientRect').mockReturnValue({
627
+ top: 700,
628
+ left: 0,
629
+ } as DOMRect);
630
+ Object.defineProperty(el, 'clientWidth', { value: 100, configurable: true });
631
+ Object.defineProperty(el, 'clientHeight', { value: 100, configurable: true });
632
+ Object.defineProperty(window, 'innerHeight', { value: 768, configurable: true });
633
+ Object.defineProperty(window, 'innerWidth', { value: 1024, configurable: true });
634
+
635
+ expect(KTDom.isPartiallyInViewport(el)).toBe(true);
636
+ });
637
+
638
+ it('returns false for element completely out of viewport', () => {
639
+ const el = document.createElement('div');
640
+ vi.spyOn(el, 'getBoundingClientRect').mockReturnValue({
641
+ top: -200,
642
+ left: -200,
643
+ } as DOMRect);
644
+ Object.defineProperty(el, 'clientWidth', { value: 50, configurable: true });
645
+ Object.defineProperty(el, 'clientHeight', { value: 50, configurable: true });
646
+ Object.defineProperty(window, 'innerHeight', { value: 768, configurable: true });
647
+ Object.defineProperty(window, 'innerWidth', { value: 1024, configurable: true });
648
+
649
+ expect(KTDom.isPartiallyInViewport(el)).toBe(false);
650
+ });
651
+ });
652
+
653
+ describe('isVisibleInParent', () => {
654
+ it('returns true for child within parent bounds', () => {
655
+ const parent = document.createElement('div');
656
+ const child = document.createElement('div');
657
+ parent.appendChild(child);
658
+ document.body.appendChild(parent);
659
+
660
+ vi.spyOn(child, 'getBoundingClientRect').mockReturnValue({
661
+ top: 10,
662
+ left: 10,
663
+ bottom: 50,
664
+ right: 50,
665
+ } as DOMRect);
666
+ vi.spyOn(parent, 'getBoundingClientRect').mockReturnValue({
667
+ top: 0,
668
+ left: 0,
669
+ bottom: 100,
670
+ right: 100,
671
+ } as DOMRect);
672
+ Object.defineProperty(child, 'offsetParent', { value: parent, configurable: true });
673
+ vi.spyOn(window, 'getComputedStyle').mockReturnValue({
674
+ visibility: 'visible',
675
+ display: 'block',
676
+ } as unknown as CSSStyleDeclaration);
677
+
678
+ expect(KTDom.isVisibleInParent(child, parent)).toBe(true);
679
+ vi.restoreAllMocks();
680
+ });
681
+
682
+ it('returns false when child has display none', () => {
683
+ const parent = document.createElement('div');
684
+ const child = document.createElement('div');
685
+ parent.appendChild(child);
686
+
687
+ Object.defineProperty(child, 'offsetParent', { value: null, configurable: true });
688
+ vi.spyOn(window, 'getComputedStyle').mockReturnValue({
689
+ visibility: 'visible',
690
+ display: 'none',
691
+ } as unknown as CSSStyleDeclaration);
692
+
693
+ expect(KTDom.isVisibleInParent(child, parent)).toBe(false);
694
+ vi.restoreAllMocks();
695
+ });
696
+ });
697
+
698
+ describe('getRelativeTopPosition', () => {
699
+ it('returns relative top position', () => {
700
+ const parent = document.createElement('div');
701
+ const child = document.createElement('div');
702
+ parent.appendChild(child);
703
+
704
+ vi.spyOn(child, 'getBoundingClientRect').mockReturnValue({
705
+ top: 50,
706
+ } as DOMRect);
707
+ vi.spyOn(parent, 'getBoundingClientRect').mockReturnValue({
708
+ top: 20,
709
+ } as DOMRect);
710
+
711
+ expect(KTDom.getRelativeTopPosition(child, parent)).toBe(30);
712
+ });
713
+ });
714
+
715
+ describe('getDataAttributes', () => {
716
+ it('returns parsed data attributes with prefix', () => {
717
+ const el = document.createElement('div');
718
+ el.setAttribute('data-kt-foo-bar', 'baz');
719
+ el.setAttribute('data-kt-foo-count', '42');
720
+ el.setAttribute('data-other', 'ignored');
721
+
722
+ const result = KTDom.getDataAttributes(el, 'kt-foo') as Record<
723
+ string,
724
+ unknown
725
+ >;
726
+ expect(result.bar).toBe('baz');
727
+ expect(result.count).toBe(42);
728
+ expect(result).not.toHaveProperty('other');
729
+ });
730
+
731
+ it('returns empty object for null element', () => {
732
+ expect(
733
+ KTDom.getDataAttributes(null as unknown as HTMLElement, 'kt-'),
734
+ ).toEqual({});
735
+ });
736
+
737
+ it('returns empty object when no matching attributes', () => {
738
+ const el = document.createElement('div');
739
+ el.setAttribute('data-other', 'value');
740
+ expect(KTDom.getDataAttributes(el, 'kt-foo')).toEqual({});
741
+ });
742
+ });
743
+
744
+ describe('ready', () => {
745
+ it('calls callback immediately when DOM is ready', () => {
746
+ const cb = vi.fn();
747
+ KTDom.ready(cb);
748
+ expect(cb).toHaveBeenCalled();
749
+ });
750
+
751
+ it('adds event listener when DOM is loading', () => {
752
+ const origReadyState = Object.getOwnPropertyDescriptor(
753
+ document,
754
+ 'readyState',
755
+ );
756
+ Object.defineProperty(document, 'readyState', {
757
+ value: 'loading',
758
+ configurable: true,
759
+ });
760
+
761
+ const addSpy = vi.spyOn(document, 'addEventListener');
762
+ const cb = vi.fn();
763
+ KTDom.ready(cb);
764
+ expect(addSpy).toHaveBeenCalledWith(
765
+ 'DOMContentLoaded',
766
+ expect.any(Function),
767
+ );
768
+ expect(cb).not.toHaveBeenCalled();
769
+
770
+ if (origReadyState) {
771
+ Object.defineProperty(document, 'readyState', origReadyState);
772
+ }
773
+ addSpy.mockRestore();
774
+ });
775
+ });
776
+ });