@ng-primitives/mcp 0.124.0 → 0.126.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-primitives/mcp",
3
- "version": "0.124.0",
3
+ "version": "0.126.0",
4
4
  "description": "MCP server for Angular Primitives - headless UI library",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -862,6 +862,16 @@
862
862
  "inputs": [],
863
863
  "outputs": []
864
864
  },
865
+ "NgpDatePickerCell": {
866
+ "name": "NgpDatePickerCell",
867
+ "description": "A cell in the date picker grid.",
868
+ "selector": "[ngpDatePickerCell]",
869
+ "exportAs": [
870
+ "ngpDatePickerCell"
871
+ ],
872
+ "inputs": [],
873
+ "outputs": []
874
+ },
865
875
  "NgpDatePickerLabel": {
866
876
  "name": "NgpDatePickerLabel",
867
877
  "description": "The label that displays the current month and year typically in the header of the date picker. This will be announced by screen readers when the date changes.",
@@ -880,16 +890,6 @@
880
890
  ],
881
891
  "outputs": []
882
892
  },
883
- "NgpDatePickerCell": {
884
- "name": "NgpDatePickerCell",
885
- "description": "A cell in the date picker grid.",
886
- "selector": "[ngpDatePickerCell]",
887
- "exportAs": [
888
- "ngpDatePickerCell"
889
- ],
890
- "inputs": [],
891
- "outputs": []
892
- },
893
893
  "NgpDatePickerNextMonth": {
894
894
  "name": "NgpDatePickerNextMonth",
895
895
  "description": "A button that navigates to the next month.",
@@ -1439,7 +1439,7 @@
1439
1439
  {
1440
1440
  "name": "ngpInputOtpValue",
1441
1441
  "type": "string",
1442
- "description": "The current value of the OTP.",
1442
+ "description": "The current value of the OTP. A null/undefined value (e.g. from a form\n`writeValue(null)` or `reset()`) is coerced to an empty string.",
1443
1443
  "isRequired": false,
1444
1444
  "defaultValue": "''"
1445
1445
  },
@@ -2564,6 +2564,99 @@
2564
2564
  }
2565
2565
  ]
2566
2566
  },
2567
+ "NgpPasswordToggle": {
2568
+ "name": "NgpPasswordToggle",
2569
+ "description": "Apply the `ngpPasswordToggle` directive to a `button` element to toggle the visibility of the\npassword within an `ngpPassword` container.",
2570
+ "selector": "button[ngpPasswordToggle]",
2571
+ "exportAs": [
2572
+ "ngpPasswordToggle"
2573
+ ],
2574
+ "inputs": [
2575
+ {
2576
+ "name": "ngpPasswordToggleShowLabel",
2577
+ "type": "string",
2578
+ "description": "The accessible label shown when the password is hidden.",
2579
+ "isRequired": false,
2580
+ "defaultValue": "'Show password'"
2581
+ },
2582
+ {
2583
+ "name": "ngpPasswordToggleHideLabel",
2584
+ "type": "string",
2585
+ "description": "The accessible label shown when the password is visible.",
2586
+ "isRequired": false,
2587
+ "defaultValue": "'Hide password'"
2588
+ },
2589
+ {
2590
+ "name": "ngpPasswordToggleShownAnnouncement",
2591
+ "type": "string",
2592
+ "description": "The message announced when the password becomes visible.",
2593
+ "isRequired": false,
2594
+ "defaultValue": "'Your password is shown'"
2595
+ },
2596
+ {
2597
+ "name": "ngpPasswordToggleHiddenAnnouncement",
2598
+ "type": "string",
2599
+ "description": "The message announced when the password becomes hidden.",
2600
+ "isRequired": false,
2601
+ "defaultValue": "'Your password is hidden'"
2602
+ }
2603
+ ],
2604
+ "outputs": []
2605
+ },
2606
+ "NgpPassword": {
2607
+ "name": "NgpPassword",
2608
+ "description": "The `NgpPassword` directive is a container for a password field and its visibility toggle.",
2609
+ "selector": "[ngpPassword]",
2610
+ "exportAs": [
2611
+ "ngpPassword"
2612
+ ],
2613
+ "inputs": [
2614
+ {
2615
+ "name": "ngpPasswordVisible",
2616
+ "type": "boolean | undefined",
2617
+ "description": "Whether the password is visible.",
2618
+ "isRequired": false
2619
+ },
2620
+ {
2621
+ "name": "ngpPasswordDefaultVisible",
2622
+ "type": "boolean",
2623
+ "description": "The default visibility state for uncontrolled usage.\n@default false",
2624
+ "isRequired": false,
2625
+ "defaultValue": "false"
2626
+ }
2627
+ ],
2628
+ "outputs": [
2629
+ {
2630
+ "name": "ngpPasswordVisibleChange",
2631
+ "type": "boolean",
2632
+ "description": "Emits when the visibility state changes."
2633
+ }
2634
+ ]
2635
+ },
2636
+ "NgpPasswordInput": {
2637
+ "name": "NgpPasswordInput",
2638
+ "description": "Apply the `ngpPasswordInput` directive to an `input` element within an `ngpPassword` container.\nIt is a complete input (form control, interactions, autofill) with password visibility toggling\nlayered on top, so it does not need to be combined with `ngpInput`.",
2639
+ "selector": "input[ngpPasswordInput]",
2640
+ "exportAs": [
2641
+ "ngpPasswordInput"
2642
+ ],
2643
+ "inputs": [
2644
+ {
2645
+ "name": "disabled",
2646
+ "type": "boolean",
2647
+ "description": "Whether the input is disabled.",
2648
+ "isRequired": false
2649
+ },
2650
+ {
2651
+ "name": "ngpPasswordInputIgnorePasswordManagers",
2652
+ "type": "boolean",
2653
+ "description": "Whether to opt the input out of password manager injection\n(1Password, LastPass, Bitwarden, etc.).",
2654
+ "isRequired": false,
2655
+ "defaultValue": "false"
2656
+ }
2657
+ ],
2658
+ "outputs": []
2659
+ },
2567
2660
  "NgpPopoverArrow": {
2568
2661
  "name": "NgpPopoverArrow",
2569
2662
  "description": "",
@@ -3222,133 +3315,133 @@
3222
3315
  }
