@aquera/nile-elements 1.6.0 → 1.6.2

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 (235) hide show
  1. package/README.md +9 -0
  2. package/dist/index.cjs.js +1 -1
  3. package/dist/index.esm.js +1 -1
  4. package/dist/index.js +1103 -629
  5. package/dist/internal/enum.cjs.js +1 -1
  6. package/dist/internal/enum.cjs.js.map +1 -1
  7. package/dist/internal/enum.esm.js +1 -1
  8. package/dist/nile-badge/index.cjs.js +1 -1
  9. package/dist/nile-badge/index.esm.js +1 -1
  10. package/dist/nile-badge/nile-badge.cjs.js +1 -1
  11. package/dist/nile-badge/nile-badge.cjs.js.map +1 -1
  12. package/dist/nile-badge/nile-badge.esm.js +1 -1
  13. package/dist/nile-button/index.cjs.js +1 -1
  14. package/dist/nile-button/index.esm.js +1 -1
  15. package/dist/nile-button/nile-button.cjs.js +1 -1
  16. package/dist/nile-button/nile-button.cjs.js.map +1 -1
  17. package/dist/nile-button/nile-button.esm.js +1 -1
  18. package/dist/nile-carousel/index.cjs.js +1 -1
  19. package/dist/nile-carousel/index.esm.js +1 -1
  20. package/dist/nile-carousel/nile-carousel.cjs.js +1 -1
  21. package/dist/nile-carousel/nile-carousel.cjs.js.map +1 -1
  22. package/dist/nile-carousel/nile-carousel.esm.js +1 -1
  23. package/dist/nile-dialog/index.cjs.js +1 -1
  24. package/dist/nile-dialog/index.esm.js +1 -1
  25. package/dist/nile-dialog/nile-dialog.cjs.js +1 -1
  26. package/dist/nile-dialog/nile-dialog.cjs.js.map +1 -1
  27. package/dist/nile-dialog/nile-dialog.esm.js +1 -1
  28. package/dist/nile-drawer/index.cjs.js +1 -1
  29. package/dist/nile-drawer/index.esm.js +1 -1
  30. package/dist/nile-drawer/nile-drawer.cjs.js +1 -1
  31. package/dist/nile-drawer/nile-drawer.cjs.js.map +1 -1
  32. package/dist/nile-drawer/nile-drawer.esm.js +1 -1
  33. package/dist/nile-floating-panel/index.cjs.js +1 -1
  34. package/dist/nile-floating-panel/index.esm.js +1 -1
  35. package/dist/nile-floating-panel/nile-floating-panel.cjs.js +1 -1
  36. package/dist/nile-floating-panel/nile-floating-panel.cjs.js.map +1 -1
  37. package/dist/nile-floating-panel/nile-floating-panel.css.cjs.js +1 -1
  38. package/dist/nile-floating-panel/nile-floating-panel.css.cjs.js.map +1 -1
  39. package/dist/nile-floating-panel/nile-floating-panel.css.esm.js +137 -21
  40. package/dist/nile-floating-panel/nile-floating-panel.esm.js +1 -1
  41. package/dist/nile-icon/icons/svg/folder_delete.cjs.js +2 -0
  42. package/dist/nile-icon/icons/svg/folder_delete.cjs.js.map +1 -0
  43. package/dist/nile-icon/icons/svg/folder_delete.esm.js +1 -0
  44. package/dist/nile-icon/icons/svg/index.cjs.js +1 -1
  45. package/dist/nile-icon/icons/svg/index.esm.js +1 -1
  46. package/dist/nile-icon/icons/svg/layers-three-02.cjs.js +1 -1
  47. package/dist/nile-icon/icons/svg/layers-three-02.cjs.js.map +1 -1
  48. package/dist/nile-icon/icons/svg/layers-three-02.esm.js +1 -1
  49. package/dist/nile-icon/index.cjs.js +1 -1
  50. package/dist/nile-icon/index.cjs.js.map +1 -1
  51. package/dist/nile-icon/index.esm.js +1 -1
  52. package/dist/nile-icon-button/index.cjs.js +1 -1
  53. package/dist/nile-icon-button/index.esm.js +1 -1
  54. package/dist/nile-icon-button/nile-icon-button.cjs.js +1 -1
  55. package/dist/nile-icon-button/nile-icon-button.cjs.js.map +1 -1
  56. package/dist/nile-icon-button/nile-icon-button.esm.js +1 -1
  57. package/dist/nile-input/index.cjs.js +1 -1
  58. package/dist/nile-input/index.esm.js +1 -1
  59. package/dist/nile-input/nile-input.cjs.js +1 -1
  60. package/dist/nile-input/nile-input.cjs.js.map +1 -1
  61. package/dist/nile-input/nile-input.esm.js +1 -1
  62. package/dist/nile-lite-tooltip/index.cjs.js +1 -1
  63. package/dist/nile-lite-tooltip/index.esm.js +1 -1
  64. package/dist/nile-lite-tooltip/nile-lite-tooltip.cjs.js +1 -1
  65. package/dist/nile-lite-tooltip/nile-lite-tooltip.cjs.js.map +1 -1
  66. package/dist/nile-lite-tooltip/nile-lite-tooltip.esm.js +1 -1
  67. package/dist/nile-menu-item/index.cjs.js +1 -1
  68. package/dist/nile-menu-item/index.esm.js +1 -1
  69. package/dist/nile-menu-item/nile-menu-item.cjs.js +1 -1
  70. package/dist/nile-menu-item/nile-menu-item.cjs.js.map +1 -1
  71. package/dist/nile-menu-item/nile-menu-item.esm.js +1 -1
  72. package/dist/nile-option/index.cjs.js +1 -1
  73. package/dist/nile-option/index.esm.js +1 -1
  74. package/dist/nile-option/nile-option.cjs.js +1 -1
  75. package/dist/nile-option/nile-option.cjs.js.map +1 -1
  76. package/dist/nile-option/nile-option.esm.js +1 -1
  77. package/dist/nile-otp-input/index.cjs.js +2 -0
  78. package/dist/nile-otp-input/index.cjs.js.map +1 -0
  79. package/dist/nile-otp-input/index.esm.js +1 -0
  80. package/dist/nile-otp-input/nile-otp-input.cjs.js +2 -0
  81. package/dist/nile-otp-input/nile-otp-input.cjs.js.map +1 -0
  82. package/dist/nile-otp-input/nile-otp-input.css.cjs.js +2 -0
  83. package/dist/nile-otp-input/nile-otp-input.css.cjs.js.map +1 -0
  84. package/dist/nile-otp-input/nile-otp-input.css.esm.js +257 -0
  85. package/dist/nile-otp-input/nile-otp-input.enum.cjs.js +2 -0
  86. package/dist/nile-otp-input/nile-otp-input.enum.cjs.js.map +1 -0
  87. package/dist/nile-otp-input/nile-otp-input.enum.esm.js +1 -0
  88. package/dist/nile-otp-input/nile-otp-input.esm.js +103 -0
  89. package/dist/nile-select/index.cjs.js +1 -1
  90. package/dist/nile-select/index.esm.js +1 -1
  91. package/dist/nile-select/nile-select.cjs.js +1 -1
  92. package/dist/nile-select/nile-select.cjs.js.map +1 -1
  93. package/dist/nile-select/nile-select.esm.js +1 -1
  94. package/dist/nile-side-bar-action-menu-item/index.cjs.js +1 -1
  95. package/dist/nile-side-bar-action-menu-item/index.esm.js +1 -1
  96. package/dist/nile-side-bar-action-menu-item/nile-side-bar-action-menu-item.cjs.js +1 -1
  97. package/dist/nile-side-bar-action-menu-item/nile-side-bar-action-menu-item.cjs.js.map +1 -1
  98. package/dist/nile-side-bar-action-menu-item/nile-side-bar-action-menu-item.esm.js +1 -1
  99. package/dist/nile-tab/index.cjs.js +1 -1
  100. package/dist/nile-tab/index.esm.js +1 -1
  101. package/dist/nile-tab/nile-tab.cjs.js +1 -1
  102. package/dist/nile-tab/nile-tab.cjs.js.map +1 -1
  103. package/dist/nile-tab/nile-tab.esm.js +1 -1
  104. package/dist/nile-tab-group/index.cjs.js +1 -1
  105. package/dist/nile-tab-group/index.esm.js +1 -1
  106. package/dist/nile-tab-group/nile-tab-group.cjs.js +1 -1
  107. package/dist/nile-tab-group/nile-tab-group.cjs.js.map +1 -1
  108. package/dist/nile-tab-group/nile-tab-group.esm.js +1 -1
  109. package/dist/nile-tag/index.cjs.js +1 -1
  110. package/dist/nile-tag/index.esm.js +1 -1
  111. package/dist/nile-tag/nile-tag.cjs.js +1 -1
  112. package/dist/nile-tag/nile-tag.cjs.js.map +1 -1
  113. package/dist/nile-tag/nile-tag.esm.js +1 -1
  114. package/dist/nile-toast/index.cjs.js +1 -1
  115. package/dist/nile-toast/index.esm.js +1 -1
  116. package/dist/nile-toast/nile-toast.cjs.js +1 -1
  117. package/dist/nile-toast/nile-toast.cjs.js.map +1 -1
  118. package/dist/nile-toast/nile-toast.esm.js +1 -1
  119. package/dist/nile-tree/index.cjs.js +1 -1
  120. package/dist/nile-tree/index.esm.js +1 -1
  121. package/dist/nile-tree/nile-tree.cjs.js +1 -1
  122. package/dist/nile-tree/nile-tree.cjs.js.map +1 -1
  123. package/dist/nile-tree/nile-tree.esm.js +1 -1
  124. package/dist/nile-tree-item/index.cjs.js +1 -1
  125. package/dist/nile-tree-item/index.esm.js +1 -1
  126. package/dist/nile-tree-item/nile-tree-item.cjs.js +1 -1
  127. package/dist/nile-tree-item/nile-tree-item.cjs.js.map +1 -1
  128. package/dist/nile-tree-item/nile-tree-item.esm.js +1 -1
  129. package/dist/nile-virtual-select/index.cjs.js +1 -1
  130. package/dist/nile-virtual-select/index.esm.js +1 -1
  131. package/dist/nile-virtual-select/nile-virtual-select.cjs.js +2 -2
  132. package/dist/nile-virtual-select/nile-virtual-select.esm.js +1 -1
  133. package/dist/src/index.d.ts +2 -1
  134. package/dist/src/index.js +2 -1
  135. package/dist/src/index.js.map +1 -1
  136. package/dist/src/internal/enum.d.ts +21 -0
  137. package/dist/src/internal/enum.js +23 -1
  138. package/dist/src/internal/enum.js.map +1 -1
  139. package/dist/src/nile-floating-panel/index.js.map +1 -1
  140. package/dist/src/nile-floating-panel/nile-floating-panel.css.d.ts +1 -1
  141. package/dist/src/nile-floating-panel/nile-floating-panel.css.js +147 -20
  142. package/dist/src/nile-floating-panel/nile-floating-panel.css.js.map +1 -1
  143. package/dist/src/nile-floating-panel/nile-floating-panel.d.ts +92 -24
  144. package/dist/src/nile-floating-panel/nile-floating-panel.js +489 -159
  145. package/dist/src/nile-floating-panel/nile-floating-panel.js.map +1 -1
  146. package/dist/src/nile-icon/icons/svg/folder_delete.d.ts +5 -0
  147. package/dist/src/nile-icon/icons/svg/folder_delete.js +5 -0
  148. package/dist/src/nile-icon/icons/svg/folder_delete.js.map +1 -0
  149. package/dist/src/nile-icon/icons/svg/index.d.ts +1 -0
  150. package/dist/src/nile-icon/icons/svg/index.js +1 -0
  151. package/dist/src/nile-icon/icons/svg/index.js.map +1 -1
  152. package/dist/src/nile-icon/icons/svg/layers-three-02.d.ts +1 -1
  153. package/dist/src/nile-icon/icons/svg/layers-three-02.js +1 -1
  154. package/dist/src/nile-icon/icons/svg/layers-three-02.js.map +1 -1
  155. package/dist/src/nile-otp-input/index.d.ts +1 -0
  156. package/dist/src/nile-otp-input/index.js +2 -0
  157. package/dist/src/nile-otp-input/index.js.map +1 -0
  158. package/dist/src/nile-otp-input/nile-otp-input.css.d.ts +12 -0
  159. package/dist/src/nile-otp-input/nile-otp-input.css.js +269 -0
  160. package/dist/src/nile-otp-input/nile-otp-input.css.js.map +1 -0
  161. package/dist/src/nile-otp-input/nile-otp-input.d.ts +156 -0
  162. package/dist/src/nile-otp-input/nile-otp-input.enum.d.ts +26 -0
  163. package/dist/src/nile-otp-input/nile-otp-input.enum.js +32 -0
  164. package/dist/src/nile-otp-input/nile-otp-input.enum.js.map +1 -0
  165. package/dist/src/nile-otp-input/nile-otp-input.js +762 -0
  166. package/dist/src/nile-otp-input/nile-otp-input.js.map +1 -0
  167. package/dist/src/nile-otp-input/nile-otp-input.test.d.ts +1 -0
  168. package/dist/src/nile-otp-input/nile-otp-input.test.js +493 -0
  169. package/dist/src/nile-otp-input/nile-otp-input.test.js.map +1 -0
  170. package/dist/src/version.js +2 -2
  171. package/dist/src/version.js.map +1 -1
  172. package/dist/tippy.esm-57628c2b.esm.js +1 -0
  173. package/dist/tippy.esm-78baa8f2.cjs.js +2 -0
  174. package/dist/tippy.esm-78baa8f2.cjs.js.map +1 -0
  175. package/dist/tsconfig.tsbuildinfo +1 -1
  176. package/package.json +5 -3
  177. package/plop-templates/lit/index.ts.hbs +1 -1
  178. package/plop-templates/lit/lit.css.ts.hbs +1 -1
  179. package/plop-templates/lit/lit.ts.hbs +1 -1
  180. package/src/index.ts +3 -2
  181. package/src/internal/enum.ts +23 -1
  182. package/src/nile-floating-panel/index.ts +0 -1
  183. package/src/nile-floating-panel/nile-floating-panel.css.ts +149 -21
  184. package/src/nile-floating-panel/nile-floating-panel.ts +498 -190
  185. package/src/nile-icon/icons/svg/folder_delete.ts +5 -0
  186. package/src/nile-icon/icons/svg/index.ts +1 -0
  187. package/src/nile-icon/icons/svg/layers-three-02.ts +1 -1
  188. package/src/nile-otp-input/index.ts +1 -0
  189. package/src/nile-otp-input/nile-otp-input.css.ts +271 -0
  190. package/src/nile-otp-input/nile-otp-input.enum.ts +30 -0
  191. package/src/nile-otp-input/nile-otp-input.test.ts +732 -0
  192. package/src/nile-otp-input/nile-otp-input.ts +835 -0
  193. package/vscode-html-custom-data.json +383 -23
  194. package/dist/nile-floating-panel/anchor-manager.cjs.js +0 -2
  195. package/dist/nile-floating-panel/anchor-manager.cjs.js.map +0 -1
  196. package/dist/nile-floating-panel/anchor-manager.esm.js +0 -1
  197. package/dist/nile-floating-panel/content-manager.cjs.js +0 -2
  198. package/dist/nile-floating-panel/content-manager.cjs.js.map +0 -1
  199. package/dist/nile-floating-panel/content-manager.esm.js +0 -1
  200. package/dist/nile-floating-panel/event-manager.cjs.js +0 -2
  201. package/dist/nile-floating-panel/event-manager.cjs.js.map +0 -1
  202. package/dist/nile-floating-panel/event-manager.esm.js +0 -1
  203. package/dist/nile-floating-panel/position-manager.cjs.js +0 -2
  204. package/dist/nile-floating-panel/position-manager.cjs.js.map +0 -1
  205. package/dist/nile-floating-panel/position-manager.esm.js +0 -1
  206. package/dist/nile-floating-panel/style-manager.cjs.js +0 -2
  207. package/dist/nile-floating-panel/style-manager.cjs.js.map +0 -1
  208. package/dist/nile-floating-panel/style-manager.esm.js +0 -1
  209. package/dist/nile-floating-panel/types.cjs.js +0 -2
  210. package/dist/nile-floating-panel/types.cjs.js.map +0 -1
  211. package/dist/nile-floating-panel/types.esm.js +0 -1
  212. package/dist/src/nile-floating-panel/anchor-manager.d.ts +0 -6
  213. package/dist/src/nile-floating-panel/anchor-manager.js +0 -27
  214. package/dist/src/nile-floating-panel/anchor-manager.js.map +0 -1
  215. package/dist/src/nile-floating-panel/content-manager.d.ts +0 -5
  216. package/dist/src/nile-floating-panel/content-manager.js +0 -44
  217. package/dist/src/nile-floating-panel/content-manager.js.map +0 -1
  218. package/dist/src/nile-floating-panel/event-manager.d.ts +0 -14
  219. package/dist/src/nile-floating-panel/event-manager.js +0 -52
  220. package/dist/src/nile-floating-panel/event-manager.js.map +0 -1
  221. package/dist/src/nile-floating-panel/position-manager.d.ts +0 -17
  222. package/dist/src/nile-floating-panel/position-manager.js +0 -72
  223. package/dist/src/nile-floating-panel/position-manager.js.map +0 -1
  224. package/dist/src/nile-floating-panel/style-manager.d.ts +0 -9
  225. package/dist/src/nile-floating-panel/style-manager.js +0 -44
  226. package/dist/src/nile-floating-panel/style-manager.js.map +0 -1
  227. package/dist/src/nile-floating-panel/types.d.ts +0 -11
  228. package/dist/src/nile-floating-panel/types.js +0 -2
  229. package/dist/src/nile-floating-panel/types.js.map +0 -1
  230. package/src/nile-floating-panel/anchor-manager.ts +0 -33
  231. package/src/nile-floating-panel/content-manager.ts +0 -54
  232. package/src/nile-floating-panel/event-manager.ts +0 -74
  233. package/src/nile-floating-panel/position-manager.ts +0 -102
  234. package/src/nile-floating-panel/style-manager.ts +0 -54
  235. package/src/nile-floating-panel/types.ts +0 -15
