@spartan-ng/brain 0.0.1-alpha.719 → 1.0.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.
Files changed (39) hide show
  1. package/README.md +3 -3
  2. package/fesm2022/spartan-ng-brain-accordion.mjs +7 -58
  3. package/fesm2022/spartan-ng-brain-accordion.mjs.map +1 -1
  4. package/fesm2022/spartan-ng-brain-alert-dialog.mjs +1 -2
  5. package/fesm2022/spartan-ng-brain-alert-dialog.mjs.map +1 -1
  6. package/fesm2022/spartan-ng-brain-autocomplete.mjs +3 -1
  7. package/fesm2022/spartan-ng-brain-autocomplete.mjs.map +1 -1
  8. package/fesm2022/spartan-ng-brain-collapsible.mjs +8 -56
  9. package/fesm2022/spartan-ng-brain-collapsible.mjs.map +1 -1
  10. package/fesm2022/spartan-ng-brain-combobox.mjs +22 -6
  11. package/fesm2022/spartan-ng-brain-combobox.mjs.map +1 -1
  12. package/fesm2022/spartan-ng-brain-core.mjs +89 -16
  13. package/fesm2022/spartan-ng-brain-core.mjs.map +1 -1
  14. package/fesm2022/spartan-ng-brain-dialog.mjs +8 -7
  15. package/fesm2022/spartan-ng-brain-dialog.mjs.map +1 -1
  16. package/fesm2022/spartan-ng-brain-drawer.mjs +8 -8
  17. package/fesm2022/spartan-ng-brain-drawer.mjs.map +1 -1
  18. package/fesm2022/spartan-ng-brain-field.mjs +7 -2
  19. package/fesm2022/spartan-ng-brain-field.mjs.map +1 -1
  20. package/fesm2022/spartan-ng-brain-navigation-menu.mjs +124 -34
  21. package/fesm2022/spartan-ng-brain-navigation-menu.mjs.map +1 -1
  22. package/fesm2022/spartan-ng-brain-overlay.mjs +27 -14
  23. package/fesm2022/spartan-ng-brain-overlay.mjs.map +1 -1
  24. package/fesm2022/spartan-ng-brain-select.mjs +3 -1
  25. package/fesm2022/spartan-ng-brain-select.mjs.map +1 -1
  26. package/fesm2022/spartan-ng-brain-tooltip.mjs +97 -14
  27. package/fesm2022/spartan-ng-brain-tooltip.mjs.map +1 -1
  28. package/package.json +6 -2
  29. package/types/spartan-ng-brain-accordion.d.ts +4 -22
  30. package/types/spartan-ng-brain-autocomplete.d.ts +1 -0
  31. package/types/spartan-ng-brain-collapsible.d.ts +5 -22
  32. package/types/spartan-ng-brain-combobox.d.ts +11 -2
  33. package/types/spartan-ng-brain-core.d.ts +40 -9
  34. package/types/spartan-ng-brain-dialog.d.ts +1 -3
  35. package/types/spartan-ng-brain-drawer.d.ts +3 -3
  36. package/types/spartan-ng-brain-navigation-menu.d.ts +9 -2
  37. package/types/spartan-ng-brain-overlay.d.ts +4 -5
  38. package/types/spartan-ng-brain-select.d.ts +1 -0
  39. package/types/spartan-ng-brain-tooltip.d.ts +38 -4