3223
3316
  ]
3224
3317
  },
3225
- "NgpSlider": {
3226
- "name": "NgpSlider",
3227
- "description": "Apply the `ngpSlider` directive to an element that represents the slider and contains the track, range, and thumb.",
3228
- "selector": "[ngpSlider]",
3318
+ "NgpRangeSlider": {
3319
+ "name": "NgpRangeSlider",
3320
+ "description": "Apply the `ngpRangeSlider` directive to an element that represents the range slider and contains the track, range, and thumbs.",
3321
+ "selector": "[ngpRangeSlider]",
3229
3322
  "exportAs": [
3230
- "ngpSlider"
3323
+ "ngpRangeSlider"
3231
3324
  ],
3232
3325
  "inputs": [
3233
3326
  {
3234
- "name": "ngpSliderValue",
3327
+ "name": "ngpRangeSliderLow",
3235
3328
  "type": "number",
3236
- "description": "The value of the slider.",
3329
+ "description": "The low value of the range slider.",
3237
3330
  "isRequired": false,
3238
3331
  "defaultValue": "0"
3239
3332
  },
3240
3333
  {
3241
- "name": "ngpSliderMin",
3334
+ "name": "ngpRangeSliderHigh",
3242
3335
  "type": "number",
3243
- "description": "The minimum value of the slider.",
3336
+ "description": "The high value of the range slider.",
3337
+ "isRequired": false,
3338
+ "defaultValue": "100"
3339
+ },
3340
+ {
3341
+ "name": "ngpRangeSliderMin",
3342
+ "type": "number",
3343
+ "description": "The minimum value of the range slider.",
3244
3344
  "isRequired": false,
3245
3345
  "defaultValue": "0"
3246
3346
  },
3247
3347
  {
3248
- "name": "ngpSliderMax",
3348
+ "name": "ngpRangeSliderMax",
3249
3349
  "type": "number",
3250
- "description": "The maximum value of the slider.",
3350
+ "description": "The maximum value of the range slider.",
3251
3351
  "isRequired": false,
3252
3352
  "defaultValue": "100"
3253
3353
  },
3254
3354
  {
3255
- "name": "ngpSliderStep",
3355
+ "name": "ngpRangeSliderStep",
3256
3356
  "type": "number",
3257
- "description": "The step value of the slider.",
3357
+ "description": "The step value of the range slider.",
3258
3358
  "isRequired": false,
3259
3359
  "defaultValue": "1"
3260
3360
  },
3261
3361
  {
3262
- "name": "ngpSliderOrientation",
3362
+ "name": "ngpRangeSliderOrientation",
3263
3363
  "type": "'horizontal' | 'vertical'",
3264
- "description": "The orientation of the slider.",
3364
+ "description": "The orientation of the range slider.",
3265
3365
  "isRequired": false,
3266
3366
  "defaultValue": "'horizontal'"
3267
3367
  },
3268
3368
  {
3269
- "name": "ngpSliderDisabled",
3369
+ "name": "ngpRangeSliderDisabled",
3270
3370
  "type": "boolean",
3271
- "description": "The disabled state of the slider.",
3371
+ "description": "The disabled state of the range slider.",
3272
3372
  "isRequired": false,
3273
3373
  "defaultValue": "false"
3274
3374
  }
3275
3375
  ],
3276
3376
  "outputs": [
3277
3377
  {
3278
- "name": "ngpSliderValueChange",
3378
+ "name": "ngpRangeSliderLowChange",
3279
3379
  "type": "number",
3280
- "description": "Emits when the value changes."
3380
+ "description": "Emits when the low value changes."
3381
+ },
3382
+ {
3383
+ "name": "ngpRangeSliderHighChange",
3384
+ "type": "number",
3385
+ "description": "Emits when the high value changes."
3281
3386
  }
3282
3387
  ]
3283
3388
  },
3284
- "NgpRangeSlider": {
3285
- "name": "NgpRangeSlider",
3286
- "description": "Apply the `ngpRangeSlider` directive to an element that represents the range slider and contains the track, range, and thumbs.",
3287
- "selector": "[ngpRangeSlider]",
3389
+ "NgpSlider": {
3390
+ "name": "NgpSlider",
3391
+ "description": "Apply the `ngpSlider` directive to an element that represents the slider and contains the track, range, and thumb.",
3392
+ "selector": "[ngpSlider]",
3288
3393
  "exportAs": [
3289
- "ngpRangeSlider"
3394
+ "ngpSlider"
3290
3395
  ],
3291
3396
  "inputs": [
3292
3397
  {
3293
- "name": "ngpRangeSliderLow",
3398
+ "name": "ngpSliderValue",
3294
3399
  "type": "number",
3295
- "description": "The low value of the range slider.",
3400
+ "description": "The value of the slider.",
3296
3401
  "isRequired": false,
3297
3402
  "defaultValue": "0"
3298
3403
  },
3299
3404
  {
3300
- "name": "ngpRangeSliderHigh",
3301
- "type": "number",
3302
- "description": "The high value of the range slider.",
3303
- "isRequired": false,
3304
- "defaultValue": "100"
3305
- },
3306
- {
3307
- "name": "ngpRangeSliderMin",
3405
+ "name": "ngpSliderMin",
3308
3406
  "type": "number",
3309
- "description": "The minimum value of the range slider.",
3407
+ "description": "The minimum value of the slider.",
3310
3408
  "isRequired": false,
3311
3409
  "defaultValue": "0"
3312
3410
  },
3313
3411
  {
3314
- "name": "ngpRangeSliderMax",
3412
+ "name": "ngpSliderMax",
3315
3413
  "type": "number",
3316
- "description": "The maximum value of the range slider.",
3414
+ "description": "The maximum value of the slider.",
3317
3415
  "isRequired": false,
3318
3416
  "defaultValue": "100"
3319
3417
  },
3320
3418
  {
3321
- "name": "ngpRangeSliderStep",
3419
+ "name": "ngpSliderStep",
3322
3420
  "type": "number",
3323
- "description": "The step value of the range slider.",
3421
+ "description": "The step value of the slider.",
3324
3422
  "isRequired": false,
3325
3423
  "defaultValue": "1"
3326
3424
  },
3327
3425
  {
3328
- "name": "ngpRangeSliderOrientation",
3426
+ "name": "ngpSliderOrientation",
3329
3427
  "type": "'horizontal' | 'vertical'",
3330
- "description": "The orientation of the range slider.",
3428
+ "description": "The orientation of the slider.",
3331
3429
  "isRequired": false,
3332
3430
  "defaultValue": "'horizontal'"
3333
3431
  },
3334
3432
  {
3335
- "name": "ngpRangeSliderDisabled",
3433
+ "name": "ngpSliderDisabled",
3336
3434
  "type": "boolean",
3337
- "description": "The disabled state of the range slider.",
3435
+ "description": "The disabled state of the slider.",
3338
3436
  "isRequired": false,
3339
3437
  "defaultValue": "false"
3340
3438
  }
3341
3439
  ],
3342
3440
  "outputs": [
3343
3441
  {
3344
- "name": "ngpRangeSliderLowChange",
3345
- "type": "number",
3346
- "description": "Emits when the low value changes."
3347
- },
3348
- {
3349
- "name": "ngpRangeSliderHighChange",
3442
+ "name": "ngpSliderValueChange",
3350
3443
  "type": "number",
3351
- "description": "Emits when the high value changes."
3444
+ "description": "Emits when the value changes."
3352
3445
  }
3353
3446
  ]
3354
3447
  },
@@ -3381,6 +3474,13 @@
3381
3474
  "description": "Determine if the switch is disabled.\n@default false",
3382
3475
  "isRequired": false,
3383
3476
  "defaultValue": "false"
3477
+ },
3478
+ {
3479
+ "name": "ngpSwitchRequired",
3480
+ "type": "boolean",
3481
+ "description": "Whether the switch is required.\n@default false",
3482
+ "isRequired": false,
3483
+ "defaultValue": "false"
3384
3484
  }
3385
3485
  ],
