@keenthemes/ktui 1.2.3 → 1.2.4

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 (141) hide show
  1. package/dist/ktui.js +1681 -957
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +110 -1
  5. package/lib/cjs/components/context-menu/context-menu.d.ts +66 -0
  6. package/lib/cjs/components/context-menu/context-menu.d.ts.map +1 -0
  7. package/lib/cjs/components/context-menu/context-menu.js +423 -0
  8. package/lib/cjs/components/context-menu/context-menu.js.map +1 -0
  9. package/lib/cjs/components/context-menu/index.d.ts +7 -0
  10. package/lib/cjs/components/context-menu/index.d.ts.map +1 -0
  11. package/lib/cjs/components/context-menu/index.js +10 -0
  12. package/lib/cjs/components/context-menu/index.js.map +1 -0
  13. package/lib/cjs/components/context-menu/types.d.ts +30 -0
  14. package/lib/cjs/components/context-menu/types.d.ts.map +1 -0
  15. package/lib/cjs/components/context-menu/types.js +7 -0
  16. package/lib/cjs/components/context-menu/types.js.map +1 -0
  17. package/lib/cjs/components/datatable/datatable-contracts.d.ts +66 -0
  18. package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -0
  19. package/lib/cjs/components/datatable/datatable-contracts.js +7 -0
  20. package/lib/cjs/components/datatable/datatable-contracts.js.map +1 -0
  21. package/lib/cjs/components/datatable/datatable-event-adapter.d.ts +7 -0
  22. package/lib/cjs/components/datatable/datatable-event-adapter.d.ts.map +1 -0
  23. package/lib/cjs/components/datatable/datatable-event-adapter.js +16 -0
  24. package/lib/cjs/components/datatable/datatable-event-adapter.js.map +1 -0
  25. package/lib/cjs/components/datatable/datatable-local-provider.d.ts +25 -0
  26. package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -0
  27. package/lib/cjs/components/datatable/datatable-local-provider.js +184 -0
  28. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -0
  29. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts +15 -0
  30. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
  31. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +128 -0
  32. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -0
  33. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts +25 -0
  34. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -0
  35. package/lib/cjs/components/datatable/datatable-remote-provider.js +188 -0
  36. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -0
  37. package/lib/cjs/components/datatable/datatable-state-store.d.ts +21 -0
  38. package/lib/cjs/components/datatable/datatable-state-store.d.ts.map +1 -0
  39. package/lib/cjs/components/datatable/datatable-state-store.js +81 -0
  40. package/lib/cjs/components/datatable/datatable-state-store.js.map +1 -0
  41. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +16 -0
  42. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -0
  43. package/lib/cjs/components/datatable/datatable-table-renderer.js +133 -0
  44. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -0
  45. package/lib/cjs/components/datatable/datatable.d.ts +9 -87
  46. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  47. package/lib/cjs/components/datatable/datatable.js +114 -686
  48. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  49. package/lib/cjs/components/select/index.d.ts +1 -1
  50. package/lib/cjs/components/select/index.d.ts.map +1 -1
  51. package/lib/cjs/index.d.ts +5 -1
  52. package/lib/cjs/index.d.ts.map +1 -1
  53. package/lib/cjs/index.js +7 -7
  54. package/lib/cjs/index.js.map +1 -1
  55. package/lib/cjs/init-all.d.ts +6 -0
  56. package/lib/cjs/init-all.d.ts.map +1 -0
  57. package/lib/cjs/init-all.js +17 -0
  58. package/lib/cjs/init-all.js.map +1 -0
  59. package/lib/cjs/legacy.d.ts +8 -0
  60. package/lib/cjs/legacy.d.ts.map +1 -0
  61. package/lib/cjs/legacy.js +26 -0
  62. package/lib/cjs/legacy.js.map +1 -0
  63. package/lib/esm/components/context-menu/context-menu.d.ts +66 -0
  64. package/lib/esm/components/context-menu/context-menu.d.ts.map +1 -0
  65. package/lib/esm/components/context-menu/context-menu.js +420 -0
  66. package/lib/esm/components/context-menu/context-menu.js.map +1 -0
  67. package/lib/esm/components/context-menu/index.d.ts +7 -0
  68. package/lib/esm/components/context-menu/index.d.ts.map +1 -0
  69. package/lib/esm/components/context-menu/index.js +6 -0
  70. package/lib/esm/components/context-menu/index.js.map +1 -0
  71. package/lib/esm/components/context-menu/types.d.ts +30 -0
  72. package/lib/esm/components/context-menu/types.d.ts.map +1 -0
  73. package/lib/esm/components/context-menu/types.js +6 -0
  74. package/lib/esm/components/context-menu/types.js.map +1 -0
  75. package/lib/esm/components/datatable/datatable-contracts.d.ts +66 -0
  76. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -0
  77. package/lib/esm/components/datatable/datatable-contracts.js +6 -0
  78. package/lib/esm/components/datatable/datatable-contracts.js.map +1 -0
  79. package/lib/esm/components/datatable/datatable-event-adapter.d.ts +7 -0
  80. package/lib/esm/components/datatable/datatable-event-adapter.d.ts.map +1 -0
  81. package/lib/esm/components/datatable/datatable-event-adapter.js +13 -0
  82. package/lib/esm/components/datatable/datatable-event-adapter.js.map +1 -0
  83. package/lib/esm/components/datatable/datatable-local-provider.d.ts +25 -0
  84. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -0
  85. package/lib/esm/components/datatable/datatable-local-provider.js +181 -0
  86. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -0
  87. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts +15 -0
  88. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
  89. package/lib/esm/components/datatable/datatable-pagination-renderer.js +125 -0
  90. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -0
  91. package/lib/esm/components/datatable/datatable-remote-provider.d.ts +25 -0
  92. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -0
  93. package/lib/esm/components/datatable/datatable-remote-provider.js +185 -0
  94. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -0
  95. package/lib/esm/components/datatable/datatable-state-store.d.ts +21 -0
  96. package/lib/esm/components/datatable/datatable-state-store.d.ts.map +1 -0
  97. package/lib/esm/components/datatable/datatable-state-store.js +78 -0
  98. package/lib/esm/components/datatable/datatable-state-store.js.map +1 -0
  99. package/lib/esm/components/datatable/datatable-table-renderer.d.ts +16 -0
  100. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -0
  101. package/lib/esm/components/datatable/datatable-table-renderer.js +130 -0
  102. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -0
  103. package/lib/esm/components/datatable/datatable.d.ts +9 -87
  104. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  105. package/lib/esm/components/datatable/datatable.js +114 -686
  106. package/lib/esm/components/datatable/datatable.js.map +1 -1
  107. package/lib/esm/components/select/index.d.ts +1 -1
  108. package/lib/esm/components/select/index.d.ts.map +1 -1
  109. package/lib/esm/index.d.ts +5 -1
  110. package/lib/esm/index.d.ts.map +1 -1
  111. package/lib/esm/index.js +4 -5
  112. package/lib/esm/index.js.map +1 -1
  113. package/lib/esm/init-all.d.ts +6 -0
  114. package/lib/esm/init-all.d.ts.map +1 -0
  115. package/lib/esm/init-all.js +13 -0
  116. package/lib/esm/init-all.js.map +1 -0
  117. package/lib/esm/legacy.d.ts +8 -0
  118. package/lib/esm/legacy.d.ts.map +1 -0
  119. package/lib/esm/legacy.js +8 -0
  120. package/lib/esm/legacy.js.map +1 -0
  121. package/package.json +34 -4
  122. package/src/__tests__/entrypoints.test.ts +71 -0
  123. package/src/components/context-menu/__tests__/context-menu.test.ts +117 -0
  124. package/src/components/context-menu/context-menu.css +32 -0
  125. package/src/components/context-menu/context-menu.ts +529 -0
  126. package/src/components/context-menu/index.ts +10 -0
  127. package/src/components/context-menu/types.ts +32 -0
  128. package/src/components/datatable/__tests__/architecture-boundaries.test.ts +259 -0
  129. package/src/components/datatable/datatable-contracts.ts +96 -0
  130. package/src/components/datatable/datatable-event-adapter.ts +21 -0
  131. package/src/components/datatable/datatable-local-provider.ts +194 -0
  132. package/src/components/datatable/datatable-pagination-renderer.ts +211 -0
  133. package/src/components/datatable/datatable-remote-provider.ts +175 -0
  134. package/src/components/datatable/datatable-state-store.ts +94 -0
  135. package/src/components/datatable/datatable-table-renderer.ts +206 -0
  136. package/src/components/datatable/datatable.ts +128 -839
  137. package/src/components/select/index.ts +1 -1
  138. package/src/index.ts +9 -5
  139. package/src/init-all.ts +15 -0
  140. package/src/legacy.ts +9 -0
  141. package/styles.css +1 -0
