@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,620 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, DestroyRef, signal, afterNextRender, InjectionToken, forwardRef, ElementRef, Injector, input, booleanAttribute, computed, viewChildren, effect, Directive, isDevMode, model } from '@angular/core';
3
+ import { setInputSignalValue, effectWithPrevious, classSignal, generateElementId } from '@ngneers/controls/utils-ng';
4
+ import { Platform, injectThemeTemplate, injectThemeControlKinds, injectThemeColors, getAppliedClasses } from '@ngneers/controls/api/ng';
5
+ import { toggleClass, getPropertyIfExists, objectKeys } from '@ngneers/controls/utils';
6
+
7
+ const NGN_INSTANCE_KEY = '__ngneers_control_instance__';
8
+ function elementWithInstance(element) {
9
+ return element;
10
+ }
11
+ function setNgnInstance(element, instance) {
12
+ elementWithInstance(element)[NGN_INSTANCE_KEY] = instance;
13
+ inject(DestroyRef).onDestroy(() => {
14
+ delete elementWithInstance(element)[NGN_INSTANCE_KEY];
15
+ });
16
+ }
17
+ function getNgnInstance(element) {
18
+ return elementWithInstance(element)[NGN_INSTANCE_KEY];
19
+ }
20
+ function getNearestNgnInstance(element, kind) {
21
+ let current = element;
22
+ while (current) {
23
+ const instance = elementWithInstance(current)[NGN_INSTANCE_KEY];
24
+ if (instance && (!kind || instance instanceof kind)) {
25
+ return instance;
26
+ }
27
+ current = current.parentElement;
28
+ }
29
+ return null;
30
+ }
31
+ function getNearestNgnInstanceSig(element, kind) {
32
+ const sig = signal(null, /* @ts-ignore */
33
+ ...(ngDevMode ? [{ debugName: "sig" }] : /* istanbul ignore next */ []));
34
+ afterNextRender(() => {
35
+ sig.set(getNearestNgnInstance(element, kind));
36
+ });
37
+ return sig;
38
+ }
39
+
40
+ const NGN_CONTROL = new InjectionToken('NGN_CONTROL');
41
+ /* eslint-enable typescript/no-explicit-any */
42
+ /**
43
+ * @internal
44
+ * Provides the control itself for dependency injection.
45
+ * @param control - The control class to provide.
46
+ * @returns A provider for the control itself.
47
+ */
48
+ function provideSelf(control) {
49
+ return { provide: NGN_CONTROL, useExisting: forwardRef(() => control) };
50
+ }
51
+ class NgnBase {
52
+ _defaultKind = signal(undefined, /* @ts-ignore */
53
+ ...(ngDevMode ? [{ debugName: "_defaultKind" }] : /* istanbul ignore next */ []));
54
+ _defaultColor = signal(undefined, /* @ts-ignore */
55
+ ...(ngDevMode ? [{ debugName: "_defaultColor" }] : /* istanbul ignore next */ []));
56
+ _kindOverride = signal(undefined, /* @ts-ignore */
57
+ ...(ngDevMode ? [{ debugName: "_kindOverride" }] : /* istanbul ignore next */ []));
58
+ /**
59
+ * The element reference for the host element.
60
+ */
61
+ element = inject(ElementRef);
62
+ /**
63
+ * The injector for the component.
64
+ */
65
+ injector = inject(Injector);
66
+ /**
67
+ * If true, the control will not apply any theme styles.
68
+ * The base styles of the control will still be applied.
69
+ * @default false
70
+ */
71
+ unstyled = input(false, { ...(ngDevMode ? { debugName: "unstyled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
72
+ /**
73
+ * Some controls support custom kinds for styling purposes.
74
+ * The available kinds depend on the control and the theme used.
75
+ *
76
+ * TODO: link to custom types documentation
77
+ */
78
+ kind = input(undefined, /* @ts-ignore */
79
+ ...(ngDevMode ? [{ debugName: "kind" }] : /* istanbul ignore next */ []));
80
+ color = input(undefined, /* @ts-ignore */
81
+ ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
82
+ appliedKind = computed(() => this._kindOverride() ?? this.kind() ?? this._defaultKind(), /* @ts-ignore */
83
+ ...(ngDevMode ? [{ debugName: "appliedKind" }] : /* istanbul ignore next */ []));
84
+ appliedColor = computed(() => this.color() ?? this._defaultColor(), /* @ts-ignore */
85
+ ...(ngDevMode ? [{ debugName: "appliedColor" }] : /* istanbul ignore next */ []));
86
+ /**
87
+ * Custom passthrough attributes to apply to the control's theme classes and its dependencies.
88
+ * This allows for fine-grained customization of attributes, styles and more.
89
+ */
90
+ pt = input(/* @ts-ignore */
91
+ ...(ngDevMode ? [undefined, { debugName: "pt" }] : /* istanbul ignore next */ []));
92
+ /**
93
+ * Marks a control as the primary value control of a surrounding field
94
+ * (e.g. `ngn-input-field`). Fields resolve their projected control by
95
+ * filtering for this flag, so auxiliary controls placed inside the field
96
+ * (buttons, icons, spin buttons, …) never shadow the actual input.
97
+ * Overridden with `true` by input, mask-input, calendar, select, ….
98
+ */
99
+ isFieldControl = false;
100
+ /**
101
+ * Hook for placing focus/selection from a pointer event that originated in the
102
+ * surrounding field (or the control itself). The default does nothing and
103
+ * returns `false`, signalling the surrounding field should fall back to its
104
+ * primitive focusing. Controls that own internal focus placement (e.g.
105
+ * mask-input) override this, act on the pointer location, and return `true`.
106
+ */
107
+ focusFromPointer(_event) {
108
+ return false;
109
+ }
110
+ /**
111
+ * Hook for stepping the control's value (e.g. from `ngn-spin-buttons` or
112
+ * other external steppers). The default does nothing and returns `false`.
113
+ * Controls with a steppable value (e.g. number-input) override this, apply
114
+ * the step and return `true`.
115
+ * @param _direction - `1` to increment, `-1` to decrement.
116
+ * @param _big - Whether to apply the control's big step (e.g. Shift+Arrow).
117
+ */
118
+ stepValue(_direction, _big = false) {
119
+ return false;
120
+ }
121
+ /**
122
+ * Hook telling external steppers (e.g. `ngn-spin-buttons`) whether the value
123
+ * can currently be stepped in the given direction. The default returns
124
+ * `false`; controls overriding {@link stepValue} override this as well
125
+ * (typically `false` at a min/max bound or while disabled/readonly).
126
+ */
127
+ canStepValue(_direction) {
128
+ return false;
129
+ }
130
+ /**
131
+ * Hook for clearing the control's own value/state (e.g. from a surrounding
132
+ * field's clear button). The default does nothing and returns `false`, so the
133
+ * caller falls back to clearing the underlying DOM input. Controls that manage
134
+ * their own value (e.g. mask-input) override this to reset and return `true`.
135
+ */
136
+ clearValue() {
137
+ return false;
138
+ }
139
+ /**
140
+ * Temporarily overrides the authored kind without mutating the public input.
141
+ * Intended for derived control states such as validation-driven hints.
142
+ */
143
+ setKindOverride(kind) {
144
+ this._kindOverride.set(kind);
145
+ }
146
+ _childNgnControls = viewChildren(NGN_CONTROL, /* @ts-ignore */
147
+ ...(ngDevMode ? [{ debugName: "_childNgnControls" }] : /* istanbul ignore next */ []));
148
+ _afterLeaveCbs = [];
149
+ constructor() {
150
+ // `pt` is typed against this control's own `T`, which makes `this`
151
+ // (NgnBase<T>) structurally incompatible with the type-erased AnyNgnBase
152
+ // alias for an unconstrained/generic T. Safe: AnyNgnBase never reads `pt`
153
+ // in a way that depends on the real T at the call site.
154
+ setNgnInstance(this.element.nativeElement, this);
155
+ this.prepareAfterLeaveHook();
156
+ effect(() => {
157
+ // Propagate unstyled state to direct child controls, does not affect
158
+ // custom user content passed into ng-content or projected templates.
159
+ this._childNgnControls().forEach(child => {
160
+ setInputSignalValue(child.unstyled, this.unstyled());
161
+ });
162
+ });
163
+ this.initializeKindAndColorClasses();
164
+ if (inject(Platform).isBrowser) {
165
+ // Remove the initializing class after the first render to prevent FOUC.
166
+ afterNextRender(() => {
167
+ this.element.nativeElement.classList.remove('ngn-control-initializing');
168
+ });
169
+ }
170
+ else {
171
+ // Remove the initializing class immediately on the server to serve a complete initial HTML.
172
+ this.element.nativeElement.classList.remove('ngn-control-initializing');
173
+ }
174
+ }
175
+ /**
176
+ * Prepares the after leave hook to be called when the control is destroyed
177
+ * and its leave animation has finished.
178
+ */
179
+ prepareAfterLeaveHook() {
180
+ inject(DestroyRef).onDestroy(() => {
181
+ if (typeof this.element.nativeElement.getAnimations !== 'function') {
182
+ this.afterLeaveInternal();
183
+ }
184
+ else {
185
+ NgnBase._enqueueLeaveCheck(this.element.nativeElement, () => this.afterLeaveInternal());
186
+ }
187
+ });
188
+ }
189
+ // --- Batched leave animation checking ---
190
+ // Collects all destroyed components in the same frame into one queue,
191
+ // then processes them in a single rAF to avoid layout thrashing from
192
+ // interleaved getAnimations() calls and DOM mutations.
193
+ static _leaveQueue = [];
194
+ static _leaveRafScheduled = false;
195
+ static _enqueueLeaveCheck(element, callback) {
196
+ NgnBase._leaveQueue.push({ element, callback });
197
+ if (!NgnBase._leaveRafScheduled) {
198
+ NgnBase._leaveRafScheduled = true;
199
+ requestAnimationFrame(() => NgnBase._processLeaveQueue());
200
+ }
201
+ }
202
+ static _processLeaveQueue() {
203
+ const queue = NgnBase._leaveQueue;
204
+ NgnBase._leaveQueue = [];
205
+ NgnBase._leaveRafScheduled = false;
206
+ // Phase 1: Read all animations (batched queries — no interleaved mutations)
207
+ const results = [];
208
+ for (const { element, callback } of queue) {
209
+ results.push({
210
+ animation: NgnBase._findLeaveAnimation(element),
211
+ callback,
212
+ });
213
+ }
214
+ // Phase 2: Process results (no more style queries needed)
215
+ for (const { animation, callback } of results) {
216
+ if (!animation) {
217
+ callback();
218
+ }
219
+ else {
220
+ animation.finished
221
+ .catch(() => {
222
+ // ignore errors from animations being cancelled
223
+ })
224
+ .finally(() => {
225
+ callback();
226
+ });
227
+ }
228
+ }
229
+ }
230
+ /**
231
+ * Recursively searches for a leave animation on the element and its parents.
232
+ */
233
+ static _findLeaveAnimation(element) {
234
+ const animations = element.getAnimations();
235
+ const leaveAnimation = animations.find(a => a instanceof CSSAnimation && a.animationName.endsWith('-leave'));
236
+ if (leaveAnimation) {
237
+ return leaveAnimation;
238
+ }
239
+ if (element.parentElement) {
240
+ return NgnBase._findLeaveAnimation(element.parentElement);
241
+ }
242
+ return null;
243
+ }
244
+ afterLeaveInternal() {
245
+ /**
246
+ * After the control is hidden/removed from the DOM,
247
+ * manually wipe the innerHTML to ensure no detached children remain
248
+ * mapped to this component's LView slots in the browser's memory.
249
+ * This is especially important for controls that use ng-content
250
+ * or dynamically render child components.
251
+ */
252
+ this.element.nativeElement.innerHTML = '';
253
+ this._afterLeaveCbs.forEach(cb => cb());
254
+ this.afterLeave();
255
+ }
256
+ /**
257
+ * Lifecycle hook that is called after the control has left (been removed from) the DOM.
258
+ * Can be overridden by subclasses to perform custom actions after the leave animation.
259
+ */
260
+ afterLeave() {
261
+ // Can be overridden by subclasses
262
+ }
263
+ /**
264
+ * @internal
265
+ */
266
+ injectThemeTemplate(template, hostClass) {
267
+ const opts = { unstyled: this.unstyled };
268
+ const theme = injectThemeTemplate(template, opts);
269
+ const kinds = injectThemeControlKinds(theme.scope)();
270
+ if (kinds.length) {
271
+ this._defaultKind.set(kinds[0]);
272
+ }
273
+ const colors = injectThemeColors(theme.scope)();
274
+ if (colors.length) {
275
+ this._defaultColor.set(colors[0]);
276
+ }
277
+ if (hostClass !== undefined) {
278
+ // See constructor comment: `this` (NgnBase<T>) isn't structurally assignable
279
+ // to NgnBaseSafe<T & string> for a generic T because `pt`'s type still
280
+ // depends on T. Narrow the erasure to this control's own T & string, which
281
+ // matches what NgnPtEngine actually needs here.
282
+ new NgnPtEngine(this, hostClass);
283
+ }
284
+ return theme;
285
+ }
286
+ /**
287
+ * Toggles the kind and color theme classes based on the current values of the kind and color inputs.
288
+ */
289
+ initializeKindAndColorClasses() {
290
+ this.initializeAutoThemeClasses('kind', this.appliedKind);
291
+ this.initializeAutoThemeClasses('color', this.appliedColor);
292
+ }
293
+ /**
294
+ * Initializes automatic theme class toggling for a given prefix based on the provided string signal.
295
+ *
296
+ * ⚠️ Caution: This method assumes that the control's theme supports classes with the given prefix.
297
+ *
298
+ * ⚠️ Caution: Make sure the signal provides only string or nullish values.
299
+ *
300
+ * @param prefix The prefix for the theme class (e.g., 'kind' or 'color').
301
+ * @param signal The signal that provides the current string value for the theme class.
302
+ */
303
+ initializeAutoThemeClasses(prefix, signal) {
304
+ const element = this.element.nativeElement;
305
+ /**
306
+ * We can't know here whether the control has any kind or color classes,
307
+ * so we cast explicitly to ThemeClassToken. In practice, controls without
308
+ * kind or color classes will simply not toggle any classes as the input type does not allow it.
309
+ */
310
+ const classToken = (value) => value;
311
+ const togglePrefixedThemeClass = (prefix, value, enabled) => {
312
+ if (!value) {
313
+ return;
314
+ }
315
+ const theme = this.theme;
316
+ if (!theme) {
317
+ return;
318
+ }
319
+ toggleClass(element, theme.class(classToken(`${prefix}-${value}`)), enabled);
320
+ };
321
+ effectWithPrevious(signal, (current, previous) => {
322
+ togglePrefixedThemeClass(prefix, previous, false);
323
+ togglePrefixedThemeClass(prefix, current, true);
324
+ });
325
+ this._afterLeaveCbs.push(() => {
326
+ togglePrefixedThemeClass(prefix, signal(), false);
327
+ });
328
+ }
329
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
330
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.5", type: NgnBase, isStandalone: true, inputs: { unstyled: { classPropertyName: "unstyled", publicName: "unstyled", isSignal: true, isRequired: false, transformFunction: null }, kind: { classPropertyName: "kind", publicName: "kind", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, pt: { classPropertyName: "pt", publicName: "pt", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "ngn-control ngn-control-initializing" }, viewQueries: [{ propertyName: "_childNgnControls", predicate: NGN_CONTROL, descendants: true, isSignal: true }], ngImport: i0 });
331
+ }
332
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnBase, decorators: [{
333
+ type: Directive,
334
+ args: [{
335
+ host: { class: 'ngn-control ngn-control-initializing' },
336
+ }]
337
+ }], ctorParameters: () => [], propDecorators: { unstyled: [{ type: i0.Input, args: [{ isSignal: true, alias: "unstyled", required: false }] }], kind: [{ type: i0.Input, args: [{ isSignal: true, alias: "kind", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], pt: [{ type: i0.Input, args: [{ isSignal: true, alias: "pt", required: false }] }], _childNgnControls: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => NGN_CONTROL), { isSignal: true }] }] } });
338
+
339
+ class NgnPtEngine {
340
+ pt;
341
+ ptClass;
342
+ _elementRef = inject(ElementRef);
343
+ _destroyRef = inject(DestroyRef);
344
+ /** The passthrough values currently applied to the element, for teardown on destroy. */
345
+ _currentAppliedPts;
346
+ classes = computed(() => {
347
+ const pt = this.pt();
348
+ if ('theme' in pt) {
349
+ const theme = pt['theme'];
350
+ const res = getAppliedClasses(this.ptClass(), theme);
351
+ return res;
352
+ }
353
+ return [];
354
+ }, /* @ts-ignore */
355
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
356
+ _appliedPts = computed(() => {
357
+ const pt = this.pt()?.pt();
358
+ if (!pt) {
359
+ return;
360
+ }
361
+ const classes = this.classes();
362
+ return classes.map(({ name: ptClass }) => {
363
+ const classPt = getPropertyIfExists(pt, ptClass);
364
+ return classPt;
365
+ });
366
+ }, /* @ts-ignore */
367
+ ...(ngDevMode ? [{ debugName: "_appliedPts" }] : /* istanbul ignore next */ []));
368
+ constructor(pt, ptClass, options) {
369
+ const sigPt = typeof pt === 'function' ? pt : signal(pt);
370
+ const sigPtClass = typeof ptClass === 'function' ? ptClass : signal(ptClass);
371
+ this.pt = sigPt;
372
+ this.ptClass = sigPtClass;
373
+ const classes = computed(() => this.classes().map(c => c.class), /* @ts-ignore */
374
+ ...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
375
+ classSignal(this._elementRef.nativeElement, classes);
376
+ if (options?.applyPassthrough === false) {
377
+ return;
378
+ }
379
+ effectWithPrevious(this._appliedPts, (current, previous) => {
380
+ if (previous) {
381
+ for (const classPt of previous) {
382
+ if (!classPt) {
383
+ continue;
384
+ }
385
+ this.removePreviousPtValues(classPt);
386
+ }
387
+ }
388
+ if (current) {
389
+ for (const classPt of current) {
390
+ if (!classPt) {
391
+ continue;
392
+ }
393
+ this.setNewPtValues(classPt);
394
+ }
395
+ }
396
+ this._currentAppliedPts = current;
397
+ });
398
+ // Detach applied passthrough (notably event listeners) when the element is
399
+ // destroyed, so nothing leaks past the control's lifetime.
400
+ this._destroyRef.onDestroy(() => {
401
+ if (!this._currentAppliedPts) {
402
+ return;
403
+ }
404
+ for (const classPt of this._currentAppliedPts) {
405
+ if (!classPt) {
406
+ continue;
407
+ }
408
+ this.removePreviousPtValues(classPt);
409
+ }
410
+ });
411
+ }
412
+ setNewPtValues(classPt) {
413
+ if (classPt.$styles) {
414
+ const keys = objectKeys(classPt.$styles);
415
+ keys.forEach(key => {
416
+ this._elementRef.nativeElement.style[key] =
417
+ classPt.$styles?.[key] || '';
418
+ });
419
+ }
420
+ if (classPt.$attributes) {
421
+ const keys = objectKeys(classPt.$attributes);
422
+ keys.forEach(key => {
423
+ this._elementRef.nativeElement.setAttribute(key, classPt.$attributes?.[key] || '');
424
+ });
425
+ }
426
+ if (classPt.$classes) {
427
+ const arrayOfClasses = Array.isArray(classPt.$classes)
428
+ ? classPt.$classes.flatMap(c => c.split(' '))
429
+ : classPt.$classes.split(' ');
430
+ arrayOfClasses.forEach(cls => {
431
+ try {
432
+ this._elementRef.nativeElement.classList.add(cls);
433
+ }
434
+ catch {
435
+ console.warn(`Could not add class '${cls}' to element`, this._elementRef.nativeElement);
436
+ }
437
+ });
438
+ }
439
+ if (classPt.$listeners) {
440
+ objectKeys(classPt.$listeners).forEach(eventName => {
441
+ const handler = classPt.$listeners?.[eventName];
442
+ if (!handler) {
443
+ return;
444
+ }
445
+ this._elementRef.nativeElement.addEventListener(eventName, handler);
446
+ });
447
+ }
448
+ }
449
+ removePreviousPtValues(classPt) {
450
+ if (classPt.$styles) {
451
+ const keys = objectKeys(classPt.$styles);
452
+ keys.forEach(key => {
453
+ this._elementRef.nativeElement.style[key] = '';
454
+ });
455
+ }
456
+ if (classPt.$attributes) {
457
+ const keys = objectKeys(classPt.$attributes);
458
+ keys.forEach(key => {
459
+ this._elementRef.nativeElement.removeAttribute(key);
460
+ });
461
+ }
462
+ if (classPt.$classes) {
463
+ const arrayOfClasses = Array.isArray(classPt.$classes)
464
+ ? classPt.$classes.flatMap(c => c.split(' '))
465
+ : classPt.$classes.split(' ');
466
+ arrayOfClasses.forEach(cls => {
467
+ try {
468
+ this._elementRef.nativeElement.classList.remove(cls);
469
+ }
470
+ catch {
471
+ console.warn(`Could not remove class '${cls}' from element`, this._elementRef.nativeElement);
472
+ }
473
+ });
474
+ }
475
+ if (classPt.$listeners) {
476
+ objectKeys(classPt.$listeners).forEach(eventName => {
477
+ const listener = classPt.$listeners?.[eventName];
478
+ if (!listener) {
479
+ return;
480
+ }
481
+ this._elementRef.nativeElement.removeEventListener(eventName, listener);
482
+ });
483
+ }
484
+ }
485
+ }
486
+
487
+ // Internal passthrough directive; intentionally uses the non-prefixed `ptInt` selector.
488
+ class NgnPt {
489
+ /** The parent control whose theme/passthrough this element pulls from. */
490
+ ptInt = input.required(/* @ts-ignore */
491
+ ...(ngDevMode ? [{ debugName: "ptInt" }] : /* istanbul ignore next */ []));
492
+ /**
493
+ * Parent-scope class token(s) applied to this element, along with the parent's
494
+ * `pt` slice for those classes. Use for flat theme parts of the parent.
495
+ */
496
+ ptClass = input(/* @ts-ignore */
497
+ ...(ngDevMode ? [undefined, { debugName: "ptClass" }] : /* istanbul ignore next */ []));
498
+ /**
499
+ * Dependency-slot class on the parent. Applies the parent's marker class
500
+ * (`{parentScope}-{ptDep}`) to this element and forwards the parent's
501
+ * `pt[ptDep]` slice into the nested control that lives on this element, so the
502
+ * nested control resolves it against its own theme.
503
+ */
504
+ ptDep = input(/* @ts-ignore */
505
+ ...(ngDevMode ? [undefined, { debugName: "ptDep" }] : /* istanbul ignore next */ []));
506
+ /**
507
+ * The nested control hosted on THIS element (present when `ptDep` targets a
508
+ * control). `self: true` is essential: without it the injection climbs to an
509
+ * ancestor control (e.g. the parent that owns `[ptInt]`), and the forwarding
510
+ * effect would then overwrite the ancestor's `pt` instead of the child's.
511
+ */
512
+ _self = inject(NGN_CONTROL, {
513
+ optional: true,
514
+ self: true,
515
+ });
516
+ /**
517
+ * Flat class engine for `ptClass`: applies the parent's scope class(es) and the
518
+ * matching `pt` slice to this element. A missing `ptClass` resolves to a no-op.
519
+ */
520
+ _classEngine = new NgnPtEngine(this.ptInt, computed(() => this.ptClass() ?? {}));
521
+ /**
522
+ * Marker engine for `ptDep`: applies ONLY the parent's `{parentScope}-{ptDep}`
523
+ * class to this element (`applyPassthrough: false`). The forwarded slice is
524
+ * applied separately, through the nested control's own engine.
525
+ */
526
+ _markerEngine = new NgnPtEngine(this.ptInt, computed(() => (this.ptDep() ?? {})), { applyPassthrough: false });
527
+ /** The parent's `pt` slice for the current `ptDep` slot, or `undefined`. */
528
+ _depSlice = computed(() => {
529
+ const dep = this.ptDep();
530
+ if (!dep) {
531
+ return undefined;
532
+ }
533
+ const parentPt = this.ptInt().pt?.();
534
+ return parentPt?.[dep];
535
+ }, /* @ts-ignore */
536
+ ...(ngDevMode ? [{ debugName: "_depSlice" }] : /* istanbul ignore next */ []));
537
+ constructor() {
538
+ const self = this._self;
539
+ if (!self) {
540
+ // No nested control on this element (e.g. `ptDep` on a plain element): only
541
+ // the marker class applies; there is no child theme to forward the slice to.
542
+ // Warn (dev only) if a `ptDep` is actually set — read it in an effect, since
543
+ // inputs are not yet resolved in the constructor.
544
+ if (isDevMode()) {
545
+ effect(() => {
546
+ if (this.ptDep()) {
547
+ console.warn('[ptDep] is set but no nested control was found on this element; the parent' +
548
+ " dependency slice can't be forwarded. Did you forget to host a control here?");
549
+ }
550
+ });
551
+ }
552
+ return;
553
+ }
554
+ // Forward the parent's dependency slice into the nested control's own `pt`
555
+ // input, so the child's existing engine(s) resolve it against its theme.
556
+ // Reactive to parent `pt` changes; torn down with the directive/child. Writes
557
+ // on every run (even `undefined`) so a slice that disappears (e.g. `ptDep`
558
+ // cleared) clears the child's `pt` instead of leaving it stale; the child's
559
+ // own engine already treats a falsy `pt` as a no-op.
560
+ effect(() => {
561
+ setInputSignalValue(self.pt, this._depSlice());
562
+ });
563
+ }
564
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnPt, deps: [], target: i0.ɵɵFactoryTarget.Directive });
565
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: NgnPt, isStandalone: true, selector: "[ptInt]", inputs: { ptInt: { classPropertyName: "ptInt", publicName: "ptInt", isSignal: true, isRequired: true, transformFunction: null }, ptClass: { classPropertyName: "ptClass", publicName: "ptClass", isSignal: true, isRequired: false, transformFunction: null }, ptDep: { classPropertyName: "ptDep", publicName: "ptDep", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
566
+ }
567
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnPt, decorators: [{
568
+ type: Directive,
569
+ args: [{ selector: '[ptInt]' }]
570
+ }], ctorParameters: () => [], propDecorators: { ptInt: [{ type: i0.Input, args: [{ isSignal: true, alias: "ptInt", required: true }] }], ptClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "ptClass", required: false }] }], ptDep: [{ type: i0.Input, args: [{ isSignal: true, alias: "ptDep", required: false }] }] } });
571
+
572
+ class ValueControlBase extends NgnBase {
573
+ /**
574
+ * The label for the control.
575
+ */
576
+ label = input(null, /* @ts-ignore */
577
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
578
+ /**
579
+ * Sets the `aria-labelledby` attribute on the control.
580
+ * @default null
581
+ */
582
+ labelledBy = input(null, /* @ts-ignore */
583
+ ...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
584
+ /**
585
+ * The ID for the control
586
+ * @default generateElementId()
587
+ */
588
+ inputId = input(generateElementId(), /* @ts-ignore */
589
+ ...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
590
+ /**
591
+ * Explicitly apply invalid state styling
592
+ * @default false
593
+ */
594
+ invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
595
+ /**
596
+ * The value of the control.
597
+ */
598
+ value = model(undefined, /* @ts-ignore */
599
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
600
+ /**
601
+ * Set the disabled state of the control.
602
+ */
603
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
604
+ readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
605
+ touched = model(false, /* @ts-ignore */
606
+ ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
607
+ dirty = input(false, { ...(ngDevMode ? { debugName: "dirty" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
608
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ValueControlBase, deps: null, target: i0.ɵɵFactoryTarget.Directive });
609
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: ValueControlBase, isStandalone: true, inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, labelledBy: { classPropertyName: "labelledBy", publicName: "labelledBy", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, usesInheritance: true, ngImport: i0 });
610
+ }
611
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: ValueControlBase, decorators: [{
612
+ type: Directive
613
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelledBy", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }] } });
614
+
615
+ /**
616
+ * Generated bundle index. Do not edit.
617
+ */
618
+
619
+ export { NGN_CONTROL, NgnBase, NgnPt, NgnPtEngine, ValueControlBase, getNearestNgnInstance, getNearestNgnInstanceSig, getNgnInstance, provideSelf, setNgnInstance };
620
+ //# sourceMappingURL=ngneers-controls-base.mjs.map