@ngneers/controls 0.0.1-next.0

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 (186) hide show
  1. package/fesm2022/ngneers-controls-accordion.mjs +234 -0
  2. package/fesm2022/ngneers-controls-accordion.mjs.map +1 -0
  3. package/fesm2022/ngneers-controls-api-ng.mjs +959 -0
  4. package/fesm2022/ngneers-controls-api-ng.mjs.map +1 -0
  5. package/fesm2022/ngneers-controls-api-resize.mjs +712 -0
  6. package/fesm2022/ngneers-controls-api-resize.mjs.map +1 -0
  7. package/fesm2022/ngneers-controls-api.mjs +347 -0
  8. package/fesm2022/ngneers-controls-api.mjs.map +1 -0
  9. package/fesm2022/ngneers-controls-avatar.mjs +77 -0
  10. package/fesm2022/ngneers-controls-avatar.mjs.map +1 -0
  11. package/fesm2022/ngneers-controls-base.mjs +620 -0
  12. package/fesm2022/ngneers-controls-base.mjs.map +1 -0
  13. package/fesm2022/ngneers-controls-breadcrumb.mjs +100 -0
  14. package/fesm2022/ngneers-controls-breadcrumb.mjs.map +1 -0
  15. package/fesm2022/ngneers-controls-button-group.mjs +110 -0
  16. package/fesm2022/ngneers-controls-button-group.mjs.map +1 -0
  17. package/fesm2022/ngneers-controls-button.mjs +83 -0
  18. package/fesm2022/ngneers-controls-button.mjs.map +1 -0
  19. package/fesm2022/ngneers-controls-calendar-time-CJT5AFlA.mjs +74 -0
  20. package/fesm2022/ngneers-controls-calendar-time-CJT5AFlA.mjs.map +1 -0
  21. package/fesm2022/ngneers-controls-calendar.mjs +801 -0
  22. package/fesm2022/ngneers-controls-calendar.mjs.map +1 -0
  23. package/fesm2022/ngneers-controls-checkbox.mjs +63 -0
  24. package/fesm2022/ngneers-controls-checkbox.mjs.map +1 -0
  25. package/fesm2022/ngneers-controls-chip.mjs +55 -0
  26. package/fesm2022/ngneers-controls-chip.mjs.map +1 -0
  27. package/fesm2022/ngneers-controls-default-icons.mjs +82 -0
  28. package/fesm2022/ngneers-controls-default-icons.mjs.map +1 -0
  29. package/fesm2022/ngneers-controls-defer.mjs +72 -0
  30. package/fesm2022/ngneers-controls-defer.mjs.map +1 -0
  31. package/fesm2022/ngneers-controls-dialog.mjs +407 -0
  32. package/fesm2022/ngneers-controls-dialog.mjs.map +1 -0
  33. package/fesm2022/ngneers-controls-directives.mjs +498 -0
  34. package/fesm2022/ngneers-controls-directives.mjs.map +1 -0
  35. package/fesm2022/ngneers-controls-drawer.mjs +271 -0
  36. package/fesm2022/ngneers-controls-drawer.mjs.map +1 -0
  37. package/fesm2022/ngneers-controls-edit-inplace.mjs +127 -0
  38. package/fesm2022/ngneers-controls-edit-inplace.mjs.map +1 -0
  39. package/fesm2022/ngneers-controls-errors.mjs +329 -0
  40. package/fesm2022/ngneers-controls-errors.mjs.map +1 -0
  41. package/fesm2022/ngneers-controls-filter.mjs +708 -0
  42. package/fesm2022/ngneers-controls-filter.mjs.map +1 -0
  43. package/fesm2022/ngneers-controls-hint.mjs +153 -0
  44. package/fesm2022/ngneers-controls-hint.mjs.map +1 -0
  45. package/fesm2022/ngneers-controls-i18n-translations-de.mjs +170 -0
  46. package/fesm2022/ngneers-controls-i18n-translations-de.mjs.map +1 -0
  47. package/fesm2022/ngneers-controls-i18n-translations-en.mjs +170 -0
  48. package/fesm2022/ngneers-controls-i18n-translations-en.mjs.map +1 -0
  49. package/fesm2022/ngneers-controls-i18n.mjs +57 -0
  50. package/fesm2022/ngneers-controls-i18n.mjs.map +1 -0
  51. package/fesm2022/ngneers-controls-icon.mjs +195 -0
  52. package/fesm2022/ngneers-controls-icon.mjs.map +1 -0
  53. package/fesm2022/ngneers-controls-inplace.mjs +150 -0
  54. package/fesm2022/ngneers-controls-inplace.mjs.map +1 -0
  55. package/fesm2022/ngneers-controls-input-field.mjs +165 -0
  56. package/fesm2022/ngneers-controls-input-field.mjs.map +1 -0
  57. package/fesm2022/ngneers-controls-input.mjs +72 -0
  58. package/fesm2022/ngneers-controls-input.mjs.map +1 -0
  59. package/fesm2022/ngneers-controls-item-view.mjs +473 -0
  60. package/fesm2022/ngneers-controls-item-view.mjs.map +1 -0
  61. package/fesm2022/ngneers-controls-list-box.mjs +281 -0
  62. package/fesm2022/ngneers-controls-list-box.mjs.map +1 -0
  63. package/fesm2022/ngneers-controls-mask-input.mjs +888 -0
  64. package/fesm2022/ngneers-controls-mask-input.mjs.map +1 -0
  65. package/fesm2022/ngneers-controls-menu.mjs +371 -0
  66. package/fesm2022/ngneers-controls-menu.mjs.map +1 -0
  67. package/fesm2022/ngneers-controls-message.mjs +30 -0
  68. package/fesm2022/ngneers-controls-message.mjs.map +1 -0
  69. package/fesm2022/ngneers-controls-number-input.mjs +366 -0
  70. package/fesm2022/ngneers-controls-number-input.mjs.map +1 -0
  71. package/fesm2022/ngneers-controls-paginator.mjs +129 -0
  72. package/fesm2022/ngneers-controls-paginator.mjs.map +1 -0
  73. package/fesm2022/ngneers-controls-popover.mjs +215 -0
  74. package/fesm2022/ngneers-controls-popover.mjs.map +1 -0
  75. package/fesm2022/ngneers-controls-progress.mjs +98 -0
  76. package/fesm2022/ngneers-controls-progress.mjs.map +1 -0
  77. package/fesm2022/ngneers-controls-radio.mjs +152 -0
  78. package/fesm2022/ngneers-controls-radio.mjs.map +1 -0
  79. package/fesm2022/ngneers-controls-roving-focus.mjs +323 -0
  80. package/fesm2022/ngneers-controls-roving-focus.mjs.map +1 -0
  81. package/fesm2022/ngneers-controls-scroll-shadow.mjs +65 -0
  82. package/fesm2022/ngneers-controls-scroll-shadow.mjs.map +1 -0
  83. package/fesm2022/ngneers-controls-scroller.mjs +308 -0
  84. package/fesm2022/ngneers-controls-scroller.mjs.map +1 -0
  85. package/fesm2022/ngneers-controls-select-button.mjs +58 -0
  86. package/fesm2022/ngneers-controls-select-button.mjs.map +1 -0
  87. package/fesm2022/ngneers-controls-select.mjs +417 -0
  88. package/fesm2022/ngneers-controls-select.mjs.map +1 -0
  89. package/fesm2022/ngneers-controls-slider.mjs +167 -0
  90. package/fesm2022/ngneers-controls-slider.mjs.map +1 -0
  91. package/fesm2022/ngneers-controls-snackbar.mjs +414 -0
  92. package/fesm2022/ngneers-controls-snackbar.mjs.map +1 -0
  93. package/fesm2022/ngneers-controls-spin-buttons.mjs +126 -0
  94. package/fesm2022/ngneers-controls-spin-buttons.mjs.map +1 -0
  95. package/fesm2022/ngneers-controls-spinner.mjs +184 -0
  96. package/fesm2022/ngneers-controls-spinner.mjs.map +1 -0
  97. package/fesm2022/ngneers-controls-splitter.mjs +604 -0
  98. package/fesm2022/ngneers-controls-splitter.mjs.map +1 -0
  99. package/fesm2022/ngneers-controls-state.mjs +185 -0
  100. package/fesm2022/ngneers-controls-state.mjs.map +1 -0
  101. package/fesm2022/ngneers-controls-switch.mjs +39 -0
  102. package/fesm2022/ngneers-controls-switch.mjs.map +1 -0
  103. package/fesm2022/ngneers-controls-table.mjs +2634 -0
  104. package/fesm2022/ngneers-controls-table.mjs.map +1 -0
  105. package/fesm2022/ngneers-controls-tabs.mjs +336 -0
  106. package/fesm2022/ngneers-controls-tabs.mjs.map +1 -0
  107. package/fesm2022/ngneers-controls-tag.mjs +30 -0
  108. package/fesm2022/ngneers-controls-tag.mjs.map +1 -0
  109. package/fesm2022/ngneers-controls-toast.mjs +350 -0
  110. package/fesm2022/ngneers-controls-toast.mjs.map +1 -0
  111. package/fesm2022/ngneers-controls-toggle-button.mjs +70 -0
  112. package/fesm2022/ngneers-controls-toggle-button.mjs.map +1 -0
  113. package/fesm2022/ngneers-controls-tooltip.mjs +506 -0
  114. package/fesm2022/ngneers-controls-tooltip.mjs.map +1 -0
  115. package/fesm2022/ngneers-controls-tree.mjs +599 -0
  116. package/fesm2022/ngneers-controls-tree.mjs.map +1 -0
  117. package/fesm2022/ngneers-controls-upload.mjs +377 -0
  118. package/fesm2022/ngneers-controls-upload.mjs.map +1 -0
  119. package/fesm2022/ngneers-controls-utils-ng.mjs +411 -0
  120. package/fesm2022/ngneers-controls-utils-ng.mjs.map +1 -0
  121. package/fesm2022/ngneers-controls-utils.mjs +261 -0
  122. package/fesm2022/ngneers-controls-utils.mjs.map +1 -0
  123. package/fesm2022/ngneers-controls.mjs +6 -0
  124. package/fesm2022/ngneers-controls.mjs.map +1 -0
  125. package/package.json +302 -0
  126. package/types/ngneers-controls-accordion.d.ts +139 -0
  127. package/types/ngneers-controls-api-ng.d.ts +364 -0
  128. package/types/ngneers-controls-api-resize.d.ts +318 -0
  129. package/types/ngneers-controls-api.d.ts +145 -0
  130. package/types/ngneers-controls-avatar.d.ts +54 -0
  131. package/types/ngneers-controls-base.d.ts +277 -0
  132. package/types/ngneers-controls-breadcrumb.d.ts +272 -0
  133. package/types/ngneers-controls-button-group.d.ts +46 -0
  134. package/types/ngneers-controls-button.d.ts +48 -0
  135. package/types/ngneers-controls-calendar.d.ts +466 -0
  136. package/types/ngneers-controls-checkbox.d.ts +40 -0
  137. package/types/ngneers-controls-chip.d.ts +202 -0
  138. package/types/ngneers-controls-default-icons.d.ts +5 -0
  139. package/types/ngneers-controls-defer.d.ts +48 -0
  140. package/types/ngneers-controls-dialog.d.ts +399 -0
  141. package/types/ngneers-controls-directives.d.ts +171 -0
  142. package/types/ngneers-controls-drawer.d.ts +308 -0
  143. package/types/ngneers-controls-edit-inplace.d.ts +263 -0
  144. package/types/ngneers-controls-errors.d.ts +155 -0
  145. package/types/ngneers-controls-filter.d.ts +609 -0
  146. package/types/ngneers-controls-hint.d.ts +88 -0
  147. package/types/ngneers-controls-i18n-translations-de.d.ts +164 -0
  148. package/types/ngneers-controls-i18n-translations-en.d.ts +164 -0
  149. package/types/ngneers-controls-i18n.d.ts +186 -0
  150. package/types/ngneers-controls-icon.d.ts +68 -0
  151. package/types/ngneers-controls-inplace.d.ts +98 -0
  152. package/types/ngneers-controls-input-field.d.ts +248 -0
  153. package/types/ngneers-controls-input.d.ts +30 -0
  154. package/types/ngneers-controls-item-view.d.ts +215 -0
  155. package/types/ngneers-controls-list-box.d.ts +334 -0
  156. package/types/ngneers-controls-mask-input.d.ts +434 -0
  157. package/types/ngneers-controls-menu.d.ts +192 -0
  158. package/types/ngneers-controls-message.d.ts +21 -0
  159. package/types/ngneers-controls-number-input.d.ts +146 -0
  160. package/types/ngneers-controls-paginator.d.ts +255 -0
  161. package/types/ngneers-controls-popover.d.ts +116 -0
  162. package/types/ngneers-controls-progress.d.ts +49 -0
  163. package/types/ngneers-controls-radio.d.ts +91 -0
  164. package/types/ngneers-controls-roving-focus.d.ts +107 -0
  165. package/types/ngneers-controls-scroll-shadow.d.ts +25 -0
  166. package/types/ngneers-controls-scroller.d.ts +151 -0
  167. package/types/ngneers-controls-select-button.d.ts +51 -0
  168. package/types/ngneers-controls-select.d.ts +409 -0
  169. package/types/ngneers-controls-slider.d.ts +57 -0
  170. package/types/ngneers-controls-snackbar.d.ts +330 -0
  171. package/types/ngneers-controls-spin-buttons.d.ts +69 -0
  172. package/types/ngneers-controls-spinner.d.ts +117 -0
  173. package/types/ngneers-controls-splitter.d.ts +465 -0
  174. package/types/ngneers-controls-state.d.ts +102 -0
  175. package/types/ngneers-controls-switch.d.ts +18 -0
  176. package/types/ngneers-controls-table.d.ts +3127 -0
  177. package/types/ngneers-controls-tabs.d.ts +118 -0
  178. package/types/ngneers-controls-tag.d.ts +21 -0
  179. package/types/ngneers-controls-toast.d.ts +298 -0
  180. package/types/ngneers-controls-toggle-button.d.ts +48 -0
  181. package/types/ngneers-controls-tooltip.d.ts +189 -0
  182. package/types/ngneers-controls-tree.d.ts +409 -0
  183. package/types/ngneers-controls-upload.d.ts +347 -0
  184. package/types/ngneers-controls-utils-ng.d.ts +129 -0
  185. package/types/ngneers-controls-utils.d.ts +130 -0
  186. package/types/ngneers-controls.d.ts +2 -0