3386
3486
  "outputs": [
@@ -3469,6 +3569,13 @@
3469
3569
  "description": "Whether tabs should activate on focus",
3470
3570
  "isRequired": false,
3471
3571
  "defaultValue": "true"
3572
+ },
3573
+ {
3574
+ "name": "ngpTabsetWrap",
3575
+ "type": "boolean",
3576
+ "description": "Whether keyboard focus should wrap around the tab list.\n@default true (from the global tabs config)",
3577
+ "isRequired": false,
3578
+ "defaultValue": "true"
3472
3579
  }
3473
3580
  ],
3474
3581
  "outputs": [
@@ -3553,7 +3660,7 @@
3553
3660
  "inputs": [
3554
3661
  {
3555
3662
  "name": "ngpToggleGroupItemValue",
3556
- "type": "string | undefined",
3663
+ "type": "T | undefined",
3557
3664
  "description": "The value of the item.",
3558
3665
  "isRequired": true
3559
3666
  },
@@ -3605,17 +3712,23 @@
3605
3712
  },
3606
3713
  {
3607
3714
  "name": "ngpToggleGroupValue",
3608
- "type": "string[] | undefined",
3715
+ "type": "T[] | undefined",
3609
3716
  "description": "The selected value(s) of the toggle group.",
3610
3717
  "isRequired": false
3611
3718
  },
3612
3719
  {
3613
3720
  "name": "ngpToggleGroupDefaultValue",
3614
- "type": "string[]",
3721
+ "type": "T[]",
3615
3722
  "description": "The default selected value(s) for uncontrolled usage.\n@default []",
3616
3723
  "isRequired": false,
3617
3724
  "defaultValue": "[]"
3618
3725
  },
3726
+ {
3727
+ "name": "ngpToggleGroupCompareWith",
3728
+ "type": "(a: T, b: T) => boolean",
3729
+ "description": "The comparator used to determine whether two values are equal.\nUseful when values are objects and equality should be checked by value rather than by reference.\n@default (a, b) => a === b",
3730
+ "isRequired": false
3731
+ },
3619
3732
  {
3620
3733
  "name": "ngpToggleGroupDisabled",
3621
3734
  "type": "boolean",
@@ -3627,7 +3740,7 @@
3627
3740
  "outputs": [
3628
3741
  {
3629
3742
  "name": "ngpToggleGroupValueChange",
3630
- "type": "string[]",
3743
+ "type": "T[]",
3631
3744
  "description": "Emits when the value of the toggle group changes."
3632
3745
  }
3633
3746
  ]
@@ -4157,6 +4270,41 @@
4157
4270
  }
4158
4271
  ]
