@ng-primitives/mcp 0.95.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.
@@ -0,0 +1,226 @@
1
+ [
2
+ {
3
+ "name": "accordion",
4
+ "code": "import { Component, input } from '@angular/core';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroChevronDownMini } from '@ng-icons/heroicons/mini';\nimport {\n NgpAccordionContent,\n NgpAccordionItem,\n NgpAccordionTrigger,\n} from 'ng-primitives/accordion';\nimport { NgpButton } from 'ng-primitives/button';\n\n@Component({\n selector: 'app-accordion-item',\n hostDirectives: [\n {\n directive: NgpAccordionItem,\n inputs: ['ngpAccordionItemValue:value', 'ngpAccordionItemDisabled:disabled'],\n },\n ],\n imports: [NgpAccordionContent, NgpAccordionTrigger, NgpButton, NgIcon],\n providers: [provideIcons({ heroChevronDownMini })],\n template: `\n <button ngpAccordionTrigger ngpButton>\n {{ heading() }}\n\n <ng-icon name=\"heroChevronDownMini\" />\n </button>\n <div ngpAccordionContent>\n <ng-content />\n </div>\n `,\n styles: `\n :host {\n display: block;\n }\n\n :host:has(+ :host) {\n border-bottom: 1px solid var(--ngp-border);\n }\n\n [ngpAccordionTrigger] {\n display: flex;\n padding-left: 1rem;\n padding-right: 1rem;\n font-size: 0.875rem;\n line-height: 1.25rem;\n font-weight: 500;\n justify-content: space-between;\n align-items: center;\n width: 100%;\n height: 2.75rem;\n border-radius: 0.75rem;\n outline: none;\n color: var(--ngp-text-primary);\n background-color: var(--ngp-background);\n border: none;\n }\n\n [ngpAccordionTrigger][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n [ngpAccordionContent] {\n font-size: 0.875rem;\n color: var(--ngp-text-secondary);\n overflow: hidden;\n padding: 0 16px;\n box-sizing: border-box;\n }\n\n [ngpAccordionContent][data-open] {\n animation: slideDown 0.2s ease-in-out forwards;\n }\n\n [ngpAccordionContent][data-closed] {\n animation: slideUp 0.2s ease-in-out forwards;\n }\n\n ng-icon {\n font-size: 1.25rem;\n color: var(--ngp-text-secondary);\n }\n\n [ngpAccordionTrigger][data-open] ng-icon {\n transform: rotate(180deg);\n }\n\n @keyframes slideDown {\n from {\n height: 0;\n }\n to {\n height: var(--ngp-accordion-content-height);\n }\n }\n\n @keyframes slideUp {\n from {\n height: var(--ngp-accordion-content-height);\n }\n to {\n height: 0;\n }\n }\n `,\n})\nexport class AccordionItem {\n /** The accordion item heading */\n readonly heading = input.required<string>();\n}\n",
5
+ "primitive": "accordion",
6
+ "hasVariants": false,
7
+ "hasSizes": true
8
+ },
9
+ {
10
+ "name": "avatar",
11
+ "code": "import { Component, input } from '@angular/core';\nimport { NgpAvatar, NgpAvatarFallback, NgpAvatarImage } from 'ng-primitives/avatar';\n\n@Component({\n selector: 'app-avatar',\n hostDirectives: [NgpAvatar],\n imports: [NgpAvatarImage, NgpAvatarFallback],\n template: `\n @if (image()) {\n <img [src]=\"image()\" ngpAvatarImage alt=\"Profile Image\" />\n }\n <span ngpAvatarFallback>{{ fallback() }}</span>\n `,\n styles: `\n :host {\n position: relative;\n display: inline-flex;\n width: 3rem;\n height: 3rem;\n align-items: center;\n justify-content: center;\n border-radius: 9999px;\n border-width: 2px;\n border-color: var(--ngp-avatar-border);\n background-color: var(--ngp-avatar-background);\n vertical-align: middle;\n overflow: hidden;\n }\n\n :host:before {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: 9999px;\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);\n }\n\n [ngpAvatarImage] {\n width: 100%;\n height: 100%;\n }\n\n [ngpAvatarFallback] {\n text-align: center;\n font-weight: 500;\n color: var(--ngp-text-emphasis);\n }\n `,\n})\nexport class Avatar {\n /** Define the avatar image source */\n readonly image = input<string>();\n\n /** Define the avatar fallback text */\n readonly fallback = input<string>();\n}\n",
12
+ "primitive": "avatar",
13
+ "hasVariants": false,
14
+ "hasSizes": false
15
+ },
16
+ {
17
+ "name": "button",
18
+ "code": "import { Component, input } from '@angular/core';\nimport { NgpButton } from 'ng-primitives/button';\n\n/**\n * The size of the button.\n */\nexport type ButtonSize = 'sm' | 'md' | 'lg' | 'xl';\n\n/**\n * The variant of the button.\n */\nexport type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'outline' | 'ghost' | 'link';\n\n@Component({\n selector: 'button[app-button]',\n hostDirectives: [{ directive: NgpButton, inputs: ['disabled'] }],\n template: `\n <ng-content select=\"[slot=leading]\" />\n <ng-content />\n <ng-content select=\"[slot=trailing]\" />\n `,\n host: {\n '[attr.data-size]': 'size()',\n '[attr.data-variant]': 'variant()',\n },\n styles: `\n :host {\n padding-left: 1rem;\n padding-right: 1rem;\n border-radius: 0.5rem;\n color: var(--ngp-text-primary);\n border: none;\n height: 2.5rem;\n font-weight: 500;\n background-color: var(--ngp-background);\n transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: var(--ngp-button-shadow);\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n gap: 0.5rem;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n /* Size variants */\n :host[data-size='sm'] {\n height: 2rem;\n padding-left: 0.75rem;\n padding-right: 0.75rem;\n font-size: 0.875rem;\n --ng-icon__size: 0.875rem;\n }\n\n :host[data-size='md'],\n :host:not([data-size]) {\n height: 2.5rem;\n padding-left: 1rem;\n padding-right: 1rem;\n font-size: 0.875rem;\n --ng-icon__size: 0.875rem;\n }\n\n :host[data-size='lg'] {\n height: 3rem;\n padding-left: 1.25rem;\n padding-right: 1.25rem;\n font-size: 1rem;\n --ng-icon__size: 1rem;\n }\n\n :host[data-size='xl'] {\n height: 3.5rem;\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n font-size: 1.125rem;\n --ng-icon__size: 1.125rem;\n }\n\n /* Variant styles */\n :host[data-variant='primary'],\n :host:not([data-variant]) {\n background-color: var(--ngp-background);\n color: var(--ngp-text-primary);\n border: none;\n }\n\n :host[data-variant='primary'][data-hover],\n :host:not([data-variant])[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-variant='primary'][data-press],\n :host:not([data-variant])[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-variant='secondary'] {\n background-color: var(--ngp-secondary-background, #f1f5f9);\n color: var(--ngp-secondary-text, #0f172a);\n border: none;\n }\n\n :host[data-variant='secondary'][data-hover] {\n background-color: var(--ngp-secondary-background-hover, #e2e8f0);\n }\n\n :host[data-variant='secondary'][data-press] {\n background-color: var(--ngp-secondary-background-active, #cbd5e1);\n }\n\n :host[data-variant='destructive'] {\n background-color: var(--ngp-destructive-background, #ef4444);\n color: var(--ngp-destructive-text, #ffffff);\n border: none;\n }\n\n :host[data-variant='destructive'][data-hover] {\n background-color: var(--ngp-destructive-background-hover, #dc2626);\n }\n\n :host[data-variant='destructive'][data-press] {\n background-color: var(--ngp-destructive-background-active, #b91c1c);\n }\n\n :host[data-variant='outline'] {\n background-color: transparent;\n color: var(--ngp-text-primary);\n border: 1px solid var(--ngp-outline-border, #e2e8f0);\n box-shadow: none;\n }\n\n :host[data-variant='outline'][data-hover] {\n background-color: var(--ngp-background-hover);\n border-color: var(--ngp-outline-border-hover, #cbd5e1);\n }\n\n :host[data-variant='outline'][data-press] {\n background-color: var(--ngp-outline-background-active, rgba(15, 23, 42, 0.1));\n }\n\n :host[data-variant='ghost'] {\n background-color: transparent;\n color: var(--ngp-text-primary);\n border: none;\n box-shadow: none;\n }\n\n :host[data-variant='ghost'][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-variant='ghost'][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-variant='link'] {\n background-color: transparent;\n color: var(--ngp-link-color, #3b82f6);\n border: none;\n box-shadow: none;\n text-decoration: underline;\n text-underline-offset: 4px;\n }\n\n :host[data-variant='link'][data-hover] {\n text-decoration-thickness: 2px;\n }\n\n :host[disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n }\n `,\n})\nexport class Button {\n /**\n * The size of the button.\n */\n readonly size = input<ButtonSize>('md');\n\n /**\n * The variant of the button.\n */\n readonly variant = input<ButtonVariant>('primary');\n}\n",
19
+ "primitive": "button",
20
+ "hasVariants": true,
21
+ "hasSizes": true
22
+ },
23
+ {
24
+ "name": "checkbox",
25
+ "code": "import { Component } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroCheckMini, heroMinusMini } from '@ng-icons/heroicons/mini';\nimport { injectCheckboxState, NgpCheckbox } from 'ng-primitives/checkbox';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-checkbox',\n hostDirectives: [\n {\n directive: NgpCheckbox,\n inputs: [\n 'ngpCheckboxChecked:checked',\n 'ngpCheckboxIndeterminate:indeterminate',\n 'ngpCheckboxDisabled:disabled',\n ],\n outputs: [\n 'ngpCheckboxCheckedChange:checkedChange',\n 'ngpCheckboxIndeterminateChange:indeterminateChange',\n ],\n },\n ],\n providers: [provideValueAccessor(Checkbox), provideIcons({ heroCheckMini, heroMinusMini })],\n imports: [NgIcon],\n template: `\n @if (state().indeterminate()) {\n <ng-icon name=\"heroMinusMini\" />\n } @else if (state().checked()) {\n <ng-icon name=\"heroCheckMini\" />\n }\n `,\n styles: `\n :host {\n display: flex;\n width: 1.25rem;\n height: 1.25rem;\n cursor: pointer;\n align-items: center;\n justify-content: center;\n border-radius: 0.25rem;\n border: 1px solid var(--ngp-border);\n background-color: transparent;\n padding: 0;\n outline: none;\n flex: none;\n color: var(--ngp-text-inverse);\n font-size: 0.75rem;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-checked],\n :host[data-indeterminate] {\n border-color: var(--ngp-background-inverse);\n background-color: var(--ngp-background-inverse);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n `,\n host: {\n '(focusout)': 'onTouchedFn?.()',\n },\n})\nexport class Checkbox implements ControlValueAccessor {\n /**\n * The checked state of the checkbox.\n */\n protected readonly state = injectCheckboxState();\n\n /**\n * The onChange function for the checkbox.\n */\n protected onChangeFn?: ChangeFn<boolean>;\n\n /**\n * The onTouched function for the checkbox.\n */\n protected onTouchedFn?: TouchedFn;\n\n constructor() {\n // Whenever the user interacts with the checkbox, call the onChange function with the new value.\n this.state().checkedChange.subscribe(checked => this.onChangeFn?.(checked));\n }\n\n writeValue(checked: boolean): void {\n this.state().setChecked(checked);\n }\n\n registerOnChange(fn: ChangeFn<boolean>): void {\n this.onChangeFn = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouchedFn = fn;\n }\n\n setDisabledState(isDisabled: boolean): void {\n this.state().setDisabled(isDisabled);\n }\n}\n",
26
+ "primitive": "checkbox",
27
+ "hasVariants": false,
28
+ "hasSizes": true
29
+ },
30
+ {
31
+ "name": "combobox",
32
+ "code": "import { BooleanInput } from '@angular/cdk/coercion';\nimport { booleanAttribute, Component, computed, 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 NgpCombobox,\n NgpComboboxButton,\n NgpComboboxDropdown,\n NgpComboboxInput,\n NgpComboboxOption,\n NgpComboboxPortal,\n} from 'ng-primitives/combobox';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-combobox',\n imports: [\n NgpCombobox,\n NgpComboboxDropdown,\n NgpComboboxOption,\n NgpComboboxInput,\n NgpComboboxPortal,\n NgpComboboxButton,\n NgIcon,\n ],\n providers: [provideIcons({ heroChevronDown }), provideValueAccessor(Combobox)],\n template: `\n <div\n [(ngpComboboxValue)]=\"value\"\n [ngpComboboxDisabled]=\"disabled() || formDisabled()\"\n (ngpComboboxOpenChange)=\"resetOnClose($event)\"\n (ngpComboboxValueChange)=\"onValueChange($event)\"\n ngpCombobox\n >\n <input\n [value]=\"filter()\"\n [placeholder]=\"placeholder()\"\n (input)=\"onFilterChange($event)\"\n (blur)=\"onTouched?.()\"\n ngpComboboxInput\n />\n\n <button ngpComboboxButton aria-label=\"Toggle dropdown\">\n <ng-icon name=\"heroChevronDown\" />\n </button>\n\n <div *ngpComboboxPortal ngpComboboxDropdown>\n @for (option of filteredOptions(); track option) {\n <div [ngpComboboxOptionValue]=\"option\" ngpComboboxOption>\n {{ option }}\n </div>\n } @empty {\n <div class=\"empty-message\">No options found</div>\n }\n </div>\n </div>\n `,\n styles: `\n [ngpCombobox] {\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 [ngpCombobox][data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n [ngpComboboxInput] {\n flex: 1;\n padding: 0 16px;\n border: none;\n background-color: transparent;\n color: var(--ngp-text);\n font-family: inherit;\n font-size: 14px;\n padding: 0 16px;\n outline: none;\n height: 100%;\n }\n\n [ngpComboboxButton] {\n display: inline-flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n width: 36px;\n background-color: transparent;\n border: none;\n color: var(--ngp-text);\n cursor: pointer;\n box-sizing: border-box;\n }\n\n [ngpComboboxDropdown] {\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-combobox-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-combobox-transform-origin);\n }\n\n [ngpComboboxDropdown][data-enter] {\n animation: combobox-show 0.1s ease-out;\n }\n\n [ngpComboboxDropdown][data-exit] {\n animation: combobox-hide 0.1s ease-out;\n }\n\n [ngpComboboxOption] {\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 [ngpComboboxOption][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n [ngpComboboxOption][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n [ngpComboboxOption][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 combobox-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 combobox-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 Combobox implements ControlValueAccessor {\n /** The options for the combobox. */\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 disabled state of the combobox. */\n readonly disabled = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /** The filter value. */\n protected readonly filter = signal<string>('');\n\n /** Get the filtered options. */\n protected readonly filteredOptions = computed(() =>\n this.options().filter(option => option.toLowerCase().includes(this.filter().toLowerCase())),\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 onFilterChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n this.filter.set(input.value);\n }\n\n writeValue(value: string | undefined): void {\n this.value.set(value);\n this.filter.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 // update the filter value\n this.filter.set(value);\n }\n\n protected resetOnClose(open: boolean): void {\n // if the dropdown is closed, reset the filter value\n if (open) {\n return;\n }\n\n // if the filter value is empty, set the value to undefined\n if (this.filter() === '') {\n this.value.set(undefined);\n } else {\n // otherwise set the filter value to the selected value\n this.filter.set(this.value() ?? '');\n }\n }\n}\n",
33
+ "primitive": "combobox",
34
+ "hasVariants": false,
35
+ "hasSizes": true
36
+ },
37
+ {
38
+ "name": "date-picker",
39
+ "code": "import { Component, computed } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroChevronLeftMini, heroChevronRightMini } from '@ng-icons/heroicons/mini';\nimport {\n injectDatePickerState,\n NgpDatePicker,\n NgpDatePickerCell,\n NgpDatePickerCellRender,\n NgpDatePickerDateButton,\n NgpDatePickerGrid,\n NgpDatePickerLabel,\n NgpDatePickerNextMonth,\n NgpDatePickerPreviousMonth,\n NgpDatePickerRowRender,\n} from 'ng-primitives/date-picker';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-date-picker',\n hostDirectives: [\n {\n directive: NgpDatePicker,\n inputs: [\n 'ngpDatePickerDate: date',\n 'ngpDatePickerMin: min',\n 'ngpDatePickerMax: max',\n 'ngpDatePickerDisabled: disabled',\n ],\n outputs: ['ngpDatePickerDateChange: dateChange'],\n },\n ],\n imports: [\n NgIcon,\n NgpDatePickerLabel,\n NgpDatePickerNextMonth,\n NgpDatePickerPreviousMonth,\n NgpDatePickerGrid,\n NgpDatePickerCell,\n NgpDatePickerRowRender,\n NgpDatePickerCellRender,\n NgpDatePickerDateButton,\n ],\n providers: [\n provideIcons({ heroChevronRightMini, heroChevronLeftMini }),\n provideValueAccessor(DatePicker),\n ],\n template: `\n <div class=\"date-picker-header\">\n <button ngpDatePickerPreviousMonth aria-label=\"previous month\">\n <ng-icon name=\"heroChevronLeftMini\" />\n </button>\n <h2 ngpDatePickerLabel>{{ label() }}</h2>\n <button ngpDatePickerNextMonth aria-label=\"next month\">\n <ng-icon name=\"heroChevronRightMini\" />\n </button>\n </div>\n <table ngpDatePickerGrid>\n <thead>\n <tr>\n <th scope=\"col\" abbr=\"Sunday\">S</th>\n <th scope=\"col\" abbr=\"Monday\">M</th>\n <th scope=\"col\" abbr=\"Tuesday\">T</th>\n <th scope=\"col\" abbr=\"Wednesday\">W</th>\n <th scope=\"col\" abbr=\"Thursday\">T</th>\n <th scope=\"col\" abbr=\"Friday\">F</th>\n <th scope=\"col\" abbr=\"Saturday\">S</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngpDatePickerRowRender>\n <td *ngpDatePickerCellRender=\"let date\" ngpDatePickerCell>\n <button ngpDatePickerDateButton>\n {{ date.getDate() }}\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n `,\n styles: `\n :host {\n display: inline-block;\n background-color: var(--ngp-background);\n border-radius: 12px;\n padding: 16px;\n box-shadow: var(--ngp-shadow);\n border: 1px solid var(--ngp-border);\n }\n\n .date-picker-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n height: 36px;\n margin-bottom: 16px;\n }\n\n th {\n font-size: 14px;\n font-weight: 500;\n width: 40px;\n height: 40px;\n text-align: center;\n color: var(--ngp-text-secondary);\n }\n\n [ngpDatePickerLabel] {\n font-size: 14px;\n font-weight: 500;\n color: var(--ngp-text-primary);\n }\n\n [ngpDatePickerPreviousMonth],\n [ngpDatePickerNextMonth] {\n all: unset;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n font-size: 20px;\n border: 1px solid var(--ngp-border);\n cursor: pointer;\n }\n\n [ngpDatePickerPreviousMonth][data-hover],\n [ngpDatePickerNextMonth][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n [ngpDatePickerPreviousMonth][data-focus-visible],\n [ngpDatePickerNextMonth][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n [ngpDatePickerPreviousMonth][data-press],\n [ngpDatePickerNextMonth][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n [ngpDatePickerPreviousMonth][data-disabled],\n [ngpDatePickerNextMonth][data-disabled] {\n cursor: not-allowed;\n color: var(--ngp-text-disabled);\n }\n\n [ngpDatePickerDateButton] {\n all: unset;\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n cursor: pointer;\n }\n\n [ngpDatePickerDateButton][data-today] {\n color: var(--ngp-text-blue);\n }\n\n [ngpDatePickerDateButton][data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n [ngpDatePickerDateButton][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n [ngpDatePickerDateButton][data-press] {\n background-color: var(--ngp-background-active);\n }\n\n [ngpDatePickerDateButton][data-outside-month] {\n color: var(--ngp-text-disabled);\n }\n\n [ngpDatePickerDateButton][data-selected] {\n background-color: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n }\n\n [ngpDatePickerDateButton][data-selected][data-outside-month] {\n background-color: var(--ngp-background-disabled);\n color: var(--ngp-text-disabled);\n }\n\n [ngpDatePickerDateButton][data-disabled] {\n cursor: not-allowed;\n color: var(--ngp-text-disabled);\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class DatePicker implements ControlValueAccessor {\n /** Access the date picker host directive */\n private readonly state = injectDatePickerState<Date>();\n\n /**\n * Get the current focused date in string format.\n * @returns The focused date in \"February 2024\" format.\n */\n readonly label = computed(\n () =>\n `${this.state().focusedDate().toLocaleString('default', { month: 'long' })} ${this.state().focusedDate().getFullYear()}`,\n );\n\n /**\n * The onChange callback function for the date picker.\n */\n protected onChange?: ChangeFn<Date | undefined>;\n\n /**\n * The onTouched callback function for the date picker.\n */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Whenever the user interacts with the date picker, call the onChange function with the new value.\n this.state().dateChange.subscribe(date => this.onChange?.(date));\n }\n\n writeValue(date: Date): void {\n this.state().select(date);\n }\n\n registerOnChange(fn: ChangeFn<Date | 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().disabled.set(isDisabled);\n }\n}\n",
40
+ "primitive": "date-picker",
41
+ "hasVariants": false,
42
+ "hasSizes": true
43
+ },
44
+ {
45
+ "name": "dialog",
46
+ "code": "import { Component, input } from '@angular/core';\nimport {\n NgpDialog,\n NgpDialogDescription,\n NgpDialogOverlay,\n NgpDialogTitle,\n provideDialogState,\n} from 'ng-primitives/dialog';\n\n@Component({\n selector: 'app-dialog',\n hostDirectives: [NgpDialogOverlay],\n imports: [NgpDialog, NgpDialogTitle, NgpDialogDescription],\n providers: [\n // We need to hoist the dialog state to the host component so that it can be used\n // within ng-content\n provideDialogState(),\n ],\n template: `\n <div ngpDialog>\n <h2 ngpDialogTitle>{{ header() }}</h2>\n <p ngpDialogDescription>\n <ng-content />\n </p>\n </div>\n `,\n styles: `\n :host {\n background-color: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n position: fixed;\n inset: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n animation: fadeIn 300ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n :host[data-exit] {\n animation: fadeOut 300ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n [ngpDialog] {\n background-color: var(--ngp-background);\n padding: 24px;\n border-radius: 8px;\n box-shadow: var(--ngp-shadow);\n animation: slideIn 300ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n [ngpDialog][data-exit] {\n animation: slideOut 300ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n [ngpDialogTitle] {\n font-size: 18px;\n line-height: 28px;\n font-weight: 600;\n color: var(--ngp-text-primary);\n margin: 0 0 4px;\n }\n\n [ngpDialogDescription] {\n font-size: 14px;\n line-height: 20px;\n color: var(--ngp-text-secondary);\n margin: 0;\n }\n\n @keyframes fadeIn {\n 0% {\n opacity: 0;\n }\n 100% {\n opacity: 1;\n }\n }\n\n @keyframes fadeOut {\n 0% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n }\n }\n\n @keyframes slideIn {\n 0% {\n transform: translateY(-20px);\n opacity: 0;\n }\n 100% {\n transform: translateY(0);\n opacity: 1;\n }\n }\n @keyframes slideOut {\n 0% {\n transform: translateY(0);\n opacity: 1;\n }\n\n 100% {\n transform: translateY(-20px);\n opacity: 0;\n }\n }\n `,\n})\nexport class Dialog {\n /** The dialog title */\n readonly header = input.required<string>();\n}\n",
47
+ "primitive": "dialog",
48
+ "hasVariants": false,
49
+ "hasSizes": true
50
+ },
51
+ {
52
+ "name": "file-upload",
53
+ "code": "import { Component } from '@angular/core';\nimport { NgpFileUpload } from 'ng-primitives/file-upload';\n\n@Component({\n selector: 'app-file-upload',\n hostDirectives: [\n {\n directive: NgpFileUpload,\n inputs: [\n 'ngpFileUploadFileTypes:types',\n 'ngpFileUploadMultiple:multiple',\n 'ngpFileUploadDirectory:directory',\n 'ngpFileUploadDragDrop:dragDrop',\n 'ngpFileUploadDisabled:disabled',\n ],\n outputs: ['ngpFileUploadSelected:selected', 'ngpFileUploadCanceled:canceled'],\n },\n ],\n template: `\n Drop files here or click to upload\n `,\n styles: `\n :host {\n display: flex;\n cursor: pointer;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n row-gap: 0.25rem;\n border-radius: 0.5rem;\n border: 1px dashed var(--ngp-border-secondary);\n background-color: var(--ngp-background);\n padding: 2rem 3rem;\n }\n\n :host[data-dragover] {\n border-color: var(--ngp-border);\n background-color: var(--ngp-background-hover);\n }\n `,\n})\nexport class FileUpload {}\n",
54
+ "primitive": "file-upload",
55
+ "hasVariants": false,
56
+ "hasSizes": false
57
+ },
58
+ {
59
+ "name": "form-field",
60
+ "code": "import { Component } from '@angular/core';\nimport { NgpFormField } from 'ng-primitives/form-field';\n\n@Component({\n selector: 'app-form-field',\n hostDirectives: [NgpFormField],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n display: flex;\n flex-direction: column;\n gap: 6px;\n width: 90%;\n }\n `,\n})\nexport class FormField {}\n",
61
+ "primitive": "form-field",
62
+ "hasVariants": false,
63
+ "hasSizes": false
64
+ },
65
+ {
66
+ "name": "input",
67
+ "code": "import { Component } from '@angular/core';\nimport { NgpInput } from 'ng-primitives/input';\n\n@Component({\n selector: 'input[app-input]',\n hostDirectives: [{ directive: NgpInput, inputs: ['disabled'] }],\n template: '',\n styles: `\n :host {\n height: 36px;\n width: 100%;\n border-radius: 8px;\n padding: 0 16px;\n border: none;\n box-shadow: var(--ngp-input-shadow);\n outline: none;\n box-sizing: border-box;\n }\n\n :host:focus {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n :host::placeholder {\n color: var(--ngp-text-placeholder);\n }\n `,\n})\nexport class Input {}\n",
68
+ "primitive": "input",
69
+ "hasVariants": false,
70
+ "hasSizes": false
71
+ },
72
+ {
73
+ "name": "input-otp",
74
+ "code": "import { BooleanInput, NumberInput } from '@angular/cdk/coercion';\nimport {\n booleanAttribute,\n Component,\n computed,\n forwardRef,\n input,\n model,\n numberAttribute,\n signal,\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\nimport { NgpInputOtp, NgpInputOtpInput, NgpInputOtpSlot } from 'ng-primitives/input-otp';\nimport { ChangeFn, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-input-otp',\n imports: [NgpInputOtp, NgpInputOtpInput, NgpInputOtpSlot],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => InputOtp),\n multi: true,\n },\n ],\n template: `\n <div\n [ngpInputOtpValue]=\"value()\"\n [ngpInputOtpDisabled]=\"disabled() || formDisabled()\"\n [ngpInputOtpPattern]=\"pattern()\"\n [ngpInputOtpPlaceholder]=\"placeholder()\"\n [ngpInputOtpInputMode]=\"inputMode()\"\n (ngpInputOtpValueChange)=\"onValueChange($event)\"\n (ngpInputOtpComplete)=\"onComplete()\"\n ngpInputOtp\n >\n <input ngpInputOtpInput />\n <div class=\"slots\">\n @for (_ of slots(); track $index) {\n <div class=\"slot\" ngpInputOtpSlot></div>\n }\n </div>\n </div>\n `,\n styles: `\n :host {\n display: inline-flex;\n flex-direction: column;\n gap: 12px;\n max-width: 100%;\n }\n\n .slots {\n display: flex;\n gap: 8px;\n }\n\n .slot {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n border: 2px solid var(--ngp-border);\n border-radius: 8px;\n background: var(--ngp-background);\n font-size: 18px;\n font-weight: 600;\n color: var(--ngp-text-primary);\n transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);\n cursor: pointer;\n position: relative;\n }\n\n .slot[data-filled] {\n border-color: var(--ngp-border-strong);\n background: var(--ngp-background-accent);\n }\n\n .slot[data-active] {\n border-color: var(--ngp-focus-ring);\n box-shadow: 0 0 0 1px var(--ngp-focus-ring);\n }\n\n .slot[data-placeholder] {\n color: var(--ngp-text-placeholder);\n }\n\n .slot[data-caret]::after {\n content: '';\n position: absolute;\n width: 2px;\n height: 20px;\n background: var(--ngp-focus-ring);\n animation: blink 1s infinite;\n }\n\n @keyframes blink {\n 0%,\n 50% {\n opacity: 1;\n }\n 51%,\n 100% {\n opacity: 0;\n }\n }\n\n :host([data-disabled]) .slot {\n background: var(--ngp-background-disabled);\n color: var(--ngp-text-disabled);\n border-color: var(--ngp-border-disabled);\n cursor: not-allowed;\n }\n `,\n})\nexport class InputOtp implements ControlValueAccessor {\n /**\n * The number of slots to display.\n */\n readonly length = input<number, NumberInput>(6, {\n transform: numberAttribute,\n });\n\n /**\n * Whether the input is disabled.\n */\n readonly disabled = input<boolean, BooleanInput>(false, {\n transform: booleanAttribute,\n });\n\n /**\n * The pattern for allowed characters.\n */\n readonly pattern = input('[0-9]');\n\n /**\n * The placeholder character for empty slots.\n */\n readonly placeholder = input('');\n\n /**\n * The input mode for the hidden input.\n */\n readonly inputMode = input<'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url'>(\n 'numeric',\n );\n\n /**\n * Create an array for tracking slots.\n */\n protected readonly slots = computed(() => Array.from({ length: this.length() }, (_, i) => i));\n\n /**\n * The current value.\n */\n readonly value = model<string>('');\n\n private onChange: ChangeFn<string> = () => {};\n private onTouched: TouchedFn = () => {};\n\n protected readonly formDisabled = signal(false);\n\n /**\n * Handle value changes from the input-otp directive.\n */\n onValueChange(value: string): void {\n this.value.set(value);\n this.onChange(value);\n }\n\n /**\n * Handle completion events from the input-otp directive.\n */\n onComplete(): void {\n this.onTouched();\n }\n\n // ControlValueAccessor implementation\n writeValue(value: string): void {\n this.value.set(value);\n }\n\n registerOnChange(fn: ChangeFn<string>): 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",
75
+ "primitive": "input-otp",
76
+ "hasVariants": false,
77
+ "hasSizes": true
78
+ },
79
+ {
80
+ "name": "listbox",
81
+ "code": "import { Component } from '@angular/core';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroCheckSolid } from '@ng-icons/heroicons/solid';\nimport { NgpListboxOption } from 'ng-primitives/listbox';\n\n@Component({\n selector: 'app-listbox-option',\n hostDirectives: [\n {\n directive: NgpListboxOption,\n inputs: ['id', 'ngpListboxOptionValue:value', 'ngpListboxOptionDisabled:disabled'],\n },\n ],\n imports: [NgIcon],\n providers: [provideIcons({ heroCheckSolid })],\n template: `\n <ng-icon name=\"heroCheckSolid\" size=\"16px\" />\n <ng-content />\n `,\n styles: `\n :host {\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 box-sizing: border-box;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-active] {\n background-color: var(--ngp-background-active);\n }\n\n :host ng-icon {\n visibility: hidden;\n }\n\n :host[data-selected] ng-icon {\n visibility: visible;\n }\n `,\n})\nexport class ListboxOption {}\n",
82
+ "primitive": "listbox",
83
+ "hasVariants": false,
84
+ "hasSizes": true
85
+ },
86
+ {
87
+ "name": "menu",
88
+ "code": "import { Component } from '@angular/core';\nimport { NgpMenuItem } from 'ng-primitives/menu';\n\n@Component({\n selector: 'button[app-menu-item]',\n hostDirectives: [NgpMenuItem],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 8px 6px 14px;\n border: none;\n background: none;\n cursor: pointer;\n transition: background-color 0.2s;\n border-radius: 4px;\n min-width: 120px;\n text-align: start;\n outline: none;\n font-size: 14px;\n font-weight: 400;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n z-index: 1;\n }\n `,\n})\nexport class MenuItem {}\n",
89
+ "primitive": "menu",
90
+ "hasVariants": false,
91
+ "hasSizes": true
92
+ },
93
+ {
94
+ "name": "meter",
95
+ "code": "import { NumberInput } from '@angular/cdk/coercion';\nimport { Component, input, numberAttribute } from '@angular/core';\nimport {\n NgpMeter,\n NgpMeterIndicator,\n NgpMeterLabel,\n NgpMeterTrack,\n NgpMeterValue,\n} from 'ng-primitives/meter';\n\n@Component({\n selector: 'app-meter',\n hostDirectives: [\n { directive: NgpMeter, inputs: ['ngpMeterValue:value', 'ngpMeterMin:min', 'ngpMeterMax:max'] },\n ],\n imports: [NgpMeterIndicator, NgpMeterLabel, NgpMeterValue, NgpMeterTrack],\n template: `\n <span ngpMeterLabel>{{ label() }}</span>\n <span ngpMeterValue>{{ value() }}%</span>\n\n <div ngpMeterTrack>\n <div ngpMeterIndicator></div>\n </div>\n `,\n styles: `\n :host {\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-row-gap: 0.5rem;\n width: 200px;\n box-sizing: border-box;\n padding: 0.5rem;\n }\n\n [ngpMeterLabel] {\n color: var(--ngp-text-emphasis);\n font-size: 14px;\n font-weight: 600;\n }\n\n [ngpMeterValue] {\n color: var(--ngp-text-secondary);\n font-size: 14px;\n font-weight: 500;\n text-align: right;\n grid-column-start: 2;\n text-align: end;\n }\n\n [ngpMeterTrack] {\n grid-column: 1 / 3;\n overflow: hidden;\n background-color: var(--ngp-background);\n box-shadow: var(--ngp-shadow-border);\n border-radius: 4px;\n height: 8px;\n }\n\n [ngpMeterIndicator] {\n background-color: var(--ngp-background-success);\n height: 100%;\n transition: width 0.2s ease-in-out;\n inset-inline-start: 0px;\n border-radius: 4px;\n }\n `,\n})\nexport class Meter {\n /** The value of the meter. */\n readonly value = input<number, NumberInput>(0, {\n transform: numberAttribute,\n });\n\n /** The label of the meter. */\n readonly label = input.required<string>();\n}\n",
96
+ "primitive": "meter",
97
+ "hasVariants": false,
98
+ "hasSizes": true
99
+ },
100
+ {
101
+ "name": "native-select",
102
+ "code": "import { Component } from '@angular/core';\nimport { NgpNativeSelect } from 'ng-primitives/select';\n\n@Component({\n selector: 'select[app-select]',\n hostDirectives: [{ directive: NgpNativeSelect, inputs: ['ngpNativeSelectDisabled:disabled'] }],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n all: unset;\n appearance: none;\n display: flex;\n width: 90%;\n align-items: center;\n height: 2.5rem;\n padding: 0 1rem;\n border-radius: 0.5rem;\n background-color: var(--ngp-background);\n text-align: start;\n box-shadow: var(--ngp-button-shadow);\n outline: none;\n background-position-x: calc(100% - 10px);\n background-position-y: 50%;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0iIzczNzM3MyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNNS4yMiA4LjIyYS43NS43NSAwIDAgMSAxLjA2IDBMMTAgMTEuOTRsMy43Mi0zLjcyYS43NS43NSAwIDEgMSAxLjA2IDEuMDZsLTQuMjUgNC4yNWEuNzUuNzUgMCAwIDEtMS4wNiAwTDUuMjIgOS4yOGEuNzUuNzUgMCAwIDEgMC0xLjA2WiIgY2xpcC1ydWxlPSJldmVub2RkIj48L3BhdGg+PC9zdmc+');\n background-size: 1.25rem;\n background-repeat: no-repeat;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 0;\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n `,\n})\nexport class NativeSelect {}\n",
103
+ "primitive": "native-select",
104
+ "hasVariants": false,
105
+ "hasSizes": true
106
+ },
107
+ {
108
+ "name": "pagination",
109
+ "code": "import { Component, computed } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport {\n heroChevronDoubleLeft,\n heroChevronDoubleRight,\n heroChevronLeft,\n heroChevronRight,\n} from '@ng-icons/heroicons/outline';\nimport {\n injectPaginationState,\n NgpPagination,\n NgpPaginationButton,\n NgpPaginationFirst,\n NgpPaginationLast,\n NgpPaginationNext,\n NgpPaginationPrevious,\n} from 'ng-primitives/pagination';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-pagination',\n hostDirectives: [\n {\n directive: NgpPagination,\n inputs: [\n 'ngpPaginationPage:page',\n 'ngpPaginationPageCount:pageCount',\n 'ngpPaginationDisabled:disabled',\n ],\n outputs: ['ngpPaginationPageChange:pageChange'],\n },\n ],\n imports: [\n NgpPaginationButton,\n NgpPaginationFirst,\n NgpPaginationLast,\n NgpPaginationNext,\n NgpPaginationPrevious,\n NgIcon,\n ],\n providers: [\n provideValueAccessor(NgpPagination),\n provideIcons({\n heroChevronDoubleLeft,\n heroChevronDoubleRight,\n heroChevronLeft,\n heroChevronRight,\n }),\n ],\n template: `\n <ul>\n <li>\n <button ngpPaginationFirst aria-label=\"First Page\">\n <ng-icon name=\"heroChevronDoubleLeft\" />\n </button>\n </li>\n\n <li>\n <button ngpPaginationPrevious aria-label=\"Previous Page\">\n <ng-icon name=\"heroChevronLeft\" />\n </button>\n </li>\n\n @for (page of pages(); track page) {\n <li>\n <button\n [ngpPaginationButtonPage]=\"page\"\n [attr.aria-label]=\"'Page ' + page\"\n ngpPaginationButton\n >\n {{ page }}\n </button>\n </li>\n }\n\n <li>\n <button ngpPaginationNext aria-label=\"Next Page\">\n <ng-icon name=\"heroChevronRight\" />\n </button>\n </li>\n\n <li>\n <button ngpPaginationLast aria-label=\"Last Page\">\n <ng-icon name=\"heroChevronDoubleRight\" />\n </button>\n </li>\n </ul>\n `,\n styles: `\n ul {\n display: flex;\n align-items: center;\n column-gap: 0.5rem;\n list-style: none;\n }\n\n [ngpPaginationFirst],\n [ngpPaginationPrevious],\n [ngpPaginationButton],\n [ngpPaginationNext],\n [ngpPaginationLast] {\n all: unset;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 2rem;\n height: 2rem;\n border-radius: 0.5rem;\n color: var(--ngp-text-primary);\n outline: none;\n font-size: 14px;\n font-weight: 500;\n background-color: var(--ngp-background);\n box-shadow: var(--ngp-button-shadow);\n cursor: pointer;\n transition: background-color 0.2s;\n\n &[data-hover]:not([data-disabled]):not([data-selected]) {\n background-color: var(--ngp-background-hover);\n }\n\n &[data-focus-visible]:not([data-disabled]) {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n &[data-press]:not([data-disabled]):not([data-selected]) {\n background-color: var(--ngp-background-active);\n }\n\n &[data-disabled] {\n color: rgb(210 210 210);\n background-color: var(--ngp-background-disabled);\n cursor: not-allowed;\n box-shadow: none;\n }\n\n &[data-selected] {\n background-color: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n }\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Pagination implements ControlValueAccessor {\n /** Access the pagination state */\n protected readonly state = injectPaginationState();\n\n /** Get the pages as an array we can iterate over */\n protected readonly pages = computed(() =>\n Array.from({ length: this.state().pageCount() }).map((_, i) => i + 1),\n );\n\n /** The onChange callback */\n private onChange?: ChangeFn<number>;\n\n /** The onTouched callback */\n protected onTouched?: TouchedFn;\n\n constructor() {\n this.state().pageChange.subscribe(value => this.onChange?.(value));\n }\n\n /** Write a new value to the control */\n writeValue(value: number): void {\n this.state().page.set(value);\n }\n\n /** Register a callback to be called when the value changes */\n registerOnChange(fn: ChangeFn<number>): void {\n this.onChange = fn;\n }\n\n /** Register a callback to be called when the control is touched */\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n}\n",
110
+ "primitive": "pagination",
111
+ "hasVariants": false,
112
+ "hasSizes": true
113
+ },
114
+ {
115
+ "name": "popover",
116
+ "code": "import { Directive, input } from '@angular/core';\nimport { injectPopoverTriggerState } from 'ng-primitives/popover';\nimport { NgpPopoverTrigger } from 'ng-primitives/popover';\nimport { Popover } from './popover';\n\n@Directive({\n selector: '[appPopoverTrigger]',\n hostDirectives: [\n {\n directive: NgpPopoverTrigger,\n inputs: [\n 'ngpPopoverTriggerDisabled:appPopoverTriggerDisabled',\n 'ngpPopoverTriggerPlacement:appPopoverTriggerPlacement',\n 'ngpPopoverTriggerOffset:appPopoverTriggerOffset',\n 'ngpPopoverTriggerShowDelay:appPopoverTriggerShowDelay',\n 'ngpPopoverTriggerHideDelay:appPopoverTriggerHideDelay',\n 'ngpPopoverTriggerFlip:appPopoverTriggerFlip',\n 'ngpPopoverTriggerContainer:appPopoverTriggerContainer',\n 'ngpPopoverTriggerCloseOnOutsideClick:appPopoverTriggerCloseOnOutsideClick',\n 'ngpPopoverTriggerCloseOnEscape:appPopoverTriggerCloseOnEscape',\n 'ngpPopoverTriggerScrollBehavior:appPopoverTriggerScrollBehavior',\n 'ngpPopoverTriggerContext:appPopoverTrigger',\n ],\n },\n ],\n})\nexport class PopoverTrigger {\n /** Access the popover trigger */\n private readonly popoverTrigger = injectPopoverTriggerState();\n\n /** Define the content of the popover */\n readonly content = input.required<string>({\n alias: 'appPopoverTrigger',\n });\n\n constructor() {\n this.popoverTrigger().popover.set(Popover);\n }\n}\n",
117
+ "primitive": "popover",
118
+ "hasVariants": false,
119
+ "hasSizes": false
120
+ },
121
+ {
122
+ "name": "progress",
123
+ "code": "import { NumberInput } from '@angular/cdk/coercion';\nimport { Component, input, numberAttribute } from '@angular/core';\nimport {\n NgpProgress,\n NgpProgressIndicator,\n NgpProgressLabel,\n NgpProgressTrack,\n NgpProgressValue,\n} from 'ng-primitives/progress';\n\n@Component({\n selector: 'app-progress',\n hostDirectives: [\n {\n directive: NgpProgress,\n inputs: ['ngpProgressValue:value', 'ngpProgressMax:max', 'ngpProgressValueLabel:valueLabel'],\n },\n ],\n imports: [NgpProgressIndicator, NgpProgressTrack, NgpProgressLabel, NgpProgressValue],\n template: `\n <label ngpProgressLabel>{{ label() }}</label>\n <span ngpProgressValue>{{ value() }}%</span>\n\n <div ngpProgressTrack>\n <div ngpProgressIndicator></div>\n </div>\n `,\n styles: `\n :host {\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-row-gap: 0.5rem;\n width: 200px;\n box-sizing: border-box;\n padding: 0.5rem;\n }\n\n [ngpProgressLabel] {\n color: var(--ngp-text-emphasis);\n font-size: 14px;\n font-weight: 600;\n }\n\n [ngpProgressValue] {\n color: var(--ngp-text-secondary);\n font-size: 14px;\n font-weight: 500;\n text-align: right;\n grid-column-start: 2;\n text-align: end;\n }\n\n [ngpProgressTrack] {\n grid-column: 1 / 3;\n position: relative;\n height: 12px;\n width: 100%;\n max-width: 320px;\n overflow: hidden;\n border-radius: 0.5rem;\n border: 1px solid var(--ngp-border);\n background-color: var(--ngp-background);\n }\n\n [ngpProgressIndicator] {\n height: 100%;\n border-radius: 0.5rem;\n background-color: var(--ngp-background-inverse);\n transition: width 150ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n `,\n})\nexport class Progress {\n /** The value of the progress. */\n readonly value = input<number, NumberInput>(0, {\n transform: numberAttribute,\n });\n\n /** The label of the progress. */\n readonly label = input.required<string>();\n}\n",
124
+ "primitive": "progress",
125
+ "hasVariants": false,
126
+ "hasSizes": true
127
+ },
128
+ {
129
+ "name": "radio",
130
+ "code": "import { Component } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { injectRadioGroupState, NgpRadioGroup } from 'ng-primitives/radio';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-radio-group',\n hostDirectives: [\n {\n directive: NgpRadioGroup,\n inputs: [\n 'ngpRadioGroupValue:value',\n 'ngpRadioGroupDisabled:disabled',\n 'ngpRadioGroupOrientation:orientation',\n ],\n outputs: ['ngpRadioGroupValueChange:valueChange'],\n },\n ],\n providers: [provideValueAccessor(RadioGroup)],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n display: flex;\n flex-direction: column;\n row-gap: 1rem;\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class RadioGroup implements ControlValueAccessor {\n /** Access the radio group state */\n protected readonly state = injectRadioGroupState<string>();\n\n /** The on change callback */\n private onChange?: ChangeFn<string | null>;\n\n /** The on touched callback */\n protected onTouched?: TouchedFn;\n\n constructor() {\n this.state().valueChange.subscribe(value => this.onChange?.(value));\n }\n\n /** Write a new value to the radio group */\n writeValue(value: string): void {\n this.state().value.set(value);\n }\n\n /** Register the on change callback */\n registerOnChange(onChange: ChangeFn<string | null>): void {\n this.onChange = onChange;\n }\n\n /** Register the on touched callback */\n registerOnTouched(onTouched: TouchedFn): void {\n this.onTouched = onTouched;\n }\n}\n",
131
+ "primitive": "radio",
132
+ "hasVariants": false,
133
+ "hasSizes": false
134
+ },
135
+ {
136
+ "name": "range-slider",
137
+ "code": "import { Component, input } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { explicitEffect } from 'ng-primitives/internal';\nimport {\n injectRangeSliderState,\n NgpRangeSlider,\n NgpRangeSliderRange,\n NgpRangeSliderThumb,\n NgpRangeSliderTrack,\n provideRangeSliderState,\n} from 'ng-primitives/slider';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-range-slider',\n hostDirectives: [\n {\n directive: NgpRangeSlider,\n inputs: [\n 'ngpRangeSliderLow:low',\n 'ngpRangeSliderHigh:high',\n 'ngpRangeSliderMin:min',\n 'ngpRangeSliderMax:max',\n 'ngpRangeSliderStep:step',\n 'ngpRangeSliderDisabled:disabled',\n 'ngpRangeSliderOrientation:orientation',\n ],\n outputs: ['ngpRangeSliderLowChange:lowChange', 'ngpRangeSliderHighChange:highChange'],\n },\n ],\n imports: [NgpRangeSliderTrack, NgpRangeSliderRange, NgpRangeSliderThumb],\n providers: [provideRangeSliderState(), provideValueAccessor(RangeSlider)],\n template: `\n <div ngpRangeSliderTrack>\n <div ngpRangeSliderRange></div>\n </div>\n <div ngpRangeSliderThumb></div>\n <div ngpRangeSliderThumb></div>\n `,\n styles: `\n :host {\n display: flex;\n position: relative;\n width: 200px;\n height: 20px;\n touch-action: none;\n user-select: none;\n align-items: center;\n }\n\n [ngpRangeSliderTrack] {\n position: relative;\n height: 5px;\n width: 100%;\n border-radius: 999px;\n background-color: var(--ngp-background-secondary);\n }\n\n [ngpRangeSliderRange] {\n position: absolute;\n height: 100%;\n border-radius: 999px;\n background-color: var(--ngp-background-inverse);\n }\n\n [ngpRangeSliderThumb] {\n position: absolute;\n display: block;\n height: 20px;\n width: 20px;\n border-radius: 10px;\n background-color: white;\n box-shadow: var(--ngp-button-shadow);\n outline: none;\n transform: translateX(-50%);\n }\n\n [ngpRangeSliderThumb][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 0;\n }\n\n [ngpRangeSliderThumb][data-thumb='high'] {\n z-index: 2;\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class RangeSlider implements ControlValueAccessor {\n /** Access the range slider state */\n private readonly state = injectRangeSliderState();\n\n /** Forward aria-labels to each thumb */\n readonly ariaLabelLow = input<string | null>(null);\n readonly ariaLabelHigh = input<string | null>(null);\n\n /** The onChange callback function. */\n private onChange?: ChangeFn<[number, number]>;\n\n /** The onTouched callback function. */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Whenever either value changes, call the onChange function with the new tuple [low, high].\n explicitEffect([this.state().low, this.state().high], ([low, high]) =>\n this.onChange?.([low, high]),\n );\n }\n\n writeValue(value: [number, number]): void {\n if (!value || value.length !== 2) {\n return;\n }\n\n const [low, high] = value;\n // Use the directive's clamping setters to respect min/max and ordering\n this.state().setLowValue(low);\n this.state().setHighValue(high);\n }\n\n registerOnChange(fn: ChangeFn<[number, number]>): 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().disabled.set(isDisabled);\n }\n}\n",
138
+ "primitive": "range-slider",
139
+ "hasVariants": false,
140
+ "hasSizes": false
141
+ },
142
+ {
143
+ "name": "search",
144
+ "code": "import { Component, input, model } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroMagnifyingGlass } from '@ng-icons/heroicons/outline';\nimport { NgpButton } from 'ng-primitives/button';\nimport { NgpInput } from 'ng-primitives/input';\nimport { NgpSearch, NgpSearchClear } from 'ng-primitives/search';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-search',\n hostDirectives: [NgpSearch],\n imports: [NgIcon, NgpSearchClear, NgpInput, NgpButton],\n providers: [provideValueAccessor(Search), provideIcons({ heroMagnifyingGlass })],\n template: `\n <ng-icon name=\"heroMagnifyingGlass\" />\n <input\n [value]=\"query()\"\n [placeholder]=\"placeholder()\"\n (input)=\"onQueryChange($event)\"\n ngpInput\n type=\"search\"\n />\n <button ngpSearchClear ngpButton aria-label=\"Clear search\">Clear</button>\n `,\n styles: `\n :host {\n display: block;\n position: relative;\n }\n\n [ngpInput] {\n height: 36px;\n width: 100%;\n border-radius: 8px;\n padding: 0 16px 0 40px;\n border: none;\n background: var(--ngp-background);\n box-shadow: var(--ngp-input-shadow);\n outline: none;\n }\n\n /* clears the 'X' from Chrome */\n [ngpInput]::-webkit-search-decoration,\n [ngpInput]::-webkit-search-cancel-button,\n [ngpInput]::-webkit-search-results-button,\n [ngpInput]::-webkit-search-results-decoration {\n display: none;\n }\n\n [ngpInput][data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 0px;\n }\n\n [ngpInput]::placeholder {\n color: var(--ngp-text-placeholder);\n }\n\n [ngpButton] {\n position: absolute;\n top: 0;\n right: 0;\n height: 36px;\n padding: 0 16px;\n border: none;\n border-radius: 0 8px 8px 0;\n background-color: transparent;\n color: var(--ngp-text-blue);\n font-size: 0.875rem;\n line-height: 1.25rem;\n cursor: pointer;\n outline: none;\n display: none;\n }\n\n [ngpButton]:not([data-empty]) {\n display: block;\n }\n\n ng-icon {\n position: absolute;\n font-size: 1.25rem;\n top: 18px;\n left: 12px;\n transform: translateY(-50%);\n color: var(--ngp-text-tertiary);\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Search implements ControlValueAccessor {\n /** The search query */\n readonly query = model<string>('');\n\n /** The placeholder text */\n readonly placeholder = input<string>('');\n\n /** The function to call when the value changes */\n private onChange?: ChangeFn<string>;\n\n /** The function to call when the control is touched */\n protected onTouched?: TouchedFn;\n\n writeValue(value: string): void {\n this.query.set(value);\n }\n\n registerOnChange(fn: ChangeFn<string>): void {\n this.onChange = fn;\n }\n\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n protected onQueryChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n this.query.set(input.value);\n this.onChange?.(input.value);\n }\n}\n",
145
+ "primitive": "search",
146
+ "hasVariants": false,
147
+ "hasSizes": true
148
+ },
149
+ {
150
+ "name": "select",
151
+ "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 (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 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",
152
+ "primitive": "select",
153
+ "hasVariants": false,
154
+ "hasSizes": true
155
+ },
156
+ {
157
+ "name": "separator",
158
+ "code": "import { Component } from '@angular/core';\nimport { NgpSeparator } from 'ng-primitives/separator';\n\n@Component({\n selector: '[app-separator]',\n hostDirectives: [{ directive: NgpSeparator, inputs: ['ngpSeparatorOrientation:orientation'] }],\n template: ``,\n styles: `\n :host {\n background-color: var(--ngp-border);\n height: 1px;\n }\n `,\n})\nexport class Separator {}\n",
159
+ "primitive": "separator",
160
+ "hasVariants": false,
161
+ "hasSizes": false
162
+ },
163
+ {
164
+ "name": "slider",
165
+ "code": "import { Component, input } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ControlValueAccessor } from '@angular/forms';\nimport {\n injectSliderState,\n NgpSlider,\n NgpSliderRange,\n NgpSliderThumb,\n NgpSliderTrack,\n} from 'ng-primitives/slider';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-slider',\n hostDirectives: [\n {\n directive: NgpSlider,\n inputs: [\n 'ngpSliderValue:value',\n 'ngpSliderMin:min',\n 'ngpSliderMax:max',\n 'ngpSliderDisabled:disabled',\n ],\n outputs: ['ngpSliderValueChange:valueChange'],\n },\n ],\n imports: [NgpSliderTrack, NgpSliderRange, NgpSliderThumb],\n providers: [provideValueAccessor(Slider)],\n template: `\n <div ngpSliderTrack>\n <div ngpSliderRange></div>\n </div>\n <div [ariaLabel]=\"ariaLabel()\" ngpSliderThumb></div>\n `,\n styles: `\n :host {\n display: flex;\n position: relative;\n width: 200px;\n height: 20px;\n touch-action: none;\n user-select: none;\n align-items: center;\n }\n\n [ngpSliderTrack] {\n position: relative;\n height: 5px;\n width: 100%;\n border-radius: 999px;\n background-color: var(--ngp-background-secondary);\n }\n\n [ngpSliderRange] {\n position: absolute;\n height: 100%;\n border-radius: 999px;\n background-color: var(--ngp-background-inverse);\n }\n\n [ngpSliderThumb] {\n position: absolute;\n display: block;\n height: 20px;\n width: 20px;\n border-radius: 10px;\n background-color: white;\n box-shadow: var(--ngp-button-shadow);\n outline: none;\n transform: translateX(-50%);\n }\n\n [ngpSliderThumb][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 0;\n }\n `,\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Slider implements ControlValueAccessor {\n /** Access the slider state */\n private readonly state = injectSliderState();\n\n /** Forward the aria-label to the thumb */\n readonly ariaLabel = input<string | null>(null, {\n alias: 'aria-label',\n });\n\n /**\n * The onChange callback function.\n */\n private onChange?: ChangeFn<number>;\n\n /**\n * The onTouched callback function.\n */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Whenever the user interacts with the slider, call the onChange function with the new value.\n this.state()\n .valueChange.pipe(takeUntilDestroyed())\n .subscribe(value => this.onChange?.(value));\n }\n\n writeValue(value: number): void {\n this.state().setValue(value);\n }\n\n registerOnChange(fn: ChangeFn<number>): 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
+ "primitive": "slider",
167
+ "hasVariants": false,
168
+ "hasSizes": false
169
+ },
170
+ {
171
+ "name": "switch",
172
+ "code": "import { Component } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { injectSwitchState, NgpSwitch, NgpSwitchThumb } from 'ng-primitives/switch';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-switch',\n hostDirectives: [\n {\n directive: NgpSwitch,\n inputs: ['ngpSwitchChecked:checked', 'ngpSwitchDisabled:disabled'],\n outputs: ['ngpSwitchCheckedChange:checkedChange'],\n },\n ],\n imports: [NgpSwitchThumb],\n template: `\n <span ngpSwitchThumb></span>\n `,\n styles: `\n :host {\n display: inline-flex;\n align-items: center;\n position: relative;\n width: 2.5rem;\n height: 1.5rem;\n border-radius: 999px;\n background-color: var(--ngp-background-secondary);\n border: 1px solid var(--ngp-border);\n padding: 0;\n outline: none;\n transition-property:\n color, background-color, border-color, text-decoration-color, fill, stroke;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n box-sizing: border-box;\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n :host[data-checked] {\n background-color: var(--ngp-background-blue);\n border-color: var(--ngp-border-blue);\n }\n\n [ngpSwitchThumb] {\n display: block;\n height: 1.25rem;\n width: 1.25rem;\n border-radius: 999px;\n background-color: white;\n box-shadow: var(--ngp-button-shadow);\n outline: none;\n transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);\n transform: translateX(1px);\n box-sizing: border-box;\n }\n\n [ngpSwitchThumb][data-checked] {\n transform: translateX(17px);\n }\n `,\n providers: [provideValueAccessor(Switch)],\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Switch implements ControlValueAccessor {\n /** Access the switch state. */\n private readonly switch = injectSwitchState();\n\n /** The on change callback */\n private onChange?: ChangeFn<boolean>;\n\n /** The on touched callback */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Any time the switch changes, update the form value.\n this.switch().checkedChange.subscribe(value => this.onChange?.(value));\n }\n\n /** Write a new value to the switch. */\n writeValue(value: boolean): void {\n this.switch().setChecked(value);\n }\n\n /** Register a callback function to be called when the value changes. */\n registerOnChange(fn: ChangeFn<boolean>): void {\n this.onChange = fn;\n }\n\n /** Register a callback function to be called when the switch is touched. */\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n /** Set the disabled state of the switch. */\n setDisabledState(isDisabled: boolean): void {\n this.switch().setDisabled(isDisabled);\n }\n}\n",
173
+ "primitive": "switch",
174
+ "hasVariants": false,
175
+ "hasSizes": false
176
+ },
177
+ {
178
+ "name": "tabs",
179
+ "code": "import { Component, input, TemplateRef, viewChild } from '@angular/core';\n\n@Component({\n selector: 'app-tab',\n template: `\n <ng-template #content>\n <ng-content />\n </ng-template>\n `,\n})\nexport class Tab {\n /**\n * The value of the tab.\n */\n readonly value = input<string>();\n\n /**\n * The label of the tab.\n */\n readonly label = input<string>();\n\n /**\n * The content of the tab.\n */\n readonly content = viewChild.required<TemplateRef<void>>('content');\n}\n",
180
+ "primitive": "tabs",
181
+ "hasVariants": false,
182
+ "hasSizes": false
183
+ },
184
+ {
185
+ "name": "textarea",
186
+ "code": "import { Component } from '@angular/core';\nimport { NgpTextarea } from 'ng-primitives/textarea';\n\n@Component({\n selector: 'textarea[app-textarea]',\n hostDirectives: [{ directive: NgpTextarea, inputs: ['disabled'] }],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n height: 72px;\n width: 90%;\n border-radius: 8px;\n padding: 8px 12px;\n border: none;\n box-shadow: var(--ngp-input-shadow);\n outline: none;\n font-family: inherit;\n }\n\n :host[data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 0;\n }\n\n :host::placeholder {\n color: var(--ngp-text-placeholder);\n }\n `,\n})\nexport class Textarea {}\n",
187
+ "primitive": "textarea",
188
+ "hasVariants": false,
189
+ "hasSizes": false
190
+ },
191
+ {
192
+ "name": "toast",
193
+ "code": "import { Component, inject } from '@angular/core';\nimport { NgpButton } from 'ng-primitives/button';\nimport { injectToastContext, NgpToast, NgpToastManager } from 'ng-primitives/toast';\n\n@Component({\n selector: 'app-toast',\n imports: [NgpButton],\n hostDirectives: [NgpToast],\n template: `\n <p class=\"toast-title\">{{ context.header }}</p>\n <p class=\"toast-description\">{{ context.description }}</p>\n <button class=\"toast-dismiss\" (click)=\"dismiss()\" ngpButton>Dismiss</button>\n `,\n styles: `\n :host {\n position: absolute;\n touch-action: none;\n transition:\n transform 0.4s,\n opacity 0.4s,\n height 0.4s,\n box-shadow 0.2s;\n box-sizing: border-box;\n align-items: center;\n gap: 6px;\n display: inline-grid;\n background: var(--ngp-background);\n box-shadow: var(--ngp-shadow);\n border: 1px solid var(--ngp-border);\n padding: 12px 16px;\n opacity: 0;\n transition: all 0.4s cubic-bezier(0.215, 0.61, 0.355, 1);\n border-radius: 8px;\n z-index: var(--ngp-toast-z-index);\n grid-template-columns: 1fr auto;\n grid-template-rows: min-content min-content;\n column-gap: 12px;\n align-items: center;\n width: var(--ngp-toast-width);\n height: fit-content;\n transform: var(--y);\n }\n\n .toast-title {\n color: var(--ngp-text-primary);\n font-size: 0.75rem;\n font-weight: 600;\n margin: 0;\n grid-column: 1 / 2;\n grid-row: 1;\n line-height: 16px;\n user-select: none;\n }\n\n .toast-description {\n font-size: 0.75rem;\n margin: 0;\n color: var(--ngp-text-secondary);\n grid-column: 1 / 2;\n grid-row: 2;\n line-height: 16px;\n user-select: none;\n }\n\n .toast-dismiss {\n background: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n border: none;\n border-radius: 8px;\n padding: 4px 8px;\n font-weight: 600;\n font-size: 12px;\n cursor: pointer;\n grid-column: 2 / 3;\n grid-row: 1 / 3;\n max-height: 27px;\n }\n\n :host[data-position-x='end'] {\n right: 0;\n }\n\n :host[data-position-x='start'] {\n left: 0;\n }\n\n :host[data-position-y='top'] {\n top: 0;\n --lift: 1;\n --lift-amount: calc(var(--lift) * var(--ngp-toast-gap));\n --y: translateY(-100%);\n }\n\n :host[data-position-y='bottom'] {\n bottom: 0;\n --lift: -1;\n --lift-amount: calc(var(--lift) * var(--ngp-toast-gap));\n --y: translateY(100%);\n }\n\n :host[data-enter] {\n opacity: 1;\n --y: translateY(0);\n }\n\n :host[data-exit] {\n opacity: 0;\n --y: translateY(calc(calc(var(--lift) * var(--ngp-toast-gap)) * -1));\n }\n\n :host[data-visible='false'] {\n opacity: 0;\n pointer-events: none;\n }\n\n :host[data-expanded='true']::after {\n content: '';\n position: absolute;\n left: 0;\n height: calc(var(--ngp-toast-gap) + 1px);\n bottom: 100%;\n width: 100%;\n }\n\n :host[data-expanded='false'][data-front='false'] {\n --scale: var(--ngp-toasts-before) * 0.05 + 1;\n --y: translateY(calc(var(--lift-amount) * var(--ngp-toasts-before)))\n scale(calc(-1 * var(--scale)));\n height: var(--ngp-toast-front-height);\n }\n\n :host[data-expanded='true'] {\n --y: translateY(calc(var(--lift) * var(--ngp-toast-offset)));\n height: var(--ngp-toast-height);\n }\n\n :host[data-swiping='true'] {\n transform: var(--y) translateY(var(--ngp-toast-swipe-amount-y, 0))\n translateX(var(--ngp-toast-swipe-amount-x, 0));\n transition: none;\n }\n\n :host[data-swiping='true'][data-swipe-direction='left'] {\n /* Fade out from -45px to -100px swipe */\n opacity: calc(1 - clamp(0, ((-1 * var(--ngp-toast-swipe-x, 0px)) - 45) / 55, 1));\n }\n\n :host[data-swiping='true'][data-swipe-direction='right'] {\n /* Fade out from 45px to 100px swipe */\n opacity: calc(1 - clamp(0, (var(--ngp-toast-swipe-x, 0px) - 45) / 55, 1));\n }\n\n :host[data-swiping='true'][data-swipe-direction='top'] {\n /* Fade out from -45px to -100px swipe */\n opacity: calc(1 - clamp(0, ((-1 * var(--ngp-toast-swipe-y, 0px)) - 45) / 55, 1));\n }\n\n :host[data-swiping='true'][data-swipe-direction='bottom'] {\n /* Fade out from 45px to 100px swipe */\n opacity: calc(1 - clamp(0, (var(--ngp-toast-swipe-y, 0px) - 45) / 55, 1));\n }\n `,\n})\nexport class Toast {\n private readonly toastManager = inject(NgpToastManager);\n private readonly toast = inject(NgpToast);\n protected readonly context = injectToastContext<ToastContext>();\n\n dismiss(): void {\n this.toastManager.dismiss(this.toast);\n }\n}\n\ninterface ToastContext {\n header: string;\n description: string;\n}\n",
194
+ "primitive": "toast",
195
+ "hasVariants": false,
196
+ "hasSizes": true
197
+ },
198
+ {
199
+ "name": "toggle",
200
+ "code": "import { Component } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgpButton } from 'ng-primitives/button';\nimport { injectToggleState, NgpToggle } from 'ng-primitives/toggle';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'button[app-toggle]',\n hostDirectives: [\n {\n directive: NgpToggle,\n inputs: ['ngpToggleSelected:selected', 'ngpToggleDisabled:disabled'],\n outputs: ['ngpToggleSelectedChange:selectedChange'],\n },\n { directive: NgpButton, inputs: ['disabled'] },\n ],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n padding-left: 1rem;\n padding-right: 1rem;\n border-radius: 0.5rem;\n color: var(--ngp-text-primary);\n border: none;\n outline: none;\n height: 2.5rem;\n font-weight: 500;\n background-color: var(--ngp-background);\n transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: var(--ngp-button-shadow);\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-selected] {\n background-color: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n }\n `,\n providers: [provideValueAccessor(Toggle)],\n host: {\n '(focusout)': 'onTouched?.()',\n },\n})\nexport class Toggle implements ControlValueAccessor {\n /** Access the toggle state. */\n private readonly toggle = injectToggleState();\n\n /** The on change callback */\n private onChange?: ChangeFn<boolean>;\n\n /** The on touched callback */\n protected onTouched?: TouchedFn;\n\n constructor() {\n // Any time the toggle changes, update the form value.\n this.toggle().selectedChange.subscribe(value => this.onChange?.(value));\n }\n\n /** Write a new value to the toggle. */\n writeValue(value: boolean): void {\n this.toggle().setSelected(value);\n }\n\n /** Register a callback function to be called when the value changes. */\n registerOnChange(fn: ChangeFn<boolean>): void {\n this.onChange = fn;\n }\n\n /** Register a callback function to be called when the toggle is touched. */\n registerOnTouched(fn: TouchedFn): void {\n this.onTouched = fn;\n }\n\n /** Set the disabled state of the toggle. */\n setDisabledState(isDisabled: boolean): void {\n this.toggle().setDisabled(isDisabled);\n }\n}\n",
201
+ "primitive": "toggle",
202
+ "hasVariants": false,
203
+ "hasSizes": false
204
+ },
205
+ {
206
+ "name": "toggle-group",
207
+ "code": "import { Component } from '@angular/core';\nimport { NgpButton } from 'ng-primitives/button';\nimport { NgpToggleGroupItem } from 'ng-primitives/toggle-group';\n\n@Component({\n selector: 'button[app-toggle-group-item]',\n hostDirectives: [\n {\n directive: NgpToggleGroupItem,\n inputs: ['ngpToggleGroupItemValue:value', 'ngpToggleGroupItemDisabled:disabled'],\n },\n {\n directive: NgpButton,\n inputs: ['disabled'],\n },\n ],\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n display: flex;\n width: 2rem;\n height: 2rem;\n align-items: center;\n justify-content: center;\n border-radius: 0.25rem;\n border: 1px solid transparent;\n background: transparent;\n outline: none;\n transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);\n box-sizing: border-box;\n color: var(--ngp-text-primary);\n font-size: 1.125rem;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n border-color: var(--ngp-border);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-selected] {\n background-color: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n border-color: transparent;\n }\n `,\n})\nexport class ToggleGroupItem {}\n",
208
+ "primitive": "toggle-group",
209
+ "hasVariants": false,
210
+ "hasSizes": true
211
+ },
212
+ {
213
+ "name": "toolbar",
214
+ "code": "import { Component } from '@angular/core';\nimport { NgpButton } from 'ng-primitives/button';\nimport { NgpRovingFocusItem } from 'ng-primitives/roving-focus';\n\n@Component({\n selector: 'button[app-toolbar-button]',\n hostDirectives: [\n { directive: NgpButton, inputs: ['disabled'] },\n {\n directive: NgpRovingFocusItem,\n inputs: ['ngpRovingFocusItemDisabled:disabled'],\n },\n ],\n host: {\n type: 'button',\n },\n template: `\n <ng-content />\n `,\n styles: `\n :host {\n display: flex;\n width: 2rem;\n height: 2rem;\n align-items: center;\n justify-content: center;\n border-radius: 0.25rem;\n border: 1px solid transparent;\n background: transparent;\n outline: none;\n transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);\n box-sizing: border-box;\n color: var(--ngp-text-primary);\n cursor: pointer;\n }\n\n :host[data-hover] {\n background-color: var(--ngp-background-hover);\n border-color: var(--ngp-border);\n }\n\n :host[data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n }\n\n :host[data-press] {\n background-color: var(--ngp-background-active);\n }\n\n :host[data-selected] {\n background-color: var(--ngp-background-inverse);\n color: var(--ngp-text-inverse);\n }\n `,\n})\nexport class ToolbarButton {}\n",
215
+ "primitive": "toolbar",
216
+ "hasVariants": false,
217
+ "hasSizes": false
218
+ },
219
+ {
220
+ "name": "tooltip",
221
+ "code": "import { Directive, input } from '@angular/core';\nimport { injectTooltipTriggerState, NgpTooltipTrigger } from 'ng-primitives/tooltip';\nimport { Tooltip } from './tooltip';\n\n@Directive({\n selector: '[appTooltipTrigger]',\n hostDirectives: [\n {\n directive: NgpTooltipTrigger,\n inputs: [\n 'ngpTooltipTriggerPlacement:appTooltipTriggerPlacement',\n 'ngpTooltipTriggerDisabled:appTooltipTriggerDisabled',\n 'ngpTooltipTriggerOffset:appTooltipTriggerOffset',\n 'ngpTooltipTriggerShowDelay:appTooltipTriggerShowDelay',\n 'ngpTooltipTriggerHideDelay:appTooltipTriggerHideDelay',\n 'ngpTooltipTriggerFlip:appTooltipTriggerFlip',\n 'ngpTooltipTriggerContainer:appTooltipTriggerContainer',\n 'ngpTooltipTriggerShowOnOverflow:appTooltipTriggerShowOnOverflow',\n 'ngpTooltipTriggerContext:appTooltipTrigger',\n ],\n },\n ],\n})\nexport class TooltipTrigger {\n /** Access the tooltip trigger */\n private readonly tooltipTrigger = injectTooltipTriggerState();\n\n /** Define the content of the tooltip */\n readonly content = input.required<string>({\n alias: 'appTooltipTrigger',\n });\n\n constructor() {\n this.tooltipTrigger().tooltip.set(Tooltip);\n }\n}\n",
222
+ "primitive": "tooltip",
223
+ "hasVariants": false,
224
+ "hasSizes": false
225
+ }
226
+ ]
@@ -0,0 +1,26 @@
1
+ export interface PrimitiveDefinition {
2
+ name: string;
3
+ entryPoint: string;
4
+ exports: string[];
5
+ hasSecondaryEntryPoint: boolean;
6
+ category: string;
7
+ description: string;
8
+ accessibility: string[];
9
+ examples?: Array<{
10
+ name: string;
11
+ code: string;
12
+ description?: string;
13
+ }>;
14
+ reusableComponent?: {
15
+ code: string;
16
+ hasVariants: boolean;
17
+ hasSizes: boolean;
18
+ };
19
+ }
20
+ export interface ReusableComponentDefinition {
21
+ name: string;
22
+ code: string;
23
+ primitive: string;
24
+ hasVariants: boolean;
25
+ hasSizes: boolean;
26
+ }
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // This file is auto-generated. Do not edit manually.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../packages/mcp/src/generated/types.ts"],"names":[],"mappings":";AAAA,qDAAqD"}
package/src/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/src/index.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const tslib_1 = require("tslib");
5
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
6
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
7
+ const tools_js_1 = require("./tools.js");
8
+ // Server setup
9
+ const server = new mcp_js_1.McpServer({
10
+ name: 'ngp-mcp',
11
+ version: '0.1.0',
12
+ }, {
13
+ capabilities: {
14
+ tools: {},
15
+ },
16
+ });
17
+ // Register all tools
18
+ (0, tools_js_1.registerTools)(server);
19
+ // Start server
20
+ function main() {
21
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
22
+ const transport = new stdio_js_1.StdioServerTransport();
23
+ yield server.connect(transport);
24
+ console.error('Angular Primitives MCP server running on stdio');
25
+ });
26
+ }
27
+ main().catch(error => {
28
+ console.error('Server error:', error);
29
+ process.exit(1);
30
+ });
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/mcp/src/index.ts"],"names":[],"mappings":";;;;AACA,oEAAoE;AACpE,wEAAiF;AACjF,yCAA2C;AAE3C,eAAe;AACf,MAAM,MAAM,GAAG,IAAI,kBAAS,CAC1B;IACE,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,OAAO;CACjB,EACD;IACE,YAAY,EAAE;QACZ,KAAK,EAAE,EAAE;KACV;CACF,CACF,CAAC;AAEF,qBAAqB;AACrB,IAAA,wBAAa,EAAC,MAAM,CAAC,CAAC;AAEtB,eAAe;AACf,SAAe,IAAI;;QACjB,MAAM,SAAS,GAAG,IAAI,+BAAoB,EAAE,CAAC;QAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;IAClE,CAAC;CAAA;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,40 @@
1
+ export interface PrimitiveDefinition {
2
+ name: string;
3
+ description: string;
4
+ entryPoint: string;
5
+ exports: string[];
6
+ category: string;
7
+ accessibility: string[];
8
+ hasSecondaryEntryPoint: boolean;
9
+ examples?: Array<{
10
+ name: string;
11
+ code: string;
12
+ description?: string;
13
+ }>;
14
+ reusableComponent?: {
15
+ code: string;
16
+ hasVariants: boolean;
17
+ hasSizes: boolean;
18
+ };
19
+ apiData?: {
20
+ selector?: string;
21
+ exportAs?: string[];
22
+ inputs?: Array<{
23
+ name: string;
24
+ type: string;
25
+ description: string;
26
+ isRequired: boolean;
27
+ defaultValue?: string;
28
+ }>;
29
+ outputs?: Array<{
30
+ name: string;
31
+ type: string;
32
+ description: string;
33
+ }>;
34
+ };
35
+ }
36
+ export declare function loadPrimitivesData(): PrimitiveDefinition[];
37
+ export declare function loadApiData(): Record<string, any>;
38
+ export declare function loadReusableComponentsData(): any[];
39
+ export declare function getEnrichedPrimitivesRegistry(): PrimitiveDefinition[];
40
+ export declare function generateUsageExample(primitive: PrimitiveDefinition): string;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadPrimitivesData = loadPrimitivesData;
4
+ exports.loadApiData = loadApiData;
5
+ exports.loadReusableComponentsData = loadReusableComponentsData;
6
+ exports.getEnrichedPrimitivesRegistry = getEnrichedPrimitivesRegistry;
7
+ exports.generateUsageExample = generateUsageExample;
8
+ // This file loads primitives data from the generated files.
9
+ // Data is extracted at build time using the mcp-data-extraction executor.
10
+ const fs_1 = require("fs");
11
+ const path_1 = require("path");
12
+ // Function to load primitives data from generated JSON
13
+ function loadPrimitivesData() {
14
+ try {
15
+ const dataPath = (0, path_1.join)(__dirname, 'generated', 'primitives-data.json');
16
+ const data = (0, fs_1.readFileSync)(dataPath, 'utf8');
17
+ return JSON.parse(data);
18
+ }
19
+ catch (error) {
20
+ console.error('Could not load primitives data:', error);
21
+ return [];
22
+ }
23
+ }
24
+ // Function to load API data from extracted JSON
25
+ function loadApiData() {
26
+ try {
27
+ const apiPath = (0, path_1.join)(__dirname, 'generated', 'api-data.json');
28
+ const data = (0, fs_1.readFileSync)(apiPath, 'utf8');
29
+ return JSON.parse(data);
30
+ }
31
+ catch (error) {
32
+ console.warn('Could not load API data:', error);
33
+ return {};
34
+ }
35
+ }
36
+ // Function to load reusable components data
37
+ function loadReusableComponentsData() {
38
+ try {
39
+ const dataPath = (0, path_1.join)(__dirname, 'generated', 'reusable-components.json');
40
+ const data = (0, fs_1.readFileSync)(dataPath, 'utf8');
41
+ return JSON.parse(data);
42
+ }
43
+ catch (error) {
44
+ console.warn('Could not load reusable components data:', error);
45
+ return [];
46
+ }
47
+ }
48
+ // Function to merge primitives with API data
49
+ function getEnrichedPrimitivesRegistry() {
50
+ const primitives = loadPrimitivesData();
51
+ const apiData = loadApiData();
52
+ return primitives.map(primitive => {
53
+ // Try to find API data for the main export
54
+ const mainExport = primitive.exports[0];
55
+ const extractedData = apiData[mainExport];
56
+ if (extractedData) {
57
+ return Object.assign(Object.assign({}, primitive), { apiData: {
58
+ selector: extractedData.selector,
59
+ exportAs: extractedData.exportAs,
60
+ inputs: extractedData.inputs,
61
+ outputs: extractedData.outputs,
62
+ } });
63
+ }
64
+ return primitive;
65
+ });
66
+ }
67
+ // Generate usage examples for primitives
68
+ function generateUsageExample(primitive) {
69
+ var _a;
70
+ // Priority 1: Use reusable component if available
71
+ if ((_a = primitive.reusableComponent) === null || _a === void 0 ? void 0 : _a.code) {
72
+ const importStatement = `import { ${primitive.exports.slice(0, 3).join(', ')}${primitive.exports.length > 3 ? ', ...' : ''} } from '${primitive.entryPoint}';`;
73
+ return `${importStatement}\n\n// Reusable Component Implementation:\n\n${primitive.reusableComponent.code}`;
74
+ }
75
+ // Priority 2: Use extracted examples from documentation
76
+ if (primitive.examples && primitive.examples.length > 0) {
77
+ const firstExample = primitive.examples[0];
78
+ const importStatement = `import { ${primitive.exports.slice(0, 3).join(', ')}${primitive.exports.length > 3 ? ', ...' : ''} } from '${primitive.entryPoint}';`;
79
+ let exampleHeader = '';
80
+ if (firstExample.description) {
81
+ exampleHeader = `// ${firstExample.description}\n\n`;
82
+ }
83
+ return `${importStatement}\n\n${exampleHeader}${firstExample.code}`;
84
+ }
85
+ // Priority 3: Generate minimal usage based on exports
86
+ const mainExport = primitive.exports.find(exp => exp.startsWith('Ngp') &&
87
+ !exp.includes('State') &&
88
+ !exp.includes('Props') &&
89
+ !exp.includes('Config'));
90
+ if (!mainExport) {
91
+ return `import { ${primitive.exports.slice(0, 3).join(', ')}${primitive.exports.length > 3 ? ', ...' : ''} } from '${primitive.entryPoint}';\n\n// See documentation for usage details`;
92
+ }
93
+ // Generate selector from export name (e.g., NgpButton -> ngpButton)
94
+ const selector = mainExport.charAt(0).toLowerCase() + mainExport.slice(1);
95
+ return `import { ${mainExport} } from '${primitive.entryPoint}';\n\n// Basic usage:\n<element ${selector}>Content</element>`;
96
+ }
97
+ //# sourceMappingURL=primitives-registry.js.map