@@ -0,0 +1,529 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ import {
7
+ Instance as PopperInstance,
8
+ createPopper,
9
+ Placement,
10
+ VirtualElement,
11
+ } from '@popperjs/core';
12
+ import KTDom from '../../helpers/dom';
13
+ import KTData from '../../helpers/data';
14
+ import KTEventHandler from '../../helpers/event-handler';
15
+ import KTComponent from '../component';
16
+ import { KTContextMenuConfigInterface, KTContextMenuInterface } from './types';
17
+
18
+ declare global {
19
+ interface Window {
20
+ KT_CONTEXT_MENU_INITIALIZED: boolean;
21
+ KTContextMenu: typeof KTContextMenu;
22
+ }
23
+ }
24
+
25
+ type Point = { x: number; y: number };
26
+
27
+ export class KTContextMenu
28
+ extends KTComponent
29
+ implements KTContextMenuInterface
30
+ {
31
+ protected override _name = 'context-menu';
32
+ protected override _defaultConfig: KTContextMenuConfigInterface = {
33
+ zindex: 105,
34
+ placement: 'right-start',
35
+ placementRtl: 'left-start',
36
+ permanent: false,
37
+ dismiss: true,
38
+ keyboard: true,
39
+ offset: '0px, 4px',
40
+ offsetRtl: '0px, 4px',
41
+ hiddenClass: 'hidden',
42
+ container: '',
43
+ preventNativeMenu: true,
44
+ flip: true,
45
+ overflow: false,
46
+ };
47
+ protected override _config: KTContextMenuConfigInterface =
48
+ this._defaultConfig;
49
+ protected _menuElement: HTMLElement;
50
+ protected _targetElement: HTMLElement;
51
+ protected _isTransitioning = false;
52
+ protected _isOpen = false;
53
+ protected _shownAt = 0;
54
+ protected _lastPoint: Point = { x: 0, y: 0 };
55
+
56
+ constructor(element: HTMLElement, config?: KTContextMenuConfigInterface) {
57
+ super();
58
+
59
+ if (KTData.has(element, this._name)) return;
60
+
61
+ this._init(element);
62
+ this._buildConfig(config);
63
+ if (!this._element) return;
64
+
65
+ this._targetElement = this._resolveTargetElement(this._element);
66
+ if (!this._targetElement) return;
67
+
68
+ this._menuElement = this._resolveMenuElement(this._element);
69
+ if (!this._menuElement) return;
70
+
71
+ KTData.set(this._menuElement, 'contextMenuElement', this._element);
72
+ this._setupNestedSubmenus();
73
+ this._handleContainer();
74
+ }
75
+
76
+ protected _resolveTargetElement(root: HTMLElement): HTMLElement {
77
+ const selector = root.getAttribute('data-kt-context-menu-target') || '';
78
+ if (selector) {
79
+ const target = document.querySelector(selector) as HTMLElement | null;
80
+ if (target) return target;
81
+ }
82
+
83
+ const localTarget = root.querySelector(
84
+ '[data-kt-context-menu-trigger]',
85
+ ) as HTMLElement | null;
86
+ const localToggle = root.querySelector(
87
+ '[data-kt-context-menu-toggle]',
88
+ ) as HTMLElement | null;
89
+ return localTarget || localToggle || root;
90
+ }
91
+
92
+ protected _resolveMenuElement(root: HTMLElement): HTMLElement {
93
+ const menu = root.querySelector(
94
+ '[data-kt-context-menu-menu]',
95
+ ) as HTMLElement | null;
96
+ return menu;
97
+ }
98
+
99
+ protected _handleContainer(): void {
100
+ const container = this._getOption('container');
101
+ if (!container) return;
102
+
103
+ if (container === 'body') {
104
+ document.body.appendChild(this._menuElement);
105
+ } else {
106
+ document
107
+ .querySelector(container as string)
108
+ ?.appendChild(this._menuElement);
109
+ }
110
+ }
111
+
112
+ protected _setupNestedSubmenus(): void {
113
+ const submenus = this._menuElement.querySelectorAll(
114
+ '[data-kt-context-menu]',
115
+ );
116
+ submenus.forEach((submenu) => {
117
+ new KTContextMenu(submenu as HTMLElement);
118
+ });
119
+ }
120
+
121
+ protected _updatePoint(x: number, y: number): void {
122
+ this._lastPoint = { x, y };
123
+ }
124
+
125
+ protected _toggleAtEvent(event: MouseEvent): void {
126
+ if (this._isOpen) {
127
+ this._hide();
128
+ return;
129
+ }
130
+
131
+ this._showAt(event.clientX, event.clientY);
132
+ }
133
+
134
+ protected _showAtEvent(event: MouseEvent): void {
135
+ event.stopPropagation();
136
+ if ((this._getOption('preventNativeMenu') as boolean) === true) {
137
+ event.preventDefault();
138
+ }
139
+
140
+ this._toggleAtEvent(event);
141
+ }
142
+
143
+ protected _showAt(x: number, y: number): void {
144
+ if (this._isOpen || this._isTransitioning) return;
145
+
146
+ const payload = { cancel: false };
147
+ this._fireEvent('show', payload);
148
+ this._dispatchEvent('show', payload);
149
+ if (payload.cancel) return;
150
+
151
+ KTContextMenu.hide(this._element);
152
+ this._updatePoint(x, y);
153
+
154
+ let zIndex = parseInt(this._getOption('zindex') as string, 10);
155
+ const parentZindex = KTDom.getHighestZindex(this._targetElement);
156
+ if (parentZindex !== null && parentZindex >= zIndex) {
157
+ zIndex = parentZindex + 1;
158
+ }
159
+ if (zIndex > 0) {
160
+ this._menuElement.style.zIndex = zIndex.toString();
161
+ }
162
+
163
+ this._menuElement.style.display = 'block';
164
+ this._menuElement.style.opacity = '0';
165
+ KTDom.reflow(this._menuElement);
166
+ this._menuElement.style.opacity = '1';
167
+ this._menuElement.classList.remove(
168
+ this._getOption('hiddenClass') as string,
169
+ );
170
+ this._menuElement.classList.add('open');
171
+ this._element.classList.add('open');
172
+
173
+ this._initPopper();
174
+
175
+ KTDom.transitionEnd(this._menuElement, () => {
176
+ this._isTransitioning = false;
177
+ this._isOpen = true;
178
+ this._fireEvent('shown');
179
+ this._dispatchEvent('shown');
180
+ });
181
+ this._shownAt = Date.now();
182
+ }
183
+
184
+ protected _hide(): void {
185
+ if (!this._isOpen || this._isTransitioning) return;
186
+
187
+ const payload = { cancel: false };
188
+ this._fireEvent('hide', payload);
189
+ this._dispatchEvent('hide', payload);
190
+ if (payload.cancel) return;
191
+
192
+ this._menuElement.style.opacity = '1';
193
+ KTDom.reflow(this._menuElement);
194
+ this._menuElement.style.opacity = '0';
195
+ this._menuElement.classList.remove('open');
196
+ this._element.classList.remove('open');
197
+
198
+ KTDom.transitionEnd(this._menuElement, () => {
199
+ this._isTransitioning = false;
200
+ this._isOpen = false;
201
+ this._menuElement.classList.add(this._getOption('hiddenClass') as string);
202
+ this._menuElement.style.display = '';
203
+ this._menuElement.style.zIndex = '';
204
+ this._destroyPopper();
205
+ this._fireEvent('hidden');
206
+ this._dispatchEvent('hidden');
207
+ });
208
+ }
209
+
210
+ protected _getVirtualReference(): VirtualElement {
211
+ const point = this._lastPoint;
212
+ return {
213
+ getBoundingClientRect: () =>
214
+ ({
215
+ x: point.x,
216
+ y: point.y,
217
+ top: point.y,
218
+ left: point.x,
219
+ right: point.x,
220
+ bottom: point.y,
221
+ width: 0,
222
+ height: 0,
223
+ toJSON: () => ({}),
224
+ }) as DOMRect,
225
+ };
226
+ }
227
+
228
+ protected _initPopper(): void {
229
+ const popper = createPopper(
230
+ this._getVirtualReference(),
231
+ this._menuElement,
232
+ this._getPopperConfig(),
233
+ );
234
+ KTData.set(this._element, 'popper', popper);
235
+ }
236
+
237
+ protected _destroyPopper(): void {
238
+ if (KTData.has(this._element, 'popper')) {
239
+ (KTData.get(this._element, 'popper') as PopperInstance).destroy();
240
+ KTData.remove(this._element, 'popper');
241
+ }
242
+ }
243
+
244
+ protected _getPopperConfig(): object {
245
+ const isRtl = KTDom.isRTL();
246
+ let placement = this._getOption('placement') as Placement;
247
+ if (isRtl && this._getOption('placementRtl')) {
248
+ placement = this._getOption('placementRtl') as Placement;
249
+ }
250
+
251
+ let offsetValue = this._getOption('offset');
252
+ if (isRtl && this._getOption('offsetRtl')) {
253
+ offsetValue = this._getOption('offsetRtl') as string;
254
+ }
255
+ const offset = offsetValue
256
+ ? offsetValue
257
+ .toString()
258
+ .split(',')
259
+ .map((value) => parseInt(value.trim(), 10))
260
+ : [0, 0];
261
+
262
+ const strategy =
263
+ this._getOption('overflow') === true ? 'absolute' : 'fixed';
264
+ const altAxis = this._getOption('flip') !== false;
265
+ return {
266
+ placement,
267
+ strategy,
268
+ modifiers: [
269
+ { name: 'offset', options: { offset } },
270
+ { name: 'preventOverflow', options: { altAxis } },
271
+ { name: 'flip', options: { flipVariations: false } },
272
+ ],
273
+ };
274
+ }
275
+
276
+ protected _isContextMenuOpen(): boolean {
277
+ return (
278
+ this._element.classList.contains('open') &&
279
+ this._menuElement.classList.contains('open')
280
+ );
281
+ }
282
+
283
+ public showAt(x: number, y: number): void {
284
+ this._showAt(x, y);
285
+ }
286
+
287
+ public showAtEvent(event: MouseEvent): void {
288
+ this._showAtEvent(event);
289
+ }
290
+
291
+ public hide(): void {
292
+ this._hide();
293
+ }
294
+
295
+ public toggleAtEvent(event: MouseEvent): void {
296
+ this._toggleAtEvent(event);
297
+ }
298
+
299
+ public isOpen(): boolean {
300
+ return this._isContextMenuOpen();
301
+ }
302
+
303
+ public getMenuElement(): HTMLElement {
304
+ return this._menuElement;
305
+ }
306
+
307
+ public getTargetElement(): HTMLElement {
308
+ return this._targetElement;
309
+ }
310
+
311
+ public static getElement(reference: HTMLElement): HTMLElement {
312
+ if (
313
+ reference &&
314
+ reference.hasAttribute('data-kt-context-menu-initialized')
315
+ ) {
316
+ return reference;
317
+ }
318
+
319
+ const initializedParent = reference?.closest(
320
+ '[data-kt-context-menu-initialized]',
321
+ ) as HTMLElement | null;
322
+ if (initializedParent) return initializedParent;
323
+
324
+ const container = reference?.closest(
325
+ '[data-kt-context-menu]',
326
+ ) as HTMLElement | null;
327
+ if (container) return container;
328
+
329
+ if (
330
+ reference &&
331
+ reference.hasAttribute('data-kt-context-menu-menu') &&
332
+ KTData.has(reference, 'contextMenuElement')
333
+ ) {
334
+ return KTData.get(reference, 'contextMenuElement') as HTMLElement;
335
+ }
336
+
337
+ return null;
338
+ }
339
+
340
+ public static getInstance(element: HTMLElement): KTContextMenu {
341
+ element = this.getElement(element);
342
+ if (!element) return null;
343
+
344
+ if (KTData.has(element, 'context-menu')) {
345
+ return KTData.get(element, 'context-menu') as KTContextMenu;
346
+ }
347
+
348
+ if (element.getAttribute('data-kt-context-menu-initialized') === 'true') {
349
+ return new KTContextMenu(element);
350
+ }
351
+
352
+ return null;
353
+ }
354
+
355
+ public static getOrCreateInstance(
356
+ element: HTMLElement,
357
+ config?: KTContextMenuConfigInterface,
358
+ ): KTContextMenu {
359
+ return this.getInstance(element) || new KTContextMenu(element, config);
360
+ }
361
+
362
+ public static update(): void {
363
+ document
364
+ .querySelectorAll('.open[data-kt-context-menu-initialized]')
365
+ .forEach((item) => {
366
+ if (KTData.has(item as HTMLElement, 'popper')) {
367
+ (
368
+ KTData.get(item as HTMLElement, 'popper') as PopperInstance
369
+ ).forceUpdate();
370
+ }
371
+ });
372
+ }
373
+
374
+ public static hide(skipElement?: HTMLElement): void {
375
+ document
376
+ .querySelectorAll(
377
+ '.open[data-kt-context-menu-initialized]:not([data-kt-context-menu-permanent="true"])',
378
+ )
379
+ .forEach((item) => {
380
+ if (skipElement && (skipElement === item || item.contains(skipElement)))
381
+ return;
382
+ const contextMenu = KTContextMenu.getInstance(item as HTMLElement);
383
+ if (contextMenu) contextMenu.hide();
384
+ });
385
+ }
386
+
387
+ public static handleOpen(): void {
388
+ KTEventHandler.on(
389
+ document.body,
390
+ '[data-kt-context-menu-trigger], [data-kt-context-menu-target], [data-kt-context-menu]',
391
+ 'contextmenu',
392
+ (event: Event, target: HTMLElement) => {
393
+ const contextMenu = KTContextMenu.getInstance(target);
394
+ if (contextMenu) {
395
+ contextMenu.showAtEvent(event as MouseEvent);
396
+ }
397
+ },
398
+ );
399
+ }
400
+
401
+ public static handleToggle(): void {
402
+ KTEventHandler.on(
403
+ document.body,
404
+ '[data-kt-context-menu-toggle]',
405
+ 'click',
406
+ (event: Event, target: HTMLElement) => {
407
+ event.preventDefault();
408
+ event.stopPropagation();
409
+
410
+ const contextMenu = KTContextMenu.getInstance(target);
411
+ if (contextMenu) {
412
+ const rect = target.getBoundingClientRect();
413
+ contextMenu.showAt(rect.right, rect.top);
414
+ }
415
+ },
416
+ );
417
+ }
418
+
419
+ public static handleClickAway(): void {
420
+ document.addEventListener('click', (event: Event) => {
421
+ document
422
+ .querySelectorAll(
423
+ '.open[data-kt-context-menu-initialized]:not([data-kt-context-menu-permanent="true"])',
424
+ )
425
+ .forEach((element) => {
426
+ const contextMenu = KTContextMenu.getInstance(element as HTMLElement);
427
+ if (!contextMenu) return;
428
+
429
+ const menuElement = contextMenu.getMenuElement();
430
+ const targetElement = contextMenu.getTargetElement();
431
+
432
+ if (
433
+ targetElement === event.target ||
434
+ targetElement.contains(event.target as HTMLElement) ||
435
+ menuElement === event.target ||
436
+ menuElement.contains(event.target as HTMLElement)
437
+ ) {
438
+ return;
439
+ }
440
+
441
+ contextMenu.hide();
442
+ });
443
+ });
444
+ }
445
+
446
+ public static handleKeyboard(): void {
447
+ document.addEventListener('keydown', (event: KeyboardEvent) => {
448
+ const contextMenuEl = document.querySelector(
449
+ '.open[data-kt-context-menu-initialized]',
450
+ ) as HTMLElement | null;
451
+ const contextMenu = KTContextMenu.getInstance(
452
+ contextMenuEl as HTMLElement,
453
+ );
454
+ if (!contextMenu || !contextMenu._getOption('keyboard')) return;
455
+
456
+ if (
457
+ event.key === 'Escape' &&
458
+ !(event.ctrlKey || event.altKey || event.shiftKey)
459
+ ) {
460
+ contextMenu.hide();
461
+ }
462
+ });
463
+ }
464
+
465
+ public static handleDismiss(): void {
466
+ KTEventHandler.on(
467
+ document.body,
468
+ '[data-kt-context-menu-dismiss], [data-kt-dropdown-dismiss]',
469
+ 'click',
470
+ (_event: Event, target: HTMLElement) => {
471
+ const contextMenu = KTContextMenu.getInstance(target);
472
+ if (contextMenu) {
473
+ contextMenu.hide();
474
+ }
475
+ },
476
+ );
477
+ }
478
+
479
+ public static initHandlers(): void {
480
+ this.handleOpen();
481
+ this.handleToggle();
482
+ this.handleClickAway();
483
+ this.handleKeyboard();
484
+ this.handleDismiss();
485
+ }
486
+
487
+ public static createInstances(): void {
488
+ const elements = document.querySelectorAll('[data-kt-context-menu]');
489
+ elements.forEach((element) => {
490
+ new KTContextMenu(element as HTMLElement);
491
+ });
492
+ }
493
+
494
+ public static init(): void {
495
+ KTContextMenu.createInstances();
496
+ if (window.KT_CONTEXT_MENU_INITIALIZED !== true) {
497
+ KTContextMenu.initHandlers();
498
+ window.KT_CONTEXT_MENU_INITIALIZED = true;
499
+ }
500
+ }
501
+
502
+ public static reinit(): void {
503
+ const elements = document.querySelectorAll('[data-kt-context-menu]');
504
+ elements.forEach((element) => {
505
+ try {
506
+ const instance = KTContextMenu.getInstance(element as HTMLElement);
507
+ if (instance && typeof instance.hide === 'function') {
508
+ instance.hide();
509
+ }
510
+ KTData.remove(element as HTMLElement, 'context-menu');
511
+ KTData.remove(element as HTMLElement, 'popper');
512
+ element.removeAttribute('data-kt-context-menu-initialized');
513
+ const menu = element.querySelector('[data-kt-context-menu-menu]');
514
+ if (menu) {
515
+ KTData.remove(menu as HTMLElement, 'contextMenuElement');
516
+ }
517
+ } catch {
518
+ // ignore per-element cleanup errors
519
+ }
520
+ });
521
+
522
+ KTContextMenu.createInstances();
523
+ KTContextMenu.initHandlers();
524
+ }
525
+ }
526
+
527
+ if (typeof window !== 'undefined') {
528
+ window.KTContextMenu = KTContextMenu;
529
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ export { KTContextMenu } from './context-menu';
7
+ export type {
8
+ KTContextMenuConfigInterface,
9
+ KTContextMenuInterface,
10
+ } from './types';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ import { Placement } from '@popperjs/core';
7
+
8
+ export interface KTContextMenuConfigInterface {
9
+ zindex: number;
10
+ keyboard: boolean;
11
+ permanent: boolean;
12
+ dismiss: boolean;
13
+ placement: Placement;
14
+ placementRtl: Placement;
15
+ offset: string;
16
+ offsetRtl: string;
17
+ hiddenClass: string;
18
+ container: string;
19
+ preventNativeMenu: boolean;
20
+ flip: boolean;
21
+ overflow: boolean;
22
+ }
23
+
24
+ export interface KTContextMenuInterface {
25
+ showAt(x: number, y: number): void;
26
+ showAtEvent(event: MouseEvent): void;
27
+ hide(): void;
28
+ toggleAtEvent(event: MouseEvent): void;
29
+ isOpen(): boolean;
30
+ getMenuElement(): HTMLElement;
31
+ getTargetElement(): HTMLElement;
32
+ }