4159
4272
  },
4273
+ "NgpPasswordConfig": {
4274
+ "name": "NgpPasswordConfig",
4275
+ "properties": [
4276
+ {
4277
+ "name": "showLabel",
4278
+ "type": "string",
4279
+ "description": "The accessible label for the toggle button when the password is hidden.",
4280
+ "default": "'Show password'"
4281
+ },
4282
+ {
4283
+ "name": "hideLabel",
4284
+ "type": "string",
4285
+ "description": "The accessible label for the toggle button when the password is visible.",
4286
+ "default": "'Hide password'"
4287
+ },
4288
+ {
4289
+ "name": "shownAnnouncement",
4290
+ "type": "string",
4291
+ "description": "The message announced to screen readers when the password becomes visible.",
4292
+ "default": "'Your password is shown'"
4293
+ },
4294
+ {
4295
+ "name": "hiddenAnnouncement",
4296
+ "type": "string",
4297
+ "description": "The message announced to screen readers when the password becomes hidden.",
4298
+ "default": "'Your password is hidden'"
4299
+ },
4300
+ {
4301
+ "name": "ignorePasswordManagers",
4302
+ "type": "boolean",
4303
+ "description": "Whether to opt the input out of password manager injection by default.",
4304
+ "default": "false"
4305
+ }
4306
+ ]
4307
+ },
4160
4308
  "NgpPopoverConfig": {
4161
4309
  "name": "NgpPopoverConfig",
4162
4310
  "properties": [
@@ -311,7 +311,7 @@
311
311
  }
312
312
  ],
