@ng-primitives/mcp 0.120.4 → 0.121.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -272,9 +272,7 @@
272
272
  "hasSecondaryEntryPoint": true,
273
273
  "category": "form",
274
274
  "description": "A button is a clickable element that can be used to trigger an action. This primitive enhances the native button element with improved accessibility and interaction handling for hover, press and focus.",
275
- "accessibility": [
276
- "ARIA pattern"
277
- ],
275
+ "accessibility": [],
278
276
  "examples": [
279
277
  {
280
278
  "name": "example-0",
@@ -1065,8 +1063,42 @@
1065
1063
  "NgpPaginationNext",
1066
1064
  "NgpPaginationPrevious",
1067
1065
  "NgpPagination",
1066
+ "NgpPaginationStateToken",
1067
+ "ngpPagination",
1068
1068
  "injectPaginationState",
1069
- "providePaginationState"
1069
+ "providePaginationState",
1070
+ "type NgpPaginationState",
1071
+ "type NgpPaginationProps",
1072
+ "NgpPaginationButtonStateToken",
1073
+ "ngpPaginationButton",
1074
+ "injectPaginationButtonState",
1075
+ "providePaginationButtonState",
1076
+ "type NgpPaginationButtonState",
1077
+ "type NgpPaginationButtonProps",
1078
+ "NgpPaginationFirstStateToken",
1079
+ "ngpPaginationFirst",
1080
+ "injectPaginationFirstState",
1081
+ "providePaginationFirstState",
1082
+ "type NgpPaginationFirstState",
1083
+ "type NgpPaginationFirstProps",
1084
+ "NgpPaginationLastStateToken",
1085
+ "ngpPaginationLast",
1086
+ "injectPaginationLastState",
1087
+ "providePaginationLastState",
1088
+ "type NgpPaginationLastState",
1089
+ "type NgpPaginationLastProps",
1090
+ "NgpPaginationNextStateToken",
1091
+ "ngpPaginationNext",
1092
+ "injectPaginationNextState",
1093
+ "providePaginationNextState",
1094
+ "type NgpPaginationNextState",
1095
+ "type NgpPaginationNextProps",
1096
+ "NgpPaginationPreviousStateToken",
1097
+ "ngpPaginationPrevious",
1098
+ "injectPaginationPreviousState",
1099
+ "providePaginationPreviousState",
1100
+ "type NgpPaginationPreviousState",
1101
+ "type NgpPaginationPreviousProps"
1070
1102
  ],
1071
1103
  "hasSecondaryEntryPoint": true,
1072
1104
  "category": "navigation",
@@ -1107,10 +1139,20 @@
1107
1139
  "ngpPopoverArrow",
1108
1140
  "providePopoverArrowState",
1109
1141
  "NgpPopoverTrigger",
1110
- "type NgpPopoverPlacement",
1142
+ "NgpPopover",
1143
+ "type NgpPopoverProps",
1144
+ "type NgpPopoverState",
1145
+ "NgpPopoverStateToken",
1146
+ "ngpPopover",
1147
+ "injectPopoverState",
1148
+ "providePopoverState",
1149
+ "NgpPopoverTriggerStateToken",
1150
+ "ngpPopoverTrigger",
1111
1151
  "injectPopoverTriggerState",
1112
1152
  "providePopoverTriggerState",
1113
- "NgpPopover"
1153
+ "type NgpPopoverTriggerState",
1154
+ "type NgpPopoverTriggerProps",
1155
+ "type NgpPopoverPlacement"
1114
1156
  ],
1115
1157
  "hasSecondaryEntryPoint": true,
1116
1158
  "category": "feedback",
@@ -1148,6 +1190,7 @@
1148
1190
  "name": "portal",
1149
1191
  "entryPoint": "ng-primitives/portal",
