@egose/shadcn-theme-ng 0.5.3 → 0.6.1

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 (61) hide show
  1. package/README.md +131 -12
  2. package/autocomplete/fesm2022/autocomplete.mjs +14 -4
  3. package/autocomplete/types/autocomplete.d.ts +4 -1
  4. package/checkbox/README.md +6 -2
  5. package/checkbox/fesm2022/checkbox.mjs +11 -7
  6. package/checkbox/types/checkbox.d.ts +6 -4
  7. package/combobox/fesm2022/combobox.mjs +13 -7
  8. package/combobox/types/combobox.d.ts +8 -2
  9. package/data-table/README.md +174 -0
  10. package/data-table/fesm2022/data-table.mjs +713 -0
  11. package/data-table/package.json +25 -0
  12. package/data-table/types/data-table.d.ts +495 -0
  13. package/date-picker/README.md +41 -9
  14. package/date-picker/fesm2022/date-picker.mjs +265 -59
  15. package/date-picker/types/date-picker.d.ts +58 -27
  16. package/dropdown-menu/README.md +2 -0
  17. package/form-autocomplete/README.md +4 -2
  18. package/form-autocomplete/fesm2022/form-autocomplete.mjs +19 -6
  19. package/form-autocomplete/types/form-autocomplete.d.ts +9 -3
  20. package/form-combobox/README.md +4 -2
  21. package/form-combobox/fesm2022/form-combobox.mjs +23 -6
  22. package/form-combobox/types/form-combobox.d.ts +10 -3
  23. package/form-input-otp/README.md +7 -5
  24. package/form-input-otp/fesm2022/form-input-otp.mjs +20 -8
  25. package/form-input-otp/types/form-input-otp.d.ts +9 -3
  26. package/form-searchable-multiselect/README.md +15 -1
  27. package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +15 -3
  28. package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +9 -1
  29. package/form-slider/README.md +3 -1
  30. package/form-slider/fesm2022/form-slider.mjs +19 -6
  31. package/form-slider/types/form-slider.d.ts +9 -3
  32. package/form-text-input/fesm2022/form-text-input.mjs +1 -1
  33. package/form-textarea/fesm2022/form-textarea.mjs +1 -1
  34. package/form-toggle/README.md +22 -18
  35. package/form-toggle/fesm2022/form-toggle.mjs +22 -9
  36. package/form-toggle/types/form-toggle.d.ts +6 -2
  37. package/input/README.md +7 -12
  38. package/input/fesm2022/input.mjs +7 -4
  39. package/input/types/input.d.ts +3 -1
  40. package/input-otp/README.md +33 -9
  41. package/input-otp/fesm2022/input-otp.mjs +127 -11
  42. package/input-otp/types/input-otp.d.ts +24 -4
  43. package/layout-simple/fesm2022/layout-simple.mjs +1 -1
  44. package/package.json +6 -1
  45. package/pagination/README.md +21 -12
  46. package/pagination/fesm2022/pagination.mjs +73 -65
  47. package/pagination/types/pagination.d.ts +21 -8
  48. package/phone-input/README.md +16 -0
  49. package/phone-input/fesm2022/phone-input.mjs +42 -4
  50. package/phone-input/types/phone-input.d.ts +5 -2
  51. package/searchable-multiselect/README.md +62 -40
  52. package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +59 -32
  53. package/searchable-multiselect/types/searchable-multiselect.d.ts +23 -6
  54. package/slider/fesm2022/slider.mjs +9 -5
  55. package/slider/types/slider.d.ts +6 -2
  56. package/stepper/README.md +39 -19
  57. package/stepper/fesm2022/stepper.mjs +64 -12
  58. package/stepper/types/stepper.d.ts +12 -1
  59. package/table/README.md +2 -0
  60. package/table/fesm2022/table.mjs +12 -8
  61. package/table/types/table.d.ts +7 -2
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, Directive, ChangeDetectionStrategy, Component, booleanAttribute, computed, model, numberAttribute, untracked, NgModule } from '@angular/core';
2
+ import { input, Directive, ChangeDetectionStrategy, Component, booleanAttribute, computed, model, numberAttribute, effect, untracked, NgModule } from '@angular/core';
3
3
  import * as i1$1 from '@egose/shadcn-theme-ng/select';
4
4
  import { HlmSelectImports } from '@egose/shadcn-theme-ng/select';
5
5
  import { classes, hlm } from '@egose/shadcn-theme-ng/utils';
@@ -207,14 +207,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
207
207
  }]
208
208
  }], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], link: [{ type: i0.Input, args: [{ isSignal: true, alias: "link", required: false }] }], queryParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryParams", required: false }] }], queryParamsHandling: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryParamsHandling", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }] } });
209
209
 