@@ -1 +1 @@
1
- {"version":3,"file":"spartan-ng-brain-field.mjs","sources":["../../../../libs/brain/field/src/lib/brn-field-aria.service.ts","../../../../libs/brain/field/src/lib/brn-field.ts","../../../../libs/brain/field/src/lib/brn-labelable.ts","../../../../libs/brain/field/src/lib/brn-field-control.ts","../../../../libs/brain/field/src/lib/brn-field-control-described-by.ts","../../../../libs/brain/field/src/index.ts","../../../../libs/brain/field/src/spartan-ng-brain-field.ts"],"sourcesContent":["import { computed, Injectable, signal } from '@angular/core';\n\n@Injectable()\nexport class BrnFieldA11yService {\n\tprivate readonly _descriptions = signal<string[]>([]);\n\tprivate readonly _errors = signal<string[]>([]);\n\n\tpublic readonly describedBy = computed(() => {\n\t\tconst ids = [...this._descriptions(), ...this._errors()].filter(Boolean);\n\t\tconst uniqueIds = [...new Set(ids)];\n\t\treturn uniqueIds.length ? uniqueIds.join(' ') : null;\n\t});\n\n\tpublic registerDescription(id: string) {\n\t\tthis._descriptions.update((ids) => (ids.includes(id) ? ids : [...ids, id]));\n\t}\n\n\tpublic unregisterDescription(id: string) {\n\t\tthis._descriptions.update((ids) => ids.filter((value) => value !== id));\n\t}\n\n\tpublic registerError(id: string) {\n\t\tthis._errors.update((ids) => (ids.includes(id) ? ids : [...ids, id]));\n\t}\n\n\tpublic unregisterError(id: string) {\n\t\tthis._errors.update((ids) => ids.filter((value) => value !== id));\n\t}\n}\n","import { BooleanInput } from '@angular/cdk/coercion';\nimport { booleanAttribute, computed, Directive, input, signal } from '@angular/core';\nimport { BrnFieldA11yService } from './brn-field-aria.service';\nimport { BrnFieldControl } from './brn-field-control';\nimport { BrnLabelable } from './brn-labelable';\n\n@Directive({\n\tselector: '[brnField],brn-field',\n\tproviders: [BrnFieldA11yService],\n\thost: {\n\t\t'[attr.data-invalid]': '_invalid() ? \"true\" : null',\n\t\t'[attr.data-matches-spartan-invalid]': '_spartanInvalid() ? \"true\" : null',\n\t\t'[attr.data-touched]': '_touched() ? \"true\" : null',\n\t\t'[attr.data-dirty]': '_dirty() ? \"true\" : null',\n\t},\n})\nexport class BrnField {\n\tprivate readonly _brnFieldControl = signal<BrnFieldControl | null>(null);\n\tprivate readonly _labelable = signal<BrnLabelable | null>(null);\n\n\t/** Whether the field is invalid. Overrides the `data-invalid` attribute. */\n\tpublic readonly dataInvalid = input<boolean, BooleanInput>(false, {\n\t\ttransform: booleanAttribute,\n\t\talias: 'data-invalid',\n\t});\n\n\t/**\n\t * Whether to force the field into an invalid state, regardless of the form control's state.\n\t * Overrides both the `data-invalid` and `data-matches-spartan-invalid` attributes.\n\t */\n\tpublic readonly forceInvalid = input<boolean, BooleanInput>(false, {\n\t\ttransform: booleanAttribute,\n\t});\n\n\tprotected readonly _invalid = computed(() => {\n\t\tif (this.forceInvalid() || this.dataInvalid()) return true;\n\n\t\tconst control = this._brnFieldControl();\n\t\tif (!control || !control.ngControl) return false;\n\n\t\treturn control.controlState()?.invalid;\n\t});\n\n\tprotected readonly _spartanInvalid = computed(() => {\n\t\treturn this.forceInvalid() || (this._brnFieldControl()?.controlState()?.spartanInvalid ?? null);\n\t});\n\n\tprotected readonly _dirty = computed(() => {\n\t\treturn this._brnFieldControl()?.controlState()?.dirty ?? null;\n\t});\n\n\tprotected readonly _touched = computed(() => {\n\t\treturn this._brnFieldControl()?.controlState()?.touched ?? null;\n\t});\n\n\tpublic readonly labelableId = computed(() => this._brnFieldControl()?.id?.() ?? this._labelable()?.labelableId());\n\n\tpublic readonly errors = computed(() => this._brnFieldControl()?.errors() ?? null);\n\tpublic readonly controlState = computed(() => this._brnFieldControl()?.controlState() ?? null);\n\n\tpublic registerFieldControl(fieldControl: BrnFieldControl) {\n\t\tthis._brnFieldControl.set(fieldControl);\n\t}\n\n\tpublic registerLabelable(labelable: BrnLabelable) {\n\t\tthis._labelable.set(labelable);\n\t}\n}\n","import { type ExistingProvider, inject, InjectionToken, type Signal, type Type } from '@angular/core';\n\nexport interface BrnLabelable {\n\tlabelableId: Signal<string | null | undefined>;\n}\n\nexport const BrnLabelable = new InjectionToken<BrnLabelable>('BrnLabelable');\n\nexport function provideBrnLabelable(labelable: Type<BrnLabelable>): ExistingProvider {\n\treturn { provide: BrnLabelable, useExisting: labelable };\n}\n\nexport function injectBrnLabelable(): BrnLabelable | null {\n\treturn inject(BrnLabelable, { optional: true });\n}\n","import { computed, DestroyRef, Directive, DoCheck, effect, inject, Injector, OnInit, signal } from '@angular/core';\nimport { type AbstractControl, FormGroupDirective, NgControl, NgForm } from '@angular/forms';\nimport { createStateTracker, ErrorStateMatcher, type StateTracker } from '@spartan-ng/brain/forms';\nimport { BrnField } from './brn-field';\nimport { BrnLabelable } from './brn-labelable';\n\n@Directive()\nexport class BrnFieldControl implements OnInit, DoCheck {\n\tprivate readonly _injector = inject(Injector);\n\tprivate readonly _errorStateMatcher = inject(ErrorStateMatcher);\n\tprivate readonly _parentForm = inject(NgForm, { optional: true });\n\tprivate readonly _parentFormGroup = inject(FormGroupDirective, { optional: true });\n\tprivate readonly _field = inject(BrnField, { optional: true });\n\tprivate readonly _destroyRef = inject(DestroyRef);\n\n\tprivate _idEffectRef?: ReturnType<typeof effect>;\n\tprivate readonly _stateTracker = signal<StateTracker | null>(null);\n\t/** Sentinel value to differentiate \"never checked\" from \"control is null\". */\n\tprivate _lastControl: AbstractControl | null = null;\n\n\t/** Gets the AbstractControlDirective for this control. */\n\tpublic ngControl: NgControl | null = null;\n\n\tpublic readonly id = signal<string | null | undefined>(undefined);\n\n\tpublic readonly controlState = computed(() => this._stateTracker()?.controlState() ?? null);\n\tpublic readonly errors = computed(() => this._stateTracker()?.errors() ?? null);\n\tpublic readonly dirty = computed(() => this._stateTracker()?.dirty() ?? null);\n\tpublic readonly invalid = computed(() => this._stateTracker()?.invalid() ?? null);\n\tpublic readonly spartanInvalid = computed(() => this._stateTracker()?.spartanInvalid() ?? null);\n\tpublic readonly touched = computed(() => this._stateTracker()?.touched() ?? null);\n\n\tconstructor() {\n\t\tthis._field?.registerFieldControl(this);\n\t\tthis._destroyRef.onDestroy(() => {\n\t\t\tthis._idEffectRef?.destroy();\n\t\t\tthis._stateTracker()?.destroy();\n\t\t});\n\t}\n\n\tngOnInit(): void {\n\t\tthis.ngControl = this._injector.get(NgControl, null);\n\n\t\t// Try to sync the tracker eagerly.\n\t\tthis._syncTracker();\n\n\t\t// For template-driven forms and FormControlName, the control is often resolved\n\t\t// asynchronously by Angular's directives after our ngOnInit/ngDoCheck.\n\t\t// Schedule a one-time microtask to catch the initial resolution.\n\t\tif (this.ngControl) {\n\t\t\tPromise.resolve().then(() => {\n\t\t\t\tthis._syncTracker();\n\t\t\t});\n\t\t}\n\n\t\tconst labelable = this._injector.get(BrnLabelable, null);\n\t\tif (labelable) {\n\t\t\tthis._idEffectRef = effect(\n\t\t\t\t() => {\n\t\t\t\t\tthis.id.set(labelable.labelableId());\n\t\t\t\t},\n\t\t\t\t{ injector: this._injector },\n\t\t\t);\n\t\t}\n\t}\n\n\t// Re-evaluate the control reference on every change detection cycle because\n\t// the underlying AbstractControl may change when [formControl] rebinds to a new instance.\n\t// When the instance changes we tear down the old tracker and create a fresh one.\n\tngDoCheck(): void {\n\t\tthis._syncTracker();\n\t}\n\n\t/** @returns true if the control reference changed */\n\tprivate _syncTracker(): void {\n\t\tif (!this.ngControl) return;\n\t\tconst currentControl = this.ngControl.control ?? null;\n\t\tif (currentControl === this._lastControl) return;\n\t\tthis._lastControl = currentControl;\n\t\tthis._stateTracker()?.destroy();\n\t\tthis._stateTracker.set(\n\t\t\tcurrentControl\n\t\t\t\t? createStateTracker(this.ngControl, this._errorStateMatcher, this._parentFormGroup, this._parentForm)\n\t\t\t\t: null,\n\t\t);\n\t}\n}\n","import { computed, Directive, inject, input } from '@angular/core';\nimport { BrnFieldA11yService } from './brn-field-aria.service';\n\n@Directive({\n\tselector: '[brnFieldControlDescribedBy]',\n\thost: {\n\t\t'[attr.aria-describedby]': '_computedDescribedBy()',\n\t},\n})\nexport class BrnFieldControlDescribedBy {\n\tpublic readonly describedBy = input<string | null>(null, { alias: 'aria-describedby' });\n\tprivate readonly _a11y = inject(BrnFieldA11yService, { optional: true });\n\n\tprotected readonly _computedDescribedBy = computed(() => {\n\t\tconst manual = this.describedBy();\n\t\tconst manualList = manual ? manual.split(/\\s+/).filter(Boolean) : [];\n\t\tconst fieldIds = this._a11y?.describedBy() ?? null;\n\t\tconst fieldList = fieldIds ? fieldIds.split(/\\s+/).filter(Boolean) : [];\n\n\t\tconst combined = [...new Set([...manualList, ...fieldList])];\n\t\treturn combined.length ? combined.join(' ') : null;\n\t});\n}\n","import { BrnField } from './lib/brn-field';\nimport { BrnFieldControl } from './lib/brn-field-control';\nimport { BrnFieldControlDescribedBy } from './lib/brn-field-control-described-by';\n\nexport * from './lib/brn-field';\nexport * from './lib/brn-field-aria.service';\nexport * from './lib/brn-field-control';\nexport * from './lib/brn-field-control-described-by';\nexport * from './lib/brn-labelable';\n\nexport const BrnFieldImports = [BrnField, BrnFieldControl, BrnFieldControlDescribedBy] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"spartan-ng-brain-field.mjs","sources":["../../../../libs/brain/field/src/lib/brn-field-aria.service.ts","../../../../libs/brain/field/src/lib/brn-field.ts","../../../../libs/brain/field/src/lib/brn-labelable.ts","../../../../libs/brain/field/src/lib/brn-field-control.ts","../../../../libs/brain/field/src/lib/brn-field-control-described-by.ts","../../../../libs/brain/field/src/index.ts","../../../../libs/brain/field/src/spartan-ng-brain-field.ts"],"sourcesContent":["import { computed, Injectable, signal } from '@angular/core';\n\n@Injectable()\nexport class BrnFieldA11yService {\n\tprivate readonly _descriptions = signal<string[]>([]);\n\tprivate readonly _errors = signal<string[]>([]);\n\n\tpublic readonly describedBy = computed(() => {\n\t\tconst ids = [...this._descriptions(), ...this._errors()].filter(Boolean);\n\t\tconst uniqueIds = [...new Set(ids)];\n\t\treturn uniqueIds.length ? uniqueIds.join(' ') : null;\n\t});\n\n\tpublic registerDescription(id: string) {\n\t\tthis._descriptions.update((ids) => (ids.includes(id) ? ids : [...ids, id]));\n\t}\n\n\tpublic unregisterDescription(id: string) {\n\t\tthis._descriptions.update((ids) => ids.filter((value) => value !== id));\n\t}\n\n\tpublic registerError(id: string) {\n\t\tthis._errors.update((ids) => (ids.includes(id) ? ids : [...ids, id]));\n\t}\n\n\tpublic unregisterError(id: string) {\n\t\tthis._errors.update((ids) => ids.filter((value) => value !== id));\n\t}\n}\n","import { BooleanInput } from '@angular/cdk/coercion';\nimport { booleanAttribute, computed, Directive, input, signal } from '@angular/core';\nimport { BrnFieldA11yService } from './brn-field-aria.service';\nimport { BrnFieldControl } from './brn-field-control';\nimport { BrnLabelable } from './brn-labelable';\n\n@Directive({\n\tselector: '[brnField],brn-field',\n\tproviders: [BrnFieldA11yService],\n\thost: {\n\t\t'[attr.data-invalid]': '_invalid() ? \"true\" : null',\n\t\t'[attr.data-matches-spartan-invalid]': '_spartanInvalid() ? \"true\" : null',\n\t\t'[attr.data-touched]': '_touched() ? \"true\" : null',\n\t\t'[attr.data-dirty]': '_dirty() ? \"true\" : null',\n\t},\n})\nexport class BrnField {\n\tprivate readonly _brnFieldControl = signal<BrnFieldControl | null>(null);\n\tprivate readonly _labelable = signal<BrnLabelable | null>(null);\n\n\t/** Whether the field is invalid. Overrides the `data-invalid` attribute. */\n\tpublic readonly dataInvalid = input<boolean, BooleanInput>(false, {\n\t\ttransform: booleanAttribute,\n\t\talias: 'data-invalid',\n\t});\n\n\t/**\n\t * Whether to force the field into an invalid state, regardless of the form control's state.\n\t * Overrides both the `data-invalid` and `data-matches-spartan-invalid` attributes.\n\t */\n\tpublic readonly forceInvalid = input<boolean, BooleanInput>(false, {\n\t\ttransform: booleanAttribute,\n\t});\n\n\tprotected readonly _invalid = computed(() => {\n\t\tif (this.forceInvalid() || this.dataInvalid()) return true;\n\n\t\tconst control = this._brnFieldControl();\n\t\tif (!control || !control.ngControl) return false;\n\n\t\treturn control.controlState()?.invalid;\n\t});\n\n\tprotected readonly _spartanInvalid = computed(() => {\n\t\treturn this.forceInvalid() || (this._brnFieldControl()?.controlState()?.spartanInvalid ?? null);\n\t});\n\n\tprotected readonly _dirty = computed(() => {\n\t\treturn this._brnFieldControl()?.controlState()?.dirty ?? null;\n\t});\n\n\tprotected readonly _touched = computed(() => {\n\t\treturn this._brnFieldControl()?.controlState()?.touched ?? null;\n\t});\n\n\tpublic readonly labelableId = computed(() => this._brnFieldControl()?.id?.() ?? this._labelable()?.labelableId());\n\n\tpublic readonly errors = computed(() => this._brnFieldControl()?.errors() ?? null);\n\tpublic readonly controlState = computed(() => this._brnFieldControl()?.controlState() ?? null);\n\n\tpublic registerFieldControl(fieldControl: BrnFieldControl) {\n\t\tthis._brnFieldControl.set(fieldControl);\n\t}\n\n\tpublic registerLabelable(labelable: BrnLabelable) {\n\t\tthis._labelable.set(labelable);\n\t}\n}\n","import { type ExistingProvider, inject, InjectionToken, type Signal, type Type } from '@angular/core';\n\nexport interface BrnLabelable {\n\tlabelableId: Signal<string | null | undefined>;\n}\n\nexport const BrnLabelable = new InjectionToken<BrnLabelable>('BrnLabelable');\n\nexport function provideBrnLabelable(labelable: Type<BrnLabelable>): ExistingProvider {\n\treturn { provide: BrnLabelable, useExisting: labelable };\n}\n\nexport function injectBrnLabelable(): BrnLabelable | null {\n\treturn inject(BrnLabelable, { optional: true });\n}\n","import { computed, DestroyRef, Directive, DoCheck, effect, inject, Injector, OnInit, signal } from '@angular/core';\nimport { type AbstractControl, FormGroupDirective, NgControl, NgForm } from '@angular/forms';\nimport { createStateTracker, ErrorStateMatcher, type StateTracker } from '@spartan-ng/brain/forms';\nimport { BrnField } from './brn-field';\nimport { BrnLabelable } from './brn-labelable';\n\n@Directive()\nexport class BrnFieldControl implements OnInit, DoCheck {\n\tprivate readonly _injector = inject(Injector);\n\tprivate readonly _errorStateMatcher = inject(ErrorStateMatcher);\n\tprivate readonly _parentForm = inject(NgForm, { optional: true });\n\tprivate readonly _parentFormGroup = inject(FormGroupDirective, { optional: true });\n\tprivate readonly _field = inject(BrnField, { optional: true });\n\tprivate readonly _destroyRef = inject(DestroyRef);\n\n\tprivate _idEffectRef?: ReturnType<typeof effect>;\n\tprivate readonly _stateTracker = signal<StateTracker | null>(null);\n\t/** Sentinel value to differentiate \"never checked\" from \"control is null\". */\n\tprivate _lastControl: AbstractControl | null = null;\n\n\t/** Gets the AbstractControlDirective for this control. */\n\tpublic ngControl: NgControl | null = null;\n\n\tpublic readonly id = signal<string | null | undefined>(undefined);\n\n\tpublic readonly controlState = computed(() => this._stateTracker()?.controlState() ?? null);\n\tpublic readonly errors = computed(() => this._stateTracker()?.errors() ?? null);\n\tpublic readonly dirty = computed(() => this._stateTracker()?.dirty() ?? null);\n\tpublic readonly invalid = computed(() => this._stateTracker()?.invalid() ?? null);\n\tpublic readonly spartanInvalid = computed(() => this._stateTracker()?.spartanInvalid() ?? null);\n\tpublic readonly touched = computed(() => this._stateTracker()?.touched() ?? null);\n\n\tconstructor() {\n\t\tthis._destroyRef.onDestroy(() => {\n\t\t\tthis._idEffectRef?.destroy();\n\t\t\tthis._stateTracker()?.destroy();\n\t\t});\n\t}\n\n\tngOnInit(): void {\n\t\t// `self` prevents descendants (e.g. calendar selects) from inheriting an ancestor's NgControl.\n\t\tthis.ngControl = this._injector.get(NgControl, null, { optional: true, self: true });\n\n\t\t// Only register with BrnField when this control has its own NgControl, otherwise descendant\n\t\t// field controls rendered in portals (e.g. calendar selects) overwrite the real control's registration.\n\t\tif (this.ngControl) {\n\t\t\tthis._field?.registerFieldControl(this);\n\t\t}\n\n\t\t// Try to sync the tracker eagerly.\n\t\tthis._syncTracker();\n\n\t\t// For template-driven forms and FormControlName, the control is often resolved\n\t\t// asynchronously by Angular's directives after our ngOnInit/ngDoCheck.\n\t\t// Schedule a one-time microtask to catch the initial resolution.\n\t\tif (this.ngControl) {\n\t\t\tPromise.resolve().then(() => {\n\t\t\t\tthis._syncTracker();\n\t\t\t});\n\t\t}\n\n\t\tconst labelable = this._injector.get(BrnLabelable, null);\n\t\tif (labelable) {\n\t\t\tthis._idEffectRef = effect(\n\t\t\t\t() => {\n\t\t\t\t\tthis.id.set(labelable.labelableId());\n\t\t\t\t},\n\t\t\t\t{ injector: this._injector },\n\t\t\t);\n\t\t}\n\t}\n\n\t// Re-evaluate the control reference on every change detection cycle because\n\t// the underlying AbstractControl may change when [formControl] rebinds to a new instance.\n\t// When the instance changes we tear down the old tracker and create a fresh one.\n\tngDoCheck(): void {\n\t\tthis._syncTracker();\n\t}\n\n\t/** @returns true if the control reference changed */\n\tprivate _syncTracker(): void {\n\t\tif (!this.ngControl) return;\n\t\tconst currentControl = this.ngControl.control ?? null;\n\t\tif (currentControl === this._lastControl) return;\n\t\tthis._lastControl = currentControl;\n\t\tthis._stateTracker()?.destroy();\n\t\tthis._stateTracker.set(\n\t\t\tcurrentControl\n\t\t\t\t? createStateTracker(this.ngControl, this._errorStateMatcher, this._parentFormGroup, this._parentForm)\n\t\t\t\t: null,\n\t\t);\n\t}\n}\n","import { computed, Directive, inject, input } from '@angular/core';\nimport { BrnFieldA11yService } from './brn-field-aria.service';\n\n@Directive({\n\tselector: '[brnFieldControlDescribedBy]',\n\thost: {\n\t\t'[attr.aria-describedby]': '_computedDescribedBy()',\n\t},\n})\nexport class BrnFieldControlDescribedBy {\n\tpublic readonly describedBy = input<string | null>(null, { alias: 'aria-describedby' });\n\tprivate readonly _a11y = inject(BrnFieldA11yService, { optional: true });\n\n\tprotected readonly _computedDescribedBy = computed(() => {\n\t\tconst manual = this.describedBy();\n\t\tconst manualList = manual ? manual.split(/\\s+/).filter(Boolean) : [];\n\t\tconst fieldIds = this._a11y?.describedBy() ?? null;\n\t\tconst fieldList = fieldIds ? fieldIds.split(/\\s+/).filter(Boolean) : [];\n\n\t\tconst combined = [...new Set([...manualList, ...fieldList])];\n\t\treturn combined.length ? combined.join(' ') : null;\n\t});\n}\n","import { BrnField } from './lib/brn-field';\nimport { BrnFieldControl } from './lib/brn-field-control';\nimport { BrnFieldControlDescribedBy } from './lib/brn-field-control-described-by';\n\nexport * from './lib/brn-field';\nexport * from './lib/brn-field-aria.service';\nexport * from './lib/brn-field-control';\nexport * from './lib/brn-field-control-described-by';\nexport * from './lib/brn-labelable';\n\nexport const BrnFieldImports = [BrnField, BrnFieldControl, BrnFieldControlDescribedBy] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -3,9 +3,9 @@ import { Directionality } from '@angular/cdk/bidi';
3
3
  import * as i0 from '@angular/core';
4
4
  import { inject, NgZone, Renderer2, signal, Injectable, InjectionToken, ElementRef, input, computed, contentChild, Directive, booleanAttribute, model, contentChildren, effect, TemplateRef, untracked, PLATFORM_ID, ViewContainerRef } from '@angular/core';
5
5
  import { toObservable, toSignal } from '@angular/core/rxjs-interop';
6
- import { createHoverObservable, waitForElementAnimations, createMenuPosition, computedPrevious, isElement } from '@spartan-ng/brain/core';
6
+ import { createHoverObservable, waitForElementAnimations, createMenuPosition, injectSkipDelay, computedPrevious, isElement } from '@spartan-ng/brain/core';
7
7
  import { Subject, BehaviorSubject, of, fromEvent, merge, combineLatest } from 'rxjs';
8
- import { switchMap, share, takeUntil, map, pairwise, filter, startWith, debounceTime, delay, tap, distinctUntilChanged } from 'rxjs/operators';
8
+ import { switchMap, share, map, takeUntil, pairwise, filter, startWith, debounceTime, delay, tap, distinctUntilChanged } from 'rxjs/operators';
9
9
  import * as i1 from '@spartan-ng/brain/button';
10
10
  import { BrnButton } from '@spartan-ng/brain/button';
11
11
  import { hasModifierKey } from '@angular/cdk/keycodes';
@@ -51,19 +51,12 @@ class BrnNavigationMenuContentService {
51
51
  _contentEl = signal(undefined, ...(ngDevMode ? [{ debugName: "_contentEl" }] : /* istanbul ignore next */ []));
52
52
  contentEl = this._contentEl.asReadonly();
53
53
  _overlayHoveredObservables$ = new BehaviorSubject(undefined);
54
- _overlayShiftTabObservables$ = new BehaviorSubject(undefined);
54
+ _overlayTabObservables$ = new BehaviorSubject(undefined);
55
55
  _overlayEscapeObservables$ = new BehaviorSubject(undefined);
56
56
  hovered$ = this._overlayHoveredObservables$.pipe(switchMap((overlayHoveredObservable) => (overlayHoveredObservable !== undefined ? overlayHoveredObservable : of())), share());
57
- _shiftTabPressed$ = this._overlayShiftTabObservables$.pipe(switchMap((contentFocused$) => (contentFocused$ !== undefined ? contentFocused$ : of())));
57
+ /** Emits `Tab`/`Shift+Tab` keydowns bubbling up the content panel so the trigger can manage focus. */
58
+ tabPressed$ = this._overlayTabObservables$.pipe(switchMap((tab$) => (tab$ !== undefined ? tab$ : of())));
58
59
  escapePressed$ = this._overlayEscapeObservables$.pipe(switchMap((contentFocused$) => (contentFocused$ !== undefined ? contentFocused$ : of())));
59
- constructor() {
60
- this._shiftTabPressed$.pipe(takeUntil(this._destroyed$)).subscribe((e) => {
61
- if (this._config.attachTo?.nativeElement) {
62
- e.preventDefault();
63
- this._config.attachTo.nativeElement.focus();
64
- }
65
- });
66
- }
67
60
  setConfig(config) {
68
61
  this._config = config;
69
62
  if (config.attachTo) {
@@ -127,7 +120,9 @@ class BrnNavigationMenuContentService {
127
120
  }
128
121
  this._contentEl.set(contentEl);
129
122
  this._overlayHoveredObservables$.next(createHoverObservable(this._overlayRef.hostElement, this._zone, this._destroyed$).pipe(map((e) => e.hover)));
130
- this._overlayShiftTabObservables$.next(fromEvent(contentEl, 'keydown').pipe(switchMap((e) => (e.key === 'Tab' && e.shiftKey && e.target === this.contentEl() ? of(e) : of())), takeUntil(this._destroyed$)));
123
+ this._overlayTabObservables$.next(fromEvent(contentEl, 'keydown').pipe(
124
+ // Forward Tab/Shift+Tab without meta modifiers; the trigger decides how to keep focus in the menu
125
+ switchMap((e) => (e.key === 'Tab' && !e.altKey && !e.ctrlKey && !e.metaKey ? of(e) : of())), takeUntil(this._destroyed$)));
131
126
  this._overlayEscapeObservables$.next(fromEvent(contentEl, 'keydown').pipe(switchMap((e) => (e.key === 'Escape' && !hasModifierKey(e) ? of(e) : of())), takeUntil(this._destroyed$)));
132
127
  }
133
128
  async hide() {
@@ -177,7 +172,7 @@ class BrnNavigationMenuContentService {
177
172
  }
178
173
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: BrnNavigationMenuContentService, decorators: [{
179
174
  type: Injectable
180
- }], ctorParameters: () => [] });
175
+ }] });
181
176
 
182
177
  const BrnNavigationMenuFocusable = new InjectionToken('BrnNavigationMenuFocusable');
183
178
  function provideBrnNavigationMenuFocusable(focusable) {
@@ -348,9 +343,8 @@ class BrnNavigationMenu {
348
343
  * The orientation of the menu.
349
344
  */
350
345
  orientation = input('horizontal', ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
351
- _isOpenDelayed = signal(true, ...(ngDevMode ? [{ debugName: "_isOpenDelayed" }] : /* istanbul ignore next */ []));
352
- isOpenDelayed = this._isOpenDelayed.asReadonly();
353
- _skipDelayTimerRef;
346
+ _skipDelay = injectSkipDelay(() => this.skipDelayDuration());
347
+ isOpenDelayed = this._skipDelay.isOpenDelayed;
354
348
  _navAndSubnavMenuItems = contentChildren(BrnNavigationMenuItem, { ...(ngDevMode ? { debugName: "_navAndSubnavMenuItems" } : /* istanbul ignore next */ {}), descendants: true });
355
349
  menuItems = computed(() => this._navAndSubnavMenuItems().filter((mi) => mi.navMenuElRef === this.el), { ...(ngDevMode ? { debugName: "menuItems" } : /* istanbul ignore next */ {}), equal: areArraysSameByElRef });
356
350
  menuItemIds = computed(() => this.menuItems().map((mi) => mi.id()), ...(ngDevMode ? [{ debugName: "menuItemIds" }] : /* istanbul ignore next */ []));
@@ -371,19 +365,10 @@ class BrnNavigationMenu {
371
365
  context = computed(() => ({ orientation: this.orientation(), dir: this.direction() }), ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
372
366
  constructor() {
373
367
  effect(() => {
374
- const isOpen = this.value() !== undefined;
375
- const hasSkipDelayDuration = this.skipDelayDuration() > 0;
376
- if (isOpen) {
377
- clearTimeout(this._skipDelayTimerRef);
378
- if (hasSkipDelayDuration)
379
- this._isOpenDelayed.set(false);
380
- }
381
- else {
382
- clearTimeout(this._skipDelayTimerRef);
383
- this._skipDelayTimerRef = setTimeout(() => {
384
- this._isOpenDelayed.set(true);
385
- }, this.skipDelayDuration());
386
- }
368
+ if (this.value() !== undefined)
369
+ this._skipDelay.open();
370
+ else
371
+ this._skipDelay.close();
387
372
  });
388
373
  combineLatest([this._hovered$, this._contentHovered$, this._anyTriggerHovered$])
389
374
  .pipe(debounceTime(0), filter(([hovered, contentHovered]) => !(hovered === undefined && contentHovered === undefined)), switchMap(([hovered, contentHovered, triggerHovered]) => {
@@ -413,13 +398,27 @@ class BrnNavigationMenu {
413
398
  setActiveItem(item) {
414
399
  this._keyManager().setActiveItem(item);
415
400
  }
401
+ /** Focuses the sibling trigger/link `delta` steps from `current`, skipping disabled ones. @internal */
402
+ focusSibling(current, delta) {
403
+ const items = this._triggersAndLinks();
404
+ const idx = items.indexOf(current);
405
+ if (idx === -1)
406
+ return false;
407
+ for (let i = idx + delta; i >= 0 && i < items.length; i += delta) {
408
+ const candidate = items[i];
409
+ if (!candidate.disabled) {
410
+ this._keyManager().setActiveItem(candidate);
411
+ return true;
412
+ }
413
+ }
414
+ return false;
415
+ }
416
416
  handleKeydown(event) {
417
417
  this._keyManager().onKeydown(event);
418
418
  }
419
419
  ngOnDestroy() {
420
420
  this._destroy$.next();
421
421
  this._destroy$.complete();
422
- clearTimeout(this._skipDelayTimerRef);
423
422
  }
424
423
  /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: BrnNavigationMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive });
425
424
  /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.2.16", type: BrnNavigationMenu, isStandalone: true, selector: "nav[brnNavigationMenu]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, delayDuration: { classPropertyName: "delayDuration", publicName: "delayDuration", isSignal: true, isRequired: false, transformFunction: null }, skipDelayDuration: { classPropertyName: "skipDelayDuration", publicName: "skipDelayDuration", isSignal: true, isRequired: false, transformFunction: null }, openOn: { classPropertyName: "openOn", publicName: "openOn", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "aria-label": "Main", "data-slot": "navigation-menu" }, listeners: { "keydown": "handleKeydown($event)" }, properties: { "attr.data-orientation": "orientation()", "attr.dir": "direction()" } }, providers: [provideBrnNavigationMenu(BrnNavigationMenu)], queries: [{ propertyName: "_navAndSubnavMenuItems", predicate: BrnNavigationMenuItem, descendants: true, isSignal: true }], ngImport: i0 });
@@ -536,6 +535,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
536
535
  }]
537
536
  }] });