@@ -1,285 +1,593 @@
1
- import {
2
- LitElement,
3
- html,
4
- CSSResultArray,
5
- TemplateResult,
6
- PropertyValues,
7
- } from 'lit';
1
+ /**
2
+ * Copyright Aquera Inc 2025
3
+ *
4
+ * This source code is licensed under the BSD-3-Clause license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { CSSResultArray, PropertyValues } from 'lit';
8
9
  import { customElement, property } from 'lit/decorators.js';
9
10
  import { styles } from './nile-floating-panel.css';
10
11
  import NileElement from '../internal/nile-element';
11
- import { PositionManager } from './position-manager';
12
- import { StyleManager } from './style-manager';
13
- import { ContentManager } from './content-manager';
14
- import { AnchorManager } from './anchor-manager';
15
- import { EventManager } from './event-manager';
16
- import { VisibilityManager } from '../utilities/visibility-manager';
17
- import type { FloatingPanelPosition, FloatingPanelAnchor } from './types';
12
+ import tippy, {
13
+ Instance,
14
+ Props,
15
+ roundArrow,
16
+ followCursor as followCursorPlugin,
17
+ } from 'tippy.js';
18
+ import {
19
+ parseFollowCursor,
20
+ parseDuration,
21
+ } from '../nile-lite-tooltip/utils';
22
+ import { VisibilityManager } from '../utilities/visibility-manager.js';
18
23
 
19
24
  /**
20
- * Nile floating panel component.
25
+ * Nile floating-panel component.
26
+ *
27
+ * A popover that supports rich content (title, body, actions).
28
+ *
29
+ * **Wrapper mode** (default): first child element is the trigger.
30
+ * **For mode**: set `for="elementId"` to attach to an external element.
21
31
  *
22
32
  * @tag nile-floating-panel
23
- * @event nile-show - Emitted when the panel opens.
24
- * @event nile-hide - Emitted when the panel closes.
33
+ *
34
+ * @fires nile-init - Component initialized.
35
+ * @fires nile-destroy - Component destroyed.
36
+ * @fires nile-show - Panel opened.
37
+ * @fires nile-hide - Panel closed.
38
+ * @fires nile-after-show - Panel fully visible after animation.
39
+ * @fires nile-after-hide - Panel fully hidden after animation.
40
+ * @fires nile-toggle - Open/close transition (detail.open).
41
+ * @fires nile-visibility-change - Hidden by scroll/tab change.
25
42
  */