1150
1192
  "exports": [
1193
+ "provideControlContainerIsolation",
1151
1194
  "coerceFlip",
1152
1195
  "NgpFlip",
1153
1196
  "NgpFlipInput",
@@ -1369,10 +1412,24 @@
1369
1412
  "injectNativeSelectState",
1370
1413
  "provideNativeSelectState",
1371
1414
  "NgpSelectDropdown",
1415
+ "injectSelectDropdownState",
1416
+ "ngpSelectDropdown",
1417
+ "NgpSelectDropdownStateToken",
1418
+ "provideSelectDropdownState",
1372
1419
  "NgpSelectOption",
1420
+ "injectSelectOptionState",
1421
+ "ngpSelectOption",
1422
+ "NgpSelectOptionStateToken",
1423
+ "provideSelectOptionState",
1373
1424
  "NgpSelectPortal",
1425
+ "injectSelectPortalState",
1426
+ "ngpSelectPortal",
1427
+ "NgpSelectPortalStateToken",
1428
+ "provideSelectPortalState",
1374
1429
  "NgpSelect",
1375
1430
  "injectSelectState",
1431
+ "ngpSelect",
1432
+ "NgpSelectStateToken",
1376
1433
  "provideSelectState",
1377
1434
  "NgpSelectConfig",
1378
1435
  "provideSelectConfig",
@@ -1408,7 +1465,7 @@
1408
1465
  }
1409
1466
  ],