538
537
 
538
+ /** Returns the tabbable elements inside `container`, in DOM order. Ported from Radix's navigation menu. */
539
+ function getTabbableCandidates(container) {
540
+ const nodes = [];
541
+ // `.tabIndex` reflects the runtime's view of tabbability, so it covers links/buttons/[tabindex] alike.
542
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, {
543
+ acceptNode: (node) => {
544
+ const el = node;
545
+ const isHiddenInput = el.tagName === 'INPUT' && el.type === 'hidden';
546
+ if (el.disabled || el.hidden || isHiddenInput)
547
+ return NodeFilter.FILTER_SKIP;
548
+ return el.tabIndex >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
549
+ },
550
+ });
551
+ while (walker.nextNode())
552
+ nodes.push(walker.currentNode);
553
+ return nodes;
554
+ }
555
+ /** Focuses the first candidate that can take focus; returns whether focus landed on a candidate. */
556
+ function focusFirst(candidates) {
557
+ const previouslyFocused = document.activeElement;
558
+ return candidates.some((candidate) => {
559
+ // already where we want to be - stop walking
560
+ if (candidate === previouslyFocused)
561
+ return true;
562
+ candidate.focus();
563
+ return document.activeElement !== previouslyFocused;
564
+ });
565
+ }
566
+
539
567
  class BrnNavigationMenuTrigger {
540
568
  static _id = 0;
541
569
  _isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
@@ -550,6 +578,8 @@ class BrnNavigationMenuTrigger {
550
578
  _id = `brn-navigation-menu-trigger-${++BrnNavigationMenuTrigger._id}`;
551
579
  _parentNavMenu = this._navigationMenu.parentNavMenu;
552
580
  _isActive = this._navigationMenuItem.isActive;
581
+ // Only reference the content panel while it is open, matching Radix's aria-controls behaviour.
582
+ _ariaControls = computed(() => (this._isActive() ? this._contentId : null), ...(ngDevMode ? [{ debugName: "_ariaControls" }] : /* istanbul ignore next */ []));
553
583
  _contentId = this._contentService.id;
554
584
  _state = this._navigationMenuItem.state;
555
585
  _dir = computed(() => this._navigationMenu.context().dir, ...(ngDevMode ? [{ debugName: "_dir" }] : /* istanbul ignore next */ []));
@@ -654,6 +684,7 @@ class BrnNavigationMenuTrigger {
654
684
  this._el.nativeElement.focus();
655
685
  this._deactivate();
656
686
  });
687
+ this._contentService.tabPressed$.pipe(takeUntil(this._destroy$)).subscribe((e) => this._handleContentTab(e));
657
688
  }