@@ -0,0 +1,959 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, provideAppInitializer, inject, ElementRef, computed, signal, afterRenderEffect, DestroyRef, Injector, runInInjectionContext, effect, PLATFORM_ID, DOCUMENT, Injectable, untracked, REQUEST, input, Directive } from '@angular/core';
3
+ import { registerCustomLanguages, I18n } from '@ngneers/controls/i18n';
4
+ import { Logger, deepCopy, toggleClass } from '@ngneers/controls/utils';
5
+ import { isPlatformBrowser } from '@angular/common';
6
+ import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
7
+ import { signalWithPrevious, NgnGlobal } from '@ngneers/controls/utils-ng';
8
+ import { Subject, fromEvent, skip } from 'rxjs';
9
+ import { getClassName, applyTheme, applyGlobalStyles } from '@ngneers/controls-themes';
10
+ import { globalStyles } from '@ngneers/controls-themes/base/global';
11
+
12
+ const NGN_CONFIG = new InjectionToken('NGN_CONFIG');
13
+ const defaultNgnConfig = {
14
+ logLevel: 'info',
15
+ disableAnimations: false,
16
+ theme: {
17
+ preset: null,
18
+ lazyLoaded: false,
19
+ styleScope: null,
20
+ cssLayer: 'ngn-controls',
21
+ namePrefix: 'ngn-',
22
+ },
23
+ defaults: {
24
+ stateStorage: 'session',
25
+ splitter: {
26
+ stateStorage: 'session',
27
+ },
28
+ tooltip: {
29
+ placement: 'bottom',
30
+ offset: 4,
31
+ showDelay: '0.5s',
32
+ hideDelay: '0.1s',
33
+ showArrow: true,
34
+ showOnHover: true,
35
+ showOnFocus: true,
36
+ hideOnTooltipHover: false,
37
+ hideOnClick: true,
38
+ autoAriaMode: 'description',
39
+ },
40
+ },
41
+ };
42
+ function provideNgnConfig(config) {
43
+ return [
44
+ {
45
+ provide: NGN_CONFIG,
46
+ useValue: {
47
+ logLevel: config?.logLevel ?? defaultNgnConfig.logLevel,
48
+ customTranslations: config?.customTranslations,
49
+ disableAnimations: config?.disableAnimations ?? defaultNgnConfig.disableAnimations,
50
+ theme: {
51
+ preset: config?.theme?.preset ?? defaultNgnConfig.theme.preset,
52
+ lazyLoaded: config?.theme?.lazyLoaded ?? defaultNgnConfig.theme.lazyLoaded,
53
+ styleScope: config?.theme?.styleScope ?? defaultNgnConfig.theme.styleScope,
54
+ cssLayer: config?.theme?.cssLayer === undefined
55
+ ? defaultNgnConfig.theme.cssLayer
56
+ : config?.theme?.cssLayer,
57
+ namePrefix: config?.theme?.namePrefix ?? defaultNgnConfig.theme.namePrefix,
58
+ },
59
+ defaults: {
60
+ stateStorage: config?.defaults?.stateStorage ?? defaultNgnConfig.defaults.stateStorage,
61
+ splitter: {
62
+ stateStorage: config?.defaults?.splitter?.stateStorage ??
63
+ config?.defaults?.stateStorage ??
64
+ defaultNgnConfig.defaults.splitter.stateStorage,
65
+ },
66
+ tooltip: {
67
+ placement: config?.defaults?.tooltip?.placement ?? defaultNgnConfig.defaults.tooltip.placement,
68
+ offset: config?.defaults?.tooltip?.offset ?? defaultNgnConfig.defaults.tooltip.offset,
69
+ showDelay: config?.defaults?.tooltip?.showDelay ?? defaultNgnConfig.defaults.tooltip.showDelay,
70
+ hideDelay: config?.defaults?.tooltip?.hideDelay ?? defaultNgnConfig.defaults.tooltip.hideDelay,
71
+ showArrow: config?.defaults?.tooltip?.showArrow ?? defaultNgnConfig.defaults.tooltip.showArrow,
72
+ showOnHover: config?.defaults?.tooltip?.showOnHover ??
73
+ defaultNgnConfig.defaults.tooltip.showOnHover,
74
+ showOnFocus: config?.defaults?.tooltip?.showOnFocus ??
75
+ defaultNgnConfig.defaults.tooltip.showOnFocus,
76
+ hideOnTooltipHover: config?.defaults?.tooltip?.hideOnTooltipHover ??
77
+ defaultNgnConfig.defaults.tooltip.hideOnTooltipHover,
78
+ hideOnClick: config?.defaults?.tooltip?.hideOnClick ??
79
+ defaultNgnConfig.defaults.tooltip.hideOnClick,
80
+ autoAriaMode: config?.defaults?.tooltip?.autoAriaMode ??
81
+ defaultNgnConfig.defaults.tooltip.autoAriaMode,
82
+ },
83
+ },
84
+ },
85
+ },
86
+ provideAppInitializer(() => {
87
+ const config = inject(NGN_CONFIG);
88
+ if (config?.customTranslations) {
89
+ registerCustomLanguages(config.customTranslations);
90
+ }
91
+ Logger.logLevel = config.logLevel ?? Logger.logLevel;
92
+ }),
93
+ ];
94
+ }
95
+
96
+ function toPopoverCloseBy(closeBy) {
97
+ switch (closeBy) {
98
+ case 'any':
99
+ return 'auto';
100
+ case 'escape':
101
+ case 'none':
102
+ return 'manual';
103
+ }
104
+ }
105
+ function toModalCloseBy(closeBy) {
106
+ switch (closeBy) {
107
+ case 'any':
108
+ return 'any';
109
+ case 'escape':
110
+ return 'closerequest';
111
+ case 'none':
112
+ return 'none';
113
+ }
114
+ }
115
+
116
+ function getElement(el) {
117
+ const rawElement = typeof el === 'function' ? el() : el;
118
+ const element = rawElement instanceof ElementRef ? rawElement.nativeElement : rawElement;
119
+ return element;
120
+ }
121
+ function elementSizeSignal(element, active) {
122
+ const val = elementsSizesSignalInt(element, active);
123
+ return computed(() => val()[0] ?? { width: 0, height: 0 });
124
+ }
125
+ function elementsSizesSignal(element, active) {
126
+ return elementsSizesSignalInt(element, active);
127
+ }
128
+ function elementsSizesSignalInt(element, active) {
129
+ const sizeSignal = signal([], { ...(ngDevMode ? { debugName: "sizeSignal" } : /* istanbul ignore next */ {}), equal: (a, b) => JSON.stringify(a) === JSON.stringify(b) });
130
+ let elements;
131
+ afterRenderEffect(() => {
132
+ resizeObserver.disconnect();
133
+ if (active && !active()) {
134
+ return;
135
+ }
136
+ const rawElement = typeof element === 'function' ? element() : element;
137
+ const arrayElement = Array.isArray(rawElement) ? rawElement : rawElement ? [rawElement] : [];
138
+ elements = arrayElement.map(el => (el instanceof ElementRef ? el.nativeElement : el));
139
+ if (!elements.length) {
140
+ return;
141
+ }
142
+ sizeSignal.set(elements.map(el => {
143
+ const rect = el.getBoundingClientRect();
144
+ return {
145
+ width: rect.width,
146
+ height: rect.height,
147
+ };
148
+ }));
149
+ elements.forEach(el => {
150
+ resizeObserver.observe(el, { box: 'border-box' });
151
+ });
152
+ });
153
+ const destroyRef = inject(DestroyRef);
154
+ if (typeof ResizeObserver === 'undefined') {
155
+ return sizeSignal; // ResizeObserver is not supported, return initial size
156
+ }
157
+ const resizeObserver = new ResizeObserver(entries => {
158
+ entries.forEach(entry => {
159
+ const index = elements.findIndex(el => el === entry.target);
160
+ sizeSignal.update(s => {
161
+ const copy = deepCopy(s);
162
+ const borderBoxSize = entry.borderBoxSize[0];
163
+ if (!borderBoxSize) {
164
+ return s;
165
+ }
166
+ copy[index] = {
167
+ width: borderBoxSize.inlineSize,
168
+ height: borderBoxSize.blockSize,
169
+ };
170
+ return copy;
171
+ });
172
+ });
173
+ });
174
+ destroyRef.onDestroy(() => {
175
+ resizeObserver.disconnect();
176
+ });
177
+ return sizeSignal;
178
+ }
179
+ const ABORT_SIGNAL_SYMBOL = Symbol('AbortSignal');
180
+ /**
181
+ * @todo use dom event functions directly and remove this function
182
+ * @deprecated use dom event functions instead
183
+ */
184
+ function abortSignalOnDestroy(options) {
185
+ const destroyRef = options?.injector?.get(DestroyRef) ?? inject(DestroyRef);
186
+ if (ABORT_SIGNAL_SYMBOL in destroyRef) {
187
+ return destroyRef[ABORT_SIGNAL_SYMBOL];
188
+ }
189
+ const abortController = new AbortController();
190
+ destroyRef.onDestroy(() => {
191
+ abortController.abort();
192
+ });
193
+ destroyRef[ABORT_SIGNAL_SYMBOL] = abortController.signal;
194
+ return abortController.signal;
195
+ }
196
+ /**
197
+ * Creates an Observable that emits the latest event of the specified type from the given DOM element.
198
+ *
199
+ * ⚠️ Cannot be used to cancel or call preventDefault on the event, as the event is emitted after the event handler phase.
200
+ * @param element The target DOM element or ElementRef. Either can be provided directly or via a Signal.
201
+ * @param eventName The name of the event to listen for.
202
+ * @param injector Optional injector to use for lifecycle management. If not provided, the current injector is used.
203
+ * @returns An Observable that emits the latest event of the specified type. Does not complete until the injector is destroyed.
204
+ */
205
+ function domEventObservable(element, eventName, injector) {
206
+ const inj = injector ?? inject(Injector);
207
+ const result = new Subject();
208
+ const destroyRef = inj.get(DestroyRef);
209
+ let subscription;
210
+ runInInjectionContext(inj, () => {
211
+ afterRenderEffect(() => {
212
+ subscription?.unsubscribe();
213
+ const el = getElement(element);
214
+ if (!el) {
215
+ return;
216
+ }
217
+ subscription = fromEvent(el, eventName)
218
+ .pipe(takeUntilDestroyed(destroyRef))
219
+ .subscribe(e => result.next(e));
220
+ });
221
+ });
222
+ return result.asObservable().pipe(takeUntilDestroyed(destroyRef));
223
+ }
224
+ /**
225
+ * Creates a Signal that emits the latest event of the specified type from the given DOM element.
226
+ *
227
+ * ⚠️ Cannot be used to cancel or call preventDefault on the event, as the event is emitted after the event handler phase.
228
+ * @param element The target DOM element or ElementRef. Either can be provided directly or via a Signal.
229
+ * @param eventName The name of the event to listen for.
230
+ * @param injector Optional injector to use for lifecycle management. If not provided, the current injector is used.
231
+ * @returns A Signal that emits the latest event of the specified type or null if no event has occurred.
232
+ */
233
+ function domEventSignal(element, eventName, injector) {
234
+ const inj = injector ?? inject(Injector);
235
+ const res = runInInjectionContext(inj, () => {
236
+ const sig = signal(null, /* @ts-ignore */
237
+ ...(ngDevMode ? [{ debugName: "sig" }] : /* istanbul ignore next */ []));
238
+ domEventObservable(element, eventName, inj).subscribe(value => {
239
+ sig.set(value);
240
+ });
241
+ return sig;
242
+ });
243
+ return res;
244
+ }
245
+ /**
246
+ * Attaches an event handler to a DOM element that is automatically removed when the injector is destroyed.
247
+ * @param element The target DOM element or ElementRef. Either can be provided directly or via a Signal.
248
+ * @param eventName The name of the event to listen for.
249
+ * @param handler The function to handle the event.
250
+ * @param injector Optional injector to use for lifecycle management. If not provided, the current injector is used.
251
+ * @param options Optional addEventListener options (e.g. `{ capture: true }`).
252
+ */
253
+ function domEventHandler(element, eventName, handler, injector, options) {
254
+ const inj = injector ?? inject(Injector);
255
+ const destroyRef = inj.get(DestroyRef);
256
+ runInInjectionContext(inj, () => {
257
+ const destroyed = signal(false, /* @ts-ignore */
258
+ ...(ngDevMode ? [{ debugName: "destroyed" }] : /* istanbul ignore next */ []));
259
+ destroyRef.onDestroy(() => {
260
+ destroyed.set(true);
261
+ });
262
+ const elSig = computed(() => (destroyed() ? null : getElement(element)), /* @ts-ignore */
263
+ ...(ngDevMode ? [{ debugName: "elSig" }] : /* istanbul ignore next */ []));
264
+ const elSigWithPrev = signalWithPrevious(elSig);
265
+ effect(() => {
266
+ const element = elSigWithPrev();
267
+ const previousElement = element.previous;
268
+ if (previousElement) {
269
+ previousElement.removeEventListener(eventName, handler, options);
270
+ }
271
+ if (element.current) {
272
+ element.current.addEventListener(eventName, handler, options);
273
+ }
274
+ });
275
+ });
276
+ }
277
+
278
+ class Platform {
279
+ _deviceType = signal('desktop', /* @ts-ignore */
280
+ ...(ngDevMode ? [{ debugName: "_deviceType" }] : /* istanbul ignore next */ []));
281
+ _prefersColorScheme = signal('light', /* @ts-ignore */
282
+ ...(ngDevMode ? [{ debugName: "_prefersColorScheme" }] : /* istanbul ignore next */ []));
283
+ isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
284
+ windowSize = this.isBrowser
285
+ ? elementSizeSignal(document.body)
286
+ : signal({ width: 0, height: 0 });
287
+ width = computed(() => this.windowSize().width, /* @ts-ignore */
288
+ ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
289
+ height = computed(() => this.windowSize().height, /* @ts-ignore */
290
+ ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
291
+ prefersColorScheme = this._prefersColorScheme;
292
+ constructor() {
293
+ const doc = inject(DOCUMENT);
294
+ const destroyRef = inject(DestroyRef);
295
+ const win = doc.defaultView;
296
+ if (!win || typeof win.matchMedia !== 'function') {
297
+ return;
298
+ }
299
+ // color scheme detection (before device-type early returns)
300
+ const colorMql = win.matchMedia('(prefers-color-scheme: dark)');
301
+ this._prefersColorScheme.set(colorMql.matches ? 'dark' : 'light');
302
+ const onColorChange = (e) => this._prefersColorScheme.set(e.matches ? 'dark' : 'light');
303
+ colorMql.addEventListener('change', onColorChange);
304
+ destroyRef.onDestroy(() => colorMql.removeEventListener('change', onColorChange));
305
+ // device type
306
+ const hasMouse = win.matchMedia('(any-hover: hover)').matches;
307
+ if (hasMouse) {
308
+ this._deviceType.set('desktop');
309
+ return;
310
+ }
311
+ const isSmallScreen = win.matchMedia('(max-width: 768px)').matches || win.matchMedia('(max-height: 768px)').matches;
312
+ if (isSmallScreen) {
313
+ this._deviceType.set('mobile');
314
+ }
315
+ else {
316
+ this._deviceType.set('tablet');
317
+ }
318
+ }
319
+ isTouchDevice = computed(() => {
320
+ return this._deviceType() === 'mobile' || this._deviceType() === 'tablet';
321
+ }, /* @ts-ignore */
322
+ ...(ngDevMode ? [{ debugName: "isTouchDevice" }] : /* istanbul ignore next */ []));
323
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: Platform, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
324
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: Platform });
325
+ }
326
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: Platform, decorators: [{
327
+ type: Injectable
328
+ }], ctorParameters: () => [] });
329
+
330
+ /** Storage key shared by ColorSchemeService and colorSchemeInitScript. */
331
+ const COLOR_SCHEME_STORAGE_KEY = 'ngn-color-scheme';
332
+ const COLOR_SCHEME_STORAGE = new InjectionToken('COLOR_SCHEME_STORAGE');
333
+ /** All valid {@link ColorScheme} values; used to validate values read from storage. */
334
+ const COLOR_SCHEME_VALUES = ['light', 'dark', 'system'];
335
+ /** Cycling order for the UI button; reorder here without affecting storage validation. */
336
+ const CYCLE = ['light', 'dark', 'system'];
337
+ class ColorSchemeService {
338
+ _platform = inject(Platform);
339
+ _storage = inject(COLOR_SCHEME_STORAGE);
340
+ // Must be declared after _platform and _storage: readInitial() reads both.
341
+ _preference = signal(this.readInitial(), /* @ts-ignore */
342
+ ...(ngDevMode ? [{ debugName: "_preference" }] : /* istanbul ignore next */ []));
343
+ preference = this._preference;
344
+ resolved = computed(() => {
345
+ const pref = this._preference();
346
+ return pref === 'system' ? this._platform.prefersColorScheme() : pref;
347
+ }, /* @ts-ignore */
348
+ ...(ngDevMode ? [{ debugName: "resolved" }] : /* istanbul ignore next */ []));
349
+ isDark = computed(() => this.resolved() === 'dark', /* @ts-ignore */
350
+ ...(ngDevMode ? [{ debugName: "isDark" }] : /* istanbul ignore next */ []));
351
+ constructor() {
352
+ if (!this._platform.isBrowser) {
353
+ return;
354
+ }
355
+ // Use the real browser document, not inject(DOCUMENT), so the dark class lands
356
+ // on the actual <html> element (matches the theme's :root scope; the SSR mock is a different object).
357
+ const doc = document;
358
+ effect(() => {
359
+ toggleClass(doc.documentElement, 'dark', this.isDark());
360
+ });
361
+ effect(() => {
362
+ this._storage.set(this._preference());
363
+ });
364
+ }
365
+ set(pref) {
366
+ this._preference.set(pref);
367
+ }
368
+ cycle() {
369
+ const i = CYCLE.indexOf(this._preference());
370
+ this._preference.set(CYCLE[(i + 1) % CYCLE.length] ?? 'system');
371
+ }
372
+ readInitial() {
373
+ if (!this._platform.isBrowser) {
374
+ return 'system';
375
+ }
376
+ const stored = this._storage.get();
377
+ return stored && COLOR_SCHEME_VALUES.includes(stored) ? stored : 'system';
378
+ }
379
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ColorSchemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
380
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ColorSchemeService });
381
+ }
382
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ColorSchemeService, decorators: [{
383
+ type: Injectable
384
+ }], ctorParameters: () => [] });
385
+
386
+ function webStorage(kind) {
387
+ return {
388
+ get() {
389
+ try {
390
+ const store = kind === 'local' ? localStorage : sessionStorage;
391
+ const v = store.getItem(COLOR_SCHEME_STORAGE_KEY);
392
+ return v && COLOR_SCHEME_VALUES.includes(v) ? v : null;
393
+ }
394
+ catch {
395
+ return null;
396
+ }
397
+ },
398
+ set(value) {
399
+ try {
400
+ const store = kind === 'local' ? localStorage : sessionStorage;
401
+ store.setItem(COLOR_SCHEME_STORAGE_KEY, value);
402
+ }
403
+ catch {
404
+ // ignore (private mode / blocked storage)
405
+ }
406
+ },
407
+ };
408
+ }
409
+ function resolveStorage(option) {
410
+ if (option === 'session')
411
+ return webStorage('session');
412
+ if (option === 'local' || option === undefined)
413
+ return webStorage('local');
414
+ return option;
415
+ }
416
+ function colorSchemeInitScript(options) {
417
+ const store = options?.storage === 'session' ? 'sessionStorage' : 'localStorage';
418
+ return (`(function(){try{` +
419
+ `var k='${COLOR_SCHEME_STORAGE_KEY}';` +
420
+ `var s=${store}.getItem(k);` +
421
+ `var sys=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light';` +
422
+ `var m=(s==='light'||s==='dark')?s:sys;` +
423
+ `if(m==='dark')document.documentElement.classList.add('dark');` +
424
+ `}catch(e){}})();`);
425
+ }
426
+ function withAutoColorScheme(options) {
427
+ return {
428
+ providers: [
429
+ ColorSchemeService,
430
+ { provide: COLOR_SCHEME_STORAGE, useValue: resolveStorage(options?.storage) },
431
+ // Sole purpose: eagerly instantiate the service so its constructor effects run.
432
+ provideAppInitializer(() => {
433
+ inject(ColorSchemeService);
434
+ }),
435
+ ],
436
+ };
437
+ }
438
+
439
+ function setComponentInput(componentRef, inputName, value) {
440
+ componentRef.setInput(inputName, value);
441
+ }
442
+
443
+ function themeTemplateToTemplateInfo(config, template, options) {
444
+ return {
445
+ scope: template.scope,
446
+ class: (className) => getClassName(config.theme.namePrefix, template.scope, className, options?.unstyled?.()),
447
+ classes: (classes) => {
448
+ let result = '';
449
+ for (const className in classes) {
450
+ if (classes[className]) {
451
+ if (result)
452
+ result += ' ';
453
+ result += getClassName(config.theme.namePrefix, template.scope, className, options?.unstyled?.());
454
+ }
455
+ }
456
+ return result;
457
+ },
458
+ };
459
+ }
460
+ function injectThemeTemplate(template, options) {
461
+ const config = options?.injector?.get(NGN_CONFIG) ?? inject(NGN_CONFIG);
462
+ const themeService = options?.injector?.get(ThemeService) ?? inject(ThemeService);
463
+ themeService.loadScope(template.scope);
464
+ return themeTemplateToTemplateInfo(config, template, { unstyled: options?.unstyled });
465
+ }
466
+ function injectTheme(controlName, options) {
467
+ const themeService = options?.injector?.get(ThemeService) ?? inject(ThemeService);
468
+ const theme = themeService.activeTheme();
469
+ if (!theme) {
470
+ throw new Error(`No active theme is set. Cannot inject theme for control "${controlName}". Please provide a theme in the configuration or set lazyLoaded to true if you plan to load themes dynamically.`);
471
+ }
472
+ const template = theme.parts.find(part => part.scope === controlName)?.controlTemplate;
473
+ if (!template) {
474
+ throw new Error(`The active theme "${theme.name}" does not contain a template for control "${controlName}".`);
475
+ }
476
+ return template;
477
+ }
478
+ function getAppliedClasses(klass, themeTemplateInfo) {
479
+ function suffixes() {
480
+ if (typeof klass === 'string') {
481
+ return [klass];
482
+ }
483
+ else if (typeof klass === 'object' && klass !== null) {
484
+ return Object.entries(klass)
485
+ .filter(([_, value]) => (typeof value === 'function' ? value() : value))
486
+ .map(([key, _]) => key);
487
+ }
488
+ return [];
489
+ }
490
+ const classes = suffixes().map(suffix => ({
491
+ class: themeTemplateInfo.class(suffix),
492
+ name: suffix,
493
+ }));
494
+ return classes;
495
+ }
496
+ class ThemeService {
497
+ _config = inject(NGN_CONFIG);
498
+ _loadedScopes = new Set();
499
+ _document = inject(DOCUMENT);
500
+ activeTheme = signal(this._config.theme?.preset, /* @ts-ignore */
501
+ ...(ngDevMode ? [{ debugName: "activeTheme" }] : /* istanbul ignore next */ []));
502
+ constructor() {
503
+ if (!this._config.theme?.preset && !this._config.theme?.lazyLoaded) {
504
+ console.warn('No active theme found. Please provide a theme in the configuration or set lazyLoaded to true if you plan to load themes dynamically.');
505
+ }
506
+ if (this._config.disableAnimations) {
507
+ const style = this._document.createElement('style');
508
+ style.setAttribute('ngn-style', '');
509
+ // we set the durations instead of setting animation/transition to none,
510
+ // so that the animation starts/ends are still applied & the events are still fired
511
+ style.innerHTML = `.ngn-control, .ngn-control * {
512
+ animation-duration: 0s !important;
513
+ transition-duration: 0s !important;
514
+ }`;
515
+ this._document.head.appendChild(style);
516
+ Logger.debug('Animations have been disabled via configuration.');
517
+ }
518
+ toObservable(this.activeTheme)
519
+ .pipe(takeUntilDestroyed(), skip(1))
520
+ .subscribe(this.onThemeChange.bind(this));
521
+ }
522
+ loadScope(scope) {
523
+ if (this._loadedScopes.has(scope)) {
524
+ Logger.debug(`Theme scope "${scope}" is already loaded.`);
525
+ return;
526
+ }
527
+ const activeTheme = untracked(() => this.activeTheme());
528
+ if (!activeTheme) {
529
+ Logger.debug(`No active theme set. Cannot load theme scope "${scope}". Scope will be loaded when the theme is set.`);
530
+ return;
531
+ }
532
+ else {
533
+ Logger.debug(`Loading theme scope "${scope}" for active theme "${activeTheme.name}".`);
534
+ this.applyTheme(activeTheme, [scope]);
535
+ }
536
+ this._loadedScopes.add(scope);
537
+ }
538
+ ngOnDestroy() {
539
+ Logger.debug('ThemeService is being destroyed. Unloading all theme scopes...');
540
+ this.unloadAllScopes();
541
+ this._loadedScopes.clear();
542
+ }
543
+ onThemeChange(newTheme) {
544
+ if (!newTheme) {
545
+ Logger.warn('The active theme has been set to null.');
546
+ this.unloadAllScopes();
547
+ return;
548
+ }
549
+ Logger.debug(`Active theme changed to "${newTheme.name}". Reloading scopes...`);
550
+ this.unloadAllScopes();
551
+ this._loadedScopes.forEach(scope => {
552
+ Logger.debug(`Reloading theme scope "${scope}" for active theme "${newTheme.name}".`);
553
+ this.applyTheme(newTheme, [scope]);
554
+ });
555
+ }
556
+ unloadAllScopes() {
557
+ this._document.head.querySelectorAll('style[ngn-style][data-theme-scope]').forEach(el => {
558
+ el.remove();
559
+ });
560
+ }
561
+ applyTheme(theme, scopes) {
562
+ applyTheme(theme, scopes, {
563
+ document: this._document,
564
+ layer: this._config.theme.cssLayer ?? undefined,
565
+ styleScope: this._config.theme.styleScope ?? undefined,
566
+ namePrefix: this._config.theme.namePrefix,
567
+ });
568
+ applyGlobalStyles(globalStyles, {
569
+ document: this._document,
570
+ layer: this._config.theme.cssLayer ?? undefined,
571
+ styleScope: this._config.theme.styleScope ?? undefined,
572
+ namePrefix: this._config.theme.namePrefix,
573
+ });
574
+ }
575
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
576
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ThemeService });
577
+ }
578
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ThemeService, decorators: [{
579
+ type: Injectable
580
+ }], ctorParameters: () => [] });
581
+
582
+ /**
583
+ * Reactively reads the currently active theme (which may be switched at runtime via
584
+ * `ThemeService`), falling back to the configured preset. Reading it inside a `computed`
585
+ * tracks `ThemeService.activeTheme`, so derived kind/color lists update on a theme switch.
586
+ */
587
+ function injectActiveTheme() {
588
+ const themeService = inject(ThemeService, { optional: true });
589
+ const config = inject(NGN_CONFIG);
590
+ // Only fall back to the configured preset when there is no `ThemeService`. When the service is
591
+ // present, `activeTheme()` — including its valid `null` (cleared) state — is authoritative and
592
+ // must be preserved rather than reintroducing the preset.
593
+ return computed(() => (themeService ? themeService.activeTheme() : config.theme.preset));
594
+ }
595
+ /**
596
+ * Retrieves the available kinds for a given control from the active theme, as a reactive signal
597
+ * that updates when the active theme changes. Does **not** return custom kinds defined by the
598
+ * user in `NgnCustomTypes`.
599
+ * @param controlName The name of the control to get kinds for.
600
+ * @returns A signal of the available kinds for the specified control.
601
+ */
602
+ function injectThemeControlKinds(controlName) {
603
+ const activeTheme = injectActiveTheme();
604
+ return computed(() => (activeTheme()?.meta.kinds?.[controlName] ?? []));
605
+ }
606
+ /**
607
+ * Retrieves the available colors from the active theme, as a reactive signal that updates when
608
+ * the active theme changes. Does **not** return custom colors defined by the user in
609
+ * `NgnCustomTypes`.
610
+ * @returns A signal of the available colors.
611
+ */
612
+ function injectThemeColors(controlName) {
613
+ const activeTheme = injectActiveTheme();
614
+ return computed(() => {
615
+ const theme = activeTheme();
616
+ if (controlName) {
617
+ const classNames = theme?.parts.find(x => x.scope === controlName)?.controlTemplate
618
+ ?.classNames;
619
+ if (!classNames?.includes('color-*')) {
620
+ return [];
621
+ }
622
+ }
623
+ return (theme?.meta.colors ?? []);
624
+ });
625
+ }
626
+
627
+ let floatingPromise;
628
+ let floating;
629
+ function loadFloating() {
630
+ if (!floatingPromise) {
631
+ floatingPromise = import('@floating-ui/dom').then(m => (floating = m));
632
+ }
633
+ return floatingPromise;
634
+ }
635
+ /**
636
+ * Snap a coordinate to the device-pixel grid. floating-ui returns subpixel
637
+ * coordinates; setting them raw puts the floating element (and its arrow) on
638
+ * fractional boundaries, producing blurry, non-deterministic anti-aliasing.
639
+ * Rounding by DPR keeps rendering crisp and stable. Matches floating-ui's own
640
+ * `roundByDPR` guidance.
641
+ */
642
+ function roundByDpr(el, value) {
643
+ const dpr = el.ownerDocument.defaultView?.devicePixelRatio || 1;
644
+ return Math.round(value * dpr) / dpr;
645
+ }
646
+ function filterNonNullishKeys(obj) {
647
+ return Object.fromEntries(Object.entries(obj).filter(([, value]) => value != null));
648
+ }
649
+ function mergeWithDefaults(options) {
650
+ return {
651
+ placement: 'bottom',
652
+ flip: true,
653
+ resize: true,
654
+ shift: true,
655
+ offset: 4,
656
+ stopped: false,
657
+ ...filterNonNullishKeys(options),
658
+ };
659
+ }
660
+ function positionElement(anchor, floatingEl, options = {}) {
661
+ options = mergeWithDefaults(options);
662
+ function run(fui) {
663
+ const { computePosition, flip, offset, shift, size } = fui;
664
+ const referenceElement = toReferenceElement(anchor);
665
+ const flipMiddleware = options.flip
666
+ ? flip(options.shift ? { crossAxis: 'alignment', fallbackAxisSideDirection: 'end' } : undefined)
667
+ : undefined;
668
+ const shiftMiddleware = options.shift ? shift() : undefined;
669
+ if (options.sizeConstraints) {
670
+ if (options.sizeConstraints.width || options.sizeConstraints.maxWidth) {
671
+ const refWidth = referenceElement.getBoundingClientRect().width;
672
+ if (options.sizeConstraints.width) {
673
+ if (typeof options.sizeConstraints.width === 'string') {
674
+ floatingEl.style.width = options.sizeConstraints.width;
675
+ }
676
+ else {
677
+ const widthConstraints = options.sizeConstraints.width * refWidth;
678
+ floatingEl.style.width = `min(100%, ${widthConstraints}px)`;
679
+ }
680
+ }
681
+ if (options.sizeConstraints.maxWidth) {
682
+ if (typeof options.sizeConstraints.maxWidth === 'string') {
683
+ floatingEl.style.maxWidth = options.sizeConstraints.maxWidth;
684
+ }
685
+ else {
686
+ const maxWidthConstraints = options.sizeConstraints.maxWidth * refWidth;
687
+ floatingEl.style.maxWidth = `min(100%, ${maxWidthConstraints}px)`;
688
+ }
689
+ }
690
+ }
691
+ if (options.sizeConstraints.height || options.sizeConstraints.maxHeight) {
692
+ if (options.sizeConstraints.height) {
693
+ floatingEl.style.height = options.sizeConstraints.height;
694
+ }
695
+ if (options.sizeConstraints.maxHeight) {
696
+ floatingEl.style.maxHeight = options.sizeConstraints.maxHeight;
697
+ }
698
+ }
699
+ if (options.sizeConstraints.minWidth) {
700
+ floatingEl.style.minWidth = options.sizeConstraints.minWidth;
701
+ }
702
+ }
703
+ void computePosition(referenceElement, floatingEl, {
704
+ placement: options.placement,
705
+ strategy: options.strategy,
706
+ middleware: [
707
+ options.offset ? offset(options.offset) : undefined,
708
+ options.resize
709
+ ? size({
710
+ apply({ availableHeight }) {
711
+ const maxHeightConstraint = options.sizeConstraints?.maxHeight;
712
+ const maxHeightInPx = maxHeightConstraint?.replace(/px$/, '');
713
+ const maxHeight = maxHeightInPx
714
+ ? Math.min(availableHeight, parseInt(maxHeightInPx))
715
+ : availableHeight;
716
+ if (options.hasShrinkableContent) {
717
+ Object.assign(floatingEl.style, {
718
+ height: `${maxHeight - 1}px`,
719
+ });
720
+ }
721
+ },
722
+ })
723
+ : undefined,
724
+ ...(options.placement?.includes('-')
725
+ ? [flipMiddleware, shiftMiddleware]
726
+ : [shiftMiddleware, flipMiddleware]),
727
+ ...(options.middleware || []),
728
+ ].filter(Boolean),
729
+ }).then(pos => {
730
+ if (!options.disableSettingStyles) {
731
+ const flipped = pos.middlewareData.flip?.index;
732
+ const flippedToLREnd = flipped &&
733
+ (pos.placement.startsWith('left-') || pos.placement.startsWith('right-')) &&
734
+ pos.placement.endsWith('-end');
735
+ const flippedToTop = flipped && pos.placement.startsWith('top');
736
+ Object.assign(floatingEl.style, {
737
+ left: `${roundByDpr(floatingEl, pos.x)}px`,
738
+ top: `${roundByDpr(floatingEl, pos.y)}px`,
739
+ justifyContent: flippedToLREnd || flippedToTop ? 'flex-end' : 'flex-start',
740
+ });
741
+ }
742
+ options.onPositionChange?.(pos);
743
+ });
744
+ }
745
+ if (floating) {
746
+ run(floating);
747
+ }
748
+ else {
749
+ void loadFloating().then(run);
750
+ }
751
+ }
752
+ function toReferenceElement(anchor) {
753
+ const referenceElement = anchor instanceof HTMLElement
754
+ ? anchor
755
+ : {
756
+ getBoundingClientRect: () => ({
757
+ x: anchor.x,
758
+ y: anchor.y,
759
+ width: 1,
760
+ height: 1,
761
+ top: anchor.y,
762
+ right: anchor.x + 1,
763
+ bottom: anchor.y + 1,
764
+ left: anchor.x,
765
+ }),
766
+ };
767
+ return referenceElement;
768
+ }
769
+ function autoPositionElement(anchor, floatingEl, options = {}) {
770
+ options = mergeWithDefaults(options);
771
+ // Prefetch the floating-ui chunk as soon as a tooltip/popover directive instantiates
772
+ // so the first open does not flash. This is a download trigger only — it does not start.
773
+ void loadFloating();
774
+ let cleanup;
775
+ let started = false; // intent: should be running
776
+ const destroyRef = options.injector?.get(DestroyRef) ?? inject(DestroyRef);
777
+ function startAutoUpdate(fui) {
778
+ // Defensive: a previous start may have already assigned a live cleanup
779
+ // (e.g. two rapid start() calls sharing the cached load promise).
780
+ cleanup?.();
781
+ cleanup = fui.autoUpdate(toReferenceElement(anchor), floatingEl, () => {
782
+ positionElement(anchor, floatingEl, options);
783
+ });
784
+ }
785
+ destroyRef.onDestroy(() => {
786
+ started = false;
787
+ cleanup?.();
788
+ cleanup = undefined;
789
+ });
790
+ return {
791
+ start: () => {
792
+ started = true;
793
+ if (floating) {
794
+ // Atomic teardown+reinstall, matching the original synchronous behavior.
795
+ startAutoUpdate(floating);
796
+ }
797
+ else {
798
+ cleanup?.();
799
+ cleanup = undefined;
800
+ void loadFloating().then(fui => {
801
+ if (!started) {
802
+ return; // a stop()/destroy landed during load — bail, no leak
803
+ }
804
+ startAutoUpdate(fui);
805
+ });
806
+ }
807
+ },
808
+ stop: () => {
809
+ started = false;
810
+ cleanup?.();
811
+ cleanup = undefined;
812
+ },
813
+ isRunning: () => started,
814
+ };
815
+ }
816
+ function splitPlacement(placement) {
817
+ return placement.split('-');
818
+ }
819
+
820
+ function provideNgnControls(config, ...features) {
821
+ return [
822
+ NgnGlobal,
823
+ ThemeService,
824
+ I18n,
825
+ Platform,
826
+ provideNgnConfig(config),
827
+ ...features.map(f => f.providers).flat(),
828
+ ];
829
+ }
830
+
831
+ /** One year, in seconds — used as the cookie `max-age`. */
832
+ const COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
833
+ class NgnStorage {
834
+ _store;
835
+ _set;
836
+ set(key, value) {
837
+ untracked(() => {
838
+ const newVal = {
839
+ ...this._store(),
840
+ [key]: value,
841
+ };
842
+ this._store.set(newVal);
843
+ this._set(newVal);
844
+ });
845
+ }
846
+ get(key) {
847
+ return this._store()[key];
848
+ }
849
+ /**
850
+ * NOTE: must be constructed within an injection context — the built-in kinds
851
+ * read the platform (`PLATFORM_ID`) to stay SSR-safe, and the `cookie` kind
852
+ * additionally reads `DOCUMENT` / the SSR `REQUEST`.
853
+ */
854
+ constructor(key, kind, initialValue) {
855
+ this._store = signal(initialValue, /* @ts-ignore */
856
+ ...(ngDevMode ? [{ debugName: "_store" }] : /* istanbul ignore next */ []));
857
+ // Custom adapter — the caller fully controls persistence.
858
+ if (typeof kind === 'object') {
859
+ this._store.set(kind.initialize() ?? initialValue);
860
+ this._set = kind.update;
861
+ return;
862
+ }
863
+ const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
864
+ // Cookies are the only kind readable during SSR (from the request headers).
865
+ if (kind === 'cookie') {
866
+ const doc = inject(DOCUMENT);
867
+ const cookieString = isBrowser
868
+ ? doc.cookie
869
+ : (inject(REQUEST, { optional: true })?.headers.get('cookie') ?? '');
870
+ this._store.set(this._parse(key, readCookie(cookieString, key), initialValue));
871
+ // Writes only happen in the browser; the server render is read-only.
872
+ this._set = isBrowser
873
+ ? value => {
874
+ const encoded = encodeURIComponent(JSON.stringify(value ?? {}));
875
+ const secure = doc.location?.protocol === 'https:' ? ';secure' : '';
876
+ doc.cookie = `${encodeURIComponent(key)}=${encoded};path=/;max-age=${COOKIE_MAX_AGE};samesite=lax${secure}`;
877
+ }
878
+ : () => {
879
+ /* no-op on the server */
880
+ };
881
+ return;
882
+ }
883
+ // localstorage / sessionstorage are browser-only; no-op (keep the initial
884
+ // value) during SSR so the render doesn't crash.
885
+ if (!isBrowser) {
886
+ this._set = () => {
887
+ /* no-op on the server */
888
+ };
889
+ return;
890
+ }
891
+ const storage = kind === 'localstorage' ? localStorage : sessionStorage;
892
+ this._store.set(this._parse(key, storage.getItem(key), initialValue));
893
+ this._set = (value) => storage.setItem(key, JSON.stringify(value ?? {}));
894
+ }
895
+ /** Parse persisted JSON, falling back to the caller's initial value. */
896
+ _parse(key, raw, fallback) {
897
+ if (!raw) {
898
+ return fallback;
899
+ }
900
+ try {
901
+ return JSON.parse(raw);
902
+ }
903
+ catch (error) {
904
+ Logger.warn(`Could not parse stored value for key ${key}`, error);
905
+ return fallback;
906
+ }
907
+ }
908
+ }
909
+ /** Read a single cookie value from a `Cookie` header / `document.cookie` string. */
910
+ function readCookie(cookieString, key) {
911
+ const name = encodeURIComponent(key);
912
+ for (const part of cookieString.split(';')) {
913
+ const separator = part.indexOf('=');
914
+ if (separator === -1) {
915
+ continue;
916
+ }
917
+ if (part.slice(0, separator).trim() === name) {
918
+ return decodeURIComponent(part.slice(separator + 1));
919
+ }
920
+ }
921
+ return null;
922
+ }
923
+
924
+ class NgnTemplate {
925
+ ngnTemplate = input.required(/* @ts-ignore */
926
+ ...(ngDevMode ? [{ debugName: "ngnTemplate" }] : /* istanbul ignore next */ []));
927
+ static ngTemplateContextGuard(dir, ctx) {
928
+ return true;
929
+ }
930
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
931
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: NgnTemplate, isStandalone: true, selector: "ng-template[ngnTemplate]", inputs: { ngnTemplate: { classPropertyName: "ngnTemplate", publicName: "ngnTemplate", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
932
+ }
933
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnTemplate, decorators: [{
934
+ type: Directive,
935
+ args: [{
936
+ selector: 'ng-template[ngnTemplate]',
937
+ }]
938
+ }], propDecorators: { ngnTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngnTemplate", required: true }] }] } });
939
+ /**
940
+ * A noop with return type T.
941
+ * This function is used to define typed properties to use in a template.
942
+ */
943
+ function templateTypesFn() {
944
+ return {};
945
+ }
946
+ /**
947
+ * A noop with return type `{ $implicit: Implicit } & Named`.
948
+ * This function is used to define a typed property to use in a template.
949
+ */
950
+ function templateTypeFn() {
951
+ return {};
952
+ }
953
+
954
+ /**
955
+ * Generated bundle index. Do not edit.
956
+ */
957
+
958
+ export { COLOR_SCHEME_STORAGE, COLOR_SCHEME_STORAGE_KEY, COLOR_SCHEME_VALUES, ColorSchemeService, NGN_CONFIG, NgnStorage, NgnTemplate, Platform, ThemeService, abortSignalOnDestroy, autoPositionElement, colorSchemeInitScript, defaultNgnConfig, domEventHandler, domEventObservable, domEventSignal, elementSizeSignal, elementsSizesSignal, getAppliedClasses, injectTheme, injectThemeColors, injectThemeControlKinds, injectThemeTemplate, positionElement, provideNgnConfig, provideNgnControls, roundByDpr, setComponentInput, splitPlacement, templateTypeFn, templateTypesFn, themeTemplateToTemplateInfo, toModalCloseBy, toPopoverCloseBy, withAutoColorScheme };
959
+ //# sourceMappingURL=ngneers-controls-api-ng.mjs.map