313
313
  "reusableComponent": {
314
- "code": "import { Component } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\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()\n .checkedChange.pipe(takeUntilDestroyed())\n .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",
314
+ "code": "import { Component } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\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()\n .checkedChange.pipe(takeUntilDestroyed())\n .subscribe(checked => this.onChangeFn?.(checked));\n }\n\n writeValue(checked: boolean): void {\n // writing a value from the model must not re-emit through onChange\n this.state().setChecked(checked, { emit: false });\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",
315
315
  "hasVariants": false,
316
316
  "hasSizes": true
317
317
  }
@@ -373,7 +373,7 @@
373
373
  }
374
374
  ],
375
375
  "reusableComponent": {
376
- "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",
376
+ "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 | undefined>;\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, clear the value and notify the form control\n if (this.filter() === '') {\n this.value.set(undefined);\n this.onChange?.(undefined);\n } else {\n // otherwise set the filter value to the selected value\n this.filter.set(this.value() ?? '');\n }\n }\n}\n",
377
377
  "hasVariants": false,
378
378
  "hasSizes": true
379
379
  }
@@ -486,7 +486,7 @@
486
486
  }
487
487
  ],
488
488
  "reusableComponent": {
489
- "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",
489
+ "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 // writing a value from the model must not re-emit through onChange\n this.state().select(date, false, { emit: false });\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",
490
490
  "hasVariants": false,
491
491
  "hasSizes": true
492
492
  }
@@ -508,6 +508,16 @@
508
508
  "accessibility": [],
509
509
  "examples": []
510
510
  },
511
+ {
512
+ "name": "date-time-date-fns",
513
+ "entryPoint": "ng-primitives/date-time-date-fns",
514
+ "exports": [],
515
+ "hasSecondaryEntryPoint": true,
516
+ "category": "date-time",
517
+ "description": "date-time-date-fns primitive component",
518
+ "accessibility": [],
519
+ "examples": []
520
+ },
511
521
  {
512
522
  "name": "date-time-luxon",
513
523
  "entryPoint": "ng-primitives/date-time-luxon",
@@ -1119,7 +1129,59 @@
1119
1129
  }
1120
1130
  ],