210
+ /** Shared arithmetic policy; invalid totals/sizes represent a single page. */
211
+ function pageCount(totalItems, itemsPerPage) {
212
+ if (!Number.isFinite(totalItems) || totalItems <= 0 || !Number.isFinite(itemsPerPage) || itemsPerPage <= 0) {
213
+ return 1;
214
+ }
215
+ return Math.min(Number.MAX_SAFE_INTEGER, Math.max(1, Math.ceil(totalItems / itemsPerPage)));
216
+ }
217
+
210
218
  class HlmNumberedPagination {
211
219
  /**
212
- * The current (active) page.
220
+ * The current (active) page, floored and clamped to the available range.
221
+ * Corrections emit currentPageChange once; empty/invalid paging uses page 1.
213
222
  */
214
223
  currentPage = model.required(/* @ts-ignore */
215
224
  ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
216
225
  /**
217
- * The number of items per paginated page.
226
+ * The number of items per paginated page. Non-finite/non-positive sizes
227
+ * disable paging (one page) without rewriting this model.
218
228
  */
219
229
  itemsPerPage = model.required(/* @ts-ignore */
220
230
  ...(ngDevMode ? [{ debugName: "itemsPerPage" }] : /* istanbul ignore next */ []));
@@ -225,7 +235,8 @@ class HlmNumberedPagination {
225
235
  */
226
236
  totalItems = input.required({ ...(ngDevMode ? { debugName: "totalItems" } : /* istanbul ignore next */ {}), transform: numberAttribute });
227
237
  /**
228
- * The number of page links to show.
238
+ * Maximum window entries, including ellipses: floored and bounded to 1–100.
239
+ * Non-finite/non-positive values use the default of 7.
229
240
  */
230
241
  maxSize = input(7, { ...(ngDevMode ? { debugName: "maxSize" } : /* istanbul ignore next */ {}), transform: numberAttribute });
231
242
  /**
@@ -245,33 +256,29 @@ class HlmNumberedPagination {
245
256
  : [...pageSizes, this.itemsPerPage()].sort((a, b) => a - b); // otherwise, add current page size and sort the array
246
257
  }, /* @ts-ignore */
247
258
  ...(ngDevMode ? [{ debugName: "_pageSizesWithCurrent" }] : /* istanbul ignore next */ []));
248
- _isFirstPageActive = computed(() => this.currentPage() === 1, /* @ts-ignore */
259
+ _currentPage = computed(() => outOfBoundCorrection(this.totalItems(), this.itemsPerPage(), this.currentPage()), /* @ts-ignore */
260
+ ...(ngDevMode ? [{ debugName: "_currentPage" }] : /* istanbul ignore next */ []));
261
+ _isFirstPageActive = computed(() => this._currentPage() === 1, /* @ts-ignore */
249
262
  ...(ngDevMode ? [{ debugName: "_isFirstPageActive" }] : /* istanbul ignore next */ []));
250
- _isLastPageActive = computed(() => this.currentPage() === this._lastPageNumber(), /* @ts-ignore */
263
+ _isLastPageActive = computed(() => this._currentPage() === this._lastPageNumber(), /* @ts-ignore */
251
264
  ...(ngDevMode ? [{ debugName: "_isLastPageActive" }] : /* istanbul ignore next */ []));
252
- _lastPageNumber = computed(() => {
253
- if (this.totalItems() < 1) {
254
- // when there are 0 or fewer (an error case) items, there are no "pages" as such,
255
- // but it makes sense to consider a single, empty page as the last page.
256
- return 1;
257
- }
258
- return Math.ceil(this.totalItems() / this.itemsPerPage());
259
- }, /* @ts-ignore */
265
+ _lastPageNumber = computed(() => pageCount(this.totalItems(), this.itemsPerPage()), /* @ts-ignore */
260
266
  ...(ngDevMode ? [{ debugName: "_lastPageNumber" }] : /* istanbul ignore next */ []));
261
- _pages = computed(() => {
262
- const correctedCurrentPage = outOfBoundCorrection(this.totalItems(), this.itemsPerPage(), this.currentPage());
263
- if (correctedCurrentPage !== this.currentPage()) {
264
- // update the current page
265
- untracked(() => this.currentPage.set(correctedCurrentPage));
266
- }
267
- return createPageArray(correctedCurrentPage, this.itemsPerPage(), this.totalItems(), this.maxSize());
268
- }, /* @ts-ignore */
267
+ _pages = computed(() => createPageArray(this._currentPage(), this.itemsPerPage(), this.totalItems(), this.maxSize()), /* @ts-ignore */
269
268
  ...(ngDevMode ? [{ debugName: "_pages" }] : /* istanbul ignore next */ []));
269
+ constructor() {
270
+ effect(() => {
271
+ const page = this._currentPage();
272
+ if (page !== this.currentPage()) {
273
+ untracked(() => this.currentPage.set(page));
274
+ }
275
+ });
276
+ }
270
277
  goToPrevious() {
271
- this.currentPage.set(this.currentPage() - 1);
278
+ this.currentPage.set(Math.max(1, this._currentPage() - 1));
272
279
  }
273
280
  goToNext() {
274
- this.currentPage.set(this.currentPage() + 1);
281
+ this.currentPage.set(Math.min(this._lastPageNumber(), this._currentPage() + 1));
275
282
  }
276
283
  goToFirst() {
277
284
  this.currentPage.set(1);
@@ -280,7 +287,7 @@ class HlmNumberedPagination {
280
287
  this.currentPage.set(this._lastPageNumber());
281
288
  }
282
289
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmNumberedPagination, deps: [], target: i0.ɵɵFactoryTarget.Component });
283
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmNumberedPagination, isStandalone: true, selector: "hlm-numbered-pagination", inputs: { currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: true, transformFunction: null }, itemsPerPage: { classPropertyName: "itemsPerPage", publicName: "itemsPerPage", isSignal: true, isRequired: true, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: true, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, showEdges: { classPropertyName: "showEdges", publicName: "showEdges", isSignal: true, isRequired: false, transformFunction: null }, pageSizes: { classPropertyName: "pageSizes", publicName: "pageSizes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", itemsPerPage: "itemsPerPageChange" }, ngImport: i0, template: "\n <div class=\"flex items-center justify-between gap-2 px-4 py-2\">\n <div class=\"flex items-center gap-1 text-sm text-nowrap text-gray-600\">\n <b>{{ totalItems() }}</b>\n total items |\n <b>{{ _lastPageNumber() }}</b>\n pages\n </div>\n\n <nav hlmPagination>\n <ul hlmPaginationContent>\n @if (showEdges() && !_isFirstPageActive()) {\n <li hlmPaginationItem (click)=\"goToPrevious()\">\n <hlm-pagination-previous />\n </li>\n }\n\n @for (page of _pages(); track page) {\n <li hlmPaginationItem>\n @if (page === '...') {\n <hlm-pagination-ellipsis />\n } @else {\n <a hlmPaginationLink [isActive]=\"currentPage() === page\" (click)=\"currentPage.set(page)\">\n {{ page }}\n </a>\n }\n </li>\n }\n\n @if (showEdges() && !_isLastPageActive()) {\n <li hlmPaginationItem (click)=\"goToNext()\">\n <hlm-pagination-next />\n </li>\n }\n </ul>\n </nav>\n\n <!-- Show Page Size selector -->\n <hlm-select [(value)]=\"itemsPerPage\" class=\"ml-auto\">\n <hlm-select-trigger class=\"w-fit\">\n <hlm-select-value />\n </hlm-select-trigger>\n <hlm-select-content *hlmSelectPortal>\n <hlm-select-group>\n @for (pageSize of _pageSizesWithCurrent(); track pageSize) {\n <hlm-select-item [value]=\"pageSize\">{{ pageSize }}</hlm-select-item>\n }\n </hlm-select-group>\n </hlm-select-content>\n </hlm-select>\n </div>\n ", isInline: true, dependencies: [{ kind: "directive", type: HlmPagination, selector: "[hlmPagination],hlm-pagination", inputs: ["aria-label"] }, { kind: "directive", type: HlmPaginationContent, selector: "ul[hlmPaginationContent]" }, { kind: "directive", type: HlmPaginationItem, selector: "li[hlmPaginationItem]" }, { kind: "component", type: HlmPaginationPrevious, selector: "hlm-pagination-previous", inputs: ["class", "link", "queryParams", "queryParamsHandling", "aria-label", "text", "iconOnly"] }, { kind: "component", type: HlmPaginationNext, selector: "hlm-pagination-next", inputs: ["class", "link", "queryParams", "queryParamsHandling", "aria-label", "text", "iconOnly"] }, { kind: "directive", type: HlmPaginationLink, selector: "[hlmPaginationLink]", inputs: ["isActive", "size", "link"] }, { kind: "component", type: HlmPaginationEllipsis, selector: "hlm-pagination-ellipsis", inputs: ["srOnlyText"] }, { kind: "directive", type: i1$1.HlmSelect, selector: "[hlmSelect],hlm-select" }, { kind: "component", type: i1$1.HlmSelectContent, selector: "hlm-select-content", inputs: ["showScroll"] }, { kind: "directive", type: i1$1.HlmSelectGroup, selector: "[hlmSelectGroup],hlm-select-group" }, { kind: "component", type: i1$1.HlmSelectItem, selector: "hlm-select-item" }, { kind: "directive", type: i1$1.HlmSelectPortal, selector: "[hlmSelectPortal]" }, { kind: "component", type: i1$1.HlmSelectTrigger, selector: "hlm-select-trigger", inputs: ["class", "buttonId", "ariaDescribedby", "wrapperDisabled", "size", "forceInvalid"] }, { kind: "directive", type: i1$1.HlmSelectValue, selector: "[hlmSelectValue],hlm-select-value" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
290
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmNumberedPagination, isStandalone: true, selector: "hlm-numbered-pagination", inputs: { currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: true, transformFunction: null }, itemsPerPage: { classPropertyName: "itemsPerPage", publicName: "itemsPerPage", isSignal: true, isRequired: true, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: true, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, showEdges: { classPropertyName: "showEdges", publicName: "showEdges", isSignal: true, isRequired: false, transformFunction: null }, pageSizes: { classPropertyName: "pageSizes", publicName: "pageSizes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", itemsPerPage: "itemsPerPageChange" }, ngImport: i0, template: "\n <div class=\"flex items-center justify-between gap-2 px-4 py-2\">\n <div class=\"flex items-center gap-1 text-sm text-nowrap text-gray-600\">\n <b>{{ totalItems() }}</b>\n total items |\n <b>{{ _lastPageNumber() }}</b>\n pages\n </div>\n\n <nav hlmPagination>\n <ul hlmPaginationContent>\n @if (showEdges() && !_isFirstPageActive()) {\n <li hlmPaginationItem (click)=\"goToPrevious()\">\n <hlm-pagination-previous />\n </li>\n }\n\n @for (page of _pages(); track $index) {\n <li hlmPaginationItem>\n @if (page === '...') {\n <hlm-pagination-ellipsis />\n } @else {\n <a hlmPaginationLink [isActive]=\"_currentPage() === page\" (click)=\"currentPage.set(page)\">\n {{ page }}\n </a>\n }\n </li>\n }\n\n @if (showEdges() && !_isLastPageActive()) {\n <li hlmPaginationItem (click)=\"goToNext()\">\n <hlm-pagination-next />\n </li>\n }\n </ul>\n </nav>\n\n <!-- Show Page Size selector -->\n <hlm-select [(value)]=\"itemsPerPage\" class=\"ml-auto\">\n <hlm-select-trigger class=\"w-fit\">\n <hlm-select-value />\n </hlm-select-trigger>\n <hlm-select-content *hlmSelectPortal>\n <hlm-select-group>\n @for (pageSize of _pageSizesWithCurrent(); track pageSize) {\n <hlm-select-item [value]=\"pageSize\">{{ pageSize }}</hlm-select-item>\n }\n </hlm-select-group>\n </hlm-select-content>\n </hlm-select>\n </div>\n ", isInline: true, dependencies: [{ kind: "directive", type: HlmPagination, selector: "[hlmPagination],hlm-pagination", inputs: ["aria-label"] }, { kind: "directive", type: HlmPaginationContent, selector: "ul[hlmPaginationContent]" }, { kind: "directive", type: HlmPaginationItem, selector: "li[hlmPaginationItem]" }, { kind: "component", type: HlmPaginationPrevious, selector: "hlm-pagination-previous", inputs: ["class", "link", "queryParams", "queryParamsHandling", "aria-label", "text", "iconOnly"] }, { kind: "component", type: HlmPaginationNext, selector: "hlm-pagination-next", inputs: ["class", "link", "queryParams", "queryParamsHandling", "aria-label", "text", "iconOnly"] }, { kind: "directive", type: HlmPaginationLink, selector: "[hlmPaginationLink]", inputs: ["isActive", "size", "link"] }, { kind: "component", type: HlmPaginationEllipsis, selector: "hlm-pagination-ellipsis", inputs: ["srOnlyText"] }, { kind: "directive", type: i1$1.HlmSelect, selector: "[hlmSelect],hlm-select" }, { kind: "component", type: i1$1.HlmSelectContent, selector: "hlm-select-content", inputs: ["showScroll"] }, { kind: "directive", type: i1$1.HlmSelectGroup, selector: "[hlmSelectGroup],hlm-select-group" }, { kind: "component", type: i1$1.HlmSelectItem, selector: "hlm-select-item" }, { kind: "directive", type: i1$1.HlmSelectPortal, selector: "[hlmSelectPortal]" }, { kind: "component", type: i1$1.HlmSelectTrigger, selector: "hlm-select-trigger", inputs: ["class", "buttonId", "ariaDescribedby", "wrapperDisabled", "size", "forceInvalid"] }, { kind: "directive", type: i1$1.HlmSelectValue, selector: "[hlmSelectValue],hlm-select-value" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
284
291
  }
285
292
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmNumberedPagination, decorators: [{
286
293
  type: Component,
@@ -297,37 +304,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
297
304
  HlmSelectImports,
298
305
  ],
299
306
  changeDetection: ChangeDetectionStrategy.OnPush,
300
- template: "\n <div class=\"flex items-center justify-between gap-2 px-4 py-2\">\n <div class=\"flex items-center gap-1 text-sm text-nowrap text-gray-600\">\n <b>{{ totalItems() }}</b>\n total items |\n <b>{{ _lastPageNumber() }}</b>\n pages\n </div>\n\n <nav hlmPagination>\n <ul hlmPaginationContent>\n @if (showEdges() && !_isFirstPageActive()) {\n <li hlmPaginationItem (click)=\"goToPrevious()\">\n <hlm-pagination-previous />\n </li>\n }\n\n @for (page of _pages(); track page) {\n <li hlmPaginationItem>\n @if (page === '...') {\n <hlm-pagination-ellipsis />\n } @else {\n <a hlmPaginationLink [isActive]=\"currentPage() === page\" (click)=\"currentPage.set(page)\">\n {{ page }}\n </a>\n }\n </li>\n }\n\n @if (showEdges() && !_isLastPageActive()) {\n <li hlmPaginationItem (click)=\"goToNext()\">\n <hlm-pagination-next />\n </li>\n }\n </ul>\n </nav>\n\n <!-- Show Page Size selector -->\n <hlm-select [(value)]=\"itemsPerPage\" class=\"ml-auto\">\n <hlm-select-trigger class=\"w-fit\">\n <hlm-select-value />\n </hlm-select-trigger>\n <hlm-select-content *hlmSelectPortal>\n <hlm-select-group>\n @for (pageSize of _pageSizesWithCurrent(); track pageSize) {\n <hlm-select-item [value]=\"pageSize\">{{ pageSize }}</hlm-select-item>\n }\n </hlm-select-group>\n </hlm-select-content>\n </hlm-select>\n </div>\n ",
307
+ template: "\n <div class=\"flex items-center justify-between gap-2 px-4 py-2\">\n <div class=\"flex items-center gap-1 text-sm text-nowrap text-gray-600\">\n <b>{{ totalItems() }}</b>\n total items |\n <b>{{ _lastPageNumber() }}</b>\n pages\n </div>\n\n <nav hlmPagination>\n <ul hlmPaginationContent>\n @if (showEdges() && !_isFirstPageActive()) {\n <li hlmPaginationItem (click)=\"goToPrevious()\">\n <hlm-pagination-previous />\n </li>\n }\n\n @for (page of _pages(); track $index) {\n <li hlmPaginationItem>\n @if (page === '...') {\n <hlm-pagination-ellipsis />\n } @else {\n <a hlmPaginationLink [isActive]=\"_currentPage() === page\" (click)=\"currentPage.set(page)\">\n {{ page }}\n </a>\n }\n </li>\n }\n\n @if (showEdges() && !_isLastPageActive()) {\n <li hlmPaginationItem (click)=\"goToNext()\">\n <hlm-pagination-next />\n </li>\n }\n </ul>\n </nav>\n\n <!-- Show Page Size selector -->\n <hlm-select [(value)]=\"itemsPerPage\" class=\"ml-auto\">\n <hlm-select-trigger class=\"w-fit\">\n <hlm-select-value />\n </hlm-select-trigger>\n <hlm-select-content *hlmSelectPortal>\n <hlm-select-group>\n @for (pageSize of _pageSizesWithCurrent(); track pageSize) {\n <hlm-select-item [value]=\"pageSize\">{{ pageSize }}</hlm-select-item>\n }\n </hlm-select-group>\n </hlm-select-content>\n </hlm-select>\n </div>\n ",
301
308
  }]
302
- }], propDecorators: { currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: true }] }, { type: i0.Output, args: ["currentPageChange"] }], itemsPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemsPerPage", required: true }] }, { type: i0.Output, args: ["itemsPerPageChange"] }], totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: true }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], showEdges: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEdges", required: false }] }], pageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizes", required: false }] }] } });
309
+ }], ctorParameters: () => [], propDecorators: { currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: true }] }, { type: i0.Output, args: ["currentPageChange"] }], itemsPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemsPerPage", required: true }] }, { type: i0.Output, args: ["itemsPerPageChange"] }], totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: true }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], showEdges: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEdges", required: false }] }], pageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizes", required: false }] }] } });
303
310
  /**
304
311
  * Checks that the instance.currentPage property is within bounds for the current page range.
305
312
  * If not, return a correct value for currentPage, or the current value if OK.
306
313
  *
307
- * Copied from 'ngx-pagination' package
314
+ * Empty/invalid totals or sizes use page 1. Finite pages are floored;
315
+ * non-finite pages use 1. Page counts saturate at Number.MAX_SAFE_INTEGER.
308
316
  */
