@egose/shadcn-theme-ng-tw 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -1
- package/autocomplete/fesm2022/autocomplete.mjs +14 -4
- package/autocomplete/types/autocomplete.d.ts +4 -1
- package/checkbox/README.md +6 -2
- package/checkbox/fesm2022/checkbox.mjs +15 -9
- package/checkbox/types/checkbox.d.ts +6 -4
- package/combobox/fesm2022/combobox.mjs +22 -6
- package/combobox/types/combobox.d.ts +8 -2
- package/data-table/README.md +11 -1
- package/data-table/fesm2022/data-table.mjs +76 -26
- package/data-table/types/data-table.d.ts +25 -6
- package/date-picker/README.md +41 -9
- package/date-picker/fesm2022/date-picker.mjs +265 -59
- package/date-picker/types/date-picker.d.ts +58 -27
- package/form-autocomplete/README.md +4 -2
- package/form-autocomplete/fesm2022/form-autocomplete.mjs +33 -8
- package/form-autocomplete/types/form-autocomplete.d.ts +9 -3
- package/form-combobox/README.md +4 -2
- package/form-combobox/fesm2022/form-combobox.mjs +65 -14
- package/form-combobox/types/form-combobox.d.ts +10 -3
- package/form-input-otp/README.md +7 -5
- package/form-input-otp/fesm2022/form-input-otp.mjs +28 -14
- package/form-input-otp/types/form-input-otp.d.ts +9 -3
- package/form-searchable-multiselect/README.md +15 -1
- package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +23 -3
- package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +9 -1
- package/form-slider/README.md +3 -1
- package/form-slider/fesm2022/form-slider.mjs +21 -6
- package/form-slider/types/form-slider.d.ts +9 -3
- package/form-toggle/README.md +22 -18
- package/form-toggle/fesm2022/form-toggle.mjs +25 -8
- package/form-toggle/types/form-toggle.d.ts +6 -2
- package/input-otp/README.md +33 -9
- package/input-otp/fesm2022/input-otp.mjs +127 -11
- package/input-otp/types/input-otp.d.ts +24 -4
- package/package.json +1 -1
- package/pagination/README.md +21 -12
- package/pagination/fesm2022/pagination.mjs +83 -75
- package/pagination/types/pagination.d.ts +21 -8
- package/phone-input/README.md +16 -0
- package/phone-input/fesm2022/phone-input.mjs +41 -3
- package/phone-input/types/phone-input.d.ts +5 -2
- package/searchable-multiselect/README.md +62 -40
- package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +97 -36
- package/searchable-multiselect/types/searchable-multiselect.d.ts +23 -6
- package/slider/fesm2022/slider.mjs +15 -5
- package/slider/types/slider.d.ts +6 -2
- package/stepper/README.md +39 -19
- package/stepper/fesm2022/stepper.mjs +71 -17
- package/stepper/types/stepper.d.ts +12 -1
|
@@ -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-tw/select';
|
|
4
4
|
import { HlmSelectImports } from '@egose/shadcn-theme-ng-tw/select';
|
|
5
5
|
import { classes, hlm } from '@egose/shadcn-theme-ng-tw/utils';
|
|
@@ -265,14 +265,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
265
265
|
}]
|
|
266
266
|
}], 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 }] }] } });
|
|
267
267
|
|
|
268
|
+
/** Shared arithmetic policy; invalid totals/sizes represent a single page. */
|
|
269
|
+
function pageCount(totalItems, itemsPerPage) {
|
|
270
|
+
if (!Number.isFinite(totalItems) || totalItems <= 0 || !Number.isFinite(itemsPerPage) || itemsPerPage <= 0) {
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
return Math.min(Number.MAX_SAFE_INTEGER, Math.max(1, Math.ceil(totalItems / itemsPerPage)));
|
|
274
|
+
}
|
|
275
|
+
|
|
268
276
|
class HlmNumberedPagination {
|
|
269
277
|
/**
|
|
270
|
-
* The current (active) page.
|
|
278
|
+
* The current (active) page, floored and clamped to the available range.
|
|
279
|
+
* Corrections emit currentPageChange once; empty/invalid paging uses page 1.
|
|
271
280
|
*/
|
|
272
281
|
currentPage = model.required(/* @ts-ignore */
|
|
273
282
|
...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
|
|
274
283
|
/**
|
|
275
|
-
* The number of items per paginated page.
|
|
284
|
+
* The number of items per paginated page. Non-finite/non-positive sizes
|
|
285
|
+
* disable paging (one page) without rewriting this model.
|
|
276
286
|
*/
|
|
277
287
|
itemsPerPage = model.required(/* @ts-ignore */
|
|
278
288
|
...(ngDevMode ? [{ debugName: "itemsPerPage" }] : /* istanbul ignore next */ []));
|
|
@@ -283,7 +293,8 @@ class HlmNumberedPagination {
|
|
|
283
293
|
*/
|
|
284
294
|
totalItems = input.required({ ...(ngDevMode ? { debugName: "totalItems" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
285
295
|
/**
|
|
286
|
-
*
|
|
296
|
+
* Maximum window entries, including ellipses: floored and bounded to 1–100.
|
|
297
|
+
* Non-finite/non-positive values use the default of 7.
|
|
287
298
|
*/
|
|
288
299
|
maxSize = input(7, { ...(ngDevMode ? { debugName: "maxSize" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
289
300
|
/**
|
|
@@ -303,33 +314,29 @@ class HlmNumberedPagination {
|
|
|
303
314
|
: [...pageSizes, this.itemsPerPage()].sort((a, b) => a - b); // otherwise, add current page size and sort the array
|
|
304
315
|
}, /* @ts-ignore */
|
|
305
316
|
...(ngDevMode ? [{ debugName: "_pageSizesWithCurrent" }] : /* istanbul ignore next */ []));
|
|
306
|
-
|
|
317
|
+
_currentPage = computed(() => outOfBoundCorrection(this.totalItems(), this.itemsPerPage(), this.currentPage()), /* @ts-ignore */
|
|
318
|
+
...(ngDevMode ? [{ debugName: "_currentPage" }] : /* istanbul ignore next */ []));
|
|
319
|
+
_isFirstPageActive = computed(() => this._currentPage() === 1, /* @ts-ignore */
|
|
307
320
|
...(ngDevMode ? [{ debugName: "_isFirstPageActive" }] : /* istanbul ignore next */ []));
|
|
308
|
-
_isLastPageActive = computed(() => this.
|
|
321
|
+
_isLastPageActive = computed(() => this._currentPage() === this._lastPageNumber(), /* @ts-ignore */
|
|
309
322
|
...(ngDevMode ? [{ debugName: "_isLastPageActive" }] : /* istanbul ignore next */ []));
|
|
310
|
-
_lastPageNumber = computed(() =>
|
|
311
|
-
if (this.totalItems() < 1) {
|
|
312
|
-
// when there are 0 or fewer (an error case) items, there are no "pages" as such,
|
|
313
|
-
// but it makes sense to consider a single, empty page as the last page.
|
|
314
|
-
return 1;
|
|
315
|
-
}
|
|
316
|
-
return Math.ceil(this.totalItems() / this.itemsPerPage());
|
|
317
|
-
}, /* @ts-ignore */
|
|
323
|
+
_lastPageNumber = computed(() => pageCount(this.totalItems(), this.itemsPerPage()), /* @ts-ignore */
|
|
318
324
|
...(ngDevMode ? [{ debugName: "_lastPageNumber" }] : /* istanbul ignore next */ []));
|
|
319
|
-
_pages = computed(() =>
|
|
320
|
-
const correctedCurrentPage = outOfBoundCorrection(this.totalItems(), this.itemsPerPage(), this.currentPage());
|
|
321
|
-
if (correctedCurrentPage !== this.currentPage()) {
|
|
322
|
-
// update the current page
|
|
323
|
-
untracked(() => this.currentPage.set(correctedCurrentPage));
|
|
324
|
-
}
|
|
325
|
-
return createPageArray(correctedCurrentPage, this.itemsPerPage(), this.totalItems(), this.maxSize());
|
|
326
|
-
}, /* @ts-ignore */
|
|
325
|
+
_pages = computed(() => createPageArray(this._currentPage(), this.itemsPerPage(), this.totalItems(), this.maxSize()), /* @ts-ignore */
|
|
327
326
|
...(ngDevMode ? [{ debugName: "_pages" }] : /* istanbul ignore next */ []));
|
|
327
|
+
constructor() {
|
|
328
|
+
effect(() => {
|
|
329
|
+
const page = this._currentPage();
|
|
330
|
+
if (page !== this.currentPage()) {
|
|
331
|
+
untracked(() => this.currentPage.set(page));
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
328
335
|
goToPrevious() {
|
|
329
|
-
this.currentPage.set(this.
|
|
336
|
+
this.currentPage.set(Math.max(1, this._currentPage() - 1));
|
|
330
337
|
}
|
|
331
338
|
goToNext() {
|
|
332
|
-
this.currentPage.set(this.
|
|
339
|
+
this.currentPage.set(Math.min(this._lastPageNumber(), this._currentPage() + 1));
|
|
333
340
|
}
|
|
334
341
|
goToFirst() {
|
|
335
342
|
this.currentPage.set(1);
|
|
@@ -355,12 +362,12 @@ class HlmNumberedPagination {
|
|
|
355
362
|
</li>
|
|
356
363
|
}
|
|
357
364
|
|
|
358
|
-
@for (page of _pages(); track
|
|
365
|
+
@for (page of _pages(); track $index) {
|
|
359
366
|
<li hlmPaginationItem>
|
|
360
367
|
@if (page === '...') {
|
|
361
368
|
<hlm-pagination-ellipsis />
|
|
362
369
|
} @else {
|
|
363
|
-
<a hlmPaginationLink [isActive]="
|
|
370
|
+
<a hlmPaginationLink [isActive]="_currentPage() === page" (click)="currentPage.set(page)">
|
|
364
371
|
{{ page }}
|
|
365
372
|
</a>
|
|
366
373
|
}
|
|
@@ -423,12 +430,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
423
430
|
</li>
|
|
424
431
|
}
|
|
425
432
|
|
|
426
|
-
@for (page of _pages(); track
|
|
433
|
+
@for (page of _pages(); track $index) {
|
|
427
434
|
<li hlmPaginationItem>
|
|
428
435
|
@if (page === '...') {
|
|
429
436
|
<hlm-pagination-ellipsis />
|
|
430
437
|
} @else {
|
|
431
|
-
<a hlmPaginationLink [isActive]="
|
|
438
|
+
<a hlmPaginationLink [isActive]="_currentPage() === page" (click)="currentPage.set(page)">
|
|
432
439
|
{{ page }}
|
|
433
440
|
</a>
|
|
434
441
|
}
|
|
@@ -459,35 +466,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
459
466
|
</div>
|
|
460
467
|
`,
|
|
461
468
|
}]
|
|
462
|
-
}], 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 }] }] } });
|
|
469
|
+
}], 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 }] }] } });
|
|
463
470
|
/**
|
|
464
471
|
* Checks that the instance.currentPage property is within bounds for the current page range.
|
|
465
472
|
* If not, return a correct value for currentPage, or the current value if OK.
|
|
466
473
|
*
|
|
467
|
-
*
|
|
474
|
+
* Empty/invalid totals or sizes use page 1. Finite pages are floored;
|
|
475
|
+
* non-finite pages use 1. Page counts saturate at Number.MAX_SAFE_INTEGER.
|
|
468
476
|
*/
|
|
469
477
|
function outOfBoundCorrection(totalItems, itemsPerPage, currentPage) {
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
return totalPages;
|
|
473
|
-
}
|
|
474
|
-
if (currentPage < 1) {
|
|
475
|
-
return 1;
|
|
476
|
-
}
|
|
477
|
-
return currentPage;
|
|
478
|
+
const page = Number.isFinite(currentPage) ? Math.floor(currentPage) : 1;
|
|
479
|
+
return Math.max(1, Math.min(pageCount(totalItems, itemsPerPage), page));
|
|
478
480
|
}
|
|
479
481
|
/**
|
|
480
482
|
* Returns an array of Page objects to use in the pagination controls.
|
|
481
483
|
*
|
|
482
|
-
*
|
|
484
|
+
* Window math adapted from 'ngx-pagination'. Range is floored and bounded to
|
|
485
|
+
* 1–100 entries (including gaps); non-finite/non-positive ranges use 7.
|
|
486
|
+
* Ranges below 5 show a contiguous window containing the active page.
|
|
487
|
+
* Work and allocation are proportional to this bounded window, not total pages.
|
|
483
488
|
*/
|
|
484
489
|
function createPageArray(currentPage, itemsPerPage, totalItems, paginationRange) {
|
|
485
|
-
|
|
486
|
-
|
|
490
|
+
paginationRange =
|
|
491
|
+
Number.isFinite(paginationRange) && paginationRange > 0
|
|
492
|
+
? Math.min(100, Math.max(1, Math.floor(paginationRange)))
|
|
493
|
+
: 7;
|
|
487
494
|
const pages = [];
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
495
|
+
const totalPages = pageCount(totalItems, itemsPerPage);
|
|
496
|
+
currentPage = outOfBoundCorrection(totalItems, itemsPerPage, currentPage);
|
|
497
|
+
if (paginationRange < 5) {
|
|
498
|
+
const length = Math.min(totalPages, paginationRange);
|
|
499
|
+
const start = Math.max(1, Math.min(currentPage - Math.floor(length / 2), totalPages - length + 1));
|
|
500
|
+
return Array.from({ length }, (_, index) => start + index);
|
|
501
|
+
}
|
|
491
502
|
const halfWay = Math.ceil(paginationRange / 2);
|
|
492
503
|
const isStart = currentPage <= halfWay;
|
|
493
504
|
const isEnd = totalPages - halfWay < currentPage;
|
|
@@ -538,12 +549,14 @@ function calculatePageNumber(i, currentPage, paginationRange, totalPages) {
|
|
|
538
549
|
|
|
539
550
|
class HlmNumberedPaginationQueryParams {
|
|
540
551
|
/**
|
|
541
|
-
* The current
|
|
552
|
+
* The current page, floored and clamped; corrections emit currentPageChange.
|
|
553
|
+
* The parent owns reading/synchronizing the URL; corrections do not navigate.
|
|
542
554
|
*/
|
|
543
555
|
currentPage = model.required(/* @ts-ignore */
|
|
544
556
|
...(ngDevMode ? [{ debugName: "currentPage" }] : /* istanbul ignore next */ []));
|
|
545
557
|
/**
|
|
546
|
-
* The number of items per paginated page.
|
|
558
|
+
* The number of items per paginated page. Non-finite/non-positive sizes
|
|
559
|
+
* disable paging (one page) without rewriting this model.
|
|
547
560
|
*/
|
|
548
561
|
itemsPerPage = model.required(/* @ts-ignore */
|
|
549
562
|
...(ngDevMode ? [{ debugName: "itemsPerPage" }] : /* istanbul ignore next */ []));
|
|
@@ -560,7 +573,8 @@ class HlmNumberedPaginationQueryParams {
|
|
|
560
573
|
link = input('.', /* @ts-ignore */
|
|
561
574
|
...(ngDevMode ? [{ debugName: "link" }] : /* istanbul ignore next */ []));
|
|
562
575
|
/**
|
|
563
|
-
*
|
|
576
|
+
* Maximum window entries, including ellipses: floored and bounded to 1–100.
|
|
577
|
+
* Non-finite/non-positive values use the default of 7.
|
|
564
578
|
*/
|
|
565
579
|
maxSize = input(7, { ...(ngDevMode ? { debugName: "maxSize" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
566
580
|
/**
|
|
@@ -580,30 +594,24 @@ class HlmNumberedPaginationQueryParams {
|
|
|
580
594
|
: [...pageSizes, this.itemsPerPage()].sort((a, b) => a - b); // otherwise, add current page size and sort the array
|
|
581
595
|
}, /* @ts-ignore */
|
|
582
596
|
...(ngDevMode ? [{ debugName: "_pageSizesWithCurrent" }] : /* istanbul ignore next */ []));
|
|
583
|
-
|
|
597
|
+
_currentPage = computed(() => outOfBoundCorrection(this.totalItems(), this.itemsPerPage(), this.currentPage()), /* @ts-ignore */
|
|
598
|
+
...(ngDevMode ? [{ debugName: "_currentPage" }] : /* istanbul ignore next */ []));
|
|
599
|
+
_isFirstPageActive = computed(() => this._currentPage() === 1, /* @ts-ignore */
|
|
584
600
|
...(ngDevMode ? [{ debugName: "_isFirstPageActive" }] : /* istanbul ignore next */ []));
|
|
585
|
-
_isLastPageActive = computed(() => this.
|
|
601
|
+
_isLastPageActive = computed(() => this._currentPage() === this._lastPageNumber(), /* @ts-ignore */
|
|
586
602
|
...(ngDevMode ? [{ debugName: "_isLastPageActive" }] : /* istanbul ignore next */ []));
|
|
587
|
-
_lastPageNumber = computed(() =>
|
|
588
|
-
if (this.totalItems() < 1) {
|
|
589
|
-
// when there are 0 or fewer (an error case) items, there are no "pages" as such,
|
|
590
|
-
// but it makes sense to consider a single, empty page as the last page.
|
|
591
|
-
return 1;
|
|
592
|
-
}
|
|
593
|
-
return Math.ceil(this.totalItems() / this.itemsPerPage());
|
|
594
|
-
}, /* @ts-ignore */
|
|
603
|
+
_lastPageNumber = computed(() => pageCount(this.totalItems(), this.itemsPerPage()), /* @ts-ignore */
|
|
595
604
|
...(ngDevMode ? [{ debugName: "_lastPageNumber" }] : /* istanbul ignore next */ []));
|
|
596
|
-
_pages = computed(() =>
|
|
597
|
-
const correctedCurrentPage = outOfBoundCorrection(this.totalItems(), this.itemsPerPage(), this.currentPage());
|
|
598
|
-
if (correctedCurrentPage !== this.currentPage()) {
|
|
599
|
-
// update the current page
|
|
600
|
-
untracked(() => this.currentPage.set(correctedCurrentPage));
|
|
601
|
-
}
|
|
602
|
-
return createPageArray(correctedCurrentPage, this.itemsPerPage(), this.totalItems(), this.maxSize());
|
|
603
|
-
}, /* @ts-ignore */
|
|
605
|
+
_pages = computed(() => createPageArray(this._currentPage(), this.itemsPerPage(), this.totalItems(), this.maxSize()), /* @ts-ignore */
|
|
604
606
|
...(ngDevMode ? [{ debugName: "_pages" }] : /* istanbul ignore next */ []));
|
|
605
607
|
constructor() {
|
|
606
608
|
classes(() => 'tw:flex tw:items-center tw:justify-between tw:gap-2 tw:px-4 tw:py-2');
|
|
609
|
+
effect(() => {
|
|
610
|
+
const page = this._currentPage();
|
|
611
|
+
if (page !== this.currentPage()) {
|
|
612
|
+
untracked(() => this.currentPage.set(page));
|
|
613
|
+
}
|
|
614
|
+
});
|
|
607
615
|
}
|
|
608
616
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmNumberedPaginationQueryParams, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
609
617
|
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: `
|
|
@@ -620,23 +628,23 @@ class HlmNumberedPaginationQueryParams {
|
|
|
620
628
|
<li hlmPaginationItem>
|
|
621
629
|
<hlm-pagination-previous
|
|
622
630
|
[link]="link()"
|
|
623
|
-
[queryParams]="{ page:
|
|
631
|
+
[queryParams]="{ page: _currentPage() - 1 }"
|
|
624
632
|
queryParamsHandling="merge"
|
|
625
633
|
/>
|
|
626
634
|
</li>
|
|
627
635
|
}
|
|
628
636
|
|
|
629
|
-
@for (page of _pages(); track
|
|
637
|
+
@for (page of _pages(); track $index) {
|
|
630
638
|
<li hlmPaginationItem>
|
|
631
639
|
@if (page === '...') {
|
|
632
640
|
<hlm-pagination-ellipsis />
|
|
633
641
|
} @else {
|
|
634
642
|
<a
|
|
635
643
|
hlmPaginationLink
|
|
636
|
-
[link]="
|
|
644
|
+
[link]="_currentPage() !== page ? link() : undefined"
|
|
637
645
|
[queryParams]="{ page }"
|
|
638
646
|
queryParamsHandling="merge"
|
|
639
|
-
[isActive]="
|
|
647
|
+
[isActive]="_currentPage() === page"
|
|
640
648
|
>
|
|
641
649
|
{{ page }}
|
|
642
650
|
</a>
|
|
@@ -648,7 +656,7 @@ class HlmNumberedPaginationQueryParams {
|
|
|
648
656
|
<li hlmPaginationItem>
|
|
649
657
|
<hlm-pagination-next
|
|
650
658
|
[link]="link()"
|
|
651
|
-
[queryParams]="{ page:
|
|
659
|
+
[queryParams]="{ page: _currentPage() + 1 }"
|
|
652
660
|
queryParamsHandling="merge"
|
|
653
661
|
/>
|
|
654
662
|
</li>
|
|
@@ -700,23 +708,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
700
708
|
<li hlmPaginationItem>
|
|
701
709
|
<hlm-pagination-previous
|
|
702
710
|
[link]="link()"
|
|
703
|
-
[queryParams]="{ page:
|
|
711
|
+
[queryParams]="{ page: _currentPage() - 1 }"
|
|
704
712
|
queryParamsHandling="merge"
|
|
705
713
|
/>
|
|
706
714
|
</li>
|
|
707
715
|
}
|
|
708
716
|
|
|
709
|
-
@for (page of _pages(); track
|
|
717
|
+
@for (page of _pages(); track $index) {
|
|
710
718
|
<li hlmPaginationItem>
|
|
711
719
|
@if (page === '...') {
|
|
712
720
|
<hlm-pagination-ellipsis />
|
|
713
721
|
} @else {
|
|
714
722
|
<a
|
|
715
723
|
hlmPaginationLink
|
|
716
|
-
[link]="
|
|
724
|
+
[link]="_currentPage() !== page ? link() : undefined"
|
|
717
725
|
[queryParams]="{ page }"
|
|
718
726
|
queryParamsHandling="merge"
|
|
719
|
-
[isActive]="
|
|
727
|
+
[isActive]="_currentPage() === page"
|
|
720
728
|
>
|
|
721
729
|
{{ page }}
|
|
722
730
|
</a>
|
|
@@ -728,7 +736,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
|
|
|
728
736
|
<li hlmPaginationItem>
|
|
729
737
|
<hlm-pagination-next
|
|
730
738
|
[link]="link()"
|
|
731
|
-
[queryParams]="{ page:
|
|
739
|
+
[queryParams]="{ page: _currentPage() + 1 }"
|
|
732
740
|
queryParamsHandling="merge"
|
|
733
741
|
/>
|
|
734
742
|
</li>
|
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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>;
|
package/phone-input/README.md
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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,6 +176,7 @@ 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
|
/>
|
|
@@ -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.
|
|
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;
|