1121
1131
  "reusableComponent": {
1122
- "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",
1132
+ "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 // writing a value from the model must not re-emit through onChange\n this.state().setPage(value, { emit: false });\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 /** Reflect the form control's disabled state onto the pagination. */\n setDisabledState(isDisabled: boolean): void {\n this.state().setDisabled(isDisabled);\n }\n}\n",
1133
+ "hasVariants": false,
1134
+ "hasSizes": true
1135
+ }
1136
+ },
1137
+ {
1138
+ "name": "password",
1139
+ "entryPoint": "ng-primitives/password",
1140
+ "exports": [
1141
+ "defaultPasswordConfig",
1142
+ "injectPasswordConfig",
1143
+ "NgpPasswordConfig",
1144
+ "NgpPasswordConfigToken",
1145
+ "providePasswordConfig",
1146
+ "NgpPasswordInput",
1147
+ "injectPasswordInputState",
1148
+ "ngpPasswordInput",
1149
+ "NgpPasswordInputProps",
1150
+ "NgpPasswordInputState",
1151
+ "providePasswordInputState",
1152
+ "NgpPasswordToggle",
1153
+ "injectPasswordToggleState",
1154
+ "ngpPasswordToggle",
1155
+ "NgpPasswordToggleProps",
1156
+ "NgpPasswordToggleState",
1157
+ "providePasswordToggleState",
1158
+ "NgpPassword",
1159
+ "injectPasswordState",
1160
+ "ngpPassword",
1161
+ "NgpPasswordProps",
1162
+ "NgpPasswordState",
1163
+ "providePasswordState"
1164
+ ],
1165
+ "hasSecondaryEntryPoint": true,
1166
+ "category": "utility",
1167
+ "description": "The password primitive is a form control that lets users reveal and hide the contents of a password field with an accessible toggle button.",
1168
+ "accessibility": [
1169
+ "screen reader"
1170
+ ],
1171
+ "examples": [
1172
+ {
1173
+ "name": "example-0",
1174
+ "code": "<div ngpFormField>\n <label ngpLabel>Password</label>\n <div ngpPassword #password=\"ngpPassword\">\n <input ngpPasswordInput type=\"password\" autocomplete=\"current-password\" />\n <button ngpButton ngpPasswordToggle>\n <ng-icon [name]=\"password.isVisible() ? 'lucideEyeOff' : 'lucideEye'\" />\n </button>\n </div>\n</div>",
1175
+ "description": "Usage"
1176
+ },
1177
+ {
1178
+ "name": "example-1",
1179
+ "code": "import { providePasswordConfig } from 'ng-primitives/password';\n\nbootstrapApplication(AppComponent, {\n providers: [\n providePasswordConfig({\n showLabel: 'Show password',\n hideLabel: 'Hide password',\n shownAnnouncement: 'Your password is shown',\n hiddenAnnouncement: 'Your password is hidden',\n ignorePasswordManagers: false,\n }),\n ],\n});",
1180
+ "description": "Global Configuration"
1181
+ }
1182
+ ],
1183
+ "reusableComponent": {
1184
+ "code": "import { Component, input, signal } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { lucideEye, lucideEyeOff } from '@ng-icons/lucide';\nimport { NgpButton } from 'ng-primitives/button';\nimport {\n injectPasswordState,\n NgpPassword,\n NgpPasswordInput,\n NgpPasswordToggle,\n} from 'ng-primitives/password';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-password',\n hostDirectives: [NgpPassword],\n imports: [NgIcon, NgpPasswordInput, NgpPasswordToggle, NgpButton],\n providers: [provideValueAccessor(Password), provideIcons({ lucideEye, lucideEyeOff })],\n template: `\n <input\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n (input)=\"onValueChange($event)\"\n ngpPasswordInput\n type=\"password\"\n autocomplete=\"current-password\"\n />\n <button ngpButton ngpPasswordToggle>\n <ng-icon [name]=\"state().visible() ? 'lucideEyeOff' : 'lucideEye'\" />\n </button>\n `,\n styles: `\n :host {\n display: block;\n position: relative;\n }\n\n [ngpPasswordInput] {\n height: 2.125rem;\n width: 100%;\n border-radius: 0.5rem;\n padding: 0 40px 0 12px;\n border: none;\n background: var(--ngp-background);\n color: var(--ngp-text-primary);\n font-size: 0.875rem;\n letter-spacing: -0.006em;\n box-shadow: var(--ngp-input-shadow);\n outline: none;\n }\n\n [ngpPasswordInput][data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n [ngpPasswordInput]::placeholder {\n color: var(--ngp-text-placeholder);\n }\n\n [ngpPasswordToggle] {\n position: absolute;\n top: 0;\n right: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n height: 2.125rem;\n width: 2.125rem;\n border: none;\n border-radius: 0.5rem;\n background-color: transparent;\n color: var(--ngp-text-tertiary);\n font-size: 1.125rem;\n cursor: pointer;\n outline: none;\n transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n [ngpPasswordToggle][data-hover] {\n color: var(--ngp-text-secondary);\n }\n\n [ngpPasswordToggle][data-visible] {\n color: var(--ngp-primary);\n }\n\n [ngpPasswordToggle][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: -2px;\n }\n `,\n host: {\n '(focusout)': 'onFocusOut($event)',\n },\n})\nexport class Password implements ControlValueAccessor {\n /** Access the password state to read the visibility. */\n protected readonly state = injectPasswordState();\n\n /** The placeholder text */\n readonly placeholder = input<string>('');\n\n /** The current value */\n protected value = signal<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.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 protected onValueChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n this.value.set(input.value);\n this.onChange?.(input.value);\n }\n\n protected onFocusOut(event: FocusEvent): void {\n // only mark as touched when focus leaves the component, not when moving\n // between the input and the toggle button\n const host = event.currentTarget as HTMLElement;\n if (!host.contains(event.relatedTarget as Node | null)) {\n this.onTouched?.();\n }\n }\n}\n",
1123
1185
  "hasVariants": false,
1124
1186
  "hasSizes": true
1125
1187
  }
@@ -1580,7 +1642,7 @@
1580
1642
  }