658
689
  ngOnDestroy() {
659
690
  this._destroy$.next();
@@ -671,13 +702,69 @@ class BrnNavigationMenuTrigger {
671
702
  const contentEl = this._contentService.contentEl();
672
703
  if (contentEl && !hasModifierKey(e)) {
673
704
  e.preventDefault();
674
- contentEl.focus();
705
+ this._focusFirstContent();
675
706
  }
676
707
  }
708
+ /** Arrow-key entry into the open content: ArrowDown (horizontal) or ArrowRight/Left (vertical, dir-aware). */
709
+ onEntryKey(e, orientation, dir) {
710
+ if (!this._navigationMenuItem.isActive())
711
+ return;
712
+ if (this._orientation() !== orientation)
713
+ return;
714
+ if (orientation === 'vertical' && dir && this._dir() !== dir)
715
+ return;
716
+ e.preventDefault();
717
+ // Stop the event from reaching the nav's CDK key manager, which would otherwise
718
+ // read event.keyCode and move focus to the next trigger, overriding the entry.
719
+ e.stopPropagation();
720
+ this._focusFirstContent();
721
+ }
677
722
  onEscape(e) {
678
723
  e.preventDefault();
679
724
  this._deactivate();
680
725
  }
726
+ _focusFirstContent() {
727
+ const contentEl = this._contentService.contentEl();
728
+ if (!contentEl)
729
+ return;
730
+ const candidates = getTabbableCandidates(contentEl);
731
+ if (candidates.length) {
732
+ focusFirst(candidates);
733
+ }
734
+ else {
735
+ contentEl.focus();
736
+ }
737
+ }
738
+ /** Keeps keyboard focus inside the menu when tabbing across the content edges (issue #1484). */
739
+ _handleContentTab(event) {
740
+ const contentEl = this._contentService.contentEl();
741
+ if (!contentEl)
742
+ return;
743
+ const candidates = getTabbableCandidates(contentEl);
744
+ const focused = document.activeElement;
745
+ const index = focused ? candidates.indexOf(focused) : -1;
746
+ if (event.shiftKey) {
747
+ const previous = index > 0 ? candidates.slice(0, index).reverse() : [];
748
+ if (previous.length && focusFirst(previous)) {
749
+ event.preventDefault();
750
+ return;
751
+ }
752
+ // at the first item or on the content container - return to the trigger, keep content open
753
+ event.preventDefault();
754
+ this._el.nativeElement.focus();
755
+ return;
756
+ }
757
+ const next = index >= 0 ? candidates.slice(index + 1) : candidates;
758
+ if (next.length && focusFirst(next)) {
759
+ event.preventDefault();
760
+ return;
761
+ }
762
+ // past the last item - hand focus to the next top-level item and close the panel
763
+ if (this._navigationMenu.focusSibling(this, 1)) {
764
+ event.preventDefault();
765
+ }
766
+ this._deactivate();
767
+ }
681
768
  _activate() {
682
769
  this._navigationMenu.value.set(this._navigationMenuItem.id());
683
770
  }
@@ -696,7 +783,7 @@ class BrnNavigationMenuTrigger {
696
783
  .find((ref) => ref.el.nativeElement === node || ref.el.nativeElement.contains(node));
697
784
  }
698
785
  /** @nocollapse */ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: BrnNavigationMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
699
- /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.16", type: BrnNavigationMenuTrigger, isStandalone: true, selector: "button[brnNavigationMenuTrigger]", inputs: { align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "navigation-menu-trigger" }, listeners: { "keydown.escape": "onEscape($event)", "keydown.tab": "onTab($event)", "focus": "handleFocus()" }, properties: { "id": "_id", "attr.data-state": "_state()", "attr.aria-expanded": "_isActive()", "attr.aria-controls": "_contentId" } }, providers: [provideBrnNavigationMenuFocusable(BrnNavigationMenuTrigger)], hostDirectives: [{ directive: i1.BrnButton, inputs: ["disabled", "disabled"] }], ngImport: i0 });
786
+ /** @nocollapse */ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.16", type: BrnNavigationMenuTrigger, isStandalone: true, selector: "button[brnNavigationMenuTrigger]", inputs: { align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "navigation-menu-trigger" }, listeners: { "keydown.escape": "onEscape($event)", "keydown.tab": "onTab($event)", "keydown.arrowDown": "onEntryKey($event, \"horizontal\")", "keydown.arrowRight": "onEntryKey($event, \"vertical\", \"ltr\")", "keydown.arrowLeft": "onEntryKey($event, \"vertical\", \"rtl\")", "focus": "handleFocus()" }, properties: { "id": "_id", "attr.data-state": "_state()", "attr.aria-expanded": "_isActive()", "attr.aria-controls": "_ariaControls()" } }, providers: [provideBrnNavigationMenuFocusable(BrnNavigationMenuTrigger)], hostDirectives: [{ directive: i1.BrnButton, inputs: ["disabled", "disabled"] }], ngImport: i0 });
700
787
  }
701
788
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: BrnNavigationMenuTrigger, decorators: [{
702
789
  type: Directive,
@@ -712,11 +799,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
712
799
  host: {
713
800
  '(keydown.escape)': 'onEscape($event)',
714
801
  '(keydown.tab)': 'onTab($event)',
802
+ '(keydown.arrowDown)': 'onEntryKey($event, "horizontal")',
803
+ '(keydown.arrowRight)': 'onEntryKey($event, "vertical", "ltr")',
804
+ '(keydown.arrowLeft)': 'onEntryKey($event, "vertical", "rtl")',
715
805
  '(focus)': 'handleFocus()',
716
806
  '[id]': '_id',
717
807
  '[attr.data-state]': '_state()',
718
808
  '[attr.aria-expanded]': '_isActive()',
719
- '[attr.aria-controls]': '_contentId',
809
+ '[attr.aria-controls]': '_ariaControls()',
720
810
  'data-slot': 'navigation-menu-trigger',
721
811
  },
722
812
  }]