1410
1467
  "reusableComponent": {
1411
- "code": "import { BooleanInput } from '@angular/cdk/coercion';\nimport { booleanAttribute, Component, input, model, signal } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroChevronDown } from '@ng-icons/heroicons/outline';\nimport {\n NgpSelect,\n NgpSelectDropdown,\n NgpSelectOption,\n NgpSelectPortal,\n} from 'ng-primitives/select';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-select',\n imports: [NgpSelect, NgpSelectDropdown, NgpSelectOption, NgpSelectPortal, NgIcon],\n providers: [provideIcons({ heroChevronDown }), provideValueAccessor(Select)],\n template: `\n <div\n [(ngpSelectValue)]=\"value\"\n [ngpSelectDisabled]=\"disabled() || formDisabled()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n (ngpSelectValueChange)=\"onValueChange($event)\"\n ngpSelect\n >\n @if (value(); as value) {\n <span class=\"select-value\">{{ value }}</span>\n } @else {\n <span class=\"select-placeholder\">{{ placeholder() }}</span>\n }\n\n <ng-icon name=\"heroChevronDown\" />\n\n <div *ngpSelectPortal ngpSelectDropdown>\n @for (option of options(); track option) {\n <div [ngpSelectOptionValue]=\"option\" ngpSelectOption>\n {{ option }}\n </div>\n } @empty {\n <div class=\"empty-message\">No options found</div>\n }\n </div>\n </div>\n `,\n styles: `\n [ngpSelect] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: 36px;\n width: 300px;\n border-radius: 8px;\n border: none;\n background-color: var(--ngp-background);\n box-shadow: var(--ngp-input-shadow);\n box-sizing: border-box;\n }\n\n [ngpSelect][data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n .select-value,\n .select-placeholder {\n display: flex;\n align-items: center;\n flex: 1;\n padding: 0 16px;\n background-color: transparent;\n color: var(--ngp-text-primary);\n font-family: inherit;\n font-size: 14px;\n padding: 0 16px;\n height: 100%;\n }\n\n .select-placeholder {\n color: var(--ngp-text-secondary);\n }\n\n ng-icon {\n display: inline-flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n margin-inline: 8px;\n font-size: 14px;\n }\n\n [ngpSelectDropdown] {\n background-color: var(--ngp-background);\n border: 1px solid var(--ngp-border);\n padding: 0.25rem;\n border-radius: 0.75rem;\n outline: none;\n position: absolute;\n animation: popover-show 0.1s ease-out;\n width: var(--ngp-select-width);\n box-shadow: var(--ngp-shadow-lg);\n box-sizing: border-box;\n margin-top: 4px;\n max-height: 240px;\n overflow-y: auto;\n transform-origin: var(--ngp-select-transform-origin);\n }\n\n [ngpSelectDropdown][data-enter] {\n animation: select-show 0.1s ease-out;\n }\n\n [ngpSelectDropdown][data-exit] {\n animation: select-hide 0.1s ease-out;\n }\n\n [ngpSelectOption] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.375rem 0.75rem;\n cursor: pointer;\n border-radius: 0.5rem;\n width: 100%;\n height: 36px;\n font-size: 14px;\n color: var(--ngp-text-primary);\n box-sizing: border-box;\n }\n\n [ngpSelectOption][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n [ngpSelectOption][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n [ngpSelectOption][data-active] {\n background-color: var(--ngp-background-active);\n }\n\n .empty-message {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 0.5rem;\n color: var(--ngp-text-secondary);\n font-size: 14px;\n font-weight: 500;\n text-align: center;\n }\n\n @keyframes select-show {\n 0% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n 100% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n @keyframes select-hide {\n 0% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n 100% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n }\n `,\n})\nexport class Select implements ControlValueAccessor {\n /** The options for the select. */\n readonly options = input<string[]>([]);\n\n /** The selected value. */\n readonly value = model<string | undefined>();\n\n /** The placeholder for the input. */\n readonly placeholder = input<string>('');\n\n /** The accessible label for the select trigger. */\n readonly ariaLabel = input<string>('');\n\n /** The disabled state of the select. */\n readonly disabled = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Store the form disabled state */\n protected readonly formDisabled = signal(false);\n\n /** The on change callback */\n private onChange?: ChangeFn<string>;\n\n /** The on touch callback */\n protected onTouched?: TouchedFn;\n\n writeValue(value: string | undefined): void {\n this.value.set(value);\n }\n\n registerOnChange(fn: ChangeFn<string | undefined>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n protected onValueChange(value: string): void {\n this.onChange?.(value);\n }\n}\n",
1468
+ "code": "import { Component, input } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroChevronDown } from '@ng-icons/heroicons/outline';\nimport {\n injectSelectState,\n NgpSelect,\n NgpSelectDropdown,\n NgpSelectOption,\n NgpSelectPortal,\n} from 'ng-primitives/select';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-select',\n hostDirectives: [\n {\n directive: NgpSelect,\n inputs: ['ngpSelectValue:value', 'ngpSelectMultiple:multiple', 'ngpSelectDisabled:disabled'],\n outputs: ['ngpSelectValueChange:valueChange'],\n },\n ],\n providers: [provideIcons({ heroChevronDown }), provideValueAccessor(Select)],\n imports: [NgpSelectDropdown, NgpSelectOption, NgpSelectPortal, NgIcon],\n template: `\n @if (state().value(); as value) {\n <span class=\"select-value\">{{ value }}</span>\n } @else {\n <span class=\"select-placeholder\">{{ placeholder() }}</span>\n }\n\n <ng-icon name=\"heroChevronDown\" />\n\n <div *ngpSelectPortal ngpSelectDropdown>\n @for (option of options(); track option) {\n <div [ngpSelectOptionValue]=\"option\" ngpSelectOption>\n {{ option }}\n </div>\n } @empty {\n <div class=\"empty-message\">No options found</div>\n }\n </div>\n `,\n styles: `\n :host {\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: 36px;\n width: 300px;\n border-radius: 8px;\n border: none;\n background-color: var(--ngp-background);\n box-shadow: var(--ngp-input-shadow);\n box-sizing: border-box;\n }\n\n :host[data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n .select-value,\n .select-placeholder {\n display: flex;\n align-items: center;\n flex: 1;\n padding: 0 16px;\n background-color: transparent;\n color: var(--ngp-text-primary);\n font-family: inherit;\n font-size: 14px;\n padding: 0 16px;\n height: 100%;\n }\n\n .select-placeholder {\n color: var(--ngp-text-secondary);\n }\n\n ng-icon {\n display: inline-flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n margin-inline: 8px;\n font-size: 14px;\n }\n\n [ngpSelectDropdown] {\n background-color: var(--ngp-background);\n border: 1px solid var(--ngp-border);\n padding: 0.25rem;\n border-radius: 0.75rem;\n outline: none;\n position: absolute;\n animation: popover-show 0.1s ease-out;\n width: var(--ngp-select-width);\n box-shadow: var(--ngp-shadow-lg);\n box-sizing: border-box;\n margin-top: 4px;\n max-height: 240px;\n overflow-y: auto;\n transform-origin: var(--ngp-select-transform-origin);\n }\n\n [ngpSelectDropdown][data-enter] {\n animation: select-show 0.1s ease-out;\n }\n\n [ngpSelectDropdown][data-exit] {\n animation: select-hide 0.1s ease-out;\n }\n\n [ngpSelectOption] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.375rem 0.75rem;\n cursor: pointer;\n border-radius: 0.5rem;\n width: 100%;\n height: 36px;\n font-size: 14px;\n color: var(--ngp-text-primary);\n box-sizing: border-box;\n }\n\n [ngpSelectOption][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n [ngpSelectOption][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n [ngpSelectOption][data-active] {\n background-color: var(--ngp-background-active);\n }\n\n .empty-message {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 0.5rem;\n color: var(--ngp-text-secondary);\n font-size: 14px;\n font-weight: 500;\n text-align: center;\n }\n\n @keyframes select-show {\n 0% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n 100% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n @keyframes select-hide {\n 0% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n 100% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n }\n `,\n host: {\n '[attr.aria-label]': 'ariaLabel() || null',\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Select implements ControlValueAccessor {\n /** Access the underlying select primitive state. */\n protected readonly state = injectSelectState<string>();\n\n /** The options for the select. */\n readonly options = input<string[]>([]);\n\n /** The placeholder for the input. */\n readonly placeholder = input<string>('');\n\n /** The accessible label for the select trigger. */\n readonly ariaLabel = input<string>('');\n\n /** Form change callback. */\n private onChange?: ChangeFn<string | undefined>;\n\n /** Form touched callback. */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Forward primitive value changes (user-driven selections) to the form.\n this.state()\n .valueChange.pipe(takeUntilDestroyed())\n .subscribe(value => this.onChange?.(value as string | undefined));\n }\n\n writeValue(value: string | undefined): void {\n // Pass { emit: false } so writeValue doesn't bounce back through onChange.\n this.state().setValue(value, { emit: false });\n }\n\n registerOnChange(fn: ChangeFn<string | undefined>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.state().setDisabled(isDisabled);\n }\n}\n",
1412
1469
  "hasVariants": false,
1413
1470
  "hasSizes": true
1414
1471
  }
