@ng-primitives/mcp 0.120.3 → 0.120.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-primitives/mcp",
3
- "version": "0.120.3",
3
+ "version": "0.120.5",
4
4
  "description": "MCP server for Angular Primitives - headless UI library",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -2560,6 +2560,16 @@
2560
2560
  ],
2561
2561
  "outputs": []
2562
2562
  },
2563
+ "NgpPopover": {
2564
+ "name": "NgpPopover",
2565
+ "description": "Apply the `ngpPopover` directive to an element that represents the popover. This typically would be a `div` inside an `ng-template`.",
2566
+ "selector": "[ngpPopover]",
2567
+ "exportAs": [
2568
+ "ngpPopover"
2569
+ ],
2570
+ "inputs": [],
2571
+ "outputs": []
2572
+ },
2563
2573
  "NgpPopoverArrow": {
2564
2574
  "name": "NgpPopoverArrow",
2565
2575
  "description": "",
@@ -2577,16 +2587,6 @@
2577
2587
  ],
2578
2588
  "outputs": []
2579
2589
  },
2580
- "NgpPopover": {
2581
- "name": "NgpPopover",
2582
- "description": "Apply the `ngpPopover` directive to an element that represents the popover. This typically would be a `div` inside an `ng-template`.",
2583
- "selector": "[ngpPopover]",
2584
- "exportAs": [
2585
- "ngpPopover"
2586
- ],
2587
- "inputs": [],
2588
- "outputs": []
2589
- },
2590
2590
  "NgpPopoverTrigger": {
2591
2591
  "name": "NgpPopoverTrigger",
2592
2592
  "description": "Apply the `ngpPopoverTrigger` directive to an element that triggers the popover to show.",
@@ -2952,22 +2952,22 @@
2952
2952
  "inputs": [],
2953
2953
  "outputs": []
2954
2954
  },
2955
- "NgpSelectDropdown": {
2956
- "name": "NgpSelectDropdown",
2955
+ "NgpSelectPortal": {
2956
+ "name": "NgpSelectPortal",
2957
2957
  "description": "",
2958
- "selector": "[ngpSelectDropdown]",
2958
+ "selector": "[ngpSelectPortal]",
2959
2959
  "exportAs": [
2960
- "ngpSelectDropdown"
2960
+ "ngpSelectPortal"
2961
2961
  ],
2962
2962
  "inputs": [],
2963
2963
  "outputs": []
2964
2964
  },
2965
- "NgpSelectPortal": {
2966
- "name": "NgpSelectPortal",
2965
+ "NgpSelectDropdown": {
2966
+ "name": "NgpSelectDropdown",
2967
2967
  "description": "",
2968
- "selector": "[ngpSelectPortal]",
2968
+ "selector": "[ngpSelectDropdown]",
2969
2969
  "exportAs": [
2970
- "ngpSelectPortal"
2970
+ "ngpSelectDropdown"
2971
2971
  ],
2972
2972
  "inputs": [],
2973
2973
  "outputs": []
@@ -4511,6 +4511,12 @@
4511
4511
  "type": "boolean",
4512
4512
  "description": "When true, only the front toast's timer will run. When a toast is dismissed, the timer will start on the next toast.",
4513
4513
  "default": "false"
4514
+ },
4515
+ {
4516
+ "name": "persistent",
4517
+ "type": "boolean",
4518
+ "description": "When true, toasts will not auto-dismiss and must be dismissed explicitly.",
4519
+ "default": "false"
4514
4520
  }
4515
4521
  ]
4516
4522
  },
@@ -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",
@@ -1107,10 +1105,20 @@
1107
1105
  "ngpPopoverArrow",
1108
1106
  "providePopoverArrowState",
1109
1107
  "NgpPopoverTrigger",
1110
- "type NgpPopoverPlacement",
1108
+ "NgpPopover",
1109
+ "type NgpPopoverProps",
1110
+ "type NgpPopoverState",
1111
+ "NgpPopoverStateToken",
1112
+ "ngpPopover",
1113
+ "injectPopoverState",
1114
+ "providePopoverState",
1115
+ "NgpPopoverTriggerStateToken",
1116
+ "ngpPopoverTrigger",
1111
1117
  "injectPopoverTriggerState",
1112
1118
  "providePopoverTriggerState",
1113
- "NgpPopover"
1119
+ "type NgpPopoverTriggerState",
1120
+ "type NgpPopoverTriggerProps",
1121
+ "type NgpPopoverPlacement"
1114
1122
  ],
1115
1123
  "hasSecondaryEntryPoint": true,
1116
1124
  "category": "feedback",
@@ -1369,10 +1377,24 @@
1369
1377
  "injectNativeSelectState",
1370
1378
  "provideNativeSelectState",
1371
1379
  "NgpSelectDropdown",
1380
+ "injectSelectDropdownState",
1381
+ "ngpSelectDropdown",
1382
+ "NgpSelectDropdownStateToken",
1383
+ "provideSelectDropdownState",
1372
1384
  "NgpSelectOption",
1385
+ "injectSelectOptionState",
1386
+ "ngpSelectOption",
1387
+ "NgpSelectOptionStateToken",
1388
+ "provideSelectOptionState",
1373
1389
  "NgpSelectPortal",
1390
+ "injectSelectPortalState",
1391
+ "ngpSelectPortal",
1392
+ "NgpSelectPortalStateToken",
1393
+ "provideSelectPortalState",
1374
1394
  "NgpSelect",
1375
1395
  "injectSelectState",
1396
+ "ngpSelect",
1397
+ "NgpSelectStateToken",
1376
1398
  "provideSelectState",
1377
1399
  "NgpSelectConfig",
1378
1400
  "provideSelectConfig",
@@ -1408,7 +1430,7 @@
1408
1430
  }
1409
1431
  ],
1410
1432
  "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",
1433
+ "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
1434
  "hasVariants": false,
1413
1435
  "hasSizes": true
1414
1436
  }
@@ -1686,17 +1708,7 @@
1686
1708
  },
1687
1709
  {
1688
1710
  "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});",
1711
+ "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
1712
  "description": "Global Configuration"
1701
1713
  }
1702
1714
  ],
@@ -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