1581
1643
  ],
1582
1644
  "reusableComponent": {
1583
- "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",
1645
+ "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 // writing a value from the model must not re-emit through onChange\n this.state().setValue(value, { emit: false });\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",
1584
1646
  "hasVariants": false,
1585
1647
  "hasSizes": false
1586
1648
  }
@@ -1626,7 +1688,7 @@
1626
1688
  }
1627
1689
  ],
1628
1690
  "reusableComponent": {
1629
- "code": "import { Component } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\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()\n .checkedChange.pipe(takeUntilDestroyed())\n .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",
1691
+ "code": "import { Component } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\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()\n .checkedChange.pipe(takeUntilDestroyed())\n .subscribe(value => this.onChange?.(value));\n }\n\n /** Write a new value to the switch. */\n writeValue(value: boolean): void {\n // writing a value from the model must not re-emit through onChange\n this.switch().setChecked(value, { emit: false });\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",
1630
1692
  "hasVariants": false,
1631
1693
  "hasSizes": false
1632
1694
  }
@@ -22,14 +22,14 @@
22
22
  },
23
23
  {
24
24
  "name": "checkbox",
25
- "code": "import { Component } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\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()\n .checkedChange.pipe(takeUntilDestroyed())\n .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",
25
+ "code": "import { Component } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\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()\n .checkedChange.pipe(takeUntilDestroyed())\n .subscribe(checked => this.onChangeFn?.(checked));\n }\n\n writeValue(checked: boolean): void {\n // writing a value from the model must not re-emit through onChange\n this.state().setChecked(checked, { emit: false });\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
26
  "primitive": "checkbox",
27
27
  "hasVariants": false,
28
28
  "hasSizes": true
29
29
  },