@@ -1686,17 +1743,7 @@
1686
1743
  },
1687
1744
  {
1688
1745
  "name": "example-2",
1689
- "code": "import { provideToastConfig } from 'ng-primitives/toast';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideToastConfig({\n sequential: true,\n // ... other config options\n }),\n ],\n});",
1690
- "description": "Sequential Mode"
1691
- },
1692
- {
1693
- "name": "example-3",
1694
- "code": "import { NgpToastManager } from 'ng-primitives/toast';\n\nexport class MyComponent {\n private readonly toastManager = inject(NgpToastManager);\n\n showToast(): void {\n this.toastManager.show(ToastComponent, {\n sequential: true,\n });\n }\n}",
1695
- "description": "Sequential Mode"
1696
- },
1697
- {
1698
- "name": "example-4",
1699
- "code": "import { provideToastConfig } from 'ng-primitives/toast';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideToastConfig({\n placement: 'top-end',\n duration: 5000,\n offsetTop: 16,\n offsetBottom: 16,\n offsetLeft: 16,\n offsetRight: 16,\n dismissible: true,\n maxToasts: 3,\n zIndex: 9999999,\n swipeDirections: ['left', 'right'],\n ariaLive: 'assertive',\n gap: 16,\n }),\n ],\n});",
1746
+ "code": "import { provideToastConfig } from 'ng-primitives/toast';\n\nbootstrapApplication(AppComponent, {\n providers: [\n provideToastConfig({\n placement: 'top-end',\n duration: 5000,\n offsetTop: 16,\n offsetBottom: 16,\n offsetLeft: 16,\n offsetRight: 16,\n dismissible: true,\n maxToasts: 3,\n zIndex: 9999999,\n swipeDirections: ['left', 'right'],\n ariaLive: 'assertive',\n gap: 16,\n persistent: false,\n }),\n ],\n});",
1700
1747
  "description": "Global Configuration"
1701
1748
  }
