@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,30 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, Component } from '@angular/core';
3
+ import { NgnBase, NgnPt, provideSelf } from '@ngneers/controls/base';
4
+ import { NgnIcon } from '@ngneers/controls/icon';
5
+ import { messageControlTemplate } from '@ngneers/controls-themes/templates/message';
6
+
7
+ /**
8
+ * @category control
9
+ */
10
+ class NgnMessage extends NgnBase {
11
+ theme = this.injectThemeTemplate(messageControlTemplate, 'root');
12
+ /**
13
+ * Set an icon to display before the text.
14
+ */
15
+ icon = input(/* @ts-ignore */
16
+ ...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
17
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnMessage, deps: null, target: i0.ɵɵFactoryTarget.Component });
18
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnMessage, isStandalone: true, selector: "ngn-message", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideSelf(NgnMessage)], usesInheritance: true, ngImport: i0, template: "@if (icon()) {\n <ngn-icon [ptInt]=\"this\" [ptClass]=\"'icon'\" [icon]=\"icon()\" />\n}\n<div [ptInt]=\"this\" [ptClass]=\"'content'\">\n <ng-content />\n</div>\n", dependencies: [{ kind: "directive", type: NgnPt, selector: "[ptInt]", inputs: ["ptInt", "ptClass", "ptDep"] }, { kind: "component", type: NgnIcon, selector: "ngn-icon", inputs: ["defaultIcon", "icon"] }] });
19
+ }
20
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnMessage, decorators: [{
21
+ type: Component,
22
+ args: [{ selector: 'ngn-message', imports: [NgnPt, NgnIcon], providers: [provideSelf(NgnMessage)], template: "@if (icon()) {\n <ngn-icon [ptInt]=\"this\" [ptClass]=\"'icon'\" [icon]=\"icon()\" />\n}\n<div [ptInt]=\"this\" [ptClass]=\"'content'\">\n <ng-content />\n</div>\n" }]
23
+ }], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }] } });
24
+
25
+ /**
26
+ * Generated bundle index. Do not edit.
27
+ */
28
+
29
+ export { NgnMessage };
30
+ //# sourceMappingURL=ngneers-controls-message.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngneers-controls-message.mjs","sources":["../../src/message/message.ts","../../src/message/message.html","../../src/message/ngneers-controls-message.ts"],"sourcesContent":["import { Component, input } from '@angular/core';\nimport { NgnBase, provideSelf, NgnPt } from '@ngneers/controls/base';\nimport { NgnIcon } from '@ngneers/controls/icon';\nimport { messageControlTemplate } from '@ngneers/controls-themes/templates/message';\n\nimport type { IconType } from '@ngneers/controls-custom-types';\n\n/**\n * @category control\n */\n@Component({\n selector: 'ngn-message',\n templateUrl: './message.html',\n imports: [NgnPt, NgnIcon],\n providers: [provideSelf(NgnMessage)],\n})\nexport class NgnMessage extends NgnBase<'message'> {\n protected readonly theme = this.injectThemeTemplate(messageControlTemplate, 'root');\n\n /**\n * Set an icon to display before the text.\n */\n public readonly icon = input<IconType>();\n}\n","@if (icon()) {\n <ngn-icon [ptInt]=\"this\" [ptClass]=\"'icon'\" [icon]=\"icon()\" />\n}\n<div [ptInt]=\"this\" [ptClass]=\"'content'\">\n <ng-content />\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;AAOA;;AAEG;AAOG,MAAO,UAAW,SAAQ,OAAkB,CAAA;IAC7B,KAAK,GAAG,IAAI,CAAC,mBAAmB,CAAC,sBAAsB,EAAE,MAAM,CAAC;AAEnF;;AAEG;AACa,IAAA,IAAI,GAAG,KAAK;wFAAY;uGAN7B,UAAU,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAV,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAU,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,aAAA,EAAA,MAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,SAAA,EAFV,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECdtC,uKAMA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDOY,KAAK,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,EAAA,OAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,OAAO,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,aAAA,EAAA,MAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;2FAGb,UAAU,EAAA,UAAA,EAAA,CAAA;kBANtB,SAAS;+BACE,aAAa,EAAA,OAAA,EAEd,CAAC,KAAK,EAAE,OAAO,CAAC,EAAA,SAAA,EACd,CAAC,WAAW,CAAA,UAAA,CAAY,CAAC,EAAA,QAAA,EAAA,uKAAA,EAAA;;;AEdtC;;AAEG;;;;"}
@@ -0,0 +1,366 @@
1
+ import * as i0 from '@angular/core';
2
+ import { numberAttribute, input, inject, LOCALE_ID, computed, signal, effect, untracked, Directive } from '@angular/core';
3
+ import { domEventHandler } from '@ngneers/controls/api/ng';
4
+ import { ValueControlBase, provideSelf } from '@ngneers/controls/base';
5
+ import { inputControlTemplate } from '@ngneers/controls-themes/templates/input';
6
+
7
+ /** Derives the decimal/grouping separators and digit glyphs for a locale. */
8
+ function localeNumberInfo(locale) {
9
+ const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);
10
+ const plain = new Intl.NumberFormat(locale, { useGrouping: false });
11
+ const digits = Array.from({ length: 10 }, (_, i) => plain.format(i));
12
+ const isAscii = digits.every((glyph, i) => glyph === String(i));
13
+ return {
14
+ decimal: parts.find(p => p.type === 'decimal')?.value ?? '.',
15
+ group: parts.find(p => p.type === 'group')?.value ?? ',',
16
+ digits: isAscii ? null : digits,
17
+ };
18
+ }
19
+ /**
20
+ * Parses user-typed text into a number using the locale's separators:
21
+ * grouping separators (and non-breaking spaces used as such) are stripped,
22
+ * the locale decimal separator is normalized to `.`. Exponent notation is
23
+ * accepted; anything else non-numeric is `invalid`.
24
+ */
25
+ function parseLocaleNumber(text, info) {
26
+ const trimmed = text.trim();
27
+ if (!trimmed) {
28
+ return { kind: 'empty' };
29
+ }
30
+ let normalized = trimmed;
31
+ // Map non-Latin digit glyphs (e.g. ar-EG ٠…٩) back to ASCII before validating.
32
+ if (info.digits) {
33
+ for (let i = 0; i < 10; i++) {
34
+ normalized = normalized.split(info.digits[i]).join(String(i));
35
+ }
36
+ }
37
+ normalized = normalized.split(info.group).join('');
38
+ // Locales that group with spaces render non-breaking variants; users type
39
+ // plain spaces — \s covers both, so strip all whitespace flavors.
40
+ normalized = normalized.replace(/\s/g, '');
41
+ if (info.decimal !== '.') {
42
+ normalized = normalized.split(info.decimal).join('.');
43
+ }
44
+ if (!/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i.test(normalized)) {
45
+ return { kind: 'invalid' };
46
+ }
47
+ const value = Number(normalized);
48
+ if (!Number.isFinite(value)) {
49
+ return { kind: 'invalid' };
50
+ }
51
+ return { kind: 'value', value };
52
+ }
53
+ /** Number of decimal places of `value` (bounded to 20, exponent-safe). */
54
+ function decimalPlaces(value) {
55
+ if (!Number.isFinite(value)) {
56
+ return 0;
57
+ }
58
+ let factor = 1;
59
+ let places = 0;
60
+ while (Math.round(value * factor) / factor !== value && places < 20) {
61
+ factor *= 10;
62
+ places++;
63
+ }
64
+ return places;
65
+ }
66
+ /** Rounds `value` to `decimals` decimal places (float-drift cleanup). */
67
+ function roundToPrecision(value, decimals) {
68
+ const factor = Math.pow(10, decimals);
69
+ return Math.round(value * factor) / factor;
70
+ }
71
+ /** Clamps `value` into `[min, max]`; open bounds when `null`/`undefined`. */
72
+ function clampValue(value, min, max) {
73
+ if (min != null && value < min) {
74
+ return min;
75
+ }
76
+ if (max != null && value > max) {
77
+ return max;
78
+ }
79
+ return value;
80
+ }
81
+ /**
82
+ * Steps `current` by `amount` in `direction`, clamped into `[min, max]` (no
83
+ * wrapping). The result is rounded to the combined precision of the operands
84
+ * so float drift (`0.1 + 0.2`) never surfaces. Stepping from `null` seeds at
85
+ * `min` (increment) or `max` (decrement), falling back to `0` on open bounds.
86
+ */
87
+ function stepNumberValue(current, direction, amount, min, max) {
88
+ if (current === null) {
89
+ const seed = direction === 1 ? (min ?? 0) : (max ?? 0);
90
+ return clampValue(seed, min, max);
91
+ }
92
+ const decimals = Math.max(decimalPlaces(current), decimalPlaces(amount));
93
+ const next = roundToPrecision(current + direction * amount, decimals);
94
+ return clampValue(next, min, max);
95
+ }
96
+
97
+ /** `numberAttribute` that keeps `undefined`/`null`/`''` as "no bound" instead of `NaN`. */
98
+ function optionalNumberAttribute(value) {
99
+ return value == null || value === '' ? undefined : numberAttribute(value);
100
+ }
101
+ /** A finite, strictly-positive step, or `undefined` for an invalid input. */
102
+ function positiveStep(value) {
103
+ return value != null && Number.isFinite(value) && value > 0 ? value : undefined;
104
+ }
105
+ /**
106
+ * Turns a native `<input>` into a locale-aware number input with a
107
+ * `number | null` value model:
108
+ *
109
+ * - Free typing while focused; the value is committed on blur/Enter
110
+ * (unparseable text reverts, out-of-range values are clamped — no wrapping).
111
+ * - Display is formatted via `Intl.NumberFormat` while blurred; while focused
112
+ * the text is the raw, ungrouped edit form.
113
+ * - `ArrowUp`/`ArrowDown` step by `step`, with `Shift` by `bigStep`.
114
+ * Results are rounded to the operands' precision so float drift never shows.
115
+ * - Exposes the {@link NgnBase.stepValue}/{@link NgnBase.canStepValue} hooks,
116
+ * so `ngn-spin-buttons` (or a surrounding field) can step the value.
117
+ *
118
+ * @category control
119
+ */
120
+ class NgnNumberInput extends ValueControlBase {
121
+ isFieldControl = true;
122
+ theme = this.injectThemeTemplate(inputControlTemplate, {
123
+ root: true,
124
+ invalid: () => this.invalid(),
125
+ empty: () => this.empty(),
126
+ });
127
+ /**
128
+ * The minimum allowed value (inclusive). Typed values below are clamped on
129
+ * commit; stepping stops here.
130
+ */
131
+ min = input(undefined, { ...(ngDevMode ? { debugName: "min" } : /* istanbul ignore next */ {}), transform: optionalNumberAttribute });
132
+ /**
133
+ * The maximum allowed value (inclusive). Typed values above are clamped on
134
+ * commit; stepping stops here.
135
+ */
136
+ max = input(undefined, { ...(ngDevMode ? { debugName: "max" } : /* istanbul ignore next */ {}), transform: optionalNumberAttribute });
137
+ /**
138
+ * The step amount applied by the spin buttons and `ArrowUp`/`ArrowDown`.
139
+ * Non-finite or non-positive values fall back to `1`.
140
+ * @default 1
141
+ */
142
+ step = input(1, { ...(ngDevMode ? { debugName: "step" } : /* istanbul ignore next */ {}), transform: numberAttribute });
143
+ /**
144
+ * The step amount applied with `Shift+ArrowUp`/`Shift+ArrowDown`.
145
+ * Non-finite or non-positive values fall back to `appliedStep * 10`.
146
+ * @default step * 10
147
+ */
148
+ bigStep = input(undefined, { ...(ngDevMode ? { debugName: "bigStep" } : /* istanbul ignore next */ {}), transform: optionalNumberAttribute });
149
+ /**
150
+ * The locale used for formatting and parsing.
151
+ * @default LOCALE_ID
152
+ */
153
+ locale = input(undefined, /* @ts-ignore */
154
+ ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
155
+ /**
156
+ * Additional `Intl.NumberFormat` options for the blurred display format only
157
+ * (e.g. `{ minimumFractionDigits: 2 }`). While focused the input shows the
158
+ * plain, ungrouped edit form, so display options never round or reshape the
159
+ * value the user is typing.
160
+ */
161
+ formatOptions = input(undefined, /* @ts-ignore */
162
+ ...(ngDevMode ? [{ debugName: "formatOptions" }] : /* istanbul ignore next */ []));
163
+ _input = this.element.nativeElement;
164
+ _localeId = inject(LOCALE_ID);
165
+ appliedLocale = computed(() => this.locale() ?? this._localeId, /* @ts-ignore */
166
+ ...(ngDevMode ? [{ debugName: "appliedLocale" }] : /* istanbul ignore next */ []));
167
+ /** Sanitized step — a finite positive number, defaulting to `1`. */
168
+ appliedStep = computed(() => positiveStep(this.step()) ?? 1, /* @ts-ignore */
169
+ ...(ngDevMode ? [{ debugName: "appliedStep" }] : /* istanbul ignore next */ []));
170
+ appliedBigStep = computed(() => positiveStep(this.bigStep()) ?? this.appliedStep() * 10, /* @ts-ignore */
171
+ ...(ngDevMode ? [{ debugName: "appliedBigStep" }] : /* istanbul ignore next */ []));
172
+ _localeInfo = computed(() => localeNumberInfo(this.appliedLocale()), /* @ts-ignore */
173
+ ...(ngDevMode ? [{ debugName: "_localeInfo" }] : /* istanbul ignore next */ []));
174
+ _displayFormat = computed(() => new Intl.NumberFormat(this.appliedLocale(), {
175
+ maximumFractionDigits: 20,
176
+ ...this.formatOptions(),
177
+ }), /* @ts-ignore */
178
+ ...(ngDevMode ? [{ debugName: "_displayFormat" }] : /* istanbul ignore next */ []));
179
+ // The edit formatter is deliberately plain — it does NOT apply formatOptions.
180
+ // Display options (fraction digits, currency, percent) would round the
181
+ // in-progress value or make the editable text unparsable on commit.
182
+ _editFormat = computed(() => new Intl.NumberFormat(this.appliedLocale(), {
183
+ maximumFractionDigits: 20,
184
+ useGrouping: false,
185
+ }), /* @ts-ignore */
186
+ ...(ngDevMode ? [{ debugName: "_editFormat" }] : /* istanbul ignore next */ []));
187
+ /** Whether the input currently holds focus (raw edit format while focused). */
188
+ focused = signal(false, /* @ts-ignore */
189
+ ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
190
+ /** Mirror of the input element's current text (drives the `empty` class). */
191
+ _text = signal(this._input.value ?? '', /* @ts-ignore */
192
+ ...(ngDevMode ? [{ debugName: "_text" }] : /* istanbul ignore next */ []));
193
+ /**
194
+ * The last value applied by an internal mutation (step/commit/clear) that
195
+ * already set the edit text itself. Lets the value→text effect ignore those
196
+ * changes while focused, so a deferred flush can't clobber text the user
197
+ * typed right after (`undefined` = nothing applied internally yet).
198
+ */
199
+ _appliedValue = signal(undefined, /* @ts-ignore */
200
+ ...(ngDevMode ? [{ debugName: "_appliedValue" }] : /* istanbul ignore next */ []));
201
+ /** Whether the input holds no text at all. */
202
+ empty = computed(() => this._text() === '', /* @ts-ignore */
203
+ ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
204
+ constructor() {
205
+ super();
206
+ // External value → text. While focused only overwrite when the parsed text
207
+ // semantically differs (programmatic set wins, typing is never clobbered).
208
+ effect(() => {
209
+ const value = this.value() ?? null;
210
+ const text = untracked(() => this._text());
211
+ if (!this.focused()) {
212
+ const next = value === null ? '' : this._displayFormat().format(value);
213
+ if (next !== text) {
214
+ this._setText(next);
215
+ }
216
+ // Record the reconciled value so a later focus (with no intervening
217
+ // external change) short-circuits below instead of re-parsing the
218
+ // edit text — otherwise focusing then typing can race this effect,
219
+ // which would clobber the freshly typed text back to `value`.
220
+ this._appliedValue.set(value);
221
+ return;
222
+ }
223
+ // Only reflect EXTERNAL value changes into the edit text. Internal
224
+ // mutations (step/commit/clear) set the text themselves and record
225
+ // `_appliedValue`; skipping them here prevents a deferred effect flush
226
+ // from clobbering text the user typed right after such a mutation.
227
+ if (value === untracked(() => this._appliedValue())) {
228
+ return;
229
+ }
230
+ const parsed = parseLocaleNumber(text, untracked(() => this._localeInfo()));
231
+ const current = parsed.kind === 'value' ? parsed.value : null;
232
+ if (current !== value) {
233
+ this._setText(value === null ? '' : this._editFormat().format(value));
234
+ }
235
+ this._appliedValue.set(value);
236
+ });
237
+ domEventHandler(this.element, 'input', () => {
238
+ this._text.set(this._input.value);
239
+ });
240
+ domEventHandler(this.element, 'focus', () => {
241
+ this.focused.set(true);
242
+ const value = this.value() ?? null;
243
+ if (value !== null) {
244
+ this._setText(this._editFormat().format(value));
245
+ }
246
+ });
247
+ domEventHandler(this.element, 'blur', () => {
248
+ this._commit();
249
+ this.focused.set(false);
250
+ this.touched.set(true);
251
+ });
252
+ domEventHandler(this.element, 'keydown', event => {
253
+ if (this.disabled() || this.readonly()) {
254
+ return;
255
+ }
256
+ if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
257
+ if (event.altKey || event.ctrlKey || event.metaKey) {
258
+ return;
259
+ }
260
+ event.preventDefault();
261
+ this.stepValue(event.key === 'ArrowUp' ? 1 : -1, event.shiftKey);
262
+ }
263
+ else if (event.key === 'Enter') {
264
+ // Commit before a potential form submit sees the value.
265
+ this._commit();
266
+ }
267
+ });
268
+ }
269
+ /**
270
+ * Steps the value by `step` (or `bigStep`) without wrapping. Uncommitted
271
+ * typed text is parsed and used as the base; an empty value seeds at `min`
272
+ * (increment) or `max` (decrement), falling back to `0` on open bounds.
273
+ */
274
+ stepValue(direction, big = false) {
275
+ if (this.disabled() || this.readonly()) {
276
+ return false;
277
+ }
278
+ const parsed = parseLocaleNumber(this._input.value, this._localeInfo());
279
+ const base = parsed.kind === 'value'
280
+ ? clampValue(parsed.value, this.min(), this.max())
281
+ : (this.value() ?? null);
282
+ const amount = big ? this.appliedBigStep() : this.appliedStep();
283
+ const next = stepNumberValue(base, direction, amount, this.min(), this.max());
284
+ this._appliedValue.set(next);
285
+ this.value.set(next);
286
+ this._setText((this.focused() ? this._editFormat() : this._displayFormat()).format(next));
287
+ return true;
288
+ }
289
+ /** Whether stepping in `direction` is currently possible (bound not reached). */
290
+ canStepValue(direction) {
291
+ if (this.disabled() || this.readonly()) {
292
+ return false;
293
+ }
294
+ const value = this.value() ?? null;
295
+ if (value === null) {
296
+ return true;
297
+ }
298
+ const bound = direction === 1 ? this.max() : this.min();
299
+ if (bound == null) {
300
+ return true;
301
+ }
302
+ return direction === 1 ? value < bound : value > bound;
303
+ }
304
+ /** Clears value and text (invoked by a surrounding field's clear button). */
305
+ clearValue() {
306
+ if (this.disabled() || this.readonly()) {
307
+ return false;
308
+ }
309
+ this._appliedValue.set(null);
310
+ this.value.set(null);
311
+ this._setText('');
312
+ return true;
313
+ }
314
+ /**
315
+ * Parses the current text and commits it to the value model: empty → `null`,
316
+ * out-of-range → clamped, unparseable → reverted to the last committed value.
317
+ */
318
+ _commit() {
319
+ const format = this.focused() ? this._editFormat() : this._displayFormat();
320
+ const parsed = parseLocaleNumber(this._input.value, this._localeInfo());
321
+ if (parsed.kind === 'invalid') {
322
+ const value = this.value() ?? null;
323
+ this._appliedValue.set(value);
324
+ this._setText(value === null ? '' : format.format(value));
325
+ return;
326
+ }
327
+ const next = parsed.kind === 'empty' ? null : clampValue(parsed.value, this.min(), this.max());
328
+ this._appliedValue.set(next);
329
+ this.value.set(next);
330
+ this._setText(next === null ? '' : format.format(next));
331
+ }
332
+ _setText(text) {
333
+ this._input.value = text;
334
+ this._text.set(text);
335
+ }
336
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnNumberInput, deps: [], target: i0.ɵɵFactoryTarget.Directive });
337
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.5", type: NgnNumberInput, isStandalone: true, selector: "input[ngnNumberInput]", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, bigStep: { classPropertyName: "bigStep", publicName: "bigStep", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, formatOptions: { classPropertyName: "formatOptions", publicName: "formatOptions", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "text", "role": "spinbutton", "inputmode": "decimal", "autocomplete": "off" }, properties: { "attr.aria-valuenow": "value() ?? null", "attr.aria-valuemin": "min() ?? null", "attr.aria-valuemax": "max() ?? null", "attr.aria-label": "label()", "attr.aria-invalid": "invalid() ? \"true\" : null", "disabled": "disabled()", "readOnly": "readonly()" } }, providers: [provideSelf(NgnNumberInput)], exportAs: ["ngnNumberInput"], usesInheritance: true, ngImport: i0 });
338
+ }
339
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnNumberInput, decorators: [{
340
+ type: Directive,
341
+ args: [{
342
+ selector: 'input[ngnNumberInput]',
343
+ providers: [provideSelf(NgnNumberInput)],
344
+ exportAs: 'ngnNumberInput',
345
+ host: {
346
+ type: 'text',
347
+ role: 'spinbutton',
348
+ inputmode: 'decimal',
349
+ autocomplete: 'off',
350
+ '[attr.aria-valuenow]': 'value() ?? null',
351
+ '[attr.aria-valuemin]': 'min() ?? null',
352
+ '[attr.aria-valuemax]': 'max() ?? null',
353
+ '[attr.aria-label]': 'label()',
354
+ '[attr.aria-invalid]': 'invalid() ? "true" : null',
355
+ '[disabled]': 'disabled()',
356
+ '[readOnly]': 'readonly()',
357
+ },
358
+ }]
359
+ }], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], bigStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "bigStep", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], formatOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatOptions", required: false }] }] } });
360
+
361
+ /**
362
+ * Generated bundle index. Do not edit.
363
+ */
364
+
365
+ export { NgnNumberInput, clampValue, decimalPlaces, localeNumberInfo, parseLocaleNumber, roundToPrecision, stepNumberValue };
366
+ //# sourceMappingURL=ngneers-controls-number-input.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngneers-controls-number-input.mjs","sources":["../../src/number-input/helper.ts","../../src/number-input/number-input.ts","../../src/number-input/ngneers-controls-number-input.ts"],"sourcesContent":["/** Locale-specific separators derived from `Intl.NumberFormat`. */\nexport type LocaleNumberInfo = {\n /** The decimal separator, e.g. `.` (en) or `,` (de). */\n decimal: string;\n /** The grouping (thousands) separator, e.g. `,` (en) or `.` (de). */\n group: string;\n /**\n * The locale's digit glyphs indexed by value (`digits[0]` … `digits[9]`).\n * Non-Latin numbering systems (e.g. `ar-EG` → `٠…٩`) are mapped back to\n * ASCII before parsing. `null` when the locale already uses ASCII digits.\n */\n digits: readonly string[] | null;\n};\n\n/** Derives the decimal/grouping separators and digit glyphs for a locale. */\nexport function localeNumberInfo(locale: string): LocaleNumberInfo {\n const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);\n const plain = new Intl.NumberFormat(locale, { useGrouping: false });\n const digits = Array.from({ length: 10 }, (_, i) => plain.format(i));\n const isAscii = digits.every((glyph, i) => glyph === String(i));\n return {\n decimal: parts.find(p => p.type === 'decimal')?.value ?? '.',\n group: parts.find(p => p.type === 'group')?.value ?? ',',\n digits: isAscii ? null : digits,\n };\n}\n\nexport type ParsedNumber =\n | { kind: 'empty' }\n | { kind: 'invalid' }\n | { kind: 'value'; value: number };\n\n/**\n * Parses user-typed text into a number using the locale's separators:\n * grouping separators (and non-breaking spaces used as such) are stripped,\n * the locale decimal separator is normalized to `.`. Exponent notation is\n * accepted; anything else non-numeric is `invalid`.\n */\nexport function parseLocaleNumber(text: string, info: LocaleNumberInfo): ParsedNumber {\n const trimmed = text.trim();\n if (!trimmed) {\n return { kind: 'empty' };\n }\n let normalized = trimmed;\n // Map non-Latin digit glyphs (e.g. ar-EG ٠…٩) back to ASCII before validating.\n if (info.digits) {\n for (let i = 0; i < 10; i++) {\n normalized = normalized.split(info.digits[i]!).join(String(i));\n }\n }\n normalized = normalized.split(info.group).join('');\n // Locales that group with spaces render non-breaking variants; users type\n // plain spaces — \\s covers both, so strip all whitespace flavors.\n normalized = normalized.replace(/\\s/g, '');\n if (info.decimal !== '.') {\n normalized = normalized.split(info.decimal).join('.');\n }\n if (!/^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)(e[+-]?\\d+)?$/i.test(normalized)) {\n return { kind: 'invalid' };\n }\n const value = Number(normalized);\n if (!Number.isFinite(value)) {\n return { kind: 'invalid' };\n }\n return { kind: 'value', value };\n}\n\n/** Number of decimal places of `value` (bounded to 20, exponent-safe). */\nexport function decimalPlaces(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n let factor = 1;\n let places = 0;\n while (Math.round(value * factor) / factor !== value && places < 20) {\n factor *= 10;\n places++;\n }\n return places;\n}\n\n/** Rounds `value` to `decimals` decimal places (float-drift cleanup). */\nexport function roundToPrecision(value: number, decimals: number): number {\n const factor = Math.pow(10, decimals);\n return Math.round(value * factor) / factor;\n}\n\n/** Clamps `value` into `[min, max]`; open bounds when `null`/`undefined`. */\nexport function clampValue(value: number, min?: number | null, max?: number | null): number {\n if (min != null && value < min) {\n return min;\n }\n if (max != null && value > max) {\n return max;\n }\n return value;\n}\n\n/**\n * Steps `current` by `amount` in `direction`, clamped into `[min, max]` (no\n * wrapping). The result is rounded to the combined precision of the operands\n * so float drift (`0.1 + 0.2`) never surfaces. Stepping from `null` seeds at\n * `min` (increment) or `max` (decrement), falling back to `0` on open bounds.\n */\nexport function stepNumberValue(\n current: number | null,\n direction: 1 | -1,\n amount: number,\n min?: number | null,\n max?: number | null\n): number {\n if (current === null) {\n const seed = direction === 1 ? (min ?? 0) : (max ?? 0);\n return clampValue(seed, min, max);\n }\n const decimals = Math.max(decimalPlaces(current), decimalPlaces(amount));\n const next = roundToPrecision(current + direction * amount, decimals);\n return clampValue(next, min, max);\n}\n","import {\n computed,\n Directive,\n effect,\n inject,\n input,\n LOCALE_ID,\n numberAttribute,\n signal,\n untracked,\n} from '@angular/core';\nimport { domEventHandler } from '@ngneers/controls/api/ng';\nimport { provideSelf, ValueControlBase } from '@ngneers/controls/base';\nimport { inputControlTemplate } from '@ngneers/controls-themes/templates/input';\n\nimport { clampValue, localeNumberInfo, parseLocaleNumber, stepNumberValue } from './helper';\n\n/** `numberAttribute` that keeps `undefined`/`null`/`''` as \"no bound\" instead of `NaN`. */\nfunction optionalNumberAttribute(value: unknown): number | undefined {\n return value == null || value === '' ? undefined : numberAttribute(value);\n}\n\n/** A finite, strictly-positive step, or `undefined` for an invalid input. */\nfunction positiveStep(value: number | undefined): number | undefined {\n return value != null && Number.isFinite(value) && value > 0 ? value : undefined;\n}\n\n/**\n * Turns a native `<input>` into a locale-aware number input with a\n * `number | null` value model:\n *\n * - Free typing while focused; the value is committed on blur/Enter\n * (unparseable text reverts, out-of-range values are clamped — no wrapping).\n * - Display is formatted via `Intl.NumberFormat` while blurred; while focused\n * the text is the raw, ungrouped edit form.\n * - `ArrowUp`/`ArrowDown` step by `step`, with `Shift` by `bigStep`.\n * Results are rounded to the operands' precision so float drift never shows.\n * - Exposes the {@link NgnBase.stepValue}/{@link NgnBase.canStepValue} hooks,\n * so `ngn-spin-buttons` (or a surrounding field) can step the value.\n *\n * @category control\n */\n@Directive({\n selector: 'input[ngnNumberInput]',\n providers: [provideSelf(NgnNumberInput)],\n exportAs: 'ngnNumberInput',\n host: {\n type: 'text',\n role: 'spinbutton',\n inputmode: 'decimal',\n autocomplete: 'off',\n '[attr.aria-valuenow]': 'value() ?? null',\n '[attr.aria-valuemin]': 'min() ?? null',\n '[attr.aria-valuemax]': 'max() ?? null',\n '[attr.aria-label]': 'label()',\n '[attr.aria-invalid]': 'invalid() ? \"true\" : null',\n '[disabled]': 'disabled()',\n '[readOnly]': 'readonly()',\n },\n})\nexport class NgnNumberInput extends ValueControlBase<'numberInput', number | null> {\n public override readonly isFieldControl = true;\n protected readonly theme = this.injectThemeTemplate(inputControlTemplate, {\n root: true,\n invalid: () => this.invalid(),\n empty: () => this.empty(),\n });\n\n /**\n * The minimum allowed value (inclusive). Typed values below are clamped on\n * commit; stepping stops here.\n */\n public readonly min = input(undefined, { transform: optionalNumberAttribute });\n /**\n * The maximum allowed value (inclusive). Typed values above are clamped on\n * commit; stepping stops here.\n */\n public readonly max = input(undefined, { transform: optionalNumberAttribute });\n /**\n * The step amount applied by the spin buttons and `ArrowUp`/`ArrowDown`.\n * Non-finite or non-positive values fall back to `1`.\n * @default 1\n */\n public readonly step = input(1, { transform: numberAttribute });\n /**\n * The step amount applied with `Shift+ArrowUp`/`Shift+ArrowDown`.\n * Non-finite or non-positive values fall back to `appliedStep * 10`.\n * @default step * 10\n */\n public readonly bigStep = input(undefined, { transform: optionalNumberAttribute });\n /**\n * The locale used for formatting and parsing.\n * @default LOCALE_ID\n */\n public readonly locale = input<string | undefined>(undefined);\n /**\n * Additional `Intl.NumberFormat` options for the blurred display format only\n * (e.g. `{ minimumFractionDigits: 2 }`). While focused the input shows the\n * plain, ungrouped edit form, so display options never round or reshape the\n * value the user is typing.\n */\n public readonly formatOptions = input<Intl.NumberFormatOptions | undefined>(undefined);\n\n private readonly _input = this.element.nativeElement as HTMLInputElement;\n private readonly _localeId = inject(LOCALE_ID);\n\n protected readonly appliedLocale = computed(() => this.locale() ?? this._localeId);\n /** Sanitized step — a finite positive number, defaulting to `1`. */\n protected readonly appliedStep = computed(() => positiveStep(this.step()) ?? 1);\n protected readonly appliedBigStep = computed(\n () => positiveStep(this.bigStep()) ?? this.appliedStep() * 10\n );\n\n private readonly _localeInfo = computed(() => localeNumberInfo(this.appliedLocale()));\n private readonly _displayFormat = computed(\n () =>\n new Intl.NumberFormat(this.appliedLocale(), {\n maximumFractionDigits: 20,\n ...this.formatOptions(),\n })\n );\n // The edit formatter is deliberately plain — it does NOT apply formatOptions.\n // Display options (fraction digits, currency, percent) would round the\n // in-progress value or make the editable text unparsable on commit.\n private readonly _editFormat = computed(\n () =>\n new Intl.NumberFormat(this.appliedLocale(), {\n maximumFractionDigits: 20,\n useGrouping: false,\n })\n );\n\n /** Whether the input currently holds focus (raw edit format while focused). */\n protected readonly focused = signal(false);\n\n /** Mirror of the input element's current text (drives the `empty` class). */\n private readonly _text = signal(this._input.value ?? '');\n\n /**\n * The last value applied by an internal mutation (step/commit/clear) that\n * already set the edit text itself. Lets the value→text effect ignore those\n * changes while focused, so a deferred flush can't clobber text the user\n * typed right after (`undefined` = nothing applied internally yet).\n */\n private readonly _appliedValue = signal<number | null | undefined>(undefined);\n\n /** Whether the input holds no text at all. */\n public readonly empty = computed(() => this._text() === '');\n\n constructor() {\n super();\n\n // External value → text. While focused only overwrite when the parsed text\n // semantically differs (programmatic set wins, typing is never clobbered).\n effect(() => {\n const value = this.value() ?? null;\n const text = untracked(() => this._text());\n if (!this.focused()) {\n const next = value === null ? '' : this._displayFormat().format(value);\n if (next !== text) {\n this._setText(next);\n }\n // Record the reconciled value so a later focus (with no intervening\n // external change) short-circuits below instead of re-parsing the\n // edit text — otherwise focusing then typing can race this effect,\n // which would clobber the freshly typed text back to `value`.\n this._appliedValue.set(value);\n return;\n }\n // Only reflect EXTERNAL value changes into the edit text. Internal\n // mutations (step/commit/clear) set the text themselves and record\n // `_appliedValue`; skipping them here prevents a deferred effect flush\n // from clobbering text the user typed right after such a mutation.\n if (value === untracked(() => this._appliedValue())) {\n return;\n }\n const parsed = parseLocaleNumber(\n text,\n untracked(() => this._localeInfo())\n );\n const current = parsed.kind === 'value' ? parsed.value : null;\n if (current !== value) {\n this._setText(value === null ? '' : this._editFormat().format(value));\n }\n this._appliedValue.set(value);\n });\n\n domEventHandler(this.element, 'input', () => {\n this._text.set(this._input.value);\n });\n\n domEventHandler(this.element, 'focus', () => {\n this.focused.set(true);\n const value = this.value() ?? null;\n if (value !== null) {\n this._setText(this._editFormat().format(value));\n }\n });\n\n domEventHandler(this.element, 'blur', () => {\n this._commit();\n this.focused.set(false);\n this.touched.set(true);\n });\n\n domEventHandler(this.element, 'keydown', event => {\n if (this.disabled() || this.readonly()) {\n return;\n }\n if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {\n if (event.altKey || event.ctrlKey || event.metaKey) {\n return;\n }\n event.preventDefault();\n this.stepValue(event.key === 'ArrowUp' ? 1 : -1, event.shiftKey);\n } else if (event.key === 'Enter') {\n // Commit before a potential form submit sees the value.\n this._commit();\n }\n });\n }\n\n /**\n * Steps the value by `step` (or `bigStep`) without wrapping. Uncommitted\n * typed text is parsed and used as the base; an empty value seeds at `min`\n * (increment) or `max` (decrement), falling back to `0` on open bounds.\n */\n public override stepValue(direction: 1 | -1, big = false): boolean {\n if (this.disabled() || this.readonly()) {\n return false;\n }\n const parsed = parseLocaleNumber(this._input.value, this._localeInfo());\n const base =\n parsed.kind === 'value'\n ? clampValue(parsed.value, this.min(), this.max())\n : (this.value() ?? null);\n const amount = big ? this.appliedBigStep() : this.appliedStep();\n const next = stepNumberValue(base, direction, amount, this.min(), this.max());\n this._appliedValue.set(next);\n this.value.set(next);\n this._setText((this.focused() ? this._editFormat() : this._displayFormat()).format(next));\n return true;\n }\n\n /** Whether stepping in `direction` is currently possible (bound not reached). */\n public override canStepValue(direction: 1 | -1): boolean {\n if (this.disabled() || this.readonly()) {\n return false;\n }\n const value = this.value() ?? null;\n if (value === null) {\n return true;\n }\n const bound = direction === 1 ? this.max() : this.min();\n if (bound == null) {\n return true;\n }\n return direction === 1 ? value < bound : value > bound;\n }\n\n /** Clears value and text (invoked by a surrounding field's clear button). */\n public override clearValue(): boolean {\n if (this.disabled() || this.readonly()) {\n return false;\n }\n this._appliedValue.set(null);\n this.value.set(null);\n this._setText('');\n return true;\n }\n\n /**\n * Parses the current text and commits it to the value model: empty → `null`,\n * out-of-range → clamped, unparseable → reverted to the last committed value.\n */\n private _commit(): void {\n const format = this.focused() ? this._editFormat() : this._displayFormat();\n const parsed = parseLocaleNumber(this._input.value, this._localeInfo());\n if (parsed.kind === 'invalid') {\n const value = this.value() ?? null;\n this._appliedValue.set(value);\n this._setText(value === null ? '' : format.format(value));\n return;\n }\n const next = parsed.kind === 'empty' ? null : clampValue(parsed.value, this.min(), this.max());\n this._appliedValue.set(next);\n this.value.set(next);\n this._setText(next === null ? '' : format.format(next));\n }\n\n private _setText(text: string): void {\n this._input.value = text;\n this._text.set(text);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ import { NgStyle, NgClass } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, input, model, booleanAttribute, computed, output, effect, Component } from '@angular/core';
4
+ import { NgnTemplate } from '@ngneers/controls/api/ng';
5
+ import { NgnBase, NgnPt, provideSelf } from '@ngneers/controls/base';
6
+ import { NgnButton } from '@ngneers/controls/button';
7
+ import { I18n } from '@ngneers/controls/i18n';
8
+ import { NgnIcon } from '@ngneers/controls/icon';
9
+ import { NgnInputField } from '@ngneers/controls/input-field';
10
+ import { NgnItemView } from '@ngneers/controls/item-view';
11
+ import { NgnSelect } from '@ngneers/controls/select';
12
+ import { throwExp } from '@ngneers/controls/utils';
13
+ import { paginatorControlTemplate } from '@ngneers/controls-themes/templates/paginator';
14
+
15
+ /**
16
+ * @category control
17
+ */
18
+ class NgnPaginator extends NgnBase {
19
+ theme = this.injectThemeTemplate(paginatorControlTemplate, 'root');
20
+ i18n = inject(I18n).translations;
21
+ /**
22
+ * Total number of items to paginate.
23
+ */
24
+ totalItems = input.required(/* @ts-ignore */
25
+ ...(ngDevMode ? [{ debugName: "totalItems" }] : /* istanbul ignore next */ []));
26
+ /**
27
+ * Number of items per page. If not set, the first value from {@link possiblePageSizes} is used.
28
+ */
29
+ pageSize = model(/* @ts-ignore */
30
+ ...(ngDevMode ? [undefined, { debugName: "pageSize" }] : /* istanbul ignore next */ []));
31
+ /**
32
+ * Possible page sizes to choose from.
33
+ * @default [5, 10, 25, 50]
34
+ */
35
+ possiblePageSizes = input([5, 10, 25, 50], { ...(ngDevMode ? { debugName: "possiblePageSizes" } : /* istanbul ignore next */ {}), transform: (sizes) => sizes.sort((a, b) => a - b) });
36
+ /**
37
+ * If set to `true`, the page size selector will be hidden and the page size cannot be changed by the user.
38
+ * @default false
39
+ */
40
+ fixedPageSize = input(false, { ...(ngDevMode ? { debugName: "fixedPageSize" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
41
+ appliedPageSize = computed(() => this.pageSize() ||
42
+ this.possiblePageSizes()[0] ||
43
+ throwExp('NgnPaginator', 'At least one page size must be provided'), /* @ts-ignore */
44
+ ...(ngDevMode ? [{ debugName: "appliedPageSize" }] : /* istanbul ignore next */ []));
45
+ pageCount = computed(() => Math.ceil(this.totalItems() / this.appliedPageSize()), /* @ts-ignore */
46
+ ...(ngDevMode ? [{ debugName: "pageCount" }] : /* istanbul ignore next */ []));
47
+ pages = computed(() => Array.from({ length: this.pageCount() }, (_, i) => ({ page: i })), /* @ts-ignore */
48
+ ...(ngDevMode ? [{ debugName: "pages" }] : /* istanbul ignore next */ []));
49
+ pageSizeOptions = computed(() => this.possiblePageSizes().map(size => ({
50
+ label: size.toString(),
51
+ value: size,
52
+ })), /* @ts-ignore */
53
+ ...(ngDevMode ? [{ debugName: "pageSizeOptions" }] : /* istanbul ignore next */ []));
54
+ /**
55
+ * Emits the current pagination state whenever the page or page size changes.
56
+ */
57
+ value = output();
58
+ /**
59
+ * Current page index (zero-based).
60
+ * @default 0
61
+ */
62
+ page = model(0, /* @ts-ignore */
63
+ ...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
64
+ constructor() {
65
+ super();
66
+ effect(() => {
67
+ const page = this.page();
68
+ const pageSize = this.appliedPageSize();
69
+ this.value.emit({
70
+ page: {
71
+ current: page,
72
+ size: pageSize,
73
+ },
74
+ slice: {
75
+ skip: page * pageSize,
76
+ take: pageSize,
77
+ },
78
+ });
79
+ });
80
+ }
81
+ previousPage(event) {
82
+ const amount = event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;
83
+ const newPage = Math.max(this.page() - amount, 0);
84
+ this.page.set(newPage);
85
+ }
86
+ nextPage(event) {
87
+ const amount = event.shiftKey ? 10 : event.ctrlKey ? 100 : 1;
88
+ const newPage = Math.min(this.page() + amount, this.pageCount() - 1);
89
+ this.page.set(newPage);
90
+ }
91
+ getButtonFontStyles(page) {
92
+ function getFontSizeMultiplier() {
93
+ const charCount = (page + 1).toString().length;
94
+ if (charCount <= 2) {
95
+ return 1;
96
+ }
97
+ return Math.pow(0.85, charCount - 2);
98
+ }
99
+ return {
100
+ fontSize: `${getFontSizeMultiplier()}em`,
101
+ width: 'calc(1rem + 2 * var(--padding))',
102
+ height: 'calc(1rem + 2 * var(--padding))',
103
+ lineHeight: '0',
104
+ };
105
+ }
106
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnPaginator, deps: [], target: i0.ɵɵFactoryTarget.Component });
107
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnPaginator, isStandalone: true, selector: "ngn-paginator", inputs: { totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: true, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, possiblePageSizes: { classPropertyName: "possiblePageSizes", publicName: "possiblePageSizes", isSignal: true, isRequired: false, transformFunction: null }, fixedPageSize: { classPropertyName: "fixedPageSize", publicName: "fixedPageSize", isSignal: true, isRequired: false, transformFunction: null }, page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pageSize: "pageSizeChange", value: "value", page: "pageChange" }, providers: [provideSelf(NgnPaginator)], usesInheritance: true, ngImport: i0, template: "<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'previous'\"\n [attr.aria-label]=\"i18n['paginator_previousPage']()\"\n (click)=\"previousPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-previous'\" />\n</button>\n\n<ngn-item-view\n #itemView\n [ptInt]=\"this\"\n [ptDep]=\"'item-view'\"\n [items]=\"pages()\"\n [idField]=\"'page'\"\n [overflowStrategy]=\"'aroundIndex'\"\n [overflowStrategyFreezeCount]=\"1\"\n [overflowStrategyIndex]=\"page()\"\n [sameWidthItems]=\"true\"\n>\n <ng-template #item let-item [ngnTemplate]=\"itemView.templateTypes.item\">\n @let buttonStyles = getButtonFontStyles(item.page);\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'page-number'\"\n [ngStyle]=\"buttonStyles\"\n [attr.aria-current]=\"page() === item.page ? 'page' : null\"\n [ngClass]=\"\n theme.classes({\n 'active-page': page() === item.page,\n })\n \"\n (click)=\"page.set(item.page)\"\n >\n {{ item.page + 1 }}\n </button>\n </ng-template>\n <ng-template #overflow let-item [ngnTemplate]=\"itemView.templateTypes.overflow\">\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'overflow'\"\n style=\"opacity: 0.5\"\n disabled\n >\n ...\n </button>\n </ng-template>\n</ngn-item-view>\n\n<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'next'\"\n [attr.aria-label]=\"i18n['paginator_nextPage']()\"\n (click)=\"nextPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-next'\" />\n</button>\n\n@if (!fixedPageSize()) {\n <ngn-input-field [ptInt]=\"this\" [ptClass]=\"'page-size-options'\">\n <ngn-select\n [options]=\"pageSizeOptions()\"\n [value]=\"appliedPageSize()\"\n (valueChange)=\"pageSize.set($event)\"\n />\n </ngn-input-field>\n}\n", dependencies: [{ kind: "directive", type: NgnButton, selector: "button[ngnButton], a[ngnButton]", inputs: ["ngnButtonInline"], exportAs: ["ngnButton"] }, { kind: "component", type: NgnIcon, selector: "ngn-icon", inputs: ["defaultIcon", "icon"] }, { kind: "component", type: NgnItemView, selector: "ngn-item-view", inputs: ["idField", "items", "separator", "iconItemSeparator", "overflowStrategy", "overflowStrategyFreezeCount", "overflowStrategyIndex", "sameWidthItems"] }, { kind: "component", type: NgnSelect, selector: "ngn-select", inputs: ["popoverOptions", "options", "filter", "filterText", "iconFilter", "iconDropdown", "virtual", "itemHeight", "editable", "editableAutoFilter", "multiple", "scrollToSelectedItemOnOpen", "tabindex", "checkbox"] }, { kind: "directive", type: NgnTemplate, selector: "ng-template[ngnTemplate]", inputs: ["ngnTemplate"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgnPt, selector: "[ptInt]", inputs: ["ptInt", "ptClass", "ptDep"] }, { kind: "component", type: NgnInputField, selector: "ngn-input-field", inputs: ["label", "labelledBy", "labelKind", "inputId", "showClearButton", "iconClearButton", "tabindex", "invalid", "readonly", "disabled"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
108
+ }
109
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnPaginator, decorators: [{
110
+ type: Component,
111
+ args: [{ selector: 'ngn-paginator', imports: [
112
+ NgnButton,
113
+ NgnIcon,
114
+ NgnItemView,
115
+ NgnSelect,
116
+ NgnTemplate,
117
+ NgStyle,
118
+ NgnPt,
119
+ NgnInputField,
120
+ NgClass,
121
+ ], providers: [provideSelf(NgnPaginator)], template: "<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'previous'\"\n [attr.aria-label]=\"i18n['paginator_previousPage']()\"\n (click)=\"previousPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-previous'\" />\n</button>\n\n<ngn-item-view\n #itemView\n [ptInt]=\"this\"\n [ptDep]=\"'item-view'\"\n [items]=\"pages()\"\n [idField]=\"'page'\"\n [overflowStrategy]=\"'aroundIndex'\"\n [overflowStrategyFreezeCount]=\"1\"\n [overflowStrategyIndex]=\"page()\"\n [sameWidthItems]=\"true\"\n>\n <ng-template #item let-item [ngnTemplate]=\"itemView.templateTypes.item\">\n @let buttonStyles = getButtonFontStyles(item.page);\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'page-number'\"\n [ngStyle]=\"buttonStyles\"\n [attr.aria-current]=\"page() === item.page ? 'page' : null\"\n [ngClass]=\"\n theme.classes({\n 'active-page': page() === item.page,\n })\n \"\n (click)=\"page.set(item.page)\"\n >\n {{ item.page + 1 }}\n </button>\n </ng-template>\n <ng-template #overflow let-item [ngnTemplate]=\"itemView.templateTypes.overflow\">\n <button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'overflow'\"\n style=\"opacity: 0.5\"\n disabled\n >\n ...\n </button>\n </ng-template>\n</ngn-item-view>\n\n<button\n ngnButton\n [kind]=\"'icon'\"\n [ptInt]=\"this\"\n [ptDep]=\"'next'\"\n [attr.aria-label]=\"i18n['paginator_nextPage']()\"\n (click)=\"nextPage($event)\"\n>\n <ngn-icon [defaultIcon]=\"'paginator-next'\" />\n</button>\n\n@if (!fixedPageSize()) {\n <ngn-input-field [ptInt]=\"this\" [ptClass]=\"'page-size-options'\">\n <ngn-select\n [options]=\"pageSizeOptions()\"\n [value]=\"appliedPageSize()\"\n (valueChange)=\"pageSize.set($event)\"\n />\n </ngn-input-field>\n}\n" }]
122
+ }], ctorParameters: () => [], propDecorators: { totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: true }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }, { type: i0.Output, args: ["pageSizeChange"] }], possiblePageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "possiblePageSizes", required: false }] }], fixedPageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fixedPageSize", required: false }] }], value: [{ type: i0.Output, args: ["value"] }], page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: false }] }, { type: i0.Output, args: ["pageChange"] }] } });
123
+
124
+ /**
125
+ * Generated bundle index. Do not edit.
126
+ */
127
+
128
+ export { NgnPaginator };
129
+ //# sourceMappingURL=ngneers-controls-paginator.mjs.map