26
43
  @customElement('nile-floating-panel')
27
44
  export class NileFloatingPanel extends NileElement {
45
+ private static _groups = new Map<string, Set<NileFloatingPanel>>();
46
+
47
+ private static _reducedMotionQuery: MediaQueryList | null = null;
48
+
49
+ private static get prefersReducedMotion(): boolean {
50
+ if (!NileFloatingPanel._reducedMotionQuery) {
51
+ NileFloatingPanel._reducedMotionQuery =
52
+ window.matchMedia('(prefers-reduced-motion: reduce)');
53
+ }
54
+ return NileFloatingPanel._reducedMotionQuery.matches;
55
+ }
56
+
28
57
  public static get styles(): CSSResultArray {
29
58
  return [styles];
30
59
  }
31
60
 
32
- @property() anchor: FloatingPanelAnchor = null;
61
+ protected createRenderRoot() {
62
+ return this;
63
+ }
64
+
65
+ // ─── Tippy.js props ───
66
+
67
+ @property({ type: String })
68
+ placement:
69
+ | 'top'
70
+ | 'top-start'
71
+ | 'top-end'
72
+ | 'right'
73
+ | 'right-start'
74
+ | 'right-end'
75
+ | 'bottom'
76
+ | 'bottom-start'
77
+ | 'bottom-end'
78
+ | 'left'
79
+ | 'left-start'
80
+ | 'left-end'
81
+ | 'auto'
82
+ | 'auto-start'
83
+ | 'auto-end' = 'bottom';
84
+
85
+ @property({ type: String }) trigger: string = 'click';
86
+
87
+ @property({ type: Number }) distance = 12;
88
+
89
+ @property({ type: Number }) skidding = 0;
90
+
91
+ @property({ type: String, reflect: true })
92
+ arrow: 'round' | 'default' | 'none' = 'round';
93
+
94
+ @property({ type: String, reflect: true }) animation: string = 'fade';
95
+
96
+ @property({ type: String, reflect: true }) duration:
97
+ | string
98
+ | number
99
+ | [number, number] = 200;
100
+
101
+ @property({ type: String, reflect: true }) delay:
102
+ | number
103
+ | [number, number] = 0;
104
+
105
+ @property({ type: Boolean, reflect: true }) interactive = true;
106
+
107
+ @property({ type: Number, reflect: true }) interactiveBorder = 2;
108
+
109
+ @property({ type: String, reflect: true }) maxWidth: string | number = 'none';
110
+
111
+ @property({ type: Number, reflect: true }) zIndex = 9999;
112
+
113
+ @property({ type: String, reflect: true })
114
+ followCursor:
115
+ | boolean
116
+ | 'initial'
117
+ | 'horizontal'
118
+ | 'vertical'
119
+ | 'true'
120
+ | 'false' = false;
121
+
122
+ @property({ type: Boolean, reflect: true }) hideOnClick:
123
+ | boolean
124
+ | 'toggle' = true;
125
+
126
+ @property({ type: Boolean, reflect: true }) inertia = false;
127
+
128
+ @property({ type: Boolean, reflect: true }) allowHTML = false;
129
+
130
+ @property({ type: Boolean, reflect: true }) flip = true;
131
+
132
+ // ─── Popover-like props ───
133
+
134
+ @property({ type: String, attribute: 'for' }) for: string | null = null;
135
+
136
+ @property({ type: Boolean, reflect: true }) open = false;
137
+
138
+ @property({ type: Boolean, reflect: true }) preventOverlayClose = false;
139
+
140
+ @property({ type: String, reflect: true }) title = '';
33
141
 
34
- @property() position: FloatingPanelPosition = 'bottom';
142
+ @property({ type: Boolean, reflect: true }) disabled = false;
35
143
 
36
- @property({ type: Boolean, reflect: true, attribute: true }) open = false;
144
+ @property({ type: String, reflect: true }) width?: string;
37
145
 
38
- @property({
39
- type: Boolean,
40
- reflect: true,
41
- attribute: 'close-on-outside-click',
42
- converter: {
43
- fromAttribute: (value: string | null) => (!value || value === 'false' ? false : true),
44
- toAttribute: (value: boolean) => (value ? 'true' : 'false'),
45
- },
46
- })
47
- closeOnOutsideClick = true;
146
+ @property({ type: String, reflect: true }) height?: string;
48
147
 
49
- @property({ type: Boolean, reflect: true }) enableVisibilityEffect = true;
148
+ /** When set, only one panel in the same group can be open at a time. */
149
+ @property({ type: String, reflect: true }) group: string | null = null;
150
+
151
+ /** Close the panel when Escape is pressed. */
152
+ @property({ type: Boolean, reflect: true }) closeOnEscape = true;
153
+
154
+ /** Custom CSS class(es) added to the Tippy popper element, allowing per-instance styling from the host. */
155
+ @property({ type: String, reflect: true, attribute: true}) panelClass: string = '';
156
+
157
+ // ─── Visibility manager props ───
158
+
159
+ @property({ type: Boolean, reflect: true }) enableVisibilityEffect = false;
50
160
 
51
161
  @property({ type: Boolean, reflect: true }) enableTabClose = false;
52
162
 
53
- private panelContainer: HTMLElement | null = null;
54
- private positionManager: PositionManager | null = null;
55
- private styleManager: StyleManager = new StyleManager();
56
- private eventManager: EventManager = new EventManager(this);
163
+ // ─── Internal state ───
164
+
165
+ private tippyInstance: Instance | null = null;
57
166
  private visibilityManager?: VisibilityManager;
167
+ private panelContainer: HTMLElement | null = null;
168
+ private anchorEl: HTMLElement | null = null;
169
+ private _suppressOpenWatch = false;
170
+ private _panelId = `nile-fp-${Math.random().toString(36).slice(2, 9)}`;
171
+ private _boundEscHandler = this._handleEscapeKey.bind(this);
172
+ private _pendingShowListener: (() => void) | null = null;
173
+ private _pendingHideListener: (() => void) | null = null;
174
+
175
+ // ─── Lifecycle ───
58
176
 
59
- connectedCallback() {
60
- super.connectedCallback();
177
+ protected firstUpdated(): void {
178
+ this._buildDOM();
179
+ this._attachTippy();
180
+ this._joinGroup();
181
+
182
+ this.visibilityManager = new VisibilityManager({
183
+ host: this,
184
+ target: this.anchorEl || null,
185
+ enableVisibilityEffect: this.enableVisibilityEffect,
186
+ enableTabClose: this.enableTabClose,
187
+ isOpen: () => this.open,
188
+ onAnchorOutOfView: () => {
189
+ this._setOpen(false);
190
+ this.tippyInstance?.hide();
191
+ this.emit('nile-visibility-change', {
192
+ visible: false,
193
+ reason: 'anchor-out-of-view',
194
+ });
195
+ },
196
+ onDocumentHidden: () => {
197
+ this._setOpen(false);
198
+ this.tippyInstance?.hide();
199
+ this.emit('nile-visibility-change', {
200
+ visible: false,
201
+ reason: 'document-hidden',
202
+ });
203
+ },
204
+ emit: (event, detail) => this.emit(`nile-${event}`, detail),
205
+ });
206
+
207
+ this.emit('nile-init');
61
208
  }
62
209
 
63
- disconnectedCallback() {
210
+ disconnectedCallback(): void {
64
211
  super.disconnectedCallback();
65
- this.cleanupPanel();
212
+ this._cleanupPendingShowListener();
213
+ this._cleanupPendingHideListener();
66
214
  this.visibilityManager?.cleanup();
215
+ this._leaveGroup();
216
+ this._removeEscListener();
217
+ this._destroyTippy();
218
+ this.emit('nile-destroy');
67
219
  }
68
220
 
69
- updated(changedProperties: Map<string, unknown>) {
70
- super.updated(changedProperties);
221
+ updated(changed: PropertyValues): void {
222
+ super.updated(changed);
223
+
224
+ if (!this.panelContainer) return;
71
225
 
72
- if (changedProperties.has('open')) {
226
+ if (changed.has('open') && !this._suppressOpenWatch) {
73
227
  if (this.open) {
74
- this.emit('nile-show');
75
- this.setupPanel();
76
228
  this.visibilityManager?.setup();
229
+ queueMicrotask(() => this.tippyInstance?.show());
77
230
  } else {
78
- this.emit('nile-hide');
79
231
  this.visibilityManager?.cleanup();
80
- this.cleanupPanel();
232
+ this.tippyInstance?.hide();
81
233
  }
82
234
  }
83
235
 
84
- if (
85
- changedProperties.has('closeOnOutsideClick') &&
86
- this.open &&
87
- this.panelContainer
88
- ) {
89
- this.eventManager.updateOutsideClickHandler(
90
- this.panelContainer,
91
- this.closeOnOutsideClick,
92
- this.open
93
- );
236
+ if (changed.has('group')) {
237
+ this._leaveGroup(changed.get('group') as string | null);
238
+ this._joinGroup();
94
239
  }
95
240
 
96
- if (changedProperties.has('anchor') && this.open) {
97
- this.cleanupPanel();
98
- this.setupPanel();
99
- }
241
+ const rebuildProps: string[] = [
242
+ 'placement', 'trigger', 'distance', 'skidding', 'arrow',
243
+ 'animation', 'duration', 'delay', 'interactive', 'interactiveBorder',
244
+ 'maxWidth', 'zIndex', 'followCursor', 'hideOnClick', 'inertia',
245
+ 'allowHTML', 'flip', 'preventOverlayClose', 'disabled', 'width', 'height',
246
+ 'panelClass'
247
+ ];
100
248
 
101
- if (
102
- (changedProperties.has('enableVisibilityEffect') ||
103
- changedProperties.has('enableTabClose')) &&
104
- this.open
105
- ) {
106
- this.setupVisibilityManager();
249
+ if (rebuildProps.some(p => changed.has(p))) {
250
+ this._attachTippy();
107
251
  }
252
+ }
108
253
 
109
- if (
110
- changedProperties.has('position') &&
111
- this.open &&
112
- this.positionManager
113
- ) {
114
- this.positionManager.updatePosition(this.position);
115
- }
254
+ // ─── Public API ───
255
+
256
+ /** Programmatically shows the panel. Returns a promise that resolves after the show animation. */
257
+ public show(): Promise<void> {
258
+ this.open = true;
259
+ return new Promise<void>(resolve => {
260
+ this._cleanupPendingShowListener();
261
+ const handler = () => {
262
+ this._pendingShowListener = null;
263
+ resolve();
264
+ };
265
+ this._pendingShowListener = handler;
266
+ this.addEventListener('nile-after-show', handler, { once: true });
267
+ });
268
+ }
116
269
 
117
- if (changedProperties.has('open') && this.open && this.panelContainer) {
118
- this.updatePanelContent();
119
- }
270
+ /** Programmatically hides the panel. Returns a promise that resolves after the hide animation. */
271
+ public hide(): Promise<void> {
272
+ this.open = false;
273
+ return new Promise<void>(resolve => {
274
+ this._cleanupPendingHideListener();
275
+ const handler = () => {
276
+ this._pendingHideListener = null;
277
+ resolve();
278
+ };
279
+ this._pendingHideListener = handler;
280
+ this.addEventListener('nile-after-hide', handler, { once: true });
281
+ });
120
282
  }
121
283
 
122
- private setupPanel() {
123
- if (this.panelContainer) {
124
- return;
284
+ private _cleanupPendingShowListener(): void {
285
+ if (this._pendingShowListener) {
286
+ this.removeEventListener('nile-after-show', this._pendingShowListener);
287
+ this._pendingShowListener = null;
125
288
  }
289
+ }
126
290
 
127
- const componentStyles = (this.constructor as typeof NileFloatingPanel)
128
- .styles;
129
- if (componentStyles) {
130
- this.styleManager.injectStyles(componentStyles);
291
+ private _cleanupPendingHideListener(): void {
292
+ if (this._pendingHideListener) {
293
+ this.removeEventListener('nile-after-hide', this._pendingHideListener);
294
+ this._pendingHideListener = null;
131
295
  }
296
+ }
132
297
 
133
- this.panelContainer = document.createElement('div');
134
- this.panelContainer.setAttribute('part', 'panel');
135
- this.panelContainer.className = 'nile-floating-panel__container';
298
+ public toggle(): void {
299
+ this.open = !this.open;
300
+ }
136
301
 
137
- this.updatePanelContent();
302
+ public refresh(): void {
303
+ this._attachTippy();
304
+ }
138
305
 
139
- const anchorElement = AnchorManager.resolveAnchor(this.anchor);
140
- AnchorManager.appendToAnchor(anchorElement, this.panelContainer);
306
+ /** Returns the current resolved placement from Tippy/Popper. */
307
+ public getCurrentPlacement(): string {
308
+ const popper = this.tippyInstance?.popper;
309
+ const box = popper?.querySelector('.tippy-box') as HTMLElement | null;
310
+ return box?.dataset.placement ?? this.placement;
311
+ }
141
312
 
142
- requestAnimationFrame(() => {
143
- this.setupPositionManager();
144
- this.setupEventHandlers();
145
- this.setupVisibilityManager();
146
- });
313
+ /** Returns true if the resolved placement matches the requested placement. */
314
+ public isPositioningOptimal(): boolean {
315
+ return this.getCurrentPlacement() === this.placement;
147
316
  }
148
317
 
149
- private setupEventHandlers() {
150
- if (!this.panelContainer) {
151
- return;
318
+ // ─── Group management ───
319
+
320
+ private _joinGroup(): void {
321
+ if (!this.group) return;
322
+ let set = NileFloatingPanel._groups.get(this.group);
323
+ if (!set) {
324
+ set = new Set();
325
+ NileFloatingPanel._groups.set(this.group, set);
152
326
  }
327
+ set.add(this);
328
+ }
153
329
 
154
- this.eventManager.setupOutsideClickHandler(
155
- this.panelContainer,
156
- this.closeOnOutsideClick,
157
- this.open,
158
- () => {
159
- this.open = false;
330
+ private _leaveGroup(oldGroup?: string | null): void {
331
+ const key = oldGroup ?? this.group;
332
+ if (!key) return;
333
+ const set = NileFloatingPanel._groups.get(key);
334
+ if (set) {
335
+ set.delete(this);
336
+ if (set.size === 0) NileFloatingPanel._groups.delete(key);
337
+ }
338
+ }
339
+
340
+ private _hideGroupSiblings(): void {
341
+ if (!this.group) return;
342
+ const set = NileFloatingPanel._groups.get(this.group);
343
+ if (!set) return;
344
+ set.forEach(panel => {
345
+ if (panel !== this && panel.open) {
346
+ panel._setOpen(false);
347
+ panel.tippyInstance?.hide();
160
348
  }
161
- );
349
+ });
162
350
  }
163
351
 
164
- private setupPositionManager() {
165
- if (!this.panelContainer) {
166
- return;
352
+ // ─── Escape key ───
353
+
354
+ private _addEscListener(): void {
355
+ if (this.closeOnEscape) {
356
+ document.addEventListener('keydown', this._boundEscHandler);
167
357
  }
358
+ }
168
359
 
169
- const referenceElement = this.findTriggerElement() || this;
170
- this.positionManager = new PositionManager(
171
- referenceElement,
172
- this.panelContainer,
173
- this.position
174
- );
360
+ private _removeEscListener(): void {
361
+ document.removeEventListener('keydown', this._boundEscHandler);
362
+ }
175
363
 
176
- this.positionManager.reposition();
177
- this.positionManager.setupAutoUpdate();
364
+ private _handleEscapeKey(e: KeyboardEvent): void {
365
+ if (e.key === 'Escape' && this.open) {
366
+ this._setOpen(false);
367
+ this.tippyInstance?.hide();
368
+ }
178
369
  }
179
370
 
180
- private findTriggerElement(): HTMLElement | null {
181
- // Try to find the next sibling element as the trigger
182
- let nextSibling = this.nextElementSibling;
183
- while (nextSibling) {
184
- if (nextSibling instanceof HTMLElement) {
185
- return nextSibling;
186
- }
187
- nextSibling = nextSibling.nextElementSibling;
371
+ // ─── ARIA ───
372
+
373
+ private _applyAria(): void {
374
+ if (!this.anchorEl || !this.panelContainer) return;
375
+ this.panelContainer.setAttribute('role', 'dialog');
376
+ this.panelContainer.id = this._panelId;
377
+ this.anchorEl.setAttribute('aria-haspopup', 'dialog');
378
+ this._syncAriaExpanded();
379
+ }
380
+
381
+ private _syncAriaExpanded(): void {
382
+ this.anchorEl?.setAttribute('aria-expanded', String(this.open));
383
+ if (this.open) {
384
+ this.anchorEl?.setAttribute('aria-describedby', this._panelId);
385
+ } else {
386
+ this.anchorEl?.removeAttribute('aria-describedby');
188
387
  }
388
+ }
189
389
 
190
- // Try to find the previous sibling element
191
- let previousSibling = this.previousElementSibling;
192
- while (previousSibling) {
193
- if (previousSibling instanceof HTMLElement) {
194
- return previousSibling;
390
+ // ─── DOM construction ───
391
+
392
+ private _buildDOM(): void {
393
+ const children = Array.from(this.childNodes);
394
+
395
+ this.anchorEl = null;
396
+ const titleNodes: Node[] = [];
397
+ const actionNodes: Node[] = [];
398
+ const bodyNodes: Node[] = [];
399
+ let firstElementSeen = false;
400
+
401
+ for (const child of children) {
402
+ if (child instanceof HTMLElement) {
403
+ const slot = child.getAttribute('slot');
404
+ if (slot === 'title') {
405
+ child.removeAttribute('slot');
406
+ titleNodes.push(child);
407
+ continue;
408
+ }
409
+ if (slot === 'action') {
410
+ child.removeAttribute('slot');
411
+ actionNodes.push(child);
412
+ continue;
413
+ }
414
+ if (!firstElementSeen && !this.for) {
415
+ this.anchorEl = child;
416
+ firstElementSeen = true;
417
+ continue;
418
+ }
195
419
  }
196
- previousSibling = previousSibling.previousElementSibling;
420
+ bodyNodes.push(child);
197
421
  }
198
422
 
199
- return null;
200
- }
423
+ if (this.for) {
424
+ const anchor = document.getElementById(this.for);
425
+ if (anchor) {
426
+ this.anchorEl = anchor;
427
+ }
428
+ }
201
429
 
202
- private setupVisibilityManager(): void {
203
- if (!this.enableVisibilityEffect) {
204
- return;
430
+ while (this.firstChild) {
431
+ this.removeChild(this.firstChild);
205
432
  }
206
433
 
207
- const triggerElement = this.findTriggerElement();
208
-
209
- // Cleanup existing visibility manager if it exists
210
- if (this.visibilityManager) {
211
- this.visibilityManager.cleanup();
434
+ if (this.anchorEl && !this.for) {
435
+ this.appendChild(this.anchorEl);
212
436
  }
213
437
 
214
- this.visibilityManager = new VisibilityManager({
215
- host: this,
216
- target: triggerElement || null,
217
- enableVisibilityEffect: this.enableVisibilityEffect,
218
- enableTabClose: this.enableTabClose,
219
- isOpen: () => this.open,
220
- onAnchorOutOfView: () => {
221
- this.open = false;
222
- this.emit('nile-visibility-change', {
223
- visible: false,
224
- reason: 'anchor-out-of-view',
225
- });
226
- },
227
- onDocumentHidden: () => {
228
- this.open = false;
229
- this.emit('nile-visibility-change', {
230
- visible: false,
231
- reason: 'document-hidden',
232
- });
233
- },
234
- emit: (event, detail) => this.emit(`nile-${event}`, detail),
235
- });
438
+ this.panelContainer = document.createElement('div');
439
+ this.panelContainer.className = 'nile-floating-panel__content';
440
+ this.panelContainer.style.display = 'none';
236
441
 
237
- if (this.open) {
238
- this.visibilityManager.setup();
239
- }
240
- }
442
+ const body = document.createElement('div');
443
+ body.className = 'nile-floating-panel__body';
241
444
 
242
- private updatePanelContent() {
243
- if (!this.panelContainer) {
244
- return;
445
+ if (titleNodes.length > 0 || this.title) {
446
+ const titleDiv = document.createElement('div');
447
+ titleDiv.className = 'nile-floating-panel__title';
448
+ if (this.title) {
449
+ titleDiv.textContent = this.title;
450
+ } else {
451
+ titleNodes.forEach(n => titleDiv.appendChild(n));
452
+ }
453
+ body.appendChild(titleDiv);
245
454
  }
246
455
 
247
- const slot = this.shadowRoot?.querySelector('slot') || null;
248
- ContentManager.updatePanelContent(this.panelContainer, slot, '');
249
- }
456
+ if (bodyNodes.length > 0) {
457
+ const mainDiv = document.createElement('div');
458
+ mainDiv.className = 'nile-floating-panel__main';
459
+ bodyNodes.forEach(n => mainDiv.appendChild(n));
460
+ body.appendChild(mainDiv);
461
+ }
250
462
 
251
- private reposition() {
252
- if (this.positionManager) {
253
- this.positionManager.reposition();
463
+ if (actionNodes.length > 0) {
464
+ const actionDiv = document.createElement('div');
465
+ actionDiv.className = 'nile-floating-panel__action';
466
+ actionNodes.forEach(n => actionDiv.appendChild(n));
467
+ body.appendChild(actionDiv);
254
468
  }
469
+
470
+ this.panelContainer.appendChild(body);
471
+ this.appendChild(this.panelContainer);
472
+
473
+ this._applyAria();
255
474
  }
256
475
 
257
- private cleanupPanel() {
258
- this.eventManager.destroy();
476
+ // ─── Tippy management ───
259
477
 
260
- if (this.positionManager) {
261
- this.positionManager.destroy();
262
- this.positionManager = null;
478
+ private _resolveArrow() {
479
+ switch (this.arrow) {
480
+ case 'round': return roundArrow;
481
+ case 'none': return false as const;
482
+ default: return true as const;
263
483
  }
484
+ }
264
485
 
265
- if (this.panelContainer) {
266
- AnchorManager.removeFromAnchor(this.panelContainer);
267
- }
486
+ private _setOpen(value: boolean): void {
487
+ this._suppressOpenWatch = true;
488
+ this.open = value;
489
+ this._syncAriaExpanded();
490
+ this._suppressOpenWatch = false;
491
+ }
268
492
 
269
- this.panelContainer = null;
493
+ private _getEffectiveDuration(): number | [number, number] {
494
+ if (NileFloatingPanel.prefersReducedMotion) return 0;
495
+ return parseDuration(this.duration);
496
+ }
270
497
 
271
- this.styleManager.cleanupStyles();
498
+ private _getEffectiveAnimation(): string | false {
499
+ if (NileFloatingPanel.prefersReducedMotion) return false;
500
+ return this.animation;
272
501
  }
273
502
 
274
- public render(): TemplateResult {
275
- return html` <slot @slotchange=${this.handleSlotChange}></slot> `;
503
+ private _attachTippy(): void {
504
+ this._destroyTippy();
505
+
506
+ if (this.disabled || !this.anchorEl || !this.panelContainer) return;
507
+
508
+ const resolvedFollowCursor = parseFollowCursor(this.followCursor);
509
+ const effectiveHideOnClick = this.preventOverlayClose ? false : this.hideOnClick;
510
+
511
+ const options: Partial<Props> = {
512
+ content: this.panelContainer,
513
+ placement: this.placement,
514
+ trigger: this.trigger,
515
+ offset: [this.skidding, this.distance],
516
+ theme: 'floating-panel',
517
+ animation: this._getEffectiveAnimation(),
518
+ interactive: this.interactive,
519
+ arrow: this._resolveArrow(),
520
+ duration: this._getEffectiveDuration(),
521
+ allowHTML: this.allowHTML,
522
+ delay: this.delay as any,
523
+ maxWidth: this.maxWidth,
524
+ zIndex: this.zIndex,
525
+ hideOnClick: effectiveHideOnClick,
526
+ inertia: NileFloatingPanel.prefersReducedMotion ? false : this.inertia,
527
+ interactiveBorder: this.interactiveBorder,
528
+ appendTo: document.body,
529
+ followCursor: resolvedFollowCursor,
530
+ plugins: resolvedFollowCursor ? [followCursorPlugin] : [],
531
+ popperOptions: {
532
+ modifiers: [{ name: 'flip', enabled: this.flip }],
533
+ },
534
+ onMount: (instance) => {
535
+ if (this.panelContainer) this.panelContainer.style.display = '';
536
+ if (this.panelClass) {
537
+ this.panelClass.split(/\s+/).filter(Boolean).forEach(cls => {
538
+ instance.popper.classList.add(cls);
539
+ });
540
+ }
541
+ },
542
+ onShow: (instance) => {
543
+ if (this.panelContainer) this.panelContainer.style.display = '';
544
+ const tc = instance.popper.querySelector('.tippy-content') as HTMLElement | null;
545
+ if (tc) {
546
+ if (this.width) tc.style.width = this.width;
547
+ if (this.height) { tc.style.height = this.height; tc.style.overflow = 'auto'; }
548
+ }
549
+ this._hideGroupSiblings();
550
+ this._setOpen(true);
551
+ this._addEscListener();
552
+ this.dispatchEvent(new CustomEvent('nile-show', { detail: { instance, target: instance.reference } }));
553
+ this.dispatchEvent(new CustomEvent('nile-toggle', { detail: { open: true, instance, target: instance.reference } }));
554
+ return undefined;
555
+ },
556
+ onShown: (instance) => {
557
+ this.dispatchEvent(new CustomEvent('nile-after-show', { detail: { instance, target: instance.reference } }));
558
+ },
559
+ onHide: (instance) => {
560
+ this._setOpen(false);
561
+ this._removeEscListener();
562
+ this.dispatchEvent(new CustomEvent('nile-hide', { detail: { instance, target: instance.reference } }));
563
+ this.dispatchEvent(new CustomEvent('nile-toggle', { detail: { open: false, instance, target: instance.reference } }));
564
+ return undefined;
565
+ },
566
+ onHidden: (instance) => {
567
+ if (this.panelContainer) this.panelContainer.style.display = 'none';
568
+ this.dispatchEvent(new CustomEvent('nile-after-hide', { detail: { instance, target: instance.reference } }));
569
+ },
570
+ };
571
+
572
+ this.tippyInstance = tippy(this.anchorEl, options);
573
+
574
+ if (this.open) {
575
+ queueMicrotask(() => this.tippyInstance?.show());
576
+ }
276
577
  }
277
578
 
278
- private handleSlotChange = () => {
279
- if (this.open && this.panelContainer) {
280
- this.updatePanelContent();
579
+ private _destroyTippy(): void {
580
+ if (this.tippyInstance) {
581
+ this.tippyInstance.destroy();
582
+ this.tippyInstance = null;
281
583
  }
282
- };
584
+ if (this.panelContainer) {
585
+ this.panelContainer.style.display = 'none';
586
+ if (this.panelContainer.parentElement !== this) {
587
+ this.appendChild(this.panelContainer);
588
+ }
589
+ }
590
+ }
283
591
  }
284
592
 
285
593
  export default NileFloatingPanel;