1702
1749
  ],
@@ -1822,10 +1869,26 @@
1822
1869
  "ngpTooltipArrow",
1823
1870
  "provideTooltipArrowState",
1824
1871
  "NgpTooltipTrigger",
1825
- "type NgpTooltipPlacement",
1872
+ "NgpTooltip",
1873
+ "NgpTooltipStateToken",
1874
+ "ngpTooltip",
1875
+ "injectTooltipState",
1876
+ "provideTooltipState",
1877
+ "type NgpTooltipState",
1878
+ "type NgpTooltipProps",
1879
+ "NgpTooltipTextContentStateToken",
1880
+ "ngpTooltipTextContent",
1881
+ "injectTooltipTextContentState",
1882
+ "provideTooltipTextContentState",
1883
+ "type NgpTooltipTextContentState",
1884
+ "type NgpTooltipTextContentProps",
1885
+ "NgpTooltipTriggerStateToken",
1886
+ "ngpTooltipTrigger",
1826
1887
  "injectTooltipTriggerState",
1827
1888
  "provideTooltipTriggerState",
1828
- "NgpTooltip"
1889
+ "type NgpTooltipTriggerState",
1890
+ "type NgpTooltipTriggerProps",
1891
+ "type NgpTooltipPlacement"
1829
1892
  ],
1830
1893
  "hasSecondaryEntryPoint": true,
1831
1894
  "category": "feedback",
@@ -162,7 +162,7 @@
162
162
  },