309
317
  function outOfBoundCorrection(totalItems, itemsPerPage, currentPage) {
310
- const totalPages = Math.ceil(totalItems / itemsPerPage);
311
- if (totalPages < currentPage && 0 < totalPages) {
312
- return totalPages;
313
- }
314
- if (currentPage < 1) {
315
- return 1;
316
- }
317
- return currentPage;
318
+ const page = Number.isFinite(currentPage) ? Math.floor(currentPage) : 1;
319
+ return Math.max(1, Math.min(pageCount(totalItems, itemsPerPage), page));
318
320
  }
319
321
  /**
320
322
  * Returns an array of Page objects to use in the pagination controls.
321
323
  *
322
- * Copied from 'ngx-pagination' package
324
+ * Window math adapted from 'ngx-pagination'. Range is floored and bounded to
325
+ * 1–100 entries (including gaps); non-finite/non-positive ranges use 7.
326
+ * Ranges below 5 show a contiguous window containing the active page.
327
+ * Work and allocation are proportional to this bounded window, not total pages.
323
328
  */
324
329
  function createPageArray(currentPage, itemsPerPage, totalItems, paginationRange) {
325
- // paginationRange could be a string if passed from attribute, so cast to number.
326
- paginationRange = +paginationRange;
330
+ paginationRange =
331
+ Number.isFinite(paginationRange) && paginationRange > 0
332
+ ? Math.min(100, Math.max(1, Math.floor(paginationRange)))
333
+ : 7;
327
334
  const pages = [];
328
- // Return 1 as default page number
329
- // Make sense to show 1 instead of empty when there are no items
330
- const totalPages = Math.max(Math.ceil(totalItems / itemsPerPage), 1);
335
+ const totalPages = pageCount(totalItems, itemsPerPage);
336
+ currentPage = outOfBoundCorrection(totalItems, itemsPerPage, currentPage);
337
+ if (paginationRange < 5) {
338
+ const length = Math.min(totalPages, paginationRange);
339
+ const start = Math.max(1, Math.min(currentPage - Math.floor(length / 2), totalPages - length + 1));
340
+ return Array.from({ length }, (_, index) => start + index);
341
+ }
331
342
  const halfWay = Math.ceil(paginationRange / 2);
332
343
  const isStart = currentPage <= halfWay;
333
344
  const isEnd = totalPages - halfWay < currentPage;
@@ -378,12 +389,14 @@ function calculatePageNumber(i, currentPage, paginationRange, totalPages) {
378
389
 
379
390
  class HlmNumberedPaginationQueryParams {
380
391
  /**
381
- * The current (active) page.
392
+ * The current page, floored and clamped; corrections emit currentPageChange.
393
+ * The parent owns reading/synchronizing the URL; corrections do not navigate.
382
394
  */
383
395
  currentPage = model.required(/* @ts-ignore */
384
396
  ...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
385
397
  /**
386
- * The number of items per paginated page.
398
+ * The number of items per paginated page. Non-finite/non-positive sizes
399
+ * disable paging (one page) without rewriting this model.
387
400
  */
388
401
  itemsPerPage = model.required(/* @ts-ignore */
389
402
  ...(ngDevMode ? [{ debugName: "itemsPerPage" }] : /* istanbul ignore next */ []));
@@ -400,7 +413,8 @@ class HlmNumberedPaginationQueryParams {
400
413
  link = input('.', /* @ts-ignore */
401
414
  ...(ngDevMode ? [{ debugName: "link" }] : /* istanbul ignore next */ []));
402
415
  /**
403
- * The number of page links to show.
416
+ * Maximum window entries, including ellipses: floored and bounded to 1–100.
417
+ * Non-finite/non-positive values use the default of 7.
404
418
  */
405
419
  maxSize = input(7, { ...(ngDevMode ? { debugName: "maxSize" } : /* istanbul ignore next */ {}), transform: numberAttribute });
406
420
  /**
@@ -420,33 +434,27 @@ class HlmNumberedPaginationQueryParams {
420
434
  : [...pageSizes, this.itemsPerPage()].sort((a, b) => a - b); // otherwise, add current page size and sort the array
421
435
  }, /* @ts-ignore */
422
436
  ...(ngDevMode ? [{ debugName: "_pageSizesWithCurrent" }] : /* istanbul ignore next */ []));
423
- _isFirstPageActive = computed(() => this.currentPage() === 1, /* @ts-ignore */
437
+ _currentPage = computed(() => outOfBoundCorrection(this.totalItems(), this.itemsPerPage(), this.currentPage()), /* @ts-ignore */
438
+ ...(ngDevMode ? [{ debugName: "_currentPage" }] : /* istanbul ignore next */ []));
439
+ _isFirstPageActive = computed(() => this._currentPage() === 1, /* @ts-ignore */
424
440
  ...(ngDevMode ? [{ debugName: "_isFirstPageActive" }] : /* istanbul ignore next */ []));
425
- _isLastPageActive = computed(() => this.currentPage() === this._lastPageNumber(), /* @ts-ignore */
441
+ _isLastPageActive = computed(() => this._currentPage() === this._lastPageNumber(), /* @ts-ignore */
426
442
  ...(ngDevMode ? [{ debugName: "_isLastPageActive" }] : /* istanbul ignore next */ []));
427
- _lastPageNumber = computed(() => {
428
- if (this.totalItems() < 1) {
429
- // when there are 0 or fewer (an error case) items, there are no "pages" as such,
430
- // but it makes sense to consider a single, empty page as the last page.
431
- return 1;
432
- }
433
- return Math.ceil(this.totalItems() / this.itemsPerPage());
434
- }, /* @ts-ignore */
443
+ _lastPageNumber = computed(() => pageCount(this.totalItems(), this.itemsPerPage()), /* @ts-ignore */
435
444
  ...(ngDevMode ? [{ debugName: "_lastPageNumber" }] : /* istanbul ignore next */ []));
436
- _pages = computed(() => {
437
- const correctedCurrentPage = outOfBoundCorrection(this.totalItems(), this.itemsPerPage(), this.currentPage());
438
- if (correctedCurrentPage !== this.currentPage()) {
439
- // update the current page
440
- untracked(() => this.currentPage.set(correctedCurrentPage));
441
- }
442
- return createPageArray(correctedCurrentPage, this.itemsPerPage(), this.totalItems(), this.maxSize());
443
- }, /* @ts-ignore */
445
+ _pages = computed(() => createPageArray(this._currentPage(), this.itemsPerPage(), this.totalItems(), this.maxSize()), /* @ts-ignore */
444
446
  ...(ngDevMode ? [{ debugName: "_pages" }] : /* istanbul ignore next */ []));
445
447
  constructor() {
446
448
  classes(() => "flex items-center justify-between gap-2 px-4 py-2");
449
+ effect(() => {
450
+ const page = this._currentPage();
451
+ if (page !== this.currentPage()) {
452
+ untracked(() => this.currentPage.set(page));
453
+ }
454
+ });
447
455
  }
448
456
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmNumberedPaginationQueryParams, deps: [], target: i0.ɵɵFactoryTarget.Component });
449
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmNumberedPaginationQueryParams, isStandalone: true, selector: "hlm-numbered-pagination-query-params", inputs: { currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: true, transformFunction: null }, itemsPerPage: { classPropertyName: "itemsPerPage", publicName: "itemsPerPage", isSignal: true, isRequired: true, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: true, transformFunction: null }, link: { classPropertyName: "link", publicName: "link", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, showEdges: { classPropertyName: "showEdges", publicName: "showEdges", isSignal: true, isRequired: false, transformFunction: null }, pageSizes: { classPropertyName: "pageSizes", publicName: "pageSizes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", itemsPerPage: "itemsPerPageChange" }, ngImport: i0, template: "\n <div class=\"flex items-center gap-1 text-sm text-nowrap text-gray-600\">\n <b>{{ totalItems() }}</b>\n total items |\n <b>{{ _lastPageNumber() }}</b>\n pages\n </div>\n\n <nav hlmPagination>\n <ul hlmPaginationContent>\n @if (showEdges() && !_isFirstPageActive()) {\n <li hlmPaginationItem>\n <hlm-pagination-previous\n [link]=\"link()\"\n [queryParams]=\"{ page: currentPage() - 1 }\"\n queryParamsHandling=\"merge\"\n />\n </li>\n }\n\n @for (page of _pages(); track page) {\n <li hlmPaginationItem>\n @if (page === '...') {\n <hlm-pagination-ellipsis />\n } @else {\n <a\n hlmPaginationLink\n [link]=\"currentPage() !== page ? link() : undefined\"\n [queryParams]=\"{ page }\"\n queryParamsHandling=\"merge\"\n [isActive]=\"currentPage() === page\"\n >\n {{ page }}\n </a>\n }\n </li>\n }\n\n @if (showEdges() && !_isLastPageActive()) {\n <li hlmPaginationItem>\n <hlm-pagination-next\n [link]=\"link()\"\n [queryParams]=\"{ page: currentPage() + 1 }\"\n queryParamsHandling=\"merge\"\n />\n </li>\n }\n </ul>\n </nav>\n\n <!-- Show Page Size selector -->\n <hlm-select [(value)]=\"itemsPerPage\" class=\"ml-auto\">\n <hlm-select-trigger class=\"w-fit\">\n <hlm-select-value />\n </hlm-select-trigger>\n <hlm-select-content *hlmSelectPortal>\n <hlm-select-group>\n @for (pageSize of _pageSizesWithCurrent(); track pageSize) {\n <hlm-select-item [value]=\"pageSize\">{{ pageSize }}</hlm-select-item>\n }\n </hlm-select-group>\n </hlm-select-content>\n </hlm-select>\n ", isInline: true, dependencies: [{ kind: "directive", type: HlmPagination, selector: "[hlmPagination],hlm-pagination", inputs: ["aria-label"] }, { kind: "directive", type: HlmPaginationContent, selector: "ul[hlmPaginationContent]" }, { kind: "directive", type: HlmPaginationItem, selector: "li[hlmPaginationItem]" }, { kind: "component", type: HlmPaginationPrevious, selector: "hlm-pagination-previous", inputs: ["class", "link", "queryParams", "queryParamsHandling", "aria-label", "text", "iconOnly"] }, { kind: "component", type: HlmPaginationNext, selector: "hlm-pagination-next", inputs: ["class", "link", "queryParams", "queryParamsHandling", "aria-label", "text", "iconOnly"] }, { kind: "directive", type: HlmPaginationLink, selector: "[hlmPaginationLink]", inputs: ["isActive", "size", "link"] }, { kind: "component", type: HlmPaginationEllipsis, selector: "hlm-pagination-ellipsis", inputs: ["srOnlyText"] }, { kind: "directive", type: i1$1.HlmSelect, selector: "[hlmSelect],hlm-select" }, { kind: "component", type: i1$1.HlmSelectContent, selector: "hlm-select-content", inputs: ["showScroll"] }, { kind: "directive", type: i1$1.HlmSelectGroup, selector: "[hlmSelectGroup],hlm-select-group" }, { kind: "component", type: i1$1.HlmSelectItem, selector: "hlm-select-item" }, { kind: "directive", type: i1$1.HlmSelectPortal, selector: "[hlmSelectPortal]" }, { kind: "component", type: i1$1.HlmSelectTrigger, selector: "hlm-select-trigger", inputs: ["class", "buttonId", "ariaDescribedby", "wrapperDisabled", "size", "forceInvalid"] }, { kind: "directive", type: i1$1.HlmSelectValue, selector: "[hlmSelectValue],hlm-select-value" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
457
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmNumberedPaginationQueryParams, isStandalone: true, selector: "hlm-numbered-pagination-query-params", inputs: { currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: true, transformFunction: null }, itemsPerPage: { classPropertyName: "itemsPerPage", publicName: "itemsPerPage", isSignal: true, isRequired: true, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: true, transformFunction: null }, link: { classPropertyName: "link", publicName: "link", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, showEdges: { classPropertyName: "showEdges", publicName: "showEdges", isSignal: true, isRequired: false, transformFunction: null }, pageSizes: { classPropertyName: "pageSizes", publicName: "pageSizes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", itemsPerPage: "itemsPerPageChange" }, ngImport: i0, template: "\n <div class=\"flex items-center gap-1 text-sm text-nowrap text-gray-600\">\n <b>{{ totalItems() }}</b>\n total items |\n <b>{{ _lastPageNumber() }}</b>\n pages\n </div>\n\n <nav hlmPagination>\n <ul hlmPaginationContent>\n @if (showEdges() && !_isFirstPageActive()) {\n <li hlmPaginationItem>\n <hlm-pagination-previous\n [link]=\"link()\"\n [queryParams]=\"{ page: _currentPage() - 1 }\"\n queryParamsHandling=\"merge\"\n />\n </li>\n }\n\n @for (page of _pages(); track $index) {\n <li hlmPaginationItem>\n @if (page === '...') {\n <hlm-pagination-ellipsis />\n } @else {\n <a\n hlmPaginationLink\n [link]=\"_currentPage() !== page ? link() : undefined\"\n [queryParams]=\"{ page }\"\n queryParamsHandling=\"merge\"\n [isActive]=\"_currentPage() === page\"\n >\n {{ page }}\n </a>\n }\n </li>\n }\n\n @if (showEdges() && !_isLastPageActive()) {\n <li hlmPaginationItem>\n <hlm-pagination-next\n [link]=\"link()\"\n [queryParams]=\"{ page: _currentPage() + 1 }\"\n queryParamsHandling=\"merge\"\n />\n </li>\n }\n </ul>\n </nav>\n\n <!-- Show Page Size selector -->\n <hlm-select [(value)]=\"itemsPerPage\" class=\"ml-auto\">\n <hlm-select-trigger class=\"w-fit\">\n <hlm-select-value />\n </hlm-select-trigger>\n <hlm-select-content *hlmSelectPortal>\n <hlm-select-group>\n @for (pageSize of _pageSizesWithCurrent(); track pageSize) {\n <hlm-select-item [value]=\"pageSize\">{{ pageSize }}</hlm-select-item>\n }\n </hlm-select-group>\n </hlm-select-content>\n </hlm-select>\n ", isInline: true, dependencies: [{ kind: "directive", type: HlmPagination, selector: "[hlmPagination],hlm-pagination", inputs: ["aria-label"] }, { kind: "directive", type: HlmPaginationContent, selector: "ul[hlmPaginationContent]" }, { kind: "directive", type: HlmPaginationItem, selector: "li[hlmPaginationItem]" }, { kind: "component", type: HlmPaginationPrevious, selector: "hlm-pagination-previous", inputs: ["class", "link", "queryParams", "queryParamsHandling", "aria-label", "text", "iconOnly"] }, { kind: "component", type: HlmPaginationNext, selector: "hlm-pagination-next", inputs: ["class", "link", "queryParams", "queryParamsHandling", "aria-label", "text", "iconOnly"] }, { kind: "directive", type: HlmPaginationLink, selector: "[hlmPaginationLink]", inputs: ["isActive", "size", "link"] }, { kind: "component", type: HlmPaginationEllipsis, selector: "hlm-pagination-ellipsis", inputs: ["srOnlyText"] }, { kind: "directive", type: i1$1.HlmSelect, selector: "[hlmSelect],hlm-select" }, { kind: "component", type: i1$1.HlmSelectContent, selector: "hlm-select-content", inputs: ["showScroll"] }, { kind: "directive", type: i1$1.HlmSelectGroup, selector: "[hlmSelectGroup],hlm-select-group" }, { kind: "component", type: i1$1.HlmSelectItem, selector: "hlm-select-item" }, { kind: "directive", type: i1$1.HlmSelectPortal, selector: "[hlmSelectPortal]" }, { kind: "component", type: i1$1.HlmSelectTrigger, selector: "hlm-select-trigger", inputs: ["class", "buttonId", "ariaDescribedby", "wrapperDisabled", "size", "forceInvalid"] }, { kind: "directive", type: i1$1.HlmSelectValue, selector: "[hlmSelectValue],hlm-select-value" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
450
458
  }
451
459
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmNumberedPaginationQueryParams, decorators: [{
452
460
  type: Component,
@@ -463,7 +471,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
463
471
  HlmSelectImports,
464
472
  ],
465
473
  changeDetection: ChangeDetectionStrategy.OnPush,
466
- template: "\n <div class=\"flex items-center gap-1 text-sm text-nowrap text-gray-600\">\n <b>{{ totalItems() }}</b>\n total items |\n <b>{{ _lastPageNumber() }}</b>\n pages\n </div>\n\n <nav hlmPagination>\n <ul hlmPaginationContent>\n @if (showEdges() && !_isFirstPageActive()) {\n <li hlmPaginationItem>\n <hlm-pagination-previous\n [link]=\"link()\"\n [queryParams]=\"{ page: currentPage() - 1 }\"\n queryParamsHandling=\"merge\"\n />\n </li>\n }\n\n @for (page of _pages(); track page) {\n <li hlmPaginationItem>\n @if (page === '...') {\n <hlm-pagination-ellipsis />\n } @else {\n <a\n hlmPaginationLink\n [link]=\"currentPage() !== page ? link() : undefined\"\n [queryParams]=\"{ page }\"\n queryParamsHandling=\"merge\"\n [isActive]=\"currentPage() === page\"\n >\n {{ page }}\n </a>\n }\n </li>\n }\n\n @if (showEdges() && !_isLastPageActive()) {\n <li hlmPaginationItem>\n <hlm-pagination-next\n [link]=\"link()\"\n [queryParams]=\"{ page: currentPage() + 1 }\"\n queryParamsHandling=\"merge\"\n />\n </li>\n }\n </ul>\n </nav>\n\n <!-- Show Page Size selector -->\n <hlm-select [(value)]=\"itemsPerPage\" class=\"ml-auto\">\n <hlm-select-trigger class=\"w-fit\">\n <hlm-select-value />\n </hlm-select-trigger>\n <hlm-select-content *hlmSelectPortal>\n <hlm-select-group>\n @for (pageSize of _pageSizesWithCurrent(); track pageSize) {\n <hlm-select-item [value]=\"pageSize\">{{ pageSize }}</hlm-select-item>\n }\n </hlm-select-group>\n </hlm-select-content>\n </hlm-select>\n ",
474
+ template: "\n <div class=\"flex items-center gap-1 text-sm text-nowrap text-gray-600\">\n <b>{{ totalItems() }}</b>\n total items |\n <b>{{ _lastPageNumber() }}</b>\n pages\n </div>\n\n <nav hlmPagination>\n <ul hlmPaginationContent>\n @if (showEdges() && !_isFirstPageActive()) {\n <li hlmPaginationItem>\n <hlm-pagination-previous\n [link]=\"link()\"\n [queryParams]=\"{ page: _currentPage() - 1 }\"\n queryParamsHandling=\"merge\"\n />\n </li>\n }\n\n @for (page of _pages(); track $index) {\n <li hlmPaginationItem>\n @if (page === '...') {\n <hlm-pagination-ellipsis />\n } @else {\n <a\n hlmPaginationLink\n [link]=\"_currentPage() !== page ? link() : undefined\"\n [queryParams]=\"{ page }\"\n queryParamsHandling=\"merge\"\n [isActive]=\"_currentPage() === page\"\n >\n {{ page }}\n </a>\n }\n </li>\n }\n\n @if (showEdges() && !_isLastPageActive()) {\n <li hlmPaginationItem>\n <hlm-pagination-next\n [link]=\"link()\"\n [queryParams]=\"{ page: _currentPage() + 1 }\"\n queryParamsHandling=\"merge\"\n />\n </li>\n }\n </ul>\n </nav>\n\n <!-- Show Page Size selector -->\n <hlm-select [(value)]=\"itemsPerPage\" class=\"ml-auto\">\n <hlm-select-trigger class=\"w-fit\">\n <hlm-select-value />\n </hlm-select-trigger>\n <hlm-select-content *hlmSelectPortal>\n <hlm-select-group>\n @for (pageSize of _pageSizesWithCurrent(); track pageSize) {\n <hlm-select-item [value]=\"pageSize\">{{ pageSize }}</hlm-select-item>\n }\n </hlm-select-group>\n </hlm-select-content>\n </hlm-select>\n ",
467
475
  }]
468
476
  }], ctorParameters: () => [], propDecorators: { currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: true }] }, { type: i0.Output, args: ["currentPageChange"] }], itemsPerPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemsPerPage", required: true }] }, { type: i0.Output, args: ["itemsPerPageChange"] }], totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: true }] }], link: [{ type: i0.Input, args: [{ isSignal: true, alias: "link", required: false }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], showEdges: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEdges", required: false }] }], pageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizes", required: false }] }] } });
469
477
 
@@ -5,11 +5,13 @@ import { ClassValue } from 'clsx';
5
5
 
6
6
  declare class HlmNumberedPagination {
7
7
  /**
8
- * The current (active) page.
8
+ * The current (active) page, floored and clamped to the available range.
9
+ * Corrections emit currentPageChange once; empty/invalid paging uses page 1.
9
10
  */
10
11
  readonly currentPage: _angular_core.ModelSignal<number>;
11
12
  /**
12
- * The number of items per paginated page.
13
+ * The number of items per paginated page. Non-finite/non-positive sizes
14
+ * disable paging (one page) without rewriting this model.
13
15
  */
14
16
  readonly itemsPerPage: _angular_core.ModelSignal<number>;
15
17
  /**
@@ -19,7 +21,8 @@ declare class HlmNumberedPagination {
19
21
  */
20
22
  readonly totalItems: _angular_core.InputSignalWithTransform<number, NumberInput>;
21
23
  /**
22
- * The number of page links to show.
24
+ * Maximum window entries, including ellipses: floored and bounded to 1–100.
25
+ * Non-finite/non-positive values use the default of 7.
23
26
  */
24
27
  readonly maxSize: _angular_core.InputSignalWithTransform<number, NumberInput>;
25
28
  /**
@@ -32,10 +35,12 @@ declare class HlmNumberedPagination {
32
35
  */
33
36
  readonly pageSizes: _angular_core.InputSignal<number[]>;
34
37
  protected readonly _pageSizesWithCurrent: _angular_core.Signal<number[]>;
38
+ protected readonly _currentPage: _angular_core.Signal<number>;
35
39
  protected readonly _isFirstPageActive: _angular_core.Signal<boolean>;
36
40
  protected readonly _isLastPageActive: _angular_core.Signal<boolean>;
37
41
  protected readonly _lastPageNumber: _angular_core.Signal<number>;
38
42
  protected readonly _pages: _angular_core.Signal<Page[]>;
43
+ constructor();
39
44
  protected goToPrevious(): void;
40
45
  protected goToNext(): void;
41
46
  protected goToFirst(): void;
@@ -48,23 +53,29 @@ type Page = number | '...';
48
53
  * Checks that the instance.currentPage property is within bounds for the current page range.
49
54
  * If not, return a correct value for currentPage, or the current value if OK.
50
55
  *
51
- * Copied from 'ngx-pagination' package
56
+ * Empty/invalid totals or sizes use page 1. Finite pages are floored;
57
+ * non-finite pages use 1. Page counts saturate at Number.MAX_SAFE_INTEGER.
52
58
  */
53
59
  declare function outOfBoundCorrection(totalItems: number, itemsPerPage: number, currentPage: number): number;
54
60
  /**
55
61
  * Returns an array of Page objects to use in the pagination controls.
56
62
  *
57
- * Copied from 'ngx-pagination' package
63
+ * Window math adapted from 'ngx-pagination'. Range is floored and bounded to
64
+ * 1–100 entries (including gaps); non-finite/non-positive ranges use 7.
65
+ * Ranges below 5 show a contiguous window containing the active page.
66
+ * Work and allocation are proportional to this bounded window, not total pages.
58
67
  */
59
68
  declare function createPageArray(currentPage: number, itemsPerPage: number, totalItems: number, paginationRange: number): Page[];
60
69
 
61
70
  declare class HlmNumberedPaginationQueryParams {
62
71
  /**
63
- * The current (active) page.
72
+ * The current page, floored and clamped; corrections emit currentPageChange.
73
+ * The parent owns reading/synchronizing the URL; corrections do not navigate.
64
74
  */
65
75
  readonly currentPage: _angular_core.ModelSignal<number>;
66
76
  /**
67
- * The number of items per paginated page.
77
+ * The number of items per paginated page. Non-finite/non-positive sizes
78
+ * disable paging (one page) without rewriting this model.
68
79
  */
69
80
  readonly itemsPerPage: _angular_core.ModelSignal<number>;
70
81
  /**
@@ -79,7 +90,8 @@ declare class HlmNumberedPaginationQueryParams {
79
90
  */
80
91
  readonly link: _angular_core.InputSignal<string>;
81
92
  /**
82
- * The number of page links to show.
93
+ * Maximum window entries, including ellipses: floored and bounded to 1–100.
94
+ * Non-finite/non-positive values use the default of 7.
83
95
  */
84
96
  readonly maxSize: _angular_core.InputSignalWithTransform<number, NumberInput>;
85
97
  /**
@@ -92,6 +104,7 @@ declare class HlmNumberedPaginationQueryParams {
92
104
  */
93
105
  readonly pageSizes: _angular_core.InputSignal<number[]>;
94
106
  protected readonly _pageSizesWithCurrent: _angular_core.Signal<number[]>;
107
+ protected readonly _currentPage: _angular_core.Signal<number>;
95
108
  protected readonly _isFirstPageActive: _angular_core.Signal<boolean>;
96
109
  protected readonly _isLastPageActive: _angular_core.Signal<boolean>;
97
110
  protected readonly _lastPageNumber: _angular_core.Signal<number>;
@@ -49,6 +49,22 @@ import { HlmPhoneInput, formatNanpPhoneNumber, type PhoneNumberFormatter } from
49
49
 
50
50
  Model values may be `string | null | undefined`; non-digit characters are stripped defensively on write. An empty model renders as empty text (no dangling mask characters) and emits `null`. With `modelFormat: 'formatted'` the model holds display text instead of digits — useful when a backend expects the masked value; validators then apply to the formatted string.
51
51
 
52
+ ### Keyboard editing
53
+
54
+ - With a collapsed caret, Backspace removes the preceding digit and Delete removes the following digit, skipping `)`, spaces, hyphens and other non-digit separators. Repeated deletion continues through the mask.
55
+ - With a selection, only selected digits are removed. A punctuation-only selection collapses to its logical digit boundary without deleting an adjacent digit or emitting a model change.
56
+ - Typing and paste replace the native selection, strip non-digits and respect `maxDigits`. The caret is restored after the preceding logical digit; clearing emits `null`. Only effective digit changes emit CVA changes, and blur marks the control touched. Programmatic writes format without emitting user changes.
57
+ - Deletion uses the native cancelable `beforeinput` event's `inputType` and original selection. Other edits retain native editing followed by `input` normalization. Read-only and disabled controls reject edits. Custom formatters should preserve digit content/order and add only non-digit separators so logical positions map correctly.
58
+
59
+ The source regression suites and real Chrome keyboard/clipboard probe can be run from the repository root:
60
+
61
+ ```bash
62
+ pnpm --dir packages/angular test:library phone-input form-phone-input
63
+ node packages/angular/test/phone-keyboard-probe.mjs
64
+ ```
65
+
66
+ The probe reuses installed Puppeteer and the shared Chrome resolver (`CHROME_BIN` or the package browser cache), compiles only an opt-in Angular test fixture, and uses Home/Arrow/Shift, Backspace/Delete, typing and Ctrl+C/Ctrl+V. Its Karma port defaults to `9877` (override with `PHONE_KEYBOARD_PORT`). It checks both primitive and wrapper controls; no published artifacts are needed.
67
+
52
68
  ### `formatNanpPhoneNumber(digits)`
53
69
 
54
70
  North-American numbering plan formatter: `4` → `(4`, `4155` → `(415) 5`, `4155552671` → `(415) 555-2671`, truncated to 10 digits. Pass a custom `PhoneNumberFormatter` for other plans (e.g. chunk-and-join national formats) together with a matching `maxDigits`.
@@ -24,7 +24,8 @@ function formatNanpPhoneNumber(digits) {
24
24
  /**
25
25
  * A masked phone input. The form model holds raw digits (`"4155552671"`);
26
26
  * the visible text is formatted (`"(415) 555-2671"`). Caret position is
27
- * preserved across mask characters while typing or deleting.
27
+ * preserved across mask characters while typing or deleting. Backspace/Delete
28
+ * skip separators in their direction; selected ranges delete only selected digits.
28
29
  */
29
30
  class HlmPhoneInput {
30
31
  static _id = 0;
@@ -53,7 +54,7 @@ class HlmPhoneInput {
53
54
  */
54
55
  modelFormat = input('digits', /* @ts-ignore */
55
56
  ...(ngDevMode ? [{ debugName: "modelFormat" }] : /* istanbul ignore next */ []));
56
- /** Display formatter. Receives raw digits, returns display text. */
57
+ /** Display formatter. Preserve digit order/content and add only non-digit separators for caret mapping. */
57
58
  formatPhoneNumber = input(formatNanpPhoneNumber, /* @ts-ignore */
58
59
  ...(ngDevMode ? [{ debugName: "formatPhoneNumber" }] : /* istanbul ignore next */ []));
59
60
  /** Extra classes merged onto the inner `<input>` (over the `HlmInput` base). */
@@ -88,11 +89,46 @@ class HlmPhoneInput {
88
89
  this._onChange?.(digits);
89
90
  }
90
91
  }
92
+ _handleBeforeInput(event) {
93
+ if (event.defaultPrevented || !event.cancelable)
94
+ return;
95
+ if (this.disabledState() || this.readonly()) {
96
+ event.preventDefault();
97
+ return;
98
+ }
99
+ if (event.inputType !== 'deleteContentBackward' && event.inputType !== 'deleteContentForward')
100
+ return;
101
+ // Input events contain the already-edited value. Only beforeinput retains
102
+ // both the browser's deletion intent and the original selected range.
103
+ const element = event.target;
104
+ const start = element.selectionStart;
105
+ const end = element.selectionEnd;
106
+ if (start === null || end === null)
107
+ return;
108
+ const digits = element.value.replace(/\D/g, '');
109
+ let from = element.value.slice(0, start).replace(/\D/g, '').length;
110
+ let to = element.value.slice(0, end).replace(/\D/g, '').length;
111
+ if (start === end) {
112
+ if (event.inputType === 'deleteContentBackward')
113
+ from = Math.max(0, from - 1);
114
+ else
115
+ to = Math.min(digits.length, to + 1);
116
+ }
117
+ event.preventDefault();
118
+ this._renderEdit(element, digits.slice(0, from) + digits.slice(to), from);
119
+ }
91
120
  _handleInput(event) {
92
121
  const element = event.target;
122
+ if (this.disabledState() || this.readonly()) {
123
+ element.value = this._display();
124
+ return;
125
+ }
93
126
  const caret = element.selectionStart ?? element.value.length;
94
127
  const digitsBeforeCaret = element.value.slice(0, caret).replace(/\D/g, '').length;
95
- const digits = element.value.replace(/\D/g, '').slice(0, Math.max(0, this.maxDigits()));
128
+ this._renderEdit(element, element.value.replace(/\D/g, ''), digitsBeforeCaret);
129
+ }
130
+ _renderEdit(element, rawDigits, digitsBeforeCaret) {
131
+ const digits = rawDigits.slice(0, Math.max(0, this.maxDigits()));
96
132
  const formatted = this.formatPhoneNumber()(digits);
97
133
  const target = Math.min(digitsBeforeCaret, digits.length);
98
134
  let position = 0;
@@ -140,10 +176,11 @@ class HlmPhoneInput {
140
176
  [forceInvalid]="forceInvalid()"
141
177
  [class]="$inputClass()"
142
178
  [value]="_display()"
179
+ (beforeinput)="_handleBeforeInput($event)"
143
180
  (input)="_handleInput($event)"
144
181
  (blur)="_handleBlur()"
145
182
  />
146
- `, isInline: true, dependencies: [{ kind: "directive", type: HlmInput, selector: "[hlmInput]", inputs: ["ariaDescribedby"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
183
+ `, isInline: true, dependencies: [{ kind: "directive", type: HlmInput, selector: "[hlmInput]", inputs: ["ariaDescribedby", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
147
184
  }
148
185
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmPhoneInput, decorators: [{
149
186
  type: Component,
@@ -172,6 +209,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
172
209
  [forceInvalid]="forceInvalid()"
173
210
  [class]="$inputClass()"
174
211
  [value]="_display()"
212
+ (beforeinput)="_handleBeforeInput($event)"
175
213
  (input)="_handleInput($event)"
176
214
  (blur)="_handleBlur()"
177
215
  />
@@ -16,7 +16,8 @@ declare function formatNanpPhoneNumber(digits: string): string;
16
16
  /**
17
17
  * A masked phone input. The form model holds raw digits (`"4155552671"`);
18
18
  * the visible text is formatted (`"(415) 555-2671"`). Caret position is
19
- * preserved across mask characters while typing or deleting.
19
+ * preserved across mask characters while typing or deleting. Backspace/Delete
20
+ * skip separators in their direction; selected ranges delete only selected digits.
20
21
  */
21
22
  declare class HlmPhoneInput implements ControlValueAccessor {
22
23
  private static _id;
@@ -37,7 +38,7 @@ declare class HlmPhoneInput implements ControlValueAccessor {
37
38
  * (`"(415) 555-2671"`).
38
39
  */
39
40
  readonly modelFormat: _angular_core.InputSignal<"digits" | "formatted">;
40
- /** Display formatter. Receives raw digits, returns display text. */
41
+ /** Display formatter. Preserve digit order/content and add only non-digit separators for caret mapping. */
41
42
  readonly formatPhoneNumber: _angular_core.InputSignal<PhoneNumberFormatter>;
42
43
  /** Extra classes merged onto the inner `<input>` (over the `HlmInput` base). */
43
44
  readonly userClass: _angular_core.InputSignal<ClassValue>;
@@ -52,7 +53,9 @@ declare class HlmPhoneInput implements ControlValueAccessor {
52
53
  protected _onChange?: ChangeFn<string | null>;
53
54
  protected _onTouched?: TouchFn;
54
55
  private _emitModel;
56
+ protected _handleBeforeInput(event: InputEvent): void;
55
57
  protected _handleInput(event: Event): void;
58
+ private _renderEdit;
56
59
  protected _handleBlur(): void;
57
60
  /** CONTROL VALUE ACCESSOR */
58
61
  writeValue(value: string | null | undefined): void;