30
30
  {
31
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",
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 | undefined>;\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, clear the value and notify the form control\n if (this.filter() === '') {\n this.value.set(undefined);\n this.onChange?.(undefined);\n } else {\n // otherwise set the filter value to the selected value\n this.filter.set(this.value() ?? '');\n }\n }\n}\n",
33
33
  "primitive": "combobox",
34
34
  "hasVariants": false,
35
35
  "hasSizes": true
@@ -43,7 +43,7 @@
43
43
  },
44
44
  {
45
45
  "name": "date-picker",
46
- "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",
46
+ "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 // writing a value from the model must not re-emit through onChange\n this.state().select(date, false, { emit: false });\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",
47
47
  "primitive": "date-picker",
48
48
  "hasVariants": false,
49
49
  "hasSizes": true
@@ -120,11 +120,18 @@
120
120
  },
121
121
  {
122
122
  "name": "pagination",
123
- "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",
123
+ "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 // writing a value from the model must not re-emit through onChange\n this.state().setPage(value, { emit: false });\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 /** Reflect the form control's disabled state onto the pagination. */\n setDisabledState(isDisabled: boolean): void {\n this.state().setDisabled(isDisabled);\n }\n}\n",
124
124
  "primitive": "pagination",
125
125
  "hasVariants": false,
126
126
  "hasSizes": true
127
127
  },
128
+ {
129
+ "name": "password",
130
+ "code": "import { Component, input, signal } from '@angular/core';\nimport { ControlValueAccessor } from '@angular/forms';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { lucideEye, lucideEyeOff } from '@ng-icons/lucide';\nimport { NgpButton } from 'ng-primitives/button';\nimport {\n injectPasswordState,\n NgpPassword,\n NgpPasswordInput,\n NgpPasswordToggle,\n} from 'ng-primitives/password';\nimport { ChangeFn, provideValueAccessor, TouchedFn } from 'ng-primitives/utils';\n\n@Component({\n selector: 'app-password',\n hostDirectives: [NgpPassword],\n imports: [NgIcon, NgpPasswordInput, NgpPasswordToggle, NgpButton],\n providers: [provideValueAccessor(Password), provideIcons({ lucideEye, lucideEyeOff })],\n template: `\n <input\n [value]=\"value()\"\n [placeholder]=\"placeholder()\"\n (input)=\"onValueChange($event)\"\n ngpPasswordInput\n type=\"password\"\n autocomplete=\"current-password\"\n />\n <button ngpButton ngpPasswordToggle>\n <ng-icon [name]=\"state().visible() ? 'lucideEyeOff' : 'lucideEye'\" />\n </button>\n `,\n styles: `\n :host {\n display: block;\n position: relative;\n }\n\n [ngpPasswordInput] {\n height: 2.125rem;\n width: 100%;\n border-radius: 0.5rem;\n padding: 0 40px 0 12px;\n border: none;\n background: var(--ngp-background);\n color: var(--ngp-text-primary);\n font-size: 0.875rem;\n letter-spacing: -0.006em;\n box-shadow: var(--ngp-input-shadow);\n outline: none;\n }\n\n [ngpPasswordInput][data-focus] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: 2px;\n }\n\n [ngpPasswordInput]::placeholder {\n color: var(--ngp-text-placeholder);\n }\n\n [ngpPasswordToggle] {\n position: absolute;\n top: 0;\n right: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n height: 2.125rem;\n width: 2.125rem;\n border: none;\n border-radius: 0.5rem;\n background-color: transparent;\n color: var(--ngp-text-tertiary);\n font-size: 1.125rem;\n cursor: pointer;\n outline: none;\n transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n [ngpPasswordToggle][data-hover] {\n color: var(--ngp-text-secondary);\n }\n\n [ngpPasswordToggle][data-visible] {\n color: var(--ngp-primary);\n }\n\n [ngpPasswordToggle][data-focus-visible] {\n outline: 2px solid var(--ngp-focus-ring);\n outline-offset: -2px;\n }\n `,\n host: {\n '(focusout)': 'onFocusOut($event)',\n },\n})\nexport class Password implements ControlValueAccessor {\n /** Access the password state to read the visibility. */\n protected readonly state = injectPasswordState();\n\n /** The placeholder text */\n readonly placeholder = input<string>('');\n\n /** The current value */\n protected value = signal<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.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 protected onValueChange(event: Event): void {\n const input = event.target as HTMLInputElement;\n this.value.set(input.value);\n this.onChange?.(input.value);\n }\n\n protected onFocusOut(event: FocusEvent): void {\n // only mark as touched when focus leaves the component, not when moving\n // between the input and the toggle button\n const host = event.currentTarget as HTMLElement;\n if (!host.contains(event.relatedTarget as Node | null)) {\n this.onTouched?.();\n }\n }\n}\n",
131
+ "primitive": "password",
132
+ "hasVariants": false,
133
+ "hasSizes": true
134
+ },
128
135
  {
129
136
  "name": "popover",
130
137
  "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",
@@ -148,7 +155,7 @@
148
155
  },
149
156
  {
150
157
  "name": "range-slider",
151
- "code": "import { Component, input } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ControlValueAccessor } from '@angular/forms';\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';\nimport { merge } from 'rxjs';\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 merge(this.state().lowChange, this.state().highChange)\n .pipe(takeUntilDestroyed())\n .subscribe(() => this.onChange?.([this.state().low(), this.state().high()]));\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().setDisabled(isDisabled);\n }\n}\n",
158
+ "code": "import { Component, input } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { ControlValueAccessor } from '@angular/forms';\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';\nimport { merge } from 'rxjs';\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 merge(this.state().lowChange, this.state().highChange)\n .pipe(takeUntilDestroyed())\n .subscribe(() => this.onChange?.([this.state().low(), this.state().high()]));\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 // writing a value from the model must not re-emit through onChange\n this.state().setLowValue(low, { emit: false });\n this.state().setHighValue(high, { emit: false });\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().setDisabled(isDisabled);\n }\n}\n",
152
159
  "primitive": "range-slider",
153
160
  "hasVariants": false,
154
161
  "hasSizes": false
@@ -176,14 +183,14 @@
176
183
  },
177
184
  {
178
185
  "name": "slider",
179
- "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",
186
+ "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 // writing a value from the model must not re-emit through onChange\n this.state().setValue(value, { emit: false });\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",
180
187
  "primitive": "slider",
181
188
  "hasVariants": false,
182
189
  "hasSizes": false
183
190
  },
184
191
  {
185
192
  "name": "switch",
186
- "code": "import { Component } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\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()\n .checkedChange.pipe(takeUntilDestroyed())\n .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",
193
+ "code": "import { Component } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\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()\n .checkedChange.pipe(takeUntilDestroyed())\n .subscribe(value => this.onChange?.(value));\n }\n\n /** Write a new value to the switch. */\n writeValue(value: boolean): void {\n // writing a value from the model must not re-emit through onChange\n this.switch().setChecked(value, { emit: false });\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",
187
194
  "primitive": "switch",
188
195
  "hasVariants": false,
189
196
  "hasSizes": false