163
163
  {
164
164
  "name": "select",
165
- "code": "import { BooleanInput } from '@angular/cdk/coercion';\nimport { booleanAttribute, Component, input, model, signal } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroChevronDown } from '@ng-icons/heroicons/outline';\nimport {\n NgpSelect,\n NgpSelectDropdown,\n NgpSelectOption,\n NgpSelectPortal,\n} from 'ng-primitives/select';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-select',\n imports: [NgpSelect, NgpSelectDropdown, NgpSelectOption, NgpSelectPortal, NgIcon],\n providers: [provideIcons({ heroChevronDown }), provideValueAccessor(Select)],\n template: `\n <div\n [(ngpSelectValue)]=\"value\"\n [ngpSelectDisabled]=\"disabled() || formDisabled()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n (ngpSelectValueChange)=\"onValueChange($event)\"\n ngpSelect\n >\n @if (value(); as value) {\n <span class=\"select-value\">{{ value }}</span>\n } @else {\n <span class=\"select-placeholder\">{{ placeholder() }}</span>\n }\n\n <ng-icon name=\"heroChevronDown\" />\n\n <div *ngpSelectPortal ngpSelectDropdown>\n @for (option of options(); track option) {\n <div [ngpSelectOptionValue]=\"option\" ngpSelectOption>\n {{ option }}\n </div>\n } @empty {\n <div class=\"empty-message\">No options found</div>\n }\n </div>\n </div>\n `,\n styles: `\n [ngpSelect] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: 36px;\n width: 300px;\n border-radius: 8px;\n border: none;\n background-color: var(--ngp-background);\n box-shadow: var(--ngp-input-shadow);\n box-sizing: border-box;\n }\n\n [ngpSelect][data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n .select-value,\n .select-placeholder {\n display: flex;\n align-items: center;\n flex: 1;\n padding: 0 16px;\n background-color: transparent;\n color: var(--ngp-text-primary);\n font-family: inherit;\n font-size: 14px;\n padding: 0 16px;\n height: 100%;\n }\n\n .select-placeholder {\n color: var(--ngp-text-secondary);\n }\n\n ng-icon {\n display: inline-flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n margin-inline: 8px;\n font-size: 14px;\n }\n\n [ngpSelectDropdown] {\n background-color: var(--ngp-background);\n border: 1px solid var(--ngp-border);\n padding: 0.25rem;\n border-radius: 0.75rem;\n outline: none;\n position: absolute;\n animation: popover-show 0.1s ease-out;\n width: var(--ngp-select-width);\n box-shadow: var(--ngp-shadow-lg);\n box-sizing: border-box;\n margin-top: 4px;\n max-height: 240px;\n overflow-y: auto;\n transform-origin: var(--ngp-select-transform-origin);\n }\n\n [ngpSelectDropdown][data-enter] {\n animation: select-show 0.1s ease-out;\n }\n\n [ngpSelectDropdown][data-exit] {\n animation: select-hide 0.1s ease-out;\n }\n\n [ngpSelectOption] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.375rem 0.75rem;\n cursor: pointer;\n border-radius: 0.5rem;\n width: 100%;\n height: 36px;\n font-size: 14px;\n color: var(--ngp-text-primary);\n box-sizing: border-box;\n }\n\n [ngpSelectOption][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n [ngpSelectOption][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n [ngpSelectOption][data-active] {\n background-color: var(--ngp-background-active);\n }\n\n .empty-message {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 0.5rem;\n color: var(--ngp-text-secondary);\n font-size: 14px;\n font-weight: 500;\n text-align: center;\n }\n\n @keyframes select-show {\n 0% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n 100% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n @keyframes select-hide {\n 0% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n 100% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n }\n `,\n})\nexport class Select implements ControlValueAccessor {\n /** The options for the select. */\n readonly options = input<string[]>([]);\n\n /** The selected value. */\n readonly value = model<string | undefined>();\n\n /** The placeholder for the input. */\n readonly placeholder = input<string>('');\n\n /** The accessible label for the select trigger. */\n readonly ariaLabel = input<string>('');\n\n /** The disabled state of the select. */\n readonly disabled = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** Store the form disabled state */\n protected readonly formDisabled = signal(false);\n\n /** The on change callback */\n private onChange?: ChangeFn<string>;\n\n /** The on touch callback */\n protected onTouched?: TouchedFn;\n\n writeValue(value: string | undefined): void {\n this.value.set(value);\n }\n\n registerOnChange(fn: ChangeFn<string | undefined>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.formDisabled.set(isDisabled);\n }\n\n protected onValueChange(value: string): void {\n this.onChange?.(value);\n }\n}\n",
165
+ "code": "import { Component, input } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroChevronDown } from '@ng-icons/heroicons/outline';\nimport {\n injectSelectState,\n NgpSelect,\n NgpSelectDropdown,\n NgpSelectOption,\n NgpSelectPortal,\n} from 'ng-primitives/select';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-select',\n hostDirectives: [\n {\n directive: NgpSelect,\n inputs: ['ngpSelectValue:value', 'ngpSelectMultiple:multiple', 'ngpSelectDisabled:disabled'],\n outputs: ['ngpSelectValueChange:valueChange'],\n },\n ],\n providers: [provideIcons({ heroChevronDown }), provideValueAccessor(Select)],\n imports: [NgpSelectDropdown, NgpSelectOption, NgpSelectPortal, NgIcon],\n template: `\n @if (state().value(); as value) {\n <span class=\"select-value\">{{ value }}</span>\n } @else {\n <span class=\"select-placeholder\">{{ placeholder() }}</span>\n }\n\n <ng-icon name=\"heroChevronDown\" />\n\n <div *ngpSelectPortal ngpSelectDropdown>\n @for (option of options(); track option) {\n <div [ngpSelectOptionValue]=\"option\" ngpSelectOption>\n {{ option }}\n </div>\n } @empty {\n <div class=\"empty-message\">No options found</div>\n }\n </div>\n `,\n styles: `\n :host {\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: 36px;\n width: 300px;\n border-radius: 8px;\n border: none;\n background-color: var(--ngp-background);\n box-shadow: var(--ngp-input-shadow);\n box-sizing: border-box;\n }\n\n :host[data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n .select-value,\n .select-placeholder {\n display: flex;\n align-items: center;\n flex: 1;\n padding: 0 16px;\n background-color: transparent;\n color: var(--ngp-text-primary);\n font-family: inherit;\n font-size: 14px;\n padding: 0 16px;\n height: 100%;\n }\n\n .select-placeholder {\n color: var(--ngp-text-secondary);\n }\n\n ng-icon {\n display: inline-flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n margin-inline: 8px;\n font-size: 14px;\n }\n\n [ngpSelectDropdown] {\n background-color: var(--ngp-background);\n border: 1px solid var(--ngp-border);\n padding: 0.25rem;\n border-radius: 0.75rem;\n outline: none;\n position: absolute;\n animation: popover-show 0.1s ease-out;\n width: var(--ngp-select-width);\n box-shadow: var(--ngp-shadow-lg);\n box-sizing: border-box;\n margin-top: 4px;\n max-height: 240px;\n overflow-y: auto;\n transform-origin: var(--ngp-select-transform-origin);\n }\n\n [ngpSelectDropdown][data-enter] {\n animation: select-show 0.1s ease-out;\n }\n\n [ngpSelectDropdown][data-exit] {\n animation: select-hide 0.1s ease-out;\n }\n\n [ngpSelectOption] {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.375rem 0.75rem;\n cursor: pointer;\n border-radius: 0.5rem;\n width: 100%;\n height: 36px;\n font-size: 14px;\n color: var(--ngp-text-primary);\n box-sizing: border-box;\n }\n\n [ngpSelectOption][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n [ngpSelectOption][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n [ngpSelectOption][data-active] {\n background-color: var(--ngp-background-active);\n }\n\n .empty-message {\n display: flex;\n justify-content: center;\n align-items: center;\n padding: 0.5rem;\n color: var(--ngp-text-secondary);\n font-size: 14px;\n font-weight: 500;\n text-align: center;\n }\n\n @keyframes select-show {\n 0% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n 100% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n @keyframes select-hide {\n 0% {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n 100% {\n opacity: 0;\n transform: translateY(-10px) scale(0.9);\n }\n }\n `,\n host: {\n '[attr.aria-label]': 'ariaLabel() || null',\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Select implements ControlValueAccessor {\n /** Access the underlying select primitive state. */\n protected readonly state = injectSelectState<string>();\n\n /** The options for the select. */\n readonly options = input<string[]>([]);\n\n /** The placeholder for the input. */\n readonly placeholder = input<string>('');\n\n /** The accessible label for the select trigger. */\n readonly ariaLabel = input<string>('');\n\n /** Form change callback. */\n private onChange?: ChangeFn<string | undefined>;\n\n /** Form touched callback. */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Forward primitive value changes (user-driven selections) to the form.\n this.state()\n .valueChange.pipe(takeUntilDestroyed())\n .subscribe(value => this.onChange?.(value as string | undefined));\n }\n\n writeValue(value: string | undefined): void {\n // Pass { emit: false } so writeValue doesn't bounce back through onChange.\n this.state().setValue(value, { emit: false });\n }\n\n registerOnChange(fn: ChangeFn<string | undefined>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.state().setDisabled(isDisabled);\n }\n}\n",
166
166
  "primitive": "select",
167
167
  "hasVariants": false,
168
168
  "hasSizes": true